omnibase_infra 0.2.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omnibase_infra/__init__.py +101 -0
- omnibase_infra/adapters/adapter_onex_tool_execution.py +451 -0
- omnibase_infra/capabilities/__init__.py +15 -0
- omnibase_infra/capabilities/capability_inference_rules.py +211 -0
- omnibase_infra/capabilities/contract_capability_extractor.py +221 -0
- omnibase_infra/capabilities/intent_type_extractor.py +160 -0
- omnibase_infra/cli/__init__.py +1 -0
- omnibase_infra/cli/commands.py +216 -0
- omnibase_infra/clients/__init__.py +0 -0
- omnibase_infra/configs/widget_mapping.yaml +176 -0
- omnibase_infra/constants_topic_patterns.py +26 -0
- omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +264 -0
- omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +141 -0
- omnibase_infra/decorators/__init__.py +29 -0
- omnibase_infra/decorators/allow_any.py +109 -0
- omnibase_infra/dlq/__init__.py +90 -0
- omnibase_infra/dlq/constants_dlq.py +57 -0
- omnibase_infra/dlq/models/__init__.py +26 -0
- omnibase_infra/dlq/models/enum_replay_status.py +37 -0
- omnibase_infra/dlq/models/model_dlq_replay_record.py +135 -0
- omnibase_infra/dlq/models/model_dlq_tracking_config.py +184 -0
- omnibase_infra/dlq/service_dlq_tracking.py +611 -0
- omnibase_infra/enums/__init__.py +132 -0
- omnibase_infra/enums/enum_any_type_violation.py +104 -0
- omnibase_infra/enums/enum_backend_type.py +27 -0
- omnibase_infra/enums/enum_capture_outcome.py +42 -0
- omnibase_infra/enums/enum_capture_state.py +88 -0
- omnibase_infra/enums/enum_chain_violation_type.py +119 -0
- omnibase_infra/enums/enum_circuit_state.py +51 -0
- omnibase_infra/enums/enum_confirmation_event_type.py +27 -0
- omnibase_infra/enums/enum_consumer_group_purpose.py +92 -0
- omnibase_infra/enums/enum_contract_type.py +84 -0
- omnibase_infra/enums/enum_dedupe_strategy.py +46 -0
- omnibase_infra/enums/enum_dispatch_status.py +191 -0
- omnibase_infra/enums/enum_environment.py +46 -0
- omnibase_infra/enums/enum_execution_shape_violation.py +103 -0
- omnibase_infra/enums/enum_handler_error_type.py +111 -0
- omnibase_infra/enums/enum_handler_loader_error.py +178 -0
- omnibase_infra/enums/enum_handler_source_mode.py +86 -0
- omnibase_infra/enums/enum_handler_source_type.py +87 -0
- omnibase_infra/enums/enum_handler_type.py +77 -0
- omnibase_infra/enums/enum_handler_type_category.py +61 -0
- omnibase_infra/enums/enum_infra_transport_type.py +73 -0
- omnibase_infra/enums/enum_introspection_reason.py +154 -0
- omnibase_infra/enums/enum_kafka_acks.py +99 -0
- omnibase_infra/enums/enum_message_category.py +213 -0
- omnibase_infra/enums/enum_node_archetype.py +74 -0
- omnibase_infra/enums/enum_node_output_type.py +185 -0
- omnibase_infra/enums/enum_non_retryable_error_category.py +224 -0
- omnibase_infra/enums/enum_policy_type.py +32 -0
- omnibase_infra/enums/enum_registration_state.py +261 -0
- omnibase_infra/enums/enum_registration_status.py +33 -0
- omnibase_infra/enums/enum_registry_response_status.py +28 -0
- omnibase_infra/enums/enum_response_status.py +26 -0
- omnibase_infra/enums/enum_retry_error_category.py +98 -0
- omnibase_infra/enums/enum_security_rule_id.py +103 -0
- omnibase_infra/enums/enum_selection_strategy.py +91 -0
- omnibase_infra/enums/enum_topic_standard.py +42 -0
- omnibase_infra/enums/enum_validation_severity.py +78 -0
- omnibase_infra/errors/__init__.py +160 -0
- omnibase_infra/errors/error_architecture_violation.py +152 -0
- omnibase_infra/errors/error_binding_resolution.py +128 -0
- omnibase_infra/errors/error_chain_propagation.py +188 -0
- omnibase_infra/errors/error_compute_registry.py +95 -0
- omnibase_infra/errors/error_consul.py +132 -0
- omnibase_infra/errors/error_container_wiring.py +243 -0
- omnibase_infra/errors/error_event_bus_registry.py +105 -0
- omnibase_infra/errors/error_infra.py +610 -0
- omnibase_infra/errors/error_message_type_registry.py +101 -0
- omnibase_infra/errors/error_policy_registry.py +115 -0
- omnibase_infra/errors/error_vault.py +123 -0
- omnibase_infra/event_bus/__init__.py +72 -0
- omnibase_infra/event_bus/configs/kafka_event_bus_config.yaml +84 -0
- omnibase_infra/event_bus/event_bus_inmemory.py +797 -0
- omnibase_infra/event_bus/event_bus_kafka.py +1716 -0
- omnibase_infra/event_bus/mixin_kafka_broadcast.py +180 -0
- omnibase_infra/event_bus/mixin_kafka_dlq.py +771 -0
- omnibase_infra/event_bus/models/__init__.py +29 -0
- omnibase_infra/event_bus/models/config/__init__.py +20 -0
- omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +693 -0
- omnibase_infra/event_bus/models/model_dlq_event.py +206 -0
- omnibase_infra/event_bus/models/model_dlq_metrics.py +304 -0
- omnibase_infra/event_bus/models/model_event_headers.py +115 -0
- omnibase_infra/event_bus/models/model_event_message.py +60 -0
- omnibase_infra/event_bus/testing/__init__.py +26 -0
- omnibase_infra/event_bus/testing/adapter_protocol_event_publisher_inmemory.py +418 -0
- omnibase_infra/event_bus/testing/model_publisher_metrics.py +64 -0
- omnibase_infra/event_bus/topic_constants.py +376 -0
- omnibase_infra/handlers/__init__.py +82 -0
- omnibase_infra/handlers/filesystem/__init__.py +48 -0
- omnibase_infra/handlers/filesystem/enum_file_system_operation.py +35 -0
- omnibase_infra/handlers/filesystem/model_file_system_request.py +298 -0
- omnibase_infra/handlers/filesystem/model_file_system_result.py +166 -0
- omnibase_infra/handlers/handler_consul.py +795 -0
- omnibase_infra/handlers/handler_db.py +1046 -0
- omnibase_infra/handlers/handler_filesystem.py +1478 -0
- omnibase_infra/handlers/handler_graph.py +2015 -0
- omnibase_infra/handlers/handler_http.py +926 -0
- omnibase_infra/handlers/handler_intent.py +387 -0
- omnibase_infra/handlers/handler_manifest_persistence.contract.yaml +184 -0
- omnibase_infra/handlers/handler_manifest_persistence.py +1539 -0
- omnibase_infra/handlers/handler_mcp.py +1430 -0
- omnibase_infra/handlers/handler_qdrant.py +1076 -0
- omnibase_infra/handlers/handler_vault.py +428 -0
- omnibase_infra/handlers/mcp/__init__.py +19 -0
- omnibase_infra/handlers/mcp/adapter_onex_to_mcp.py +446 -0
- omnibase_infra/handlers/mcp/protocols.py +178 -0
- omnibase_infra/handlers/mcp/transport_streamable_http.py +352 -0
- omnibase_infra/handlers/mixins/__init__.py +47 -0
- omnibase_infra/handlers/mixins/mixin_consul_initialization.py +349 -0
- omnibase_infra/handlers/mixins/mixin_consul_kv.py +338 -0
- omnibase_infra/handlers/mixins/mixin_consul_service.py +542 -0
- omnibase_infra/handlers/mixins/mixin_consul_topic_index.py +585 -0
- omnibase_infra/handlers/mixins/mixin_vault_initialization.py +338 -0
- omnibase_infra/handlers/mixins/mixin_vault_retry.py +412 -0
- omnibase_infra/handlers/mixins/mixin_vault_secrets.py +450 -0
- omnibase_infra/handlers/mixins/mixin_vault_token.py +365 -0
- omnibase_infra/handlers/models/__init__.py +286 -0
- omnibase_infra/handlers/models/consul/__init__.py +81 -0
- omnibase_infra/handlers/models/consul/enum_consul_operation_type.py +57 -0
- omnibase_infra/handlers/models/consul/model_consul_deregister_payload.py +51 -0
- omnibase_infra/handlers/models/consul/model_consul_handler_config.py +153 -0
- omnibase_infra/handlers/models/consul/model_consul_handler_payload.py +89 -0
- omnibase_infra/handlers/models/consul/model_consul_kv_get_found_payload.py +55 -0
- omnibase_infra/handlers/models/consul/model_consul_kv_get_not_found_payload.py +49 -0
- omnibase_infra/handlers/models/consul/model_consul_kv_get_recurse_payload.py +50 -0
- omnibase_infra/handlers/models/consul/model_consul_kv_item.py +33 -0
- omnibase_infra/handlers/models/consul/model_consul_kv_put_payload.py +41 -0
- omnibase_infra/handlers/models/consul/model_consul_register_payload.py +53 -0
- omnibase_infra/handlers/models/consul/model_consul_retry_config.py +66 -0
- omnibase_infra/handlers/models/consul/model_payload_consul.py +66 -0
- omnibase_infra/handlers/models/consul/registry_payload_consul.py +214 -0
- omnibase_infra/handlers/models/graph/__init__.py +35 -0
- omnibase_infra/handlers/models/graph/enum_graph_operation_type.py +20 -0
- omnibase_infra/handlers/models/graph/model_graph_execute_payload.py +38 -0
- omnibase_infra/handlers/models/graph/model_graph_handler_config.py +54 -0
- omnibase_infra/handlers/models/graph/model_graph_handler_payload.py +44 -0
- omnibase_infra/handlers/models/graph/model_graph_query_payload.py +40 -0
- omnibase_infra/handlers/models/graph/model_graph_record.py +22 -0
- omnibase_infra/handlers/models/http/__init__.py +50 -0
- omnibase_infra/handlers/models/http/enum_http_operation_type.py +29 -0
- omnibase_infra/handlers/models/http/model_http_body_content.py +45 -0
- omnibase_infra/handlers/models/http/model_http_get_payload.py +88 -0
- omnibase_infra/handlers/models/http/model_http_handler_payload.py +90 -0
- omnibase_infra/handlers/models/http/model_http_post_payload.py +88 -0
- omnibase_infra/handlers/models/http/model_payload_http.py +66 -0
- omnibase_infra/handlers/models/http/registry_payload_http.py +212 -0
- omnibase_infra/handlers/models/mcp/__init__.py +23 -0
- omnibase_infra/handlers/models/mcp/enum_mcp_operation_type.py +24 -0
- omnibase_infra/handlers/models/mcp/model_mcp_handler_config.py +40 -0
- omnibase_infra/handlers/models/mcp/model_mcp_tool_call.py +32 -0
- omnibase_infra/handlers/models/mcp/model_mcp_tool_result.py +45 -0
- omnibase_infra/handlers/models/model_consul_handler_response.py +96 -0
- omnibase_infra/handlers/models/model_db_describe_response.py +83 -0
- omnibase_infra/handlers/models/model_db_query_payload.py +95 -0
- omnibase_infra/handlers/models/model_db_query_response.py +60 -0
- omnibase_infra/handlers/models/model_filesystem_config.py +98 -0
- omnibase_infra/handlers/models/model_filesystem_delete_payload.py +54 -0
- omnibase_infra/handlers/models/model_filesystem_delete_result.py +77 -0
- omnibase_infra/handlers/models/model_filesystem_directory_entry.py +75 -0
- omnibase_infra/handlers/models/model_filesystem_ensure_directory_payload.py +54 -0
- omnibase_infra/handlers/models/model_filesystem_ensure_directory_result.py +60 -0
- omnibase_infra/handlers/models/model_filesystem_list_directory_payload.py +60 -0
- omnibase_infra/handlers/models/model_filesystem_list_directory_result.py +68 -0
- omnibase_infra/handlers/models/model_filesystem_read_payload.py +62 -0
- omnibase_infra/handlers/models/model_filesystem_read_result.py +61 -0
- omnibase_infra/handlers/models/model_filesystem_write_payload.py +70 -0
- omnibase_infra/handlers/models/model_filesystem_write_result.py +55 -0
- omnibase_infra/handlers/models/model_graph_handler_response.py +98 -0
- omnibase_infra/handlers/models/model_handler_response.py +103 -0
- omnibase_infra/handlers/models/model_http_handler_response.py +101 -0
- omnibase_infra/handlers/models/model_manifest_metadata.py +75 -0
- omnibase_infra/handlers/models/model_manifest_persistence_config.py +62 -0
- omnibase_infra/handlers/models/model_manifest_query_payload.py +90 -0
- omnibase_infra/handlers/models/model_manifest_query_result.py +97 -0
- omnibase_infra/handlers/models/model_manifest_retrieve_payload.py +44 -0
- omnibase_infra/handlers/models/model_manifest_retrieve_result.py +98 -0
- omnibase_infra/handlers/models/model_manifest_store_payload.py +47 -0
- omnibase_infra/handlers/models/model_manifest_store_result.py +67 -0
- omnibase_infra/handlers/models/model_operation_context.py +187 -0
- omnibase_infra/handlers/models/model_qdrant_handler_response.py +98 -0
- omnibase_infra/handlers/models/model_retry_state.py +162 -0
- omnibase_infra/handlers/models/model_vault_handler_response.py +98 -0
- omnibase_infra/handlers/models/qdrant/__init__.py +44 -0
- omnibase_infra/handlers/models/qdrant/enum_qdrant_operation_type.py +26 -0
- omnibase_infra/handlers/models/qdrant/model_qdrant_collection_payload.py +42 -0
- omnibase_infra/handlers/models/qdrant/model_qdrant_delete_payload.py +36 -0
- omnibase_infra/handlers/models/qdrant/model_qdrant_handler_config.py +42 -0
- omnibase_infra/handlers/models/qdrant/model_qdrant_handler_payload.py +54 -0
- omnibase_infra/handlers/models/qdrant/model_qdrant_search_payload.py +42 -0
- omnibase_infra/handlers/models/qdrant/model_qdrant_search_result.py +30 -0
- omnibase_infra/handlers/models/qdrant/model_qdrant_upsert_payload.py +36 -0
- omnibase_infra/handlers/models/vault/__init__.py +69 -0
- omnibase_infra/handlers/models/vault/enum_vault_operation_type.py +35 -0
- omnibase_infra/handlers/models/vault/model_payload_vault.py +66 -0
- omnibase_infra/handlers/models/vault/model_vault_delete_payload.py +57 -0
- omnibase_infra/handlers/models/vault/model_vault_handler_config.py +148 -0
- omnibase_infra/handlers/models/vault/model_vault_handler_payload.py +101 -0
- omnibase_infra/handlers/models/vault/model_vault_list_payload.py +58 -0
- omnibase_infra/handlers/models/vault/model_vault_renew_token_payload.py +67 -0
- omnibase_infra/handlers/models/vault/model_vault_retry_config.py +66 -0
- omnibase_infra/handlers/models/vault/model_vault_secret_payload.py +106 -0
- omnibase_infra/handlers/models/vault/model_vault_write_payload.py +66 -0
- omnibase_infra/handlers/models/vault/registry_payload_vault.py +213 -0
- omnibase_infra/handlers/registration_storage/__init__.py +43 -0
- omnibase_infra/handlers/registration_storage/handler_registration_storage_mock.py +392 -0
- omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +922 -0
- omnibase_infra/handlers/registration_storage/models/__init__.py +23 -0
- omnibase_infra/handlers/registration_storage/models/model_delete_registration_request.py +58 -0
- omnibase_infra/handlers/registration_storage/models/model_update_registration_request.py +73 -0
- omnibase_infra/handlers/registration_storage/protocol_registration_persistence.py +191 -0
- omnibase_infra/handlers/service_discovery/__init__.py +43 -0
- omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +1051 -0
- omnibase_infra/handlers/service_discovery/handler_service_discovery_mock.py +258 -0
- omnibase_infra/handlers/service_discovery/models/__init__.py +22 -0
- omnibase_infra/handlers/service_discovery/models/model_discovery_result.py +64 -0
- omnibase_infra/handlers/service_discovery/models/model_registration_result.py +138 -0
- omnibase_infra/handlers/service_discovery/models/model_service_info.py +109 -0
- omnibase_infra/handlers/service_discovery/protocol_discovery_operations.py +170 -0
- omnibase_infra/idempotency/__init__.py +94 -0
- omnibase_infra/idempotency/models/__init__.py +43 -0
- omnibase_infra/idempotency/models/model_idempotency_check_result.py +85 -0
- omnibase_infra/idempotency/models/model_idempotency_guard_config.py +130 -0
- omnibase_infra/idempotency/models/model_idempotency_record.py +86 -0
- omnibase_infra/idempotency/models/model_idempotency_store_health_check_result.py +81 -0
- omnibase_infra/idempotency/models/model_idempotency_store_metrics.py +140 -0
- omnibase_infra/idempotency/models/model_postgres_idempotency_store_config.py +299 -0
- omnibase_infra/idempotency/protocol_idempotency_store.py +184 -0
- omnibase_infra/idempotency/store_inmemory.py +265 -0
- omnibase_infra/idempotency/store_postgres.py +923 -0
- omnibase_infra/infrastructure/__init__.py +0 -0
- omnibase_infra/migrations/001_create_event_ledger.sql +166 -0
- omnibase_infra/migrations/001_drop_event_ledger.sql +18 -0
- omnibase_infra/mixins/__init__.py +71 -0
- omnibase_infra/mixins/mixin_async_circuit_breaker.py +656 -0
- omnibase_infra/mixins/mixin_dict_like_accessors.py +146 -0
- omnibase_infra/mixins/mixin_envelope_extraction.py +119 -0
- omnibase_infra/mixins/mixin_node_introspection.py +2670 -0
- omnibase_infra/mixins/mixin_retry_execution.py +386 -0
- omnibase_infra/mixins/protocol_circuit_breaker_aware.py +133 -0
- omnibase_infra/models/__init__.py +144 -0
- omnibase_infra/models/bindings/__init__.py +59 -0
- omnibase_infra/models/bindings/constants.py +144 -0
- omnibase_infra/models/bindings/model_binding_resolution_result.py +103 -0
- omnibase_infra/models/bindings/model_operation_binding.py +44 -0
- omnibase_infra/models/bindings/model_operation_bindings_subcontract.py +152 -0
- omnibase_infra/models/bindings/model_parsed_binding.py +52 -0
- omnibase_infra/models/corpus/__init__.py +17 -0
- omnibase_infra/models/corpus/model_capture_config.py +133 -0
- omnibase_infra/models/corpus/model_capture_result.py +86 -0
- omnibase_infra/models/discovery/__init__.py +42 -0
- omnibase_infra/models/discovery/model_dependency_spec.py +319 -0
- omnibase_infra/models/discovery/model_discovered_capabilities.py +50 -0
- omnibase_infra/models/discovery/model_introspection_config.py +330 -0
- omnibase_infra/models/discovery/model_introspection_performance_metrics.py +169 -0
- omnibase_infra/models/discovery/model_introspection_task_config.py +116 -0
- omnibase_infra/models/dispatch/__init__.py +155 -0
- omnibase_infra/models/dispatch/model_debug_trace_snapshot.py +114 -0
- omnibase_infra/models/dispatch/model_dispatch_context.py +439 -0
- omnibase_infra/models/dispatch/model_dispatch_error.py +336 -0
- omnibase_infra/models/dispatch/model_dispatch_log_context.py +400 -0
- omnibase_infra/models/dispatch/model_dispatch_metadata.py +228 -0
- omnibase_infra/models/dispatch/model_dispatch_metrics.py +496 -0
- omnibase_infra/models/dispatch/model_dispatch_outcome.py +317 -0
- omnibase_infra/models/dispatch/model_dispatch_outputs.py +231 -0
- omnibase_infra/models/dispatch/model_dispatch_result.py +436 -0
- omnibase_infra/models/dispatch/model_dispatch_route.py +279 -0
- omnibase_infra/models/dispatch/model_dispatcher_metrics.py +275 -0
- omnibase_infra/models/dispatch/model_dispatcher_registration.py +352 -0
- omnibase_infra/models/dispatch/model_materialized_dispatch.py +141 -0
- omnibase_infra/models/dispatch/model_parsed_topic.py +135 -0
- omnibase_infra/models/dispatch/model_topic_parser.py +725 -0
- omnibase_infra/models/dispatch/model_tracing_context.py +285 -0
- omnibase_infra/models/errors/__init__.py +45 -0
- omnibase_infra/models/errors/model_handler_validation_error.py +594 -0
- omnibase_infra/models/errors/model_infra_error_context.py +99 -0
- omnibase_infra/models/errors/model_message_type_registry_error_context.py +71 -0
- omnibase_infra/models/errors/model_timeout_error_context.py +110 -0
- omnibase_infra/models/handlers/__init__.py +80 -0
- omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
- omnibase_infra/models/handlers/model_contract_discovery_result.py +82 -0
- omnibase_infra/models/handlers/model_handler_descriptor.py +200 -0
- omnibase_infra/models/handlers/model_handler_identifier.py +215 -0
- omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
- omnibase_infra/models/health/__init__.py +9 -0
- omnibase_infra/models/health/model_health_check_result.py +40 -0
- omnibase_infra/models/lifecycle/__init__.py +39 -0
- omnibase_infra/models/logging/__init__.py +51 -0
- omnibase_infra/models/logging/model_log_context.py +756 -0
- omnibase_infra/models/mcp/__init__.py +15 -0
- omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
- omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
- omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
- omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
- omnibase_infra/models/model_node_identity.py +126 -0
- omnibase_infra/models/model_retry_error_classification.py +78 -0
- omnibase_infra/models/projection/__init__.py +43 -0
- omnibase_infra/models/projection/model_capability_fields.py +112 -0
- omnibase_infra/models/projection/model_registration_projection.py +434 -0
- omnibase_infra/models/projection/model_registration_snapshot.py +322 -0
- omnibase_infra/models/projection/model_sequence_info.py +182 -0
- omnibase_infra/models/projection/model_snapshot_topic_config.py +591 -0
- omnibase_infra/models/projectors/__init__.py +41 -0
- omnibase_infra/models/projectors/model_projector_column.py +289 -0
- omnibase_infra/models/projectors/model_projector_discovery_result.py +65 -0
- omnibase_infra/models/projectors/model_projector_index.py +270 -0
- omnibase_infra/models/projectors/model_projector_schema.py +415 -0
- omnibase_infra/models/projectors/model_projector_validation_error.py +63 -0
- omnibase_infra/models/projectors/util_sql_identifiers.py +115 -0
- omnibase_infra/models/registration/__init__.py +68 -0
- omnibase_infra/models/registration/commands/__init__.py +15 -0
- omnibase_infra/models/registration/commands/model_node_registration_acked.py +108 -0
- omnibase_infra/models/registration/events/__init__.py +56 -0
- omnibase_infra/models/registration/events/model_node_became_active.py +103 -0
- omnibase_infra/models/registration/events/model_node_liveness_expired.py +103 -0
- omnibase_infra/models/registration/events/model_node_registration_accepted.py +98 -0
- omnibase_infra/models/registration/events/model_node_registration_ack_received.py +98 -0
- omnibase_infra/models/registration/events/model_node_registration_ack_timed_out.py +112 -0
- omnibase_infra/models/registration/events/model_node_registration_initiated.py +107 -0
- omnibase_infra/models/registration/events/model_node_registration_rejected.py +104 -0
- omnibase_infra/models/registration/model_event_bus_topic_entry.py +59 -0
- omnibase_infra/models/registration/model_introspection_metrics.py +253 -0
- omnibase_infra/models/registration/model_node_capabilities.py +190 -0
- omnibase_infra/models/registration/model_node_event_bus_config.py +99 -0
- omnibase_infra/models/registration/model_node_heartbeat_event.py +126 -0
- omnibase_infra/models/registration/model_node_introspection_event.py +195 -0
- omnibase_infra/models/registration/model_node_metadata.py +79 -0
- omnibase_infra/models/registration/model_node_registration.py +162 -0
- omnibase_infra/models/registration/model_node_registration_record.py +162 -0
- omnibase_infra/models/registry/__init__.py +29 -0
- omnibase_infra/models/registry/model_domain_constraint.py +202 -0
- omnibase_infra/models/registry/model_message_type_entry.py +271 -0
- omnibase_infra/models/resilience/__init__.py +9 -0
- omnibase_infra/models/resilience/model_circuit_breaker_config.py +227 -0
- omnibase_infra/models/routing/__init__.py +25 -0
- omnibase_infra/models/routing/model_routing_entry.py +52 -0
- omnibase_infra/models/routing/model_routing_subcontract.py +70 -0
- omnibase_infra/models/runtime/__init__.py +49 -0
- omnibase_infra/models/runtime/model_contract_security_config.py +41 -0
- omnibase_infra/models/runtime/model_discovery_error.py +81 -0
- omnibase_infra/models/runtime/model_discovery_result.py +162 -0
- omnibase_infra/models/runtime/model_discovery_warning.py +74 -0
- omnibase_infra/models/runtime/model_failed_plugin_load.py +63 -0
- omnibase_infra/models/runtime/model_handler_contract.py +296 -0
- omnibase_infra/models/runtime/model_loaded_handler.py +129 -0
- omnibase_infra/models/runtime/model_plugin_load_context.py +93 -0
- omnibase_infra/models/runtime/model_plugin_load_summary.py +124 -0
- omnibase_infra/models/security/__init__.py +50 -0
- omnibase_infra/models/security/classification_levels.py +99 -0
- omnibase_infra/models/security/model_environment_policy.py +145 -0
- omnibase_infra/models/security/model_handler_security_policy.py +107 -0
- omnibase_infra/models/security/model_security_error.py +81 -0
- omnibase_infra/models/security/model_security_validation_result.py +328 -0
- omnibase_infra/models/security/model_security_warning.py +67 -0
- omnibase_infra/models/snapshot/__init__.py +27 -0
- omnibase_infra/models/snapshot/model_field_change.py +65 -0
- omnibase_infra/models/snapshot/model_snapshot.py +270 -0
- omnibase_infra/models/snapshot/model_snapshot_diff.py +203 -0
- omnibase_infra/models/snapshot/model_subject_ref.py +81 -0
- omnibase_infra/models/types/__init__.py +71 -0
- omnibase_infra/models/validation/__init__.py +89 -0
- omnibase_infra/models/validation/model_any_type_validation_result.py +118 -0
- omnibase_infra/models/validation/model_any_type_violation.py +141 -0
- omnibase_infra/models/validation/model_category_match_result.py +345 -0
- omnibase_infra/models/validation/model_chain_violation.py +166 -0
- omnibase_infra/models/validation/model_coverage_metrics.py +316 -0
- omnibase_infra/models/validation/model_execution_shape_rule.py +159 -0
- omnibase_infra/models/validation/model_execution_shape_validation.py +208 -0
- omnibase_infra/models/validation/model_execution_shape_validation_result.py +294 -0
- omnibase_infra/models/validation/model_execution_shape_violation.py +122 -0
- omnibase_infra/models/validation/model_localhandler_validation_result.py +139 -0
- omnibase_infra/models/validation/model_localhandler_violation.py +100 -0
- omnibase_infra/models/validation/model_output_validation_params.py +74 -0
- omnibase_infra/models/validation/model_validate_and_raise_params.py +84 -0
- omnibase_infra/models/validation/model_validation_error_params.py +84 -0
- omnibase_infra/models/validation/model_validation_outcome.py +287 -0
- omnibase_infra/nodes/__init__.py +57 -0
- omnibase_infra/nodes/architecture_validator/__init__.py +79 -0
- omnibase_infra/nodes/architecture_validator/contract.yaml +252 -0
- omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +203 -0
- omnibase_infra/nodes/architecture_validator/mixins/__init__.py +16 -0
- omnibase_infra/nodes/architecture_validator/mixins/mixin_file_path_rule.py +92 -0
- omnibase_infra/nodes/architecture_validator/models/__init__.py +36 -0
- omnibase_infra/nodes/architecture_validator/models/model_architecture_validation_request.py +56 -0
- omnibase_infra/nodes/architecture_validator/models/model_architecture_validation_result.py +311 -0
- omnibase_infra/nodes/architecture_validator/models/model_architecture_violation.py +163 -0
- omnibase_infra/nodes/architecture_validator/models/model_rule_check_result.py +265 -0
- omnibase_infra/nodes/architecture_validator/models/model_validation_request.py +105 -0
- omnibase_infra/nodes/architecture_validator/models/model_validation_result.py +314 -0
- omnibase_infra/nodes/architecture_validator/node.py +262 -0
- omnibase_infra/nodes/architecture_validator/node_architecture_validator.py +383 -0
- omnibase_infra/nodes/architecture_validator/protocols/__init__.py +9 -0
- omnibase_infra/nodes/architecture_validator/protocols/protocol_architecture_rule.py +225 -0
- omnibase_infra/nodes/architecture_validator/registry/__init__.py +28 -0
- omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +106 -0
- omnibase_infra/nodes/architecture_validator/validators/__init__.py +104 -0
- omnibase_infra/nodes/architecture_validator/validators/validator_no_direct_dispatch.py +422 -0
- omnibase_infra/nodes/architecture_validator/validators/validator_no_handler_publishing.py +481 -0
- omnibase_infra/nodes/architecture_validator/validators/validator_no_orchestrator_fsm.py +491 -0
- omnibase_infra/nodes/contract_registry_reducer/__init__.py +29 -0
- omnibase_infra/nodes/contract_registry_reducer/contract.yaml +255 -0
- omnibase_infra/nodes/contract_registry_reducer/models/__init__.py +38 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_contract_registry_state.py +266 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_cleanup_topic_references.py +55 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_deactivate_contract.py +58 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_mark_stale.py +49 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_heartbeat.py +71 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_topic.py +66 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_upsert_contract.py +92 -0
- omnibase_infra/nodes/contract_registry_reducer/node.py +121 -0
- omnibase_infra/nodes/contract_registry_reducer/reducer.py +784 -0
- omnibase_infra/nodes/contract_registry_reducer/registry/__init__.py +9 -0
- omnibase_infra/nodes/contract_registry_reducer/registry/registry_infra_contract_registry_reducer.py +101 -0
- omnibase_infra/nodes/effects/README.md +358 -0
- omnibase_infra/nodes/effects/__init__.py +26 -0
- omnibase_infra/nodes/effects/contract.yaml +167 -0
- omnibase_infra/nodes/effects/models/__init__.py +32 -0
- omnibase_infra/nodes/effects/models/model_backend_result.py +190 -0
- omnibase_infra/nodes/effects/models/model_effect_idempotency_config.py +92 -0
- omnibase_infra/nodes/effects/models/model_registry_request.py +132 -0
- omnibase_infra/nodes/effects/models/model_registry_response.py +263 -0
- omnibase_infra/nodes/effects/protocol_consul_client.py +89 -0
- omnibase_infra/nodes/effects/protocol_effect_idempotency_store.py +143 -0
- omnibase_infra/nodes/effects/protocol_postgres_adapter.py +96 -0
- omnibase_infra/nodes/effects/registry_effect.py +525 -0
- omnibase_infra/nodes/effects/store_effect_idempotency_inmemory.py +425 -0
- omnibase_infra/nodes/handlers/consul/contract.yaml +85 -0
- omnibase_infra/nodes/handlers/db/contract.yaml +72 -0
- omnibase_infra/nodes/handlers/graph/contract.yaml +127 -0
- omnibase_infra/nodes/handlers/http/contract.yaml +74 -0
- omnibase_infra/nodes/handlers/intent/contract.yaml +66 -0
- omnibase_infra/nodes/handlers/mcp/contract.yaml +69 -0
- omnibase_infra/nodes/handlers/vault/contract.yaml +91 -0
- omnibase_infra/nodes/node_intent_storage_effect/__init__.py +50 -0
- omnibase_infra/nodes/node_intent_storage_effect/contract.yaml +194 -0
- omnibase_infra/nodes/node_intent_storage_effect/models/__init__.py +24 -0
- omnibase_infra/nodes/node_intent_storage_effect/models/model_intent_storage_input.py +141 -0
- omnibase_infra/nodes/node_intent_storage_effect/models/model_intent_storage_output.py +130 -0
- omnibase_infra/nodes/node_intent_storage_effect/node.py +94 -0
- omnibase_infra/nodes/node_intent_storage_effect/registry/__init__.py +35 -0
- omnibase_infra/nodes/node_intent_storage_effect/registry/registry_infra_intent_storage.py +294 -0
- omnibase_infra/nodes/node_ledger_projection_compute/__init__.py +50 -0
- omnibase_infra/nodes/node_ledger_projection_compute/contract.yaml +104 -0
- omnibase_infra/nodes/node_ledger_projection_compute/node.py +284 -0
- omnibase_infra/nodes/node_ledger_projection_compute/registry/__init__.py +29 -0
- omnibase_infra/nodes/node_ledger_projection_compute/registry/registry_infra_ledger_projection.py +118 -0
- omnibase_infra/nodes/node_ledger_write_effect/__init__.py +82 -0
- omnibase_infra/nodes/node_ledger_write_effect/contract.yaml +200 -0
- omnibase_infra/nodes/node_ledger_write_effect/handlers/__init__.py +22 -0
- omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_append.py +372 -0
- omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_query.py +597 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/__init__.py +31 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_append_result.py +54 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_entry.py +92 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query.py +53 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query_result.py +41 -0
- omnibase_infra/nodes/node_ledger_write_effect/node.py +89 -0
- omnibase_infra/nodes/node_ledger_write_effect/protocols/__init__.py +13 -0
- omnibase_infra/nodes/node_ledger_write_effect/protocols/protocol_ledger_persistence.py +127 -0
- omnibase_infra/nodes/node_ledger_write_effect/registry/__init__.py +9 -0
- omnibase_infra/nodes/node_ledger_write_effect/registry/registry_infra_ledger_write.py +121 -0
- omnibase_infra/nodes/node_registration_orchestrator/README.md +542 -0
- omnibase_infra/nodes/node_registration_orchestrator/__init__.py +120 -0
- omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +482 -0
- omnibase_infra/nodes/node_registration_orchestrator/dispatchers/__init__.py +53 -0
- omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_node_introspected.py +376 -0
- omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_node_registration_acked.py +376 -0
- omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_runtime_tick.py +373 -0
- omnibase_infra/nodes/node_registration_orchestrator/handlers/__init__.py +62 -0
- omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_heartbeat.py +376 -0
- omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +694 -0
- omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_registration_acked.py +458 -0
- omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_runtime_tick.py +364 -0
- omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +544 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/__init__.py +75 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_consul_intent_payload.py +194 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_consul_registration_intent.py +67 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_intent_execution_result.py +50 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_node_liveness_expired.py +107 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_config.py +67 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_input.py +41 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_output.py +166 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_intent_payload.py +235 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_upsert_intent.py +68 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_reducer_execution_result.py +384 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_reducer_state.py +60 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_registration_intent.py +177 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_registry_intent.py +247 -0
- omnibase_infra/nodes/node_registration_orchestrator/node.py +195 -0
- omnibase_infra/nodes/node_registration_orchestrator/plugin.py +909 -0
- omnibase_infra/nodes/node_registration_orchestrator/protocols.py +439 -0
- omnibase_infra/nodes/node_registration_orchestrator/registry/__init__.py +41 -0
- omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +528 -0
- omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +393 -0
- omnibase_infra/nodes/node_registration_orchestrator/wiring.py +743 -0
- omnibase_infra/nodes/node_registration_reducer/__init__.py +15 -0
- omnibase_infra/nodes/node_registration_reducer/contract.yaml +301 -0
- omnibase_infra/nodes/node_registration_reducer/models/__init__.py +38 -0
- omnibase_infra/nodes/node_registration_reducer/models/model_validation_result.py +113 -0
- omnibase_infra/nodes/node_registration_reducer/node.py +139 -0
- omnibase_infra/nodes/node_registration_reducer/registry/__init__.py +9 -0
- omnibase_infra/nodes/node_registration_reducer/registry/registry_infra_node_registration_reducer.py +79 -0
- omnibase_infra/nodes/node_registration_storage_effect/__init__.py +41 -0
- omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +220 -0
- omnibase_infra/nodes/node_registration_storage_effect/models/__init__.py +44 -0
- omnibase_infra/nodes/node_registration_storage_effect/models/model_delete_result.py +132 -0
- omnibase_infra/nodes/node_registration_storage_effect/models/model_registration_record.py +199 -0
- omnibase_infra/nodes/node_registration_storage_effect/models/model_registration_update.py +155 -0
- omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_health_check_details.py +123 -0
- omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_health_check_result.py +117 -0
- omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_query.py +100 -0
- omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_result.py +136 -0
- omnibase_infra/nodes/node_registration_storage_effect/models/model_upsert_result.py +127 -0
- omnibase_infra/nodes/node_registration_storage_effect/node.py +112 -0
- omnibase_infra/nodes/node_registration_storage_effect/protocols/__init__.py +22 -0
- omnibase_infra/nodes/node_registration_storage_effect/protocols/protocol_registration_persistence.py +333 -0
- omnibase_infra/nodes/node_registration_storage_effect/registry/__init__.py +23 -0
- omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +215 -0
- omnibase_infra/nodes/node_registry_effect/__init__.py +85 -0
- omnibase_infra/nodes/node_registry_effect/contract.yaml +677 -0
- omnibase_infra/nodes/node_registry_effect/handlers/__init__.py +70 -0
- omnibase_infra/nodes/node_registry_effect/handlers/handler_consul_deregister.py +211 -0
- omnibase_infra/nodes/node_registry_effect/handlers/handler_consul_register.py +212 -0
- omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +417 -0
- omnibase_infra/nodes/node_registry_effect/handlers/handler_postgres_deactivate.py +215 -0
- omnibase_infra/nodes/node_registry_effect/handlers/handler_postgres_upsert.py +208 -0
- omnibase_infra/nodes/node_registry_effect/models/__init__.py +43 -0
- omnibase_infra/nodes/node_registry_effect/models/model_partial_retry_request.py +92 -0
- omnibase_infra/nodes/node_registry_effect/node.py +165 -0
- omnibase_infra/nodes/node_registry_effect/registry/__init__.py +27 -0
- omnibase_infra/nodes/node_registry_effect/registry/registry_infra_registry_effect.py +196 -0
- omnibase_infra/nodes/node_service_discovery_effect/__init__.py +111 -0
- omnibase_infra/nodes/node_service_discovery_effect/contract.yaml +246 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/__init__.py +67 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/enum_health_status.py +72 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/enum_service_discovery_operation.py +58 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/model_discovery_query.py +99 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/model_discovery_result.py +98 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/model_health_check_config.py +121 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/model_query_metadata.py +63 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/model_registration_result.py +130 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/model_service_discovery_health_check_details.py +111 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/model_service_discovery_health_check_result.py +119 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/model_service_info.py +106 -0
- omnibase_infra/nodes/node_service_discovery_effect/models/model_service_registration.py +121 -0
- omnibase_infra/nodes/node_service_discovery_effect/node.py +111 -0
- omnibase_infra/nodes/node_service_discovery_effect/protocols/__init__.py +14 -0
- omnibase_infra/nodes/node_service_discovery_effect/protocols/protocol_discovery_operations.py +279 -0
- omnibase_infra/nodes/node_service_discovery_effect/registry/__init__.py +13 -0
- omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +222 -0
- omnibase_infra/nodes/reducers/__init__.py +30 -0
- omnibase_infra/nodes/reducers/models/__init__.py +37 -0
- omnibase_infra/nodes/reducers/models/model_payload_consul_register.py +87 -0
- omnibase_infra/nodes/reducers/models/model_payload_ledger_append.py +133 -0
- omnibase_infra/nodes/reducers/models/model_payload_postgres_upsert_registration.py +60 -0
- omnibase_infra/nodes/reducers/models/model_registration_confirmation.py +166 -0
- omnibase_infra/nodes/reducers/models/model_registration_state.py +433 -0
- omnibase_infra/nodes/reducers/registration_reducer.py +1138 -0
- omnibase_infra/observability/__init__.py +143 -0
- omnibase_infra/observability/constants_metrics.py +91 -0
- omnibase_infra/observability/factory_observability_sink.py +525 -0
- omnibase_infra/observability/handlers/__init__.py +118 -0
- omnibase_infra/observability/handlers/handler_logging_structured.py +967 -0
- omnibase_infra/observability/handlers/handler_metrics_prometheus.py +1120 -0
- omnibase_infra/observability/handlers/model_logging_handler_config.py +71 -0
- omnibase_infra/observability/handlers/model_logging_handler_response.py +77 -0
- omnibase_infra/observability/handlers/model_metrics_handler_config.py +172 -0
- omnibase_infra/observability/handlers/model_metrics_handler_payload.py +135 -0
- omnibase_infra/observability/handlers/model_metrics_handler_response.py +101 -0
- omnibase_infra/observability/hooks/__init__.py +74 -0
- omnibase_infra/observability/hooks/hook_observability.py +1223 -0
- omnibase_infra/observability/models/__init__.py +30 -0
- omnibase_infra/observability/models/enum_required_log_context_key.py +77 -0
- omnibase_infra/observability/models/model_buffered_log_entry.py +117 -0
- omnibase_infra/observability/models/model_logging_sink_config.py +73 -0
- omnibase_infra/observability/models/model_metrics_sink_config.py +156 -0
- omnibase_infra/observability/sinks/__init__.py +69 -0
- omnibase_infra/observability/sinks/sink_logging_structured.py +809 -0
- omnibase_infra/observability/sinks/sink_metrics_prometheus.py +710 -0
- omnibase_infra/plugins/__init__.py +27 -0
- omnibase_infra/plugins/examples/__init__.py +28 -0
- omnibase_infra/plugins/examples/plugin_json_normalizer.py +271 -0
- omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +210 -0
- omnibase_infra/plugins/models/__init__.py +21 -0
- omnibase_infra/plugins/models/model_plugin_context.py +76 -0
- omnibase_infra/plugins/models/model_plugin_input_data.py +58 -0
- omnibase_infra/plugins/models/model_plugin_output_data.py +62 -0
- omnibase_infra/plugins/plugin_compute_base.py +449 -0
- omnibase_infra/projectors/__init__.py +30 -0
- omnibase_infra/projectors/contracts/__init__.py +63 -0
- omnibase_infra/projectors/contracts/registration_projector.yaml +370 -0
- omnibase_infra/projectors/projection_reader_registration.py +1559 -0
- omnibase_infra/projectors/snapshot_publisher_registration.py +1329 -0
- omnibase_infra/protocols/__init__.py +104 -0
- omnibase_infra/protocols/protocol_capability_projection.py +253 -0
- omnibase_infra/protocols/protocol_capability_query.py +251 -0
- omnibase_infra/protocols/protocol_container_aware.py +200 -0
- omnibase_infra/protocols/protocol_dispatch_engine.py +152 -0
- omnibase_infra/protocols/protocol_event_bus_like.py +127 -0
- omnibase_infra/protocols/protocol_event_projector.py +96 -0
- omnibase_infra/protocols/protocol_idempotency_store.py +142 -0
- omnibase_infra/protocols/protocol_message_dispatcher.py +247 -0
- omnibase_infra/protocols/protocol_message_type_registry.py +306 -0
- omnibase_infra/protocols/protocol_plugin_compute.py +368 -0
- omnibase_infra/protocols/protocol_projector_schema_validator.py +82 -0
- omnibase_infra/protocols/protocol_registry_metrics.py +215 -0
- omnibase_infra/protocols/protocol_snapshot_publisher.py +396 -0
- omnibase_infra/protocols/protocol_snapshot_store.py +567 -0
- omnibase_infra/runtime/__init__.py +445 -0
- omnibase_infra/runtime/binding_config_resolver.py +2771 -0
- omnibase_infra/runtime/binding_resolver.py +753 -0
- omnibase_infra/runtime/chain_aware_dispatch.py +467 -0
- omnibase_infra/runtime/constants_notification.py +75 -0
- omnibase_infra/runtime/constants_security.py +70 -0
- omnibase_infra/runtime/contract_handler_discovery.py +587 -0
- omnibase_infra/runtime/contract_loaders/__init__.py +51 -0
- omnibase_infra/runtime/contract_loaders/handler_routing_loader.py +464 -0
- omnibase_infra/runtime/contract_loaders/operation_bindings_loader.py +789 -0
- omnibase_infra/runtime/dispatch_context_enforcer.py +427 -0
- omnibase_infra/runtime/emit_daemon/__init__.py +97 -0
- omnibase_infra/runtime/emit_daemon/cli.py +844 -0
- omnibase_infra/runtime/emit_daemon/client.py +811 -0
- omnibase_infra/runtime/emit_daemon/config.py +535 -0
- omnibase_infra/runtime/emit_daemon/daemon.py +812 -0
- omnibase_infra/runtime/emit_daemon/event_registry.py +477 -0
- omnibase_infra/runtime/emit_daemon/model_daemon_request.py +139 -0
- omnibase_infra/runtime/emit_daemon/model_daemon_response.py +191 -0
- omnibase_infra/runtime/emit_daemon/queue.py +618 -0
- omnibase_infra/runtime/enums/__init__.py +18 -0
- omnibase_infra/runtime/enums/enum_config_ref_scheme.py +33 -0
- omnibase_infra/runtime/enums/enum_scheduler_status.py +170 -0
- omnibase_infra/runtime/envelope_validator.py +179 -0
- omnibase_infra/runtime/event_bus_subcontract_wiring.py +466 -0
- omnibase_infra/runtime/handler_bootstrap_source.py +507 -0
- omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
- omnibase_infra/runtime/handler_contract_source.py +750 -0
- omnibase_infra/runtime/handler_identity.py +81 -0
- omnibase_infra/runtime/handler_plugin_loader.py +2046 -0
- omnibase_infra/runtime/handler_registry.py +329 -0
- omnibase_infra/runtime/handler_source_resolver.py +367 -0
- omnibase_infra/runtime/invocation_security_enforcer.py +427 -0
- omnibase_infra/runtime/kafka_contract_source.py +984 -0
- omnibase_infra/runtime/kernel.py +40 -0
- omnibase_infra/runtime/mixin_policy_validation.py +522 -0
- omnibase_infra/runtime/mixin_semver_cache.py +402 -0
- omnibase_infra/runtime/mixins/__init__.py +24 -0
- omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
- omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +778 -0
- omnibase_infra/runtime/models/__init__.py +229 -0
- omnibase_infra/runtime/models/model_batch_lifecycle_result.py +217 -0
- omnibase_infra/runtime/models/model_binding_config.py +168 -0
- omnibase_infra/runtime/models/model_binding_config_cache_stats.py +135 -0
- omnibase_infra/runtime/models/model_binding_config_resolver_config.py +329 -0
- omnibase_infra/runtime/models/model_cached_secret.py +138 -0
- omnibase_infra/runtime/models/model_compute_key.py +138 -0
- omnibase_infra/runtime/models/model_compute_registration.py +97 -0
- omnibase_infra/runtime/models/model_config_cache_entry.py +61 -0
- omnibase_infra/runtime/models/model_config_ref.py +331 -0
- omnibase_infra/runtime/models/model_config_ref_parse_result.py +125 -0
- omnibase_infra/runtime/models/model_contract_load_result.py +224 -0
- omnibase_infra/runtime/models/model_domain_plugin_config.py +92 -0
- omnibase_infra/runtime/models/model_domain_plugin_result.py +270 -0
- omnibase_infra/runtime/models/model_duplicate_response.py +54 -0
- omnibase_infra/runtime/models/model_enabled_protocols_config.py +61 -0
- omnibase_infra/runtime/models/model_event_bus_config.py +54 -0
- omnibase_infra/runtime/models/model_failed_component.py +55 -0
- omnibase_infra/runtime/models/model_health_check_response.py +168 -0
- omnibase_infra/runtime/models/model_health_check_result.py +229 -0
- omnibase_infra/runtime/models/model_lifecycle_result.py +245 -0
- omnibase_infra/runtime/models/model_logging_config.py +42 -0
- omnibase_infra/runtime/models/model_optional_correlation_id.py +167 -0
- omnibase_infra/runtime/models/model_optional_string.py +94 -0
- omnibase_infra/runtime/models/model_optional_uuid.py +110 -0
- omnibase_infra/runtime/models/model_policy_context.py +100 -0
- omnibase_infra/runtime/models/model_policy_key.py +138 -0
- omnibase_infra/runtime/models/model_policy_registration.py +139 -0
- omnibase_infra/runtime/models/model_policy_result.py +103 -0
- omnibase_infra/runtime/models/model_policy_type_filter.py +157 -0
- omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
- omnibase_infra/runtime/models/model_projector_plugin_loader_config.py +47 -0
- omnibase_infra/runtime/models/model_protocol_registration_config.py +65 -0
- omnibase_infra/runtime/models/model_retry_policy.py +105 -0
- omnibase_infra/runtime/models/model_runtime_config.py +150 -0
- omnibase_infra/runtime/models/model_runtime_contract_config.py +268 -0
- omnibase_infra/runtime/models/model_runtime_scheduler_config.py +625 -0
- omnibase_infra/runtime/models/model_runtime_scheduler_metrics.py +233 -0
- omnibase_infra/runtime/models/model_runtime_tick.py +193 -0
- omnibase_infra/runtime/models/model_secret_cache_stats.py +82 -0
- omnibase_infra/runtime/models/model_secret_mapping.py +63 -0
- omnibase_infra/runtime/models/model_secret_resolver_config.py +107 -0
- omnibase_infra/runtime/models/model_secret_resolver_metrics.py +111 -0
- omnibase_infra/runtime/models/model_secret_source_info.py +72 -0
- omnibase_infra/runtime/models/model_secret_source_spec.py +66 -0
- omnibase_infra/runtime/models/model_security_config.py +109 -0
- omnibase_infra/runtime/models/model_shutdown_batch_result.py +75 -0
- omnibase_infra/runtime/models/model_shutdown_config.py +94 -0
- omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
- omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
- omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
- omnibase_infra/runtime/projector_plugin_loader.py +1462 -0
- omnibase_infra/runtime/projector_schema_manager.py +565 -0
- omnibase_infra/runtime/projector_shell.py +1330 -0
- omnibase_infra/runtime/protocol_contract_descriptor.py +92 -0
- omnibase_infra/runtime/protocol_contract_source.py +92 -0
- omnibase_infra/runtime/protocol_domain_plugin.py +474 -0
- omnibase_infra/runtime/protocol_handler_discovery.py +221 -0
- omnibase_infra/runtime/protocol_handler_plugin_loader.py +327 -0
- omnibase_infra/runtime/protocol_lifecycle_executor.py +435 -0
- omnibase_infra/runtime/protocol_policy.py +366 -0
- omnibase_infra/runtime/protocols/__init__.py +37 -0
- omnibase_infra/runtime/protocols/protocol_runtime_scheduler.py +468 -0
- omnibase_infra/runtime/publisher_topic_scoped.py +294 -0
- omnibase_infra/runtime/registry/__init__.py +93 -0
- omnibase_infra/runtime/registry/mixin_message_type_query.py +326 -0
- omnibase_infra/runtime/registry/mixin_message_type_registration.py +354 -0
- omnibase_infra/runtime/registry/registry_event_bus_binding.py +268 -0
- omnibase_infra/runtime/registry/registry_message_type.py +542 -0
- omnibase_infra/runtime/registry/registry_protocol_binding.py +445 -0
- omnibase_infra/runtime/registry_compute.py +1143 -0
- omnibase_infra/runtime/registry_contract_source.py +693 -0
- omnibase_infra/runtime/registry_dispatcher.py +678 -0
- omnibase_infra/runtime/registry_policy.py +1185 -0
- omnibase_infra/runtime/runtime_contract_config_loader.py +406 -0
- omnibase_infra/runtime/runtime_scheduler.py +1070 -0
- omnibase_infra/runtime/secret_resolver.py +2112 -0
- omnibase_infra/runtime/security_metadata_validator.py +776 -0
- omnibase_infra/runtime/service_kernel.py +1651 -0
- omnibase_infra/runtime/service_message_dispatch_engine.py +2350 -0
- omnibase_infra/runtime/service_runtime_host_process.py +3493 -0
- omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
- omnibase_infra/runtime/transition_notification_publisher.py +765 -0
- omnibase_infra/runtime/util_container_wiring.py +1124 -0
- omnibase_infra/runtime/util_validation.py +314 -0
- omnibase_infra/runtime/util_version.py +98 -0
- omnibase_infra/runtime/util_wiring.py +723 -0
- omnibase_infra/schemas/schema_registration_projection.sql +320 -0
- omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
- omnibase_infra/services/__init__.py +89 -0
- omnibase_infra/services/corpus_capture.py +684 -0
- omnibase_infra/services/mcp/__init__.py +31 -0
- omnibase_infra/services/mcp/mcp_server_lifecycle.py +449 -0
- omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
- omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
- omnibase_infra/services/mcp/service_mcp_tool_sync.py +565 -0
- omnibase_infra/services/registry_api/__init__.py +40 -0
- omnibase_infra/services/registry_api/main.py +261 -0
- omnibase_infra/services/registry_api/models/__init__.py +66 -0
- omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
- omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
- omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
- omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
- omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
- omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
- omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
- omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
- omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
- omnibase_infra/services/registry_api/models/model_warning.py +49 -0
- omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
- omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
- omnibase_infra/services/registry_api/routes.py +371 -0
- omnibase_infra/services/registry_api/service.py +837 -0
- omnibase_infra/services/service_capability_query.py +945 -0
- omnibase_infra/services/service_health.py +898 -0
- omnibase_infra/services/service_node_selector.py +530 -0
- omnibase_infra/services/service_timeout_emitter.py +699 -0
- omnibase_infra/services/service_timeout_scanner.py +394 -0
- omnibase_infra/services/session/__init__.py +56 -0
- omnibase_infra/services/session/config_consumer.py +137 -0
- omnibase_infra/services/session/config_store.py +139 -0
- omnibase_infra/services/session/consumer.py +1007 -0
- omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
- omnibase_infra/services/session/store.py +997 -0
- omnibase_infra/services/snapshot/__init__.py +31 -0
- omnibase_infra/services/snapshot/service_snapshot.py +647 -0
- omnibase_infra/services/snapshot/store_inmemory.py +637 -0
- omnibase_infra/services/snapshot/store_postgres.py +1279 -0
- omnibase_infra/shared/__init__.py +8 -0
- omnibase_infra/testing/__init__.py +10 -0
- omnibase_infra/testing/utils.py +23 -0
- omnibase_infra/topics/__init__.py +45 -0
- omnibase_infra/topics/platform_topic_suffixes.py +140 -0
- omnibase_infra/topics/util_topic_composition.py +95 -0
- omnibase_infra/types/__init__.py +48 -0
- omnibase_infra/types/type_cache_info.py +49 -0
- omnibase_infra/types/type_dsn.py +173 -0
- omnibase_infra/types/type_infra_aliases.py +60 -0
- omnibase_infra/types/typed_dict/__init__.py +29 -0
- omnibase_infra/types/typed_dict/typed_dict_envelope_build_params.py +115 -0
- omnibase_infra/types/typed_dict/typed_dict_introspection_cache.py +128 -0
- omnibase_infra/types/typed_dict/typed_dict_performance_metrics_cache.py +140 -0
- omnibase_infra/types/typed_dict_capabilities.py +64 -0
- omnibase_infra/utils/__init__.py +117 -0
- omnibase_infra/utils/correlation.py +208 -0
- omnibase_infra/utils/util_atomic_file.py +261 -0
- omnibase_infra/utils/util_consumer_group.py +232 -0
- omnibase_infra/utils/util_datetime.py +372 -0
- omnibase_infra/utils/util_db_transaction.py +239 -0
- omnibase_infra/utils/util_dsn_validation.py +333 -0
- omnibase_infra/utils/util_env_parsing.py +264 -0
- omnibase_infra/utils/util_error_sanitization.py +457 -0
- omnibase_infra/utils/util_pydantic_validators.py +477 -0
- omnibase_infra/utils/util_retry_optimistic.py +281 -0
- omnibase_infra/utils/util_semver.py +233 -0
- omnibase_infra/validation/__init__.py +307 -0
- omnibase_infra/validation/contracts/security.validation.yaml +114 -0
- omnibase_infra/validation/enums/__init__.py +11 -0
- omnibase_infra/validation/enums/enum_contract_violation_severity.py +13 -0
- omnibase_infra/validation/infra_validators.py +1514 -0
- omnibase_infra/validation/linter_contract.py +907 -0
- omnibase_infra/validation/mixin_any_type_classification.py +120 -0
- omnibase_infra/validation/mixin_any_type_exemption.py +580 -0
- omnibase_infra/validation/mixin_any_type_reporting.py +106 -0
- omnibase_infra/validation/mixin_execution_shape_violation_checks.py +596 -0
- omnibase_infra/validation/mixin_node_archetype_detection.py +254 -0
- omnibase_infra/validation/models/__init__.py +15 -0
- omnibase_infra/validation/models/model_contract_lint_result.py +101 -0
- omnibase_infra/validation/models/model_contract_violation.py +41 -0
- omnibase_infra/validation/service_validation_aggregator.py +395 -0
- omnibase_infra/validation/validation_exemptions.yaml +2033 -0
- omnibase_infra/validation/validator_any_type.py +715 -0
- omnibase_infra/validation/validator_chain_propagation.py +839 -0
- omnibase_infra/validation/validator_execution_shape.py +465 -0
- omnibase_infra/validation/validator_localhandler.py +261 -0
- omnibase_infra/validation/validator_registration_security.py +410 -0
- omnibase_infra/validation/validator_routing_coverage.py +1020 -0
- omnibase_infra/validation/validator_runtime_shape.py +915 -0
- omnibase_infra/validation/validator_security.py +513 -0
- omnibase_infra/validation/validator_topic_category.py +1152 -0
- omnibase_infra-0.2.6.dist-info/METADATA +197 -0
- omnibase_infra-0.2.6.dist-info/RECORD +833 -0
- omnibase_infra-0.2.6.dist-info/WHEEL +4 -0
- omnibase_infra-0.2.6.dist-info/entry_points.txt +5 -0
- omnibase_infra-0.2.6.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""PostgreSQL Database Handler - MVP implementation using asyncpg async client.
|
|
4
|
+
|
|
5
|
+
Supports query and execute operations with fixed pool size (5).
|
|
6
|
+
Transaction support deferred to Beta. Configurable pool size deferred to Beta.
|
|
7
|
+
|
|
8
|
+
All queries MUST use parameterized statements for SQL injection protection.
|
|
9
|
+
|
|
10
|
+
Envelope-Based Routing:
|
|
11
|
+
This handler uses envelope-based operation routing. See CLAUDE.md section
|
|
12
|
+
"Intent Model Architecture > Envelope-Based Handler Routing" for the full
|
|
13
|
+
design pattern and how orchestrators translate intents to handler envelopes.
|
|
14
|
+
|
|
15
|
+
Single-Statement SQL Limitation
|
|
16
|
+
===============================
|
|
17
|
+
|
|
18
|
+
This handler uses asyncpg's ``execute()`` and ``fetch()`` methods, which only
|
|
19
|
+
support **single SQL statements per call**. Multi-statement SQL (statements
|
|
20
|
+
separated by semicolons) is NOT supported and will raise an error.
|
|
21
|
+
|
|
22
|
+
**Example - Incorrect (will fail):**
|
|
23
|
+
|
|
24
|
+
.. code-block:: python
|
|
25
|
+
|
|
26
|
+
# This will fail - multiple statements in one call
|
|
27
|
+
envelope = {
|
|
28
|
+
"operation": "db.execute",
|
|
29
|
+
"payload": {
|
|
30
|
+
"sql": "CREATE TABLE foo (id INT); INSERT INTO foo VALUES (1);",
|
|
31
|
+
"parameters": [],
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
**Example - Correct (split into separate calls):**
|
|
36
|
+
|
|
37
|
+
.. code-block:: python
|
|
38
|
+
|
|
39
|
+
# Execute each statement separately
|
|
40
|
+
create_envelope = {
|
|
41
|
+
"operation": "db.execute",
|
|
42
|
+
"payload": {"sql": "CREATE TABLE foo (id INT)", "parameters": []},
|
|
43
|
+
}
|
|
44
|
+
await handler.execute(create_envelope)
|
|
45
|
+
|
|
46
|
+
insert_envelope = {
|
|
47
|
+
"operation": "db.execute",
|
|
48
|
+
"payload": {"sql": "INSERT INTO foo VALUES (1)", "parameters": []},
|
|
49
|
+
}
|
|
50
|
+
await handler.execute(insert_envelope)
|
|
51
|
+
|
|
52
|
+
This is a deliberate design choice for security and clarity:
|
|
53
|
+
1. Prevents SQL injection through statement concatenation
|
|
54
|
+
2. Provides clear error attribution per statement
|
|
55
|
+
3. Enables proper row count tracking per operation
|
|
56
|
+
4. Aligns with asyncpg's native API design
|
|
57
|
+
|
|
58
|
+
For multi-statement operations requiring atomicity, use the ``db.transaction``
|
|
59
|
+
operation (planned for Beta release).
|
|
60
|
+
|
|
61
|
+
Note:
|
|
62
|
+
Environment variable configuration (ONEX_DB_POOL_SIZE, ONEX_DB_TIMEOUT) is parsed
|
|
63
|
+
at module import time, not at handler instantiation. This means:
|
|
64
|
+
|
|
65
|
+
- Changes to environment variables require application restart to take effect
|
|
66
|
+
- Tests should use ``unittest.mock.patch.dict(os.environ, ...)`` before importing,
|
|
67
|
+
or use ``importlib.reload()`` to re-import the module after patching
|
|
68
|
+
- This is an intentional design choice for startup-time validation
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
from __future__ import annotations
|
|
72
|
+
|
|
73
|
+
import logging
|
|
74
|
+
from uuid import UUID, uuid4
|
|
75
|
+
|
|
76
|
+
import asyncpg
|
|
77
|
+
|
|
78
|
+
from omnibase_core.container import ModelONEXContainer
|
|
79
|
+
from omnibase_core.models.dispatch import ModelHandlerOutput
|
|
80
|
+
from omnibase_core.types import JsonType
|
|
81
|
+
from omnibase_infra.enums import (
|
|
82
|
+
EnumHandlerType,
|
|
83
|
+
EnumHandlerTypeCategory,
|
|
84
|
+
EnumInfraTransportType,
|
|
85
|
+
EnumResponseStatus,
|
|
86
|
+
)
|
|
87
|
+
from omnibase_infra.errors import (
|
|
88
|
+
InfraAuthenticationError,
|
|
89
|
+
InfraConnectionError,
|
|
90
|
+
InfraTimeoutError,
|
|
91
|
+
ModelInfraErrorContext,
|
|
92
|
+
ModelTimeoutErrorContext,
|
|
93
|
+
RuntimeHostError,
|
|
94
|
+
)
|
|
95
|
+
from omnibase_infra.handlers.models import (
|
|
96
|
+
ModelDbDescribeResponse,
|
|
97
|
+
ModelDbQueryPayload,
|
|
98
|
+
ModelDbQueryResponse,
|
|
99
|
+
)
|
|
100
|
+
from omnibase_infra.mixins import MixinAsyncCircuitBreaker, MixinEnvelopeExtraction
|
|
101
|
+
from omnibase_infra.utils.util_env_parsing import parse_env_float, parse_env_int
|
|
102
|
+
|
|
103
|
+
logger = logging.getLogger(__name__)
|
|
104
|
+
|
|
105
|
+
# MVP pool size fixed at 5 connections.
|
|
106
|
+
# Note: Recommended range is 10-20 for production workloads.
|
|
107
|
+
# Configurable pool size deferred to Beta release.
|
|
108
|
+
_DEFAULT_POOL_SIZE: int = parse_env_int(
|
|
109
|
+
"ONEX_DB_POOL_SIZE",
|
|
110
|
+
5,
|
|
111
|
+
min_value=1,
|
|
112
|
+
max_value=100,
|
|
113
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
114
|
+
service_name="db_handler",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Handler ID for ModelHandlerOutput
|
|
118
|
+
HANDLER_ID_DB: str = "db-handler"
|
|
119
|
+
_DEFAULT_TIMEOUT_SECONDS: float = parse_env_float(
|
|
120
|
+
"ONEX_DB_TIMEOUT",
|
|
121
|
+
30.0,
|
|
122
|
+
min_value=0.1,
|
|
123
|
+
max_value=3600.0,
|
|
124
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
125
|
+
service_name="db_handler",
|
|
126
|
+
)
|
|
127
|
+
_SUPPORTED_OPERATIONS: frozenset[str] = frozenset({"db.query", "db.execute"})
|
|
128
|
+
|
|
129
|
+
# Error message prefixes for PostgreSQL errors
|
|
130
|
+
# Used by _map_postgres_error to build descriptive error messages
|
|
131
|
+
_POSTGRES_ERROR_PREFIXES: dict[type[asyncpg.PostgresError], str] = {
|
|
132
|
+
asyncpg.PostgresSyntaxError: "SQL syntax error",
|
|
133
|
+
asyncpg.UndefinedTableError: "Table not found",
|
|
134
|
+
asyncpg.UndefinedColumnError: "Column not found",
|
|
135
|
+
asyncpg.UniqueViolationError: "Unique constraint violation",
|
|
136
|
+
asyncpg.ForeignKeyViolationError: "Foreign key constraint violation",
|
|
137
|
+
asyncpg.NotNullViolationError: "Not null constraint violation",
|
|
138
|
+
asyncpg.CheckViolationError: "Check constraint violation",
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# PostgreSQL SQLSTATE class codes for error classification
|
|
142
|
+
# See: https://www.postgresql.org/docs/current/errcodes-appendix.html
|
|
143
|
+
#
|
|
144
|
+
# TRANSIENT errors (should trip circuit breaker):
|
|
145
|
+
# - Class 08: Connection Exception (database unreachable, connection lost)
|
|
146
|
+
# - Class 53: Insufficient Resources (out of memory, disk full, too many connections)
|
|
147
|
+
# - Class 57: Operator Intervention (admin shutdown, crash recovery, cannot connect now)
|
|
148
|
+
# - Class 58: System Error (I/O error, undefined file, duplicate file)
|
|
149
|
+
#
|
|
150
|
+
# PERMANENT errors (should NOT trip circuit breaker):
|
|
151
|
+
# - Class 23: Integrity Constraint Violation (FK, NOT NULL, unique, check)
|
|
152
|
+
# - Class 42: Syntax Error or Access Rule Violation (bad SQL, undefined table/column)
|
|
153
|
+
# - Class 28: Invalid Authorization Specification (bad credentials)
|
|
154
|
+
# - Class 22: Data Exception (division by zero, string data truncation)
|
|
155
|
+
# - Class 40: Transaction Rollback (serialization failure, deadlock detected)
|
|
156
|
+
#
|
|
157
|
+
# DESIGN DECISION: Classified as PERMANENT despite deadlocks being retry-able.
|
|
158
|
+
#
|
|
159
|
+
# Rationale:
|
|
160
|
+
# 1. Deadlocks indicate transaction contention, not infrastructure failure
|
|
161
|
+
# 2. The database is healthy - it correctly detected and resolved the deadlock
|
|
162
|
+
# 3. Retrying at application level (with backoff) typically succeeds
|
|
163
|
+
# 4. Tripping the circuit would block ALL queries, not just the conflicting ones
|
|
164
|
+
# 5. High deadlock rates indicate application design issues (lock ordering,
|
|
165
|
+
# transaction scope) that should be fixed in code, not masked by circuit breaker
|
|
166
|
+
#
|
|
167
|
+
# Note: If sustained deadlock storms occur, this is a symptom of application
|
|
168
|
+
# issues or schema contention that monitoring/alerting should surface, but
|
|
169
|
+
# the circuit breaker is not the right mitigation tool.
|
|
170
|
+
#
|
|
171
|
+
# The key insight: transient errors indicate the DATABASE INFRASTRUCTURE is unhealthy,
|
|
172
|
+
# while permanent errors indicate the QUERY/APPLICATION is invalid. The circuit breaker
|
|
173
|
+
# protects against infrastructure failures, not application bugs.
|
|
174
|
+
_TRANSIENT_SQLSTATE_CLASSES: frozenset[str] = frozenset({"08", "53", "57", "58"})
|
|
175
|
+
_PERMANENT_SQLSTATE_CLASSES: frozenset[str] = frozenset({"22", "23", "28", "42"})
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class HandlerDb(MixinAsyncCircuitBreaker, MixinEnvelopeExtraction):
|
|
179
|
+
"""PostgreSQL database handler using asyncpg connection pool (MVP: query, execute only).
|
|
180
|
+
|
|
181
|
+
Security Policy - DSN Handling:
|
|
182
|
+
The database connection string (DSN) contains sensitive credentials and is
|
|
183
|
+
treated as a secret throughout this handler. The following security measures
|
|
184
|
+
are enforced:
|
|
185
|
+
|
|
186
|
+
1. DSN is stored internally in ``_dsn`` but NEVER logged or exposed in errors
|
|
187
|
+
2. All error messages use generic descriptions (e.g., "check host and port")
|
|
188
|
+
rather than exposing connection details
|
|
189
|
+
3. The ``_sanitize_dsn()`` method is available if DSN info ever needs to be
|
|
190
|
+
logged for debugging, but should only be used in development environments
|
|
191
|
+
4. The ``describe()`` method returns capabilities without credentials
|
|
192
|
+
|
|
193
|
+
See CLAUDE.md "Error Sanitization Guidelines" for the full security policy
|
|
194
|
+
on what information is safe vs unsafe to include in errors and logs.
|
|
195
|
+
|
|
196
|
+
Production Database Safety:
|
|
197
|
+
When connecting to production databases, ensure the following safeguards:
|
|
198
|
+
|
|
199
|
+
1. **Use read-only credentials** when possible to prevent accidental mutations
|
|
200
|
+
2. **Connection isolation**: Use separate DSNs for read and write operations
|
|
201
|
+
3. **Query timeouts**: Configure appropriate timeouts to prevent long-running
|
|
202
|
+
queries from exhausting connection pools (default: 30 seconds)
|
|
203
|
+
4. **Pool limits**: Production workloads should use 10-20 connections
|
|
204
|
+
(currently fixed at 5 for MVP - see Beta roadmap)
|
|
205
|
+
5. **SSL/TLS**: Always use encrypted connections (sslmode=require/verify-full)
|
|
206
|
+
6. **Audit logging**: Enable PostgreSQL statement logging for compliance
|
|
207
|
+
7. **Connection pooling**: Consider PgBouncer for high-traffic scenarios
|
|
208
|
+
|
|
209
|
+
WARNING: This handler executes arbitrary SQL. Ensure all queries use
|
|
210
|
+
parameterized statements to prevent SQL injection. Multi-statement SQL
|
|
211
|
+
is intentionally blocked for security.
|
|
212
|
+
|
|
213
|
+
Circuit Breaker:
|
|
214
|
+
This handler uses MixinAsyncCircuitBreaker for connection resilience.
|
|
215
|
+
The circuit breaker is initialized after the connection pool is created
|
|
216
|
+
in the initialize() method. Only connection-related errors (PostgresConnectionError,
|
|
217
|
+
QueryCanceledError) trip the circuit - application errors (syntax errors,
|
|
218
|
+
missing tables/columns) do not affect the circuit state.
|
|
219
|
+
|
|
220
|
+
States:
|
|
221
|
+
- CLOSED: Normal operation, requests allowed
|
|
222
|
+
- OPEN: Circuit tripped after threshold failures, requests blocked
|
|
223
|
+
- HALF_OPEN: Testing recovery after reset timeout, limited requests allowed
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
def __init__(self, container: ModelONEXContainer) -> None:
|
|
227
|
+
"""Initialize HandlerDb with ONEX container for dependency injection.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
container: ONEX container for dependency injection.
|
|
231
|
+
"""
|
|
232
|
+
self._container = container
|
|
233
|
+
self._pool: asyncpg.Pool | None = None
|
|
234
|
+
self._pool_size: int = _DEFAULT_POOL_SIZE
|
|
235
|
+
self._timeout: float = _DEFAULT_TIMEOUT_SECONDS
|
|
236
|
+
self._initialized: bool = False
|
|
237
|
+
self._dsn: str = ""
|
|
238
|
+
self._circuit_breaker_initialized: bool = False
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def handler_type(self) -> EnumHandlerType:
|
|
242
|
+
"""Return the architectural role of this handler.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
EnumHandlerType.INFRA_HANDLER - This handler is an infrastructure
|
|
246
|
+
protocol/transport handler (as opposed to NODE_HANDLER for event
|
|
247
|
+
processing, PROJECTION_HANDLER for read models, or COMPUTE_HANDLER
|
|
248
|
+
for pure computation).
|
|
249
|
+
|
|
250
|
+
Note:
|
|
251
|
+
handler_type determines lifecycle, protocol selection, and runtime
|
|
252
|
+
invocation patterns. It answers "what is this handler in the architecture?"
|
|
253
|
+
|
|
254
|
+
See Also:
|
|
255
|
+
- handler_category: Behavioral classification (EFFECT/COMPUTE)
|
|
256
|
+
- docs/architecture/HANDLER_PROTOCOL_DRIVEN_ARCHITECTURE.md
|
|
257
|
+
"""
|
|
258
|
+
return EnumHandlerType.INFRA_HANDLER
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def handler_category(self) -> EnumHandlerTypeCategory:
|
|
262
|
+
"""Return the behavioral classification of this handler.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
EnumHandlerTypeCategory.EFFECT - This handler performs side-effecting
|
|
266
|
+
I/O operations (database queries and mutations). EFFECT handlers are
|
|
267
|
+
not deterministic and interact with external systems.
|
|
268
|
+
|
|
269
|
+
Note:
|
|
270
|
+
handler_category determines security rules, determinism guarantees,
|
|
271
|
+
replay safety, and permissions. It answers "how does this handler
|
|
272
|
+
behave at runtime?"
|
|
273
|
+
|
|
274
|
+
Categories:
|
|
275
|
+
- COMPUTE: Pure, deterministic transformations (no side effects)
|
|
276
|
+
- EFFECT: Side-effecting I/O (database, HTTP, service calls)
|
|
277
|
+
- NONDETERMINISTIC_COMPUTE: Pure but not deterministic (UUID, random)
|
|
278
|
+
|
|
279
|
+
See Also:
|
|
280
|
+
- handler_type: Architectural role (INFRA_HANDLER/NODE_HANDLER/etc.)
|
|
281
|
+
- docs/architecture/HANDLER_PROTOCOL_DRIVEN_ARCHITECTURE.md
|
|
282
|
+
"""
|
|
283
|
+
return EnumHandlerTypeCategory.EFFECT
|
|
284
|
+
|
|
285
|
+
async def initialize(self, config: dict[str, object]) -> None:
|
|
286
|
+
"""Initialize database connection pool with fixed size (5).
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
config: Configuration dict containing:
|
|
290
|
+
- dsn: PostgreSQL connection string (required)
|
|
291
|
+
- timeout: Optional timeout in seconds (default: 30.0)
|
|
292
|
+
|
|
293
|
+
Raises:
|
|
294
|
+
RuntimeHostError: If DSN is missing or pool creation fails.
|
|
295
|
+
"""
|
|
296
|
+
# Generate correlation_id for initialization tracing
|
|
297
|
+
init_correlation_id = uuid4()
|
|
298
|
+
|
|
299
|
+
logger.info(
|
|
300
|
+
"Initializing %s",
|
|
301
|
+
self.__class__.__name__,
|
|
302
|
+
extra={
|
|
303
|
+
"handler": self.__class__.__name__,
|
|
304
|
+
"correlation_id": str(init_correlation_id),
|
|
305
|
+
},
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
dsn = config.get("dsn")
|
|
309
|
+
if not isinstance(dsn, str) or not dsn:
|
|
310
|
+
ctx = ModelInfraErrorContext(
|
|
311
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
312
|
+
operation="initialize",
|
|
313
|
+
target_name="db_handler",
|
|
314
|
+
correlation_id=init_correlation_id,
|
|
315
|
+
)
|
|
316
|
+
raise RuntimeHostError(
|
|
317
|
+
"Missing or invalid 'dsn' in config - PostgreSQL connection string required",
|
|
318
|
+
context=ctx,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
timeout_raw = config.get("timeout", _DEFAULT_TIMEOUT_SECONDS)
|
|
322
|
+
if isinstance(timeout_raw, int | float):
|
|
323
|
+
self._timeout = float(timeout_raw)
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
self._pool = await asyncpg.create_pool(
|
|
327
|
+
dsn=dsn,
|
|
328
|
+
min_size=1,
|
|
329
|
+
max_size=self._pool_size,
|
|
330
|
+
command_timeout=self._timeout,
|
|
331
|
+
)
|
|
332
|
+
self._dsn = dsn
|
|
333
|
+
# Note: DSN stored internally but never logged or exposed in errors.
|
|
334
|
+
# Use _sanitize_dsn() if DSN info ever needs to be logged.
|
|
335
|
+
self._initialized = True
|
|
336
|
+
|
|
337
|
+
# Initialize circuit breaker after pool creation succeeds
|
|
338
|
+
self._init_circuit_breaker(
|
|
339
|
+
threshold=5,
|
|
340
|
+
reset_timeout=30.0,
|
|
341
|
+
service_name="db_handler",
|
|
342
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
343
|
+
)
|
|
344
|
+
self._circuit_breaker_initialized = True
|
|
345
|
+
|
|
346
|
+
logger.info(
|
|
347
|
+
"%s initialized successfully",
|
|
348
|
+
self.__class__.__name__,
|
|
349
|
+
extra={
|
|
350
|
+
"handler": self.__class__.__name__,
|
|
351
|
+
"pool_min_size": 1,
|
|
352
|
+
"pool_max_size": self._pool_size,
|
|
353
|
+
"timeout_seconds": self._timeout,
|
|
354
|
+
"correlation_id": str(init_correlation_id),
|
|
355
|
+
},
|
|
356
|
+
)
|
|
357
|
+
except asyncpg.InvalidPasswordError as e:
|
|
358
|
+
ctx = ModelInfraErrorContext(
|
|
359
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
360
|
+
operation="initialize",
|
|
361
|
+
target_name="db_handler",
|
|
362
|
+
correlation_id=init_correlation_id,
|
|
363
|
+
)
|
|
364
|
+
raise InfraAuthenticationError(
|
|
365
|
+
"Database authentication failed - check credentials", context=ctx
|
|
366
|
+
) from e
|
|
367
|
+
except asyncpg.InvalidCatalogNameError as e:
|
|
368
|
+
ctx = ModelInfraErrorContext(
|
|
369
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
370
|
+
operation="initialize",
|
|
371
|
+
target_name="db_handler",
|
|
372
|
+
correlation_id=init_correlation_id,
|
|
373
|
+
)
|
|
374
|
+
raise RuntimeHostError(
|
|
375
|
+
"Database not found - check database name", context=ctx
|
|
376
|
+
) from e
|
|
377
|
+
except OSError as e:
|
|
378
|
+
ctx = ModelInfraErrorContext(
|
|
379
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
380
|
+
operation="initialize",
|
|
381
|
+
target_name="db_handler",
|
|
382
|
+
correlation_id=init_correlation_id,
|
|
383
|
+
)
|
|
384
|
+
raise InfraConnectionError(
|
|
385
|
+
"Failed to connect to database - check host and port", context=ctx
|
|
386
|
+
) from e
|
|
387
|
+
except Exception as e:
|
|
388
|
+
ctx = ModelInfraErrorContext(
|
|
389
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
390
|
+
operation="initialize",
|
|
391
|
+
target_name="db_handler",
|
|
392
|
+
correlation_id=init_correlation_id,
|
|
393
|
+
)
|
|
394
|
+
raise RuntimeHostError(
|
|
395
|
+
f"Failed to initialize database pool: {type(e).__name__}", context=ctx
|
|
396
|
+
) from e
|
|
397
|
+
|
|
398
|
+
async def shutdown(self) -> None:
|
|
399
|
+
"""Close database connection pool and release resources."""
|
|
400
|
+
# Reset circuit breaker state
|
|
401
|
+
if self._circuit_breaker_initialized:
|
|
402
|
+
async with self._circuit_breaker_lock:
|
|
403
|
+
await self._reset_circuit_breaker()
|
|
404
|
+
self._circuit_breaker_initialized = False
|
|
405
|
+
|
|
406
|
+
if self._pool is not None:
|
|
407
|
+
await self._pool.close()
|
|
408
|
+
self._pool = None
|
|
409
|
+
self._initialized = False
|
|
410
|
+
logger.info("HandlerDb shutdown complete")
|
|
411
|
+
|
|
412
|
+
async def execute(
|
|
413
|
+
self, envelope: dict[str, object]
|
|
414
|
+
) -> ModelHandlerOutput[ModelDbQueryResponse]:
|
|
415
|
+
"""Execute database operation (db.query or db.execute) from envelope.
|
|
416
|
+
|
|
417
|
+
Args:
|
|
418
|
+
envelope: Request envelope containing:
|
|
419
|
+
- operation: "db.query" or "db.execute"
|
|
420
|
+
- payload: dict with "sql" (required) and "parameters" (optional list)
|
|
421
|
+
- correlation_id: Optional correlation ID for tracing
|
|
422
|
+
- envelope_id: Optional envelope ID for causality tracking
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
ModelHandlerOutput[ModelDbQueryResponse] containing:
|
|
426
|
+
- result: ModelDbQueryResponse with status, payload, and correlation_id
|
|
427
|
+
- input_envelope_id: UUID for causality tracking
|
|
428
|
+
- correlation_id: UUID for request/response correlation
|
|
429
|
+
- handler_id: "db-handler"
|
|
430
|
+
|
|
431
|
+
Raises:
|
|
432
|
+
RuntimeHostError: If handler not initialized or invalid input.
|
|
433
|
+
InfraConnectionError: If database connection fails.
|
|
434
|
+
InfraTimeoutError: If query times out.
|
|
435
|
+
"""
|
|
436
|
+
correlation_id = self._extract_correlation_id(envelope)
|
|
437
|
+
input_envelope_id = self._extract_envelope_id(envelope)
|
|
438
|
+
|
|
439
|
+
if not self._initialized or self._pool is None:
|
|
440
|
+
ctx = ModelInfraErrorContext(
|
|
441
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
442
|
+
operation="execute",
|
|
443
|
+
target_name="db_handler",
|
|
444
|
+
correlation_id=correlation_id,
|
|
445
|
+
)
|
|
446
|
+
raise RuntimeHostError(
|
|
447
|
+
"HandlerDb not initialized. Call initialize() first.", context=ctx
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
operation = envelope.get("operation")
|
|
451
|
+
if not isinstance(operation, str):
|
|
452
|
+
ctx = ModelInfraErrorContext(
|
|
453
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
454
|
+
operation="execute",
|
|
455
|
+
target_name="db_handler",
|
|
456
|
+
correlation_id=correlation_id,
|
|
457
|
+
)
|
|
458
|
+
raise RuntimeHostError(
|
|
459
|
+
"Missing or invalid 'operation' in envelope", context=ctx
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
if operation not in _SUPPORTED_OPERATIONS:
|
|
463
|
+
ctx = ModelInfraErrorContext(
|
|
464
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
465
|
+
operation=operation,
|
|
466
|
+
target_name="db_handler",
|
|
467
|
+
correlation_id=correlation_id,
|
|
468
|
+
)
|
|
469
|
+
raise RuntimeHostError(
|
|
470
|
+
f"Operation '{operation}' not supported in MVP. Available: {', '.join(sorted(_SUPPORTED_OPERATIONS))}",
|
|
471
|
+
context=ctx,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
payload = envelope.get("payload")
|
|
475
|
+
if not isinstance(payload, dict):
|
|
476
|
+
ctx = ModelInfraErrorContext(
|
|
477
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
478
|
+
operation=operation,
|
|
479
|
+
target_name="db_handler",
|
|
480
|
+
correlation_id=correlation_id,
|
|
481
|
+
)
|
|
482
|
+
raise RuntimeHostError(
|
|
483
|
+
"Missing or invalid 'payload' in envelope", context=ctx
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
sql = payload.get("sql")
|
|
487
|
+
if not isinstance(sql, str) or not sql.strip():
|
|
488
|
+
ctx = ModelInfraErrorContext(
|
|
489
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
490
|
+
operation=operation,
|
|
491
|
+
target_name="db_handler",
|
|
492
|
+
correlation_id=correlation_id,
|
|
493
|
+
)
|
|
494
|
+
raise RuntimeHostError("Missing or invalid 'sql' in payload", context=ctx)
|
|
495
|
+
|
|
496
|
+
parameters = self._extract_parameters(payload, operation, correlation_id)
|
|
497
|
+
|
|
498
|
+
if operation == "db.query":
|
|
499
|
+
return await self._execute_query(
|
|
500
|
+
sql, parameters, correlation_id, input_envelope_id
|
|
501
|
+
)
|
|
502
|
+
else: # db.execute
|
|
503
|
+
return await self._execute_statement(
|
|
504
|
+
sql, parameters, correlation_id, input_envelope_id
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def _sanitize_dsn(self, dsn: str) -> str:
|
|
508
|
+
"""Sanitize DSN by removing password for safe logging.
|
|
509
|
+
|
|
510
|
+
SECURITY: This method exists to support debugging scenarios where
|
|
511
|
+
connection information may be helpful, while ensuring credentials
|
|
512
|
+
are never exposed. The raw DSN should NEVER be logged directly.
|
|
513
|
+
|
|
514
|
+
Uses urllib.parse for robust parsing instead of regex, handling
|
|
515
|
+
edge cases like IPv6 addresses and URL-encoded passwords.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
dsn: Raw PostgreSQL connection string containing credentials.
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
Sanitized DSN with password replaced by '***'.
|
|
522
|
+
|
|
523
|
+
Example:
|
|
524
|
+
>>> handler._sanitize_dsn("postgresql://user:secret@host:5432/db")
|
|
525
|
+
'postgresql://user:***@host:5432/db'
|
|
526
|
+
|
|
527
|
+
>>> handler._sanitize_dsn("postgresql://user:p%40ss@[::1]:5432/db")
|
|
528
|
+
'postgresql://user:***@[::1]:5432/db'
|
|
529
|
+
|
|
530
|
+
Note:
|
|
531
|
+
This method is intentionally NOT used in production error paths.
|
|
532
|
+
It exists as a utility for development/debugging only. See class
|
|
533
|
+
docstring "Security Policy - DSN Handling" for full policy.
|
|
534
|
+
"""
|
|
535
|
+
from omnibase_infra.utils.util_dsn_validation import sanitize_dsn
|
|
536
|
+
|
|
537
|
+
return sanitize_dsn(dsn)
|
|
538
|
+
|
|
539
|
+
def _extract_parameters(
|
|
540
|
+
self, payload: dict[str, object], operation: str, correlation_id: UUID
|
|
541
|
+
) -> list[object]:
|
|
542
|
+
"""Extract and validate parameters from payload."""
|
|
543
|
+
params_raw = payload.get("parameters")
|
|
544
|
+
if params_raw is None:
|
|
545
|
+
return []
|
|
546
|
+
if isinstance(params_raw, list):
|
|
547
|
+
return list(params_raw)
|
|
548
|
+
ctx = ModelInfraErrorContext(
|
|
549
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
550
|
+
operation=operation,
|
|
551
|
+
target_name="db_handler",
|
|
552
|
+
correlation_id=correlation_id,
|
|
553
|
+
)
|
|
554
|
+
raise RuntimeHostError(
|
|
555
|
+
"Invalid 'parameters' in payload - must be a list", context=ctx
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
def _is_transient_error(self, error: asyncpg.PostgresError) -> bool:
|
|
559
|
+
"""Determine if a PostgreSQL error is transient (should trip circuit breaker).
|
|
560
|
+
|
|
561
|
+
Transient errors indicate infrastructure issues where retrying later may succeed:
|
|
562
|
+
- Connection failures (Class 08)
|
|
563
|
+
- Resource exhaustion (Class 53)
|
|
564
|
+
- Server shutdown/restart (Class 57)
|
|
565
|
+
- System I/O errors (Class 58)
|
|
566
|
+
|
|
567
|
+
Permanent errors indicate application/query bugs that won't be fixed by retrying:
|
|
568
|
+
- Constraint violations (Class 23): FK, NOT NULL, unique, check
|
|
569
|
+
- Syntax errors (Class 42): bad SQL, undefined table/column
|
|
570
|
+
- Authorization failures (Class 28): invalid credentials
|
|
571
|
+
- Data exceptions (Class 22): division by zero, truncation
|
|
572
|
+
|
|
573
|
+
The circuit breaker ONLY trips on transient errors because:
|
|
574
|
+
1. Transient errors suggest the database infrastructure is unhealthy
|
|
575
|
+
2. Opening the circuit prevents cascading failures and gives the DB time to recover
|
|
576
|
+
3. Permanent errors are application bugs that should be fixed in code, not retried
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
error: The asyncpg PostgresError exception to classify.
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
True if the error is transient (should increment circuit breaker failure count),
|
|
583
|
+
False if the error is permanent (should NOT affect circuit breaker state).
|
|
584
|
+
|
|
585
|
+
Examples:
|
|
586
|
+
>>> # Connection lost - transient, should trip circuit
|
|
587
|
+
>>> handler._is_transient_error(asyncpg.PostgresConnectionError())
|
|
588
|
+
True
|
|
589
|
+
|
|
590
|
+
>>> # FK violation - permanent, should NOT trip circuit
|
|
591
|
+
>>> handler._is_transient_error(asyncpg.ForeignKeyViolationError())
|
|
592
|
+
False
|
|
593
|
+
"""
|
|
594
|
+
# Get SQLSTATE code from exception (5-character code like '23503')
|
|
595
|
+
sqlstate = getattr(error, "sqlstate", None)
|
|
596
|
+
|
|
597
|
+
if sqlstate is None:
|
|
598
|
+
# No SQLSTATE available - fall back to exception type classification
|
|
599
|
+
# Connection-related errors are always transient
|
|
600
|
+
if isinstance(error, asyncpg.PostgresConnectionError):
|
|
601
|
+
return True
|
|
602
|
+
# Query canceled (timeout) is transient - indicates server overload
|
|
603
|
+
if isinstance(error, asyncpg.QueryCanceledError):
|
|
604
|
+
return True
|
|
605
|
+
# Default: assume permanent (don't trip circuit for unknown errors)
|
|
606
|
+
# This is conservative - we'd rather miss a transient error than
|
|
607
|
+
# incorrectly trip the circuit on application bugs
|
|
608
|
+
logger.debug(
|
|
609
|
+
"No SQLSTATE for PostgreSQL error, defaulting to permanent classification",
|
|
610
|
+
extra={
|
|
611
|
+
"error_type": type(error).__name__,
|
|
612
|
+
},
|
|
613
|
+
)
|
|
614
|
+
return False
|
|
615
|
+
|
|
616
|
+
# Extract class code (first 2 characters of SQLSTATE)
|
|
617
|
+
# e.g., '23503' -> '23' (Integrity Constraint Violation)
|
|
618
|
+
sqlstate_class = sqlstate[:2]
|
|
619
|
+
|
|
620
|
+
# Check if class is explicitly transient
|
|
621
|
+
if sqlstate_class in _TRANSIENT_SQLSTATE_CLASSES:
|
|
622
|
+
logger.debug(
|
|
623
|
+
"Classified PostgreSQL error as TRANSIENT (will trip circuit)",
|
|
624
|
+
extra={
|
|
625
|
+
"sqlstate": sqlstate,
|
|
626
|
+
"sqlstate_class": sqlstate_class,
|
|
627
|
+
"error_type": type(error).__name__,
|
|
628
|
+
},
|
|
629
|
+
)
|
|
630
|
+
return True
|
|
631
|
+
|
|
632
|
+
# Check if class is explicitly permanent
|
|
633
|
+
if sqlstate_class in _PERMANENT_SQLSTATE_CLASSES:
|
|
634
|
+
logger.debug(
|
|
635
|
+
"Classified PostgreSQL error as PERMANENT (will NOT trip circuit)",
|
|
636
|
+
extra={
|
|
637
|
+
"sqlstate": sqlstate,
|
|
638
|
+
"sqlstate_class": sqlstate_class,
|
|
639
|
+
"error_type": type(error).__name__,
|
|
640
|
+
},
|
|
641
|
+
)
|
|
642
|
+
return False
|
|
643
|
+
|
|
644
|
+
# Unknown class - log warning and default to permanent (conservative)
|
|
645
|
+
# Unknown errors are more likely to be application bugs than infrastructure issues
|
|
646
|
+
logger.warning(
|
|
647
|
+
"Unknown PostgreSQL SQLSTATE class, defaulting to permanent classification",
|
|
648
|
+
extra={
|
|
649
|
+
"sqlstate": sqlstate,
|
|
650
|
+
"sqlstate_class": sqlstate_class,
|
|
651
|
+
"error_type": type(error).__name__,
|
|
652
|
+
},
|
|
653
|
+
)
|
|
654
|
+
return False
|
|
655
|
+
|
|
656
|
+
async def _execute_query(
|
|
657
|
+
self,
|
|
658
|
+
sql: str,
|
|
659
|
+
parameters: list[object],
|
|
660
|
+
correlation_id: UUID,
|
|
661
|
+
input_envelope_id: UUID,
|
|
662
|
+
) -> ModelHandlerOutput[ModelDbQueryResponse]:
|
|
663
|
+
"""Execute SELECT query and return rows."""
|
|
664
|
+
if self._pool is None:
|
|
665
|
+
ctx = ModelInfraErrorContext(
|
|
666
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
667
|
+
operation="db.query",
|
|
668
|
+
target_name="db_handler",
|
|
669
|
+
correlation_id=correlation_id,
|
|
670
|
+
)
|
|
671
|
+
raise RuntimeHostError(
|
|
672
|
+
"HandlerDb not initialized - call initialize() first", context=ctx
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
ctx = ModelInfraErrorContext(
|
|
676
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
677
|
+
operation="db.query",
|
|
678
|
+
target_name="db_handler",
|
|
679
|
+
correlation_id=correlation_id,
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
# Check circuit breaker before operation
|
|
683
|
+
if self._circuit_breaker_initialized:
|
|
684
|
+
async with self._circuit_breaker_lock:
|
|
685
|
+
await self._check_circuit_breaker(
|
|
686
|
+
operation="db.query",
|
|
687
|
+
correlation_id=correlation_id,
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
try:
|
|
691
|
+
async with self._pool.acquire() as conn:
|
|
692
|
+
rows = await conn.fetch(sql, *parameters)
|
|
693
|
+
|
|
694
|
+
# Reset circuit breaker immediately after successful operation,
|
|
695
|
+
# within connection context to avoid race conditions
|
|
696
|
+
if self._circuit_breaker_initialized:
|
|
697
|
+
async with self._circuit_breaker_lock:
|
|
698
|
+
await self._reset_circuit_breaker()
|
|
699
|
+
|
|
700
|
+
return self._build_response(
|
|
701
|
+
[dict(row) for row in rows],
|
|
702
|
+
len(rows),
|
|
703
|
+
correlation_id,
|
|
704
|
+
input_envelope_id,
|
|
705
|
+
)
|
|
706
|
+
except asyncpg.QueryCanceledError as e:
|
|
707
|
+
# Record failure for timeout errors (database overloaded)
|
|
708
|
+
if self._circuit_breaker_initialized:
|
|
709
|
+
async with self._circuit_breaker_lock:
|
|
710
|
+
await self._record_circuit_failure(
|
|
711
|
+
operation="db.query",
|
|
712
|
+
correlation_id=correlation_id,
|
|
713
|
+
)
|
|
714
|
+
timeout_ctx = ModelTimeoutErrorContext(
|
|
715
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
716
|
+
operation="db.query",
|
|
717
|
+
target_name="db_handler",
|
|
718
|
+
correlation_id=correlation_id,
|
|
719
|
+
timeout_seconds=self._timeout,
|
|
720
|
+
)
|
|
721
|
+
raise InfraTimeoutError(
|
|
722
|
+
f"Query timed out after {self._timeout}s",
|
|
723
|
+
context=timeout_ctx,
|
|
724
|
+
) from e
|
|
725
|
+
except asyncpg.PostgresConnectionError as e:
|
|
726
|
+
# Record failure for connection errors (database unavailable)
|
|
727
|
+
if self._circuit_breaker_initialized:
|
|
728
|
+
async with self._circuit_breaker_lock:
|
|
729
|
+
await self._record_circuit_failure(
|
|
730
|
+
operation="db.query",
|
|
731
|
+
correlation_id=correlation_id,
|
|
732
|
+
)
|
|
733
|
+
raise InfraConnectionError(
|
|
734
|
+
"Database connection lost during query", context=ctx
|
|
735
|
+
) from e
|
|
736
|
+
except asyncpg.PostgresSyntaxError as e:
|
|
737
|
+
# Application error - do NOT trip circuit
|
|
738
|
+
raise RuntimeHostError(f"SQL syntax error: {e.message}", context=ctx) from e
|
|
739
|
+
except asyncpg.UndefinedTableError as e:
|
|
740
|
+
# Application error - do NOT trip circuit
|
|
741
|
+
raise RuntimeHostError(f"Table not found: {e.message}", context=ctx) from e
|
|
742
|
+
except asyncpg.UndefinedColumnError as e:
|
|
743
|
+
# Application error - do NOT trip circuit
|
|
744
|
+
raise RuntimeHostError(f"Column not found: {e.message}", context=ctx) from e
|
|
745
|
+
except asyncpg.ForeignKeyViolationError as e:
|
|
746
|
+
# Application error - do NOT trip circuit
|
|
747
|
+
raise RuntimeHostError(
|
|
748
|
+
f"Foreign key constraint violation: {e.message}", context=ctx
|
|
749
|
+
) from e
|
|
750
|
+
except asyncpg.NotNullViolationError as e:
|
|
751
|
+
# Application error - do NOT trip circuit
|
|
752
|
+
raise RuntimeHostError(
|
|
753
|
+
f"Not null constraint violation: {e.message}", context=ctx
|
|
754
|
+
) from e
|
|
755
|
+
except asyncpg.UniqueViolationError as e:
|
|
756
|
+
# Application error - do NOT trip circuit
|
|
757
|
+
raise RuntimeHostError(
|
|
758
|
+
f"Unique constraint violation: {e.message}", context=ctx
|
|
759
|
+
) from e
|
|
760
|
+
except asyncpg.PostgresError as e:
|
|
761
|
+
# Generic PostgreSQL error - use intelligent classification based on SQLSTATE
|
|
762
|
+
# to determine if this is a transient infrastructure issue or permanent app bug
|
|
763
|
+
if self._is_transient_error(e):
|
|
764
|
+
# Transient error (e.g., resource exhaustion, system error)
|
|
765
|
+
# Record failure to potentially trip circuit breaker
|
|
766
|
+
if self._circuit_breaker_initialized:
|
|
767
|
+
async with self._circuit_breaker_lock:
|
|
768
|
+
await self._record_circuit_failure(
|
|
769
|
+
operation="db.query",
|
|
770
|
+
correlation_id=correlation_id,
|
|
771
|
+
)
|
|
772
|
+
# Re-raise as RuntimeHostError regardless of classification
|
|
773
|
+
raise RuntimeHostError(
|
|
774
|
+
f"Database error: {type(e).__name__}", context=ctx
|
|
775
|
+
) from e
|
|
776
|
+
|
|
777
|
+
async def _execute_statement(
|
|
778
|
+
self,
|
|
779
|
+
sql: str,
|
|
780
|
+
parameters: list[object],
|
|
781
|
+
correlation_id: UUID,
|
|
782
|
+
input_envelope_id: UUID,
|
|
783
|
+
) -> ModelHandlerOutput[ModelDbQueryResponse]:
|
|
784
|
+
"""Execute INSERT/UPDATE/DELETE statement and return affected row count."""
|
|
785
|
+
if self._pool is None:
|
|
786
|
+
ctx = ModelInfraErrorContext(
|
|
787
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
788
|
+
operation="db.execute",
|
|
789
|
+
target_name="db_handler",
|
|
790
|
+
correlation_id=correlation_id,
|
|
791
|
+
)
|
|
792
|
+
raise RuntimeHostError(
|
|
793
|
+
"HandlerDb not initialized - call initialize() first", context=ctx
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
ctx = ModelInfraErrorContext(
|
|
797
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
798
|
+
operation="db.execute",
|
|
799
|
+
target_name="db_handler",
|
|
800
|
+
correlation_id=correlation_id,
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
# Check circuit breaker before operation
|
|
804
|
+
if self._circuit_breaker_initialized:
|
|
805
|
+
async with self._circuit_breaker_lock:
|
|
806
|
+
await self._check_circuit_breaker(
|
|
807
|
+
operation="db.execute",
|
|
808
|
+
correlation_id=correlation_id,
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
try:
|
|
812
|
+
async with self._pool.acquire() as conn:
|
|
813
|
+
result = await conn.execute(sql, *parameters)
|
|
814
|
+
|
|
815
|
+
# Reset circuit breaker immediately after successful operation,
|
|
816
|
+
# within connection context to avoid race conditions
|
|
817
|
+
if self._circuit_breaker_initialized:
|
|
818
|
+
async with self._circuit_breaker_lock:
|
|
819
|
+
await self._reset_circuit_breaker()
|
|
820
|
+
|
|
821
|
+
# asyncpg returns string like "INSERT 0 1" or "UPDATE 5"
|
|
822
|
+
row_count = self._parse_row_count(result)
|
|
823
|
+
return self._build_response(
|
|
824
|
+
[], row_count, correlation_id, input_envelope_id
|
|
825
|
+
)
|
|
826
|
+
except asyncpg.QueryCanceledError as e:
|
|
827
|
+
# Record failure for timeout errors (database overloaded)
|
|
828
|
+
if self._circuit_breaker_initialized:
|
|
829
|
+
async with self._circuit_breaker_lock:
|
|
830
|
+
await self._record_circuit_failure(
|
|
831
|
+
operation="db.execute",
|
|
832
|
+
correlation_id=correlation_id,
|
|
833
|
+
)
|
|
834
|
+
timeout_ctx = ModelTimeoutErrorContext(
|
|
835
|
+
transport_type=EnumInfraTransportType.DATABASE,
|
|
836
|
+
operation="db.execute",
|
|
837
|
+
target_name="db_handler",
|
|
838
|
+
correlation_id=correlation_id,
|
|
839
|
+
timeout_seconds=self._timeout,
|
|
840
|
+
)
|
|
841
|
+
raise InfraTimeoutError(
|
|
842
|
+
f"Statement timed out after {self._timeout}s",
|
|
843
|
+
context=timeout_ctx,
|
|
844
|
+
) from e
|
|
845
|
+
except asyncpg.PostgresConnectionError as e:
|
|
846
|
+
# Record failure for connection errors (database unavailable)
|
|
847
|
+
if self._circuit_breaker_initialized:
|
|
848
|
+
async with self._circuit_breaker_lock:
|
|
849
|
+
await self._record_circuit_failure(
|
|
850
|
+
operation="db.execute",
|
|
851
|
+
correlation_id=correlation_id,
|
|
852
|
+
)
|
|
853
|
+
raise InfraConnectionError(
|
|
854
|
+
"Database connection lost during statement execution", context=ctx
|
|
855
|
+
) from e
|
|
856
|
+
except asyncpg.PostgresSyntaxError as e:
|
|
857
|
+
# Application error - do NOT trip circuit
|
|
858
|
+
raise RuntimeHostError(f"SQL syntax error: {e.message}", context=ctx) from e
|
|
859
|
+
except asyncpg.UndefinedTableError as e:
|
|
860
|
+
# Application error - do NOT trip circuit
|
|
861
|
+
raise RuntimeHostError(f"Table not found: {e.message}", context=ctx) from e
|
|
862
|
+
except asyncpg.UndefinedColumnError as e:
|
|
863
|
+
# Application error - do NOT trip circuit
|
|
864
|
+
raise RuntimeHostError(f"Column not found: {e.message}", context=ctx) from e
|
|
865
|
+
except asyncpg.ForeignKeyViolationError as e:
|
|
866
|
+
# Application error - do NOT trip circuit
|
|
867
|
+
raise RuntimeHostError(
|
|
868
|
+
f"Foreign key constraint violation: {e.message}", context=ctx
|
|
869
|
+
) from e
|
|
870
|
+
except asyncpg.NotNullViolationError as e:
|
|
871
|
+
# Application error - do NOT trip circuit
|
|
872
|
+
raise RuntimeHostError(
|
|
873
|
+
f"Not null constraint violation: {e.message}", context=ctx
|
|
874
|
+
) from e
|
|
875
|
+
except asyncpg.UniqueViolationError as e:
|
|
876
|
+
# Application error - do NOT trip circuit
|
|
877
|
+
raise RuntimeHostError(
|
|
878
|
+
f"Unique constraint violation: {e.message}", context=ctx
|
|
879
|
+
) from e
|
|
880
|
+
except asyncpg.PostgresError as e:
|
|
881
|
+
# Generic PostgreSQL error - use intelligent classification based on SQLSTATE
|
|
882
|
+
# to determine if this is a transient infrastructure issue or permanent app bug
|
|
883
|
+
if self._is_transient_error(e):
|
|
884
|
+
# Transient error (e.g., resource exhaustion, system error)
|
|
885
|
+
# Record failure to potentially trip circuit breaker
|
|
886
|
+
if self._circuit_breaker_initialized:
|
|
887
|
+
async with self._circuit_breaker_lock:
|
|
888
|
+
await self._record_circuit_failure(
|
|
889
|
+
operation="db.execute",
|
|
890
|
+
correlation_id=correlation_id,
|
|
891
|
+
)
|
|
892
|
+
# Re-raise as RuntimeHostError regardless of classification
|
|
893
|
+
raise RuntimeHostError(
|
|
894
|
+
f"Database error: {type(e).__name__}", context=ctx
|
|
895
|
+
) from e
|
|
896
|
+
|
|
897
|
+
def _parse_row_count(self, result: str) -> int:
|
|
898
|
+
"""Parse row count from asyncpg execute result string.
|
|
899
|
+
|
|
900
|
+
asyncpg returns strings like:
|
|
901
|
+
- "INSERT 0 1" -> 1 row inserted
|
|
902
|
+
- "UPDATE 5" -> 5 rows updated
|
|
903
|
+
- "DELETE 3" -> 3 rows deleted
|
|
904
|
+
"""
|
|
905
|
+
try:
|
|
906
|
+
parts = result.split()
|
|
907
|
+
if len(parts) >= 2:
|
|
908
|
+
return int(parts[-1])
|
|
909
|
+
except (ValueError, IndexError):
|
|
910
|
+
pass
|
|
911
|
+
return 0
|
|
912
|
+
|
|
913
|
+
def _map_postgres_error(
|
|
914
|
+
self,
|
|
915
|
+
exc: asyncpg.PostgresError,
|
|
916
|
+
ctx: ModelInfraErrorContext,
|
|
917
|
+
) -> RuntimeHostError | InfraTimeoutError | InfraConnectionError:
|
|
918
|
+
"""Map asyncpg exception to ONEX infrastructure error.
|
|
919
|
+
|
|
920
|
+
This helper reduces complexity of _execute_statement and _execute_query
|
|
921
|
+
by centralizing exception-to-error mapping logic.
|
|
922
|
+
|
|
923
|
+
Args:
|
|
924
|
+
exc: The asyncpg exception that was raised.
|
|
925
|
+
ctx: Error context with transport type, operation, and correlation ID.
|
|
926
|
+
|
|
927
|
+
Returns:
|
|
928
|
+
Appropriate ONEX infrastructure error based on exception type.
|
|
929
|
+
"""
|
|
930
|
+
exc_type = type(exc)
|
|
931
|
+
|
|
932
|
+
# Special cases requiring specific error types or additional arguments
|
|
933
|
+
if exc_type is asyncpg.QueryCanceledError:
|
|
934
|
+
# Convert ModelInfraErrorContext to ModelTimeoutErrorContext for stricter typing
|
|
935
|
+
timeout_ctx = ModelTimeoutErrorContext(
|
|
936
|
+
transport_type=ctx.transport_type or EnumInfraTransportType.DATABASE,
|
|
937
|
+
operation=ctx.operation or "db.statement",
|
|
938
|
+
target_name=ctx.target_name,
|
|
939
|
+
correlation_id=ctx.correlation_id,
|
|
940
|
+
timeout_seconds=self._timeout,
|
|
941
|
+
)
|
|
942
|
+
return InfraTimeoutError(
|
|
943
|
+
f"Statement timed out after {self._timeout}s",
|
|
944
|
+
context=timeout_ctx,
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
if exc_type is asyncpg.PostgresConnectionError:
|
|
948
|
+
return InfraConnectionError(
|
|
949
|
+
"Database connection lost during statement execution",
|
|
950
|
+
context=ctx,
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
# All other errors map to RuntimeHostError with descriptive message
|
|
954
|
+
prefix = _POSTGRES_ERROR_PREFIXES.get(exc_type, "Database error")
|
|
955
|
+
# Use message attribute if available and non-empty, else use type name
|
|
956
|
+
message = getattr(exc, "message", None) or type(exc).__name__
|
|
957
|
+
return RuntimeHostError(f"{prefix}: {message}", context=ctx)
|
|
958
|
+
|
|
959
|
+
def _build_response(
|
|
960
|
+
self,
|
|
961
|
+
rows: list[dict[str, object]],
|
|
962
|
+
row_count: int,
|
|
963
|
+
correlation_id: UUID,
|
|
964
|
+
input_envelope_id: UUID,
|
|
965
|
+
) -> ModelHandlerOutput[ModelDbQueryResponse]:
|
|
966
|
+
"""Build response wrapped in ModelHandlerOutput from query/execute result."""
|
|
967
|
+
result = ModelDbQueryResponse(
|
|
968
|
+
status=EnumResponseStatus.SUCCESS,
|
|
969
|
+
payload=ModelDbQueryPayload(rows=rows, row_count=row_count),
|
|
970
|
+
correlation_id=correlation_id,
|
|
971
|
+
)
|
|
972
|
+
return ModelHandlerOutput.for_compute(
|
|
973
|
+
input_envelope_id=input_envelope_id,
|
|
974
|
+
correlation_id=correlation_id,
|
|
975
|
+
handler_id=HANDLER_ID_DB,
|
|
976
|
+
result=result,
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
def describe(self) -> ModelDbDescribeResponse:
|
|
980
|
+
"""Return handler metadata and capabilities for introspection.
|
|
981
|
+
|
|
982
|
+
This method exposes the handler's type classification along with its
|
|
983
|
+
operational configuration and capabilities.
|
|
984
|
+
|
|
985
|
+
Returns:
|
|
986
|
+
ModelDbDescribeResponse containing:
|
|
987
|
+
- handler_type: Architectural role from handler_type property
|
|
988
|
+
(e.g., "infra_handler"). See EnumHandlerType for valid values.
|
|
989
|
+
- handler_category: Behavioral classification from handler_category
|
|
990
|
+
property (e.g., "effect"). See EnumHandlerTypeCategory for valid values.
|
|
991
|
+
- supported_operations: List of supported operations
|
|
992
|
+
- pool_size: Connection pool size
|
|
993
|
+
- timeout_seconds: Query timeout in seconds
|
|
994
|
+
- initialized: Whether the handler is initialized
|
|
995
|
+
- version: Handler version string
|
|
996
|
+
|
|
997
|
+
Note:
|
|
998
|
+
The handler_type and handler_category fields form the handler
|
|
999
|
+
classification system:
|
|
1000
|
+
|
|
1001
|
+
1. handler_type (architectural role): Determines lifecycle and invocation
|
|
1002
|
+
patterns. This handler is INFRA_HANDLER (protocol/transport handler).
|
|
1003
|
+
|
|
1004
|
+
2. handler_category (behavioral classification): Determines security rules
|
|
1005
|
+
and replay safety. This handler is EFFECT (side-effecting I/O).
|
|
1006
|
+
|
|
1007
|
+
The transport type for this handler is DATABASE (PostgreSQL).
|
|
1008
|
+
|
|
1009
|
+
Security Consideration:
|
|
1010
|
+
The circuit_breaker field exposes operational state including failure counts.
|
|
1011
|
+
This information is intended for internal monitoring and observability.
|
|
1012
|
+
|
|
1013
|
+
WARNING: If describe() is exposed via external APIs, failure counts could
|
|
1014
|
+
reveal information useful to attackers (e.g., timing attacks when circuit
|
|
1015
|
+
is about to open). For external exposure, consider:
|
|
1016
|
+
|
|
1017
|
+
1. Restricting describe() to authenticated admin/monitoring endpoints only
|
|
1018
|
+
2. Sanitizing output to show only state (OPEN/CLOSED/HALF_OPEN), not counts
|
|
1019
|
+
3. Rate-limiting describe() calls to prevent information harvesting
|
|
1020
|
+
|
|
1021
|
+
The current implementation assumes describe() is used for internal
|
|
1022
|
+
monitoring dashboards and health checks, not public APIs.
|
|
1023
|
+
|
|
1024
|
+
See Also:
|
|
1025
|
+
- handler_type property: Full documentation of architectural role
|
|
1026
|
+
- handler_category property: Full documentation of behavioral classification
|
|
1027
|
+
- docs/architecture/HANDLER_PROTOCOL_DRIVEN_ARCHITECTURE.md
|
|
1028
|
+
"""
|
|
1029
|
+
# Get circuit breaker state if initialized
|
|
1030
|
+
cb_state: dict[str, JsonType] | None = None
|
|
1031
|
+
if self._circuit_breaker_initialized:
|
|
1032
|
+
cb_state = self._get_circuit_breaker_state()
|
|
1033
|
+
|
|
1034
|
+
return ModelDbDescribeResponse(
|
|
1035
|
+
handler_type=self.handler_type.value,
|
|
1036
|
+
handler_category=self.handler_category.value,
|
|
1037
|
+
supported_operations=sorted(_SUPPORTED_OPERATIONS),
|
|
1038
|
+
pool_size=self._pool_size,
|
|
1039
|
+
timeout_seconds=self._timeout,
|
|
1040
|
+
initialized=self._initialized,
|
|
1041
|
+
version="0.1.0-mvp",
|
|
1042
|
+
circuit_breaker=cb_state,
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
__all__: list[str] = ["HandlerDb"]
|