From db27263767669577f7d17ef2968f70e7d0041932 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Sep 2025 22:06:08 +0000 Subject: [PATCH 1/2] Initial plan From 3e5b2c9de9e41e3d1c1f022b439c315eb3dd582a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 28 Sep 2025 22:16:16 +0000 Subject: [PATCH 2/2] Complete OSBAPI 2.17 gap analysis with implementation snippets Co-authored-by: eruvanos <9437863+eruvanos@users.noreply.github.com> --- MISSING_FEATURES_SNIPPETS.md | 390 +++++++++++++++++++++++++++++++++++ OSBAPI_2_17_ANALYSIS.md | 363 ++++++++++++++++++++++++++++++++ TODO.md | 48 +++-- 3 files changed, 786 insertions(+), 15 deletions(-) create mode 100644 MISSING_FEATURES_SNIPPETS.md create mode 100644 OSBAPI_2_17_ANALYSIS.md diff --git a/MISSING_FEATURES_SNIPPETS.md b/MISSING_FEATURES_SNIPPETS.md new file mode 100644 index 0000000..c73deab --- /dev/null +++ b/MISSING_FEATURES_SNIPPETS.md @@ -0,0 +1,390 @@ +# OSBAPI 2.17 Missing Features - Implementation Snippets + +## 1. Service Binding Rotation Support + +### ServicePlan.binding_rotatable field +```python +# In openbrokerapi/catalog.py ServicePlan class +class ServicePlan: + def __init__( + self, + id: str, + name: str, + description: str, + metadata: Optional[ServicePlanMetadata] = None, + free: Optional[bool] = None, + bindable: Optional[bool] = None, + binding_rotatable: Optional[bool] = None, # NEW: Supports binding rotation + schemas: Optional[Schemas] = None, + **kwargs, + ): + # ... existing fields ... + self.binding_rotatable = binding_rotatable +``` + +### BindDetails.predecessor_binding_id field +```python +# In openbrokerapi/service_broker.py BindDetails class +class BindDetails: + def __init__( + self, + service_id: str, + plan_id: str, + app_guid: Optional[str] = None, + bind_resource: Optional[dict] = None, + context: Optional[dict] = None, + parameters: Optional[dict] = None, + predecessor_binding_id: Optional[str] = None, # NEW: For binding rotation + **kwargs, + ): + # ... existing initialization ... + self.predecessor_binding_id = predecessor_binding_id +``` + +### BindingMetadata class +```python +# New class in openbrokerapi/service_broker.py +class BindingMetadata: + def __init__( + self, + expires_at: Optional[str] = None, + renew_before: Optional[str] = None, + **kwargs + ): + self.expires_at = expires_at + self.renew_before = renew_before + self.__dict__.update(kwargs) +``` + +## 2. Maintenance Info Object + +### MaintenanceInfo class +```python +# New class in openbrokerapi/catalog.py +class MaintenanceInfo: + def __init__( + self, + version: str, + description: Optional[str] = None, + **kwargs + ): + self.version = version + self.description = description + self.__dict__.update(kwargs) +``` + +### ServicePlan.maintenance_info field +```python +# In openbrokerapi/catalog.py ServicePlan class +class ServicePlan: + def __init__( + self, + # ... existing fields ... + maintenance_info: Optional[MaintenanceInfo] = None, # NEW + **kwargs, + ): + # ... existing initialization ... + self.maintenance_info = maintenance_info +``` + +### ProvisionDetails.maintenance_info field +```python +# In openbrokerapi/service_broker.py ProvisionDetails class +class ProvisionDetails: + def __init__( + self, + service_id: str, + plan_id: str, + organization_guid: Optional[str] = None, + space_guid: Optional[str] = None, + parameters: Optional[dict] = None, + context: Optional[dict] = None, + maintenance_info: Optional[MaintenanceInfo] = None, # NEW + **kwargs, + ): + # ... existing initialization ... + self.maintenance_info = maintenance_info +``` + +### UpdateDetails.maintenance_info field +```python +# In openbrokerapi/service_broker.py UpdateDetails class +class UpdateDetails: + def __init__( + self, + service_id: str, + plan_id: Optional[str] = None, + parameters: Optional[dict] = None, + previous_values: Optional[dict] = None, + context: Optional[dict] = None, + maintenance_info: Optional[MaintenanceInfo] = None, # NEW + **kwargs, + ): + # ... existing initialization ... + self.maintenance_info = maintenance_info +``` + +### PreviousValues.maintenance_info field +```python +# In openbrokerapi/service_broker.py PreviousValues class +class PreviousValues: + def __init__( + self, + plan_id: Optional[str] = None, + service_id: Optional[str] = None, + organization_id: Optional[str] = None, + space_id: Optional[str] = None, + maintenance_info: Optional[MaintenanceInfo] = None, # NEW + **kwargs, + ): + # ... existing initialization ... + self.maintenance_info = maintenance_info +``` + +## 3. Service Instance Metadata + +### ServiceInstanceMetadata class +```python +# New class in openbrokerapi/service_broker.py or response.py +class ServiceInstanceMetadata: + def __init__( + self, + labels: Optional[dict] = None, + attributes: Optional[dict] = None, + **kwargs + ): + self.labels = labels + self.attributes = attributes + self.__dict__.update(kwargs) +``` + +### ProvisioningResponse.metadata field +```python +# In openbrokerapi/response.py ProvisioningResponse class +class ProvisioningResponse(AsyncResponse): + def __init__( + self, + dashboard_url: str, + operation: str, + metadata: Optional[ServiceInstanceMetadata] = None, # NEW + ): + self.dashboard_url = dashboard_url + self.operation = operation + self.metadata = metadata +``` + +### GetInstanceResponse.metadata field +```python +# In openbrokerapi/response.py GetInstanceResponse class +class GetInstanceResponse: + def __init__( + self, + service_id: str, + plan_id: str, + dashboard_url: Optional[str] = None, + parameters: Optional[dict] = None, + maintenance_info: Optional[MaintenanceInfo] = None, # NEW + metadata: Optional[ServiceInstanceMetadata] = None, # NEW + ): + # ... existing initialization ... + self.maintenance_info = maintenance_info + self.metadata = metadata +``` + +## 4. Network Endpoints + +### Endpoint class +```python +# New class in openbrokerapi/service_broker.py +class Endpoint: + def __init__( + self, + host: str, + ports: List[str], + protocol: Optional[str] = "tcp", + **kwargs + ): + self.host = host + self.ports = ports + self.protocol = protocol + self.__dict__.update(kwargs) +``` + +### BindResponse.endpoints field +```python +# In openbrokerapi/response.py BindResponse class +class BindResponse: + def __init__( + self, + credentials: Optional[dict] = None, + syslog_drain_url: Optional[str] = None, + route_service_url: Optional[str] = None, + volume_mounts: Optional[List[VolumeMount]] = None, + operation: Optional[str] = None, + metadata: Optional[BindingMetadata] = None, # NEW + endpoints: Optional[List[Endpoint]] = None, # NEW + ): + # ... existing initialization ... + self.metadata = metadata + self.endpoints = endpoints +``` + +### GetBindingResponse.endpoints field +```python +# In openbrokerapi/response.py GetBindingResponse class +class GetBindingResponse: + def __init__( + self, + credentials: Optional[dict] = None, + syslog_drain_url: Optional[str] = None, + route_service_url: Optional[str] = None, + volume_mounts: Optional[List[VolumeMount]] = None, + parameters: Optional[dict] = None, + metadata: Optional[BindingMetadata] = None, # NEW + endpoints: Optional[List[Endpoint]] = None, # NEW + ): + # ... existing initialization ... + self.metadata = metadata + self.endpoints = endpoints +``` + +## 5. Service Context Updates + +### Service.allow_context_updates field +```python +# In openbrokerapi/service_broker.py Service class +class Service: + def __init__( + self, + id: str, + name: str, + description: str, + bindable: bool, + plans: List[ServicePlan], + tags: Optional[List[str]] = None, + requires: Optional[List[str]] = None, + metadata: Optional[ServiceMetadata] = None, + dashboard_client: Optional[ServiceDashboardClient] = None, + plan_updateable: bool = False, + instances_retrievable: bool = False, + bindings_retrievable: bool = False, + allow_context_updates: Optional[bool] = None, # NEW + **kwargs, + ): + # ... existing initialization ... + self.allow_context_updates = allow_context_updates +``` + +## 6. Error Codes + +### MaintenanceInfoConflict error +```python +# In openbrokerapi/errors.py +class ErrMaintenanceInfoConflict(ServiceException): + def __init__(self): + super().__init__("MaintenanceInfoConflict") +``` + +## 7. Polling Duration + +### ServicePlan.maximum_polling_duration field +```python +# In openbrokerapi/catalog.py ServicePlan class +class ServicePlan: + def __init__( + self, + # ... existing fields ... + maximum_polling_duration: Optional[int] = None, # NEW: Duration in seconds + **kwargs, + ): + # ... existing initialization ... + self.maximum_polling_duration = maximum_polling_duration +``` + +## 8. Retry-After Headers + +### LastOperationResponse with retry timing +```python +# In openbrokerapi/response.py LastOperationResponse class +class LastOperationResponse: + def __init__( + self, + state: OperationState, + description: str, + retry_after: Optional[int] = None, # NEW: Retry-After header value + instance_usable: Optional[bool] = None, + update_repeatable: Optional[bool] = None, + ): + self.state = state.value + self.description = description + self.retry_after = retry_after + self.instance_usable = instance_usable + self.update_repeatable = update_repeatable +``` + +## Usage Examples + +### Service Binding Rotation Example +```python +# Creating a service plan that supports binding rotation +plan = ServicePlan( + id="rotatable-plan", + name="Rotatable Plan", + description="A plan that supports credential rotation", + binding_rotatable=True, # Enable rotation + maintenance_info=MaintenanceInfo( + version="1.0.0", + description="Initial version" + ) +) + +# Creating a successor binding during rotation +bind_details = BindDetails( + service_id="service-123", + plan_id="rotatable-plan", + predecessor_binding_id="old-binding-456" # Rotating from this binding +) +``` + +### Service with Maintenance Info Example +```python +# Service with maintenance support +service = Service( + id="maintenance-service", + name="Maintenance Service", + description="Service supporting maintenance operations", + bindable=True, + allow_context_updates=True, # Allow context updates + plans=[ + ServicePlan( + id="plan-1", + name="Standard", + description="Standard plan with maintenance", + maintenance_info=MaintenanceInfo( + version="2.1.0", + description="Latest stable version" + ), + maximum_polling_duration=300 # 5 minutes max polling + ) + ] +) +``` + +### Binding with Expiration Metadata Example +```python +# Binding response with expiration information +binding_response = BindResponse( + credentials={"username": "user", "password": "pass"}, + metadata=BindingMetadata( + expires_at="2024-12-31T23:59:59.0Z", + renew_before="2024-12-30T23:59:59.0Z" + ), + endpoints=[ + Endpoint( + host="database.example.com", + ports=["5432"], + protocol="tcp" + ) + ] +) +``` \ No newline at end of file diff --git a/OSBAPI_2_17_ANALYSIS.md b/OSBAPI_2_17_ANALYSIS.md new file mode 100644 index 0000000..a2cd5cc --- /dev/null +++ b/OSBAPI_2_17_ANALYSIS.md @@ -0,0 +1,363 @@ +# Open Service Broker API 2.17 - Missing Features Analysis + +This document identifies the differences between the current openbrokerapi implementation (supporting 2.13+) and the OSBAPI 2.17 specification. + +## Current Implementation Status +- **Minimum Supported Version**: 2.13 +- **Library Version**: 4.7.3 +- **Last Updated**: Based on code analysis, implementation seems to support up to ~2.14-2.15 features + +## Missing Features from OSBAPI 2.17 + +### 1. Service Plan Object - New Fields + +#### binding_rotatable +**Status**: ❌ Missing +**Spec Reference**: Service Plan Object +**Description**: Boolean field indicating whether a Service Binding of that Plan supports Service Binding rotation. + +```python +# Missing from openbrokerapi/catalog.py ServicePlan class +class ServicePlan: + def __init__( + self, + # ... existing fields ... + binding_rotatable: Optional[bool] = None, # MISSING + # ... + ): + # ... existing init code ... + self.binding_rotatable = binding_rotatable # MISSING +``` + +#### maximum_polling_duration +**Status**: ❌ Missing +**Spec Reference**: Service Plan Object +**Description**: A duration, in seconds, that the Platform SHOULD use as the Service's maximum polling duration. + +```python +# Missing from openbrokerapi/catalog.py ServicePlan class +class ServicePlan: + def __init__( + self, + # ... existing fields ... + maximum_polling_duration: Optional[int] = None, # MISSING + # ... + ): + # ... existing init code ... + self.maximum_polling_duration = maximum_polling_duration # MISSING +``` + +#### maintenance_info +**Status**: ❌ Missing +**Spec Reference**: Service Plan Object, Maintenance Info Object +**Description**: Maintenance information for a Service Instance which is provisioned using the Service Plan. + +```python +# Missing from openbrokerapi/catalog.py +class MaintenanceInfo: + def __init__( + self, + version: str, + description: Optional[str] = None, + **kwargs + ): + self.version = version + self.description = description + self.__dict__.update(kwargs) + +# Missing from ServicePlan class +class ServicePlan: + def __init__( + self, + # ... existing fields ... + maintenance_info: Optional[MaintenanceInfo] = None, # MISSING + # ... + ): + # ... existing init code ... + self.maintenance_info = maintenance_info # MISSING +``` + +### 2. Service Object - New Fields + +#### allow_context_updates +**Status**: ❌ Missing +**Spec Reference**: Service Offering Object +**Description**: Specifies whether a Service Instance supports Update requests when contextual data for the Service Instance in the Platform changes. + +```python +# Missing from openbrokerapi/service_broker.py Service class +class Service: + def __init__( + self, + # ... existing fields ... + allow_context_updates: Optional[bool] = None, # MISSING + # ... + ): + # ... existing init code ... + self.allow_context_updates = allow_context_updates # MISSING +``` + +### 3. Binding - Service Binding Rotation + +#### predecessor_binding_id +**Status**: ❌ Missing +**Spec Reference**: Binding Request (Rotating a Service Binding) +**Description**: Field for creating successor bindings in Service Binding rotation. + +```python +# Missing from openbrokerapi/service_broker.py BindDetails class +class BindDetails: + def __init__( + self, + # ... existing fields ... + predecessor_binding_id: Optional[str] = None, # MISSING + # ... + ): + # ... existing init code ... + self.predecessor_binding_id = predecessor_binding_id # MISSING +``` + +### 4. Binding Metadata Object + +#### metadata field with expires_at and renew_before +**Status**: ❌ Missing +**Spec Reference**: Binding Response, Binding Metadata Object +**Description**: Metadata for Service Bindings including expiration information. + +```python +# Missing from openbrokerapi/service_broker.py or response.py +class BindingMetadata: + def __init__( + self, + expires_at: Optional[str] = None, + renew_before: Optional[str] = None, + **kwargs + ): + self.expires_at = expires_at + self.renew_before = renew_before + self.__dict__.update(kwargs) + +# Missing from Binding and response classes +class Binding: + def __init__( + self, + # ... existing fields ... + metadata: Optional[BindingMetadata] = None, # MISSING + # ... + ): + # ... existing init code ... + self.metadata = metadata # MISSING +``` + +### 5. Endpoint Object + +#### endpoints array +**Status**: ❌ Missing +**Spec Reference**: Binding Response, Endpoint Object +**Description**: Network endpoints that the Application uses to connect to the Service Instance. + +```python +# Missing from openbrokerapi/service_broker.py +class Endpoint: + def __init__( + self, + host: str, + ports: List[str], + protocol: Optional[str] = "tcp", + **kwargs + ): + self.host = host + self.ports = ports + self.protocol = protocol + self.__dict__.update(kwargs) + +# Missing from Binding and response classes +class Binding: + def __init__( + self, + # ... existing fields ... + endpoints: Optional[List[Endpoint]] = None, # MISSING + # ... + ): + # ... existing init code ... + self.endpoints = endpoints # MISSING +``` + +### 6. Error Codes + +#### MaintenanceInfoConflict +**Status**: ❌ Missing +**Spec Reference**: Service Broker Errors +**Description**: Error code for when maintenance_info.version field provided in request doesn't match catalog. + +```python +# Missing from openbrokerapi/errors.py +class ErrMaintenanceInfoConflict(ServiceException): + def __init__(self): + super().__init__("MaintenanceInfoConflict") +``` + +### 7. HTTP Headers - Retry-After + +#### Retry-After header support +**Status**: ❌ Missing +**Spec Reference**: Polling Last Operation endpoints +**Description**: Support for Retry-After HTTP header in last operation responses. + +```python +# Missing from openbrokerapi/response.py LastOperationResponse +class LastOperationResponse: + def __init__( + self, + state: OperationState, + description: str, + retry_after: Optional[int] = None, # MISSING + # ... other fields ... + ): + # ... existing init code ... + self.retry_after = retry_after # MISSING +``` + +### 8. Provision Request - maintenance_info field + +#### maintenance_info in ProvisionDetails +**Status**: ❌ Missing +**Spec Reference**: Provisioning Request +**Description**: Maintenance information in provision requests. + +```python +# Missing from openbrokerapi/service_broker.py ProvisionDetails class +class ProvisionDetails: + def __init__( + self, + # ... existing fields ... + maintenance_info: Optional[dict] = None, # MISSING (should be MaintenanceInfo) + # ... + ): + # ... existing init code ... + self.maintenance_info = maintenance_info # MISSING +``` + +### 9. Update Request - maintenance_info field + +#### maintenance_info in UpdateDetails +**Status**: ❌ Missing +**Spec Reference**: Updating a Service Instance Request +**Description**: Maintenance information in update requests. + +```python +# Missing from openbrokerapi/service_broker.py UpdateDetails class +class UpdateDetails: + def __init__( + self, + # ... existing fields ... + maintenance_info: Optional[dict] = None, # MISSING (should be MaintenanceInfo) + # ... + ): + # ... existing init code ... + self.maintenance_info = maintenance_info # MISSING +``` + +### 10. Service Instance Metadata + +#### Service Instance metadata object +**Status**: ❌ Missing +**Spec Reference**: Provisioning Response, Update Response, Fetch Service Instance Response +**Description**: Metadata object for Service Instances with labels and attributes. + +```python +# Missing from openbrokerapi/service_broker.py or response.py +class ServiceInstanceMetadata: + def __init__( + self, + labels: Optional[dict] = None, + attributes: Optional[dict] = None, + **kwargs + ): + self.labels = labels + self.attributes = attributes + self.__dict__.update(kwargs) + +# Missing from response classes +class ProvisioningResponse: + def __init__( + self, + # ... existing fields ... + metadata: Optional[ServiceInstanceMetadata] = None, # MISSING + # ... + ): + # ... existing init code ... + self.metadata = metadata # MISSING +``` + +### 11. Previous Values Object - maintenance_info field + +#### maintenance_info in PreviousValues +**Status**: ❌ Missing +**Spec Reference**: Previous Values Object +**Description**: Maintenance information in previous values for update requests. + +```python +# Missing from openbrokerapi/service_broker.py PreviousValues class +class PreviousValues: + def __init__( + self, + # ... existing fields ... + maintenance_info: Optional[dict] = None, # MISSING (should be MaintenanceInfo) + # ... + ): + # ... existing init code ... + self.maintenance_info = maintenance_info # MISSING +``` + +### 12. Fetch Service Instance Response - maintenance_info field + +#### maintenance_info in GetInstanceDetailsSpec +**Status**: ❌ Missing +**Spec Reference**: Fetching a Service Instance Response +**Description**: Maintenance information in service instance fetch responses. + +```python +# Missing from openbrokerapi/service_broker.py GetInstanceDetailsSpec class +class GetInstanceDetailsSpec: + def __init__( + self, + # ... existing fields ... + maintenance_info: Optional[dict] = None, # MISSING (should be MaintenanceInfo) + metadata: Optional[ServiceInstanceMetadata] = None, # MISSING + # ... + ): + # ... existing init code ... + self.maintenance_info = maintenance_info # MISSING + self.metadata = metadata # MISSING +``` + +## Summary of Changes Needed + +### High Priority (Core 2.17 Features) +1. **Service Binding Rotation** - Add `binding_rotatable` and `predecessor_binding_id` +2. **Maintenance Info Object** - Complete implementation across all relevant classes +3. **Service Instance Metadata** - Add metadata support for service instances +4. **Binding Metadata** - Add metadata with expiration fields for bindings +5. **MaintenanceInfoConflict Error** - Add new error code + +### Medium Priority (Enhancement Features) +6. **Endpoints Array** - Add network endpoint information to bindings +7. **Allow Context Updates** - Add service-level flag for context update support +8. **Maximum Polling Duration** - Add per-plan polling duration limits +9. **Retry-After Headers** - Add retry timing headers to last operation responses + +### Implementation Notes +- Most missing features are additive and should not break backward compatibility +- The `maintenance_info` field appears in multiple places and needs a consistent `MaintenanceInfo` class +- Service binding rotation is a significant new feature that requires careful implementation +- All new fields should be optional to maintain backward compatibility +- Tests will need to be added for all new features + +### Files That Need Updates +- `openbrokerapi/catalog.py` - ServicePlan updates +- `openbrokerapi/service_broker.py` - Multiple class updates +- `openbrokerapi/response.py` - Response class updates +- `openbrokerapi/errors.py` - New error class +- `openbrokerapi/api.py` - Request handling updates +- Test files - Comprehensive test coverage for new features \ No newline at end of file diff --git a/TODO.md b/TODO.md index f69ac60..e0f28e8 100644 --- a/TODO.md +++ b/TODO.md @@ -1,16 +1,34 @@ -- 379: Catalog/Plan - plan_updateable -- 480: Catalog/Plan - maximum_polling_duration -- 481: Catalog/Plan - maintenance_info -- 481: Catalog/Plan - maintenance_info +# TODO - OSBAPI Spec Implementation -- 340: Error codes - MaintenanceInfoConflict -- 521f: Maintenance Info Object -- 541: Catalog - allow_context_updates -- 789: LastOperation - Retry-After Header -- 871: LastBindingOperation - Retry-After Header -- 931: Provision - maintenance_info -- 1073: Update - profile#cloud-foundry-context-object -- 1121: Update - maintenance_info -- 1428: Bind - Endpoints -- 1454: Bind - Endpoint Object -- 1545: FetchBind - Endpoint Object +## Status: Based on OSBAPI 2.17 Analysis + +### Existing TODO Items (Legacy) +- 379: Catalog/Plan - plan_updateable ✅ **IMPLEMENTED** +- 480: Catalog/Plan - maximum_polling_duration ❌ **MISSING in 2.17** +- 481: Catalog/Plan - maintenance_info ❌ **MISSING in 2.17** +- 481: Catalog/Plan - maintenance_info (duplicate) + +- 340: Error codes - MaintenanceInfoConflict ❌ **MISSING in 2.17** +- 521f: Maintenance Info Object ❌ **MISSING in 2.17** +- 541: Catalog - allow_context_updates ❌ **MISSING in 2.17** +- 789: LastOperation - Retry-After Header ❌ **MISSING in 2.17** +- 871: LastBindingOperation - Retry-After Header ❌ **MISSING in 2.17** +- 931: Provision - maintenance_info ❌ **MISSING in 2.17** +- 1073: Update - profile#cloud-foundry-context-object ❓ **NEEDS REVIEW** +- 1121: Update - maintenance_info ❌ **MISSING in 2.17** +- 1428: Bind - Endpoints ❌ **MISSING in 2.17** +- 1454: Bind - Endpoint Object ❌ **MISSING in 2.17** +- 1545: FetchBind - Endpoint Object ❌ **MISSING in 2.17** + +### Additional Items Found in 2.17 Spec Analysis +- ServicePlan.binding_rotatable ❌ **MISSING in 2.17** +- BindDetails.predecessor_binding_id ❌ **MISSING in 2.17** +- BindingMetadata object (expires_at, renew_before) ❌ **MISSING in 2.17** +- ServiceInstanceMetadata object (labels, attributes) ❌ **MISSING in 2.17** +- ProvisionDetails.maintenance_info ❌ **MISSING in 2.17** +- UpdateDetails.maintenance_info ❌ **MISSING in 2.17** +- PreviousValues.maintenance_info ❌ **MISSING in 2.17** +- GetInstanceDetailsSpec.maintenance_info ❌ **MISSING in 2.17** +- GetInstanceDetailsSpec.metadata ❌ **MISSING in 2.17** + +## See OSBAPI_2_17_ANALYSIS.md for detailed implementation guidance