omnibase_infra 0.2.1__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/cli/__init__.py +1 -0
- omnibase_infra/cli/commands.py +216 -0
- omnibase_infra/clients/__init__.py +0 -0
- omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +261 -0
- omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +138 -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 +123 -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_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 +101 -0
- omnibase_infra/enums/enum_handler_loader_error.py +178 -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_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 +156 -0
- omnibase_infra/errors/error_architecture_violation.py +152 -0
- omnibase_infra/errors/error_chain_propagation.py +188 -0
- omnibase_infra/errors/error_compute_registry.py +92 -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 +102 -0
- omnibase_infra/errors/error_infra.py +608 -0
- omnibase_infra/errors/error_message_type_registry.py +101 -0
- omnibase_infra/errors/error_policy_registry.py +112 -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 +86 -0
- omnibase_infra/event_bus/event_bus_inmemory.py +743 -0
- omnibase_infra/event_bus/event_bus_kafka.py +1658 -0
- omnibase_infra/event_bus/mixin_kafka_broadcast.py +184 -0
- omnibase_infra/event_bus/mixin_kafka_dlq.py +765 -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 +725 -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/topic_constants.py +376 -0
- omnibase_infra/handlers/__init__.py +75 -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 +787 -0
- omnibase_infra/handlers/handler_db.py +1039 -0
- omnibase_infra/handlers/handler_filesystem.py +1478 -0
- omnibase_infra/handlers/handler_graph.py +1154 -0
- omnibase_infra/handlers/handler_http.py +920 -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 +748 -0
- omnibase_infra/handlers/handler_qdrant.py +1076 -0
- omnibase_infra/handlers/handler_vault.py +422 -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 +42 -0
- omnibase_infra/handlers/mixins/mixin_consul_initialization.py +349 -0
- omnibase_infra/handlers/mixins/mixin_consul_kv.py +337 -0
- omnibase_infra/handlers/mixins/mixin_consul_service.py +277 -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 +915 -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 +747 -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 +99 -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/mixins/__init__.py +71 -0
- omnibase_infra/mixins/mixin_async_circuit_breaker.py +655 -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 +2465 -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 +136 -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 +311 -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 +147 -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_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 +37 -0
- omnibase_infra/models/handlers/model_contract_discovery_result.py +80 -0
- omnibase_infra/models/handlers/model_handler_descriptor.py +185 -0
- omnibase_infra/models/handlers/model_handler_identifier.py +215 -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/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 +590 -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 +59 -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_introspection_metrics.py +253 -0
- omnibase_infra/models/registration/model_node_capabilities.py +179 -0
- omnibase_infra/models/registration/model_node_heartbeat_event.py +126 -0
- omnibase_infra/models/registration/model_node_introspection_event.py +175 -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 +40 -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 +280 -0
- omnibase_infra/models/runtime/model_loaded_handler.py +120 -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 +48 -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 +208 -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 +99 -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/effects/README.md +358 -0
- omnibase_infra/nodes/effects/__init__.py +26 -0
- omnibase_infra/nodes/effects/contract.yaml +172 -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/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 +475 -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 +609 -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 +525 -0
- omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +392 -0
- omnibase_infra/nodes/node_registration_orchestrator/wiring.py +742 -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 +225 -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 +109 -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 +194 -0
- omnibase_infra/nodes/node_registry_effect/__init__.py +85 -0
- omnibase_infra/nodes/node_registry_effect/contract.yaml +682 -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 +416 -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 +214 -0
- omnibase_infra/nodes/reducers/__init__.py +30 -0
- omnibase_infra/nodes/reducers/models/__init__.py +32 -0
- omnibase_infra/nodes/reducers/models/model_payload_consul_register.py +76 -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 +1137 -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 +435 -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 +99 -0
- omnibase_infra/protocols/protocol_capability_projection.py +253 -0
- omnibase_infra/protocols/protocol_capability_query.py +251 -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 +296 -0
- omnibase_infra/runtime/binding_config_resolver.py +2706 -0
- omnibase_infra/runtime/chain_aware_dispatch.py +467 -0
- omnibase_infra/runtime/contract_handler_discovery.py +582 -0
- omnibase_infra/runtime/contract_loaders/__init__.py +42 -0
- omnibase_infra/runtime/contract_loaders/handler_routing_loader.py +464 -0
- omnibase_infra/runtime/dispatch_context_enforcer.py +427 -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/handler_contract_source.py +669 -0
- omnibase_infra/runtime/handler_plugin_loader.py +2029 -0
- omnibase_infra/runtime/handler_registry.py +321 -0
- omnibase_infra/runtime/invocation_security_enforcer.py +427 -0
- omnibase_infra/runtime/kernel.py +40 -0
- omnibase_infra/runtime/mixin_policy_validation.py +522 -0
- omnibase_infra/runtime/mixin_semver_cache.py +378 -0
- omnibase_infra/runtime/mixins/__init__.py +17 -0
- omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +757 -0
- omnibase_infra/runtime/models/__init__.py +192 -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_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 +228 -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_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_scheduler_config.py +624 -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_shutdown_batch_result.py +75 -0
- omnibase_infra/runtime/models/model_shutdown_config.py +94 -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 +1102 -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 +27 -0
- omnibase_infra/runtime/protocols/protocol_runtime_scheduler.py +468 -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 +444 -0
- omnibase_infra/runtime/registry_compute.py +1143 -0
- omnibase_infra/runtime/registry_dispatcher.py +678 -0
- omnibase_infra/runtime/registry_policy.py +1502 -0
- omnibase_infra/runtime/runtime_scheduler.py +1070 -0
- omnibase_infra/runtime/secret_resolver.py +2110 -0
- omnibase_infra/runtime/security_metadata_validator.py +776 -0
- omnibase_infra/runtime/service_kernel.py +1573 -0
- omnibase_infra/runtime/service_message_dispatch_engine.py +1805 -0
- omnibase_infra/runtime/service_runtime_host_process.py +2260 -0
- omnibase_infra/runtime/util_container_wiring.py +1123 -0
- omnibase_infra/runtime/util_validation.py +314 -0
- omnibase_infra/runtime/util_version.py +98 -0
- omnibase_infra/runtime/util_wiring.py +566 -0
- omnibase_infra/schemas/schema_registration_projection.sql +320 -0
- omnibase_infra/services/__init__.py +68 -0
- omnibase_infra/services/corpus_capture.py +678 -0
- omnibase_infra/services/service_capability_query.py +945 -0
- omnibase_infra/services/service_health.py +897 -0
- omnibase_infra/services/service_node_selector.py +530 -0
- omnibase_infra/services/service_timeout_emitter.py +682 -0
- omnibase_infra/services/service_timeout_scanner.py +390 -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/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 +21 -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 +89 -0
- omnibase_infra/utils/correlation.py +208 -0
- omnibase_infra/utils/util_datetime.py +372 -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_semver.py +233 -0
- omnibase_infra/validation/__init__.py +307 -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 +1486 -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 +1710 -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 +410 -0
- omnibase_infra/validation/validator_topic_category.py +1152 -0
- omnibase_infra-0.2.1.dist-info/METADATA +197 -0
- omnibase_infra-0.2.1.dist-info/RECORD +675 -0
- omnibase_infra-0.2.1.dist-info/WHEEL +4 -0
- omnibase_infra-0.2.1.dist-info/entry_points.txt +4 -0
- omnibase_infra-0.2.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1070 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""RuntimeScheduler - Core service for emitting RuntimeTick events.
|
|
4
|
+
|
|
5
|
+
This module implements the RuntimeScheduler service that emits RuntimeTick events
|
|
6
|
+
at configured intervals. The scheduler is the single source of truth for "now"
|
|
7
|
+
across all orchestrators in the ONEX infrastructure.
|
|
8
|
+
|
|
9
|
+
Key Features:
|
|
10
|
+
- Configurable tick interval (10ms - 60,000ms)
|
|
11
|
+
- Circuit breaker pattern for publish resilience
|
|
12
|
+
- Restart-safe sequence number tracking
|
|
13
|
+
- Jitter to prevent thundering herd
|
|
14
|
+
- Graceful shutdown with proper lifecycle management
|
|
15
|
+
- Metrics collection for observability
|
|
16
|
+
|
|
17
|
+
Architecture:
|
|
18
|
+
The RuntimeScheduler is an INFRASTRUCTURE concern. It emits RuntimeTick events
|
|
19
|
+
that orchestrators subscribe to for timeout decisions (DOMAIN concern). This
|
|
20
|
+
separation ensures clear ownership and testability.
|
|
21
|
+
|
|
22
|
+
Concurrency Safety:
|
|
23
|
+
This scheduler is coroutine-safe, not thread-safe. All locking uses
|
|
24
|
+
asyncio primitives which protect against concurrent coroutine access:
|
|
25
|
+
- Circuit breaker operations protected by `_circuit_breaker_lock` (asyncio.Lock)
|
|
26
|
+
- State variables protected by `_state_lock` (asyncio.Lock)
|
|
27
|
+
- Tick loop runs as background task with shutdown signaling via `asyncio.Event`
|
|
28
|
+
For multi-threaded access, additional synchronization would be required.
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
```python
|
|
32
|
+
from omnibase_infra.runtime.runtime_scheduler import RuntimeScheduler
|
|
33
|
+
from omnibase_infra.runtime.models import ModelRuntimeSchedulerConfig
|
|
34
|
+
from omnibase_infra.event_bus.event_bus_kafka import EventBusKafka
|
|
35
|
+
|
|
36
|
+
# Create scheduler with configuration
|
|
37
|
+
config = ModelRuntimeSchedulerConfig.default()
|
|
38
|
+
event_bus = EventBusKafka.default()
|
|
39
|
+
await event_bus.start()
|
|
40
|
+
|
|
41
|
+
scheduler = RuntimeScheduler(config=config, event_bus=event_bus)
|
|
42
|
+
await scheduler.start()
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
# Scheduler runs, emitting ticks at configured interval
|
|
46
|
+
while scheduler.is_running:
|
|
47
|
+
await asyncio.sleep(1.0)
|
|
48
|
+
finally:
|
|
49
|
+
await scheduler.stop()
|
|
50
|
+
metrics = scheduler.get_metrics()
|
|
51
|
+
print(f"Emitted {metrics.ticks_emitted} ticks")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Related:
|
|
55
|
+
- OMN-953: RuntimeTick scheduler implementation
|
|
56
|
+
- ModelRuntimeTick: The event model emitted by the scheduler
|
|
57
|
+
- ModelRuntimeSchedulerConfig: Configuration model
|
|
58
|
+
- ModelRuntimeSchedulerMetrics: Metrics model for observability
|
|
59
|
+
- ProtocolRuntimeScheduler: Protocol interface
|
|
60
|
+
|
|
61
|
+
.. versionadded:: 0.4.0
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
from __future__ import annotations
|
|
65
|
+
|
|
66
|
+
import asyncio
|
|
67
|
+
import logging
|
|
68
|
+
import random
|
|
69
|
+
import time
|
|
70
|
+
from datetime import UTC, datetime
|
|
71
|
+
from uuid import UUID, uuid4
|
|
72
|
+
|
|
73
|
+
import redis.asyncio as redis
|
|
74
|
+
from redis.asyncio import Redis
|
|
75
|
+
from redis.exceptions import ConnectionError as RedisConnectionError
|
|
76
|
+
from redis.exceptions import RedisError
|
|
77
|
+
from redis.exceptions import TimeoutError as RedisTimeoutError
|
|
78
|
+
|
|
79
|
+
from omnibase_infra.enums import EnumInfraTransportType
|
|
80
|
+
from omnibase_infra.errors import (
|
|
81
|
+
InfraConnectionError,
|
|
82
|
+
InfraTimeoutError,
|
|
83
|
+
InfraUnavailableError,
|
|
84
|
+
ModelInfraErrorContext,
|
|
85
|
+
ModelTimeoutErrorContext,
|
|
86
|
+
ProtocolConfigurationError,
|
|
87
|
+
)
|
|
88
|
+
from omnibase_infra.event_bus.event_bus_kafka import EventBusKafka
|
|
89
|
+
from omnibase_infra.event_bus.models import ModelEventHeaders
|
|
90
|
+
from omnibase_infra.mixins import MixinAsyncCircuitBreaker
|
|
91
|
+
from omnibase_infra.runtime.enums import EnumSchedulerStatus
|
|
92
|
+
from omnibase_infra.runtime.models import (
|
|
93
|
+
ModelRuntimeSchedulerConfig,
|
|
94
|
+
ModelRuntimeSchedulerMetrics,
|
|
95
|
+
ModelRuntimeTick,
|
|
96
|
+
)
|
|
97
|
+
from omnibase_infra.utils.util_error_sanitization import sanitize_error_string
|
|
98
|
+
|
|
99
|
+
logger = logging.getLogger(__name__)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class RuntimeScheduler(MixinAsyncCircuitBreaker):
|
|
103
|
+
"""Runtime scheduler that emits RuntimeTick events at configured intervals.
|
|
104
|
+
|
|
105
|
+
The scheduler is the single source of truth for "now" across all orchestrators.
|
|
106
|
+
It emits RuntimeTick events that orchestrators subscribe to for timeout decisions.
|
|
107
|
+
|
|
108
|
+
This is an INFRASTRUCTURE concern - orchestrators derive timeout decisions
|
|
109
|
+
(DOMAIN concern) from the ticks.
|
|
110
|
+
|
|
111
|
+
Attributes:
|
|
112
|
+
scheduler_id: Unique identifier for this scheduler instance.
|
|
113
|
+
is_running: Whether the scheduler is currently running.
|
|
114
|
+
current_sequence_number: Current sequence number for restart-safety.
|
|
115
|
+
|
|
116
|
+
Concurrency Safety:
|
|
117
|
+
This scheduler is coroutine-safe using asyncio primitives:
|
|
118
|
+
- Circuit breaker operations protected by `_circuit_breaker_lock` (asyncio.Lock)
|
|
119
|
+
- State variables protected by `_state_lock` (asyncio.Lock)
|
|
120
|
+
- Shutdown signaling via `asyncio.Event`
|
|
121
|
+
Note: This is coroutine-safe, not thread-safe.
|
|
122
|
+
|
|
123
|
+
Restart Safety:
|
|
124
|
+
The `current_sequence_number` property returns a monotonically increasing
|
|
125
|
+
value that helps orchestrators detect scheduler restarts. If the sequence
|
|
126
|
+
number decreases or resets, orchestrators know a restart occurred.
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
```python
|
|
130
|
+
config = ModelRuntimeSchedulerConfig.default()
|
|
131
|
+
event_bus = EventBusKafka.default()
|
|
132
|
+
await event_bus.start()
|
|
133
|
+
|
|
134
|
+
scheduler = RuntimeScheduler(config=config, event_bus=event_bus)
|
|
135
|
+
await scheduler.start()
|
|
136
|
+
|
|
137
|
+
# Scheduler runs in background
|
|
138
|
+
await asyncio.sleep(10.0)
|
|
139
|
+
|
|
140
|
+
await scheduler.stop()
|
|
141
|
+
print(f"Emitted {scheduler.get_metrics().ticks_emitted} ticks")
|
|
142
|
+
```
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
config: ModelRuntimeSchedulerConfig,
|
|
148
|
+
event_bus: EventBusKafka,
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Initialize the RuntimeScheduler.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
config: Configuration model containing all scheduler settings.
|
|
154
|
+
event_bus: EventBusKafka instance for publishing tick events.
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
ProtocolConfigurationError: If config or event_bus is None.
|
|
158
|
+
"""
|
|
159
|
+
context = ModelInfraErrorContext(
|
|
160
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
161
|
+
operation="scheduler_init",
|
|
162
|
+
)
|
|
163
|
+
if config is None:
|
|
164
|
+
raise ProtocolConfigurationError("config cannot be None", context=context)
|
|
165
|
+
if event_bus is None:
|
|
166
|
+
raise ProtocolConfigurationError(
|
|
167
|
+
"event_bus cannot be None", context=context
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Store configuration
|
|
171
|
+
self._config = config
|
|
172
|
+
self._event_bus = event_bus
|
|
173
|
+
|
|
174
|
+
# Initialize circuit breaker mixin
|
|
175
|
+
self._init_circuit_breaker(
|
|
176
|
+
threshold=config.circuit_breaker_threshold,
|
|
177
|
+
reset_timeout=config.circuit_breaker_reset_timeout_seconds,
|
|
178
|
+
service_name=f"runtime-scheduler.{config.scheduler_id}",
|
|
179
|
+
transport_type=EnumInfraTransportType.KAFKA,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# State variables (protected by _state_lock)
|
|
183
|
+
self._status = EnumSchedulerStatus.STOPPED
|
|
184
|
+
self._sequence_number: int = 0
|
|
185
|
+
self._started_at: datetime | None = None
|
|
186
|
+
self._last_tick_at: datetime | None = None
|
|
187
|
+
self._last_tick_duration_ms: float = 0.0
|
|
188
|
+
self._total_tick_duration_ms: float = 0.0
|
|
189
|
+
self._max_tick_duration_ms: float = 0.0
|
|
190
|
+
self._ticks_emitted: int = 0
|
|
191
|
+
self._ticks_failed: int = 0
|
|
192
|
+
self._consecutive_failures: int = 0
|
|
193
|
+
self._last_persisted_sequence: int = 0
|
|
194
|
+
|
|
195
|
+
# Synchronization primitives
|
|
196
|
+
self._state_lock = asyncio.Lock()
|
|
197
|
+
self._shutdown_event = asyncio.Event()
|
|
198
|
+
self._tick_task: asyncio.Task[None] | None = None
|
|
199
|
+
|
|
200
|
+
# Valkey client for sequence number persistence
|
|
201
|
+
# Created lazily on first use to avoid blocking __init__
|
|
202
|
+
self._valkey_client: Redis | None = None
|
|
203
|
+
self._valkey_available: bool = True # Assume available until proven otherwise
|
|
204
|
+
|
|
205
|
+
# =========================================================================
|
|
206
|
+
# Properties (ProtocolRuntimeScheduler interface)
|
|
207
|
+
# =========================================================================
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def scheduler_id(self) -> str:
|
|
211
|
+
"""Return the unique identifier for this scheduler instance.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Unique scheduler identifier from configuration.
|
|
215
|
+
"""
|
|
216
|
+
return self._config.scheduler_id
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def is_running(self) -> bool:
|
|
220
|
+
"""Return whether the scheduler is currently running.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
True if running and emitting ticks, False otherwise.
|
|
224
|
+
"""
|
|
225
|
+
return self._status == EnumSchedulerStatus.RUNNING
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def current_sequence_number(self) -> int:
|
|
229
|
+
"""Return the current sequence number for restart-safety tracking.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Current sequence number (non-negative).
|
|
233
|
+
"""
|
|
234
|
+
return self._sequence_number
|
|
235
|
+
|
|
236
|
+
# =========================================================================
|
|
237
|
+
# Lifecycle Methods
|
|
238
|
+
# =========================================================================
|
|
239
|
+
|
|
240
|
+
async def start(self) -> None:
|
|
241
|
+
"""Start the scheduler and begin emitting ticks.
|
|
242
|
+
|
|
243
|
+
Initializes the scheduler and starts the tick emission loop.
|
|
244
|
+
After calling start(), the scheduler will emit RuntimeTick events
|
|
245
|
+
at its configured interval.
|
|
246
|
+
|
|
247
|
+
Idempotency:
|
|
248
|
+
Calling start() on an already-running scheduler is a no-op
|
|
249
|
+
with a warning log.
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
InfraUnavailableError: If the circuit breaker is open.
|
|
253
|
+
"""
|
|
254
|
+
async with self._state_lock:
|
|
255
|
+
if self._status.is_active():
|
|
256
|
+
logger.warning(
|
|
257
|
+
"Scheduler already active, ignoring start()",
|
|
258
|
+
extra={
|
|
259
|
+
"scheduler_id": self.scheduler_id,
|
|
260
|
+
"status": str(self._status),
|
|
261
|
+
},
|
|
262
|
+
)
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
# Transition to STARTING
|
|
266
|
+
self._status = EnumSchedulerStatus.STARTING
|
|
267
|
+
|
|
268
|
+
# Check circuit breaker before starting (outside state lock)
|
|
269
|
+
async with self._circuit_breaker_lock:
|
|
270
|
+
try:
|
|
271
|
+
await self._check_circuit_breaker(
|
|
272
|
+
operation="start",
|
|
273
|
+
correlation_id=uuid4(),
|
|
274
|
+
)
|
|
275
|
+
except InfraUnavailableError:
|
|
276
|
+
async with self._state_lock:
|
|
277
|
+
self._status = EnumSchedulerStatus.ERROR
|
|
278
|
+
raise
|
|
279
|
+
|
|
280
|
+
# Load persisted sequence number (if enabled)
|
|
281
|
+
await self._load_sequence_number()
|
|
282
|
+
|
|
283
|
+
# Start tick loop
|
|
284
|
+
async with self._state_lock:
|
|
285
|
+
self._shutdown_event.clear()
|
|
286
|
+
self._started_at = datetime.now(UTC)
|
|
287
|
+
self._status = EnumSchedulerStatus.RUNNING
|
|
288
|
+
self._tick_task = asyncio.create_task(self._tick_loop())
|
|
289
|
+
|
|
290
|
+
logger.info(
|
|
291
|
+
"Scheduler started",
|
|
292
|
+
extra={
|
|
293
|
+
"scheduler_id": self.scheduler_id,
|
|
294
|
+
"tick_interval_ms": self._config.tick_interval_ms,
|
|
295
|
+
"max_jitter_ms": self._config.max_tick_jitter_ms,
|
|
296
|
+
},
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
async def stop(self) -> None:
|
|
300
|
+
"""Stop the scheduler gracefully.
|
|
301
|
+
|
|
302
|
+
Performs a graceful shutdown of the scheduler:
|
|
303
|
+
- Signals the tick loop to stop
|
|
304
|
+
- Waits for any in-flight tick emission to complete
|
|
305
|
+
- Sets status to STOPPED
|
|
306
|
+
- Persists sequence number if enabled
|
|
307
|
+
|
|
308
|
+
Idempotency:
|
|
309
|
+
Calling stop() on an already-stopped scheduler is a no-op.
|
|
310
|
+
"""
|
|
311
|
+
async with self._state_lock:
|
|
312
|
+
if self._status.is_terminal():
|
|
313
|
+
logger.debug(
|
|
314
|
+
"Scheduler already stopped, ignoring stop()",
|
|
315
|
+
extra={
|
|
316
|
+
"scheduler_id": self.scheduler_id,
|
|
317
|
+
"status": str(self._status),
|
|
318
|
+
},
|
|
319
|
+
)
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
# Transition to STOPPING
|
|
323
|
+
self._status = EnumSchedulerStatus.STOPPING
|
|
324
|
+
|
|
325
|
+
# Signal shutdown to tick loop
|
|
326
|
+
self._shutdown_event.set()
|
|
327
|
+
|
|
328
|
+
# Wait for tick task to complete
|
|
329
|
+
if self._tick_task is not None:
|
|
330
|
+
try:
|
|
331
|
+
await asyncio.wait_for(self._tick_task, timeout=5.0)
|
|
332
|
+
except TimeoutError:
|
|
333
|
+
logger.warning(
|
|
334
|
+
"Tick task did not complete within timeout, cancelling",
|
|
335
|
+
extra={"scheduler_id": self.scheduler_id},
|
|
336
|
+
)
|
|
337
|
+
self._tick_task.cancel()
|
|
338
|
+
try:
|
|
339
|
+
await self._tick_task
|
|
340
|
+
except asyncio.CancelledError:
|
|
341
|
+
pass
|
|
342
|
+
except asyncio.CancelledError:
|
|
343
|
+
pass
|
|
344
|
+
self._tick_task = None
|
|
345
|
+
|
|
346
|
+
# Persist sequence number (if enabled)
|
|
347
|
+
await self._persist_sequence_number()
|
|
348
|
+
|
|
349
|
+
# Finalize state
|
|
350
|
+
async with self._state_lock:
|
|
351
|
+
self._status = EnumSchedulerStatus.STOPPED
|
|
352
|
+
|
|
353
|
+
logger.info(
|
|
354
|
+
"Scheduler stopped",
|
|
355
|
+
extra={
|
|
356
|
+
"scheduler_id": self.scheduler_id,
|
|
357
|
+
"ticks_emitted": self._ticks_emitted,
|
|
358
|
+
"ticks_failed": self._ticks_failed,
|
|
359
|
+
"final_sequence": self._sequence_number,
|
|
360
|
+
},
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# Always close Valkey client if it exists (idempotent)
|
|
364
|
+
await self._close_valkey_client()
|
|
365
|
+
|
|
366
|
+
# =========================================================================
|
|
367
|
+
# Core Methods
|
|
368
|
+
# =========================================================================
|
|
369
|
+
|
|
370
|
+
async def emit_tick(self, now: datetime | None = None) -> None:
|
|
371
|
+
"""Emit a single tick immediately.
|
|
372
|
+
|
|
373
|
+
This method emits a RuntimeTick event outside the normal interval loop.
|
|
374
|
+
It is primarily used for testing and manual intervention.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
now: Optional override for current time. If None, uses actual
|
|
378
|
+
current time (datetime.now(timezone.utc)).
|
|
379
|
+
|
|
380
|
+
Raises:
|
|
381
|
+
InfraUnavailableError: When the circuit breaker is open.
|
|
382
|
+
InfraTimeoutError: When tick emission to Kafka times out.
|
|
383
|
+
InfraConnectionError: When tick emission fails due to connection issues.
|
|
384
|
+
|
|
385
|
+
Concurrency Safety:
|
|
386
|
+
This method is safe for concurrent coroutine calls. State modifications
|
|
387
|
+
are protected by `_state_lock` (asyncio.Lock).
|
|
388
|
+
"""
|
|
389
|
+
tick_time = now or datetime.now(UTC)
|
|
390
|
+
correlation_id = uuid4()
|
|
391
|
+
tick_id = uuid4()
|
|
392
|
+
start_time = time.monotonic()
|
|
393
|
+
|
|
394
|
+
# Increment sequence number (protected)
|
|
395
|
+
async with self._state_lock:
|
|
396
|
+
self._sequence_number += 1
|
|
397
|
+
current_sequence = self._sequence_number
|
|
398
|
+
|
|
399
|
+
# Create tick event
|
|
400
|
+
tick = ModelRuntimeTick(
|
|
401
|
+
now=tick_time,
|
|
402
|
+
tick_id=tick_id,
|
|
403
|
+
sequence_number=current_sequence,
|
|
404
|
+
scheduled_at=tick_time,
|
|
405
|
+
correlation_id=correlation_id,
|
|
406
|
+
scheduler_id=self.scheduler_id,
|
|
407
|
+
tick_interval_ms=self._config.tick_interval_ms,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# Check circuit breaker before publishing
|
|
411
|
+
async with self._circuit_breaker_lock:
|
|
412
|
+
try:
|
|
413
|
+
await self._check_circuit_breaker(
|
|
414
|
+
operation="emit_tick",
|
|
415
|
+
correlation_id=correlation_id,
|
|
416
|
+
)
|
|
417
|
+
except InfraUnavailableError:
|
|
418
|
+
await self._record_tick_failure(correlation_id)
|
|
419
|
+
raise
|
|
420
|
+
|
|
421
|
+
# Create headers for the event
|
|
422
|
+
headers = ModelEventHeaders(
|
|
423
|
+
correlation_id=correlation_id,
|
|
424
|
+
message_id=tick_id,
|
|
425
|
+
timestamp=tick_time,
|
|
426
|
+
source=f"runtime-scheduler.{self.scheduler_id}",
|
|
427
|
+
event_type="runtime.tick.v1",
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# Serialize tick to JSON bytes
|
|
431
|
+
tick_bytes = tick.model_dump_json().encode("utf-8")
|
|
432
|
+
|
|
433
|
+
# Prepare error context for ONEX error types
|
|
434
|
+
ctx = ModelInfraErrorContext(
|
|
435
|
+
transport_type=EnumInfraTransportType.KAFKA,
|
|
436
|
+
operation="emit_tick",
|
|
437
|
+
target_name=self._config.tick_topic,
|
|
438
|
+
correlation_id=correlation_id,
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
try:
|
|
442
|
+
# Publish tick event
|
|
443
|
+
await self._event_bus.publish(
|
|
444
|
+
topic=self._config.tick_topic,
|
|
445
|
+
key=self.scheduler_id.encode("utf-8"),
|
|
446
|
+
value=tick_bytes,
|
|
447
|
+
headers=headers,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
# Record success
|
|
451
|
+
async with self._circuit_breaker_lock:
|
|
452
|
+
await self._reset_circuit_breaker()
|
|
453
|
+
|
|
454
|
+
await self._record_tick_success(start_time, tick_time)
|
|
455
|
+
|
|
456
|
+
logger.debug(
|
|
457
|
+
"Tick emitted",
|
|
458
|
+
extra={
|
|
459
|
+
"scheduler_id": self.scheduler_id,
|
|
460
|
+
"sequence_number": current_sequence,
|
|
461
|
+
"tick_id": str(tick_id),
|
|
462
|
+
"correlation_id": str(correlation_id),
|
|
463
|
+
},
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
except TimeoutError as e:
|
|
467
|
+
# Record failure for timeout
|
|
468
|
+
async with self._circuit_breaker_lock:
|
|
469
|
+
await self._record_circuit_failure(
|
|
470
|
+
operation="emit_tick",
|
|
471
|
+
correlation_id=correlation_id,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
await self._record_tick_failure(correlation_id)
|
|
475
|
+
|
|
476
|
+
timeout_ctx = ModelTimeoutErrorContext(
|
|
477
|
+
transport_type=EnumInfraTransportType.KAFKA,
|
|
478
|
+
operation="emit_tick",
|
|
479
|
+
target_name=self._config.tick_topic,
|
|
480
|
+
correlation_id=correlation_id,
|
|
481
|
+
# timeout_seconds omitted - Kafka timeout is event bus level, not available here
|
|
482
|
+
)
|
|
483
|
+
raise InfraTimeoutError(
|
|
484
|
+
f"Timeout emitting tick to topic {self._config.tick_topic}",
|
|
485
|
+
context=timeout_ctx,
|
|
486
|
+
) from e
|
|
487
|
+
|
|
488
|
+
except Exception as e:
|
|
489
|
+
# Record failure for other errors
|
|
490
|
+
async with self._circuit_breaker_lock:
|
|
491
|
+
await self._record_circuit_failure(
|
|
492
|
+
operation="emit_tick",
|
|
493
|
+
correlation_id=correlation_id,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
await self._record_tick_failure(correlation_id)
|
|
497
|
+
|
|
498
|
+
raise InfraConnectionError(
|
|
499
|
+
f"Failed to emit tick to topic {self._config.tick_topic}",
|
|
500
|
+
context=ctx,
|
|
501
|
+
) from e
|
|
502
|
+
|
|
503
|
+
async def get_metrics(self) -> ModelRuntimeSchedulerMetrics:
|
|
504
|
+
"""Get current scheduler metrics.
|
|
505
|
+
|
|
506
|
+
Returns a snapshot of the scheduler's operational metrics for
|
|
507
|
+
observability and monitoring purposes.
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
ModelRuntimeSchedulerMetrics: Current metrics snapshot.
|
|
511
|
+
|
|
512
|
+
Concurrency Safety:
|
|
513
|
+
This method acquires both ``_circuit_breaker_lock`` and ``_state_lock``
|
|
514
|
+
(asyncio.Lock instances) to ensure a consistent snapshot of all metrics.
|
|
515
|
+
Circuit breaker state is read under its own lock first (consistent with
|
|
516
|
+
modification patterns), then scheduler state is read under the state lock.
|
|
517
|
+
The returned Pydantic model is immutable and safe to use after locks are
|
|
518
|
+
released. Note: This is coroutine-safe, not thread-safe.
|
|
519
|
+
|
|
520
|
+
Example:
|
|
521
|
+
>>> scheduler = RuntimeScheduler(config=config, event_bus=event_bus)
|
|
522
|
+
>>> await scheduler.start()
|
|
523
|
+
>>> # After some ticks have been emitted...
|
|
524
|
+
>>> metrics = await scheduler.get_metrics()
|
|
525
|
+
>>> print(f"Scheduler: {metrics.scheduler_id}")
|
|
526
|
+
>>> print(f"Status: {metrics.status}")
|
|
527
|
+
>>> print(f"Ticks emitted: {metrics.ticks_emitted}")
|
|
528
|
+
>>> print(f"Ticks failed: {metrics.ticks_failed}")
|
|
529
|
+
>>> print(f"Success rate: {metrics.tick_success_rate()}")
|
|
530
|
+
>>> print(f"Average tick duration: {metrics.average_tick_duration_ms}ms")
|
|
531
|
+
>>> print(f"Circuit breaker open: {metrics.circuit_breaker_open}")
|
|
532
|
+
>>> print(f"Consecutive failures: {metrics.consecutive_failures}")
|
|
533
|
+
>>> print(f"Uptime: {metrics.total_uptime_seconds}s")
|
|
534
|
+
>>> if metrics.is_healthy():
|
|
535
|
+
... print("Scheduler is healthy")
|
|
536
|
+
"""
|
|
537
|
+
# First, capture circuit breaker state under its own lock
|
|
538
|
+
# This ensures consistency with how _circuit_breaker_open is modified
|
|
539
|
+
async with self._circuit_breaker_lock:
|
|
540
|
+
circuit_breaker_open = self._circuit_breaker_open
|
|
541
|
+
|
|
542
|
+
# Then capture scheduler state under state lock
|
|
543
|
+
async with self._state_lock:
|
|
544
|
+
# Calculate uptime
|
|
545
|
+
uptime_seconds = 0.0
|
|
546
|
+
if self._started_at is not None:
|
|
547
|
+
uptime_seconds = (datetime.now(UTC) - self._started_at).total_seconds()
|
|
548
|
+
|
|
549
|
+
# Calculate average tick duration
|
|
550
|
+
average_tick_duration_ms = 0.0
|
|
551
|
+
if self._ticks_emitted > 0:
|
|
552
|
+
average_tick_duration_ms = (
|
|
553
|
+
self._total_tick_duration_ms / self._ticks_emitted
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
return ModelRuntimeSchedulerMetrics(
|
|
557
|
+
scheduler_id=self.scheduler_id,
|
|
558
|
+
status=self._status,
|
|
559
|
+
ticks_emitted=self._ticks_emitted,
|
|
560
|
+
ticks_failed=self._ticks_failed,
|
|
561
|
+
last_tick_at=self._last_tick_at,
|
|
562
|
+
last_tick_duration_ms=self._last_tick_duration_ms,
|
|
563
|
+
average_tick_duration_ms=average_tick_duration_ms,
|
|
564
|
+
max_tick_duration_ms=self._max_tick_duration_ms,
|
|
565
|
+
current_sequence_number=self._sequence_number,
|
|
566
|
+
last_persisted_sequence=self._last_persisted_sequence,
|
|
567
|
+
circuit_breaker_open=circuit_breaker_open,
|
|
568
|
+
consecutive_failures=self._consecutive_failures,
|
|
569
|
+
started_at=self._started_at,
|
|
570
|
+
total_uptime_seconds=uptime_seconds,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
# =========================================================================
|
|
574
|
+
# Internal Methods
|
|
575
|
+
# =========================================================================
|
|
576
|
+
|
|
577
|
+
async def _tick_loop(self) -> None:
|
|
578
|
+
"""Main tick loop that emits ticks at configured intervals.
|
|
579
|
+
|
|
580
|
+
This method runs as a background task and continuously emits ticks
|
|
581
|
+
until the shutdown event is set. It handles jitter and graceful
|
|
582
|
+
shutdown.
|
|
583
|
+
"""
|
|
584
|
+
logger.debug(
|
|
585
|
+
"Tick loop started",
|
|
586
|
+
extra={
|
|
587
|
+
"scheduler_id": self.scheduler_id,
|
|
588
|
+
"tick_interval_ms": self._config.tick_interval_ms,
|
|
589
|
+
},
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
try:
|
|
593
|
+
while not self._shutdown_event.is_set():
|
|
594
|
+
# Calculate interval with jitter
|
|
595
|
+
interval_seconds = self._config.tick_interval_ms / 1000.0
|
|
596
|
+
if self._config.max_tick_jitter_ms > 0:
|
|
597
|
+
jitter_ms = random.randint(0, self._config.max_tick_jitter_ms)
|
|
598
|
+
interval_seconds += jitter_ms / 1000.0
|
|
599
|
+
|
|
600
|
+
# Wait for interval or shutdown
|
|
601
|
+
try:
|
|
602
|
+
await asyncio.wait_for(
|
|
603
|
+
self._shutdown_event.wait(),
|
|
604
|
+
timeout=interval_seconds,
|
|
605
|
+
)
|
|
606
|
+
# Shutdown event was set - exit loop
|
|
607
|
+
break
|
|
608
|
+
except TimeoutError:
|
|
609
|
+
# Timeout expired - time to emit tick
|
|
610
|
+
pass
|
|
611
|
+
|
|
612
|
+
# Check if we should still be running
|
|
613
|
+
if self._shutdown_event.is_set():
|
|
614
|
+
break
|
|
615
|
+
|
|
616
|
+
# Emit tick (errors are logged but don't crash the loop)
|
|
617
|
+
try:
|
|
618
|
+
await self.emit_tick()
|
|
619
|
+
except Exception as e:
|
|
620
|
+
# Log but don't crash the loop
|
|
621
|
+
logger.exception(
|
|
622
|
+
"Error in tick loop, continuing",
|
|
623
|
+
extra={
|
|
624
|
+
"scheduler_id": self.scheduler_id,
|
|
625
|
+
"error": str(e),
|
|
626
|
+
"error_type": type(e).__name__,
|
|
627
|
+
},
|
|
628
|
+
)
|
|
629
|
+
# Increment consecutive failures for monitoring
|
|
630
|
+
async with self._state_lock:
|
|
631
|
+
self._consecutive_failures += 1
|
|
632
|
+
|
|
633
|
+
except asyncio.CancelledError:
|
|
634
|
+
logger.info(
|
|
635
|
+
"Tick loop cancelled",
|
|
636
|
+
extra={"scheduler_id": self.scheduler_id},
|
|
637
|
+
)
|
|
638
|
+
raise
|
|
639
|
+
|
|
640
|
+
except Exception as e:
|
|
641
|
+
logger.exception(
|
|
642
|
+
"Unexpected error in tick loop",
|
|
643
|
+
extra={
|
|
644
|
+
"scheduler_id": self.scheduler_id,
|
|
645
|
+
"error": str(e),
|
|
646
|
+
},
|
|
647
|
+
)
|
|
648
|
+
async with self._state_lock:
|
|
649
|
+
self._status = EnumSchedulerStatus.ERROR
|
|
650
|
+
|
|
651
|
+
finally:
|
|
652
|
+
logger.debug(
|
|
653
|
+
"Tick loop exiting",
|
|
654
|
+
extra={
|
|
655
|
+
"scheduler_id": self.scheduler_id,
|
|
656
|
+
"ticks_emitted": self._ticks_emitted,
|
|
657
|
+
},
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
async def _record_tick_success(
|
|
661
|
+
self,
|
|
662
|
+
start_time: float,
|
|
663
|
+
tick_time: datetime,
|
|
664
|
+
) -> None:
|
|
665
|
+
"""Record a successful tick emission.
|
|
666
|
+
|
|
667
|
+
Args:
|
|
668
|
+
start_time: Monotonic time when tick started.
|
|
669
|
+
tick_time: The timestamp used in the tick.
|
|
670
|
+
"""
|
|
671
|
+
duration_ms = (time.monotonic() - start_time) * 1000.0
|
|
672
|
+
|
|
673
|
+
async with self._state_lock:
|
|
674
|
+
self._ticks_emitted += 1
|
|
675
|
+
self._last_tick_at = tick_time
|
|
676
|
+
self._last_tick_duration_ms = duration_ms
|
|
677
|
+
self._total_tick_duration_ms += duration_ms
|
|
678
|
+
self._max_tick_duration_ms = max(duration_ms, self._max_tick_duration_ms)
|
|
679
|
+
self._consecutive_failures = 0
|
|
680
|
+
|
|
681
|
+
async def _record_tick_failure(self, correlation_id: UUID) -> None:
|
|
682
|
+
"""Record a failed tick emission.
|
|
683
|
+
|
|
684
|
+
Args:
|
|
685
|
+
correlation_id: Correlation ID for tracing.
|
|
686
|
+
"""
|
|
687
|
+
async with self._state_lock:
|
|
688
|
+
self._ticks_failed += 1
|
|
689
|
+
self._consecutive_failures += 1
|
|
690
|
+
|
|
691
|
+
# =========================================================================
|
|
692
|
+
# Valkey Persistence (for restart-safety)
|
|
693
|
+
# =========================================================================
|
|
694
|
+
|
|
695
|
+
async def _get_valkey_client(self) -> Redis | None:
|
|
696
|
+
"""Get or create the Valkey client for sequence number persistence.
|
|
697
|
+
|
|
698
|
+
This method lazily creates a Valkey client on first use. If the client
|
|
699
|
+
has been marked as unavailable (due to connection failures), it returns
|
|
700
|
+
None without attempting to reconnect.
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
Redis client instance if available, None if unavailable or disabled.
|
|
704
|
+
|
|
705
|
+
Note:
|
|
706
|
+
The client is created with the configured host, port, password, and
|
|
707
|
+
timeout settings. Connection failures are handled gracefully with
|
|
708
|
+
retry logic.
|
|
709
|
+
"""
|
|
710
|
+
# Skip if persistence is disabled or Valkey was marked unavailable
|
|
711
|
+
if not self._config.persist_sequence_number:
|
|
712
|
+
return None
|
|
713
|
+
|
|
714
|
+
if not self._valkey_available:
|
|
715
|
+
return None
|
|
716
|
+
|
|
717
|
+
# Return existing client if already created
|
|
718
|
+
if self._valkey_client is not None:
|
|
719
|
+
return self._valkey_client
|
|
720
|
+
|
|
721
|
+
# Create new client with retry logic
|
|
722
|
+
correlation_id = uuid4()
|
|
723
|
+
retries = self._config.valkey_connection_retries
|
|
724
|
+
|
|
725
|
+
for attempt in range(retries + 1):
|
|
726
|
+
try:
|
|
727
|
+
self._valkey_client = redis.Redis(
|
|
728
|
+
host=self._config.valkey_host,
|
|
729
|
+
port=self._config.valkey_port,
|
|
730
|
+
password=self._config.valkey_password,
|
|
731
|
+
socket_timeout=self._config.valkey_timeout_seconds,
|
|
732
|
+
socket_connect_timeout=self._config.valkey_timeout_seconds,
|
|
733
|
+
decode_responses=True,
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
# Test connection with a ping
|
|
737
|
+
await asyncio.wait_for(
|
|
738
|
+
self._valkey_client.ping(),
|
|
739
|
+
timeout=self._config.valkey_timeout_seconds,
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
logger.info(
|
|
743
|
+
"Valkey client connected for sequence persistence",
|
|
744
|
+
extra={
|
|
745
|
+
"scheduler_id": self.scheduler_id,
|
|
746
|
+
"valkey_host": self._config.valkey_host,
|
|
747
|
+
"valkey_port": self._config.valkey_port,
|
|
748
|
+
"correlation_id": str(correlation_id),
|
|
749
|
+
},
|
|
750
|
+
)
|
|
751
|
+
return self._valkey_client
|
|
752
|
+
|
|
753
|
+
except (RedisConnectionError, RedisTimeoutError, TimeoutError) as e:
|
|
754
|
+
if attempt < retries:
|
|
755
|
+
# Calculate exponential backoff delay: 1s, 2s, 4s, 8s... max 60s
|
|
756
|
+
backoff_delay = min(1.0 * (2**attempt), 60.0)
|
|
757
|
+
|
|
758
|
+
logger.warning(
|
|
759
|
+
"Valkey connection attempt %d/%d failed, retrying in %.1fs",
|
|
760
|
+
attempt + 1,
|
|
761
|
+
retries + 1,
|
|
762
|
+
backoff_delay,
|
|
763
|
+
extra={
|
|
764
|
+
"scheduler_id": self.scheduler_id,
|
|
765
|
+
"valkey_host": self._config.valkey_host,
|
|
766
|
+
"valkey_port": self._config.valkey_port,
|
|
767
|
+
# SECURITY: Sanitize error to prevent credential exposure
|
|
768
|
+
"error": sanitize_error_string(str(e)),
|
|
769
|
+
"error_type": type(e).__name__,
|
|
770
|
+
"correlation_id": str(correlation_id),
|
|
771
|
+
"backoff_delay_seconds": backoff_delay,
|
|
772
|
+
},
|
|
773
|
+
)
|
|
774
|
+
await asyncio.sleep(backoff_delay)
|
|
775
|
+
else:
|
|
776
|
+
# All retries exhausted - mark as unavailable
|
|
777
|
+
self._valkey_available = False
|
|
778
|
+
self._valkey_client = None
|
|
779
|
+
|
|
780
|
+
logger.warning(
|
|
781
|
+
"Valkey unavailable after %d attempts, using in-memory",
|
|
782
|
+
retries + 1,
|
|
783
|
+
extra={
|
|
784
|
+
"scheduler_id": self.scheduler_id,
|
|
785
|
+
"valkey_host": self._config.valkey_host,
|
|
786
|
+
"valkey_port": self._config.valkey_port,
|
|
787
|
+
# SECURITY: Sanitize error to prevent credential exposure
|
|
788
|
+
"error": sanitize_error_string(str(e)),
|
|
789
|
+
"error_type": type(e).__name__,
|
|
790
|
+
"correlation_id": str(correlation_id),
|
|
791
|
+
},
|
|
792
|
+
)
|
|
793
|
+
return None
|
|
794
|
+
|
|
795
|
+
except RedisError as e:
|
|
796
|
+
# Unexpected Redis error - mark as unavailable
|
|
797
|
+
self._valkey_available = False
|
|
798
|
+
self._valkey_client = None
|
|
799
|
+
|
|
800
|
+
logger.warning(
|
|
801
|
+
"Valkey error during connection, using in-memory fallback",
|
|
802
|
+
extra={
|
|
803
|
+
"scheduler_id": self.scheduler_id,
|
|
804
|
+
"valkey_host": self._config.valkey_host,
|
|
805
|
+
"valkey_port": self._config.valkey_port,
|
|
806
|
+
# SECURITY: Sanitize error to prevent credential exposure
|
|
807
|
+
"error": sanitize_error_string(str(e)),
|
|
808
|
+
"error_type": type(e).__name__,
|
|
809
|
+
"correlation_id": str(correlation_id),
|
|
810
|
+
},
|
|
811
|
+
)
|
|
812
|
+
return None
|
|
813
|
+
|
|
814
|
+
return None
|
|
815
|
+
|
|
816
|
+
async def _close_valkey_client(self) -> None:
|
|
817
|
+
"""Close the Valkey client connection.
|
|
818
|
+
|
|
819
|
+
This method gracefully closes the Valkey client connection if one exists.
|
|
820
|
+
It is called during scheduler shutdown to ensure proper resource cleanup.
|
|
821
|
+
|
|
822
|
+
Idempotency:
|
|
823
|
+
This method is safe to call multiple times. It atomically swaps the
|
|
824
|
+
client reference to None before attempting close, preventing double-close
|
|
825
|
+
scenarios even with concurrent coroutine access.
|
|
826
|
+
"""
|
|
827
|
+
# Atomically swap client reference to None to prevent double-close
|
|
828
|
+
client = self._valkey_client
|
|
829
|
+
self._valkey_client = None
|
|
830
|
+
|
|
831
|
+
if client is not None:
|
|
832
|
+
try:
|
|
833
|
+
await client.aclose()
|
|
834
|
+
logger.debug(
|
|
835
|
+
"Valkey client closed",
|
|
836
|
+
extra={"scheduler_id": self.scheduler_id},
|
|
837
|
+
)
|
|
838
|
+
except Exception as e:
|
|
839
|
+
logger.warning(
|
|
840
|
+
"Error closing Valkey client",
|
|
841
|
+
extra={
|
|
842
|
+
"scheduler_id": self.scheduler_id,
|
|
843
|
+
# SECURITY: Sanitize error to prevent credential exposure
|
|
844
|
+
"error": sanitize_error_string(str(e)),
|
|
845
|
+
"error_type": type(e).__name__,
|
|
846
|
+
},
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
async def _load_sequence_number(self) -> None:
|
|
850
|
+
"""Load persisted sequence number from Valkey for restart-safety.
|
|
851
|
+
|
|
852
|
+
Attempts to read the sequence number from Valkey using the configured
|
|
853
|
+
`sequence_number_key`. If Valkey is unavailable or the key doesn't exist,
|
|
854
|
+
gracefully falls back to starting from 0.
|
|
855
|
+
|
|
856
|
+
Graceful Fallback:
|
|
857
|
+
- If Valkey is unavailable: Logs warning and starts from 0
|
|
858
|
+
- If key doesn't exist: Logs debug and starts from 0
|
|
859
|
+
- If value is not a valid integer: Logs warning and starts from 0
|
|
860
|
+
|
|
861
|
+
Note:
|
|
862
|
+
This method is called during start() if `persist_sequence_number`
|
|
863
|
+
is enabled in configuration.
|
|
864
|
+
"""
|
|
865
|
+
if not self._config.persist_sequence_number:
|
|
866
|
+
logger.debug(
|
|
867
|
+
"Sequence number persistence disabled, starting from 0",
|
|
868
|
+
extra={"scheduler_id": self.scheduler_id},
|
|
869
|
+
)
|
|
870
|
+
return
|
|
871
|
+
|
|
872
|
+
correlation_id = uuid4()
|
|
873
|
+
client = await self._get_valkey_client()
|
|
874
|
+
|
|
875
|
+
if client is None:
|
|
876
|
+
logger.warning(
|
|
877
|
+
"Valkey unavailable for sequence load, starting from 0",
|
|
878
|
+
extra={
|
|
879
|
+
"scheduler_id": self.scheduler_id,
|
|
880
|
+
"sequence_key": self._config.sequence_number_key,
|
|
881
|
+
"correlation_id": str(correlation_id),
|
|
882
|
+
},
|
|
883
|
+
)
|
|
884
|
+
return
|
|
885
|
+
|
|
886
|
+
try:
|
|
887
|
+
# Read sequence number from Valkey
|
|
888
|
+
value = await asyncio.wait_for(
|
|
889
|
+
client.get(self._config.sequence_number_key),
|
|
890
|
+
timeout=self._config.valkey_timeout_seconds,
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
if value is None:
|
|
894
|
+
# Key doesn't exist - this is a fresh start
|
|
895
|
+
logger.debug(
|
|
896
|
+
"No persisted sequence number found, starting from 0",
|
|
897
|
+
extra={
|
|
898
|
+
"scheduler_id": self.scheduler_id,
|
|
899
|
+
"sequence_key": self._config.sequence_number_key,
|
|
900
|
+
"correlation_id": str(correlation_id),
|
|
901
|
+
},
|
|
902
|
+
)
|
|
903
|
+
return
|
|
904
|
+
|
|
905
|
+
# Parse the sequence number
|
|
906
|
+
try:
|
|
907
|
+
loaded_sequence = int(value)
|
|
908
|
+
if loaded_sequence < 0:
|
|
909
|
+
logger.warning(
|
|
910
|
+
"Persisted sequence number is negative, starting from 0",
|
|
911
|
+
extra={
|
|
912
|
+
"scheduler_id": self.scheduler_id,
|
|
913
|
+
"sequence_key": self._config.sequence_number_key,
|
|
914
|
+
"persisted_value": value,
|
|
915
|
+
"correlation_id": str(correlation_id),
|
|
916
|
+
},
|
|
917
|
+
)
|
|
918
|
+
return
|
|
919
|
+
|
|
920
|
+
# Successfully loaded - update state
|
|
921
|
+
async with self._state_lock:
|
|
922
|
+
self._sequence_number = loaded_sequence
|
|
923
|
+
self._last_persisted_sequence = loaded_sequence
|
|
924
|
+
|
|
925
|
+
logger.info(
|
|
926
|
+
"Loaded persisted sequence number",
|
|
927
|
+
extra={
|
|
928
|
+
"scheduler_id": self.scheduler_id,
|
|
929
|
+
"sequence_number": loaded_sequence,
|
|
930
|
+
"sequence_key": self._config.sequence_number_key,
|
|
931
|
+
"correlation_id": str(correlation_id),
|
|
932
|
+
},
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
except ValueError:
|
|
936
|
+
logger.warning(
|
|
937
|
+
"Persisted sequence number is not a valid integer, starting from 0",
|
|
938
|
+
extra={
|
|
939
|
+
"scheduler_id": self.scheduler_id,
|
|
940
|
+
"sequence_key": self._config.sequence_number_key,
|
|
941
|
+
"persisted_value": value,
|
|
942
|
+
"correlation_id": str(correlation_id),
|
|
943
|
+
},
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
except (RedisConnectionError, RedisTimeoutError, TimeoutError) as e:
|
|
947
|
+
# Connection failed during operation - mark unavailable
|
|
948
|
+
self._valkey_available = False
|
|
949
|
+
|
|
950
|
+
logger.warning(
|
|
951
|
+
"Valkey connection failed during sequence load, starting from 0",
|
|
952
|
+
extra={
|
|
953
|
+
"scheduler_id": self.scheduler_id,
|
|
954
|
+
"sequence_key": self._config.sequence_number_key,
|
|
955
|
+
# SECURITY: Sanitize error to prevent credential exposure
|
|
956
|
+
"error": sanitize_error_string(str(e)),
|
|
957
|
+
"error_type": type(e).__name__,
|
|
958
|
+
"correlation_id": str(correlation_id),
|
|
959
|
+
},
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
except RedisError as e:
|
|
963
|
+
logger.warning(
|
|
964
|
+
"Valkey error during sequence load, starting from 0",
|
|
965
|
+
extra={
|
|
966
|
+
"scheduler_id": self.scheduler_id,
|
|
967
|
+
"sequence_key": self._config.sequence_number_key,
|
|
968
|
+
# SECURITY: Sanitize error to prevent credential exposure
|
|
969
|
+
"error": sanitize_error_string(str(e)),
|
|
970
|
+
"error_type": type(e).__name__,
|
|
971
|
+
"correlation_id": str(correlation_id),
|
|
972
|
+
},
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
async def _persist_sequence_number(self) -> None:
|
|
976
|
+
"""Persist current sequence number to Valkey for restart-safety.
|
|
977
|
+
|
|
978
|
+
Writes the current sequence number to Valkey using the configured
|
|
979
|
+
`sequence_number_key`. If Valkey is unavailable, gracefully logs a
|
|
980
|
+
warning and continues without persistence.
|
|
981
|
+
|
|
982
|
+
Graceful Fallback:
|
|
983
|
+
- If Valkey is unavailable: Logs warning and skips persistence
|
|
984
|
+
- If write fails: Logs warning with error details
|
|
985
|
+
|
|
986
|
+
Note:
|
|
987
|
+
This method is called during stop() if `persist_sequence_number`
|
|
988
|
+
is enabled in configuration.
|
|
989
|
+
"""
|
|
990
|
+
if not self._config.persist_sequence_number:
|
|
991
|
+
return
|
|
992
|
+
|
|
993
|
+
correlation_id = uuid4()
|
|
994
|
+
client = await self._get_valkey_client()
|
|
995
|
+
|
|
996
|
+
if client is None:
|
|
997
|
+
# Valkey unavailable - log but don't fail shutdown
|
|
998
|
+
# Note: _last_persisted_sequence is NOT updated because persistence failed
|
|
999
|
+
logger.warning(
|
|
1000
|
+
"Valkey unavailable for sequence persistence",
|
|
1001
|
+
extra={
|
|
1002
|
+
"scheduler_id": self.scheduler_id,
|
|
1003
|
+
"sequence_number": self._sequence_number,
|
|
1004
|
+
"sequence_key": self._config.sequence_number_key,
|
|
1005
|
+
"correlation_id": str(correlation_id),
|
|
1006
|
+
},
|
|
1007
|
+
)
|
|
1008
|
+
return
|
|
1009
|
+
|
|
1010
|
+
try:
|
|
1011
|
+
# Write sequence number to Valkey
|
|
1012
|
+
await asyncio.wait_for(
|
|
1013
|
+
client.set(
|
|
1014
|
+
self._config.sequence_number_key,
|
|
1015
|
+
str(self._sequence_number),
|
|
1016
|
+
),
|
|
1017
|
+
timeout=self._config.valkey_timeout_seconds,
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
self._last_persisted_sequence = self._sequence_number
|
|
1021
|
+
|
|
1022
|
+
logger.info(
|
|
1023
|
+
"Persisted sequence number to Valkey",
|
|
1024
|
+
extra={
|
|
1025
|
+
"scheduler_id": self.scheduler_id,
|
|
1026
|
+
"sequence_number": self._sequence_number,
|
|
1027
|
+
"sequence_key": self._config.sequence_number_key,
|
|
1028
|
+
"correlation_id": str(correlation_id),
|
|
1029
|
+
},
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
except (RedisConnectionError, RedisTimeoutError, TimeoutError) as e:
|
|
1033
|
+
# Connection failed during operation - log but don't fail shutdown
|
|
1034
|
+
# Note: _last_persisted_sequence is NOT updated because persistence failed
|
|
1035
|
+
self._valkey_available = False
|
|
1036
|
+
|
|
1037
|
+
logger.warning(
|
|
1038
|
+
"Valkey connection failed during sequence persistence",
|
|
1039
|
+
extra={
|
|
1040
|
+
"scheduler_id": self.scheduler_id,
|
|
1041
|
+
"sequence_number": self._sequence_number,
|
|
1042
|
+
"sequence_key": self._config.sequence_number_key,
|
|
1043
|
+
# SECURITY: Sanitize error to prevent credential exposure
|
|
1044
|
+
"error": sanitize_error_string(str(e)),
|
|
1045
|
+
"error_type": type(e).__name__,
|
|
1046
|
+
"correlation_id": str(correlation_id),
|
|
1047
|
+
},
|
|
1048
|
+
)
|
|
1049
|
+
|
|
1050
|
+
except RedisError as e:
|
|
1051
|
+
# Note: _last_persisted_sequence is NOT updated because persistence failed
|
|
1052
|
+
logger.warning(
|
|
1053
|
+
"Valkey error during sequence persistence",
|
|
1054
|
+
extra={
|
|
1055
|
+
"scheduler_id": self.scheduler_id,
|
|
1056
|
+
"sequence_number": self._sequence_number,
|
|
1057
|
+
"sequence_key": self._config.sequence_number_key,
|
|
1058
|
+
# SECURITY: Sanitize error to prevent credential exposure
|
|
1059
|
+
"error": sanitize_error_string(str(e)),
|
|
1060
|
+
"error_type": type(e).__name__,
|
|
1061
|
+
"correlation_id": str(correlation_id),
|
|
1062
|
+
},
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1065
|
+
finally:
|
|
1066
|
+
# Close the Valkey client during shutdown
|
|
1067
|
+
await self._close_valkey_client()
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
__all__: list[str] = ["RuntimeScheduler"]
|