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,1120 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Prometheus metrics handler - EFFECT handler exposing /metrics HTTP endpoint.
|
|
4
|
+
|
|
5
|
+
This module provides an EFFECT handler that exposes Prometheus metrics via an
|
|
6
|
+
HTTP endpoint for scraping. It follows the ONEX handler pattern with contract-
|
|
7
|
+
driven lifecycle management.
|
|
8
|
+
|
|
9
|
+
Architecture Principle: "Handlers own lifecycle, sinks own hot path"
|
|
10
|
+
- Handler: Contract-driven lifecycle, expose /metrics HTTP endpoint
|
|
11
|
+
- Sink: Fast in-process emission (SinkMetricsPrometheus)
|
|
12
|
+
|
|
13
|
+
Supported Operations:
|
|
14
|
+
- metrics.scrape: Return current metrics in Prometheus text format
|
|
15
|
+
- metrics.push: Push metrics to Prometheus Pushgateway (if configured)
|
|
16
|
+
|
|
17
|
+
HTTP Server:
|
|
18
|
+
The handler starts an aiohttp-based HTTP server during initialize() that
|
|
19
|
+
serves metrics at the configured path (default: /metrics on port 9090).
|
|
20
|
+
The server is non-blocking and runs in the background.
|
|
21
|
+
|
|
22
|
+
**SECURE BY DEFAULT**: The server binds to localhost (127.0.0.1) by default.
|
|
23
|
+
External access requires explicit configuration AND reverse proxy protection.
|
|
24
|
+
|
|
25
|
+
Thread-Safety:
|
|
26
|
+
The handler is thread-safe. The underlying SinkMetricsPrometheus uses
|
|
27
|
+
thread-safe metric caches with locking. The aiohttp server handles
|
|
28
|
+
concurrent requests safely.
|
|
29
|
+
|
|
30
|
+
Security Model:
|
|
31
|
+
**Secure Defaults:**
|
|
32
|
+
- Binds to localhost (127.0.0.1) by default - NOT accessible externally
|
|
33
|
+
- Request body size limited to 1MB (configurable via max_request_size_bytes)
|
|
34
|
+
- Request timeout of 30 seconds (configurable via request_timeout_seconds)
|
|
35
|
+
- Error responses use generic messages - no stack traces or internal details
|
|
36
|
+
|
|
37
|
+
**Production Deployment Requirements:**
|
|
38
|
+
|
|
39
|
+
To expose metrics externally, you MUST:
|
|
40
|
+
|
|
41
|
+
1. **Explicit Configuration**: Set host="0.0.0.0" explicitly (a warning will
|
|
42
|
+
be logged when binding to non-localhost addresses).
|
|
43
|
+
|
|
44
|
+
2. **Reverse Proxy**: Deploy behind nginx/traefik/envoy with:
|
|
45
|
+
- TLS termination (HTTPS only)
|
|
46
|
+
- IP allowlisting for Prometheus scrapers
|
|
47
|
+
- Rate limiting to prevent DoS (NOT implemented in this handler)
|
|
48
|
+
- Authentication (mTLS or bearer tokens recommended)
|
|
49
|
+
|
|
50
|
+
3. **Network Isolation**: Consider using internal network CIDRs for
|
|
51
|
+
container/pod networks instead of 0.0.0.0.
|
|
52
|
+
|
|
53
|
+
**Built-in Protections:**
|
|
54
|
+
- Input Validation: X-Correlation-ID headers validated as UUID format
|
|
55
|
+
- Error Sanitization: 500/503 errors return generic messages only
|
|
56
|
+
- Request Size Limits: Configurable max_request_size_bytes (default 1MB)
|
|
57
|
+
- Request Timeouts: Configurable request_timeout_seconds (default 30s)
|
|
58
|
+
|
|
59
|
+
**NOT Implemented (Requires Reverse Proxy):**
|
|
60
|
+
- Rate limiting: Use nginx/traefik/envoy limit_req/rate_limit
|
|
61
|
+
- TLS/HTTPS: Use reverse proxy for TLS termination
|
|
62
|
+
- Authentication: Use reverse proxy for mTLS or token auth
|
|
63
|
+
|
|
64
|
+
**Metric Labels**: Ensure metric labels do not contain:
|
|
65
|
+
- PII (personally identifiable information)
|
|
66
|
+
- Secrets or credentials
|
|
67
|
+
- Internal hostnames/IPs (if sensitive)
|
|
68
|
+
|
|
69
|
+
**Threat Model:**
|
|
70
|
+
- DoS via metric scraping: Mitigate with rate limiting at proxy
|
|
71
|
+
- Information disclosure: Metrics may reveal system topology
|
|
72
|
+
- Header injection: X-Correlation-ID is validated and sanitized
|
|
73
|
+
- Request body attacks: Limited by max_request_size_bytes
|
|
74
|
+
|
|
75
|
+
Usage:
|
|
76
|
+
>>> from omnibase_infra.observability.handlers import HandlerMetricsPrometheus
|
|
77
|
+
>>>
|
|
78
|
+
>>> # Secure default: localhost only
|
|
79
|
+
>>> handler = HandlerMetricsPrometheus()
|
|
80
|
+
>>> await handler.initialize({"port": 9090, "path": "/metrics"})
|
|
81
|
+
>>> # Metrics available at http://127.0.0.1:9090/metrics (localhost only)
|
|
82
|
+
>>>
|
|
83
|
+
>>> # External access (REQUIRES reverse proxy protection)
|
|
84
|
+
>>> handler = HandlerMetricsPrometheus()
|
|
85
|
+
>>> await handler.initialize({
|
|
86
|
+
... "host": "0.0.0.0", # WARNING: Use only with reverse proxy
|
|
87
|
+
... "port": 9090,
|
|
88
|
+
... "path": "/metrics",
|
|
89
|
+
... })
|
|
90
|
+
>>>
|
|
91
|
+
>>> await handler.shutdown()
|
|
92
|
+
|
|
93
|
+
See Also:
|
|
94
|
+
- SinkMetricsPrometheus: Hot-path metrics sink
|
|
95
|
+
- ModelMetricsHandlerConfig: Configuration model
|
|
96
|
+
- docs/patterns/observability_patterns.md: Full observability documentation
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
from __future__ import annotations
|
|
100
|
+
|
|
101
|
+
import asyncio
|
|
102
|
+
import logging
|
|
103
|
+
import re
|
|
104
|
+
import threading
|
|
105
|
+
import time
|
|
106
|
+
from datetime import UTC, datetime
|
|
107
|
+
from uuid import UUID, uuid4
|
|
108
|
+
|
|
109
|
+
# Optional dependencies with graceful degradation
|
|
110
|
+
# These are checked during initialization with clear error messages
|
|
111
|
+
_PROMETHEUS_AVAILABLE: bool = False
|
|
112
|
+
_AIOHTTP_AVAILABLE: bool = False
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
from aiohttp import web
|
|
116
|
+
|
|
117
|
+
_AIOHTTP_AVAILABLE = True
|
|
118
|
+
except ImportError:
|
|
119
|
+
web = None # type: ignore[assignment, misc]
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
from prometheus_client import CONTENT_TYPE_LATEST, Histogram, generate_latest
|
|
123
|
+
|
|
124
|
+
_PROMETHEUS_AVAILABLE = True
|
|
125
|
+
except ImportError:
|
|
126
|
+
# Provide stubs for type checking when prometheus_client is not installed
|
|
127
|
+
CONTENT_TYPE_LATEST = "text/plain; version=0.0.4; charset=utf-8"
|
|
128
|
+
Histogram = None # type: ignore[assignment, misc]
|
|
129
|
+
|
|
130
|
+
def generate_latest() -> bytes: # type: ignore[misc]
|
|
131
|
+
"""Stub for when prometheus_client is not installed."""
|
|
132
|
+
raise ImportError("prometheus_client is required but not installed")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
from omnibase_core.models.dispatch import ModelHandlerOutput
|
|
136
|
+
from omnibase_infra.enums import (
|
|
137
|
+
EnumHandlerType,
|
|
138
|
+
EnumHandlerTypeCategory,
|
|
139
|
+
EnumInfraTransportType,
|
|
140
|
+
EnumResponseStatus,
|
|
141
|
+
)
|
|
142
|
+
from omnibase_infra.errors import (
|
|
143
|
+
InfraTimeoutError,
|
|
144
|
+
ModelInfraErrorContext,
|
|
145
|
+
ModelTimeoutErrorContext,
|
|
146
|
+
ProtocolConfigurationError,
|
|
147
|
+
RuntimeHostError,
|
|
148
|
+
)
|
|
149
|
+
from omnibase_infra.mixins import MixinEnvelopeExtraction
|
|
150
|
+
from omnibase_infra.observability.handlers.model_metrics_handler_config import (
|
|
151
|
+
ModelMetricsHandlerConfig,
|
|
152
|
+
)
|
|
153
|
+
from omnibase_infra.observability.handlers.model_metrics_handler_response import (
|
|
154
|
+
ModelMetricsHandlerPayload,
|
|
155
|
+
ModelMetricsHandlerResponse,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
logger = logging.getLogger(__name__)
|
|
159
|
+
|
|
160
|
+
# Handler ID for ModelHandlerOutput
|
|
161
|
+
HANDLER_ID_METRICS: str = "metrics-prometheus-handler"
|
|
162
|
+
|
|
163
|
+
# Security: UUID validation regex for X-Correlation-ID header
|
|
164
|
+
# Matches standard UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
165
|
+
_UUID_REGEX: re.Pattern[str] = re.compile(
|
|
166
|
+
r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Maximum length for correlation ID header to prevent memory issues
|
|
170
|
+
_MAX_CORRELATION_ID_LENGTH: int = 64
|
|
171
|
+
|
|
172
|
+
# Default timeout for metrics generation to prevent blocking (seconds)
|
|
173
|
+
# This is used when config is not yet loaded or as internal timeout
|
|
174
|
+
_DEFAULT_METRICS_GENERATION_TIMEOUT: float = 5.0
|
|
175
|
+
|
|
176
|
+
# Timeout for Pushgateway operations (seconds)
|
|
177
|
+
_PUSH_GATEWAY_TIMEOUT: float = 10.0
|
|
178
|
+
|
|
179
|
+
# Default maximum request body size (1MB) - used when config not loaded
|
|
180
|
+
_DEFAULT_MAX_REQUEST_SIZE_BYTES: int = 1048576
|
|
181
|
+
|
|
182
|
+
# Import scrape duration buckets from constants module (ONEX pattern: constants in constants_*.py)
|
|
183
|
+
# See constants_metrics.py for bucket configuration documentation.
|
|
184
|
+
from omnibase_infra.observability.constants_metrics import SCRAPE_DURATION_BUCKETS
|
|
185
|
+
|
|
186
|
+
_SCRAPE_DURATION_BUCKETS = SCRAPE_DURATION_BUCKETS
|
|
187
|
+
|
|
188
|
+
# Lazily initialized scrape duration histogram
|
|
189
|
+
# Initialized on first use to avoid import-time side effects when prometheus_client
|
|
190
|
+
# is not installed. The metric is observed AFTER generate_latest() returns, so the
|
|
191
|
+
# duration value is available in the NEXT scrape (avoiding chicken-and-egg recursion).
|
|
192
|
+
_scrape_duration_histogram: Histogram | None = None
|
|
193
|
+
|
|
194
|
+
# Lock for thread-safe lazy initialization of the scrape duration histogram.
|
|
195
|
+
# Prevents race conditions where multiple threads could create duplicate histograms.
|
|
196
|
+
_histogram_init_lock: threading.Lock = threading.Lock()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _get_scrape_duration_histogram() -> Histogram | None:
|
|
200
|
+
"""Get or create the scrape duration histogram.
|
|
201
|
+
|
|
202
|
+
Lazily initializes the histogram on first call to avoid import-time errors
|
|
203
|
+
when prometheus_client is not installed.
|
|
204
|
+
|
|
205
|
+
Thread Safety:
|
|
206
|
+
Uses double-checked locking pattern to ensure thread-safe initialization.
|
|
207
|
+
The lock is only acquired when the histogram needs to be created, minimizing
|
|
208
|
+
contention after initial setup.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Histogram metric for scrape duration, or None if prometheus_client unavailable.
|
|
212
|
+
"""
|
|
213
|
+
global _scrape_duration_histogram # noqa: PLW0603 - Module-level metric cache
|
|
214
|
+
|
|
215
|
+
if not _PROMETHEUS_AVAILABLE or Histogram is None:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
# Double-checked locking pattern for thread-safe lazy initialization.
|
|
219
|
+
# First check without lock (fast path for already-initialized case).
|
|
220
|
+
if _scrape_duration_histogram is None:
|
|
221
|
+
with _histogram_init_lock:
|
|
222
|
+
# Second check with lock (handles race condition where multiple threads
|
|
223
|
+
# passed the first check before one acquired the lock and initialized).
|
|
224
|
+
if _scrape_duration_histogram is None:
|
|
225
|
+
_scrape_duration_histogram = Histogram(
|
|
226
|
+
"prometheus_handler_scrape_duration_seconds",
|
|
227
|
+
"Time spent generating Prometheus metrics for scrape requests",
|
|
228
|
+
buckets=_SCRAPE_DURATION_BUCKETS,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return _scrape_duration_histogram
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
SUPPORTED_OPERATIONS: frozenset[str] = frozenset(
|
|
235
|
+
{
|
|
236
|
+
"metrics.scrape",
|
|
237
|
+
"metrics.push",
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class HandlerMetricsPrometheus(MixinEnvelopeExtraction):
|
|
243
|
+
"""Prometheus metrics EFFECT handler exposing HTTP /metrics endpoint.
|
|
244
|
+
|
|
245
|
+
This handler implements the ONEX handler protocol for exposing Prometheus
|
|
246
|
+
metrics via an HTTP endpoint. It manages the lifecycle of an aiohttp HTTP
|
|
247
|
+
server that serves metrics in Prometheus text exposition format.
|
|
248
|
+
|
|
249
|
+
Handler Classification:
|
|
250
|
+
- handler_type: INFRA_HANDLER (protocol/transport handler)
|
|
251
|
+
- handler_category: EFFECT (side-effecting I/O - HTTP server)
|
|
252
|
+
|
|
253
|
+
Lifecycle:
|
|
254
|
+
- initialize(): Validates config, starts HTTP server
|
|
255
|
+
- execute(): Returns metrics text or pushes to Pushgateway
|
|
256
|
+
- shutdown(): Gracefully stops HTTP server
|
|
257
|
+
|
|
258
|
+
HTTP Server Integration:
|
|
259
|
+
The handler uses aiohttp for async HTTP serving. The server runs in the
|
|
260
|
+
background without blocking the event loop. Multiple concurrent scrapes
|
|
261
|
+
are handled safely by aiohttp's request handling.
|
|
262
|
+
|
|
263
|
+
Push Gateway Support:
|
|
264
|
+
When push_gateway_url is configured, the handler can push metrics to a
|
|
265
|
+
Prometheus Pushgateway. This is useful for short-lived batch jobs that
|
|
266
|
+
may not live long enough to be scraped.
|
|
267
|
+
|
|
268
|
+
Security:
|
|
269
|
+
**SECURE BY DEFAULT**: The server binds to localhost (127.0.0.1) by default.
|
|
270
|
+
|
|
271
|
+
Built-in protections:
|
|
272
|
+
- Localhost binding by default (external access requires explicit config)
|
|
273
|
+
- Request size limits (configurable, default 1MB)
|
|
274
|
+
- Request timeouts (configurable, default 30s)
|
|
275
|
+
- Generic error messages (no stack traces or internal details)
|
|
276
|
+
- Correlation ID validation (UUID format enforced)
|
|
277
|
+
|
|
278
|
+
For external access, you MUST:
|
|
279
|
+
1. Explicitly set host="0.0.0.0" (a warning will be logged)
|
|
280
|
+
2. Deploy behind a reverse proxy with TLS, auth, and rate limiting
|
|
281
|
+
|
|
282
|
+
See module docstring for complete security model.
|
|
283
|
+
|
|
284
|
+
Attributes:
|
|
285
|
+
_config: Handler configuration.
|
|
286
|
+
_initialized: Whether the handler has been initialized.
|
|
287
|
+
_server: aiohttp web server instance.
|
|
288
|
+
_runner: aiohttp application runner.
|
|
289
|
+
_site: aiohttp TCP site for serving requests.
|
|
290
|
+
|
|
291
|
+
Example:
|
|
292
|
+
>>> # Secure default: localhost only
|
|
293
|
+
>>> handler = HandlerMetricsPrometheus()
|
|
294
|
+
>>> await handler.initialize({"port": 9090})
|
|
295
|
+
>>> # Metrics at http://127.0.0.1:9090/metrics (localhost only)
|
|
296
|
+
>>>
|
|
297
|
+
>>> # External access (REQUIRES reverse proxy)
|
|
298
|
+
>>> handler = HandlerMetricsPrometheus()
|
|
299
|
+
>>> await handler.initialize({
|
|
300
|
+
... "host": "0.0.0.0", # WARNING: Use only with reverse proxy
|
|
301
|
+
... "port": 9090,
|
|
302
|
+
... })
|
|
303
|
+
>>> await handler.shutdown()
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
def __init__(self) -> None:
|
|
307
|
+
"""Initialize HandlerMetricsPrometheus in uninitialized state.
|
|
308
|
+
|
|
309
|
+
The handler is not ready for use until initialize() is called with
|
|
310
|
+
a valid configuration dictionary.
|
|
311
|
+
"""
|
|
312
|
+
self._config: ModelMetricsHandlerConfig | None = None
|
|
313
|
+
self._initialized: bool = False
|
|
314
|
+
self._app: web.Application | None = None
|
|
315
|
+
self._runner: web.AppRunner | None = None
|
|
316
|
+
self._site: web.TCPSite | None = None
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def handler_type(self) -> EnumHandlerType:
|
|
320
|
+
"""Return the architectural role of this handler.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
EnumHandlerType.INFRA_HANDLER - This handler is an infrastructure
|
|
324
|
+
protocol/transport handler that manages an HTTP server for metrics
|
|
325
|
+
exposition.
|
|
326
|
+
|
|
327
|
+
Note:
|
|
328
|
+
handler_type determines lifecycle, protocol selection, and runtime
|
|
329
|
+
invocation patterns. It answers "what is this handler in the architecture?"
|
|
330
|
+
|
|
331
|
+
See Also:
|
|
332
|
+
- handler_category: Behavioral classification (EFFECT/COMPUTE)
|
|
333
|
+
"""
|
|
334
|
+
return EnumHandlerType.INFRA_HANDLER
|
|
335
|
+
|
|
336
|
+
@property
|
|
337
|
+
def handler_category(self) -> EnumHandlerTypeCategory:
|
|
338
|
+
"""Return the behavioral classification of this handler.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
EnumHandlerTypeCategory.EFFECT - This handler performs side-effecting
|
|
342
|
+
I/O operations (HTTP server, network I/O). EFFECT handlers are not
|
|
343
|
+
deterministic and interact with external systems.
|
|
344
|
+
|
|
345
|
+
Note:
|
|
346
|
+
handler_category determines security rules, determinism guarantees,
|
|
347
|
+
replay safety, and permissions. It answers "how does this handler
|
|
348
|
+
behave at runtime?"
|
|
349
|
+
|
|
350
|
+
See Also:
|
|
351
|
+
- handler_type: Architectural role (INFRA_HANDLER)
|
|
352
|
+
"""
|
|
353
|
+
return EnumHandlerTypeCategory.EFFECT
|
|
354
|
+
|
|
355
|
+
async def initialize(self, config: dict[str, object]) -> None:
|
|
356
|
+
"""Initialize the metrics handler with configuration.
|
|
357
|
+
|
|
358
|
+
Validates the configuration and starts the HTTP server for metric
|
|
359
|
+
scraping if enable_server is True.
|
|
360
|
+
|
|
361
|
+
Security Note:
|
|
362
|
+
The default bind address is "127.0.0.1" (localhost only).
|
|
363
|
+
To expose externally, explicitly set host="0.0.0.0" AND deploy
|
|
364
|
+
behind a reverse proxy with authentication, TLS, and rate limiting.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
config: Configuration dict containing:
|
|
368
|
+
- host: Bind address (default: "127.0.0.1" - localhost only)
|
|
369
|
+
- port: Port number (default: 9090)
|
|
370
|
+
- path: Metrics endpoint path (default: "/metrics")
|
|
371
|
+
- push_gateway_url: Optional Pushgateway URL
|
|
372
|
+
- enable_server: Whether to start HTTP server (default: True)
|
|
373
|
+
- job_name: Job name for Pushgateway (default: "onex_metrics")
|
|
374
|
+
- shutdown_timeout_seconds: Shutdown timeout (default: 5.0)
|
|
375
|
+
- max_request_size_bytes: Max request body size (default: 1MB)
|
|
376
|
+
- request_timeout_seconds: Request processing timeout (default: 30s)
|
|
377
|
+
|
|
378
|
+
Raises:
|
|
379
|
+
ProtocolConfigurationError: If configuration validation fails.
|
|
380
|
+
RuntimeHostError: If HTTP server fails to start.
|
|
381
|
+
|
|
382
|
+
Example:
|
|
383
|
+
>>> handler = HandlerMetricsPrometheus()
|
|
384
|
+
>>> await handler.initialize({"port": 9091})
|
|
385
|
+
>>> # Server bound to 127.0.0.1:9091 (localhost only - secure)
|
|
386
|
+
"""
|
|
387
|
+
init_correlation_id = uuid4()
|
|
388
|
+
|
|
389
|
+
# Validate required dependencies before proceeding
|
|
390
|
+
missing_deps: list[str] = []
|
|
391
|
+
if not _PROMETHEUS_AVAILABLE:
|
|
392
|
+
missing_deps.append("prometheus_client")
|
|
393
|
+
if not _AIOHTTP_AVAILABLE:
|
|
394
|
+
missing_deps.append("aiohttp")
|
|
395
|
+
|
|
396
|
+
if missing_deps:
|
|
397
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
398
|
+
correlation_id=init_correlation_id,
|
|
399
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
400
|
+
operation="initialize",
|
|
401
|
+
target_name="metrics_prometheus_handler",
|
|
402
|
+
)
|
|
403
|
+
raise ProtocolConfigurationError(
|
|
404
|
+
f"Missing required dependencies: {', '.join(missing_deps)}. "
|
|
405
|
+
f"Install with: pip install {' '.join(missing_deps)}",
|
|
406
|
+
context=context,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
logger.info(
|
|
410
|
+
"Initializing %s",
|
|
411
|
+
self.__class__.__name__,
|
|
412
|
+
extra={
|
|
413
|
+
"handler": self.__class__.__name__,
|
|
414
|
+
"correlation_id": str(init_correlation_id),
|
|
415
|
+
},
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# Validate and parse configuration
|
|
419
|
+
# NOTE: Broad Exception catch is intentional here because Pydantic can raise
|
|
420
|
+
# various exception types (ValidationError, TypeError, ValueError) depending
|
|
421
|
+
# on the validation failure. We wrap all in ProtocolConfigurationError.
|
|
422
|
+
try:
|
|
423
|
+
self._config = ModelMetricsHandlerConfig(**config)
|
|
424
|
+
except Exception as e:
|
|
425
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
426
|
+
correlation_id=init_correlation_id,
|
|
427
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
428
|
+
operation="initialize",
|
|
429
|
+
target_name="metrics_prometheus_handler",
|
|
430
|
+
)
|
|
431
|
+
raise ProtocolConfigurationError(
|
|
432
|
+
f"Invalid metrics handler configuration: {e}",
|
|
433
|
+
context=context,
|
|
434
|
+
) from e
|
|
435
|
+
|
|
436
|
+
# Start HTTP server if enabled
|
|
437
|
+
if self._config.enable_server:
|
|
438
|
+
await self._start_http_server(init_correlation_id)
|
|
439
|
+
|
|
440
|
+
self._initialized = True
|
|
441
|
+
|
|
442
|
+
logger.info(
|
|
443
|
+
"HandlerMetricsPrometheus initialized successfully",
|
|
444
|
+
extra={
|
|
445
|
+
"correlation_id": str(init_correlation_id),
|
|
446
|
+
"host": self._config.host,
|
|
447
|
+
"port": self._config.port,
|
|
448
|
+
"path": self._config.path,
|
|
449
|
+
"server_enabled": self._config.enable_server,
|
|
450
|
+
"push_gateway_configured": self._config.push_gateway_url is not None,
|
|
451
|
+
},
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
async def _start_http_server(self, correlation_id: UUID) -> None:
|
|
455
|
+
"""Start the aiohttp HTTP server for metrics exposition.
|
|
456
|
+
|
|
457
|
+
Creates an aiohttp web application with a single route for metrics
|
|
458
|
+
and starts it as a background TCP site.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
correlation_id: Correlation ID for tracing.
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
RuntimeHostError: If server fails to start.
|
|
465
|
+
"""
|
|
466
|
+
if self._config is None:
|
|
467
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
468
|
+
correlation_id=correlation_id,
|
|
469
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
470
|
+
operation="start_http_server",
|
|
471
|
+
target_name="metrics_prometheus_handler",
|
|
472
|
+
)
|
|
473
|
+
raise RuntimeHostError(
|
|
474
|
+
"Configuration not set before starting HTTP server",
|
|
475
|
+
context=context,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
# Create aiohttp application with security limits
|
|
480
|
+
# client_max_size limits the maximum request body size
|
|
481
|
+
self._app = web.Application(
|
|
482
|
+
client_max_size=self._config.max_request_size_bytes,
|
|
483
|
+
)
|
|
484
|
+
self._app.router.add_get(self._config.path, self._handle_metrics_request)
|
|
485
|
+
|
|
486
|
+
# Create runner and site
|
|
487
|
+
self._runner = web.AppRunner(self._app)
|
|
488
|
+
await self._runner.setup()
|
|
489
|
+
|
|
490
|
+
self._site = web.TCPSite(
|
|
491
|
+
self._runner,
|
|
492
|
+
self._config.host,
|
|
493
|
+
self._config.port,
|
|
494
|
+
)
|
|
495
|
+
await self._site.start()
|
|
496
|
+
|
|
497
|
+
# Log security-relevant configuration
|
|
498
|
+
is_localhost = self._config.host in ("127.0.0.1", "localhost", "::1")
|
|
499
|
+
if not is_localhost:
|
|
500
|
+
logger.warning(
|
|
501
|
+
"HTTP metrics server binding to non-localhost address - "
|
|
502
|
+
"ensure reverse proxy with auth/TLS is in place",
|
|
503
|
+
extra={
|
|
504
|
+
"correlation_id": str(correlation_id),
|
|
505
|
+
"host": self._config.host,
|
|
506
|
+
"port": self._config.port,
|
|
507
|
+
},
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
logger.info(
|
|
511
|
+
"HTTP metrics server started",
|
|
512
|
+
extra={
|
|
513
|
+
"correlation_id": str(correlation_id),
|
|
514
|
+
"host": self._config.host,
|
|
515
|
+
"port": self._config.port,
|
|
516
|
+
"path": self._config.path,
|
|
517
|
+
"max_request_size_bytes": self._config.max_request_size_bytes,
|
|
518
|
+
"request_timeout_seconds": self._config.request_timeout_seconds,
|
|
519
|
+
"localhost_only": is_localhost,
|
|
520
|
+
},
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
except OSError as e:
|
|
524
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
525
|
+
correlation_id=correlation_id,
|
|
526
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
527
|
+
operation="start_http_server",
|
|
528
|
+
target_name="metrics_prometheus_handler",
|
|
529
|
+
)
|
|
530
|
+
raise RuntimeHostError(
|
|
531
|
+
f"Failed to start HTTP server on {self._config.host}:{self._config.port}: {e}",
|
|
532
|
+
context=context,
|
|
533
|
+
) from e
|
|
534
|
+
|
|
535
|
+
def _parse_correlation_id_header(self, header_value: str | None) -> UUID:
|
|
536
|
+
"""Parse and validate X-Correlation-ID header value with security guards.
|
|
537
|
+
|
|
538
|
+
Safely extracts a correlation ID from the request header. If the header
|
|
539
|
+
is missing, empty, or contains an invalid UUID format, generates a new
|
|
540
|
+
UUID and logs a warning for invalid values.
|
|
541
|
+
|
|
542
|
+
Security Measures:
|
|
543
|
+
- Length check: Headers exceeding 64 chars are rejected before parsing
|
|
544
|
+
- Format validation: Regex validates UUID format before UUID() parsing
|
|
545
|
+
- No reflection: Invalid values are NOT echoed back in responses
|
|
546
|
+
- Truncated logging: Invalid values logged with max 36 chars (UUID length)
|
|
547
|
+
|
|
548
|
+
Args:
|
|
549
|
+
header_value: The raw X-Correlation-ID header value, or None if absent.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
A valid UUID - either parsed from the header or newly generated.
|
|
553
|
+
|
|
554
|
+
Note:
|
|
555
|
+
Invalid correlation IDs are handled gracefully to avoid crashing
|
|
556
|
+
the metrics endpoint. A warning is logged to help identify
|
|
557
|
+
misconfigured clients, but the invalid value is NOT exposed in
|
|
558
|
+
HTTP responses to prevent header injection attacks.
|
|
559
|
+
"""
|
|
560
|
+
if not header_value:
|
|
561
|
+
return uuid4()
|
|
562
|
+
|
|
563
|
+
# Security: Reject excessively long headers before any processing
|
|
564
|
+
if len(header_value) > _MAX_CORRELATION_ID_LENGTH:
|
|
565
|
+
fallback_id = uuid4()
|
|
566
|
+
logger.warning(
|
|
567
|
+
"X-Correlation-ID header exceeds maximum length, using generated UUID",
|
|
568
|
+
extra={
|
|
569
|
+
"header_length": len(header_value),
|
|
570
|
+
"max_length": _MAX_CORRELATION_ID_LENGTH,
|
|
571
|
+
"generated_correlation_id": str(fallback_id),
|
|
572
|
+
},
|
|
573
|
+
)
|
|
574
|
+
return fallback_id
|
|
575
|
+
|
|
576
|
+
# Security: Validate UUID format with regex before parsing
|
|
577
|
+
# This prevents malformed input from reaching UUID() constructor
|
|
578
|
+
if not _UUID_REGEX.match(header_value):
|
|
579
|
+
fallback_id = uuid4()
|
|
580
|
+
logger.warning(
|
|
581
|
+
"Invalid X-Correlation-ID header format, using generated UUID",
|
|
582
|
+
extra={
|
|
583
|
+
# Truncate to UUID length (36 chars) max for safe logging
|
|
584
|
+
"invalid_header_preview": header_value[:36],
|
|
585
|
+
"generated_correlation_id": str(fallback_id),
|
|
586
|
+
},
|
|
587
|
+
)
|
|
588
|
+
return fallback_id
|
|
589
|
+
|
|
590
|
+
try:
|
|
591
|
+
return UUID(header_value)
|
|
592
|
+
except (ValueError, AttributeError):
|
|
593
|
+
# Fallback for any edge cases not caught by regex
|
|
594
|
+
fallback_id = uuid4()
|
|
595
|
+
logger.warning(
|
|
596
|
+
"X-Correlation-ID UUID parsing failed, using generated UUID",
|
|
597
|
+
extra={
|
|
598
|
+
"generated_correlation_id": str(fallback_id),
|
|
599
|
+
},
|
|
600
|
+
)
|
|
601
|
+
return fallback_id
|
|
602
|
+
|
|
603
|
+
async def _handle_metrics_request(self, request: web.Request) -> web.Response:
|
|
604
|
+
"""Handle HTTP GET requests to the metrics endpoint.
|
|
605
|
+
|
|
606
|
+
Generates Prometheus metrics in text exposition format and returns
|
|
607
|
+
them with the appropriate content type.
|
|
608
|
+
|
|
609
|
+
Security:
|
|
610
|
+
- Correlation IDs are validated before use (see _parse_correlation_id_header)
|
|
611
|
+
- Error messages are sanitized - no internal exception details exposed
|
|
612
|
+
- Timeout protection prevents DoS via slow metric generation
|
|
613
|
+
|
|
614
|
+
Args:
|
|
615
|
+
request: aiohttp Request object.
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
aiohttp Response with metrics text, or generic error on failure.
|
|
619
|
+
"""
|
|
620
|
+
# Get and validate correlation ID from request headers
|
|
621
|
+
correlation_id = self._parse_correlation_id_header(
|
|
622
|
+
request.headers.get("X-Correlation-ID")
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
logger.debug(
|
|
626
|
+
"Handling metrics scrape request",
|
|
627
|
+
extra={
|
|
628
|
+
"correlation_id": str(correlation_id),
|
|
629
|
+
"remote": str(request.remote),
|
|
630
|
+
},
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
# Start timing for scrape duration metric
|
|
634
|
+
start_time = time.perf_counter()
|
|
635
|
+
|
|
636
|
+
# Get timeout from config if available, otherwise use default
|
|
637
|
+
metrics_timeout = (
|
|
638
|
+
self._config.request_timeout_seconds
|
|
639
|
+
if self._config
|
|
640
|
+
else _DEFAULT_METRICS_GENERATION_TIMEOUT
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
try:
|
|
644
|
+
# Generate metrics with timeout protection to prevent DoS
|
|
645
|
+
# generate_latest() is synchronous, so run in executor with timeout
|
|
646
|
+
loop = asyncio.get_running_loop()
|
|
647
|
+
metrics_bytes = await asyncio.wait_for(
|
|
648
|
+
loop.run_in_executor(None, generate_latest),
|
|
649
|
+
timeout=metrics_timeout,
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
# Record scrape duration after generation completes
|
|
653
|
+
# The duration is observed AFTER generate_latest() returns, so it will
|
|
654
|
+
# be available in the NEXT scrape (avoiding chicken-and-egg recursion)
|
|
655
|
+
duration_seconds = time.perf_counter() - start_time
|
|
656
|
+
histogram = _get_scrape_duration_histogram()
|
|
657
|
+
if histogram is not None:
|
|
658
|
+
histogram.observe(duration_seconds)
|
|
659
|
+
|
|
660
|
+
logger.debug(
|
|
661
|
+
"Metrics scrape completed",
|
|
662
|
+
extra={
|
|
663
|
+
"correlation_id": str(correlation_id),
|
|
664
|
+
"duration_seconds": duration_seconds,
|
|
665
|
+
"metrics_size_bytes": len(metrics_bytes),
|
|
666
|
+
},
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
# Use headers dict for Content-Type because CONTENT_TYPE_LATEST includes
|
|
670
|
+
# charset which conflicts with aiohttp's content_type parameter validation
|
|
671
|
+
return web.Response(
|
|
672
|
+
body=metrics_bytes,
|
|
673
|
+
headers={
|
|
674
|
+
"Content-Type": CONTENT_TYPE_LATEST,
|
|
675
|
+
"X-Correlation-ID": str(correlation_id),
|
|
676
|
+
},
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
except TimeoutError:
|
|
680
|
+
# Record duration even on timeout (will be at or near timeout threshold)
|
|
681
|
+
duration_seconds = time.perf_counter() - start_time
|
|
682
|
+
histogram = _get_scrape_duration_histogram()
|
|
683
|
+
if histogram is not None:
|
|
684
|
+
histogram.observe(duration_seconds)
|
|
685
|
+
|
|
686
|
+
# Log timeout with full details, but return generic message
|
|
687
|
+
# Using warning level since timeout is expected behavior (DoS protection)
|
|
688
|
+
logger.warning(
|
|
689
|
+
"Metrics generation timed out",
|
|
690
|
+
extra={
|
|
691
|
+
"correlation_id": str(correlation_id),
|
|
692
|
+
"timeout_seconds": metrics_timeout,
|
|
693
|
+
"duration_seconds": duration_seconds,
|
|
694
|
+
},
|
|
695
|
+
)
|
|
696
|
+
# Security: Generic error message - no internal details exposed
|
|
697
|
+
return web.Response(
|
|
698
|
+
text="Service temporarily unavailable",
|
|
699
|
+
status=503, # Service Unavailable for timeout
|
|
700
|
+
headers={"X-Correlation-ID": str(correlation_id)},
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
except Exception:
|
|
704
|
+
# NOTE: Broad Exception catch is intentional here to ensure observability
|
|
705
|
+
# (duration recording) even for unexpected errors, while returning a
|
|
706
|
+
# generic 500 response that prevents internal details from leaking.
|
|
707
|
+
# The exception is fully logged internally for debugging.
|
|
708
|
+
#
|
|
709
|
+
# Record duration even on error for observability
|
|
710
|
+
duration_seconds = time.perf_counter() - start_time
|
|
711
|
+
histogram = _get_scrape_duration_histogram()
|
|
712
|
+
if histogram is not None:
|
|
713
|
+
histogram.observe(duration_seconds)
|
|
714
|
+
|
|
715
|
+
# Log full exception details internally for debugging
|
|
716
|
+
logger.exception(
|
|
717
|
+
"Failed to generate metrics",
|
|
718
|
+
extra={
|
|
719
|
+
"correlation_id": str(correlation_id),
|
|
720
|
+
"duration_seconds": duration_seconds,
|
|
721
|
+
},
|
|
722
|
+
)
|
|
723
|
+
# Security: Generic error message - no exception type or details exposed
|
|
724
|
+
# This prevents information leakage about internal implementation
|
|
725
|
+
return web.Response(
|
|
726
|
+
text="Internal server error generating metrics",
|
|
727
|
+
status=500,
|
|
728
|
+
headers={"X-Correlation-ID": str(correlation_id)},
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
async def shutdown(self) -> None:
|
|
732
|
+
"""Shutdown the HTTP server and release resources.
|
|
733
|
+
|
|
734
|
+
Gracefully stops the HTTP server, waiting for pending requests
|
|
735
|
+
to complete within the configured timeout. The shutdown process:
|
|
736
|
+
|
|
737
|
+
1. Stops accepting new connections (site.stop())
|
|
738
|
+
2. Waits for pending requests to drain (runner.cleanup())
|
|
739
|
+
3. Releases all resources
|
|
740
|
+
|
|
741
|
+
If cleanup times out, pending requests are forcibly terminated.
|
|
742
|
+
"""
|
|
743
|
+
shutdown_correlation_id = uuid4()
|
|
744
|
+
|
|
745
|
+
logger.info(
|
|
746
|
+
"Shutting down HandlerMetricsPrometheus",
|
|
747
|
+
extra={"correlation_id": str(shutdown_correlation_id)},
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
timeout = self._config.shutdown_timeout_seconds if self._config else 5.0
|
|
751
|
+
|
|
752
|
+
# Phase 1: Stop accepting new connections
|
|
753
|
+
# This allows pending requests to complete before we close
|
|
754
|
+
if self._site is not None:
|
|
755
|
+
try:
|
|
756
|
+
await self._site.stop()
|
|
757
|
+
logger.debug(
|
|
758
|
+
"HTTP server stopped accepting new connections",
|
|
759
|
+
extra={"correlation_id": str(shutdown_correlation_id)},
|
|
760
|
+
)
|
|
761
|
+
except OSError as e:
|
|
762
|
+
# OSError can occur if socket is already closed
|
|
763
|
+
logger.warning(
|
|
764
|
+
"Error stopping HTTP site: %s",
|
|
765
|
+
e,
|
|
766
|
+
extra={
|
|
767
|
+
"correlation_id": str(shutdown_correlation_id),
|
|
768
|
+
"error_type": type(e).__name__,
|
|
769
|
+
},
|
|
770
|
+
)
|
|
771
|
+
finally:
|
|
772
|
+
self._site = None
|
|
773
|
+
|
|
774
|
+
# Phase 2: Drain pending requests and cleanup runner
|
|
775
|
+
# This gives in-flight requests time to complete
|
|
776
|
+
if self._runner is not None:
|
|
777
|
+
try:
|
|
778
|
+
await asyncio.wait_for(
|
|
779
|
+
self._runner.cleanup(),
|
|
780
|
+
timeout=timeout,
|
|
781
|
+
)
|
|
782
|
+
logger.debug(
|
|
783
|
+
"HTTP server cleanup completed successfully",
|
|
784
|
+
extra={"correlation_id": str(shutdown_correlation_id)},
|
|
785
|
+
)
|
|
786
|
+
except TimeoutError:
|
|
787
|
+
# Timeout means pending requests couldn't complete in time
|
|
788
|
+
# This is a warning because it may indicate slow consumers
|
|
789
|
+
logger.warning(
|
|
790
|
+
"HTTP server cleanup timed out after %.1f seconds - "
|
|
791
|
+
"pending requests may have been forcibly terminated",
|
|
792
|
+
timeout,
|
|
793
|
+
extra={
|
|
794
|
+
"correlation_id": str(shutdown_correlation_id),
|
|
795
|
+
"timeout_seconds": timeout,
|
|
796
|
+
},
|
|
797
|
+
)
|
|
798
|
+
except OSError as e:
|
|
799
|
+
# OSError can occur during cleanup if connections are in bad state
|
|
800
|
+
logger.warning(
|
|
801
|
+
"OSError during HTTP server cleanup: %s",
|
|
802
|
+
e,
|
|
803
|
+
extra={
|
|
804
|
+
"correlation_id": str(shutdown_correlation_id),
|
|
805
|
+
"error_type": type(e).__name__,
|
|
806
|
+
},
|
|
807
|
+
)
|
|
808
|
+
finally:
|
|
809
|
+
self._runner = None
|
|
810
|
+
|
|
811
|
+
self._app = None
|
|
812
|
+
self._initialized = False
|
|
813
|
+
self._config = None
|
|
814
|
+
|
|
815
|
+
logger.info(
|
|
816
|
+
"HandlerMetricsPrometheus shutdown complete",
|
|
817
|
+
extra={"correlation_id": str(shutdown_correlation_id)},
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
def _build_response(
|
|
821
|
+
self,
|
|
822
|
+
payload: ModelMetricsHandlerPayload,
|
|
823
|
+
correlation_id: UUID,
|
|
824
|
+
input_envelope_id: UUID,
|
|
825
|
+
) -> ModelHandlerOutput[ModelMetricsHandlerResponse]:
|
|
826
|
+
"""Build standardized response wrapped in ModelHandlerOutput.
|
|
827
|
+
|
|
828
|
+
Args:
|
|
829
|
+
payload: Operation-specific response payload.
|
|
830
|
+
correlation_id: Correlation ID for tracing.
|
|
831
|
+
input_envelope_id: Input envelope ID for causality tracking.
|
|
832
|
+
|
|
833
|
+
Returns:
|
|
834
|
+
ModelHandlerOutput wrapping the metrics handler response.
|
|
835
|
+
"""
|
|
836
|
+
response = ModelMetricsHandlerResponse(
|
|
837
|
+
status=EnumResponseStatus.SUCCESS,
|
|
838
|
+
payload=payload,
|
|
839
|
+
correlation_id=correlation_id,
|
|
840
|
+
)
|
|
841
|
+
return ModelHandlerOutput.for_compute(
|
|
842
|
+
input_envelope_id=input_envelope_id,
|
|
843
|
+
correlation_id=correlation_id,
|
|
844
|
+
handler_id=HANDLER_ID_METRICS,
|
|
845
|
+
result=response,
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
async def execute(
|
|
849
|
+
self, envelope: dict[str, object]
|
|
850
|
+
) -> ModelHandlerOutput[ModelMetricsHandlerResponse]:
|
|
851
|
+
"""Execute a metrics operation from envelope.
|
|
852
|
+
|
|
853
|
+
Args:
|
|
854
|
+
envelope: Request envelope containing:
|
|
855
|
+
- operation: "metrics.scrape" or "metrics.push"
|
|
856
|
+
- payload: dict with operation-specific parameters (optional)
|
|
857
|
+
- correlation_id: Optional correlation ID for tracing
|
|
858
|
+
- envelope_id: Optional envelope ID for causality tracking
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
ModelHandlerOutput wrapping the operation result.
|
|
862
|
+
|
|
863
|
+
Raises:
|
|
864
|
+
RuntimeHostError: If handler not initialized or invalid operation.
|
|
865
|
+
|
|
866
|
+
Example:
|
|
867
|
+
>>> result = await handler.execute({
|
|
868
|
+
... "operation": "metrics.scrape",
|
|
869
|
+
... "correlation_id": str(uuid4()),
|
|
870
|
+
... })
|
|
871
|
+
"""
|
|
872
|
+
correlation_id = self._extract_correlation_id(envelope)
|
|
873
|
+
input_envelope_id = self._extract_envelope_id(envelope)
|
|
874
|
+
|
|
875
|
+
if not self._initialized:
|
|
876
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
877
|
+
correlation_id=correlation_id,
|
|
878
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
879
|
+
operation="execute",
|
|
880
|
+
target_name="metrics_prometheus_handler",
|
|
881
|
+
)
|
|
882
|
+
raise RuntimeHostError(
|
|
883
|
+
"Metrics handler not initialized. Call initialize() first.",
|
|
884
|
+
context=context,
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
operation = envelope.get("operation")
|
|
888
|
+
if not isinstance(operation, str):
|
|
889
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
890
|
+
correlation_id=correlation_id,
|
|
891
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
892
|
+
operation="execute",
|
|
893
|
+
target_name="metrics_prometheus_handler",
|
|
894
|
+
)
|
|
895
|
+
raise RuntimeHostError(
|
|
896
|
+
"Missing or invalid 'operation' in envelope",
|
|
897
|
+
context=context,
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
if operation not in SUPPORTED_OPERATIONS:
|
|
901
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
902
|
+
correlation_id=correlation_id,
|
|
903
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
904
|
+
operation=operation,
|
|
905
|
+
target_name="metrics_prometheus_handler",
|
|
906
|
+
)
|
|
907
|
+
raise RuntimeHostError(
|
|
908
|
+
f"Operation '{operation}' not supported. "
|
|
909
|
+
f"Available: {', '.join(sorted(SUPPORTED_OPERATIONS))}",
|
|
910
|
+
context=context,
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
# Route to appropriate handler
|
|
914
|
+
if operation == "metrics.scrape":
|
|
915
|
+
return await self._handle_scrape(correlation_id, input_envelope_id)
|
|
916
|
+
else: # metrics.push
|
|
917
|
+
return await self._handle_push(envelope, correlation_id, input_envelope_id)
|
|
918
|
+
|
|
919
|
+
async def _handle_scrape(
|
|
920
|
+
self,
|
|
921
|
+
correlation_id: UUID,
|
|
922
|
+
input_envelope_id: UUID,
|
|
923
|
+
) -> ModelHandlerOutput[ModelMetricsHandlerResponse]:
|
|
924
|
+
"""Handle metrics.scrape operation.
|
|
925
|
+
|
|
926
|
+
Generates Prometheus metrics from the default registry and returns
|
|
927
|
+
them in text exposition format.
|
|
928
|
+
|
|
929
|
+
Args:
|
|
930
|
+
correlation_id: Correlation ID for tracing.
|
|
931
|
+
input_envelope_id: Input envelope ID for causality tracking.
|
|
932
|
+
|
|
933
|
+
Returns:
|
|
934
|
+
ModelHandlerOutput with metrics text in payload.
|
|
935
|
+
"""
|
|
936
|
+
logger.debug(
|
|
937
|
+
"Executing metrics.scrape operation",
|
|
938
|
+
extra={"correlation_id": str(correlation_id)},
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
# Get timeout from config if available, otherwise use default
|
|
942
|
+
scrape_timeout = (
|
|
943
|
+
self._config.request_timeout_seconds
|
|
944
|
+
if self._config
|
|
945
|
+
else _DEFAULT_METRICS_GENERATION_TIMEOUT
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
# Generate metrics from default Prometheus registry
|
|
949
|
+
# generate_latest() is synchronous, so run in executor with timeout
|
|
950
|
+
# to prevent blocking the event loop
|
|
951
|
+
try:
|
|
952
|
+
loop = asyncio.get_running_loop()
|
|
953
|
+
metrics_bytes = await asyncio.wait_for(
|
|
954
|
+
loop.run_in_executor(None, generate_latest),
|
|
955
|
+
timeout=scrape_timeout,
|
|
956
|
+
)
|
|
957
|
+
except TimeoutError:
|
|
958
|
+
context = ModelTimeoutErrorContext(
|
|
959
|
+
correlation_id=correlation_id,
|
|
960
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
961
|
+
operation="metrics.scrape",
|
|
962
|
+
target_name="metrics_prometheus_handler",
|
|
963
|
+
timeout_seconds=scrape_timeout,
|
|
964
|
+
)
|
|
965
|
+
raise InfraTimeoutError(
|
|
966
|
+
f"Metrics generation timed out after {scrape_timeout}s",
|
|
967
|
+
context=context,
|
|
968
|
+
) from None
|
|
969
|
+
|
|
970
|
+
metrics_text = metrics_bytes.decode("utf-8")
|
|
971
|
+
|
|
972
|
+
payload = ModelMetricsHandlerPayload(
|
|
973
|
+
operation_type="metrics.scrape",
|
|
974
|
+
metrics_text=metrics_text,
|
|
975
|
+
content_type=CONTENT_TYPE_LATEST,
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
return self._build_response(payload, correlation_id, input_envelope_id)
|
|
979
|
+
|
|
980
|
+
async def _handle_push(
|
|
981
|
+
self,
|
|
982
|
+
envelope: dict[str, object],
|
|
983
|
+
correlation_id: UUID,
|
|
984
|
+
input_envelope_id: UUID,
|
|
985
|
+
) -> ModelHandlerOutput[ModelMetricsHandlerResponse]:
|
|
986
|
+
"""Handle metrics.push operation.
|
|
987
|
+
|
|
988
|
+
Pushes current metrics to the configured Prometheus Pushgateway.
|
|
989
|
+
Requires push_gateway_url to be configured.
|
|
990
|
+
|
|
991
|
+
Args:
|
|
992
|
+
envelope: Request envelope (may contain override configuration).
|
|
993
|
+
correlation_id: Correlation ID for tracing.
|
|
994
|
+
input_envelope_id: Input envelope ID for causality tracking.
|
|
995
|
+
|
|
996
|
+
Returns:
|
|
997
|
+
ModelHandlerOutput with push confirmation in payload.
|
|
998
|
+
|
|
999
|
+
Raises:
|
|
1000
|
+
RuntimeHostError: If Pushgateway is not configured.
|
|
1001
|
+
"""
|
|
1002
|
+
if self._config is None or self._config.push_gateway_url is None:
|
|
1003
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
1004
|
+
correlation_id=correlation_id,
|
|
1005
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
1006
|
+
operation="metrics.push",
|
|
1007
|
+
target_name="metrics_prometheus_handler",
|
|
1008
|
+
)
|
|
1009
|
+
raise RuntimeHostError(
|
|
1010
|
+
"Pushgateway not configured. Set push_gateway_url in config.",
|
|
1011
|
+
context=context,
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
# Capture config values in local variables for lambda closure
|
|
1015
|
+
# This ensures type safety and avoids mypy errors with Optional access
|
|
1016
|
+
push_gateway_url: str = self._config.push_gateway_url
|
|
1017
|
+
job_name: str = self._config.job_name
|
|
1018
|
+
|
|
1019
|
+
logger.debug(
|
|
1020
|
+
"Executing metrics.push operation",
|
|
1021
|
+
extra={
|
|
1022
|
+
"correlation_id": str(correlation_id),
|
|
1023
|
+
"push_gateway_url": push_gateway_url,
|
|
1024
|
+
},
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
# Use prometheus_client's push_to_gateway functionality
|
|
1028
|
+
# Note: This is a synchronous operation, so we run it in executor with timeout
|
|
1029
|
+
# NOTE: Broad Exception catch is intentional here because push_to_gateway
|
|
1030
|
+
# can raise various exceptions: URLError for network issues, HTTPError for
|
|
1031
|
+
# gateway errors, and other unexpected exceptions from the prometheus_client
|
|
1032
|
+
# library. We wrap all failures in RuntimeHostError for consistent handling.
|
|
1033
|
+
try:
|
|
1034
|
+
from prometheus_client import REGISTRY, push_to_gateway
|
|
1035
|
+
|
|
1036
|
+
loop = asyncio.get_running_loop()
|
|
1037
|
+
await asyncio.wait_for(
|
|
1038
|
+
loop.run_in_executor(
|
|
1039
|
+
None,
|
|
1040
|
+
lambda: push_to_gateway(
|
|
1041
|
+
push_gateway_url,
|
|
1042
|
+
job=job_name,
|
|
1043
|
+
registry=REGISTRY,
|
|
1044
|
+
),
|
|
1045
|
+
),
|
|
1046
|
+
timeout=_PUSH_GATEWAY_TIMEOUT,
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
pushed_at = datetime.now(UTC).isoformat()
|
|
1050
|
+
|
|
1051
|
+
payload = ModelMetricsHandlerPayload(
|
|
1052
|
+
operation_type="metrics.push",
|
|
1053
|
+
pushed_at=pushed_at,
|
|
1054
|
+
push_gateway_url=push_gateway_url,
|
|
1055
|
+
job_name=job_name,
|
|
1056
|
+
)
|
|
1057
|
+
|
|
1058
|
+
logger.info(
|
|
1059
|
+
"Metrics pushed to Pushgateway",
|
|
1060
|
+
extra={
|
|
1061
|
+
"correlation_id": str(correlation_id),
|
|
1062
|
+
"push_gateway_url": push_gateway_url,
|
|
1063
|
+
"pushed_at": pushed_at,
|
|
1064
|
+
},
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
return self._build_response(payload, correlation_id, input_envelope_id)
|
|
1068
|
+
|
|
1069
|
+
except TimeoutError:
|
|
1070
|
+
timeout_ctx = ModelTimeoutErrorContext(
|
|
1071
|
+
correlation_id=correlation_id,
|
|
1072
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
1073
|
+
operation="metrics.push",
|
|
1074
|
+
target_name="metrics_prometheus_handler",
|
|
1075
|
+
timeout_seconds=_PUSH_GATEWAY_TIMEOUT,
|
|
1076
|
+
)
|
|
1077
|
+
raise InfraTimeoutError(
|
|
1078
|
+
f"Pushgateway request timed out after {_PUSH_GATEWAY_TIMEOUT}s",
|
|
1079
|
+
context=timeout_ctx,
|
|
1080
|
+
) from None
|
|
1081
|
+
|
|
1082
|
+
except Exception as e:
|
|
1083
|
+
# NOTE: Broad Exception catch is intentional here because httpx and
|
|
1084
|
+
# network operations can raise many exception types (ConnectionError,
|
|
1085
|
+
# ProtocolError, etc.). We wrap all in RuntimeHostError with correlation
|
|
1086
|
+
# context while preserving the error chain for debugging.
|
|
1087
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
1088
|
+
correlation_id=correlation_id,
|
|
1089
|
+
transport_type=EnumInfraTransportType.HTTP,
|
|
1090
|
+
operation="metrics.push",
|
|
1091
|
+
target_name="metrics_prometheus_handler",
|
|
1092
|
+
)
|
|
1093
|
+
raise RuntimeHostError(
|
|
1094
|
+
f"Failed to push metrics to Pushgateway: {type(e).__name__}",
|
|
1095
|
+
context=context,
|
|
1096
|
+
) from e
|
|
1097
|
+
|
|
1098
|
+
def describe(self) -> dict[str, object]:
|
|
1099
|
+
"""Return handler metadata and capabilities for introspection.
|
|
1100
|
+
|
|
1101
|
+
Returns:
|
|
1102
|
+
dict containing handler type, category, operations, and status.
|
|
1103
|
+
"""
|
|
1104
|
+
return {
|
|
1105
|
+
"handler_type": self.handler_type.value,
|
|
1106
|
+
"handler_category": self.handler_category.value,
|
|
1107
|
+
"supported_operations": sorted(SUPPORTED_OPERATIONS),
|
|
1108
|
+
"initialized": self._initialized,
|
|
1109
|
+
"server_enabled": (self._config.enable_server if self._config else False),
|
|
1110
|
+
"host": self._config.host if self._config else None,
|
|
1111
|
+
"port": self._config.port if self._config else None,
|
|
1112
|
+
"path": self._config.path if self._config else None,
|
|
1113
|
+
"push_gateway_configured": (
|
|
1114
|
+
self._config.push_gateway_url is not None if self._config else False
|
|
1115
|
+
),
|
|
1116
|
+
"version": "0.1.0",
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
|
|
1120
|
+
__all__: list[str] = ["HandlerMetricsPrometheus", "HANDLER_ID_METRICS"]
|