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,1539 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Manifest Persistence Handler - Stores execution manifests to filesystem.
|
|
4
|
+
|
|
5
|
+
This handler persists ModelExecutionManifest objects to the filesystem with
|
|
6
|
+
date-based partitioning, atomic writes, and query support.
|
|
7
|
+
|
|
8
|
+
Supported Operations:
|
|
9
|
+
- manifest.store: Store a manifest (idempotent by manifest_id)
|
|
10
|
+
- manifest.retrieve: Retrieve a manifest by ID
|
|
11
|
+
- manifest.query: Query manifests with filters (correlation_id, node_id, date range)
|
|
12
|
+
|
|
13
|
+
Storage Structure:
|
|
14
|
+
manifests/
|
|
15
|
+
2025/
|
|
16
|
+
01/
|
|
17
|
+
14/
|
|
18
|
+
{manifest_id}.json
|
|
19
|
+
|
|
20
|
+
Security Features:
|
|
21
|
+
- Atomic writes using temp file + rename (prevents partial writes)
|
|
22
|
+
- Idempotent storage (existing manifests are not overwritten)
|
|
23
|
+
- Circuit breaker for resilient I/O operations
|
|
24
|
+
|
|
25
|
+
TOCTOU Race Condition Behavior:
|
|
26
|
+
This handler has inherent Time-Of-Check-Time-Of-Use (TOCTOU) race conditions
|
|
27
|
+
due to filesystem operations. These are documented for transparency:
|
|
28
|
+
|
|
29
|
+
**manifest.store (idempotency check)**:
|
|
30
|
+
The check ``file_path.exists()`` and subsequent write are not atomic.
|
|
31
|
+
Between the existence check and the write, another process could:
|
|
32
|
+
- Create the same file (harmless: atomic rename will overwrite or fail)
|
|
33
|
+
- Delete the file (harmless: write will succeed)
|
|
34
|
+
|
|
35
|
+
Mitigation: Atomic writes use temp file + rename. On POSIX systems, rename()
|
|
36
|
+
is atomic within the same filesystem. The worst case is two concurrent writes
|
|
37
|
+
for the same manifest_id both succeed, but they write identical content.
|
|
38
|
+
|
|
39
|
+
**manifest.retrieve/query (directory scan)**:
|
|
40
|
+
Directory iteration via ``iterdir()`` returns a point-in-time snapshot.
|
|
41
|
+
Files may be added or removed during iteration. This is acceptable because:
|
|
42
|
+
- Manifests are append-only (never deleted during normal operation)
|
|
43
|
+
- Query results are best-effort snapshots, not transactional reads
|
|
44
|
+
|
|
45
|
+
**Deployment Considerations**:
|
|
46
|
+
- For multi-process deployments writing to shared storage, use a database
|
|
47
|
+
backend instead of filesystem storage for strong consistency guarantees.
|
|
48
|
+
- Single-process deployments (typical ONEX node) have no TOCTOU concerns.
|
|
49
|
+
- NFS and network filesystems may have weaker atomicity guarantees than
|
|
50
|
+
local filesystems; test rename behavior on your target storage.
|
|
51
|
+
|
|
52
|
+
Performance Characteristics (O(n) Directory Scan):
|
|
53
|
+
This handler uses O(n) directory scanning for retrieve and query operations,
|
|
54
|
+
where n is the total number of manifest files across all date partitions.
|
|
55
|
+
|
|
56
|
+
**Why O(n) is acceptable for current use case**:
|
|
57
|
+
- Manifest operations are low-frequency (debugging, auditing, troubleshooting)
|
|
58
|
+
- Date-based partitioning enables manual pruning of old directories
|
|
59
|
+
- Typical deployments have <10,000 manifests
|
|
60
|
+
- Recent manifests (most common access pattern) are found quickly due to
|
|
61
|
+
reverse-chronological iteration
|
|
62
|
+
|
|
63
|
+
**Scaling recommendations for high-volume deployments**:
|
|
64
|
+
- **>10k manifests**: Consider adding an index file (manifest_id -> path mapping)
|
|
65
|
+
- **>100k manifests**: Consider SQLite or PostgreSQL backend with indexed queries
|
|
66
|
+
- **>1M manifests**: Use dedicated manifest storage service with sharding
|
|
67
|
+
|
|
68
|
+
**Alternative approaches not implemented**:
|
|
69
|
+
- Bloom filter for fast negative lookups (adds complexity, marginal benefit)
|
|
70
|
+
- In-memory manifest_id index (memory overhead, persistence complexity)
|
|
71
|
+
- Filename encoding of creation date (breaks existing storage format)
|
|
72
|
+
|
|
73
|
+
Datetime Handling:
|
|
74
|
+
All datetime values (created_at, created_after, created_before) should be
|
|
75
|
+
timezone-aware for accurate comparisons. ISO 8601 strings with timezone info
|
|
76
|
+
(e.g., "2025-01-14T12:00:00+00:00" or "2025-01-14T12:00:00Z") are parsed
|
|
77
|
+
correctly. Naive datetimes may cause comparison issues when filtering.
|
|
78
|
+
|
|
79
|
+
Timezone Awareness:
|
|
80
|
+
- ISO strings with "Z" suffix are converted to UTC (+00:00)
|
|
81
|
+
- ISO strings with explicit offset (e.g., "+05:00") are preserved
|
|
82
|
+
- Naive datetime objects passed directly are accepted but logged as warnings
|
|
83
|
+
- Comparisons between aware and naive datetimes will raise TypeError in Python 3
|
|
84
|
+
- Best practice: Always use timezone-aware datetimes (e.g., datetime.now(timezone.utc))
|
|
85
|
+
|
|
86
|
+
Note:
|
|
87
|
+
Environment variable configuration (ONEX_MANIFEST_MAX_FILE_SIZE) is parsed
|
|
88
|
+
at module import time, not at handler instantiation. This means:
|
|
89
|
+
|
|
90
|
+
- Changes to environment variables require application restart to take effect
|
|
91
|
+
- Tests should use ``unittest.mock.patch.dict(os.environ, ...)`` before importing,
|
|
92
|
+
or use ``importlib.reload()`` to re-import the module after patching
|
|
93
|
+
- This is an intentional design choice for startup-time validation
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
from __future__ import annotations
|
|
97
|
+
|
|
98
|
+
import asyncio
|
|
99
|
+
import json
|
|
100
|
+
import logging
|
|
101
|
+
import os
|
|
102
|
+
import tempfile
|
|
103
|
+
import time
|
|
104
|
+
from collections.abc import Awaitable, Callable
|
|
105
|
+
from datetime import datetime
|
|
106
|
+
from pathlib import Path
|
|
107
|
+
from typing import TypeVar
|
|
108
|
+
from uuid import UUID, uuid4
|
|
109
|
+
|
|
110
|
+
from omnibase_core.container import ModelONEXContainer
|
|
111
|
+
from omnibase_core.models.dispatch import ModelHandlerOutput
|
|
112
|
+
from omnibase_infra.enums import (
|
|
113
|
+
EnumHandlerType,
|
|
114
|
+
EnumHandlerTypeCategory,
|
|
115
|
+
EnumInfraTransportType,
|
|
116
|
+
EnumRetryErrorCategory,
|
|
117
|
+
)
|
|
118
|
+
from omnibase_infra.errors import (
|
|
119
|
+
InfraUnavailableError,
|
|
120
|
+
ModelInfraErrorContext,
|
|
121
|
+
ProtocolConfigurationError,
|
|
122
|
+
RuntimeHostError,
|
|
123
|
+
)
|
|
124
|
+
from omnibase_infra.handlers.models import ModelRetryState
|
|
125
|
+
from omnibase_infra.handlers.models.model_manifest_metadata import ModelManifestMetadata
|
|
126
|
+
from omnibase_infra.handlers.models.model_manifest_query_result import (
|
|
127
|
+
ModelManifestQueryResult,
|
|
128
|
+
)
|
|
129
|
+
from omnibase_infra.handlers.models.model_manifest_retrieve_result import (
|
|
130
|
+
ModelManifestRetrieveResult,
|
|
131
|
+
)
|
|
132
|
+
from omnibase_infra.handlers.models.model_manifest_store_result import (
|
|
133
|
+
ModelManifestStoreResult,
|
|
134
|
+
)
|
|
135
|
+
from omnibase_infra.mixins import MixinAsyncCircuitBreaker, MixinEnvelopeExtraction
|
|
136
|
+
from omnibase_infra.mixins.mixin_retry_execution import MixinRetryExecution
|
|
137
|
+
from omnibase_infra.models.model_retry_error_classification import (
|
|
138
|
+
ModelRetryErrorClassification,
|
|
139
|
+
)
|
|
140
|
+
from omnibase_infra.utils import parse_env_int, warn_if_naive_datetime
|
|
141
|
+
|
|
142
|
+
logger = logging.getLogger(__name__)
|
|
143
|
+
|
|
144
|
+
# Default configuration from environment
|
|
145
|
+
_DEFAULT_MAX_FILE_SIZE: int = parse_env_int(
|
|
146
|
+
"ONEX_MANIFEST_MAX_FILE_SIZE",
|
|
147
|
+
50 * 1024 * 1024, # 50 MB
|
|
148
|
+
min_value=1024,
|
|
149
|
+
max_value=500 * 1024 * 1024, # 500 MB
|
|
150
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
151
|
+
service_name="manifest_persistence_handler",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
_SUPPORTED_OPERATIONS: frozenset[str] = frozenset(
|
|
155
|
+
{
|
|
156
|
+
"manifest.store",
|
|
157
|
+
"manifest.retrieve",
|
|
158
|
+
"manifest.query",
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
HANDLER_ID_MANIFEST_PERSISTENCE: str = "manifest-persistence-handler"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class HandlerManifestPersistence(
|
|
166
|
+
MixinEnvelopeExtraction, MixinAsyncCircuitBreaker, MixinRetryExecution
|
|
167
|
+
):
|
|
168
|
+
"""Manifest persistence handler for storing/retrieving ModelExecutionManifest.
|
|
169
|
+
|
|
170
|
+
This handler stores ModelExecutionManifest objects to the filesystem with:
|
|
171
|
+
- Date-based partitioning (year/month/day directories)
|
|
172
|
+
- Atomic writes (write to temp, then rename)
|
|
173
|
+
- Idempotent storage (same manifest_id = no duplicate)
|
|
174
|
+
- Query support with filters
|
|
175
|
+
- Circuit breaker for resilient I/O operations
|
|
176
|
+
- Retry with exponential backoff for transient I/O errors
|
|
177
|
+
|
|
178
|
+
Storage Pattern:
|
|
179
|
+
{storage_path}/{year}/{month}/{day}/{manifest_id}.json
|
|
180
|
+
|
|
181
|
+
Example: /data/manifests/2025/01/14/550e8400-e29b-41d4-a716-446655440000.json
|
|
182
|
+
|
|
183
|
+
Attributes:
|
|
184
|
+
handler_type: Returns INFRA_HANDLER (infrastructure protocol handler)
|
|
185
|
+
handler_category: Returns EFFECT (side-effecting I/O)
|
|
186
|
+
|
|
187
|
+
Example:
|
|
188
|
+
>>> handler = HandlerManifestPersistence(container)
|
|
189
|
+
>>> await handler.initialize({"storage_path": "/data/manifests"})
|
|
190
|
+
>>> result = await handler.execute({
|
|
191
|
+
... "operation": "manifest.store",
|
|
192
|
+
... "payload": {"manifest": manifest.model_dump()},
|
|
193
|
+
... })
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
def __init__(self, container: ModelONEXContainer) -> None:
|
|
197
|
+
"""Initialize HandlerManifestPersistence with required container injection.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
container: ONEX container for dependency injection. Required per ONEX
|
|
201
|
+
pattern (``def __init__(self, container: ModelONEXContainer)``).
|
|
202
|
+
Enables full ONEX integration (logging, metrics, service discovery).
|
|
203
|
+
|
|
204
|
+
See Also:
|
|
205
|
+
- CLAUDE.md "Container-Based Dependency Injection" section for the
|
|
206
|
+
standard ONEX container injection pattern.
|
|
207
|
+
- docs/patterns/container_dependency_injection.md for detailed DI patterns.
|
|
208
|
+
"""
|
|
209
|
+
self._container = container
|
|
210
|
+
self._storage_path: Path | None = None
|
|
211
|
+
self._max_file_size: int = _DEFAULT_MAX_FILE_SIZE
|
|
212
|
+
self._initialized: bool = False
|
|
213
|
+
|
|
214
|
+
# Retry configuration (populated from contract in initialize())
|
|
215
|
+
self._retry_config: dict[str, float] = {
|
|
216
|
+
"max_retries": 3,
|
|
217
|
+
"initial_delay_seconds": 0.1, # 100ms
|
|
218
|
+
"max_delay_seconds": 5.0, # 5000ms
|
|
219
|
+
"exponential_base": 2.0,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# Required by MixinRetryExecution (no thread pool needed for async I/O)
|
|
223
|
+
self._executor = None
|
|
224
|
+
|
|
225
|
+
# Required by MixinRetryExecution for circuit breaker integration check
|
|
226
|
+
# Set to True after _init_circuit_breaker() is called in initialize()
|
|
227
|
+
self._circuit_breaker_initialized: bool = False
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def handler_type(self) -> EnumHandlerType:
|
|
231
|
+
"""Return the architectural role of this handler.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
EnumHandlerType.INFRA_HANDLER - This handler is an infrastructure
|
|
235
|
+
protocol/transport handler for manifest persistence operations.
|
|
236
|
+
|
|
237
|
+
Note:
|
|
238
|
+
handler_type determines lifecycle, protocol selection, and runtime
|
|
239
|
+
invocation patterns. It answers "what is this handler in the architecture?"
|
|
240
|
+
|
|
241
|
+
See Also:
|
|
242
|
+
- handler_category: Behavioral classification (EFFECT/COMPUTE)
|
|
243
|
+
"""
|
|
244
|
+
return EnumHandlerType.INFRA_HANDLER
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def handler_category(self) -> EnumHandlerTypeCategory:
|
|
248
|
+
"""Return the behavioral classification of this handler.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
EnumHandlerTypeCategory.EFFECT - This handler performs side-effecting
|
|
252
|
+
I/O operations (filesystem read/write). EFFECT handlers are not
|
|
253
|
+
deterministic and interact with external systems.
|
|
254
|
+
|
|
255
|
+
Note:
|
|
256
|
+
handler_category determines security rules, determinism guarantees,
|
|
257
|
+
replay safety, and permissions. It answers "how does this handler
|
|
258
|
+
behave at runtime?"
|
|
259
|
+
|
|
260
|
+
Categories:
|
|
261
|
+
- COMPUTE: Pure, deterministic transformations (no side effects)
|
|
262
|
+
- EFFECT: Side-effecting I/O (database, HTTP, filesystem)
|
|
263
|
+
- NONDETERMINISTIC_COMPUTE: Pure but not deterministic (UUID, random)
|
|
264
|
+
|
|
265
|
+
See Also:
|
|
266
|
+
- handler_type: Architectural role (INFRA_HANDLER/NODE_HANDLER/etc.)
|
|
267
|
+
"""
|
|
268
|
+
return EnumHandlerTypeCategory.EFFECT
|
|
269
|
+
|
|
270
|
+
# =========================================================================
|
|
271
|
+
# MixinRetryExecution Abstract Method Implementations
|
|
272
|
+
# =========================================================================
|
|
273
|
+
|
|
274
|
+
def _classify_error(
|
|
275
|
+
self, error: Exception, operation: str
|
|
276
|
+
) -> ModelRetryErrorClassification:
|
|
277
|
+
"""Classify filesystem errors for retry handling.
|
|
278
|
+
|
|
279
|
+
This method determines whether an error is retriable and how it should
|
|
280
|
+
affect the circuit breaker state.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
error: The exception to classify.
|
|
284
|
+
operation: The operation name for context.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
ModelRetryErrorClassification with retry decision and error details.
|
|
288
|
+
|
|
289
|
+
Error Classification:
|
|
290
|
+
- TimeoutError: TIMEOUT category, retriable, records circuit failure
|
|
291
|
+
- BlockingIOError: TIMEOUT category, retriable, records circuit failure
|
|
292
|
+
(EAGAIN/EWOULDBLOCK - resource temporarily unavailable)
|
|
293
|
+
- FileNotFoundError: NOT_FOUND category, NOT retriable, NO circuit failure
|
|
294
|
+
- PermissionError: AUTHENTICATION category, NOT retriable, records circuit failure
|
|
295
|
+
- OSError/IOError: CONNECTION category, retriable, records circuit failure
|
|
296
|
+
- Other: UNKNOWN category, retriable, records circuit failure
|
|
297
|
+
|
|
298
|
+
Note:
|
|
299
|
+
BlockingIOError must be checked BEFORE OSError since it's a subclass.
|
|
300
|
+
We classify BlockingIOError as TIMEOUT rather than CONNECTION because
|
|
301
|
+
it indicates "resource temporarily unavailable" which is semantically
|
|
302
|
+
closer to a timeout condition than a connection error.
|
|
303
|
+
"""
|
|
304
|
+
if isinstance(error, TimeoutError):
|
|
305
|
+
return ModelRetryErrorClassification(
|
|
306
|
+
category=EnumRetryErrorCategory.TIMEOUT,
|
|
307
|
+
should_retry=True,
|
|
308
|
+
record_circuit_failure=True,
|
|
309
|
+
error_message=f"Filesystem operation timed out: {operation}",
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# BlockingIOError indicates EAGAIN/EWOULDBLOCK (resource temporarily unavailable)
|
|
313
|
+
# Must be checked before OSError since it's a subclass
|
|
314
|
+
if isinstance(error, BlockingIOError):
|
|
315
|
+
return ModelRetryErrorClassification(
|
|
316
|
+
category=EnumRetryErrorCategory.TIMEOUT,
|
|
317
|
+
should_retry=True,
|
|
318
|
+
record_circuit_failure=True,
|
|
319
|
+
error_message=f"Resource temporarily unavailable: {operation}",
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
if isinstance(error, FileNotFoundError):
|
|
323
|
+
# File not found is a user/logic error, not infrastructure failure
|
|
324
|
+
return ModelRetryErrorClassification(
|
|
325
|
+
category=EnumRetryErrorCategory.NOT_FOUND,
|
|
326
|
+
should_retry=False,
|
|
327
|
+
record_circuit_failure=False,
|
|
328
|
+
error_message=f"File not found: {error}",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
if isinstance(error, PermissionError):
|
|
332
|
+
# Permission errors are not retriable and indicate config issues
|
|
333
|
+
return ModelRetryErrorClassification(
|
|
334
|
+
category=EnumRetryErrorCategory.AUTHENTICATION,
|
|
335
|
+
should_retry=False,
|
|
336
|
+
record_circuit_failure=True,
|
|
337
|
+
error_message=f"Permission denied: {operation}",
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
if isinstance(error, OSError | IOError):
|
|
341
|
+
# General I/O errors are retriable (disk full, temp unavailable, etc.)
|
|
342
|
+
return ModelRetryErrorClassification(
|
|
343
|
+
category=EnumRetryErrorCategory.CONNECTION,
|
|
344
|
+
should_retry=True,
|
|
345
|
+
record_circuit_failure=True,
|
|
346
|
+
error_message=f"Filesystem I/O error: {type(error).__name__}",
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Unknown errors - retry but record failure
|
|
350
|
+
return ModelRetryErrorClassification(
|
|
351
|
+
category=EnumRetryErrorCategory.UNKNOWN,
|
|
352
|
+
should_retry=True,
|
|
353
|
+
record_circuit_failure=True,
|
|
354
|
+
error_message=f"Unexpected error: {type(error).__name__}",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
def _get_transport_type(self) -> EnumInfraTransportType:
|
|
358
|
+
"""Return the transport type for error context.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
EnumInfraTransportType.FILESYSTEM for filesystem operations.
|
|
362
|
+
"""
|
|
363
|
+
return EnumInfraTransportType.FILESYSTEM
|
|
364
|
+
|
|
365
|
+
def _get_target_name(self) -> str:
|
|
366
|
+
"""Return the target name for error context.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
The handler identifier for error context and logging.
|
|
370
|
+
"""
|
|
371
|
+
return "manifest_persistence_handler"
|
|
372
|
+
|
|
373
|
+
async def initialize(self, config: dict[str, object]) -> None:
|
|
374
|
+
"""Initialize manifest persistence handler with storage path.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
config: Configuration dict containing:
|
|
378
|
+
- storage_path: Required path to manifest storage directory
|
|
379
|
+
- max_file_size: Optional max file size in bytes (default: 50 MB)
|
|
380
|
+
- correlation_id: Optional UUID or string for error tracing
|
|
381
|
+
|
|
382
|
+
Raises:
|
|
383
|
+
ProtocolConfigurationError: If storage_path is missing or invalid.
|
|
384
|
+
|
|
385
|
+
Security:
|
|
386
|
+
- Storage directory is created if it doesn't exist
|
|
387
|
+
- Non-writable paths are logged as warnings
|
|
388
|
+
"""
|
|
389
|
+
init_correlation_id = uuid4()
|
|
390
|
+
|
|
391
|
+
logger.info(
|
|
392
|
+
"Initializing %s",
|
|
393
|
+
self.__class__.__name__,
|
|
394
|
+
extra={
|
|
395
|
+
"handler": self.__class__.__name__,
|
|
396
|
+
"correlation_id": str(init_correlation_id),
|
|
397
|
+
},
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
ctx = ModelInfraErrorContext(
|
|
401
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
402
|
+
operation="initialize",
|
|
403
|
+
target_name="manifest_persistence_handler",
|
|
404
|
+
correlation_id=init_correlation_id,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# Extract and validate storage_path (required)
|
|
408
|
+
storage_path_raw = config.get("storage_path")
|
|
409
|
+
if storage_path_raw is None:
|
|
410
|
+
raise ProtocolConfigurationError(
|
|
411
|
+
"Missing required 'storage_path' configuration - manifest persistence "
|
|
412
|
+
"handler requires a storage directory path",
|
|
413
|
+
context=ctx,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
if not isinstance(storage_path_raw, str) or not storage_path_raw:
|
|
417
|
+
raise ProtocolConfigurationError(
|
|
418
|
+
"Configuration 'storage_path' must be a non-empty string",
|
|
419
|
+
context=ctx,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
# Resolve to absolute path
|
|
423
|
+
storage_path = Path(storage_path_raw).resolve()
|
|
424
|
+
|
|
425
|
+
# Create storage directory if it doesn't exist
|
|
426
|
+
if not storage_path.exists():
|
|
427
|
+
try:
|
|
428
|
+
storage_path.mkdir(parents=True, exist_ok=True)
|
|
429
|
+
logger.info(
|
|
430
|
+
"Created manifest storage directory: %s",
|
|
431
|
+
storage_path,
|
|
432
|
+
extra={
|
|
433
|
+
"path": str(storage_path),
|
|
434
|
+
"correlation_id": str(init_correlation_id),
|
|
435
|
+
},
|
|
436
|
+
)
|
|
437
|
+
except OSError as e:
|
|
438
|
+
raise ProtocolConfigurationError(
|
|
439
|
+
f"Failed to create storage directory: {e}",
|
|
440
|
+
context=ctx,
|
|
441
|
+
) from e
|
|
442
|
+
|
|
443
|
+
if not storage_path.is_dir():
|
|
444
|
+
raise ProtocolConfigurationError(
|
|
445
|
+
f"Storage path exists but is not a directory: {storage_path}",
|
|
446
|
+
context=ctx,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
self._storage_path = storage_path
|
|
450
|
+
|
|
451
|
+
# Verify storage path is writable (fail fast on permission issues)
|
|
452
|
+
test_file = self._storage_path / ".write_test"
|
|
453
|
+
try:
|
|
454
|
+
test_file.touch()
|
|
455
|
+
test_file.unlink()
|
|
456
|
+
except (PermissionError, OSError) as e:
|
|
457
|
+
raise ProtocolConfigurationError(
|
|
458
|
+
f"Storage path is not writable: {self._storage_path}",
|
|
459
|
+
context=ModelInfraErrorContext(
|
|
460
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
461
|
+
operation="initialize",
|
|
462
|
+
target_name="manifest_persistence_handler",
|
|
463
|
+
correlation_id=init_correlation_id,
|
|
464
|
+
),
|
|
465
|
+
) from e
|
|
466
|
+
|
|
467
|
+
# Extract optional max_file_size
|
|
468
|
+
max_file_size_raw = config.get("max_file_size")
|
|
469
|
+
if max_file_size_raw is not None:
|
|
470
|
+
if isinstance(max_file_size_raw, int) and max_file_size_raw > 0:
|
|
471
|
+
self._max_file_size = max_file_size_raw
|
|
472
|
+
else:
|
|
473
|
+
logger.warning(
|
|
474
|
+
"Invalid max_file_size config value ignored, using default",
|
|
475
|
+
extra={
|
|
476
|
+
"provided_value": max_file_size_raw,
|
|
477
|
+
"default_value": self._max_file_size,
|
|
478
|
+
},
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
# Initialize circuit breaker for resilient I/O operations
|
|
482
|
+
self._init_circuit_breaker(
|
|
483
|
+
threshold=5,
|
|
484
|
+
reset_timeout=60.0,
|
|
485
|
+
service_name="manifest_persistence_handler",
|
|
486
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
487
|
+
)
|
|
488
|
+
# Mark circuit breaker as initialized for MixinRetryExecution integration
|
|
489
|
+
self._circuit_breaker_initialized = True
|
|
490
|
+
|
|
491
|
+
# Parse retry_policy from configuration (matches contract defaults)
|
|
492
|
+
# Fail-fast: Invalid retry config raises ProtocolConfigurationError
|
|
493
|
+
retry_policy = config.get("retry_policy")
|
|
494
|
+
if isinstance(retry_policy, dict):
|
|
495
|
+
# max_retries (default: 3) - must be positive integer
|
|
496
|
+
max_retries = retry_policy.get("max_retries")
|
|
497
|
+
if max_retries is not None:
|
|
498
|
+
if not isinstance(max_retries, int) or max_retries <= 0:
|
|
499
|
+
raise ProtocolConfigurationError(
|
|
500
|
+
"Invalid retry_policy.max_retries: must be a positive "
|
|
501
|
+
f"integer, got {type(max_retries).__name__}={max_retries!r}",
|
|
502
|
+
context=ctx,
|
|
503
|
+
)
|
|
504
|
+
self._retry_config["max_retries"] = max_retries
|
|
505
|
+
|
|
506
|
+
# initial_delay_ms -> convert to seconds (default: 100ms = 0.1s)
|
|
507
|
+
initial_delay_ms = retry_policy.get("initial_delay_ms")
|
|
508
|
+
if initial_delay_ms is not None:
|
|
509
|
+
is_valid_type = isinstance(initial_delay_ms, int | float)
|
|
510
|
+
if not is_valid_type or initial_delay_ms <= 0:
|
|
511
|
+
raise ProtocolConfigurationError(
|
|
512
|
+
"Invalid retry_policy.initial_delay_ms: must be a "
|
|
513
|
+
f"positive number, got "
|
|
514
|
+
f"{type(initial_delay_ms).__name__}={initial_delay_ms!r}",
|
|
515
|
+
context=ctx,
|
|
516
|
+
)
|
|
517
|
+
self._retry_config["initial_delay_seconds"] = initial_delay_ms / 1000.0
|
|
518
|
+
|
|
519
|
+
# max_delay_ms -> convert to seconds (default: 5000ms = 5.0s)
|
|
520
|
+
max_delay_ms = retry_policy.get("max_delay_ms")
|
|
521
|
+
if max_delay_ms is not None:
|
|
522
|
+
if not isinstance(max_delay_ms, int | float) or max_delay_ms <= 0:
|
|
523
|
+
raise ProtocolConfigurationError(
|
|
524
|
+
"Invalid retry_policy.max_delay_ms: must be a positive "
|
|
525
|
+
f"number, got {type(max_delay_ms).__name__}={max_delay_ms!r}",
|
|
526
|
+
context=ctx,
|
|
527
|
+
)
|
|
528
|
+
self._retry_config["max_delay_seconds"] = max_delay_ms / 1000.0
|
|
529
|
+
|
|
530
|
+
# exponential_base (default: 2.0) - must be >= 1.0
|
|
531
|
+
exponential_base = retry_policy.get("exponential_base")
|
|
532
|
+
if exponential_base is not None:
|
|
533
|
+
is_valid_type = isinstance(exponential_base, int | float)
|
|
534
|
+
if not is_valid_type or exponential_base < 1.0:
|
|
535
|
+
raise ProtocolConfigurationError(
|
|
536
|
+
"Invalid retry_policy.exponential_base: must be a "
|
|
537
|
+
f"number >= 1.0, got "
|
|
538
|
+
f"{type(exponential_base).__name__}={exponential_base!r}",
|
|
539
|
+
context=ctx,
|
|
540
|
+
)
|
|
541
|
+
self._retry_config["exponential_base"] = float(exponential_base)
|
|
542
|
+
|
|
543
|
+
logger.debug(
|
|
544
|
+
"Retry policy configured",
|
|
545
|
+
extra={
|
|
546
|
+
"max_retries": self._retry_config["max_retries"],
|
|
547
|
+
"initial_delay_seconds": self._retry_config[
|
|
548
|
+
"initial_delay_seconds"
|
|
549
|
+
],
|
|
550
|
+
"max_delay_seconds": self._retry_config["max_delay_seconds"],
|
|
551
|
+
"exponential_base": self._retry_config["exponential_base"],
|
|
552
|
+
"correlation_id": str(init_correlation_id),
|
|
553
|
+
},
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
self._initialized = True
|
|
557
|
+
|
|
558
|
+
logger.info(
|
|
559
|
+
"%s initialized successfully",
|
|
560
|
+
self.__class__.__name__,
|
|
561
|
+
extra={
|
|
562
|
+
"handler": self.__class__.__name__,
|
|
563
|
+
"storage_path": str(self._storage_path),
|
|
564
|
+
"max_file_size_bytes": self._max_file_size,
|
|
565
|
+
"correlation_id": str(init_correlation_id),
|
|
566
|
+
},
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
async def shutdown(self) -> None:
|
|
570
|
+
"""Shutdown manifest persistence handler and clear configuration."""
|
|
571
|
+
self._storage_path = None
|
|
572
|
+
self._initialized = False
|
|
573
|
+
logger.info("HandlerManifestPersistence shutdown complete")
|
|
574
|
+
|
|
575
|
+
# =========================================================================
|
|
576
|
+
# Retry Logic Helper
|
|
577
|
+
# =========================================================================
|
|
578
|
+
|
|
579
|
+
_T = TypeVar("_T")
|
|
580
|
+
|
|
581
|
+
async def _execute_with_retry(
|
|
582
|
+
self,
|
|
583
|
+
operation: str,
|
|
584
|
+
func: Callable[[], Awaitable[_T]],
|
|
585
|
+
correlation_id: UUID,
|
|
586
|
+
) -> _T:
|
|
587
|
+
"""Execute an async operation with exponential backoff retry logic.
|
|
588
|
+
|
|
589
|
+
This method wraps I/O operations with retry logic, integrating with the
|
|
590
|
+
circuit breaker for resilient operations.
|
|
591
|
+
|
|
592
|
+
Args:
|
|
593
|
+
operation: Operation name for logging and error context.
|
|
594
|
+
func: Async callable to execute (returns the result).
|
|
595
|
+
correlation_id: Correlation ID for distributed tracing.
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
The result from func().
|
|
599
|
+
|
|
600
|
+
Raises:
|
|
601
|
+
InfraTimeoutError: If operation times out after retries exhausted.
|
|
602
|
+
InfraConnectionError: If connection fails after retries exhausted.
|
|
603
|
+
InfraAuthenticationError: If authentication fails (not retriable).
|
|
604
|
+
InfraUnavailableError: If circuit breaker is OPEN.
|
|
605
|
+
|
|
606
|
+
Circuit Breaker Integration:
|
|
607
|
+
- Checks circuit state before execution (raises if OPEN)
|
|
608
|
+
- Records success/failure for circuit state management
|
|
609
|
+
- Failure recorded only when retries are exhausted
|
|
610
|
+
|
|
611
|
+
Retry Logic:
|
|
612
|
+
- Uses exponential backoff with configurable parameters
|
|
613
|
+
- Classifies errors to determine retry eligibility
|
|
614
|
+
- Logs retry attempts with correlation tracking
|
|
615
|
+
"""
|
|
616
|
+
# Check circuit breaker before execution
|
|
617
|
+
await self._check_circuit_if_enabled(operation, correlation_id)
|
|
618
|
+
|
|
619
|
+
# Initialize retry state from configuration
|
|
620
|
+
retry_state = ModelRetryState(
|
|
621
|
+
attempt=0,
|
|
622
|
+
max_attempts=int(self._retry_config["max_retries"]) + 1, # +1 for initial
|
|
623
|
+
delay_seconds=float(self._retry_config["initial_delay_seconds"]),
|
|
624
|
+
backoff_multiplier=float(self._retry_config["exponential_base"]),
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
max_delay_seconds = float(self._retry_config["max_delay_seconds"])
|
|
628
|
+
last_error: Exception | None = None
|
|
629
|
+
|
|
630
|
+
while retry_state.is_retriable():
|
|
631
|
+
try:
|
|
632
|
+
result = await func()
|
|
633
|
+
# Reset circuit breaker on success
|
|
634
|
+
await self._reset_circuit_if_enabled()
|
|
635
|
+
return result
|
|
636
|
+
|
|
637
|
+
except Exception as e:
|
|
638
|
+
last_error = e
|
|
639
|
+
classification = self._classify_error(e, operation)
|
|
640
|
+
|
|
641
|
+
if not classification.should_retry:
|
|
642
|
+
# Non-retriable error - record failure and raise immediately
|
|
643
|
+
if classification.record_circuit_failure:
|
|
644
|
+
await self._record_circuit_failure_if_enabled(
|
|
645
|
+
operation, correlation_id
|
|
646
|
+
)
|
|
647
|
+
raise
|
|
648
|
+
|
|
649
|
+
# Update retry state for next attempt
|
|
650
|
+
retry_state = retry_state.next_attempt(
|
|
651
|
+
error_message=classification.error_message,
|
|
652
|
+
max_delay_seconds=max_delay_seconds,
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
if not retry_state.is_retriable():
|
|
656
|
+
# Retries exhausted - record failure and raise
|
|
657
|
+
if classification.record_circuit_failure:
|
|
658
|
+
await self._record_circuit_failure_if_enabled(
|
|
659
|
+
operation, correlation_id
|
|
660
|
+
)
|
|
661
|
+
raise
|
|
662
|
+
|
|
663
|
+
# Log retry attempt
|
|
664
|
+
await self._log_retry_attempt(operation, retry_state, correlation_id)
|
|
665
|
+
|
|
666
|
+
# Wait before next attempt
|
|
667
|
+
await asyncio.sleep(retry_state.delay_seconds)
|
|
668
|
+
|
|
669
|
+
# Should never reach here, but satisfy type checker
|
|
670
|
+
if last_error is not None:
|
|
671
|
+
raise last_error
|
|
672
|
+
ctx = ModelInfraErrorContext(
|
|
673
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
674
|
+
operation=operation,
|
|
675
|
+
target_name="manifest_persistence_handler",
|
|
676
|
+
correlation_id=correlation_id,
|
|
677
|
+
)
|
|
678
|
+
raise InfraUnavailableError(
|
|
679
|
+
f"Retry loop completed without result for {operation}",
|
|
680
|
+
context=ctx,
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
def _get_manifest_path(self, manifest_id: UUID, created_at: datetime) -> Path:
|
|
684
|
+
"""Get the file path for a manifest based on ID and creation date.
|
|
685
|
+
|
|
686
|
+
Args:
|
|
687
|
+
manifest_id: Unique identifier of the manifest
|
|
688
|
+
created_at: Creation timestamp for date partitioning
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
Path: {storage_path}/{year}/{month:02d}/{day:02d}/{manifest_id}.json
|
|
692
|
+
"""
|
|
693
|
+
if self._storage_path is None:
|
|
694
|
+
raise RuntimeHostError(
|
|
695
|
+
"Handler not initialized - storage_path is None",
|
|
696
|
+
context=ModelInfraErrorContext(
|
|
697
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
698
|
+
operation="get_manifest_path",
|
|
699
|
+
target_name="manifest_persistence_handler",
|
|
700
|
+
),
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
return (
|
|
704
|
+
self._storage_path
|
|
705
|
+
/ str(created_at.year)
|
|
706
|
+
/ f"{created_at.month:02d}"
|
|
707
|
+
/ f"{created_at.day:02d}"
|
|
708
|
+
/ f"{manifest_id}.json"
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
async def execute(
|
|
712
|
+
self, envelope: dict[str, object]
|
|
713
|
+
) -> ModelHandlerOutput[dict[str, object]]:
|
|
714
|
+
"""Execute manifest persistence operation from envelope.
|
|
715
|
+
|
|
716
|
+
Args:
|
|
717
|
+
envelope: Request envelope containing:
|
|
718
|
+
- operation: One of the supported manifest operations
|
|
719
|
+
- payload: Operation-specific payload
|
|
720
|
+
- correlation_id: Optional correlation ID for tracing
|
|
721
|
+
- envelope_id: Optional envelope ID for causality tracking
|
|
722
|
+
|
|
723
|
+
Returns:
|
|
724
|
+
ModelHandlerOutput[dict[str, object]] containing operation result
|
|
725
|
+
|
|
726
|
+
Raises:
|
|
727
|
+
RuntimeHostError: If handler not initialized
|
|
728
|
+
ProtocolConfigurationError: If operation or payload is invalid
|
|
729
|
+
"""
|
|
730
|
+
correlation_id = self._extract_correlation_id(envelope)
|
|
731
|
+
input_envelope_id = self._extract_envelope_id(envelope)
|
|
732
|
+
|
|
733
|
+
if not self._initialized:
|
|
734
|
+
ctx = ModelInfraErrorContext(
|
|
735
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
736
|
+
operation="execute",
|
|
737
|
+
target_name="manifest_persistence_handler",
|
|
738
|
+
correlation_id=correlation_id,
|
|
739
|
+
)
|
|
740
|
+
raise RuntimeHostError(
|
|
741
|
+
"HandlerManifestPersistence not initialized. Call initialize() first.",
|
|
742
|
+
context=ctx,
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
operation = envelope.get("operation")
|
|
746
|
+
if not isinstance(operation, str):
|
|
747
|
+
ctx = ModelInfraErrorContext(
|
|
748
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
749
|
+
operation="execute",
|
|
750
|
+
target_name="manifest_persistence_handler",
|
|
751
|
+
correlation_id=correlation_id,
|
|
752
|
+
)
|
|
753
|
+
raise ProtocolConfigurationError(
|
|
754
|
+
"Missing or invalid 'operation' in envelope", context=ctx
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
if operation not in _SUPPORTED_OPERATIONS:
|
|
758
|
+
ctx = ModelInfraErrorContext(
|
|
759
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
760
|
+
operation=operation,
|
|
761
|
+
target_name="manifest_persistence_handler",
|
|
762
|
+
correlation_id=correlation_id,
|
|
763
|
+
)
|
|
764
|
+
raise ProtocolConfigurationError(
|
|
765
|
+
f"Operation '{operation}' not supported. Available: {', '.join(sorted(_SUPPORTED_OPERATIONS))}",
|
|
766
|
+
context=ctx,
|
|
767
|
+
)
|
|
768
|
+
|
|
769
|
+
payload = envelope.get("payload")
|
|
770
|
+
if not isinstance(payload, dict):
|
|
771
|
+
ctx = ModelInfraErrorContext(
|
|
772
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
773
|
+
operation=operation,
|
|
774
|
+
target_name="manifest_persistence_handler",
|
|
775
|
+
correlation_id=correlation_id,
|
|
776
|
+
)
|
|
777
|
+
raise ProtocolConfigurationError(
|
|
778
|
+
"Missing or invalid 'payload' in envelope", context=ctx
|
|
779
|
+
)
|
|
780
|
+
|
|
781
|
+
# Route to appropriate operation handler
|
|
782
|
+
if operation == "manifest.store":
|
|
783
|
+
return await self._execute_store(payload, correlation_id, input_envelope_id)
|
|
784
|
+
elif operation == "manifest.retrieve":
|
|
785
|
+
return await self._execute_retrieve(
|
|
786
|
+
payload, correlation_id, input_envelope_id
|
|
787
|
+
)
|
|
788
|
+
else: # manifest.query
|
|
789
|
+
return await self._execute_query(payload, correlation_id, input_envelope_id)
|
|
790
|
+
|
|
791
|
+
async def _execute_store(
|
|
792
|
+
self,
|
|
793
|
+
payload: dict[str, object],
|
|
794
|
+
correlation_id: UUID,
|
|
795
|
+
input_envelope_id: UUID,
|
|
796
|
+
) -> ModelHandlerOutput[dict[str, object]]:
|
|
797
|
+
"""Execute manifest.store operation with retry logic.
|
|
798
|
+
|
|
799
|
+
Stores a manifest with atomic write (temp file + rename) and
|
|
800
|
+
idempotent behavior (existing manifests are not overwritten).
|
|
801
|
+
|
|
802
|
+
Payload:
|
|
803
|
+
- manifest: dict (required) - Serialized ModelExecutionManifest
|
|
804
|
+
|
|
805
|
+
Returns:
|
|
806
|
+
Result with manifest_id, file_path, created, and bytes_written.
|
|
807
|
+
|
|
808
|
+
Raises:
|
|
809
|
+
InfraConnectionError: If write fails after retries exhausted
|
|
810
|
+
InfraUnavailableError: If circuit breaker is open
|
|
811
|
+
"""
|
|
812
|
+
operation = "manifest.store"
|
|
813
|
+
|
|
814
|
+
# Extract manifest (required)
|
|
815
|
+
manifest_raw = payload.get("manifest")
|
|
816
|
+
if not isinstance(manifest_raw, dict):
|
|
817
|
+
ctx = ModelInfraErrorContext(
|
|
818
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
819
|
+
operation=operation,
|
|
820
|
+
target_name="manifest_persistence_handler",
|
|
821
|
+
correlation_id=correlation_id,
|
|
822
|
+
)
|
|
823
|
+
raise ProtocolConfigurationError(
|
|
824
|
+
"Missing or invalid 'manifest' in payload - must be a dictionary",
|
|
825
|
+
context=ctx,
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
# Extract required fields from manifest
|
|
829
|
+
manifest_id_raw = manifest_raw.get("manifest_id")
|
|
830
|
+
created_at_raw = manifest_raw.get("created_at")
|
|
831
|
+
|
|
832
|
+
ctx = ModelInfraErrorContext(
|
|
833
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
834
|
+
operation=operation,
|
|
835
|
+
target_name="manifest_persistence_handler",
|
|
836
|
+
correlation_id=correlation_id,
|
|
837
|
+
)
|
|
838
|
+
|
|
839
|
+
# Parse manifest_id
|
|
840
|
+
try:
|
|
841
|
+
if isinstance(manifest_id_raw, UUID):
|
|
842
|
+
manifest_id = manifest_id_raw
|
|
843
|
+
elif isinstance(manifest_id_raw, str):
|
|
844
|
+
manifest_id = UUID(manifest_id_raw)
|
|
845
|
+
else:
|
|
846
|
+
raise ProtocolConfigurationError(
|
|
847
|
+
"Manifest missing required 'manifest_id' field or invalid type",
|
|
848
|
+
context=ctx,
|
|
849
|
+
)
|
|
850
|
+
except ValueError as e:
|
|
851
|
+
raise ProtocolConfigurationError(
|
|
852
|
+
f"Invalid manifest_id format: {e}",
|
|
853
|
+
context=ctx,
|
|
854
|
+
) from e
|
|
855
|
+
|
|
856
|
+
# Parse created_at
|
|
857
|
+
try:
|
|
858
|
+
if isinstance(created_at_raw, datetime):
|
|
859
|
+
created_at = created_at_raw
|
|
860
|
+
warn_if_naive_datetime(
|
|
861
|
+
created_at, field_name="created_at", correlation_id=correlation_id
|
|
862
|
+
)
|
|
863
|
+
elif isinstance(created_at_raw, str):
|
|
864
|
+
# Try ISO format parsing (Z suffix converted to +00:00)
|
|
865
|
+
created_at = datetime.fromisoformat(
|
|
866
|
+
created_at_raw.replace("Z", "+00:00")
|
|
867
|
+
)
|
|
868
|
+
else:
|
|
869
|
+
raise ProtocolConfigurationError(
|
|
870
|
+
"Manifest missing required 'created_at' field or invalid type",
|
|
871
|
+
context=ctx,
|
|
872
|
+
)
|
|
873
|
+
except ValueError as e:
|
|
874
|
+
raise ProtocolConfigurationError(
|
|
875
|
+
f"Invalid created_at format: {e}",
|
|
876
|
+
context=ctx,
|
|
877
|
+
) from e
|
|
878
|
+
|
|
879
|
+
# Get file path
|
|
880
|
+
file_path = self._get_manifest_path(manifest_id, created_at)
|
|
881
|
+
|
|
882
|
+
async def _do_store_io() -> ModelHandlerOutput[dict[str, object]]:
|
|
883
|
+
"""Inner function containing I/O operations (wrapped with retry)."""
|
|
884
|
+
# Check if manifest already exists (idempotent behavior)
|
|
885
|
+
if file_path.exists():
|
|
886
|
+
logger.debug(
|
|
887
|
+
"Manifest already exists, skipping write (idempotent)",
|
|
888
|
+
extra={
|
|
889
|
+
"manifest_id": str(manifest_id),
|
|
890
|
+
"path": str(file_path),
|
|
891
|
+
"correlation_id": str(correlation_id),
|
|
892
|
+
},
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
result = ModelManifestStoreResult(
|
|
896
|
+
manifest_id=manifest_id,
|
|
897
|
+
file_path=str(file_path),
|
|
898
|
+
created=False,
|
|
899
|
+
bytes_written=0,
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
return ModelHandlerOutput.for_compute(
|
|
903
|
+
input_envelope_id=input_envelope_id,
|
|
904
|
+
correlation_id=correlation_id,
|
|
905
|
+
handler_id=HANDLER_ID_MANIFEST_PERSISTENCE,
|
|
906
|
+
result={
|
|
907
|
+
"status": "success",
|
|
908
|
+
"payload": result.model_dump(mode="json"),
|
|
909
|
+
"correlation_id": str(correlation_id),
|
|
910
|
+
},
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
# Create parent directories
|
|
914
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
915
|
+
|
|
916
|
+
# Serialize manifest to JSON
|
|
917
|
+
manifest_json = json.dumps(manifest_raw, indent=2, default=str)
|
|
918
|
+
manifest_bytes = manifest_json.encode("utf-8")
|
|
919
|
+
|
|
920
|
+
# Atomic write: write to temp file, then rename
|
|
921
|
+
temp_fd, temp_path = tempfile.mkstemp(
|
|
922
|
+
suffix=".tmp",
|
|
923
|
+
prefix=f"{manifest_id}_",
|
|
924
|
+
dir=file_path.parent,
|
|
925
|
+
)
|
|
926
|
+
try:
|
|
927
|
+
with os.fdopen(temp_fd, "wb") as f:
|
|
928
|
+
f.write(manifest_bytes)
|
|
929
|
+
# Atomic rename
|
|
930
|
+
temp_path_obj = Path(temp_path)
|
|
931
|
+
temp_path_obj.rename(file_path)
|
|
932
|
+
except OSError:
|
|
933
|
+
# Clean up temp file on failure
|
|
934
|
+
temp_path_obj = Path(temp_path)
|
|
935
|
+
if temp_path_obj.exists():
|
|
936
|
+
temp_path_obj.unlink()
|
|
937
|
+
raise
|
|
938
|
+
|
|
939
|
+
bytes_written = len(manifest_bytes)
|
|
940
|
+
|
|
941
|
+
logger.debug(
|
|
942
|
+
"Manifest stored successfully",
|
|
943
|
+
extra={
|
|
944
|
+
"manifest_id": str(manifest_id),
|
|
945
|
+
"path": str(file_path),
|
|
946
|
+
"bytes_written": bytes_written,
|
|
947
|
+
"correlation_id": str(correlation_id),
|
|
948
|
+
},
|
|
949
|
+
)
|
|
950
|
+
|
|
951
|
+
result = ModelManifestStoreResult(
|
|
952
|
+
manifest_id=manifest_id,
|
|
953
|
+
file_path=str(file_path),
|
|
954
|
+
created=True,
|
|
955
|
+
bytes_written=bytes_written,
|
|
956
|
+
)
|
|
957
|
+
|
|
958
|
+
return ModelHandlerOutput.for_compute(
|
|
959
|
+
input_envelope_id=input_envelope_id,
|
|
960
|
+
correlation_id=correlation_id,
|
|
961
|
+
handler_id=HANDLER_ID_MANIFEST_PERSISTENCE,
|
|
962
|
+
result={
|
|
963
|
+
"status": "success",
|
|
964
|
+
"payload": result.model_dump(mode="json"),
|
|
965
|
+
"correlation_id": str(correlation_id),
|
|
966
|
+
},
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
# Execute with retry logic (handles circuit breaker and backoff)
|
|
970
|
+
return await self._execute_with_retry(operation, _do_store_io, correlation_id)
|
|
971
|
+
|
|
972
|
+
async def _execute_retrieve(
|
|
973
|
+
self,
|
|
974
|
+
payload: dict[str, object],
|
|
975
|
+
correlation_id: UUID,
|
|
976
|
+
input_envelope_id: UUID,
|
|
977
|
+
) -> ModelHandlerOutput[dict[str, object]]:
|
|
978
|
+
"""Execute manifest.retrieve operation with retry logic.
|
|
979
|
+
|
|
980
|
+
Retrieves a manifest by scanning date directories.
|
|
981
|
+
|
|
982
|
+
Complexity:
|
|
983
|
+
O(d) where d is the number of date directories (year/month/day).
|
|
984
|
+
This is a full directory scan because manifest_id does not encode
|
|
985
|
+
the creation date, requiring us to search all partitions. This is
|
|
986
|
+
acceptable for the current use case (low query volume, typically
|
|
987
|
+
recent manifests). For high-volume retrieval patterns, consider
|
|
988
|
+
maintaining a separate index file or using the query operation
|
|
989
|
+
with correlation_id filter.
|
|
990
|
+
|
|
991
|
+
Payload:
|
|
992
|
+
- manifest_id: UUID or string (required) - Manifest to retrieve
|
|
993
|
+
|
|
994
|
+
Returns:
|
|
995
|
+
Result with manifest_id, manifest data, file_path, and found flag.
|
|
996
|
+
|
|
997
|
+
Raises:
|
|
998
|
+
InfraConnectionError: If read fails after retries exhausted
|
|
999
|
+
InfraUnavailableError: If circuit breaker is open
|
|
1000
|
+
"""
|
|
1001
|
+
operation = "manifest.retrieve"
|
|
1002
|
+
|
|
1003
|
+
# Extract manifest_id (required)
|
|
1004
|
+
manifest_id_raw = payload.get("manifest_id")
|
|
1005
|
+
ctx = ModelInfraErrorContext(
|
|
1006
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
1007
|
+
operation=operation,
|
|
1008
|
+
target_name="manifest_persistence_handler",
|
|
1009
|
+
correlation_id=correlation_id,
|
|
1010
|
+
)
|
|
1011
|
+
|
|
1012
|
+
try:
|
|
1013
|
+
if isinstance(manifest_id_raw, UUID):
|
|
1014
|
+
manifest_id = manifest_id_raw
|
|
1015
|
+
elif isinstance(manifest_id_raw, str):
|
|
1016
|
+
manifest_id = UUID(manifest_id_raw)
|
|
1017
|
+
else:
|
|
1018
|
+
raise ProtocolConfigurationError(
|
|
1019
|
+
"Missing or invalid 'manifest_id' in payload",
|
|
1020
|
+
context=ctx,
|
|
1021
|
+
)
|
|
1022
|
+
except ValueError as e:
|
|
1023
|
+
raise ProtocolConfigurationError(
|
|
1024
|
+
f"Invalid manifest_id format: {e}",
|
|
1025
|
+
context=ctx,
|
|
1026
|
+
) from e
|
|
1027
|
+
|
|
1028
|
+
async def _do_retrieve_io() -> ModelHandlerOutput[dict[str, object]]:
|
|
1029
|
+
"""Inner function containing I/O operations (wrapped with retry)."""
|
|
1030
|
+
# Search for manifest in date directories
|
|
1031
|
+
found_path: Path | None = None
|
|
1032
|
+
manifest_data: dict[str, object] | None = None
|
|
1033
|
+
|
|
1034
|
+
if self._storage_path is None:
|
|
1035
|
+
raise RuntimeHostError(
|
|
1036
|
+
"Handler not initialized - storage_path is None",
|
|
1037
|
+
context=ctx,
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
# Scan year/month/day directories with performance tracking
|
|
1041
|
+
scan_start_time = time.monotonic()
|
|
1042
|
+
directories_scanned = 0
|
|
1043
|
+
|
|
1044
|
+
for year_dir in sorted(self._storage_path.iterdir(), reverse=True):
|
|
1045
|
+
if not year_dir.is_dir():
|
|
1046
|
+
continue
|
|
1047
|
+
directories_scanned += 1
|
|
1048
|
+
for month_dir in sorted(year_dir.iterdir(), reverse=True):
|
|
1049
|
+
if not month_dir.is_dir():
|
|
1050
|
+
continue
|
|
1051
|
+
directories_scanned += 1
|
|
1052
|
+
for day_dir in sorted(month_dir.iterdir(), reverse=True):
|
|
1053
|
+
if not day_dir.is_dir():
|
|
1054
|
+
continue
|
|
1055
|
+
directories_scanned += 1
|
|
1056
|
+
manifest_file = day_dir / f"{manifest_id}.json"
|
|
1057
|
+
if manifest_file.exists():
|
|
1058
|
+
found_path = manifest_file
|
|
1059
|
+
break
|
|
1060
|
+
if found_path:
|
|
1061
|
+
break
|
|
1062
|
+
if found_path:
|
|
1063
|
+
break
|
|
1064
|
+
|
|
1065
|
+
scan_duration = time.monotonic() - scan_start_time
|
|
1066
|
+
logger.debug(
|
|
1067
|
+
"Directory scan completed for retrieve",
|
|
1068
|
+
extra={
|
|
1069
|
+
"duration_seconds": round(scan_duration, 6),
|
|
1070
|
+
"directories_scanned": directories_scanned,
|
|
1071
|
+
"manifest_found": found_path is not None,
|
|
1072
|
+
"manifest_id": str(manifest_id),
|
|
1073
|
+
"correlation_id": str(correlation_id),
|
|
1074
|
+
},
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
if found_path:
|
|
1078
|
+
# Check file size before reading
|
|
1079
|
+
file_size = found_path.stat().st_size
|
|
1080
|
+
if file_size > self._max_file_size:
|
|
1081
|
+
raise InfraUnavailableError(
|
|
1082
|
+
"Manifest file size exceeds configured limit",
|
|
1083
|
+
context=ctx,
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
# Read and parse manifest
|
|
1087
|
+
manifest_json = found_path.read_text(encoding="utf-8")
|
|
1088
|
+
manifest_data = json.loads(manifest_json)
|
|
1089
|
+
|
|
1090
|
+
result = ModelManifestRetrieveResult(
|
|
1091
|
+
manifest_id=manifest_id,
|
|
1092
|
+
manifest=manifest_data,
|
|
1093
|
+
file_path=str(found_path) if found_path else None,
|
|
1094
|
+
found=found_path is not None,
|
|
1095
|
+
)
|
|
1096
|
+
|
|
1097
|
+
logger.debug(
|
|
1098
|
+
"Manifest retrieve completed",
|
|
1099
|
+
extra={
|
|
1100
|
+
"manifest_id": str(manifest_id),
|
|
1101
|
+
"found": result.found,
|
|
1102
|
+
"path": str(found_path) if found_path else None,
|
|
1103
|
+
"correlation_id": str(correlation_id),
|
|
1104
|
+
},
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
return ModelHandlerOutput.for_compute(
|
|
1108
|
+
input_envelope_id=input_envelope_id,
|
|
1109
|
+
correlation_id=correlation_id,
|
|
1110
|
+
handler_id=HANDLER_ID_MANIFEST_PERSISTENCE,
|
|
1111
|
+
result={
|
|
1112
|
+
"status": "success",
|
|
1113
|
+
"payload": result.model_dump(mode="json"),
|
|
1114
|
+
"correlation_id": str(correlation_id),
|
|
1115
|
+
},
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
# Execute with retry logic (handles circuit breaker and backoff)
|
|
1119
|
+
return await self._execute_with_retry(
|
|
1120
|
+
operation, _do_retrieve_io, correlation_id
|
|
1121
|
+
)
|
|
1122
|
+
|
|
1123
|
+
async def _execute_query(
|
|
1124
|
+
self,
|
|
1125
|
+
payload: dict[str, object],
|
|
1126
|
+
correlation_id: UUID,
|
|
1127
|
+
input_envelope_id: UUID,
|
|
1128
|
+
) -> ModelHandlerOutput[dict[str, object]]:
|
|
1129
|
+
"""Execute manifest.query operation with retry logic.
|
|
1130
|
+
|
|
1131
|
+
Queries manifests with filters and respects metadata_only flag.
|
|
1132
|
+
|
|
1133
|
+
Complexity:
|
|
1134
|
+
O(n) where n is the total number of manifest files. Each file must
|
|
1135
|
+
be read and parsed to apply filters. The limit parameter provides
|
|
1136
|
+
early termination but worst case (few matches) scans all files.
|
|
1137
|
+
This is acceptable for the current use case where:
|
|
1138
|
+
- Query operations are infrequent (debugging, auditing)
|
|
1139
|
+
- Date-based partitioning enables manual pruning of old directories
|
|
1140
|
+
- Typical deployments have <10k manifests
|
|
1141
|
+
|
|
1142
|
+
Payload:
|
|
1143
|
+
- correlation_id: UUID or string (optional) - Filter by correlation_id
|
|
1144
|
+
- node_id: string (optional) - Filter by node_id
|
|
1145
|
+
- created_after: datetime or ISO string (optional) - Filter by creation time
|
|
1146
|
+
- created_before: datetime or ISO string (optional) - Filter by creation time
|
|
1147
|
+
- metadata_only: bool (optional, default False) - Return only metadata
|
|
1148
|
+
- limit: int (optional, default 100) - Maximum results
|
|
1149
|
+
|
|
1150
|
+
Returns:
|
|
1151
|
+
Result with manifests list, total_count, and metadata_only flag.
|
|
1152
|
+
|
|
1153
|
+
Raises:
|
|
1154
|
+
InfraConnectionError: If read fails after retries exhausted
|
|
1155
|
+
InfraUnavailableError: If circuit breaker is open
|
|
1156
|
+
"""
|
|
1157
|
+
operation = "manifest.query"
|
|
1158
|
+
|
|
1159
|
+
ctx = ModelInfraErrorContext(
|
|
1160
|
+
transport_type=EnumInfraTransportType.FILESYSTEM,
|
|
1161
|
+
operation=operation,
|
|
1162
|
+
target_name="manifest_persistence_handler",
|
|
1163
|
+
correlation_id=correlation_id,
|
|
1164
|
+
)
|
|
1165
|
+
|
|
1166
|
+
# Extract filter parameters
|
|
1167
|
+
filter_correlation_id: UUID | None = None
|
|
1168
|
+
correlation_id_raw = payload.get("correlation_id")
|
|
1169
|
+
if correlation_id_raw is not None:
|
|
1170
|
+
try:
|
|
1171
|
+
if isinstance(correlation_id_raw, UUID):
|
|
1172
|
+
filter_correlation_id = correlation_id_raw
|
|
1173
|
+
elif isinstance(correlation_id_raw, str):
|
|
1174
|
+
filter_correlation_id = UUID(correlation_id_raw)
|
|
1175
|
+
else:
|
|
1176
|
+
logger.warning(
|
|
1177
|
+
"Invalid correlation_id filter type, ignoring filter",
|
|
1178
|
+
extra={
|
|
1179
|
+
"provided_type": type(correlation_id_raw).__name__,
|
|
1180
|
+
"correlation_id": str(correlation_id),
|
|
1181
|
+
},
|
|
1182
|
+
)
|
|
1183
|
+
except ValueError as e:
|
|
1184
|
+
logger.warning(
|
|
1185
|
+
"Invalid correlation_id filter format, ignoring filter",
|
|
1186
|
+
extra={
|
|
1187
|
+
"provided_value": str(correlation_id_raw)[:100],
|
|
1188
|
+
"error": str(e),
|
|
1189
|
+
"correlation_id": str(correlation_id),
|
|
1190
|
+
},
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
filter_node_id: str | None = None
|
|
1194
|
+
node_id_raw = payload.get("node_id")
|
|
1195
|
+
if node_id_raw is not None:
|
|
1196
|
+
if isinstance(node_id_raw, str):
|
|
1197
|
+
filter_node_id = node_id_raw
|
|
1198
|
+
else:
|
|
1199
|
+
logger.warning(
|
|
1200
|
+
"Invalid node_id filter type, ignoring filter",
|
|
1201
|
+
extra={
|
|
1202
|
+
"provided_type": type(node_id_raw).__name__,
|
|
1203
|
+
"correlation_id": str(correlation_id),
|
|
1204
|
+
},
|
|
1205
|
+
)
|
|
1206
|
+
|
|
1207
|
+
filter_created_after: datetime | None = None
|
|
1208
|
+
created_after_raw = payload.get("created_after")
|
|
1209
|
+
if created_after_raw is not None:
|
|
1210
|
+
try:
|
|
1211
|
+
if isinstance(created_after_raw, datetime):
|
|
1212
|
+
filter_created_after = created_after_raw
|
|
1213
|
+
warn_if_naive_datetime(
|
|
1214
|
+
filter_created_after,
|
|
1215
|
+
field_name="created_after",
|
|
1216
|
+
correlation_id=correlation_id,
|
|
1217
|
+
)
|
|
1218
|
+
elif isinstance(created_after_raw, str):
|
|
1219
|
+
filter_created_after = datetime.fromisoformat(
|
|
1220
|
+
created_after_raw.replace("Z", "+00:00")
|
|
1221
|
+
)
|
|
1222
|
+
else:
|
|
1223
|
+
logger.warning(
|
|
1224
|
+
"Invalid created_after filter type, ignoring filter",
|
|
1225
|
+
extra={
|
|
1226
|
+
"provided_type": type(created_after_raw).__name__,
|
|
1227
|
+
"correlation_id": str(correlation_id),
|
|
1228
|
+
},
|
|
1229
|
+
)
|
|
1230
|
+
except ValueError as e:
|
|
1231
|
+
logger.warning(
|
|
1232
|
+
"Invalid created_after filter format, ignoring filter",
|
|
1233
|
+
extra={
|
|
1234
|
+
"provided_value": str(created_after_raw)[:100],
|
|
1235
|
+
"error": str(e),
|
|
1236
|
+
"correlation_id": str(correlation_id),
|
|
1237
|
+
},
|
|
1238
|
+
)
|
|
1239
|
+
|
|
1240
|
+
filter_created_before: datetime | None = None
|
|
1241
|
+
created_before_raw = payload.get("created_before")
|
|
1242
|
+
if created_before_raw is not None:
|
|
1243
|
+
try:
|
|
1244
|
+
if isinstance(created_before_raw, datetime):
|
|
1245
|
+
filter_created_before = created_before_raw
|
|
1246
|
+
warn_if_naive_datetime(
|
|
1247
|
+
filter_created_before,
|
|
1248
|
+
field_name="created_before",
|
|
1249
|
+
correlation_id=correlation_id,
|
|
1250
|
+
)
|
|
1251
|
+
elif isinstance(created_before_raw, str):
|
|
1252
|
+
filter_created_before = datetime.fromisoformat(
|
|
1253
|
+
created_before_raw.replace("Z", "+00:00")
|
|
1254
|
+
)
|
|
1255
|
+
else:
|
|
1256
|
+
logger.warning(
|
|
1257
|
+
"Invalid created_before filter type, ignoring filter",
|
|
1258
|
+
extra={
|
|
1259
|
+
"provided_type": type(created_before_raw).__name__,
|
|
1260
|
+
"correlation_id": str(correlation_id),
|
|
1261
|
+
},
|
|
1262
|
+
)
|
|
1263
|
+
except ValueError as e:
|
|
1264
|
+
logger.warning(
|
|
1265
|
+
"Invalid created_before filter format, ignoring filter",
|
|
1266
|
+
extra={
|
|
1267
|
+
"provided_value": str(created_before_raw)[:100],
|
|
1268
|
+
"error": str(e),
|
|
1269
|
+
"correlation_id": str(correlation_id),
|
|
1270
|
+
},
|
|
1271
|
+
)
|
|
1272
|
+
|
|
1273
|
+
metadata_only_raw = payload.get("metadata_only", False)
|
|
1274
|
+
if isinstance(metadata_only_raw, bool):
|
|
1275
|
+
metadata_only = metadata_only_raw
|
|
1276
|
+
else:
|
|
1277
|
+
logger.warning(
|
|
1278
|
+
"Invalid metadata_only filter type, using default False",
|
|
1279
|
+
extra={
|
|
1280
|
+
"provided_type": type(metadata_only_raw).__name__,
|
|
1281
|
+
"provided_value": str(metadata_only_raw)[:100],
|
|
1282
|
+
"correlation_id": str(correlation_id),
|
|
1283
|
+
},
|
|
1284
|
+
)
|
|
1285
|
+
metadata_only = False
|
|
1286
|
+
|
|
1287
|
+
limit_raw = payload.get("limit", 100)
|
|
1288
|
+
if isinstance(limit_raw, int) and limit_raw >= 1:
|
|
1289
|
+
limit = min(limit_raw, 10000) # Cap at 10000
|
|
1290
|
+
else:
|
|
1291
|
+
logger.warning(
|
|
1292
|
+
"Invalid limit filter value, using default 100",
|
|
1293
|
+
extra={
|
|
1294
|
+
"provided_type": type(limit_raw).__name__,
|
|
1295
|
+
"provided_value": str(limit_raw)[:100],
|
|
1296
|
+
"correlation_id": str(correlation_id),
|
|
1297
|
+
},
|
|
1298
|
+
)
|
|
1299
|
+
limit = 100
|
|
1300
|
+
|
|
1301
|
+
async def _do_query_io() -> ModelHandlerOutput[dict[str, object]]:
|
|
1302
|
+
"""Inner function containing I/O operations (wrapped with retry)."""
|
|
1303
|
+
if self._storage_path is None:
|
|
1304
|
+
raise RuntimeHostError(
|
|
1305
|
+
"Handler not initialized - storage_path is None",
|
|
1306
|
+
context=ctx,
|
|
1307
|
+
)
|
|
1308
|
+
|
|
1309
|
+
manifests_metadata: list[ModelManifestMetadata] = []
|
|
1310
|
+
manifests_data: list[dict[str, object]] = []
|
|
1311
|
+
count = 0
|
|
1312
|
+
|
|
1313
|
+
# Scan date directories with performance tracking
|
|
1314
|
+
scan_start_time = time.monotonic()
|
|
1315
|
+
files_scanned = 0
|
|
1316
|
+
directories_scanned = 0
|
|
1317
|
+
|
|
1318
|
+
for year_dir in sorted(self._storage_path.iterdir(), reverse=True):
|
|
1319
|
+
if not year_dir.is_dir() or count >= limit:
|
|
1320
|
+
continue
|
|
1321
|
+
directories_scanned += 1
|
|
1322
|
+
for month_dir in sorted(year_dir.iterdir(), reverse=True):
|
|
1323
|
+
if not month_dir.is_dir() or count >= limit:
|
|
1324
|
+
continue
|
|
1325
|
+
directories_scanned += 1
|
|
1326
|
+
for day_dir in sorted(month_dir.iterdir(), reverse=True):
|
|
1327
|
+
if not day_dir.is_dir() or count >= limit:
|
|
1328
|
+
continue
|
|
1329
|
+
directories_scanned += 1
|
|
1330
|
+
for manifest_file in sorted(
|
|
1331
|
+
day_dir.glob("*.json"), reverse=True
|
|
1332
|
+
):
|
|
1333
|
+
if count >= limit:
|
|
1334
|
+
break
|
|
1335
|
+
files_scanned += 1
|
|
1336
|
+
|
|
1337
|
+
try:
|
|
1338
|
+
file_stat = manifest_file.stat()
|
|
1339
|
+
file_size = file_stat.st_size
|
|
1340
|
+
|
|
1341
|
+
# Skip files that are too large
|
|
1342
|
+
if file_size > self._max_file_size:
|
|
1343
|
+
continue
|
|
1344
|
+
|
|
1345
|
+
# Full deserialization required to access filter
|
|
1346
|
+
# fields (correlation_id, node_id, created_at)
|
|
1347
|
+
# stored within the manifest JSON.
|
|
1348
|
+
#
|
|
1349
|
+
# The `metadata_only` flag controls the RETURN
|
|
1350
|
+
# format (full manifest vs. summary), not the
|
|
1351
|
+
# read pattern. This is a limitation of
|
|
1352
|
+
# filesystem storage: filter fields are not
|
|
1353
|
+
# available as external file metadata.
|
|
1354
|
+
manifest_json = manifest_file.read_text(
|
|
1355
|
+
encoding="utf-8"
|
|
1356
|
+
)
|
|
1357
|
+
manifest_data = json.loads(manifest_json)
|
|
1358
|
+
|
|
1359
|
+
# Extract fields for filtering
|
|
1360
|
+
manifest_id_str = manifest_data.get("manifest_id")
|
|
1361
|
+
if not manifest_id_str:
|
|
1362
|
+
continue
|
|
1363
|
+
|
|
1364
|
+
try:
|
|
1365
|
+
manifest_id = UUID(str(manifest_id_str))
|
|
1366
|
+
except ValueError:
|
|
1367
|
+
continue
|
|
1368
|
+
|
|
1369
|
+
created_at_str = manifest_data.get("created_at")
|
|
1370
|
+
try:
|
|
1371
|
+
if isinstance(created_at_str, str):
|
|
1372
|
+
manifest_created_at = datetime.fromisoformat(
|
|
1373
|
+
created_at_str.replace("Z", "+00:00")
|
|
1374
|
+
)
|
|
1375
|
+
else:
|
|
1376
|
+
continue
|
|
1377
|
+
except ValueError:
|
|
1378
|
+
continue
|
|
1379
|
+
|
|
1380
|
+
manifest_correlation_id: UUID | None = None
|
|
1381
|
+
manifest_corr_id_raw = manifest_data.get(
|
|
1382
|
+
"correlation_id"
|
|
1383
|
+
)
|
|
1384
|
+
if manifest_corr_id_raw:
|
|
1385
|
+
try:
|
|
1386
|
+
manifest_correlation_id = UUID(
|
|
1387
|
+
str(manifest_corr_id_raw)
|
|
1388
|
+
)
|
|
1389
|
+
except ValueError:
|
|
1390
|
+
pass
|
|
1391
|
+
|
|
1392
|
+
node_identity = manifest_data.get("node_identity", {})
|
|
1393
|
+
manifest_node_id = (
|
|
1394
|
+
node_identity.get("node_id")
|
|
1395
|
+
if isinstance(node_identity, dict)
|
|
1396
|
+
else None
|
|
1397
|
+
)
|
|
1398
|
+
|
|
1399
|
+
# Apply filters
|
|
1400
|
+
if filter_correlation_id is not None:
|
|
1401
|
+
if manifest_correlation_id != filter_correlation_id:
|
|
1402
|
+
continue
|
|
1403
|
+
|
|
1404
|
+
if filter_node_id is not None:
|
|
1405
|
+
if manifest_node_id != filter_node_id:
|
|
1406
|
+
continue
|
|
1407
|
+
|
|
1408
|
+
if filter_created_after is not None:
|
|
1409
|
+
if manifest_created_at < filter_created_after:
|
|
1410
|
+
continue
|
|
1411
|
+
|
|
1412
|
+
if filter_created_before is not None:
|
|
1413
|
+
if manifest_created_at > filter_created_before:
|
|
1414
|
+
continue
|
|
1415
|
+
|
|
1416
|
+
# Manifest passes filters
|
|
1417
|
+
if metadata_only:
|
|
1418
|
+
metadata = ModelManifestMetadata(
|
|
1419
|
+
manifest_id=manifest_id,
|
|
1420
|
+
created_at=manifest_created_at,
|
|
1421
|
+
correlation_id=manifest_correlation_id,
|
|
1422
|
+
node_id=manifest_node_id,
|
|
1423
|
+
file_path=str(manifest_file),
|
|
1424
|
+
file_size=file_size,
|
|
1425
|
+
)
|
|
1426
|
+
manifests_metadata.append(metadata)
|
|
1427
|
+
else:
|
|
1428
|
+
manifests_data.append(manifest_data)
|
|
1429
|
+
|
|
1430
|
+
count += 1
|
|
1431
|
+
|
|
1432
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
1433
|
+
logger.warning(
|
|
1434
|
+
"Failed to read manifest file: %s - %s",
|
|
1435
|
+
manifest_file,
|
|
1436
|
+
e,
|
|
1437
|
+
extra={
|
|
1438
|
+
"path": str(manifest_file),
|
|
1439
|
+
"error": str(e),
|
|
1440
|
+
"correlation_id": str(correlation_id),
|
|
1441
|
+
},
|
|
1442
|
+
)
|
|
1443
|
+
continue
|
|
1444
|
+
|
|
1445
|
+
scan_duration = time.monotonic() - scan_start_time
|
|
1446
|
+
logger.debug(
|
|
1447
|
+
"Directory scan completed for query",
|
|
1448
|
+
extra={
|
|
1449
|
+
"duration_seconds": round(scan_duration, 6),
|
|
1450
|
+
"directories_scanned": directories_scanned,
|
|
1451
|
+
"files_scanned": files_scanned,
|
|
1452
|
+
"matches_found": count,
|
|
1453
|
+
"limit": limit,
|
|
1454
|
+
"correlation_id": str(correlation_id),
|
|
1455
|
+
},
|
|
1456
|
+
)
|
|
1457
|
+
|
|
1458
|
+
result = ModelManifestQueryResult(
|
|
1459
|
+
manifests=manifests_metadata,
|
|
1460
|
+
manifest_data=manifests_data,
|
|
1461
|
+
total_count=count,
|
|
1462
|
+
metadata_only=metadata_only,
|
|
1463
|
+
)
|
|
1464
|
+
|
|
1465
|
+
logger.debug(
|
|
1466
|
+
"Manifest query completed",
|
|
1467
|
+
extra={
|
|
1468
|
+
"total_count": count,
|
|
1469
|
+
"metadata_only": metadata_only,
|
|
1470
|
+
"correlation_id": str(correlation_id),
|
|
1471
|
+
},
|
|
1472
|
+
)
|
|
1473
|
+
|
|
1474
|
+
return ModelHandlerOutput.for_compute(
|
|
1475
|
+
input_envelope_id=input_envelope_id,
|
|
1476
|
+
correlation_id=correlation_id,
|
|
1477
|
+
handler_id=HANDLER_ID_MANIFEST_PERSISTENCE,
|
|
1478
|
+
result={
|
|
1479
|
+
"status": "success",
|
|
1480
|
+
"payload": result.model_dump(mode="json"),
|
|
1481
|
+
"correlation_id": str(correlation_id),
|
|
1482
|
+
},
|
|
1483
|
+
)
|
|
1484
|
+
|
|
1485
|
+
# Execute with retry logic (handles circuit breaker and backoff)
|
|
1486
|
+
return await self._execute_with_retry(operation, _do_query_io, correlation_id)
|
|
1487
|
+
|
|
1488
|
+
def describe(self) -> dict[str, object]:
|
|
1489
|
+
"""Return handler metadata and capabilities for introspection.
|
|
1490
|
+
|
|
1491
|
+
This method exposes the handler's type classification along with
|
|
1492
|
+
its operational configuration and capabilities, including detailed
|
|
1493
|
+
circuit breaker state for operational observability.
|
|
1494
|
+
|
|
1495
|
+
Returns:
|
|
1496
|
+
dict containing:
|
|
1497
|
+
- handler_type: Architectural role from handler_type property
|
|
1498
|
+
- handler_category: Behavioral classification
|
|
1499
|
+
- supported_operations: List of supported operations
|
|
1500
|
+
- storage_path: Storage directory path (when initialized)
|
|
1501
|
+
- initialized: Whether the handler is initialized
|
|
1502
|
+
- version: Handler version string
|
|
1503
|
+
- circuit_breaker: Circuit breaker state for observability
|
|
1504
|
+
- initialized: Whether circuit breaker is initialized
|
|
1505
|
+
- state: Current state ("closed", "open", or "half_open")
|
|
1506
|
+
- failures: Current failure count
|
|
1507
|
+
- threshold: Configured failure threshold before opening
|
|
1508
|
+
- reset_timeout_seconds: Configured timeout before half_open transition
|
|
1509
|
+
- seconds_until_half_open: Seconds remaining until half_open (only when open)
|
|
1510
|
+
|
|
1511
|
+
Circuit Breaker States:
|
|
1512
|
+
- **closed**: Normal operation, requests allowed. Failures tracked.
|
|
1513
|
+
- **open**: Circuit tripped after threshold failures. Requests blocked.
|
|
1514
|
+
Will transition to half_open after reset_timeout_seconds.
|
|
1515
|
+
- **half_open**: Recovery testing phase. Next success closes circuit,
|
|
1516
|
+
next failure reopens it. This state is transient and detected when
|
|
1517
|
+
the circuit is marked open but the reset timeout has elapsed.
|
|
1518
|
+
"""
|
|
1519
|
+
# Get circuit breaker state from mixin (encapsulated access)
|
|
1520
|
+
circuit_breaker_info = self._get_circuit_breaker_state()
|
|
1521
|
+
|
|
1522
|
+
# Override initialized with handler's own flag for precise tracking
|
|
1523
|
+
# (handler may track initialization more granularly than mixin detection)
|
|
1524
|
+
circuit_breaker_info["initialized"] = self._circuit_breaker_initialized
|
|
1525
|
+
|
|
1526
|
+
result: dict[str, object] = {
|
|
1527
|
+
"handler_type": self.handler_type.value,
|
|
1528
|
+
"handler_category": self.handler_category.value,
|
|
1529
|
+
"supported_operations": sorted(_SUPPORTED_OPERATIONS),
|
|
1530
|
+
"storage_path": str(self._storage_path) if self._storage_path else None,
|
|
1531
|
+
"initialized": self._initialized,
|
|
1532
|
+
"version": "0.1.0",
|
|
1533
|
+
"circuit_breaker": circuit_breaker_info,
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
return result
|
|
1537
|
+
|
|
1538
|
+
|
|
1539
|
+
__all__: list[str] = ["HandlerManifestPersistence", "HANDLER_ID_MANIFEST_PERSISTENCE"]
|