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,1223 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Pipeline hook for cross-cutting observability concerns.
|
|
4
|
+
|
|
5
|
+
This module provides the HookObservability class, a pipeline hook that enables
|
|
6
|
+
cross-cutting observability instrumentation for infrastructure components.
|
|
7
|
+
The hook tracks operation timing, emits metrics, and maintains execution context
|
|
8
|
+
across async boundaries.
|
|
9
|
+
|
|
10
|
+
CRITICAL: Concurrency Safety via contextvars
|
|
11
|
+
--------------------------------------------
|
|
12
|
+
This implementation uses contextvars exclusively for all timing and operation
|
|
13
|
+
state. This is a CRITICAL design decision to prevent concurrency bugs in async
|
|
14
|
+
code. Each async task gets its own isolated context, preventing race conditions
|
|
15
|
+
where multiple concurrent operations would corrupt shared timing state.
|
|
16
|
+
|
|
17
|
+
Why NOT use instance variables:
|
|
18
|
+
# WRONG - Race condition in async code!
|
|
19
|
+
class BadHook:
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self._start_time = 0.0 # Shared across all concurrent operations!
|
|
22
|
+
|
|
23
|
+
def before_operation(self, operation: str):
|
|
24
|
+
self._start_time = time.perf_counter() # Overwrites previous!
|
|
25
|
+
|
|
26
|
+
def after_operation(self):
|
|
27
|
+
return time.perf_counter() - self._start_time # Wrong value!
|
|
28
|
+
|
|
29
|
+
Why contextvars ARE correct:
|
|
30
|
+
# CORRECT - Each async task has isolated state
|
|
31
|
+
_start_time: ContextVar[float | None] = ContextVar("start_time", default=None)
|
|
32
|
+
|
|
33
|
+
class GoodHook:
|
|
34
|
+
def before_operation(self, operation: str):
|
|
35
|
+
_start_time.set(time.perf_counter()) # Isolated per-task
|
|
36
|
+
|
|
37
|
+
def after_operation(self):
|
|
38
|
+
return time.perf_counter() - _start_time.get() # Correct per-task
|
|
39
|
+
|
|
40
|
+
Thread-Safety Guarantees
|
|
41
|
+
------------------------
|
|
42
|
+
This class is thread-safe with the following guarantees:
|
|
43
|
+
|
|
44
|
+
1. **Timing State**: All timing and operation state is stored in contextvars,
|
|
45
|
+
which provide per-task isolation in async code and per-thread isolation in
|
|
46
|
+
threaded code. Concurrent operations NEVER share timing state.
|
|
47
|
+
|
|
48
|
+
2. **Metrics Sink**: The metrics_sink is accessed via a read-only property
|
|
49
|
+
from multiple async tasks. The metrics_sink implementation MUST be
|
|
50
|
+
thread-safe. The built-in SinkMetricsPrometheus satisfies this requirement
|
|
51
|
+
via internal locking. If using a custom sink, ensure it is thread-safe.
|
|
52
|
+
The metrics_sink is set once during __init__ and exposed as read-only to
|
|
53
|
+
prevent accidental modification.
|
|
54
|
+
|
|
55
|
+
3. **Class Constants**: _HIGH_CARDINALITY_KEYS is a frozenset (immutable),
|
|
56
|
+
ensuring thread-safe read access.
|
|
57
|
+
|
|
58
|
+
4. **Instance Variables**: The only instance variable (__metrics_sink) is set
|
|
59
|
+
once during __init__ and exposed via a read-only property, ensuring
|
|
60
|
+
thread-safe reads and preventing modification after construction.
|
|
61
|
+
|
|
62
|
+
5. **Module-Level Singleton**: The global singleton hook instance is protected
|
|
63
|
+
by a threading.Lock for thread-safe initialization across threads.
|
|
64
|
+
|
|
65
|
+
Singleton Support
|
|
66
|
+
-----------------
|
|
67
|
+
This module provides optional singleton support via get_global_hook() and
|
|
68
|
+
configure_global_hook(). The singleton pattern is useful when:
|
|
69
|
+
|
|
70
|
+
- Multiple components need to share the same observability infrastructure
|
|
71
|
+
- You want centralized metrics collection across the application
|
|
72
|
+
- Resource efficiency is important
|
|
73
|
+
|
|
74
|
+
Important: When a singleton already exists, calling configure_global_hook()
|
|
75
|
+
with different configuration will log a warning and return the existing
|
|
76
|
+
instance. Use clear_global_hook() to reset the singleton if reconfiguration
|
|
77
|
+
is needed.
|
|
78
|
+
|
|
79
|
+
Usage Example:
|
|
80
|
+
```python
|
|
81
|
+
from omnibase_infra.observability.hooks import HookObservability
|
|
82
|
+
from omnibase_spi.protocols.observability import ProtocolHotPathMetricsSink
|
|
83
|
+
|
|
84
|
+
# Create hook with optional metrics sink
|
|
85
|
+
sink: ProtocolHotPathMetricsSink = get_metrics_sink()
|
|
86
|
+
hook = HookObservability(metrics_sink=sink)
|
|
87
|
+
|
|
88
|
+
# Use in handler execution
|
|
89
|
+
hook.before_operation("handler.execute", correlation_id="abc-123")
|
|
90
|
+
try:
|
|
91
|
+
result = await handler.execute(payload)
|
|
92
|
+
hook.record_success()
|
|
93
|
+
except Exception as e:
|
|
94
|
+
hook.record_failure(str(type(e).__name__))
|
|
95
|
+
raise
|
|
96
|
+
finally:
|
|
97
|
+
duration_ms = hook.after_operation()
|
|
98
|
+
logger.info(f"Operation took {duration_ms:.2f}ms")
|
|
99
|
+
|
|
100
|
+
# Or use the global singleton
|
|
101
|
+
from omnibase_infra.observability.hooks import get_global_hook
|
|
102
|
+
hook = get_global_hook() # Returns singleton, creates if needed
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
See Also:
|
|
106
|
+
- ProtocolHotPathMetricsSink: Metrics collection interface
|
|
107
|
+
- correlation.py: Correlation ID context management pattern
|
|
108
|
+
- docs/patterns/observability_patterns.md: Observability guidelines
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
from __future__ import annotations
|
|
112
|
+
|
|
113
|
+
import logging
|
|
114
|
+
import threading
|
|
115
|
+
import time
|
|
116
|
+
from contextvars import ContextVar, Token
|
|
117
|
+
from typing import TYPE_CHECKING
|
|
118
|
+
from uuid import UUID
|
|
119
|
+
|
|
120
|
+
_logger = logging.getLogger(__name__)
|
|
121
|
+
|
|
122
|
+
# =============================================================================
|
|
123
|
+
# MODULE-LEVEL SINGLETON STATE
|
|
124
|
+
# =============================================================================
|
|
125
|
+
#
|
|
126
|
+
# Thread-safe singleton support for global hook instance. Protected by
|
|
127
|
+
# _global_hook_lock for safe initialization from multiple threads.
|
|
128
|
+
#
|
|
129
|
+
# =============================================================================
|
|
130
|
+
|
|
131
|
+
# Forward reference resolved by `from __future__ import annotations`
|
|
132
|
+
_global_hook_instance: HookObservability | None = None
|
|
133
|
+
_global_hook_lock = threading.Lock()
|
|
134
|
+
|
|
135
|
+
if TYPE_CHECKING:
|
|
136
|
+
from types import TracebackType
|
|
137
|
+
|
|
138
|
+
from omnibase_spi.protocols.observability import ProtocolHotPathMetricsSink
|
|
139
|
+
|
|
140
|
+
# =============================================================================
|
|
141
|
+
# CONTEXT VARIABLES FOR CONCURRENCY-SAFE OPERATION TRACKING
|
|
142
|
+
# =============================================================================
|
|
143
|
+
#
|
|
144
|
+
# These ContextVars provide per-async-task isolation for operation state.
|
|
145
|
+
# Each concurrent operation gets its own isolated copy of these values,
|
|
146
|
+
# preventing race conditions in high-concurrency environments.
|
|
147
|
+
#
|
|
148
|
+
# DO NOT convert these to instance variables - that would break concurrency!
|
|
149
|
+
# =============================================================================
|
|
150
|
+
|
|
151
|
+
# Operation start time in perf_counter units (high-resolution monotonic clock)
|
|
152
|
+
_start_time: ContextVar[float | None] = ContextVar("hook_start_time", default=None)
|
|
153
|
+
|
|
154
|
+
# Current operation name (e.g., "handler.execute", "retry.attempt")
|
|
155
|
+
_operation_name: ContextVar[str | None] = ContextVar(
|
|
156
|
+
"hook_operation_name", default=None
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Correlation ID for distributed tracing (propagated from request context)
|
|
160
|
+
_correlation_id: ContextVar[str | None] = ContextVar(
|
|
161
|
+
"hook_correlation_id", default=None
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Additional operation labels for metrics (e.g., handler name, status)
|
|
165
|
+
# Note: ContextVar doesn't support default_factory, so we use None and handle it
|
|
166
|
+
_operation_labels: ContextVar[dict[str, str] | None] = ContextVar(
|
|
167
|
+
"hook_operation_labels", default=None
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class HookObservability:
|
|
172
|
+
"""Pipeline hook for cross-cutting observability instrumentation.
|
|
173
|
+
|
|
174
|
+
This hook provides timing, metrics, and context management for infrastructure
|
|
175
|
+
operations. It uses contextvars for all state to ensure concurrency safety
|
|
176
|
+
in async code paths.
|
|
177
|
+
|
|
178
|
+
Key Features:
|
|
179
|
+
- Concurrency-safe timing via contextvars (NOT instance variables)
|
|
180
|
+
- Metrics emission via ProtocolHotPathMetricsSink
|
|
181
|
+
- Operation context propagation across async boundaries
|
|
182
|
+
- Support for nested operation tracking via context manager
|
|
183
|
+
- High-cardinality label filtering to prevent metric explosion
|
|
184
|
+
|
|
185
|
+
Thread-Safety Guarantees:
|
|
186
|
+
This class is safe for concurrent use from multiple async tasks.
|
|
187
|
+
All timing and operation state is stored in contextvars, which provide
|
|
188
|
+
per-task isolation. The metrics sink (if provided) MUST be thread-safe.
|
|
189
|
+
|
|
190
|
+
Specific guarantees:
|
|
191
|
+
1. Timing state (start_time, operation_name, etc.) is isolated per-task
|
|
192
|
+
2. The metrics_sink is set once in __init__ and never modified
|
|
193
|
+
3. _HIGH_CARDINALITY_KEYS is immutable (frozenset)
|
|
194
|
+
4. No mutable shared state exists between concurrent operations
|
|
195
|
+
|
|
196
|
+
High-Cardinality Label Filtering:
|
|
197
|
+
Labels containing high-cardinality keys (correlation_id, request_id,
|
|
198
|
+
trace_id, span_id, session_id, user_id) are automatically filtered
|
|
199
|
+
from metrics to prevent cardinality explosion. These values remain
|
|
200
|
+
available via get_current_context() for logging and tracing.
|
|
201
|
+
|
|
202
|
+
When labels are filtered, a debug log is emitted to aid troubleshooting.
|
|
203
|
+
Metrics are NEVER dropped entirely - only high-cardinality labels are
|
|
204
|
+
removed from the label set.
|
|
205
|
+
|
|
206
|
+
Metrics Emitted:
|
|
207
|
+
- `operation_started_total`: Counter incremented when operation starts
|
|
208
|
+
- `operation_completed_total`: Counter incremented when operation completes
|
|
209
|
+
- `operation_failed_total`: Counter incremented on failure
|
|
210
|
+
- `operation_duration_seconds`: Histogram of operation durations
|
|
211
|
+
- `retry_attempt_total`: Counter for retry attempts
|
|
212
|
+
- `circuit_breaker_state_change_total`: Counter for circuit state changes
|
|
213
|
+
|
|
214
|
+
Attributes:
|
|
215
|
+
metrics_sink: Read-only property for the metrics sink. Returns None if
|
|
216
|
+
no sink was provided. The sink MUST be thread-safe for concurrent
|
|
217
|
+
access from multiple async tasks.
|
|
218
|
+
|
|
219
|
+
Example:
|
|
220
|
+
```python
|
|
221
|
+
hook = HookObservability(metrics_sink=sink)
|
|
222
|
+
|
|
223
|
+
# Manual instrumentation
|
|
224
|
+
hook.before_operation("db.query", correlation_id="req-123")
|
|
225
|
+
try:
|
|
226
|
+
result = await db.execute(query)
|
|
227
|
+
hook.record_success()
|
|
228
|
+
except Exception:
|
|
229
|
+
hook.record_failure("DatabaseError")
|
|
230
|
+
raise
|
|
231
|
+
finally:
|
|
232
|
+
duration = hook.after_operation()
|
|
233
|
+
|
|
234
|
+
# Context manager for automatic timing
|
|
235
|
+
with hook.operation_context("http.request", correlation_id="req-456"):
|
|
236
|
+
response = await http_client.get(url)
|
|
237
|
+
```
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
# Use __slots__ to prevent accidental attribute addition and improve memory
|
|
241
|
+
# _metrics_lock provides defense-in-depth thread safety for metrics operations
|
|
242
|
+
__slots__ = ("__metrics_sink", "_metrics_lock")
|
|
243
|
+
|
|
244
|
+
def __init__(
|
|
245
|
+
self,
|
|
246
|
+
metrics_sink: ProtocolHotPathMetricsSink | None = None,
|
|
247
|
+
) -> None:
|
|
248
|
+
"""Initialize the observability hook.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
metrics_sink: Optional metrics sink for emitting observability data.
|
|
252
|
+
If None, the hook operates in no-op mode for metrics (timing
|
|
253
|
+
is still tracked). This allows the hook to be used even when
|
|
254
|
+
metrics infrastructure is not available.
|
|
255
|
+
|
|
256
|
+
Thread-Safety Requirements:
|
|
257
|
+
The metrics_sink (if provided) MUST be thread-safe for concurrent
|
|
258
|
+
access from multiple async tasks. The built-in SinkMetricsPrometheus
|
|
259
|
+
satisfies this requirement. If using a custom sink implementation,
|
|
260
|
+
ensure all methods (increment_counter, set_gauge, observe_histogram)
|
|
261
|
+
are thread-safe.
|
|
262
|
+
|
|
263
|
+
Note:
|
|
264
|
+
The metrics_sink is stored as a private instance variable and
|
|
265
|
+
exposed via a read-only property. This prevents accidental
|
|
266
|
+
modification after construction, ensuring thread-safe reads.
|
|
267
|
+
This is intentionally different from timing state which MUST be
|
|
268
|
+
in contextvars for per-task isolation.
|
|
269
|
+
|
|
270
|
+
Thread-Safety Implementation:
|
|
271
|
+
A threading.Lock (_metrics_lock) is used for defense-in-depth protection
|
|
272
|
+
of all metrics sink operations. While the metrics_sink is expected to be
|
|
273
|
+
thread-safe, this lock ensures atomic operation sequences and protects
|
|
274
|
+
against potential subtle thread-safety issues in sink implementations.
|
|
275
|
+
"""
|
|
276
|
+
# Use name mangling (__) to prevent external modification
|
|
277
|
+
# Access via the metrics_sink property
|
|
278
|
+
self.__metrics_sink = metrics_sink
|
|
279
|
+
# Defense-in-depth lock for metrics operations
|
|
280
|
+
# Ensures atomic counter increments and metric emissions even if sink
|
|
281
|
+
# has subtle thread-safety issues
|
|
282
|
+
self._metrics_lock = threading.Lock()
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def metrics_sink(self) -> ProtocolHotPathMetricsSink | None:
|
|
286
|
+
"""Read-only access to the metrics sink.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
The metrics sink provided during construction, or None if no
|
|
290
|
+
sink was provided.
|
|
291
|
+
|
|
292
|
+
Thread-Safety:
|
|
293
|
+
This property is thread-safe. The underlying value is set once
|
|
294
|
+
during __init__ and never modified.
|
|
295
|
+
"""
|
|
296
|
+
return self.__metrics_sink
|
|
297
|
+
|
|
298
|
+
# =========================================================================
|
|
299
|
+
# CORE TIMING API
|
|
300
|
+
# =========================================================================
|
|
301
|
+
|
|
302
|
+
def before_operation(
|
|
303
|
+
self,
|
|
304
|
+
operation: str,
|
|
305
|
+
correlation_id: str | UUID | None = None,
|
|
306
|
+
labels: dict[str, str] | None = None,
|
|
307
|
+
) -> None:
|
|
308
|
+
"""Mark the start of an operation for timing.
|
|
309
|
+
|
|
310
|
+
Sets up the timing context for the current async task. This method
|
|
311
|
+
MUST be called before after_operation() to establish the start time.
|
|
312
|
+
|
|
313
|
+
Concurrency Safety:
|
|
314
|
+
Uses contextvars to store timing state, ensuring each async task
|
|
315
|
+
has its own isolated start time. Multiple concurrent operations
|
|
316
|
+
will not interfere with each other.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
operation: Name of the operation being tracked. Should follow
|
|
320
|
+
a dotted naming convention (e.g., "handler.execute",
|
|
321
|
+
"db.query", "http.request"). This is used as the metric name.
|
|
322
|
+
correlation_id: Optional correlation ID for distributed tracing.
|
|
323
|
+
Can be a string or UUID. If UUID, it will be converted to string.
|
|
324
|
+
labels: Optional additional labels to attach to metrics. Keys and
|
|
325
|
+
values must be strings. Common labels: "handler", "status".
|
|
326
|
+
|
|
327
|
+
Side Effects:
|
|
328
|
+
- Sets _start_time contextvar to current perf_counter value
|
|
329
|
+
- Sets _operation_name contextvar to operation parameter
|
|
330
|
+
- Sets _correlation_id contextvar if provided
|
|
331
|
+
- Sets _operation_labels contextvar if provided
|
|
332
|
+
- Increments "operation_started_total" counter if metrics sink present
|
|
333
|
+
|
|
334
|
+
Example:
|
|
335
|
+
```python
|
|
336
|
+
hook.before_operation(
|
|
337
|
+
"handler.process",
|
|
338
|
+
correlation_id="abc-123",
|
|
339
|
+
labels={"handler": "UserHandler"},
|
|
340
|
+
)
|
|
341
|
+
```
|
|
342
|
+
"""
|
|
343
|
+
# Store timing state in contextvars for concurrency safety
|
|
344
|
+
_start_time.set(time.perf_counter())
|
|
345
|
+
_operation_name.set(operation)
|
|
346
|
+
|
|
347
|
+
# Convert UUID to string if needed
|
|
348
|
+
if correlation_id is not None:
|
|
349
|
+
_correlation_id.set(str(correlation_id))
|
|
350
|
+
else:
|
|
351
|
+
_correlation_id.set(None)
|
|
352
|
+
|
|
353
|
+
# Store labels (or empty dict if none provided)
|
|
354
|
+
_operation_labels.set(labels.copy() if labels else {})
|
|
355
|
+
|
|
356
|
+
# Emit start metric if sink is available
|
|
357
|
+
# Lock ensures atomic counter increment across concurrent calls
|
|
358
|
+
if self.metrics_sink is not None:
|
|
359
|
+
metric_labels = self._build_metric_labels(operation)
|
|
360
|
+
with self._metrics_lock:
|
|
361
|
+
self.metrics_sink.increment_counter(
|
|
362
|
+
name="operation_started_total",
|
|
363
|
+
labels=metric_labels,
|
|
364
|
+
increment=1,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def after_operation(self) -> float:
|
|
368
|
+
"""Mark the end of an operation and calculate duration.
|
|
369
|
+
|
|
370
|
+
Calculates the elapsed time since before_operation() was called and
|
|
371
|
+
optionally emits the duration as a histogram observation.
|
|
372
|
+
|
|
373
|
+
Concurrency Safety:
|
|
374
|
+
Reads timing state from contextvars, which are isolated per async
|
|
375
|
+
task. The returned duration is specific to the current task's
|
|
376
|
+
operation timing.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Duration in milliseconds since before_operation() was called.
|
|
380
|
+
Returns 0.0 if before_operation() was not called (start_time is None).
|
|
381
|
+
|
|
382
|
+
Side Effects:
|
|
383
|
+
- Observes "operation_duration_seconds" histogram if metrics sink present
|
|
384
|
+
- Clears _start_time contextvar (sets to None)
|
|
385
|
+
- Clears _operation_name contextvar (sets to None)
|
|
386
|
+
- Does NOT clear correlation_id (may be needed for error handling)
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
```python
|
|
390
|
+
hook.before_operation("db.query")
|
|
391
|
+
result = await db.execute(query)
|
|
392
|
+
duration_ms = hook.after_operation() # e.g., 42.5
|
|
393
|
+
logger.info(f"Query took {duration_ms:.2f}ms")
|
|
394
|
+
```
|
|
395
|
+
"""
|
|
396
|
+
start = _start_time.get()
|
|
397
|
+
operation = _operation_name.get()
|
|
398
|
+
|
|
399
|
+
# Handle case where before_operation was not called
|
|
400
|
+
if start is None:
|
|
401
|
+
return 0.0
|
|
402
|
+
|
|
403
|
+
# Calculate duration
|
|
404
|
+
end = time.perf_counter()
|
|
405
|
+
duration_seconds = end - start
|
|
406
|
+
duration_ms = duration_seconds * 1000.0
|
|
407
|
+
|
|
408
|
+
# Emit duration metric if sink is available
|
|
409
|
+
# Lock ensures atomic histogram observation across concurrent calls
|
|
410
|
+
if self.metrics_sink is not None and operation is not None:
|
|
411
|
+
metric_labels = self._build_metric_labels(operation)
|
|
412
|
+
with self._metrics_lock:
|
|
413
|
+
self.metrics_sink.observe_histogram(
|
|
414
|
+
name="operation_duration_seconds",
|
|
415
|
+
labels=metric_labels,
|
|
416
|
+
value=duration_seconds,
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# Clear timing state (but keep correlation_id for potential error handling)
|
|
420
|
+
_start_time.set(None)
|
|
421
|
+
_operation_name.set(None)
|
|
422
|
+
_operation_labels.set(None)
|
|
423
|
+
|
|
424
|
+
return duration_ms
|
|
425
|
+
|
|
426
|
+
def get_current_context(self) -> dict[str, str | None]:
|
|
427
|
+
"""Get the current operation context from contextvars.
|
|
428
|
+
|
|
429
|
+
Returns the current operation context including operation name,
|
|
430
|
+
correlation ID, and any additional labels. Useful for logging
|
|
431
|
+
and debugging.
|
|
432
|
+
|
|
433
|
+
Concurrency Safety:
|
|
434
|
+
Reads from contextvars, returning context specific to the
|
|
435
|
+
current async task.
|
|
436
|
+
|
|
437
|
+
Returns:
|
|
438
|
+
Dictionary containing:
|
|
439
|
+
- "operation": Current operation name or None
|
|
440
|
+
- "correlation_id": Current correlation ID or None
|
|
441
|
+
- Plus any additional labels from _operation_labels
|
|
442
|
+
|
|
443
|
+
Example:
|
|
444
|
+
```python
|
|
445
|
+
hook.before_operation("handler.process", correlation_id="abc-123")
|
|
446
|
+
ctx = hook.get_current_context()
|
|
447
|
+
# ctx = {"operation": "handler.process", "correlation_id": "abc-123"}
|
|
448
|
+
logger.info("Processing", extra=ctx)
|
|
449
|
+
```
|
|
450
|
+
"""
|
|
451
|
+
result: dict[str, str | None] = {
|
|
452
|
+
"operation": _operation_name.get(),
|
|
453
|
+
"correlation_id": _correlation_id.get(),
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
# Add any additional labels (they're already string -> string)
|
|
457
|
+
labels = _operation_labels.get()
|
|
458
|
+
if labels is not None:
|
|
459
|
+
for key, value in labels.items():
|
|
460
|
+
result[key] = value
|
|
461
|
+
|
|
462
|
+
return result
|
|
463
|
+
|
|
464
|
+
# =========================================================================
|
|
465
|
+
# SUCCESS/FAILURE TRACKING
|
|
466
|
+
# =========================================================================
|
|
467
|
+
|
|
468
|
+
def record_success(self, labels: dict[str, str] | None = None) -> None:
|
|
469
|
+
"""Record a successful operation completion.
|
|
470
|
+
|
|
471
|
+
Increments the operation_completed_total counter with success status.
|
|
472
|
+
Should be called after the operation completes successfully, before
|
|
473
|
+
after_operation().
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
labels: Optional additional labels to merge with operation labels.
|
|
477
|
+
|
|
478
|
+
Side Effects:
|
|
479
|
+
- Increments "operation_completed_total" counter with status="success"
|
|
480
|
+
|
|
481
|
+
Example:
|
|
482
|
+
```python
|
|
483
|
+
hook.before_operation("handler.process")
|
|
484
|
+
result = await handler.execute()
|
|
485
|
+
hook.record_success()
|
|
486
|
+
duration = hook.after_operation()
|
|
487
|
+
```
|
|
488
|
+
"""
|
|
489
|
+
if self.metrics_sink is None:
|
|
490
|
+
return
|
|
491
|
+
|
|
492
|
+
operation = _operation_name.get()
|
|
493
|
+
if operation is None:
|
|
494
|
+
return
|
|
495
|
+
|
|
496
|
+
metric_labels = self._build_metric_labels(operation, labels)
|
|
497
|
+
metric_labels["status"] = "success"
|
|
498
|
+
|
|
499
|
+
# Lock ensures atomic counter increment across concurrent calls
|
|
500
|
+
with self._metrics_lock:
|
|
501
|
+
self.metrics_sink.increment_counter(
|
|
502
|
+
name="operation_completed_total",
|
|
503
|
+
labels=metric_labels,
|
|
504
|
+
increment=1,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def record_failure(
|
|
508
|
+
self,
|
|
509
|
+
error_type: str,
|
|
510
|
+
labels: dict[str, str] | None = None,
|
|
511
|
+
) -> None:
|
|
512
|
+
"""Record a failed operation.
|
|
513
|
+
|
|
514
|
+
Increments the operation_failed_total counter with the error type.
|
|
515
|
+
Should be called when an operation fails, before after_operation().
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
error_type: Type/class name of the error that occurred.
|
|
519
|
+
Should be a stable identifier (e.g., "TimeoutError",
|
|
520
|
+
"DatabaseConnectionError"), not the error message.
|
|
521
|
+
labels: Optional additional labels to merge with operation labels.
|
|
522
|
+
|
|
523
|
+
Side Effects:
|
|
524
|
+
- Increments "operation_failed_total" counter with error_type label
|
|
525
|
+
|
|
526
|
+
Example:
|
|
527
|
+
```python
|
|
528
|
+
hook.before_operation("db.query")
|
|
529
|
+
try:
|
|
530
|
+
result = await db.execute(query)
|
|
531
|
+
hook.record_success()
|
|
532
|
+
except DatabaseError as e:
|
|
533
|
+
hook.record_failure("DatabaseError")
|
|
534
|
+
raise
|
|
535
|
+
finally:
|
|
536
|
+
hook.after_operation()
|
|
537
|
+
```
|
|
538
|
+
"""
|
|
539
|
+
if self.metrics_sink is None:
|
|
540
|
+
return
|
|
541
|
+
|
|
542
|
+
operation = _operation_name.get()
|
|
543
|
+
if operation is None:
|
|
544
|
+
return
|
|
545
|
+
|
|
546
|
+
metric_labels = self._build_metric_labels(operation, labels)
|
|
547
|
+
metric_labels["status"] = "failure"
|
|
548
|
+
metric_labels["error_type"] = error_type
|
|
549
|
+
|
|
550
|
+
# Lock ensures atomic counter increment across concurrent calls
|
|
551
|
+
with self._metrics_lock:
|
|
552
|
+
self.metrics_sink.increment_counter(
|
|
553
|
+
name="operation_failed_total",
|
|
554
|
+
labels=metric_labels,
|
|
555
|
+
increment=1,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
# =========================================================================
|
|
559
|
+
# SPECIALIZED TRACKING METHODS
|
|
560
|
+
# =========================================================================
|
|
561
|
+
|
|
562
|
+
def record_retry_attempt(
|
|
563
|
+
self,
|
|
564
|
+
attempt_number: int,
|
|
565
|
+
reason: str,
|
|
566
|
+
labels: dict[str, str] | None = None,
|
|
567
|
+
) -> None:
|
|
568
|
+
"""Record a retry attempt for an operation.
|
|
569
|
+
|
|
570
|
+
Tracks retry attempts with attempt number and reason. Useful for
|
|
571
|
+
monitoring retry behavior and identifying flaky operations.
|
|
572
|
+
|
|
573
|
+
Args:
|
|
574
|
+
attempt_number: The current attempt number (1-based). First attempt
|
|
575
|
+
is 1, first retry is 2, etc.
|
|
576
|
+
reason: Reason for the retry (e.g., "timeout", "connection_reset",
|
|
577
|
+
"rate_limited"). Should be a stable identifier.
|
|
578
|
+
labels: Optional additional labels to merge with operation labels.
|
|
579
|
+
|
|
580
|
+
Side Effects:
|
|
581
|
+
- Increments "retry_attempt_total" counter
|
|
582
|
+
|
|
583
|
+
Example:
|
|
584
|
+
```python
|
|
585
|
+
for attempt in range(1, max_retries + 1):
|
|
586
|
+
try:
|
|
587
|
+
result = await operation()
|
|
588
|
+
break
|
|
589
|
+
except RetryableError as e:
|
|
590
|
+
hook.record_retry_attempt(attempt, "transient_error")
|
|
591
|
+
if attempt == max_retries:
|
|
592
|
+
raise
|
|
593
|
+
```
|
|
594
|
+
"""
|
|
595
|
+
if self.metrics_sink is None:
|
|
596
|
+
return
|
|
597
|
+
|
|
598
|
+
operation = _operation_name.get() or "unknown"
|
|
599
|
+
metric_labels = self._build_metric_labels(operation, labels)
|
|
600
|
+
metric_labels["attempt"] = str(attempt_number)
|
|
601
|
+
metric_labels["reason"] = reason
|
|
602
|
+
|
|
603
|
+
# Lock ensures atomic counter increment across concurrent calls
|
|
604
|
+
with self._metrics_lock:
|
|
605
|
+
self.metrics_sink.increment_counter(
|
|
606
|
+
name="retry_attempt_total",
|
|
607
|
+
labels=metric_labels,
|
|
608
|
+
increment=1,
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
def record_circuit_breaker_state_change(
|
|
612
|
+
self,
|
|
613
|
+
service_name: str,
|
|
614
|
+
from_state: str,
|
|
615
|
+
to_state: str,
|
|
616
|
+
labels: dict[str, str] | None = None,
|
|
617
|
+
) -> None:
|
|
618
|
+
"""Record a circuit breaker state transition.
|
|
619
|
+
|
|
620
|
+
Tracks circuit breaker state changes for monitoring circuit health
|
|
621
|
+
and identifying unstable services.
|
|
622
|
+
|
|
623
|
+
Args:
|
|
624
|
+
service_name: Name of the service protected by the circuit breaker.
|
|
625
|
+
from_state: Previous circuit state (e.g., "CLOSED", "OPEN", "HALF_OPEN").
|
|
626
|
+
to_state: New circuit state after transition.
|
|
627
|
+
labels: Optional additional labels.
|
|
628
|
+
|
|
629
|
+
Side Effects:
|
|
630
|
+
- Increments "circuit_breaker_state_change_total" counter
|
|
631
|
+
|
|
632
|
+
Example:
|
|
633
|
+
```python
|
|
634
|
+
# In circuit breaker implementation
|
|
635
|
+
hook.record_circuit_breaker_state_change(
|
|
636
|
+
service_name="database",
|
|
637
|
+
from_state="CLOSED",
|
|
638
|
+
to_state="OPEN",
|
|
639
|
+
)
|
|
640
|
+
```
|
|
641
|
+
"""
|
|
642
|
+
if self.metrics_sink is None:
|
|
643
|
+
return
|
|
644
|
+
|
|
645
|
+
metric_labels: dict[str, str] = {
|
|
646
|
+
"service": service_name,
|
|
647
|
+
"from_state": from_state,
|
|
648
|
+
"to_state": to_state,
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
# Merge additional labels
|
|
652
|
+
if labels:
|
|
653
|
+
for key, value in labels.items():
|
|
654
|
+
if key not in metric_labels: # Don't overwrite required labels
|
|
655
|
+
metric_labels[key] = value
|
|
656
|
+
|
|
657
|
+
# Lock ensures atomic counter increment across concurrent calls
|
|
658
|
+
with self._metrics_lock:
|
|
659
|
+
self.metrics_sink.increment_counter(
|
|
660
|
+
name="circuit_breaker_state_change_total",
|
|
661
|
+
labels=metric_labels,
|
|
662
|
+
increment=1,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
def set_gauge(
|
|
666
|
+
self,
|
|
667
|
+
name: str,
|
|
668
|
+
value: float,
|
|
669
|
+
labels: dict[str, str] | None = None,
|
|
670
|
+
) -> None:
|
|
671
|
+
"""Set a gauge metric value.
|
|
672
|
+
|
|
673
|
+
Convenience method for setting gauge values through the hook.
|
|
674
|
+
Useful for tracking current state like queue depths or active connections.
|
|
675
|
+
|
|
676
|
+
Note:
|
|
677
|
+
Gauges can legitimately be negative (e.g., temperature, delta values).
|
|
678
|
+
For buffer/queue metrics that should never be negative, use
|
|
679
|
+
set_buffer_gauge() instead, which enforces non-negative values.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
name: Metric name following Prometheus conventions.
|
|
683
|
+
value: Current gauge value. Can be negative for appropriate metrics.
|
|
684
|
+
labels: Optional labels for the metric.
|
|
685
|
+
|
|
686
|
+
Side Effects:
|
|
687
|
+
- Sets gauge metric via metrics sink
|
|
688
|
+
|
|
689
|
+
Example:
|
|
690
|
+
```python
|
|
691
|
+
hook.set_gauge(
|
|
692
|
+
"active_handlers",
|
|
693
|
+
value=len(active_handlers),
|
|
694
|
+
labels={"handler_type": "http"},
|
|
695
|
+
)
|
|
696
|
+
```
|
|
697
|
+
"""
|
|
698
|
+
if self.metrics_sink is None:
|
|
699
|
+
return
|
|
700
|
+
|
|
701
|
+
# Lock ensures atomic gauge update across concurrent calls
|
|
702
|
+
with self._metrics_lock:
|
|
703
|
+
self.metrics_sink.set_gauge(
|
|
704
|
+
name=name,
|
|
705
|
+
labels=labels or {},
|
|
706
|
+
value=value,
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
def set_buffer_gauge(
|
|
710
|
+
self,
|
|
711
|
+
name: str,
|
|
712
|
+
value: float,
|
|
713
|
+
labels: dict[str, str] | None = None,
|
|
714
|
+
) -> None:
|
|
715
|
+
"""Set a gauge metric value for buffer/queue metrics (non-negative).
|
|
716
|
+
|
|
717
|
+
Similar to set_gauge(), but enforces non-negative values. Use this for
|
|
718
|
+
metrics that represent counts, sizes, or capacities that logically
|
|
719
|
+
cannot be negative (e.g., queue depth, buffer size, connection pool size).
|
|
720
|
+
|
|
721
|
+
The value is clamped to 0.0 if negative, preventing invalid metric values
|
|
722
|
+
that could occur from race conditions or calculation errors. A warning
|
|
723
|
+
is logged when clamping occurs to aid debugging.
|
|
724
|
+
|
|
725
|
+
Args:
|
|
726
|
+
name: Metric name following Prometheus conventions.
|
|
727
|
+
value: Current buffer/queue value. Clamped to 0.0 if negative.
|
|
728
|
+
labels: Optional labels for the metric.
|
|
729
|
+
|
|
730
|
+
Side Effects:
|
|
731
|
+
- Sets gauge metric via metrics sink with max(0.0, value)
|
|
732
|
+
- Logs warning if negative value is clamped
|
|
733
|
+
|
|
734
|
+
Example:
|
|
735
|
+
```python
|
|
736
|
+
# Queue depth can't go negative even with concurrent updates
|
|
737
|
+
hook.set_buffer_gauge(
|
|
738
|
+
"message_queue_depth",
|
|
739
|
+
value=queue.qsize(),
|
|
740
|
+
labels={"queue_name": "events"},
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
# Safe even if calculation error produces negative
|
|
744
|
+
hook.set_buffer_gauge(
|
|
745
|
+
"buffer_available_slots",
|
|
746
|
+
value=total_slots - used_slots, # Clamped to 0 if oversubscribed
|
|
747
|
+
labels={"buffer": "write"},
|
|
748
|
+
)
|
|
749
|
+
```
|
|
750
|
+
"""
|
|
751
|
+
if self.metrics_sink is None:
|
|
752
|
+
return
|
|
753
|
+
|
|
754
|
+
# Enforce non-negative values for buffer/count metrics
|
|
755
|
+
if value < 0.0:
|
|
756
|
+
_logger.warning(
|
|
757
|
+
"Buffer gauge received negative value; clamping to 0.0",
|
|
758
|
+
extra={
|
|
759
|
+
"metric_name": name,
|
|
760
|
+
"original_value": value,
|
|
761
|
+
"labels": labels,
|
|
762
|
+
},
|
|
763
|
+
)
|
|
764
|
+
safe_value = 0.0
|
|
765
|
+
else:
|
|
766
|
+
safe_value = value
|
|
767
|
+
|
|
768
|
+
# Lock ensures atomic gauge update across concurrent calls
|
|
769
|
+
with self._metrics_lock:
|
|
770
|
+
self.metrics_sink.set_gauge(
|
|
771
|
+
name=name,
|
|
772
|
+
labels=labels or {},
|
|
773
|
+
value=safe_value,
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
# =========================================================================
|
|
777
|
+
# CONTEXT MANAGER SUPPORT
|
|
778
|
+
# =========================================================================
|
|
779
|
+
|
|
780
|
+
def operation_context(
|
|
781
|
+
self,
|
|
782
|
+
operation: str,
|
|
783
|
+
correlation_id: str | UUID | None = None,
|
|
784
|
+
labels: dict[str, str] | None = None,
|
|
785
|
+
) -> OperationScope:
|
|
786
|
+
"""Create a context manager for operation timing.
|
|
787
|
+
|
|
788
|
+
Returns a context manager that automatically calls before_operation()
|
|
789
|
+
on entry and after_operation() on exit. This is the recommended way
|
|
790
|
+
to instrument operations as it ensures proper cleanup even on exceptions.
|
|
791
|
+
|
|
792
|
+
Args:
|
|
793
|
+
operation: Name of the operation to track.
|
|
794
|
+
correlation_id: Optional correlation ID for tracing.
|
|
795
|
+
labels: Optional additional labels for metrics.
|
|
796
|
+
|
|
797
|
+
Returns:
|
|
798
|
+
A context manager that yields the duration in milliseconds on exit.
|
|
799
|
+
|
|
800
|
+
Example:
|
|
801
|
+
```python
|
|
802
|
+
# Basic usage
|
|
803
|
+
with hook.operation_context("handler.process") as ctx:
|
|
804
|
+
result = await handler.execute()
|
|
805
|
+
print(f"Operation took {ctx.duration_ms:.2f}ms")
|
|
806
|
+
|
|
807
|
+
# With correlation ID
|
|
808
|
+
with hook.operation_context(
|
|
809
|
+
"db.query",
|
|
810
|
+
correlation_id=request_id,
|
|
811
|
+
labels={"table": "users"},
|
|
812
|
+
):
|
|
813
|
+
rows = await db.fetch_all(query)
|
|
814
|
+
```
|
|
815
|
+
"""
|
|
816
|
+
return OperationScope(
|
|
817
|
+
hook=self,
|
|
818
|
+
operation=operation,
|
|
819
|
+
correlation_id=correlation_id,
|
|
820
|
+
labels=labels,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
# =========================================================================
|
|
824
|
+
# INTERNAL HELPERS
|
|
825
|
+
# =========================================================================
|
|
826
|
+
|
|
827
|
+
# High-cardinality label keys that must be excluded from metrics.
|
|
828
|
+
# These would cause metrics cardinality explosion and be dropped by
|
|
829
|
+
# ModelMetricsPolicy when on_violation is WARN_AND_DROP or DROP_SILENT.
|
|
830
|
+
_HIGH_CARDINALITY_KEYS: frozenset[str] = frozenset(
|
|
831
|
+
{
|
|
832
|
+
"correlation_id",
|
|
833
|
+
"request_id",
|
|
834
|
+
"trace_id",
|
|
835
|
+
"span_id",
|
|
836
|
+
"session_id",
|
|
837
|
+
"user_id",
|
|
838
|
+
}
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
def _build_metric_labels(
|
|
842
|
+
self,
|
|
843
|
+
operation: str,
|
|
844
|
+
extra_labels: dict[str, str] | None = None,
|
|
845
|
+
) -> dict[str, str]:
|
|
846
|
+
"""Build the complete label set for a metric.
|
|
847
|
+
|
|
848
|
+
Combines operation name, stored operation labels, and any extra labels
|
|
849
|
+
into a single label dictionary. High-cardinality keys are automatically
|
|
850
|
+
filtered out to prevent metrics from being dropped by the policy.
|
|
851
|
+
|
|
852
|
+
CRITICAL - Metric Recording Guarantee:
|
|
853
|
+
This method NEVER drops or suppresses metric recording. When called,
|
|
854
|
+
the metric WILL be recorded with the returned labels. The only
|
|
855
|
+
filtering that occurs is removal of high-cardinality label KEYS
|
|
856
|
+
from the label dictionary - the metric itself is ALWAYS recorded.
|
|
857
|
+
|
|
858
|
+
Example with correlation_id:
|
|
859
|
+
- Input: operation="db.query", labels={"correlation_id": "abc-123"}
|
|
860
|
+
- Output: {"operation": "db.query"} # correlation_id filtered
|
|
861
|
+
- Metric: RECORDED with {"operation": "db.query"}
|
|
862
|
+
|
|
863
|
+
The correlation_id and other high-cardinality values remain available
|
|
864
|
+
via the _correlation_id contextvar for structured logging and
|
|
865
|
+
distributed tracing - they are just excluded from Prometheus labels
|
|
866
|
+
to prevent cardinality explosion.
|
|
867
|
+
|
|
868
|
+
Why Filter High-Cardinality Labels:
|
|
869
|
+
High-cardinality values (correlation_id, request_id, trace_id, etc.)
|
|
870
|
+
are unique per request. Including them in Prometheus labels would:
|
|
871
|
+
1. Create millions of unique time series (cardinality explosion)
|
|
872
|
+
2. Cause metrics to be dropped by ModelMetricsPolicy (WARN_AND_DROP)
|
|
873
|
+
3. Overwhelm Prometheus storage and query performance
|
|
874
|
+
|
|
875
|
+
By filtering these keys early, we ensure metrics are ALWAYS recorded
|
|
876
|
+
with stable, low-cardinality labels.
|
|
877
|
+
|
|
878
|
+
Args:
|
|
879
|
+
operation: Operation name to include. Always present in output.
|
|
880
|
+
extra_labels: Optional additional labels to merge.
|
|
881
|
+
|
|
882
|
+
Returns:
|
|
883
|
+
Complete label dictionary for metric emission, with high-cardinality
|
|
884
|
+
keys filtered out. GUARANTEED to contain at least {"operation": operation}.
|
|
885
|
+
Never returns empty dict. Never returns None.
|
|
886
|
+
"""
|
|
887
|
+
# INVARIANT: labels always contains at least {"operation": operation}
|
|
888
|
+
# This ensures metrics are NEVER dropped due to empty labels
|
|
889
|
+
labels: dict[str, str] = {"operation": operation}
|
|
890
|
+
filtered_keys: list[str] = []
|
|
891
|
+
|
|
892
|
+
# Merge stored operation labels, filtering out high-cardinality keys
|
|
893
|
+
# Note: stored_labels dict is a copy made in before_operation(), safe to iterate
|
|
894
|
+
stored_labels = _operation_labels.get()
|
|
895
|
+
if stored_labels is not None:
|
|
896
|
+
for key, value in stored_labels.items():
|
|
897
|
+
if key in self._HIGH_CARDINALITY_KEYS:
|
|
898
|
+
filtered_keys.append(key)
|
|
899
|
+
else:
|
|
900
|
+
labels[key] = value
|
|
901
|
+
|
|
902
|
+
# Merge extra labels (overrides stored if same key), filtering high-cardinality
|
|
903
|
+
if extra_labels:
|
|
904
|
+
for key, value in extra_labels.items():
|
|
905
|
+
if key in self._HIGH_CARDINALITY_KEYS:
|
|
906
|
+
# Only add to filtered_keys if not already tracked
|
|
907
|
+
if key not in filtered_keys:
|
|
908
|
+
filtered_keys.append(key)
|
|
909
|
+
else:
|
|
910
|
+
labels[key] = value
|
|
911
|
+
|
|
912
|
+
# Log when high-cardinality keys are filtered (debug level)
|
|
913
|
+
# IMPORTANT: This is informational logging only - the metric IS being recorded
|
|
914
|
+
# We only remove specific label KEYS, NOT the entire metric
|
|
915
|
+
# The log includes correlation_id for tracing even though it's filtered from labels
|
|
916
|
+
if filtered_keys:
|
|
917
|
+
# Include correlation_id in log for tracing purposes
|
|
918
|
+
correlation_id = _correlation_id.get()
|
|
919
|
+
_logger.debug(
|
|
920
|
+
"Removed high-cardinality keys from metric labels - "
|
|
921
|
+
"metric WILL be recorded with %d remaining labels (keys removed: %s)",
|
|
922
|
+
len(labels),
|
|
923
|
+
filtered_keys,
|
|
924
|
+
extra={
|
|
925
|
+
"operation": operation,
|
|
926
|
+
"filtered_keys": filtered_keys,
|
|
927
|
+
"remaining_labels": list(labels.keys()),
|
|
928
|
+
"correlation_id": correlation_id, # Available for log correlation
|
|
929
|
+
},
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
# CRITICAL: Defense-in-depth guarantee that metrics are NEVER dropped.
|
|
933
|
+
# The invariant at line 889 ensures "operation" is always present, but this
|
|
934
|
+
# explicit check provides runtime safety if the invariant is ever violated.
|
|
935
|
+
# This protects against data loss from unexpected edge cases.
|
|
936
|
+
if "operation" not in labels:
|
|
937
|
+
_logger.error(
|
|
938
|
+
"BUG: operation key missing from labels after filtering; "
|
|
939
|
+
"restoring to prevent metric data loss",
|
|
940
|
+
extra={"operation": operation, "labels_keys": list(labels.keys())},
|
|
941
|
+
)
|
|
942
|
+
labels["operation"] = operation
|
|
943
|
+
|
|
944
|
+
# GUARANTEE: This method ALWAYS returns a non-empty dict containing at least
|
|
945
|
+
# {"operation": operation}. Metrics are NEVER dropped - only high-cardinality
|
|
946
|
+
# label keys (correlation_id, request_id, etc.) are removed from the label set.
|
|
947
|
+
# The metric itself is ALWAYS recorded with the remaining labels.
|
|
948
|
+
return labels
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
class OperationScope:
|
|
952
|
+
"""Context manager for scoped operation timing.
|
|
953
|
+
|
|
954
|
+
This internal class provides context manager support for HookObservability.
|
|
955
|
+
It automatically calls before_operation() on entry and after_operation()
|
|
956
|
+
on exit, ensuring proper cleanup even when exceptions occur.
|
|
957
|
+
|
|
958
|
+
Attributes:
|
|
959
|
+
duration_ms: Duration of the operation in milliseconds, available
|
|
960
|
+
after the context exits. Will be 0.0 if accessed before exit.
|
|
961
|
+
|
|
962
|
+
Note:
|
|
963
|
+
This class stores tokens for contextvar restoration, enabling proper
|
|
964
|
+
nesting of operation contexts. Each context saves and restores the
|
|
965
|
+
previous contextvar values.
|
|
966
|
+
"""
|
|
967
|
+
|
|
968
|
+
def __init__(
|
|
969
|
+
self,
|
|
970
|
+
hook: HookObservability,
|
|
971
|
+
operation: str,
|
|
972
|
+
correlation_id: str | UUID | None = None,
|
|
973
|
+
labels: dict[str, str] | None = None,
|
|
974
|
+
) -> None:
|
|
975
|
+
"""Initialize the context manager.
|
|
976
|
+
|
|
977
|
+
Args:
|
|
978
|
+
hook: The HookObservability instance to use.
|
|
979
|
+
operation: Operation name to track.
|
|
980
|
+
correlation_id: Optional correlation ID.
|
|
981
|
+
labels: Optional additional labels.
|
|
982
|
+
"""
|
|
983
|
+
self._hook = hook
|
|
984
|
+
self._operation = operation
|
|
985
|
+
self._correlation_id = correlation_id
|
|
986
|
+
self._labels = labels
|
|
987
|
+
self.duration_ms: float = 0.0
|
|
988
|
+
|
|
989
|
+
# Tokens for restoring previous contextvar values (for nesting support)
|
|
990
|
+
self._start_time_token: Token[float | None] | None = None
|
|
991
|
+
self._operation_name_token: Token[str | None] | None = None
|
|
992
|
+
self._correlation_id_token: Token[str | None] | None = None
|
|
993
|
+
self._labels_token: Token[dict[str, str] | None] | None = None
|
|
994
|
+
|
|
995
|
+
def __enter__(self) -> OperationScope:
|
|
996
|
+
"""Enter the operation context.
|
|
997
|
+
|
|
998
|
+
Saves current contextvar values and calls before_operation().
|
|
999
|
+
|
|
1000
|
+
Returns:
|
|
1001
|
+
Self, for accessing duration_ms after exit.
|
|
1002
|
+
"""
|
|
1003
|
+
# Save current values for restoration on exit (nesting support)
|
|
1004
|
+
self._start_time_token = _start_time.set(_start_time.get())
|
|
1005
|
+
self._operation_name_token = _operation_name.set(_operation_name.get())
|
|
1006
|
+
self._correlation_id_token = _correlation_id.set(_correlation_id.get())
|
|
1007
|
+
current_labels = _operation_labels.get()
|
|
1008
|
+
# NOTE: Shallow copy is sufficient here because:
|
|
1009
|
+
# 1. Labels dict is typed as dict[str, str] (string keys and values)
|
|
1010
|
+
# 2. Strings are immutable in Python, so no aliasing issues can occur
|
|
1011
|
+
# 3. We only need isolation of the dict structure, not deep cloning of values
|
|
1012
|
+
self._labels_token = _operation_labels.set(
|
|
1013
|
+
current_labels.copy() if current_labels is not None else None
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
# Now start the new operation
|
|
1017
|
+
self._hook.before_operation(
|
|
1018
|
+
operation=self._operation,
|
|
1019
|
+
correlation_id=self._correlation_id,
|
|
1020
|
+
labels=self._labels,
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
return self
|
|
1024
|
+
|
|
1025
|
+
def __exit__(
|
|
1026
|
+
self,
|
|
1027
|
+
exc_type: type[BaseException] | None,
|
|
1028
|
+
exc_val: BaseException | None,
|
|
1029
|
+
exc_tb: TracebackType | None,
|
|
1030
|
+
) -> None:
|
|
1031
|
+
"""Exit the operation context.
|
|
1032
|
+
|
|
1033
|
+
Calls after_operation() and restores previous contextvar values.
|
|
1034
|
+
Records success or failure based on whether an exception occurred.
|
|
1035
|
+
|
|
1036
|
+
Concurrency Safety:
|
|
1037
|
+
Uses try/finally to ensure contextvar tokens are ALWAYS restored,
|
|
1038
|
+
even if record_failure/record_success/after_operation raise exceptions.
|
|
1039
|
+
This prevents contextvar state leakage in error scenarios.
|
|
1040
|
+
|
|
1041
|
+
Args:
|
|
1042
|
+
exc_type: Exception type if an exception was raised.
|
|
1043
|
+
exc_val: Exception value if an exception was raised.
|
|
1044
|
+
exc_tb: Traceback if an exception was raised.
|
|
1045
|
+
"""
|
|
1046
|
+
try:
|
|
1047
|
+
# Record success or failure before timing
|
|
1048
|
+
if exc_type is not None:
|
|
1049
|
+
self._hook.record_failure(exc_type.__name__)
|
|
1050
|
+
else:
|
|
1051
|
+
self._hook.record_success()
|
|
1052
|
+
|
|
1053
|
+
# Get duration
|
|
1054
|
+
self.duration_ms = self._hook.after_operation()
|
|
1055
|
+
finally:
|
|
1056
|
+
# CRITICAL: Always restore previous contextvar values (for nesting support)
|
|
1057
|
+
# This must happen even if the above code raises an exception to prevent
|
|
1058
|
+
# contextvar state leakage.
|
|
1059
|
+
if self._start_time_token is not None:
|
|
1060
|
+
_start_time.reset(self._start_time_token)
|
|
1061
|
+
if self._operation_name_token is not None:
|
|
1062
|
+
_operation_name.reset(self._operation_name_token)
|
|
1063
|
+
if self._correlation_id_token is not None:
|
|
1064
|
+
_correlation_id.reset(self._correlation_id_token)
|
|
1065
|
+
if self._labels_token is not None:
|
|
1066
|
+
_operation_labels.reset(self._labels_token)
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
# =============================================================================
|
|
1070
|
+
# MODULE-LEVEL SINGLETON FUNCTIONS
|
|
1071
|
+
# =============================================================================
|
|
1072
|
+
#
|
|
1073
|
+
# These functions provide optional singleton access to a global HookObservability
|
|
1074
|
+
# instance. The singleton is thread-safe and lazily initialized.
|
|
1075
|
+
#
|
|
1076
|
+
# =============================================================================
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
def get_global_hook(
|
|
1080
|
+
metrics_sink: ProtocolHotPathMetricsSink | None = None,
|
|
1081
|
+
) -> HookObservability:
|
|
1082
|
+
"""Get or create the global singleton HookObservability instance.
|
|
1083
|
+
|
|
1084
|
+
Returns the cached singleton hook if one exists, otherwise creates a new
|
|
1085
|
+
one with the provided configuration and caches it. This is the recommended
|
|
1086
|
+
way to access a shared observability hook across the application.
|
|
1087
|
+
|
|
1088
|
+
Thread Safety:
|
|
1089
|
+
This function is thread-safe. Multiple concurrent calls will receive
|
|
1090
|
+
the same singleton instance. A threading.Lock ensures only one thread
|
|
1091
|
+
creates the singleton.
|
|
1092
|
+
|
|
1093
|
+
Note:
|
|
1094
|
+
The metrics_sink parameter is only used on first call when the singleton
|
|
1095
|
+
is created. Subsequent calls ignore this parameter and return the
|
|
1096
|
+
existing instance. A warning is logged when configuration is provided
|
|
1097
|
+
but ignored due to an existing singleton.
|
|
1098
|
+
|
|
1099
|
+
To reconfigure the singleton, call clear_global_hook() first.
|
|
1100
|
+
|
|
1101
|
+
Args:
|
|
1102
|
+
metrics_sink: Optional metrics sink for the hook. Only used if no
|
|
1103
|
+
singleton exists yet. Subsequent calls ignore this parameter.
|
|
1104
|
+
|
|
1105
|
+
Returns:
|
|
1106
|
+
The global singleton HookObservability instance.
|
|
1107
|
+
|
|
1108
|
+
Example:
|
|
1109
|
+
```python
|
|
1110
|
+
# First call creates the singleton with the provided sink
|
|
1111
|
+
hook1 = get_global_hook(metrics_sink=my_sink)
|
|
1112
|
+
|
|
1113
|
+
# Subsequent calls return the same instance
|
|
1114
|
+
hook2 = get_global_hook() # metrics_sink parameter ignored
|
|
1115
|
+
assert hook1 is hook2
|
|
1116
|
+
|
|
1117
|
+
# Reconfigure if needed
|
|
1118
|
+
clear_global_hook()
|
|
1119
|
+
hook3 = get_global_hook(metrics_sink=new_sink) # New singleton
|
|
1120
|
+
```
|
|
1121
|
+
"""
|
|
1122
|
+
global _global_hook_instance # noqa: PLW0603 - Standard singleton pattern
|
|
1123
|
+
|
|
1124
|
+
with _global_hook_lock:
|
|
1125
|
+
if _global_hook_instance is None:
|
|
1126
|
+
_global_hook_instance = HookObservability(metrics_sink=metrics_sink)
|
|
1127
|
+
_logger.debug(
|
|
1128
|
+
"Created global singleton HookObservability",
|
|
1129
|
+
extra={"has_metrics_sink": metrics_sink is not None},
|
|
1130
|
+
)
|
|
1131
|
+
elif metrics_sink is not None:
|
|
1132
|
+
# Log warning when config is provided but ignored for existing singleton
|
|
1133
|
+
_logger.warning(
|
|
1134
|
+
"Global HookObservability singleton already exists; "
|
|
1135
|
+
"provided metrics_sink configuration ignored. "
|
|
1136
|
+
"Call clear_global_hook() first to reconfigure.",
|
|
1137
|
+
extra={
|
|
1138
|
+
"existing_has_metrics_sink": (
|
|
1139
|
+
_global_hook_instance.metrics_sink is not None
|
|
1140
|
+
),
|
|
1141
|
+
"provided_metrics_sink_type": type(metrics_sink).__name__,
|
|
1142
|
+
},
|
|
1143
|
+
)
|
|
1144
|
+
return _global_hook_instance
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
def configure_global_hook(
|
|
1148
|
+
metrics_sink: ProtocolHotPathMetricsSink | None = None,
|
|
1149
|
+
) -> HookObservability:
|
|
1150
|
+
"""Configure the global singleton HookObservability instance.
|
|
1151
|
+
|
|
1152
|
+
This is an alias for get_global_hook() that makes the intent clearer when
|
|
1153
|
+
you want to explicitly configure the singleton on first use.
|
|
1154
|
+
|
|
1155
|
+
Args:
|
|
1156
|
+
metrics_sink: Optional metrics sink for the hook.
|
|
1157
|
+
|
|
1158
|
+
Returns:
|
|
1159
|
+
The global singleton HookObservability instance.
|
|
1160
|
+
|
|
1161
|
+
See Also:
|
|
1162
|
+
get_global_hook(): Primary singleton access function.
|
|
1163
|
+
clear_global_hook(): Reset the singleton for reconfiguration.
|
|
1164
|
+
"""
|
|
1165
|
+
return get_global_hook(metrics_sink=metrics_sink)
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
def clear_global_hook() -> None:
|
|
1169
|
+
"""Clear the global singleton HookObservability instance.
|
|
1170
|
+
|
|
1171
|
+
Removes the reference to the cached singleton hook, allowing it to be
|
|
1172
|
+
garbage collected if no other references exist. Subsequent calls to
|
|
1173
|
+
get_global_hook() will create a new instance.
|
|
1174
|
+
|
|
1175
|
+
Use Cases:
|
|
1176
|
+
- Testing: Reset singleton state between tests
|
|
1177
|
+
- Reconfiguration: Allow new singleton with different metrics_sink
|
|
1178
|
+
- Shutdown: Release resources before application exit
|
|
1179
|
+
|
|
1180
|
+
Thread Safety:
|
|
1181
|
+
This function is thread-safe.
|
|
1182
|
+
|
|
1183
|
+
Warning:
|
|
1184
|
+
Existing references to the old singleton remain valid. Only future
|
|
1185
|
+
get_global_hook() calls will create a new instance.
|
|
1186
|
+
|
|
1187
|
+
Example:
|
|
1188
|
+
```python
|
|
1189
|
+
hook1 = get_global_hook(metrics_sink=sink1)
|
|
1190
|
+
clear_global_hook()
|
|
1191
|
+
hook2 = get_global_hook(metrics_sink=sink2)
|
|
1192
|
+
assert hook1 is not hook2 # Different instances
|
|
1193
|
+
# hook1 still works but is no longer the singleton
|
|
1194
|
+
```
|
|
1195
|
+
"""
|
|
1196
|
+
global _global_hook_instance # noqa: PLW0603 - Standard singleton pattern
|
|
1197
|
+
|
|
1198
|
+
with _global_hook_lock:
|
|
1199
|
+
if _global_hook_instance is not None:
|
|
1200
|
+
_logger.debug("Cleared global HookObservability singleton")
|
|
1201
|
+
_global_hook_instance = None
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
def has_global_hook() -> bool:
|
|
1205
|
+
"""Check if a global singleton HookObservability exists.
|
|
1206
|
+
|
|
1207
|
+
Returns:
|
|
1208
|
+
True if a singleton hook has been created, False otherwise.
|
|
1209
|
+
|
|
1210
|
+
Thread Safety:
|
|
1211
|
+
This function is thread-safe.
|
|
1212
|
+
"""
|
|
1213
|
+
with _global_hook_lock:
|
|
1214
|
+
return _global_hook_instance is not None
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
__all__ = [
|
|
1218
|
+
"HookObservability",
|
|
1219
|
+
"get_global_hook",
|
|
1220
|
+
"configure_global_hook",
|
|
1221
|
+
"clear_global_hook",
|
|
1222
|
+
"has_global_hook",
|
|
1223
|
+
]
|