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,1502 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Policy Registry - SINGLE SOURCE OF TRUTH for policy plugin registration.
|
|
4
|
+
|
|
5
|
+
This module provides the RegistryPolicy class for registering and resolving
|
|
6
|
+
pure decision policy plugins in the ONEX infrastructure layer.
|
|
7
|
+
|
|
8
|
+
The registry is responsible for:
|
|
9
|
+
- Registering policy plugins by (policy_id, policy_type, version) tuple
|
|
10
|
+
- Resolving policy classes for specific policy configurations
|
|
11
|
+
- Thread-safe registration operations
|
|
12
|
+
- Listing all registered policies
|
|
13
|
+
- Enforcing synchronous-by-default policy execution
|
|
14
|
+
|
|
15
|
+
Design Principles:
|
|
16
|
+
- Single source of truth: All policy registrations go through this registry
|
|
17
|
+
- Sync enforcement: Async policies must be explicitly flagged
|
|
18
|
+
- Type-safe: Full typing for policy registrations (no Any types)
|
|
19
|
+
- Thread-safe: Registration operations protected by lock
|
|
20
|
+
- Testable: Easy to mock and test policy configurations
|
|
21
|
+
|
|
22
|
+
Policy Categories (by policy type):
|
|
23
|
+
- Orchestrator policies: Workflow coordination, retry strategies, routing
|
|
24
|
+
- Reducer policies: State aggregation, conflict resolution, projections
|
|
25
|
+
|
|
26
|
+
CRITICAL: Policy plugins are PURE decision logic only.
|
|
27
|
+
|
|
28
|
+
Policy plugins MUST NOT:
|
|
29
|
+
- Perform I/O operations (file, network, database)
|
|
30
|
+
- Have side effects (state mutation outside return values)
|
|
31
|
+
- Make external service calls
|
|
32
|
+
- Log at runtime
|
|
33
|
+
- Depend on mutable global state
|
|
34
|
+
|
|
35
|
+
Example Usage:
|
|
36
|
+
```python
|
|
37
|
+
from omnibase_core.container import ModelONEXContainer
|
|
38
|
+
from omnibase_infra.runtime.registry_policy import RegistryPolicy, ModelPolicyRegistration
|
|
39
|
+
from omnibase_infra.runtime.util_container_wiring import wire_infrastructure_services
|
|
40
|
+
from omnibase_infra.enums import EnumPolicyType
|
|
41
|
+
|
|
42
|
+
# Container-based DI (preferred)
|
|
43
|
+
container = ModelONEXContainer()
|
|
44
|
+
await wire_infrastructure_services(container)
|
|
45
|
+
registry = await container.service_registry.resolve_service(RegistryPolicy)
|
|
46
|
+
|
|
47
|
+
# Register a synchronous policy using the model (PREFERRED API)
|
|
48
|
+
registration = ModelPolicyRegistration(
|
|
49
|
+
policy_id="exponential_backoff",
|
|
50
|
+
policy_class=ExponentialBackoffPolicy,
|
|
51
|
+
policy_type=EnumPolicyType.ORCHESTRATOR,
|
|
52
|
+
version="1.0.0",
|
|
53
|
+
)
|
|
54
|
+
registry.register(registration)
|
|
55
|
+
|
|
56
|
+
# Register using convenience method (preserves original API)
|
|
57
|
+
# Note: For new code, prefer register(ModelPolicyRegistration(...)) instead
|
|
58
|
+
registry.register_policy(
|
|
59
|
+
policy_id="async_merge",
|
|
60
|
+
policy_class=AsyncMergePolicy,
|
|
61
|
+
policy_type=EnumPolicyType.REDUCER,
|
|
62
|
+
version="1.0.0",
|
|
63
|
+
allow_async=True, # MUST be explicit for async policies
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Retrieve a policy
|
|
67
|
+
policy_cls = registry.get("exponential_backoff")
|
|
68
|
+
policy = policy_cls()
|
|
69
|
+
result = policy.evaluate(context)
|
|
70
|
+
|
|
71
|
+
# List all policies
|
|
72
|
+
policies = registry.list_keys() # [(id, type, version), ...]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Integration Points:
|
|
76
|
+
- RuntimeHostProcess uses this registry to discover and instantiate policies
|
|
77
|
+
- Policies are loaded based on contract definitions
|
|
78
|
+
- Supports hot-reload patterns for development
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
from __future__ import annotations
|
|
82
|
+
|
|
83
|
+
import functools
|
|
84
|
+
import threading
|
|
85
|
+
import warnings
|
|
86
|
+
from collections.abc import Callable
|
|
87
|
+
from typing import TYPE_CHECKING
|
|
88
|
+
|
|
89
|
+
from pydantic import ValidationError
|
|
90
|
+
|
|
91
|
+
from omnibase_core.errors import ModelOnexError
|
|
92
|
+
from omnibase_core.models.primitives import ModelSemVer
|
|
93
|
+
from omnibase_infra.enums import EnumInfraTransportType, EnumPolicyType
|
|
94
|
+
from omnibase_infra.errors import PolicyRegistryError, ProtocolConfigurationError
|
|
95
|
+
from omnibase_infra.models.errors.model_infra_error_context import (
|
|
96
|
+
ModelInfraErrorContext,
|
|
97
|
+
)
|
|
98
|
+
from omnibase_infra.runtime.mixin_policy_validation import MixinPolicyValidation
|
|
99
|
+
from omnibase_infra.runtime.mixin_semver_cache import MixinSemverCache
|
|
100
|
+
from omnibase_infra.runtime.models import ModelPolicyKey, ModelPolicyRegistration
|
|
101
|
+
from omnibase_infra.runtime.util_version import normalize_version
|
|
102
|
+
from omnibase_infra.types import PolicyTypeInput
|
|
103
|
+
|
|
104
|
+
if TYPE_CHECKING:
|
|
105
|
+
from omnibase_infra.runtime.protocol_policy import ProtocolPolicy
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# =============================================================================
|
|
109
|
+
# Policy Registry
|
|
110
|
+
# =============================================================================
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class RegistryPolicy(MixinPolicyValidation, MixinSemverCache):
|
|
114
|
+
"""SINGLE SOURCE OF TRUTH for policy plugin registration in omnibase_infra.
|
|
115
|
+
|
|
116
|
+
Thread-safe registry for policy plugins. Manages pure decision logic plugins
|
|
117
|
+
that can be used by orchestrator and reducer nodes.
|
|
118
|
+
|
|
119
|
+
The registry maintains a mapping from ModelPolicyKey instances to policy classes
|
|
120
|
+
that implement the ProtocolPolicy protocol. ModelPolicyKey provides strong typing
|
|
121
|
+
and replaces the legacy tuple[str, str, str] pattern.
|
|
122
|
+
|
|
123
|
+
Container Integration:
|
|
124
|
+
RegistryPolicy is designed to be managed by ModelONEXContainer from omnibase_core.
|
|
125
|
+
Use container_wiring.wire_infrastructure_services() to register RegistryPolicy
|
|
126
|
+
in the container, then resolve it via:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from omnibase_core.container import ModelONEXContainer
|
|
130
|
+
from omnibase_infra.runtime.registry_policy import RegistryPolicy
|
|
131
|
+
|
|
132
|
+
# Resolve from container (preferred) - async in omnibase_core v0.5.6+
|
|
133
|
+
registry = await container.service_registry.resolve_service(RegistryPolicy)
|
|
134
|
+
|
|
135
|
+
# Or use helper function (also async)
|
|
136
|
+
from omnibase_infra.runtime.util_container_wiring import get_policy_registry_from_container
|
|
137
|
+
registry = await get_policy_registry_from_container(container)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Thread Safety:
|
|
141
|
+
All registration operations are protected by a threading.Lock to ensure
|
|
142
|
+
thread-safe access in concurrent environments.
|
|
143
|
+
|
|
144
|
+
Sync Enforcement:
|
|
145
|
+
By default, policies must be synchronous. If a policy has async methods
|
|
146
|
+
(evaluate, decide, reduce), registration will fail unless
|
|
147
|
+
allow_async=True is explicitly specified.
|
|
148
|
+
|
|
149
|
+
Scale and Performance Characteristics:
|
|
150
|
+
|
|
151
|
+
Expected Registry Scale:
|
|
152
|
+
- Typical ONEX system: 20-50 unique policies across 2-5 versions each
|
|
153
|
+
- Medium deployment: 50-100 policies across 3-8 versions each
|
|
154
|
+
- Large deployment: 100-200 policies across 5-10 versions each
|
|
155
|
+
- Stress tested: 500+ total registrations (100 policies x 5 versions)
|
|
156
|
+
|
|
157
|
+
Policy categories (by policy_type):
|
|
158
|
+
- Orchestrator policies: Workflow coordination, retry strategies, routing
|
|
159
|
+
- Reducer policies: State aggregation, conflict resolution, projections
|
|
160
|
+
|
|
161
|
+
Typical distribution: 60% orchestrator policies, 40% reducer policies
|
|
162
|
+
|
|
163
|
+
Performance Characteristics:
|
|
164
|
+
|
|
165
|
+
Primary Operations:
|
|
166
|
+
- register(): O(1) - Direct dictionary insert with secondary index update
|
|
167
|
+
- get(policy_id): O(1) best case, O(k) average, O(k*log k) filtered worst case
|
|
168
|
+
where k = number of matching versions after filtering
|
|
169
|
+
- Uses secondary index (_policy_id_index) for O(1) policy_id lookup
|
|
170
|
+
- Fast path (no filters, single version): O(1) direct lookup
|
|
171
|
+
- Multi-version (no filters): O(k) to find max version via comparison
|
|
172
|
+
- Filtered path (policy_type + multi-version): O(k*log k) for filter + semver sort
|
|
173
|
+
- Deferred error generation: Expensive _list_internal() only on error
|
|
174
|
+
- Cached semver parsing: LRU cache (128 entries) avoids re-parsing
|
|
175
|
+
|
|
176
|
+
- is_registered(): O(k) where k = versions for policy_id
|
|
177
|
+
- list_keys(): O(n*log n) where n = total registrations (full scan + sort)
|
|
178
|
+
- list_versions(): O(k) where k = versions for policy_id
|
|
179
|
+
- unregister(): O(k) where k = versions for policy_id
|
|
180
|
+
|
|
181
|
+
Benchmark Results (500 policy registrations):
|
|
182
|
+
- 1000 sequential get() calls: < 100ms (< 0.1ms per lookup)
|
|
183
|
+
- 1000 concurrent get() calls (10 threads): < 500ms
|
|
184
|
+
- 100 failed lookups (missing policy_id): < 500ms (early exit optimization)
|
|
185
|
+
- Fast path speedup vs filtered path: > 1.1x
|
|
186
|
+
|
|
187
|
+
Lock Contention:
|
|
188
|
+
- Read operations (get, is_registered): Hold lock during lookup only
|
|
189
|
+
- Write operations (register, unregister): Hold lock for full operation
|
|
190
|
+
- Critical sections minimized to reduce contention
|
|
191
|
+
- Expected concurrent throughput: > 2000 reads/sec under 10-thread load
|
|
192
|
+
|
|
193
|
+
Memory Footprint:
|
|
194
|
+
|
|
195
|
+
Per Policy Registration:
|
|
196
|
+
- ModelPolicyKey: ~200 bytes (3 strings: policy_id, policy_type, version)
|
|
197
|
+
- Policy class reference: 8 bytes (Python object pointer)
|
|
198
|
+
- Secondary index entry: ~50 bytes (list entry + key reference)
|
|
199
|
+
- Total per registration: ~260 bytes
|
|
200
|
+
|
|
201
|
+
Estimated Registry Memory:
|
|
202
|
+
- 50 registrations: ~13 KB
|
|
203
|
+
- 100 registrations: ~26 KB
|
|
204
|
+
- 500 registrations: ~130 KB
|
|
205
|
+
- 1000 registrations: ~260 KB
|
|
206
|
+
|
|
207
|
+
Cache Overhead:
|
|
208
|
+
- Semver LRU cache: 128 entries x ~100 bytes = ~12.8 KB
|
|
209
|
+
- Total with cache: Registry memory + 12.8 KB
|
|
210
|
+
|
|
211
|
+
Note: Memory footprint is negligible compared to typical ONEX process memory
|
|
212
|
+
(100-500 MB). Registry memory is not a bottleneck in production systems.
|
|
213
|
+
|
|
214
|
+
Secondary Indexes (Performance Optimization):
|
|
215
|
+
|
|
216
|
+
Current Indexes:
|
|
217
|
+
- _policy_id_index: Maps policy_id -> list[ModelPolicyKey]
|
|
218
|
+
- Purpose: O(1) lookup by policy_id (avoids O(n) scan of all registrations)
|
|
219
|
+
- Updated on: register(), unregister()
|
|
220
|
+
- Memory: ~50 bytes per policy_id + 8 bytes per version
|
|
221
|
+
- Hit rate: 100% for all get() operations
|
|
222
|
+
|
|
223
|
+
When to Add Additional Indexes:
|
|
224
|
+
|
|
225
|
+
Consider _policy_type_index if:
|
|
226
|
+
- Frequent list_keys(policy_type=...) calls (currently O(n))
|
|
227
|
+
- Deployment has > 500 total registrations
|
|
228
|
+
- Profiling shows list_keys filtering as bottleneck
|
|
229
|
+
|
|
230
|
+
Consider _version_index if:
|
|
231
|
+
- Frequent cross-policy version queries
|
|
232
|
+
- Complex version-based policy routing logic
|
|
233
|
+
- Deployment has > 10 versions per policy on average
|
|
234
|
+
|
|
235
|
+
Trade-off Analysis:
|
|
236
|
+
- Each index adds ~50-100 bytes per entry
|
|
237
|
+
- Benefits: O(n) -> O(1) for filtered queries
|
|
238
|
+
- Costs: Write amplification (update multiple indexes per register/unregister)
|
|
239
|
+
- Recommendation: Profile first, optimize only if proven bottleneck
|
|
240
|
+
|
|
241
|
+
Monitoring Recommendations:
|
|
242
|
+
|
|
243
|
+
Key Metrics to Track:
|
|
244
|
+
1. Registry size: len(registry) - Track growth over time
|
|
245
|
+
2. Lookup latency: Time for get() operations (p50, p95, p99)
|
|
246
|
+
3. Lookup errors: PolicyRegistryError frequency (indicates config issues)
|
|
247
|
+
4. Cache hit rate: LRU cache effectiveness (_parse_semver cache)
|
|
248
|
+
5. Lock contention: Concurrent access patterns and throughput
|
|
249
|
+
|
|
250
|
+
Performance Thresholds (alert if exceeded):
|
|
251
|
+
- Average get() latency: > 1ms (indicates potential lock contention)
|
|
252
|
+
- P99 get() latency: > 10ms (indicates blocking on write operations)
|
|
253
|
+
- Registry size: > 1000 registrations (may need index optimization)
|
|
254
|
+
- Cache miss rate: > 10% (indicates cache size insufficient)
|
|
255
|
+
- Concurrent throughput: < 1000 reads/sec (indicates lock bottleneck)
|
|
256
|
+
|
|
257
|
+
Recommended Instrumentation:
|
|
258
|
+
```python
|
|
259
|
+
import time
|
|
260
|
+
from omnibase_core.metrics import histogram, counter
|
|
261
|
+
|
|
262
|
+
# In production RegistryPolicy wrapper:
|
|
263
|
+
start = time.perf_counter()
|
|
264
|
+
policy_cls = registry.get(policy_id)
|
|
265
|
+
histogram("policy_registry.get_latency_ms", (time.perf_counter() - start) * 1000)
|
|
266
|
+
counter("policy_registry.get_total")
|
|
267
|
+
|
|
268
|
+
# Track registry growth:
|
|
269
|
+
histogram("policy_registry.size", len(registry))
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Health Check Integration:
|
|
273
|
+
- Include len(registry) in health check response
|
|
274
|
+
- Alert if registry empty (indicates bootstrap failure)
|
|
275
|
+
- Alert if registry size changes unexpectedly (> 20% delta)
|
|
276
|
+
|
|
277
|
+
Trust Model and Security Considerations:
|
|
278
|
+
|
|
279
|
+
RegistryPolicy performs LIMITED validation on registered policy classes.
|
|
280
|
+
This section documents what guarantees exist and what the caller is
|
|
281
|
+
responsible for.
|
|
282
|
+
|
|
283
|
+
VALIDATED (by RegistryPolicy):
|
|
284
|
+
- Async method detection: Policies with async methods (reduce, decide,
|
|
285
|
+
evaluate) must explicitly set allow_async=True. This prevents
|
|
286
|
+
accidental async policy registration that could cause runtime issues.
|
|
287
|
+
- Policy type validation: policy_type must be a valid EnumPolicyType
|
|
288
|
+
value ("orchestrator" or "reducer"). Invalid types raise PolicyRegistryError.
|
|
289
|
+
- Version format validation: version must be valid semver format
|
|
290
|
+
(e.g., "1.0.0", "1.2.3-beta"). Invalid formats raise ProtocolConfigurationError.
|
|
291
|
+
- Non-empty policy_id: Validated via ModelPolicyRegistration Pydantic model.
|
|
292
|
+
- Thread-safe registration: Registration operations are protected by lock.
|
|
293
|
+
|
|
294
|
+
NOT VALIDATED (caller's responsibility):
|
|
295
|
+
- Policy class correctness: The registry does not verify that a policy
|
|
296
|
+
class correctly implements ProtocolPolicy methods. A class missing
|
|
297
|
+
required methods will only fail at runtime when invoked.
|
|
298
|
+
- Policy class safety: No static analysis, sandboxing, or security
|
|
299
|
+
scanning is performed. Malicious code in a policy class will execute
|
|
300
|
+
with the same privileges as the host process.
|
|
301
|
+
- Policy behavior: The registry cannot validate that policy decision
|
|
302
|
+
logic is correct, deterministic, or free of bugs.
|
|
303
|
+
- Policy dependencies: Import-time side effects, malicious dependencies,
|
|
304
|
+
or resource-intensive imports are not prevented.
|
|
305
|
+
- Runtime behavior: Policies that hang, exhaust memory, raise unexpected
|
|
306
|
+
exceptions, or violate timeouts are not sandboxed.
|
|
307
|
+
- Idempotency: The registry does not verify that policies are idempotent
|
|
308
|
+
or safe to retry.
|
|
309
|
+
|
|
310
|
+
Trust Assumptions:
|
|
311
|
+
1. Policy classes come from TRUSTED sources only:
|
|
312
|
+
- Internal codebase modules
|
|
313
|
+
- Vetted first-party packages
|
|
314
|
+
- Audited third-party packages
|
|
315
|
+
2. Policy classes do not execute arbitrary code on registration:
|
|
316
|
+
- No __init_subclass__ side effects
|
|
317
|
+
- No metaclass execution during class reference
|
|
318
|
+
- No import-time network calls or file I/O
|
|
319
|
+
3. Policy instances are created and used within the same trust boundary:
|
|
320
|
+
- No cross-tenant policy sharing
|
|
321
|
+
- No user-provided policy classes at runtime
|
|
322
|
+
4. Policy implementers follow the purity contract:
|
|
323
|
+
- No I/O operations (file, network, database)
|
|
324
|
+
- No side effects (state mutation outside return values)
|
|
325
|
+
- No external service calls
|
|
326
|
+
- No runtime logging (use structured outputs only)
|
|
327
|
+
|
|
328
|
+
For High-Security Environments:
|
|
329
|
+
If deploying RegistryPolicy in environments with stricter security
|
|
330
|
+
requirements, consider implementing additional safeguards:
|
|
331
|
+
|
|
332
|
+
- Code Review: Mandatory review for all policy implementations before
|
|
333
|
+
registration approval.
|
|
334
|
+
|
|
335
|
+
- Static Analysis: Run linters and security scanners on policy modules
|
|
336
|
+
before allowing registration:
|
|
337
|
+
```python
|
|
338
|
+
# Example: Pre-registration validation hook
|
|
339
|
+
def validate_policy_module(module_path: str) -> bool:
|
|
340
|
+
# Run bandit, semgrep, or custom security checks
|
|
341
|
+
result = run_security_scan(module_path)
|
|
342
|
+
return result.passed
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
- Allowlist Pattern: Maintain an explicit allowlist of approved policy_ids
|
|
346
|
+
and reject registration attempts for unlisted policies:
|
|
347
|
+
```python
|
|
348
|
+
APPROVED_POLICIES = {"exponential_backoff", "rate_limiter", "retry_strategy"}
|
|
349
|
+
|
|
350
|
+
def register_with_allowlist(registration: ModelPolicyRegistration) -> None:
|
|
351
|
+
if registration.policy_id not in APPROVED_POLICIES:
|
|
352
|
+
raise PolicyRegistryError(
|
|
353
|
+
f"Policy '{registration.policy_id}' not in approved list",
|
|
354
|
+
policy_id=registration.policy_id,
|
|
355
|
+
)
|
|
356
|
+
registry.register(registration)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
- Sandboxing: Execute policy code in isolated environments (not built
|
|
360
|
+
into RegistryPolicy, requires external infrastructure):
|
|
361
|
+
- Process isolation (subprocess with resource limits)
|
|
362
|
+
- Container isolation (Docker with security profiles)
|
|
363
|
+
- WASM isolation (for extreme security requirements)
|
|
364
|
+
|
|
365
|
+
- Runtime Monitoring: Instrument policy execution with timeouts and
|
|
366
|
+
resource monitoring:
|
|
367
|
+
```python
|
|
368
|
+
async def execute_policy_with_limits(
|
|
369
|
+
policy: ProtocolPolicy,
|
|
370
|
+
context: dict,
|
|
371
|
+
timeout_seconds: float = 1.0,
|
|
372
|
+
) -> PolicyResult:
|
|
373
|
+
try:
|
|
374
|
+
return await asyncio.wait_for(
|
|
375
|
+
policy.evaluate(context),
|
|
376
|
+
timeout=timeout_seconds,
|
|
377
|
+
)
|
|
378
|
+
except asyncio.TimeoutError:
|
|
379
|
+
raise PolicyRegistryError(
|
|
380
|
+
f"Policy '{policy.policy_id}' exceeded timeout",
|
|
381
|
+
policy_id=policy.policy_id,
|
|
382
|
+
)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Safe Usage Patterns:
|
|
386
|
+
|
|
387
|
+
DO:
|
|
388
|
+
- Register policies from known, reviewed source modules
|
|
389
|
+
- Use container-based DI for better lifecycle management
|
|
390
|
+
- Document policy dependencies and requirements
|
|
391
|
+
- Test policies in isolation before registration
|
|
392
|
+
- Monitor policy execution metrics (latency, error rates)
|
|
393
|
+
|
|
394
|
+
DON'T:
|
|
395
|
+
- Register policy classes provided by untrusted users
|
|
396
|
+
- Allow dynamic policy class construction from user input
|
|
397
|
+
- Skip code review for new policy implementations
|
|
398
|
+
- Assume policies are safe because they're in the registry
|
|
399
|
+
- Share registries across trust boundaries
|
|
400
|
+
|
|
401
|
+
See Also:
|
|
402
|
+
- docs/patterns/policy_registry_trust_model.md for detailed security guide
|
|
403
|
+
- ProtocolPolicy for interface requirements
|
|
404
|
+
- ModelPolicyRegistration for registration model validation
|
|
405
|
+
|
|
406
|
+
Attributes:
|
|
407
|
+
_registry: Internal dictionary mapping ModelPolicyKey instances to policy classes
|
|
408
|
+
_lock: Threading lock for thread-safe registration operations
|
|
409
|
+
_policy_id_index: Secondary index for O(1) policy_id lookup
|
|
410
|
+
|
|
411
|
+
Inherited from MixinSemverCache:
|
|
412
|
+
SEMVER_CACHE_SIZE: Class variable for configuring LRU cache size (default: 128)
|
|
413
|
+
|
|
414
|
+
Class-Level Configuration:
|
|
415
|
+
The semver parsing cache size can be configured for large deployments:
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
# Option 1: Set class attribute before first use
|
|
419
|
+
RegistryPolicy.SEMVER_CACHE_SIZE = 256
|
|
420
|
+
|
|
421
|
+
# Option 2: Use configure method (recommended)
|
|
422
|
+
RegistryPolicy.configure_semver_cache(maxsize=256)
|
|
423
|
+
|
|
424
|
+
# Must be done BEFORE any registry operations
|
|
425
|
+
registry = RegistryPolicy()
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
For testing, use _reset_semver_cache() to clear and reconfigure.
|
|
429
|
+
|
|
430
|
+
Example:
|
|
431
|
+
>>> from omnibase_infra.runtime.models import ModelPolicyRegistration
|
|
432
|
+
>>> registry = RegistryPolicy()
|
|
433
|
+
>>> registration = ModelPolicyRegistration(
|
|
434
|
+
... policy_id="retry_backoff",
|
|
435
|
+
... policy_class=RetryBackoffPolicy,
|
|
436
|
+
... policy_type=EnumPolicyType.ORCHESTRATOR,
|
|
437
|
+
... )
|
|
438
|
+
>>> registry.register(registration)
|
|
439
|
+
>>> policy_cls = registry.get("retry_backoff")
|
|
440
|
+
>>> print(registry.list_keys())
|
|
441
|
+
[('retry_backoff', 'orchestrator', '1.0.0')]
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
def __init__(self) -> None:
|
|
445
|
+
"""Initialize an empty policy registry with thread lock."""
|
|
446
|
+
# Key: ModelPolicyKey -> policy_class (strong typing replaces tuple pattern)
|
|
447
|
+
self._registry: dict[ModelPolicyKey, type[ProtocolPolicy]] = {}
|
|
448
|
+
self._lock: threading.Lock = threading.Lock()
|
|
449
|
+
|
|
450
|
+
# Performance optimization: Secondary indexes for O(1) lookups
|
|
451
|
+
# Maps policy_id -> list of ModelPolicyKey instances
|
|
452
|
+
self._policy_id_index: dict[str, list[ModelPolicyKey]] = {}
|
|
453
|
+
|
|
454
|
+
# Note: _validate_protocol_implementation and _validate_sync_enforcement
|
|
455
|
+
# are inherited from MixinPolicyValidation with the correct signatures.
|
|
456
|
+
# Do not override them here.
|
|
457
|
+
|
|
458
|
+
def _normalize_policy_type(
|
|
459
|
+
self,
|
|
460
|
+
policy_type: PolicyTypeInput,
|
|
461
|
+
) -> str:
|
|
462
|
+
"""Normalize policy type to string value and validate against EnumPolicyType.
|
|
463
|
+
|
|
464
|
+
This method provides centralized policy type validation logic used by all
|
|
465
|
+
registration and query methods. It accepts both EnumPolicyType enum values
|
|
466
|
+
and string literals, normalizing them to their string representation while
|
|
467
|
+
ensuring they match valid EnumPolicyType values.
|
|
468
|
+
|
|
469
|
+
Validation Process:
|
|
470
|
+
1. If policy_type is EnumPolicyType instance, extract .value
|
|
471
|
+
2. If policy_type is string, validate against EnumPolicyType values
|
|
472
|
+
3. Raise PolicyRegistryError if string doesn't match any enum value
|
|
473
|
+
4. Return normalized string value
|
|
474
|
+
|
|
475
|
+
This centralized validation ensures consistent policy type handling across
|
|
476
|
+
all registry operations (register, get, list_keys, is_registered, unregister).
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
policy_type: Policy type as EnumPolicyType enum or string literal.
|
|
480
|
+
Valid values: "orchestrator", "reducer"
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
Normalized string value for the policy type (e.g., "orchestrator", "reducer")
|
|
484
|
+
|
|
485
|
+
Raises:
|
|
486
|
+
PolicyRegistryError: If policy_type is a string that doesn't match any
|
|
487
|
+
EnumPolicyType value. Error includes the invalid value
|
|
488
|
+
and list of valid options.
|
|
489
|
+
|
|
490
|
+
Example:
|
|
491
|
+
>>> from omnibase_infra.enums import EnumPolicyType
|
|
492
|
+
>>> registry = RegistryPolicy()
|
|
493
|
+
>>> # Enum to string
|
|
494
|
+
>>> registry._normalize_policy_type(EnumPolicyType.ORCHESTRATOR)
|
|
495
|
+
'orchestrator'
|
|
496
|
+
>>> # Valid string passthrough
|
|
497
|
+
>>> registry._normalize_policy_type("reducer")
|
|
498
|
+
'reducer'
|
|
499
|
+
>>> # Invalid string raises error
|
|
500
|
+
>>> registry._normalize_policy_type("invalid")
|
|
501
|
+
PolicyRegistryError: Invalid policy_type: 'invalid'.
|
|
502
|
+
Must be one of: ['orchestrator', 'reducer']
|
|
503
|
+
"""
|
|
504
|
+
if isinstance(policy_type, EnumPolicyType):
|
|
505
|
+
return policy_type.value
|
|
506
|
+
|
|
507
|
+
# Validate string against enum values
|
|
508
|
+
valid_types = {e.value for e in EnumPolicyType}
|
|
509
|
+
if policy_type not in valid_types:
|
|
510
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
511
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
512
|
+
operation="normalize_policy_type",
|
|
513
|
+
)
|
|
514
|
+
raise PolicyRegistryError(
|
|
515
|
+
f"Invalid policy_type: {policy_type!r}. "
|
|
516
|
+
f"Must be one of: {sorted(valid_types)}",
|
|
517
|
+
policy_id=None,
|
|
518
|
+
policy_type=policy_type,
|
|
519
|
+
context=context,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
return policy_type
|
|
523
|
+
|
|
524
|
+
@staticmethod
|
|
525
|
+
def _normalize_version(version: str) -> str:
|
|
526
|
+
"""Normalize version string for consistent lookups.
|
|
527
|
+
|
|
528
|
+
Delegates to the shared normalize_version utility which is the
|
|
529
|
+
SINGLE SOURCE OF TRUTH for version normalization in omnibase_infra.
|
|
530
|
+
|
|
531
|
+
This method wraps the shared utility to convert ValueError to
|
|
532
|
+
ProtocolConfigurationError for RegistryPolicy's error contract.
|
|
533
|
+
|
|
534
|
+
Normalization rules:
|
|
535
|
+
1. Strip leading/trailing whitespace
|
|
536
|
+
2. Strip leading 'v' or 'V' prefix
|
|
537
|
+
3. Expand partial versions (1 -> 1.0.0, 1.0 -> 1.0.0)
|
|
538
|
+
4. Parse with ModelSemVer.parse() for validation
|
|
539
|
+
5. Preserve prerelease suffix if present
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
version: The version string to normalize
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
Normalized version string in "x.y.z" or "x.y.z-prerelease" format
|
|
546
|
+
|
|
547
|
+
Raises:
|
|
548
|
+
ProtocolConfigurationError: If the version format is invalid
|
|
549
|
+
|
|
550
|
+
Example:
|
|
551
|
+
>>> RegistryPolicy._normalize_version("1.0")
|
|
552
|
+
'1.0.0'
|
|
553
|
+
>>> RegistryPolicy._normalize_version("v2.1")
|
|
554
|
+
'2.1.0'
|
|
555
|
+
"""
|
|
556
|
+
try:
|
|
557
|
+
return normalize_version(version)
|
|
558
|
+
except ValueError as e:
|
|
559
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
560
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
561
|
+
operation="normalize_version",
|
|
562
|
+
)
|
|
563
|
+
raise ProtocolConfigurationError(
|
|
564
|
+
str(e),
|
|
565
|
+
version=version,
|
|
566
|
+
context=context,
|
|
567
|
+
) from e
|
|
568
|
+
|
|
569
|
+
def register(
|
|
570
|
+
self,
|
|
571
|
+
registration: ModelPolicyRegistration,
|
|
572
|
+
) -> None:
|
|
573
|
+
"""Register a policy plugin using a registration model.
|
|
574
|
+
|
|
575
|
+
Associates a (policy_id, policy_type, version) tuple with a policy class.
|
|
576
|
+
If the combination is already registered, the existing registration is
|
|
577
|
+
overwritten.
|
|
578
|
+
|
|
579
|
+
This is the PREFERRED API for registering policies. For new code, use this
|
|
580
|
+
method with ModelPolicyRegistration instead of register_policy().
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
registration: ModelPolicyRegistration containing all registration parameters:
|
|
584
|
+
- policy_id: Unique identifier for the policy
|
|
585
|
+
- policy_class: The policy class to register (must implement ProtocolPolicy)
|
|
586
|
+
- policy_type: Whether this is orchestrator or reducer policy
|
|
587
|
+
- version: Semantic version string (default: "1.0.0")
|
|
588
|
+
- allow_async: If True, allows async interface
|
|
589
|
+
|
|
590
|
+
Raises:
|
|
591
|
+
PolicyRegistryError: If policy has async methods and
|
|
592
|
+
allow_async=False, or if policy_type is invalid
|
|
593
|
+
|
|
594
|
+
Example:
|
|
595
|
+
>>> from omnibase_infra.runtime.models import ModelPolicyRegistration
|
|
596
|
+
>>> registry = RegistryPolicy()
|
|
597
|
+
>>> registration = ModelPolicyRegistration(
|
|
598
|
+
... policy_id="retry_backoff",
|
|
599
|
+
... policy_class=RetryBackoffPolicy,
|
|
600
|
+
... policy_type=EnumPolicyType.ORCHESTRATOR,
|
|
601
|
+
... version="1.0.0",
|
|
602
|
+
... )
|
|
603
|
+
>>> registry.register(registration)
|
|
604
|
+
"""
|
|
605
|
+
# Extract fields from model
|
|
606
|
+
policy_id = registration.policy_id
|
|
607
|
+
policy_class = registration.policy_class
|
|
608
|
+
policy_type = registration.policy_type
|
|
609
|
+
version = registration.version
|
|
610
|
+
allow_async = registration.allow_async
|
|
611
|
+
|
|
612
|
+
# Validate protocol implementation (evaluate() method exists and is callable)
|
|
613
|
+
# Pass policy_type for complete parameter validation and error context
|
|
614
|
+
self._validate_protocol_implementation(policy_id, policy_class, policy_type)
|
|
615
|
+
|
|
616
|
+
# Validate sync enforcement (pass policy_type for better error context)
|
|
617
|
+
self._validate_sync_enforcement(
|
|
618
|
+
policy_id, policy_class, allow_async, policy_type
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
# Normalize policy type
|
|
622
|
+
normalized_type = self._normalize_policy_type(policy_type)
|
|
623
|
+
|
|
624
|
+
# Normalize version string before storing to prevent lookup mismatches
|
|
625
|
+
# This ensures "1.0", "1.0.0", and "v1.0.0" all resolve to "1.0.0"
|
|
626
|
+
# Note: ModelPolicyRegistration already normalizes, but we normalize again
|
|
627
|
+
# here to guarantee consistency with lookup operations
|
|
628
|
+
normalized_version = self._normalize_version(version)
|
|
629
|
+
|
|
630
|
+
# Validate version format (ensures semantic versioning compliance)
|
|
631
|
+
# This calls _parse_semver which will raise ProtocolConfigurationError if invalid
|
|
632
|
+
self._parse_semver(normalized_version)
|
|
633
|
+
|
|
634
|
+
# Register the policy using ModelPolicyKey with normalized version
|
|
635
|
+
key = ModelPolicyKey(
|
|
636
|
+
policy_id=policy_id,
|
|
637
|
+
policy_type=normalized_type,
|
|
638
|
+
version=normalized_version,
|
|
639
|
+
)
|
|
640
|
+
with self._lock:
|
|
641
|
+
self._registry[key] = policy_class
|
|
642
|
+
# Update secondary index for performance optimization
|
|
643
|
+
if policy_id not in self._policy_id_index:
|
|
644
|
+
self._policy_id_index[policy_id] = []
|
|
645
|
+
if key not in self._policy_id_index[policy_id]:
|
|
646
|
+
self._policy_id_index[policy_id].append(key)
|
|
647
|
+
|
|
648
|
+
def register_policy(
|
|
649
|
+
self,
|
|
650
|
+
policy_id: str,
|
|
651
|
+
policy_class: type[ProtocolPolicy],
|
|
652
|
+
policy_type: PolicyTypeInput,
|
|
653
|
+
version: str = "1.0.0",
|
|
654
|
+
allow_async: bool = False,
|
|
655
|
+
) -> None:
|
|
656
|
+
"""Convenience method to register a policy with individual parameters.
|
|
657
|
+
|
|
658
|
+
Wraps parameters in ModelPolicyRegistration and calls register().
|
|
659
|
+
Partial version strings (e.g., "1", "1.0") are auto-normalized to
|
|
660
|
+
"x.y.z" format by ModelPolicyRegistration.
|
|
661
|
+
|
|
662
|
+
Note:
|
|
663
|
+
For new code, prefer using register(ModelPolicyRegistration(...))
|
|
664
|
+
directly. This is a convenience method for simple registrations.
|
|
665
|
+
|
|
666
|
+
Args:
|
|
667
|
+
policy_id: Unique identifier for the policy (e.g., 'exponential_backoff')
|
|
668
|
+
policy_class: The policy class to register. Must implement ProtocolPolicy.
|
|
669
|
+
policy_type: Whether this is orchestrator or reducer policy.
|
|
670
|
+
Can be EnumPolicyType or string literal.
|
|
671
|
+
version: Semantic version string (default: "1.0.0"). Partial versions
|
|
672
|
+
like "1" or "1.0" are auto-normalized to "1.0.0" or "1.0.0".
|
|
673
|
+
allow_async: If True, allows async interface. MUST be explicitly
|
|
674
|
+
flagged for policies with async methods.
|
|
675
|
+
|
|
676
|
+
Raises:
|
|
677
|
+
PolicyRegistryError: If policy has async methods and
|
|
678
|
+
allow_async=False, or if policy_type is invalid
|
|
679
|
+
ProtocolConfigurationError: If version format is invalid
|
|
680
|
+
|
|
681
|
+
Example:
|
|
682
|
+
>>> registry = RegistryPolicy()
|
|
683
|
+
>>> registry.register_policy(
|
|
684
|
+
... policy_id="retry_backoff",
|
|
685
|
+
... policy_class=RetryBackoffPolicy,
|
|
686
|
+
... policy_type=EnumPolicyType.ORCHESTRATOR,
|
|
687
|
+
... version="1.0.0",
|
|
688
|
+
... )
|
|
689
|
+
"""
|
|
690
|
+
# Version normalization is handled by ModelPolicyRegistration validator
|
|
691
|
+
# which normalizes partial versions and v-prefixed versions automatically
|
|
692
|
+
try:
|
|
693
|
+
registration = ModelPolicyRegistration(
|
|
694
|
+
policy_id=policy_id,
|
|
695
|
+
policy_class=policy_class,
|
|
696
|
+
policy_type=policy_type,
|
|
697
|
+
version=version,
|
|
698
|
+
allow_async=allow_async,
|
|
699
|
+
)
|
|
700
|
+
except ValidationError as e:
|
|
701
|
+
# Convert all validation errors to ProtocolConfigurationError for consistency
|
|
702
|
+
# This ensures uniform error handling across all validation failures
|
|
703
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
704
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
705
|
+
operation="register_policy",
|
|
706
|
+
)
|
|
707
|
+
for error in e.errors():
|
|
708
|
+
field_loc = error.get("loc", ())
|
|
709
|
+
field_name = field_loc[0] if field_loc else "unknown"
|
|
710
|
+
error_msg = error.get("msg", str(e))
|
|
711
|
+
|
|
712
|
+
if field_name == "version":
|
|
713
|
+
raise ProtocolConfigurationError(
|
|
714
|
+
f"Invalid version format: {error_msg}",
|
|
715
|
+
version=version,
|
|
716
|
+
context=context,
|
|
717
|
+
) from e
|
|
718
|
+
if field_name == "policy_id":
|
|
719
|
+
raise ProtocolConfigurationError(
|
|
720
|
+
f"Invalid policy_id: {error_msg}",
|
|
721
|
+
policy_id=policy_id,
|
|
722
|
+
context=context,
|
|
723
|
+
) from e
|
|
724
|
+
if field_name == "policy_type":
|
|
725
|
+
raise ProtocolConfigurationError(
|
|
726
|
+
f"Invalid policy_type: {error_msg}",
|
|
727
|
+
policy_type=str(policy_type),
|
|
728
|
+
context=context,
|
|
729
|
+
) from e
|
|
730
|
+
if field_name == "policy_class":
|
|
731
|
+
raise ProtocolConfigurationError(
|
|
732
|
+
f"Invalid policy_class: {error_msg}",
|
|
733
|
+
context=context,
|
|
734
|
+
) from e
|
|
735
|
+
# Fallback for any unhandled validation errors
|
|
736
|
+
raise ProtocolConfigurationError(
|
|
737
|
+
f"Validation error in policy registration: {e}",
|
|
738
|
+
context=context,
|
|
739
|
+
) from e
|
|
740
|
+
|
|
741
|
+
self.register(registration)
|
|
742
|
+
|
|
743
|
+
def get(
|
|
744
|
+
self,
|
|
745
|
+
policy_id: str,
|
|
746
|
+
policy_type: PolicyTypeInput | None = None,
|
|
747
|
+
version: str | None = None,
|
|
748
|
+
) -> type[ProtocolPolicy]:
|
|
749
|
+
"""Get policy class by ID, type, and optional version.
|
|
750
|
+
|
|
751
|
+
Resolves the policy class registered for the given policy configuration.
|
|
752
|
+
If policy_type is not specified, returns the first matching policy_id.
|
|
753
|
+
If version is not specified, returns the latest version (by semantic version).
|
|
754
|
+
|
|
755
|
+
Performance Characteristics:
|
|
756
|
+
- Best case: O(1) - Direct lookup with policy_id only (single version, no filters)
|
|
757
|
+
- Average case: O(k) where k = number of matching versions (multi-version, no filters)
|
|
758
|
+
- Worst case: O(k*log(k)) when policy_type filter applied with multiple versions
|
|
759
|
+
(requires both filtering candidates and sorting by semver to find latest)
|
|
760
|
+
- Uses secondary index for O(1) policy_id lookup instead of O(n) scan
|
|
761
|
+
- Defers expensive error message generation until actually needed
|
|
762
|
+
- Fast path optimization when no filters applied (common case)
|
|
763
|
+
|
|
764
|
+
Args:
|
|
765
|
+
policy_id: Policy identifier.
|
|
766
|
+
policy_type: Optional policy type filter (orchestrator or reducer).
|
|
767
|
+
version: Optional version filter. If None, returns latest version.
|
|
768
|
+
|
|
769
|
+
Returns:
|
|
770
|
+
Policy class registered for the configuration.
|
|
771
|
+
|
|
772
|
+
Raises:
|
|
773
|
+
PolicyRegistryError: If no matching policy is found.
|
|
774
|
+
|
|
775
|
+
Example:
|
|
776
|
+
>>> registry = RegistryPolicy()
|
|
777
|
+
>>> registry.register("retry", RetryPolicy, EnumPolicyType.ORCHESTRATOR)
|
|
778
|
+
>>> policy_cls = registry.get("retry")
|
|
779
|
+
>>> policy_cls = registry.get("retry", policy_type="orchestrator")
|
|
780
|
+
>>> policy_cls = registry.get("retry", version="1.0.0")
|
|
781
|
+
"""
|
|
782
|
+
# Normalize policy_type if provided (outside lock for minimal critical section)
|
|
783
|
+
# Use empty string as sentinel for "no filter" to reduce union types
|
|
784
|
+
normalized_type: str = (
|
|
785
|
+
self._normalize_policy_type(policy_type) if policy_type is not None else ""
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
# Normalize version for consistent lookup (e.g., "1.0" matches "1.0.0")
|
|
789
|
+
normalized_version: str | None = None
|
|
790
|
+
if version is not None:
|
|
791
|
+
normalized_version = self._normalize_version(version)
|
|
792
|
+
|
|
793
|
+
with self._lock:
|
|
794
|
+
# Performance optimization: Use secondary index for O(1) lookup by policy_id
|
|
795
|
+
# This avoids iterating through all registry entries (O(n) -> O(1))
|
|
796
|
+
candidate_keys = self._policy_id_index.get(policy_id, [])
|
|
797
|
+
|
|
798
|
+
# Early exit if policy_id not found - avoid building matches list
|
|
799
|
+
if not candidate_keys:
|
|
800
|
+
# Defer expensive _list_internal() call until actually raising error
|
|
801
|
+
filters = [f"policy_id={policy_id!r}"]
|
|
802
|
+
if policy_type is not None:
|
|
803
|
+
filters.append(f"policy_type={policy_type!r}")
|
|
804
|
+
if version is not None:
|
|
805
|
+
filters.append(f"version={version!r}")
|
|
806
|
+
|
|
807
|
+
# Inline list generation for error message (avoids separate method)
|
|
808
|
+
registered = [
|
|
809
|
+
k.to_tuple()
|
|
810
|
+
for k in sorted(
|
|
811
|
+
self._registry.keys(),
|
|
812
|
+
key=lambda k: (k.policy_id, k.policy_type, k.version),
|
|
813
|
+
)
|
|
814
|
+
]
|
|
815
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
816
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
817
|
+
operation="get_policy",
|
|
818
|
+
)
|
|
819
|
+
raise PolicyRegistryError(
|
|
820
|
+
f"No policy registered matching: {', '.join(filters)}. "
|
|
821
|
+
f"Registered policies: {registered}",
|
|
822
|
+
policy_id=policy_id,
|
|
823
|
+
policy_type=str(policy_type) if policy_type else None,
|
|
824
|
+
context=context,
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
# Find matching entries from candidates (optimized to reduce allocations)
|
|
828
|
+
# Fast path: no filtering needed (common case - just get latest version)
|
|
829
|
+
if not normalized_type and normalized_version is None:
|
|
830
|
+
# Fast path optimization: avoid tuple allocation and batch dict lookups
|
|
831
|
+
# Only build the matches list if we have multiple versions
|
|
832
|
+
if len(candidate_keys) == 1:
|
|
833
|
+
# Single version - direct return without any allocations
|
|
834
|
+
return self._registry[candidate_keys[0]]
|
|
835
|
+
else:
|
|
836
|
+
# Multiple versions - need to find latest
|
|
837
|
+
# Use direct key comparison instead of building tuples
|
|
838
|
+
latest_key = max(
|
|
839
|
+
candidate_keys,
|
|
840
|
+
key=lambda k: self._parse_semver(k.version),
|
|
841
|
+
)
|
|
842
|
+
return self._registry[latest_key]
|
|
843
|
+
else:
|
|
844
|
+
# Filtered path: apply type and version filters
|
|
845
|
+
matches = []
|
|
846
|
+
for key in candidate_keys:
|
|
847
|
+
if normalized_type and key.policy_type != normalized_type:
|
|
848
|
+
continue
|
|
849
|
+
if (
|
|
850
|
+
normalized_version is not None
|
|
851
|
+
and key.version != normalized_version
|
|
852
|
+
):
|
|
853
|
+
continue
|
|
854
|
+
matches.append((key, self._registry[key]))
|
|
855
|
+
|
|
856
|
+
if not matches:
|
|
857
|
+
# Filters eliminated all candidates - build error message
|
|
858
|
+
filters = [f"policy_id={policy_id!r}"]
|
|
859
|
+
if policy_type is not None:
|
|
860
|
+
filters.append(f"policy_type={policy_type!r}")
|
|
861
|
+
if version is not None:
|
|
862
|
+
filters.append(f"version={version!r}")
|
|
863
|
+
|
|
864
|
+
# Inline list generation for error message (avoids separate method)
|
|
865
|
+
registered = [
|
|
866
|
+
k.to_tuple()
|
|
867
|
+
for k in sorted(
|
|
868
|
+
self._registry.keys(),
|
|
869
|
+
key=lambda k: (k.policy_id, k.policy_type, k.version),
|
|
870
|
+
)
|
|
871
|
+
]
|
|
872
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
873
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
874
|
+
operation="get_policy",
|
|
875
|
+
)
|
|
876
|
+
raise PolicyRegistryError(
|
|
877
|
+
f"No policy registered matching: {', '.join(filters)}. "
|
|
878
|
+
f"Registered policies: {registered}",
|
|
879
|
+
policy_id=policy_id,
|
|
880
|
+
policy_type=str(policy_type) if policy_type else None,
|
|
881
|
+
context=context,
|
|
882
|
+
)
|
|
883
|
+
|
|
884
|
+
# If version not specified and multiple matches, return latest
|
|
885
|
+
# (using cached semantic version comparison)
|
|
886
|
+
if normalized_version is None and len(matches) > 1:
|
|
887
|
+
# Sort in-place to avoid allocating a new list
|
|
888
|
+
matches.sort(
|
|
889
|
+
key=lambda x: self._parse_semver(x[0].version), reverse=True
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
return matches[0][1]
|
|
893
|
+
|
|
894
|
+
# ==========================================================================
|
|
895
|
+
# Semver Cache Configuration Methods
|
|
896
|
+
# ==========================================================================
|
|
897
|
+
|
|
898
|
+
@classmethod
|
|
899
|
+
def configure_semver_cache(cls, maxsize: int) -> None:
|
|
900
|
+
"""Configure semver cache size. Must be called before first parse.
|
|
901
|
+
|
|
902
|
+
This method allows configuring the LRU cache size for semver parsing
|
|
903
|
+
in large deployments with many policy versions. For most deployments,
|
|
904
|
+
the default of 128 entries is sufficient.
|
|
905
|
+
|
|
906
|
+
When to Increase Cache Size:
|
|
907
|
+
- Very large deployments with > 100 unique policy versions
|
|
908
|
+
- High-frequency lookups across many version combinations
|
|
909
|
+
- Observed cache eviction causing performance regression
|
|
910
|
+
|
|
911
|
+
Args:
|
|
912
|
+
maxsize: Maximum cache entries (default: 128).
|
|
913
|
+
Recommended range: 64-512 for most deployments.
|
|
914
|
+
Each entry uses ~100 bytes.
|
|
915
|
+
|
|
916
|
+
Raises:
|
|
917
|
+
ProtocolConfigurationError: If cache already initialized (first parse already occurred)
|
|
918
|
+
|
|
919
|
+
Example:
|
|
920
|
+
>>> # Configure before any registry operations
|
|
921
|
+
>>> RegistryPolicy.configure_semver_cache(maxsize=256)
|
|
922
|
+
>>> registry = RegistryPolicy()
|
|
923
|
+
|
|
924
|
+
Note:
|
|
925
|
+
For testing purposes, use _reset_semver_cache() to clear the cache
|
|
926
|
+
and allow reconfiguration.
|
|
927
|
+
"""
|
|
928
|
+
with cls._semver_cache_lock:
|
|
929
|
+
if cls._semver_cache is not None:
|
|
930
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
931
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
932
|
+
operation="configure_semver_cache",
|
|
933
|
+
)
|
|
934
|
+
raise ProtocolConfigurationError(
|
|
935
|
+
"Cannot reconfigure semver cache after first use. "
|
|
936
|
+
"Set RegistryPolicy.SEMVER_CACHE_SIZE before creating any "
|
|
937
|
+
"registry instances, or use _reset_semver_cache() for testing.",
|
|
938
|
+
context=context,
|
|
939
|
+
)
|
|
940
|
+
cls.SEMVER_CACHE_SIZE = maxsize
|
|
941
|
+
|
|
942
|
+
@classmethod
|
|
943
|
+
def _reset_semver_cache(cls) -> None:
|
|
944
|
+
"""Reset semver cache. For testing only.
|
|
945
|
+
|
|
946
|
+
Clears the cached semver parser, allowing reconfiguration of cache size.
|
|
947
|
+
This should only be used in test fixtures to ensure test isolation.
|
|
948
|
+
|
|
949
|
+
Thread Safety:
|
|
950
|
+
This method is thread-safe and uses the class-level lock. The reset
|
|
951
|
+
operation is atomic - either the cache is fully reset or not at all.
|
|
952
|
+
|
|
953
|
+
In-flight Operations:
|
|
954
|
+
If other threads have already obtained a reference to the cache
|
|
955
|
+
via _get_semver_parser(), they will continue using the old cache
|
|
956
|
+
until they complete. This is safe because the old cache remains
|
|
957
|
+
a valid callable until garbage collected. New operations after
|
|
958
|
+
reset will get the new cache instance when created.
|
|
959
|
+
|
|
960
|
+
Memory Reclamation:
|
|
961
|
+
The old cache's internal LRU entries are explicitly cleared via
|
|
962
|
+
cache_clear() before the reference is released. This ensures
|
|
963
|
+
prompt memory reclamation rather than waiting for garbage
|
|
964
|
+
collection.
|
|
965
|
+
|
|
966
|
+
Concurrent Reset:
|
|
967
|
+
Multiple concurrent reset calls are safe. Each reset will clear
|
|
968
|
+
the current cache (if any) and set the reference to None. The
|
|
969
|
+
lock ensures only one reset executes at a time.
|
|
970
|
+
|
|
971
|
+
Example:
|
|
972
|
+
>>> # In test fixture
|
|
973
|
+
>>> RegistryPolicy._reset_semver_cache()
|
|
974
|
+
>>> RegistryPolicy.SEMVER_CACHE_SIZE = 64
|
|
975
|
+
>>> # Now cache will be initialized with size 64 on next use
|
|
976
|
+
"""
|
|
977
|
+
with cls._semver_cache_lock:
|
|
978
|
+
# Clear the inner LRU-cached function (has the actual cache)
|
|
979
|
+
inner_cache = cls._semver_cache_inner
|
|
980
|
+
if inner_cache is not None:
|
|
981
|
+
# Clear internal LRU cache entries before releasing reference.
|
|
982
|
+
# This ensures prompt memory reclamation rather than waiting
|
|
983
|
+
# for garbage collection of the orphaned function object.
|
|
984
|
+
# Note: cache_clear() is added by @lru_cache decorator but not
|
|
985
|
+
# reflected in Callable type annotation. This is a known mypy
|
|
986
|
+
# limitation with lru_cache wrappers.
|
|
987
|
+
inner_cache.cache_clear() # type: ignore[attr-defined]
|
|
988
|
+
cls._semver_cache = None
|
|
989
|
+
cls._semver_cache_inner = None
|
|
990
|
+
|
|
991
|
+
@classmethod
|
|
992
|
+
def _get_semver_parser(cls) -> Callable[[str], ModelSemVer]:
|
|
993
|
+
"""Get or create the semver parser with configured cache size.
|
|
994
|
+
|
|
995
|
+
This method implements lazy initialization of the LRU-cached semver parser.
|
|
996
|
+
The cache size is determined by SEMVER_CACHE_SIZE at initialization time.
|
|
997
|
+
|
|
998
|
+
Thread Safety:
|
|
999
|
+
Uses double-checked locking pattern for thread-safe lazy initialization.
|
|
1000
|
+
The fast path stores the cache reference in a local variable to prevent
|
|
1001
|
+
TOCTOU (time-of-check-time-of-use) race conditions where another thread
|
|
1002
|
+
could call _reset_semver_cache() between the None check and the return.
|
|
1003
|
+
|
|
1004
|
+
Cache Key Normalization:
|
|
1005
|
+
Version strings are normalized BEFORE being used as cache keys to ensure
|
|
1006
|
+
that equivalent versions (e.g., "1.0" and "1.0.0") share the same cache
|
|
1007
|
+
entry. This prevents cache fragmentation and improves hit rates.
|
|
1008
|
+
|
|
1009
|
+
Returns:
|
|
1010
|
+
Cached semver parsing function that returns ModelSemVer instances.
|
|
1011
|
+
The returned function accepts a version string and returns a ModelSemVer.
|
|
1012
|
+
|
|
1013
|
+
Raises:
|
|
1014
|
+
ProtocolConfigurationError: Raised by the returned parsing function if
|
|
1015
|
+
the version format is invalid (e.g., non-numeric components,
|
|
1016
|
+
negative numbers, or more than 3 version parts).
|
|
1017
|
+
|
|
1018
|
+
Performance:
|
|
1019
|
+
- First call: Creates LRU-cached function (one-time cost)
|
|
1020
|
+
- Subsequent calls: Returns cached function reference (O(1))
|
|
1021
|
+
- Cache hit rate improved by normalizing keys before lookup
|
|
1022
|
+
"""
|
|
1023
|
+
# Fast path: cache already initialized
|
|
1024
|
+
# CRITICAL: Store in local variable to prevent TOCTOU race condition.
|
|
1025
|
+
# Without this, another thread could call _reset_semver_cache() between
|
|
1026
|
+
# the None check and the return, causing this method to return None.
|
|
1027
|
+
cache = cls._semver_cache
|
|
1028
|
+
if cache is not None:
|
|
1029
|
+
return cache
|
|
1030
|
+
|
|
1031
|
+
# Slow path: initialize with lock
|
|
1032
|
+
with cls._semver_cache_lock:
|
|
1033
|
+
# Double-check after acquiring lock
|
|
1034
|
+
if cls._semver_cache is not None:
|
|
1035
|
+
return cls._semver_cache
|
|
1036
|
+
|
|
1037
|
+
# Create LRU-cached parser with configured size
|
|
1038
|
+
# The cache key is the NORMALIZED version string to prevent
|
|
1039
|
+
# fragmentation (e.g., "1.0" and "1.0.0" share the same entry)
|
|
1040
|
+
@functools.lru_cache(maxsize=cls.SEMVER_CACHE_SIZE)
|
|
1041
|
+
def _parse_semver_cached(normalized_version: str) -> ModelSemVer:
|
|
1042
|
+
"""Parse normalized semantic version string into ModelSemVer.
|
|
1043
|
+
|
|
1044
|
+
This function receives ALREADY NORMALIZED version strings.
|
|
1045
|
+
The normalization is done by the wrapper function before
|
|
1046
|
+
caching to ensure equivalent versions share cache entries.
|
|
1047
|
+
|
|
1048
|
+
Args:
|
|
1049
|
+
normalized_version: Pre-normalized version in "x.y.z" or
|
|
1050
|
+
"x.y.z-prerelease" format
|
|
1051
|
+
|
|
1052
|
+
Returns:
|
|
1053
|
+
ModelSemVer instance for comparison
|
|
1054
|
+
|
|
1055
|
+
Raises:
|
|
1056
|
+
ProtocolConfigurationError: If version format is invalid
|
|
1057
|
+
"""
|
|
1058
|
+
# ModelOnexError is imported at module level
|
|
1059
|
+
try:
|
|
1060
|
+
return ModelSemVer.parse(normalized_version)
|
|
1061
|
+
except ModelOnexError as e:
|
|
1062
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
1063
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
1064
|
+
operation="parse_semver",
|
|
1065
|
+
)
|
|
1066
|
+
raise ProtocolConfigurationError(
|
|
1067
|
+
str(e),
|
|
1068
|
+
version=normalized_version,
|
|
1069
|
+
context=context,
|
|
1070
|
+
) from e
|
|
1071
|
+
except ValueError as e:
|
|
1072
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
1073
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
1074
|
+
operation="parse_semver",
|
|
1075
|
+
)
|
|
1076
|
+
raise ProtocolConfigurationError(
|
|
1077
|
+
str(e),
|
|
1078
|
+
version=normalized_version,
|
|
1079
|
+
context=context,
|
|
1080
|
+
) from e
|
|
1081
|
+
|
|
1082
|
+
def _parse_semver_impl(version: str) -> ModelSemVer:
|
|
1083
|
+
"""Parse semantic version string into ModelSemVer.
|
|
1084
|
+
|
|
1085
|
+
Implementation moved here to support configurable cache size.
|
|
1086
|
+
See _parse_semver docstring for full documentation.
|
|
1087
|
+
|
|
1088
|
+
IMPORTANT: This wrapper normalizes version strings BEFORE
|
|
1089
|
+
passing to the LRU-cached parsing function. This ensures that
|
|
1090
|
+
equivalent versions (e.g., "1.0" and "1.0.0", "v1.0.0" and "1.0.0")
|
|
1091
|
+
share the same cache entry, improving cache hit rates.
|
|
1092
|
+
|
|
1093
|
+
All validation (empty strings, prerelease suffix, format) is
|
|
1094
|
+
delegated to _normalize_version to eliminate code duplication.
|
|
1095
|
+
"""
|
|
1096
|
+
# Delegate all validation to _normalize_version (single source of truth)
|
|
1097
|
+
# This eliminates duplicated validation logic (empty check, prerelease suffix)
|
|
1098
|
+
normalized = RegistryPolicy._normalize_version(version)
|
|
1099
|
+
|
|
1100
|
+
# Now call the cached function with the NORMALIZED version
|
|
1101
|
+
# This ensures "1.0", "1.0.0", "v1.0.0" all use the same cache entry
|
|
1102
|
+
return _parse_semver_cached(normalized)
|
|
1103
|
+
|
|
1104
|
+
# Store both the outer wrapper and inner cached function
|
|
1105
|
+
# The wrapper is what callers use (_semver_cache)
|
|
1106
|
+
# The inner function is needed for cache_clear() access (_semver_cache_inner)
|
|
1107
|
+
cls._semver_cache = _parse_semver_impl
|
|
1108
|
+
cls._semver_cache_inner = _parse_semver_cached
|
|
1109
|
+
return cls._semver_cache
|
|
1110
|
+
|
|
1111
|
+
@classmethod
|
|
1112
|
+
def _parse_semver(cls, version: str) -> ModelSemVer:
|
|
1113
|
+
"""Parse semantic version string into ModelSemVer for comparison.
|
|
1114
|
+
|
|
1115
|
+
This method implements SEMANTIC VERSION SORTING, not lexicographic sorting.
|
|
1116
|
+
This is critical for correct "latest version" selection.
|
|
1117
|
+
|
|
1118
|
+
Why This Matters (PR #36 feedback):
|
|
1119
|
+
Lexicographic sorting (string comparison):
|
|
1120
|
+
"1.10.0" < "1.9.0" WRONG (because '1' < '9' in strings)
|
|
1121
|
+
"10.0.0" < "2.0.0" WRONG (because '1' < '2' in strings)
|
|
1122
|
+
|
|
1123
|
+
Semantic version sorting (integer comparison):
|
|
1124
|
+
1.10.0 > 1.9.0 CORRECT (because 10 > 9 as integers)
|
|
1125
|
+
10.0.0 > 2.0.0 CORRECT (because 10 > 2 as integers)
|
|
1126
|
+
|
|
1127
|
+
Implementation:
|
|
1128
|
+
- Returns ModelSemVer instance with integer major, minor, patch
|
|
1129
|
+
- ModelSemVer implements comparison operators for correct ordering
|
|
1130
|
+
- Prerelease is parsed but NOT used in comparisons (major.minor.patch only)
|
|
1131
|
+
- "1.0.0-alpha" and "1.0.0" compare as EQUAL (same major.minor.patch)
|
|
1132
|
+
|
|
1133
|
+
Supported Formats:
|
|
1134
|
+
- Full: "1.2.3", "1.2.3-beta"
|
|
1135
|
+
- Partial: "1" -> (1, 0, 0), "1.2" -> (1, 2, 0)
|
|
1136
|
+
- Prerelease: "1.0.0-alpha", "2.1.0-rc.1"
|
|
1137
|
+
|
|
1138
|
+
Validation:
|
|
1139
|
+
- Rejects empty strings
|
|
1140
|
+
- Rejects non-numeric components
|
|
1141
|
+
- Rejects negative numbers
|
|
1142
|
+
- Rejects >3 version parts (e.g., "1.2.3.4")
|
|
1143
|
+
|
|
1144
|
+
Performance:
|
|
1145
|
+
This method uses an LRU cache with configurable size (default: 128)
|
|
1146
|
+
to avoid re-parsing the same version strings repeatedly, improving
|
|
1147
|
+
performance for lookups that compare multiple versions.
|
|
1148
|
+
|
|
1149
|
+
Cache Size Configuration:
|
|
1150
|
+
For large deployments, configure before first use:
|
|
1151
|
+
RegistryPolicy.configure_semver_cache(maxsize=256)
|
|
1152
|
+
|
|
1153
|
+
Cache Size Rationale (default 128):
|
|
1154
|
+
- Typical registry: 10-50 unique policy versions
|
|
1155
|
+
- Peak scenarios: 50-100 versions across multiple policy types
|
|
1156
|
+
- Each cache entry: ~200 bytes (string key + ModelSemVer instance)
|
|
1157
|
+
- Total memory: ~25.6KB worst case (negligible overhead)
|
|
1158
|
+
- Hit rate: >95% for repeated get() calls with version comparisons
|
|
1159
|
+
- Eviction: Rare in practice, LRU ensures least-used versions purged
|
|
1160
|
+
|
|
1161
|
+
Args:
|
|
1162
|
+
version: Semantic version string (e.g., "1.2.3" or "1.0.0-beta")
|
|
1163
|
+
|
|
1164
|
+
Returns:
|
|
1165
|
+
ModelSemVer instance for comparison.
|
|
1166
|
+
Components are INTEGERS (not strings) for correct semantic sorting.
|
|
1167
|
+
Prerelease is parsed and stored but ignored in version comparisons.
|
|
1168
|
+
|
|
1169
|
+
Raises:
|
|
1170
|
+
ProtocolConfigurationError: If version format is invalid
|
|
1171
|
+
|
|
1172
|
+
Examples:
|
|
1173
|
+
>>> RegistryPolicy._parse_semver("1.9.0")
|
|
1174
|
+
ModelSemVer(major=1, minor=9, patch=0, prerelease='')
|
|
1175
|
+
>>> RegistryPolicy._parse_semver("1.10.0")
|
|
1176
|
+
ModelSemVer(major=1, minor=10, patch=0, prerelease='')
|
|
1177
|
+
>>> RegistryPolicy._parse_semver("1.10.0") > RegistryPolicy._parse_semver("1.9.0")
|
|
1178
|
+
True
|
|
1179
|
+
>>> RegistryPolicy._parse_semver("10.0.0") > RegistryPolicy._parse_semver("2.0.0")
|
|
1180
|
+
True
|
|
1181
|
+
>>> RegistryPolicy._parse_semver("1.0.0-alpha")
|
|
1182
|
+
ModelSemVer(major=1, minor=0, patch=0, prerelease='alpha')
|
|
1183
|
+
>>> # Prerelease is parsed but NOT used in comparisons:
|
|
1184
|
+
>>> RegistryPolicy._parse_semver("1.0.0-alpha") == RegistryPolicy._parse_semver("1.0.0")
|
|
1185
|
+
True # Same major.minor.patch, prerelease ignored
|
|
1186
|
+
"""
|
|
1187
|
+
parser = cls._get_semver_parser()
|
|
1188
|
+
return parser(version)
|
|
1189
|
+
|
|
1190
|
+
@classmethod
|
|
1191
|
+
def _get_semver_cache_info(cls) -> functools._CacheInfo | None:
|
|
1192
|
+
"""Get cache statistics for the semver parser. For testing only.
|
|
1193
|
+
|
|
1194
|
+
Returns the cache_info() from the inner LRU-cached function.
|
|
1195
|
+
This allows tests to verify cache behavior without accessing
|
|
1196
|
+
internal implementation details.
|
|
1197
|
+
|
|
1198
|
+
Returns:
|
|
1199
|
+
functools._CacheInfo with hits, misses, maxsize, currsize,
|
|
1200
|
+
or None if cache not yet initialized.
|
|
1201
|
+
|
|
1202
|
+
Example:
|
|
1203
|
+
>>> RegistryPolicy._reset_semver_cache()
|
|
1204
|
+
>>> RegistryPolicy._parse_semver("1.0.0")
|
|
1205
|
+
>>> info = RegistryPolicy._get_semver_cache_info()
|
|
1206
|
+
>>> info.misses # First call is a miss
|
|
1207
|
+
1
|
|
1208
|
+
>>> RegistryPolicy._parse_semver("1.0.0")
|
|
1209
|
+
>>> info = RegistryPolicy._get_semver_cache_info()
|
|
1210
|
+
>>> info.hits # Second call is a hit
|
|
1211
|
+
1
|
|
1212
|
+
"""
|
|
1213
|
+
if cls._semver_cache_inner is None:
|
|
1214
|
+
return None
|
|
1215
|
+
# cache_info() is added by @lru_cache decorator
|
|
1216
|
+
# The return type is functools._CacheInfo
|
|
1217
|
+
result: functools._CacheInfo = cls._semver_cache_inner.cache_info() # type: ignore[attr-defined]
|
|
1218
|
+
return result
|
|
1219
|
+
|
|
1220
|
+
def _list_internal(self) -> list[tuple[str, str, str]]:
|
|
1221
|
+
"""Internal list method (assumes lock is held).
|
|
1222
|
+
|
|
1223
|
+
Returns:
|
|
1224
|
+
List of (policy_id, policy_type, version) tuples.
|
|
1225
|
+
"""
|
|
1226
|
+
return [
|
|
1227
|
+
k.to_tuple()
|
|
1228
|
+
for k in sorted(
|
|
1229
|
+
self._registry.keys(),
|
|
1230
|
+
key=lambda k: (k.policy_id, k.policy_type, k.version),
|
|
1231
|
+
)
|
|
1232
|
+
]
|
|
1233
|
+
|
|
1234
|
+
def list_keys(
|
|
1235
|
+
self,
|
|
1236
|
+
policy_type: PolicyTypeInput | None = None,
|
|
1237
|
+
) -> list[tuple[str, str, str]]:
|
|
1238
|
+
"""List registered policy keys as (id, type, version) tuples.
|
|
1239
|
+
|
|
1240
|
+
Args:
|
|
1241
|
+
policy_type: Optional filter to list only policies of a specific type.
|
|
1242
|
+
|
|
1243
|
+
Returns:
|
|
1244
|
+
List of (policy_id, policy_type, version) tuples, sorted alphabetically.
|
|
1245
|
+
|
|
1246
|
+
Example:
|
|
1247
|
+
>>> registry = RegistryPolicy()
|
|
1248
|
+
>>> registry.register("retry", RetryPolicy, EnumPolicyType.ORCHESTRATOR)
|
|
1249
|
+
>>> registry.register("merge", MergePolicy, EnumPolicyType.REDUCER)
|
|
1250
|
+
>>> print(registry.list_keys())
|
|
1251
|
+
[('merge', 'reducer', '1.0.0'), ('retry', 'orchestrator', '1.0.0')]
|
|
1252
|
+
>>> print(registry.list_keys(policy_type="orchestrator"))
|
|
1253
|
+
[('retry', 'orchestrator', '1.0.0')]
|
|
1254
|
+
"""
|
|
1255
|
+
# Normalize policy_type if provided
|
|
1256
|
+
# Use empty string as sentinel for "no filter" to reduce union types
|
|
1257
|
+
normalized_type: str = (
|
|
1258
|
+
self._normalize_policy_type(policy_type) if policy_type is not None else ""
|
|
1259
|
+
)
|
|
1260
|
+
|
|
1261
|
+
with self._lock:
|
|
1262
|
+
results: list[tuple[str, str, str]] = []
|
|
1263
|
+
for key in sorted(
|
|
1264
|
+
self._registry.keys(),
|
|
1265
|
+
key=lambda k: (k.policy_id, k.policy_type, k.version),
|
|
1266
|
+
):
|
|
1267
|
+
if normalized_type and key.policy_type != normalized_type:
|
|
1268
|
+
continue
|
|
1269
|
+
results.append(key.to_tuple())
|
|
1270
|
+
return results
|
|
1271
|
+
|
|
1272
|
+
def list_policy_types(self) -> list[str]:
|
|
1273
|
+
"""List registered policy types.
|
|
1274
|
+
|
|
1275
|
+
Returns:
|
|
1276
|
+
List of unique policy type strings that have registered policies.
|
|
1277
|
+
|
|
1278
|
+
Example:
|
|
1279
|
+
>>> registry = RegistryPolicy()
|
|
1280
|
+
>>> registry.register("retry", RetryPolicy, EnumPolicyType.ORCHESTRATOR)
|
|
1281
|
+
>>> print(registry.list_policy_types())
|
|
1282
|
+
['orchestrator']
|
|
1283
|
+
"""
|
|
1284
|
+
with self._lock:
|
|
1285
|
+
types = {key.policy_type for key in self._registry}
|
|
1286
|
+
return sorted(types)
|
|
1287
|
+
|
|
1288
|
+
def list_versions(self, policy_id: str) -> list[str]:
|
|
1289
|
+
"""List registered versions for a policy ID.
|
|
1290
|
+
|
|
1291
|
+
Args:
|
|
1292
|
+
policy_id: The policy ID to list versions for.
|
|
1293
|
+
|
|
1294
|
+
Returns:
|
|
1295
|
+
List of version strings registered for the policy ID, sorted.
|
|
1296
|
+
|
|
1297
|
+
Example:
|
|
1298
|
+
>>> registry = RegistryPolicy()
|
|
1299
|
+
>>> registry.register("retry", RetryPolicyV1, "orchestrator", "1.0.0")
|
|
1300
|
+
>>> registry.register("retry", RetryPolicyV2, "orchestrator", "2.0.0")
|
|
1301
|
+
>>> print(registry.list_versions("retry"))
|
|
1302
|
+
['1.0.0', '2.0.0']
|
|
1303
|
+
"""
|
|
1304
|
+
with self._lock:
|
|
1305
|
+
# Performance optimization: Use secondary index
|
|
1306
|
+
candidate_keys = self._policy_id_index.get(policy_id, [])
|
|
1307
|
+
versions = {key.version for key in candidate_keys}
|
|
1308
|
+
return sorted(versions)
|
|
1309
|
+
|
|
1310
|
+
def is_registered(
|
|
1311
|
+
self,
|
|
1312
|
+
policy_id: str,
|
|
1313
|
+
policy_type: PolicyTypeInput | None = None,
|
|
1314
|
+
version: str | None = None,
|
|
1315
|
+
) -> bool:
|
|
1316
|
+
"""Check if a policy is registered.
|
|
1317
|
+
|
|
1318
|
+
Args:
|
|
1319
|
+
policy_id: Policy identifier.
|
|
1320
|
+
policy_type: Optional policy type filter.
|
|
1321
|
+
version: Optional version filter.
|
|
1322
|
+
|
|
1323
|
+
Returns:
|
|
1324
|
+
True if a matching policy is registered, False otherwise.
|
|
1325
|
+
|
|
1326
|
+
Example:
|
|
1327
|
+
>>> registry = RegistryPolicy()
|
|
1328
|
+
>>> registry.register("retry", RetryPolicy, EnumPolicyType.ORCHESTRATOR)
|
|
1329
|
+
>>> registry.is_registered("retry")
|
|
1330
|
+
True
|
|
1331
|
+
>>> registry.is_registered("unknown")
|
|
1332
|
+
False
|
|
1333
|
+
"""
|
|
1334
|
+
# Normalize policy_type if provided
|
|
1335
|
+
# Use empty string as sentinel for "no filter" to reduce union types
|
|
1336
|
+
normalized_type: str = ""
|
|
1337
|
+
if policy_type is not None:
|
|
1338
|
+
try:
|
|
1339
|
+
normalized_type = self._normalize_policy_type(policy_type)
|
|
1340
|
+
except PolicyRegistryError:
|
|
1341
|
+
return False
|
|
1342
|
+
|
|
1343
|
+
# Normalize version for consistent lookup (e.g., "1.0" matches "1.0.0")
|
|
1344
|
+
normalized_version: str | None = None
|
|
1345
|
+
if version is not None:
|
|
1346
|
+
normalized_version = self._normalize_version(version)
|
|
1347
|
+
|
|
1348
|
+
with self._lock:
|
|
1349
|
+
# Performance optimization: Use secondary index
|
|
1350
|
+
candidate_keys = self._policy_id_index.get(policy_id, [])
|
|
1351
|
+
for key in candidate_keys:
|
|
1352
|
+
if normalized_type and key.policy_type != normalized_type:
|
|
1353
|
+
continue
|
|
1354
|
+
if normalized_version is not None and key.version != normalized_version:
|
|
1355
|
+
continue
|
|
1356
|
+
return True
|
|
1357
|
+
return False
|
|
1358
|
+
|
|
1359
|
+
def unregister(
|
|
1360
|
+
self,
|
|
1361
|
+
policy_id: str,
|
|
1362
|
+
policy_type: PolicyTypeInput | None = None,
|
|
1363
|
+
version: str | None = None,
|
|
1364
|
+
) -> int:
|
|
1365
|
+
"""Unregister policy plugins.
|
|
1366
|
+
|
|
1367
|
+
Removes policy registrations matching the given criteria.
|
|
1368
|
+
This is useful for testing and hot-reload scenarios.
|
|
1369
|
+
|
|
1370
|
+
Args:
|
|
1371
|
+
policy_id: Policy identifier to unregister.
|
|
1372
|
+
policy_type: Optional policy type filter.
|
|
1373
|
+
version: Optional version filter.
|
|
1374
|
+
|
|
1375
|
+
Returns:
|
|
1376
|
+
Number of policies unregistered.
|
|
1377
|
+
|
|
1378
|
+
Example:
|
|
1379
|
+
>>> registry = RegistryPolicy()
|
|
1380
|
+
>>> registry.register("retry", RetryPolicyV1, "orchestrator", "1.0.0")
|
|
1381
|
+
>>> registry.register("retry", RetryPolicyV2, "orchestrator", "2.0.0")
|
|
1382
|
+
>>> registry.unregister("retry") # Removes all versions
|
|
1383
|
+
2
|
|
1384
|
+
>>> registry.unregister("retry", version="1.0.0") # Remove specific version
|
|
1385
|
+
1
|
|
1386
|
+
"""
|
|
1387
|
+
# Normalize policy_type if provided
|
|
1388
|
+
# Use empty string as sentinel for "no filter" to reduce union types
|
|
1389
|
+
normalized_type: str = ""
|
|
1390
|
+
if policy_type is not None:
|
|
1391
|
+
try:
|
|
1392
|
+
normalized_type = self._normalize_policy_type(policy_type)
|
|
1393
|
+
except PolicyRegistryError:
|
|
1394
|
+
return 0
|
|
1395
|
+
|
|
1396
|
+
# Normalize version for consistent lookup (e.g., "1.0" matches "1.0.0")
|
|
1397
|
+
normalized_version: str | None = None
|
|
1398
|
+
if version is not None:
|
|
1399
|
+
normalized_version = self._normalize_version(version)
|
|
1400
|
+
|
|
1401
|
+
# Thread safety: Lock held during full unregister operation (write operation)
|
|
1402
|
+
with self._lock:
|
|
1403
|
+
# Performance optimization: Use secondary index
|
|
1404
|
+
candidate_keys = self._policy_id_index.get(policy_id, [])
|
|
1405
|
+
keys_to_remove: list[ModelPolicyKey] = []
|
|
1406
|
+
|
|
1407
|
+
for key in candidate_keys:
|
|
1408
|
+
if normalized_type and key.policy_type != normalized_type:
|
|
1409
|
+
continue
|
|
1410
|
+
if normalized_version is not None and key.version != normalized_version:
|
|
1411
|
+
continue
|
|
1412
|
+
keys_to_remove.append(key)
|
|
1413
|
+
|
|
1414
|
+
for key in keys_to_remove:
|
|
1415
|
+
del self._registry[key]
|
|
1416
|
+
# Update secondary index
|
|
1417
|
+
self._policy_id_index[policy_id].remove(key)
|
|
1418
|
+
|
|
1419
|
+
# Clean up empty index entries
|
|
1420
|
+
if (
|
|
1421
|
+
policy_id in self._policy_id_index
|
|
1422
|
+
and not self._policy_id_index[policy_id]
|
|
1423
|
+
):
|
|
1424
|
+
del self._policy_id_index[policy_id]
|
|
1425
|
+
|
|
1426
|
+
return len(keys_to_remove)
|
|
1427
|
+
|
|
1428
|
+
def clear(self) -> None:
|
|
1429
|
+
"""Clear all policy registrations.
|
|
1430
|
+
|
|
1431
|
+
Removes all registered policies from the registry.
|
|
1432
|
+
|
|
1433
|
+
Warning:
|
|
1434
|
+
This method is intended for **testing purposes only**.
|
|
1435
|
+
Calling it in production code will emit a warning.
|
|
1436
|
+
It breaks the immutability guarantee after startup.
|
|
1437
|
+
|
|
1438
|
+
Example:
|
|
1439
|
+
>>> registry = RegistryPolicy()
|
|
1440
|
+
>>> registry.register("retry", RetryPolicy, EnumPolicyType.ORCHESTRATOR)
|
|
1441
|
+
>>> registry.clear()
|
|
1442
|
+
>>> registry.list_keys()
|
|
1443
|
+
[]
|
|
1444
|
+
"""
|
|
1445
|
+
warnings.warn(
|
|
1446
|
+
"RegistryPolicy.clear() is intended for testing only. "
|
|
1447
|
+
"Do not use in production code.",
|
|
1448
|
+
UserWarning,
|
|
1449
|
+
stacklevel=2,
|
|
1450
|
+
)
|
|
1451
|
+
with self._lock:
|
|
1452
|
+
self._registry.clear()
|
|
1453
|
+
self._policy_id_index.clear()
|
|
1454
|
+
|
|
1455
|
+
def __len__(self) -> int:
|
|
1456
|
+
"""Return the number of registered policies.
|
|
1457
|
+
|
|
1458
|
+
Returns:
|
|
1459
|
+
Number of registered policy (id, type, version) combinations.
|
|
1460
|
+
|
|
1461
|
+
Example:
|
|
1462
|
+
>>> registry = RegistryPolicy()
|
|
1463
|
+
>>> len(registry)
|
|
1464
|
+
0
|
|
1465
|
+
>>> registry.register("retry", RetryPolicy, EnumPolicyType.ORCHESTRATOR)
|
|
1466
|
+
>>> len(registry)
|
|
1467
|
+
1
|
|
1468
|
+
"""
|
|
1469
|
+
with self._lock:
|
|
1470
|
+
return len(self._registry)
|
|
1471
|
+
|
|
1472
|
+
def __contains__(self, policy_id: str) -> bool:
|
|
1473
|
+
"""Check if policy ID is registered using 'in' operator.
|
|
1474
|
+
|
|
1475
|
+
Args:
|
|
1476
|
+
policy_id: Policy identifier.
|
|
1477
|
+
|
|
1478
|
+
Returns:
|
|
1479
|
+
True if policy ID is registered (any type/version), False otherwise.
|
|
1480
|
+
|
|
1481
|
+
Example:
|
|
1482
|
+
>>> registry = RegistryPolicy()
|
|
1483
|
+
>>> registry.register("retry", RetryPolicy, EnumPolicyType.ORCHESTRATOR)
|
|
1484
|
+
>>> "retry" in registry
|
|
1485
|
+
True
|
|
1486
|
+
>>> "unknown" in registry
|
|
1487
|
+
False
|
|
1488
|
+
"""
|
|
1489
|
+
return self.is_registered(policy_id)
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
# =============================================================================
|
|
1493
|
+
# Module Exports
|
|
1494
|
+
# =============================================================================
|
|
1495
|
+
|
|
1496
|
+
__all__: list[str] = [
|
|
1497
|
+
"ModelPolicyKey",
|
|
1498
|
+
# Models
|
|
1499
|
+
"ModelPolicyRegistration",
|
|
1500
|
+
# Registry class
|
|
1501
|
+
"RegistryPolicy",
|
|
1502
|
+
]
|