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,1486 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Infrastructure-specific validation wrappers.
|
|
3
|
+
|
|
4
|
+
Provides validators from omnibase_core with sensible defaults for infrastructure code.
|
|
5
|
+
All wrappers maintain strong typing and follow ONEX validation patterns.
|
|
6
|
+
|
|
7
|
+
Exemption System:
|
|
8
|
+
This module uses a YAML-based exemption system for managing validation exceptions.
|
|
9
|
+
Exemption patterns are defined in `validation_exemptions.yaml` alongside this module.
|
|
10
|
+
|
|
11
|
+
The exemption system provides:
|
|
12
|
+
- Centralized management of all validation exemptions
|
|
13
|
+
- Clear documentation of rationale and ticket references
|
|
14
|
+
- Regex-based matching resilient to code changes (no line numbers)
|
|
15
|
+
- Separation of exemption configuration from validation logic
|
|
16
|
+
|
|
17
|
+
See validation_exemptions.yaml for:
|
|
18
|
+
- pattern_exemptions: Method count, parameter count, naming violations
|
|
19
|
+
- union_exemptions: Complex union type violations
|
|
20
|
+
|
|
21
|
+
Adding new exemptions:
|
|
22
|
+
1. Identify the exact violation message from validator output
|
|
23
|
+
2. Add entry to appropriate section in validation_exemptions.yaml
|
|
24
|
+
3. Document the rationale and link to relevant tickets
|
|
25
|
+
4. Run tests to verify the exemption works
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Standard library imports
|
|
29
|
+
import ast
|
|
30
|
+
import logging
|
|
31
|
+
import re
|
|
32
|
+
from functools import lru_cache
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import TypedDict
|
|
35
|
+
|
|
36
|
+
# Third-party imports
|
|
37
|
+
import yaml
|
|
38
|
+
|
|
39
|
+
from omnibase_core.models.common import ModelValidationMetadata
|
|
40
|
+
from omnibase_core.models.validation.model_union_pattern import ModelUnionPattern
|
|
41
|
+
from omnibase_core.validation import (
|
|
42
|
+
CircularImportValidator,
|
|
43
|
+
ModelContractValidationResult,
|
|
44
|
+
ModelModuleImportResult,
|
|
45
|
+
ModelValidationResult,
|
|
46
|
+
validate_architecture,
|
|
47
|
+
validate_contracts,
|
|
48
|
+
validate_patterns,
|
|
49
|
+
validate_union_usage_file,
|
|
50
|
+
validate_yaml_file,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Local imports
|
|
54
|
+
from omnibase_infra.types import PathInput
|
|
55
|
+
|
|
56
|
+
# Module-level initialization (AFTER all imports)
|
|
57
|
+
logger = logging.getLogger(__name__)
|
|
58
|
+
|
|
59
|
+
# Type alias for cleaner return types in infrastructure validators
|
|
60
|
+
# Most validation results return None as the data payload (validation only)
|
|
61
|
+
# Using Python 3.12+ type keyword for modern type alias syntax
|
|
62
|
+
type ValidationResult = ModelValidationResult[None]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ExemptionPattern(TypedDict, total=False):
|
|
66
|
+
"""
|
|
67
|
+
Structure for validation exemption patterns.
|
|
68
|
+
|
|
69
|
+
Uses regex-based matching to handle code evolution gracefully without
|
|
70
|
+
hardcoded line numbers that break when code changes.
|
|
71
|
+
|
|
72
|
+
Fields:
|
|
73
|
+
file_pattern: Regex pattern matching the filename (e.g., r"event_bus_kafka\\.py")
|
|
74
|
+
class_pattern: Optional regex for class name (e.g., r"Class 'EventBusKafka'")
|
|
75
|
+
method_pattern: Optional regex for method name (e.g., r"Function '__init__'")
|
|
76
|
+
violation_pattern: Regex matching the violation type (e.g., r"too many (methods|parameters)")
|
|
77
|
+
|
|
78
|
+
Example:
|
|
79
|
+
{
|
|
80
|
+
"file_pattern": r"event_bus_kafka\\.py",
|
|
81
|
+
"class_pattern": r"Class 'EventBusKafka'",
|
|
82
|
+
"violation_pattern": r"has \\d+ methods"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Notes:
|
|
86
|
+
- Patterns are matched using re.search() for flexibility
|
|
87
|
+
- All specified patterns must match for an exemption to apply
|
|
88
|
+
- Omitted optional fields are not checked
|
|
89
|
+
- Use raw strings (r"...") for regex patterns
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
file_pattern: str
|
|
93
|
+
class_pattern: str
|
|
94
|
+
method_pattern: str
|
|
95
|
+
violation_pattern: str
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Path to the exemptions YAML file (alongside this module)
|
|
99
|
+
EXEMPTIONS_YAML_PATH = Path(__file__).parent / "validation_exemptions.yaml"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@lru_cache(maxsize=1)
|
|
103
|
+
def _load_exemptions_yaml() -> dict[str, list[ExemptionPattern]]:
|
|
104
|
+
"""
|
|
105
|
+
Load and cache exemption patterns from YAML configuration.
|
|
106
|
+
|
|
107
|
+
The exemption patterns are cached to avoid repeated file I/O during validation.
|
|
108
|
+
Cache is cleared when the module is reloaded.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dictionary with 'pattern_exemptions', 'union_exemptions', and
|
|
112
|
+
'architecture_exemptions' keys, each containing a list of
|
|
113
|
+
ExemptionPattern dictionaries.
|
|
114
|
+
Returns empty lists if file is missing or malformed.
|
|
115
|
+
|
|
116
|
+
Note:
|
|
117
|
+
The YAML file is expected to be at validation_exemptions.yaml alongside
|
|
118
|
+
this module. See that file for schema documentation and exemption rationale.
|
|
119
|
+
"""
|
|
120
|
+
if not EXEMPTIONS_YAML_PATH.exists():
|
|
121
|
+
# Fallback to empty exemptions if file is missing
|
|
122
|
+
return {
|
|
123
|
+
"pattern_exemptions": [],
|
|
124
|
+
"union_exemptions": [],
|
|
125
|
+
"architecture_exemptions": [],
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
with EXEMPTIONS_YAML_PATH.open("r", encoding="utf-8") as f:
|
|
130
|
+
data = yaml.safe_load(f)
|
|
131
|
+
|
|
132
|
+
if not isinstance(data, dict):
|
|
133
|
+
return {
|
|
134
|
+
"pattern_exemptions": [],
|
|
135
|
+
"union_exemptions": [],
|
|
136
|
+
"architecture_exemptions": [],
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Extract exemption lists, converting YAML structure to ExemptionPattern format
|
|
140
|
+
pattern_exemptions = _convert_yaml_exemptions(
|
|
141
|
+
data.get("pattern_exemptions", [])
|
|
142
|
+
)
|
|
143
|
+
union_exemptions = _convert_yaml_exemptions(data.get("union_exemptions", []))
|
|
144
|
+
architecture_exemptions = _convert_yaml_exemptions(
|
|
145
|
+
data.get("architecture_exemptions", [])
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"pattern_exemptions": pattern_exemptions,
|
|
150
|
+
"union_exemptions": union_exemptions,
|
|
151
|
+
"architecture_exemptions": architecture_exemptions,
|
|
152
|
+
}
|
|
153
|
+
except (yaml.YAMLError, OSError) as e:
|
|
154
|
+
# Log warning but continue with empty exemptions
|
|
155
|
+
logger.warning(
|
|
156
|
+
"Failed to load validation exemptions from %s: %s. Using empty exemptions.",
|
|
157
|
+
EXEMPTIONS_YAML_PATH,
|
|
158
|
+
e,
|
|
159
|
+
)
|
|
160
|
+
return {
|
|
161
|
+
"pattern_exemptions": [],
|
|
162
|
+
"union_exemptions": [],
|
|
163
|
+
"architecture_exemptions": [],
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _convert_yaml_exemptions(yaml_list: list[dict]) -> list[ExemptionPattern]:
|
|
168
|
+
"""
|
|
169
|
+
Convert YAML exemption entries to ExemptionPattern format.
|
|
170
|
+
|
|
171
|
+
The YAML format includes additional metadata (reason, ticket) that is used
|
|
172
|
+
for documentation but not for pattern matching. This function extracts only
|
|
173
|
+
the pattern fields needed for matching.
|
|
174
|
+
|
|
175
|
+
Regex patterns are validated at load time to prevent runtime errors during
|
|
176
|
+
validation. Entries with invalid regex patterns are skipped with a warning.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
yaml_list: List of exemption entries from YAML.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
List of ExemptionPattern dictionaries with only pattern fields.
|
|
183
|
+
Entries with invalid regex patterns are excluded.
|
|
184
|
+
|
|
185
|
+
Invalid Entry Handling:
|
|
186
|
+
This function is defensive and skips invalid entries to ensure
|
|
187
|
+
validation continues even with malformed exemption configuration:
|
|
188
|
+
|
|
189
|
+
- If yaml_list is not a list: returns empty list (no exemptions applied)
|
|
190
|
+
- If an entry is not a dict: entry is skipped silently
|
|
191
|
+
- If entry lacks required fields (file_pattern AND violation_pattern):
|
|
192
|
+
entry is skipped silently (both fields are required for meaningful matching)
|
|
193
|
+
- If any pattern field contains an invalid regex: entry is skipped
|
|
194
|
+
with a warning log (prevents runtime errors during pattern matching)
|
|
195
|
+
- All pattern field values are coerced to str via str() to handle
|
|
196
|
+
non-string values gracefully
|
|
197
|
+
|
|
198
|
+
Design Rationale:
|
|
199
|
+
Skipping invalid entries (rather than raising exceptions) is intentional:
|
|
200
|
+
1. Validation should not fail due to exemption configuration issues
|
|
201
|
+
2. Missing exemptions result in stricter validation (safer default)
|
|
202
|
+
3. Errors in exemption config are detected during exemption testing
|
|
203
|
+
4. Production validation continues even with partial exemption config
|
|
204
|
+
5. Invalid regex patterns are logged to aid debugging
|
|
205
|
+
"""
|
|
206
|
+
if not isinstance(yaml_list, list):
|
|
207
|
+
return []
|
|
208
|
+
|
|
209
|
+
result: list[ExemptionPattern] = []
|
|
210
|
+
for entry in yaml_list:
|
|
211
|
+
if not isinstance(entry, dict):
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
# Extract only pattern fields (ignore reason, ticket metadata)
|
|
215
|
+
# Validate each regex pattern before adding to prevent runtime errors
|
|
216
|
+
pattern: ExemptionPattern = {}
|
|
217
|
+
entry_valid = True
|
|
218
|
+
|
|
219
|
+
if "file_pattern" in entry:
|
|
220
|
+
file_pattern = str(entry["file_pattern"])
|
|
221
|
+
try:
|
|
222
|
+
re.compile(file_pattern)
|
|
223
|
+
pattern["file_pattern"] = file_pattern
|
|
224
|
+
except re.error as e:
|
|
225
|
+
logger.warning(
|
|
226
|
+
"Invalid regex in file_pattern '%s': %s. Skipping exemption entry.",
|
|
227
|
+
file_pattern,
|
|
228
|
+
e,
|
|
229
|
+
)
|
|
230
|
+
entry_valid = False
|
|
231
|
+
|
|
232
|
+
if entry_valid and "class_pattern" in entry:
|
|
233
|
+
class_pattern = str(entry["class_pattern"])
|
|
234
|
+
try:
|
|
235
|
+
re.compile(class_pattern)
|
|
236
|
+
pattern["class_pattern"] = class_pattern
|
|
237
|
+
except re.error as e:
|
|
238
|
+
logger.warning(
|
|
239
|
+
"Invalid regex in class_pattern '%s': %s. Skipping exemption entry.",
|
|
240
|
+
class_pattern,
|
|
241
|
+
e,
|
|
242
|
+
)
|
|
243
|
+
entry_valid = False
|
|
244
|
+
|
|
245
|
+
if entry_valid and "method_pattern" in entry:
|
|
246
|
+
method_pattern = str(entry["method_pattern"])
|
|
247
|
+
try:
|
|
248
|
+
re.compile(method_pattern)
|
|
249
|
+
pattern["method_pattern"] = method_pattern
|
|
250
|
+
except re.error as e:
|
|
251
|
+
logger.warning(
|
|
252
|
+
"Invalid regex in method_pattern '%s': %s. Skipping exemption entry.",
|
|
253
|
+
method_pattern,
|
|
254
|
+
e,
|
|
255
|
+
)
|
|
256
|
+
entry_valid = False
|
|
257
|
+
|
|
258
|
+
if entry_valid and "violation_pattern" in entry:
|
|
259
|
+
violation_pattern = str(entry["violation_pattern"])
|
|
260
|
+
try:
|
|
261
|
+
re.compile(violation_pattern)
|
|
262
|
+
pattern["violation_pattern"] = violation_pattern
|
|
263
|
+
except re.error as e:
|
|
264
|
+
logger.warning(
|
|
265
|
+
"Invalid regex in violation_pattern '%s': %s. Skipping exemption entry.",
|
|
266
|
+
violation_pattern,
|
|
267
|
+
e,
|
|
268
|
+
)
|
|
269
|
+
entry_valid = False
|
|
270
|
+
|
|
271
|
+
# Only include if entry is valid and has required patterns
|
|
272
|
+
if entry_valid and "file_pattern" in pattern and "violation_pattern" in pattern:
|
|
273
|
+
result.append(pattern)
|
|
274
|
+
|
|
275
|
+
return result
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def get_pattern_exemptions() -> list[ExemptionPattern]:
|
|
279
|
+
"""
|
|
280
|
+
Get pattern validator exemptions from YAML configuration.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
List of ExemptionPattern dictionaries for pattern validation.
|
|
284
|
+
"""
|
|
285
|
+
return _load_exemptions_yaml()["pattern_exemptions"]
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def get_union_exemptions() -> list[ExemptionPattern]:
|
|
289
|
+
"""
|
|
290
|
+
Get union validator exemptions from YAML configuration.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
List of ExemptionPattern dictionaries for union validation.
|
|
294
|
+
"""
|
|
295
|
+
return _load_exemptions_yaml()["union_exemptions"]
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def get_architecture_exemptions() -> list[ExemptionPattern]:
|
|
299
|
+
"""
|
|
300
|
+
Get architecture validator exemptions from YAML configuration.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
List of ExemptionPattern dictionaries for architecture validation.
|
|
304
|
+
"""
|
|
305
|
+
exemptions = _load_exemptions_yaml()
|
|
306
|
+
return exemptions.get("architecture_exemptions", [])
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# Default paths for infrastructure validation
|
|
310
|
+
INFRA_SRC_PATH = "src/omnibase_infra/"
|
|
311
|
+
INFRA_NODES_PATH = "src/omnibase_infra/nodes/"
|
|
312
|
+
|
|
313
|
+
# ============================================================================
|
|
314
|
+
# Pattern Validator Threshold Reference (from omnibase_core.validation)
|
|
315
|
+
# ============================================================================
|
|
316
|
+
# These thresholds are defined in omnibase_core and applied by validate_patterns().
|
|
317
|
+
# Documented here for reference and to explain infrastructure exemptions.
|
|
318
|
+
#
|
|
319
|
+
# See CLAUDE.md "Accepted Pattern Exceptions" section for full rationale.
|
|
320
|
+
# Ticket: OMN-934 (message dispatch engine implementation)
|
|
321
|
+
# Updated: PR #61 review feedback - added explicit threshold documentation
|
|
322
|
+
#
|
|
323
|
+
# DEFAULT_MAX_METHODS = 10 # Maximum methods per class
|
|
324
|
+
# DEFAULT_MAX_INIT_PARAMS = 5 # Maximum __init__ parameters
|
|
325
|
+
#
|
|
326
|
+
# Infrastructure Pattern Exemptions (OMN-934, PR #61):
|
|
327
|
+
# ----------------------------------------------------
|
|
328
|
+
# EventBusKafka (14 methods, 10 __init__ params):
|
|
329
|
+
# - Event bus pattern requires: lifecycle (start/stop/health), pub/sub
|
|
330
|
+
# (subscribe/unsubscribe/publish), circuit breaker, protocol compatibility
|
|
331
|
+
# - Backwards compatibility during config migration requires multiple __init__ params
|
|
332
|
+
# - See: event_bus_kafka.py class docstring, CLAUDE.md "Accepted Pattern Exceptions"
|
|
333
|
+
#
|
|
334
|
+
# RuntimeHostProcess (11+ methods, 6+ __init__ params):
|
|
335
|
+
# - Central coordinator requires: lifecycle management, message handling,
|
|
336
|
+
# graceful shutdown, handler management
|
|
337
|
+
# - See: runtime_host_process.py class docstring, CLAUDE.md "Accepted Pattern Exceptions"
|
|
338
|
+
#
|
|
339
|
+
# These exemptions are handled via exempted_patterns in validate_infra_patterns(),
|
|
340
|
+
# NOT by modifying global thresholds.
|
|
341
|
+
#
|
|
342
|
+
# Exemption Pattern Examples (explicit format):
|
|
343
|
+
# ---------------------------------------------
|
|
344
|
+
# EventBusKafka method count:
|
|
345
|
+
# {"file_pattern": r"event_bus_kafka\.py", "class_pattern": r"Class 'EventBusKafka'",
|
|
346
|
+
# "violation_pattern": r"has \d+ methods"}
|
|
347
|
+
#
|
|
348
|
+
# EventBusKafka __init__ params:
|
|
349
|
+
# {"file_pattern": r"event_bus_kafka\.py", "method_pattern": r"Function '__init__'",
|
|
350
|
+
# "violation_pattern": r"has \d+ parameters"}
|
|
351
|
+
#
|
|
352
|
+
# RuntimeHostProcess method count:
|
|
353
|
+
# {"file_pattern": r"runtime_host_process\.py", "class_pattern": r"Class 'RuntimeHostProcess'",
|
|
354
|
+
# "violation_pattern": r"has \d+ methods"}
|
|
355
|
+
#
|
|
356
|
+
# RuntimeHostProcess __init__ params:
|
|
357
|
+
# {"file_pattern": r"runtime_host_process\.py", "method_pattern": r"Function '__init__'",
|
|
358
|
+
# "violation_pattern": r"has \d+ parameters"}
|
|
359
|
+
#
|
|
360
|
+
# See exempted_patterns list in validate_infra_patterns() for complete definitions.
|
|
361
|
+
# ============================================================================
|
|
362
|
+
|
|
363
|
+
# Maximum allowed union count in infrastructure code.
|
|
364
|
+
# This threshold counts ONLY complex type annotation unions.
|
|
365
|
+
#
|
|
366
|
+
# Excluded patterns (NOT counted toward threshold):
|
|
367
|
+
# 1. Simple optionals (`X | None`) - idiomatic nullable pattern (PEP 604)
|
|
368
|
+
# 2. isinstance() unions (`isinstance(x, A | B)`) - runtime type checks, not annotations
|
|
369
|
+
#
|
|
370
|
+
# What IS counted (threshold applies to):
|
|
371
|
+
# - Multi-type unions in annotations: `def foo(x: str | int)`
|
|
372
|
+
# - Complex patterns: `dict[str, str | int]` (nested unions)
|
|
373
|
+
# - Unions with 3+ types (potential "primitive soup")
|
|
374
|
+
#
|
|
375
|
+
# What is NOT counted (excluded from threshold):
|
|
376
|
+
# - Simple optionals: `str | None`, `int | None`, `ModelFoo | None`
|
|
377
|
+
# - These are idiomatic Python nullable patterns, not complexity concerns
|
|
378
|
+
# - isinstance() unions: `isinstance(x, str | int)` (ruff UP038 modern syntax)
|
|
379
|
+
# - These are runtime type checks, not type annotations
|
|
380
|
+
# - Encouraged by ruff UP038 over isinstance(x, (str, int))
|
|
381
|
+
#
|
|
382
|
+
# Threshold history (after exclusion logic):
|
|
383
|
+
# - 120 (2025-12-25): Initial threshold after excluding ~470 `X | None` patterns
|
|
384
|
+
# - ~568 total unions in codebase
|
|
385
|
+
# - ~468 are simple `X | None` optionals (82%)
|
|
386
|
+
# - ~100 non-optional unions remain
|
|
387
|
+
# - Buffer of 20 above baseline for codebase growth
|
|
388
|
+
# - 121 (2025-12-25): OMN-881 introspection feature (+1 non-optional union)
|
|
389
|
+
# - 121 (2025-12-25): OMN-949 DLQ, OMN-816, OMN-811, OMN-1006 merges (all used X | None patterns, excluded)
|
|
390
|
+
# - 121 (2025-12-26): OMN-1007 registry pattern + merge with main (X | None patterns excluded)
|
|
391
|
+
# - 122 (2026-01-15): OMN-1203 corpus capture service, OMN-1346 extract registration domain plugin
|
|
392
|
+
# - 142 (2026-01-16): OMN-1305 ruff UP038 isinstance union syntax modernization (+20 unions)
|
|
393
|
+
# - 121 (2026-01-16): OMN-1305 isinstance union exclusion (excluding 21 isinstance unions)
|
|
394
|
+
# - Updated validator to exclude isinstance(x, A | B) patterns
|
|
395
|
+
# - These are runtime checks, not type annotations
|
|
396
|
+
# - 70 (2026-01-16): OMN-1358 type alias replacements reduced from 122 to 63 non-optional unions
|
|
397
|
+
# Applied HandlerMap, NodeId, PayloadDict, EventMetadata, MetadataDict type aliases
|
|
398
|
+
# - 81 (2026-01-16): OMN-1305 PR #151 merge with main - combined changes
|
|
399
|
+
# isinstance exclusion + type alias refactoring + PR #151 fixes
|
|
400
|
+
# - 83 (2026-01-16): OMN-1181 structured errors merge with main
|
|
401
|
+
# (+2 unions for EnumPolicyType | str in validate_policy_type_value)
|
|
402
|
+
# - 82 (2026-01-16): OMN-1181 fix PolicyTypeInput validator coercion
|
|
403
|
+
# (-1 union: changed return type from str | EnumPolicyType to EnumPolicyType)
|
|
404
|
+
# Validators now coerce strings to EnumPolicyType, ensuring type-safe access.
|
|
405
|
+
#
|
|
406
|
+
# Soft ceiling guidance:
|
|
407
|
+
# - 100-120: Healthy range, minor increments OK for legitimate features
|
|
408
|
+
# - 120-140: Caution zone, consider refactoring before incrementing
|
|
409
|
+
# - 140+: Refactor required - extract type aliases or use domain models from omnibase_core
|
|
410
|
+
#
|
|
411
|
+
# When incrementing threshold:
|
|
412
|
+
# 1. Document the ticket/PR that added unions in threshold history above
|
|
413
|
+
# 2. Verify new unions are not simple X | None (those should be excluded automatically)
|
|
414
|
+
# 3. Verify new unions are not isinstance() patterns (also excluded automatically)
|
|
415
|
+
# 4. Consider if a domain-specific type from omnibase_core would be cleaner
|
|
416
|
+
#
|
|
417
|
+
# Target: Keep below 150 - if this grows, consider typed patterns from omnibase_core.
|
|
418
|
+
# - 95 (2026-01-16): OMN-1142 Qdrant/Graph handlers (+14 legitimate union patterns)
|
|
419
|
+
# - str | int for graph node IDs (5 occurrences in handler_graph.py)
|
|
420
|
+
# - UUID | str for Qdrant point IDs (2 occurrences in Qdrant models)
|
|
421
|
+
# - float | int for score fields (1 occurrence)
|
|
422
|
+
# - 96 (2026-01-16): OMN-1181 structured errors merge with main (+1 net)
|
|
423
|
+
# (+2 unions for EnumPolicyType | str in validate_policy_type_value)
|
|
424
|
+
# (-1 union: fix PolicyTypeInput validator coercion, changed return
|
|
425
|
+
# type from str | EnumPolicyType to EnumPolicyType)
|
|
426
|
+
INFRA_MAX_UNIONS = 96
|
|
427
|
+
|
|
428
|
+
# Maximum allowed architecture violations in infrastructure code.
|
|
429
|
+
# Set to 0 (strict enforcement) to ensure one-model-per-file principle is always followed.
|
|
430
|
+
# Infrastructure nodes require strict architecture compliance for maintainability and
|
|
431
|
+
# contract-driven code generation.
|
|
432
|
+
INFRA_MAX_VIOLATIONS = 0
|
|
433
|
+
|
|
434
|
+
# Strict mode for pattern validation.
|
|
435
|
+
# Enabled: All violations must be exempted or fixed.
|
|
436
|
+
# See validate_infra_patterns() exempted_patterns list for documented exemptions.
|
|
437
|
+
INFRA_PATTERNS_STRICT = True
|
|
438
|
+
|
|
439
|
+
# Strict mode for union usage validation.
|
|
440
|
+
# Enabled: The validator will flag actual violations (not just count unions).
|
|
441
|
+
INFRA_UNIONS_STRICT = True
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def validate_infra_architecture(
|
|
445
|
+
directory: PathInput = INFRA_SRC_PATH,
|
|
446
|
+
max_violations: int = INFRA_MAX_VIOLATIONS,
|
|
447
|
+
) -> ValidationResult:
|
|
448
|
+
"""
|
|
449
|
+
Validate infrastructure architecture with strict defaults.
|
|
450
|
+
|
|
451
|
+
Enforces ONEX one-model-per-file principle critical for infrastructure nodes.
|
|
452
|
+
|
|
453
|
+
Exemptions:
|
|
454
|
+
Exemption patterns are loaded from validation_exemptions.yaml (architecture_exemptions section).
|
|
455
|
+
See that file for the complete list of exemptions with rationale and ticket references.
|
|
456
|
+
|
|
457
|
+
Key exemption categories:
|
|
458
|
+
- contract_linter.py: Domain-grouped validation models (PR-57)
|
|
459
|
+
- protocols.py: Domain-grouped protocols per CLAUDE.md convention (OMN-888)
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
directory: Directory to validate. Defaults to infrastructure source.
|
|
463
|
+
max_violations: Maximum allowed violations. Defaults to INFRA_MAX_VIOLATIONS (0).
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
ModelValidationResult with validation status and filtered errors.
|
|
467
|
+
Documented exemptions are filtered from error list but logged for transparency.
|
|
468
|
+
"""
|
|
469
|
+
# Run base validation
|
|
470
|
+
base_result = validate_architecture(str(directory), max_violations=max_violations)
|
|
471
|
+
|
|
472
|
+
# Load exemption patterns from YAML configuration
|
|
473
|
+
# See validation_exemptions.yaml for pattern definitions and rationale
|
|
474
|
+
exempted_patterns = get_architecture_exemptions()
|
|
475
|
+
|
|
476
|
+
# Filter errors using regex-based pattern matching
|
|
477
|
+
filtered_errors = _filter_exempted_errors(base_result.errors, exempted_patterns)
|
|
478
|
+
|
|
479
|
+
# Create wrapper result (avoid mutation)
|
|
480
|
+
return _create_filtered_result(base_result, filtered_errors)
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def validate_infra_contracts(
|
|
484
|
+
directory: PathInput = INFRA_NODES_PATH,
|
|
485
|
+
) -> ValidationResult:
|
|
486
|
+
"""
|
|
487
|
+
Validate all infrastructure node contracts.
|
|
488
|
+
|
|
489
|
+
Validates YAML contract files for Consul, Kafka, Vault, PostgreSQL adapters.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
directory: Directory containing node contracts. Defaults to nodes path.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
ModelValidationResult with validation status and any errors.
|
|
496
|
+
"""
|
|
497
|
+
return validate_contracts(str(directory))
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def validate_infra_patterns(
|
|
501
|
+
directory: PathInput = INFRA_SRC_PATH,
|
|
502
|
+
strict: bool = INFRA_PATTERNS_STRICT,
|
|
503
|
+
) -> ValidationResult:
|
|
504
|
+
"""
|
|
505
|
+
Validate infrastructure code patterns with infrastructure-specific exemptions.
|
|
506
|
+
|
|
507
|
+
Enforces:
|
|
508
|
+
- Model prefix naming (Model*)
|
|
509
|
+
- snake_case file naming
|
|
510
|
+
- Anti-pattern detection (no *Manager, *Handler, *Helper)
|
|
511
|
+
|
|
512
|
+
Exemptions:
|
|
513
|
+
Exemption patterns are loaded from validation_exemptions.yaml (pattern_exemptions section).
|
|
514
|
+
See that file for the complete list of exemptions with rationale and ticket references.
|
|
515
|
+
|
|
516
|
+
Key exemption categories:
|
|
517
|
+
- EventBusKafka: Event bus pattern with many methods/params (OMN-934)
|
|
518
|
+
- RuntimeHostProcess: Central coordinator pattern (OMN-756)
|
|
519
|
+
- RegistryPolicy: Domain registry pattern
|
|
520
|
+
- ExecutionShapeValidator: AST analysis validator pattern (OMN-958)
|
|
521
|
+
- MixinNodeIntrospection: Introspection mixin pattern (OMN-958)
|
|
522
|
+
|
|
523
|
+
Exemption Pattern Format:
|
|
524
|
+
Uses regex-based matching instead of hardcoded line numbers for resilience
|
|
525
|
+
to code changes. See ExemptionPattern TypedDict for structure details.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
directory: Directory to validate. Defaults to infrastructure source.
|
|
529
|
+
strict: Enable strict mode. Defaults to INFRA_PATTERNS_STRICT (True).
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
ModelValidationResult with validation status and filtered errors.
|
|
533
|
+
Documented exemptions are filtered from error list but logged for transparency.
|
|
534
|
+
"""
|
|
535
|
+
# Run base validation
|
|
536
|
+
base_result = validate_patterns(str(directory), strict=strict)
|
|
537
|
+
|
|
538
|
+
# Load exemption patterns from YAML configuration
|
|
539
|
+
# See validation_exemptions.yaml for pattern definitions and rationale
|
|
540
|
+
exempted_patterns = get_pattern_exemptions()
|
|
541
|
+
|
|
542
|
+
# Filter errors using regex-based pattern matching
|
|
543
|
+
filtered_errors = _filter_exempted_errors(base_result.errors, exempted_patterns)
|
|
544
|
+
|
|
545
|
+
# Create wrapper result (avoid mutation)
|
|
546
|
+
return _create_filtered_result(base_result, filtered_errors)
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _filter_exempted_errors(
|
|
550
|
+
errors: list[str],
|
|
551
|
+
exempted_patterns: list[ExemptionPattern],
|
|
552
|
+
) -> list[str]:
|
|
553
|
+
"""
|
|
554
|
+
Filter errors based on regex exemption patterns.
|
|
555
|
+
|
|
556
|
+
Uses regex-based matching to identify exempted violations without relying on
|
|
557
|
+
hardcoded line numbers or exact counts. This makes exemptions resilient to
|
|
558
|
+
code changes while still precisely targeting specific violations.
|
|
559
|
+
|
|
560
|
+
Pattern Matching Logic:
|
|
561
|
+
- All specified pattern fields must match for exemption to apply
|
|
562
|
+
- Unspecified optional fields are not checked (e.g., missing method_pattern)
|
|
563
|
+
- Uses re.search() for flexible substring matching
|
|
564
|
+
- Case-sensitive matching for precision
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
errors: List of error messages from validation.
|
|
568
|
+
exempted_patterns: List of ExemptionPattern dictionaries with regex patterns.
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
Filtered list of errors excluding exempted patterns.
|
|
572
|
+
Returns empty list if inputs are not the expected types.
|
|
573
|
+
|
|
574
|
+
Example:
|
|
575
|
+
Pattern:
|
|
576
|
+
{
|
|
577
|
+
"file_pattern": r"event_bus_kafka\\.py",
|
|
578
|
+
"class_pattern": r"Class 'EventBusKafka'",
|
|
579
|
+
"violation_pattern": r"has \\d+ methods"
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
Matches error:
|
|
583
|
+
"event_bus_kafka.py:123: Class 'EventBusKafka' has 14 methods (threshold: 10)"
|
|
584
|
+
|
|
585
|
+
Does not match:
|
|
586
|
+
"event_bus_kafka.py:50: Function 'connect' has 7 parameters" (no class_pattern)
|
|
587
|
+
"other_file.py:10: Class 'EventBusKafka' has 14 methods" (wrong file)
|
|
588
|
+
"""
|
|
589
|
+
# Defensive type checks for list inputs
|
|
590
|
+
if not isinstance(errors, list):
|
|
591
|
+
return []
|
|
592
|
+
if not isinstance(exempted_patterns, list):
|
|
593
|
+
# If no valid exemption patterns, return errors as-is (no filtering)
|
|
594
|
+
return [err for err in errors if isinstance(err, str)]
|
|
595
|
+
|
|
596
|
+
filtered = []
|
|
597
|
+
for err in errors:
|
|
598
|
+
# Skip non-string errors
|
|
599
|
+
if not isinstance(err, str):
|
|
600
|
+
continue
|
|
601
|
+
is_exempted = False
|
|
602
|
+
|
|
603
|
+
for pattern in exempted_patterns:
|
|
604
|
+
# Skip non-dict patterns
|
|
605
|
+
if not isinstance(pattern, dict):
|
|
606
|
+
continue
|
|
607
|
+
|
|
608
|
+
# Extract pattern fields (all are optional except file_pattern in practice)
|
|
609
|
+
file_pattern = pattern.get("file_pattern", "")
|
|
610
|
+
class_pattern = pattern.get("class_pattern", "")
|
|
611
|
+
method_pattern = pattern.get("method_pattern", "")
|
|
612
|
+
violation_pattern = pattern.get("violation_pattern", "")
|
|
613
|
+
|
|
614
|
+
# Check if all specified patterns match
|
|
615
|
+
# Skip unspecified (empty) patterns - they match everything
|
|
616
|
+
matches_file = not file_pattern or re.search(file_pattern, err)
|
|
617
|
+
matches_class = not class_pattern or re.search(class_pattern, err)
|
|
618
|
+
matches_method = not method_pattern or re.search(method_pattern, err)
|
|
619
|
+
matches_violation = not violation_pattern or re.search(
|
|
620
|
+
violation_pattern, err
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
# All specified patterns must match for exemption
|
|
624
|
+
if matches_file and matches_class and matches_method and matches_violation:
|
|
625
|
+
is_exempted = True
|
|
626
|
+
break
|
|
627
|
+
|
|
628
|
+
if not is_exempted:
|
|
629
|
+
filtered.append(err)
|
|
630
|
+
|
|
631
|
+
return filtered
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _create_filtered_result(
|
|
635
|
+
base_result: ValidationResult,
|
|
636
|
+
filtered_errors: list[str],
|
|
637
|
+
) -> ValidationResult:
|
|
638
|
+
"""
|
|
639
|
+
Create a new validation result with filtered errors (wrapper approach).
|
|
640
|
+
|
|
641
|
+
Avoids mutating the original result object for better functional programming practices.
|
|
642
|
+
Creates new metadata using model_validate to prevent mutation of Pydantic models.
|
|
643
|
+
|
|
644
|
+
Guards against missing attributes on base_result to handle edge cases where
|
|
645
|
+
validation results may have incomplete or missing fields.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
base_result: Original validation result.
|
|
649
|
+
filtered_errors: Filtered error list.
|
|
650
|
+
|
|
651
|
+
Returns:
|
|
652
|
+
New ValidationResult with filtered errors and updated metadata.
|
|
653
|
+
"""
|
|
654
|
+
# Guard against missing errors attribute on base_result
|
|
655
|
+
base_errors = getattr(base_result, "errors", None)
|
|
656
|
+
base_errors_count = len(base_errors) if base_errors is not None else 0
|
|
657
|
+
|
|
658
|
+
# Calculate filtering statistics
|
|
659
|
+
violations_filtered = base_errors_count - len(filtered_errors)
|
|
660
|
+
all_violations_exempted = violations_filtered > 0 and len(filtered_errors) == 0
|
|
661
|
+
|
|
662
|
+
# Create new metadata if present (avoid mutation)
|
|
663
|
+
# Use getattr to guard against missing metadata attribute on base_result
|
|
664
|
+
new_metadata = None
|
|
665
|
+
base_metadata = getattr(base_result, "metadata", None)
|
|
666
|
+
if base_metadata is not None:
|
|
667
|
+
# Use model_copy for deep copy with updates (Pydantic v2 pattern)
|
|
668
|
+
# This works with both real Pydantic models and test mocks
|
|
669
|
+
try:
|
|
670
|
+
new_metadata = base_metadata.model_copy(deep=True)
|
|
671
|
+
# Update violations_found if the field exists and is writable
|
|
672
|
+
# Guard against None return from model_copy and missing/read-only attributes
|
|
673
|
+
if new_metadata is not None and hasattr(new_metadata, "violations_found"):
|
|
674
|
+
try:
|
|
675
|
+
new_metadata.violations_found = len(filtered_errors)
|
|
676
|
+
except (AttributeError, TypeError):
|
|
677
|
+
# violations_found may be a read-only property or frozen field
|
|
678
|
+
pass
|
|
679
|
+
except AttributeError:
|
|
680
|
+
# Fallback for test mocks that don't support model_copy
|
|
681
|
+
# Use original metadata without modification to avoid mutation
|
|
682
|
+
new_metadata = base_metadata
|
|
683
|
+
|
|
684
|
+
# Guard against missing attributes on base_result
|
|
685
|
+
# Use getattr with sensible defaults to handle incomplete validation results
|
|
686
|
+
base_is_valid = getattr(base_result, "is_valid", False)
|
|
687
|
+
base_validated_value = getattr(base_result, "validated_value", None)
|
|
688
|
+
base_issues = getattr(base_result, "issues", [])
|
|
689
|
+
base_warnings = getattr(base_result, "warnings", [])
|
|
690
|
+
base_suggestions = getattr(base_result, "suggestions", [])
|
|
691
|
+
base_summary = getattr(base_result, "summary", None)
|
|
692
|
+
base_details = getattr(base_result, "details", None)
|
|
693
|
+
|
|
694
|
+
# Create new result (wrapper pattern - no mutation)
|
|
695
|
+
return ModelValidationResult(
|
|
696
|
+
is_valid=all_violations_exempted or base_is_valid,
|
|
697
|
+
validated_value=base_validated_value,
|
|
698
|
+
issues=base_issues if base_issues is not None else [],
|
|
699
|
+
errors=filtered_errors,
|
|
700
|
+
warnings=base_warnings if base_warnings is not None else [],
|
|
701
|
+
suggestions=base_suggestions if base_suggestions is not None else [],
|
|
702
|
+
summary=base_summary,
|
|
703
|
+
details=base_details,
|
|
704
|
+
metadata=new_metadata,
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def validate_infra_contract_deep(
|
|
709
|
+
contract_path: PathInput,
|
|
710
|
+
) -> ModelContractValidationResult:
|
|
711
|
+
"""
|
|
712
|
+
Perform deep contract validation for ONEX compliance.
|
|
713
|
+
|
|
714
|
+
Uses validate_yaml_file() from omnibase_core for comprehensive contract
|
|
715
|
+
checking suitable for autonomous code generation.
|
|
716
|
+
|
|
717
|
+
Args:
|
|
718
|
+
contract_path: Path to the contract YAML file.
|
|
719
|
+
|
|
720
|
+
Returns:
|
|
721
|
+
ModelContractValidationResult with validation status, score, and any errors.
|
|
722
|
+
|
|
723
|
+
Raises:
|
|
724
|
+
OnexError: If YAML validation fails with an unexpected error.
|
|
725
|
+
"""
|
|
726
|
+
from uuid import uuid4
|
|
727
|
+
|
|
728
|
+
from omnibase_core.enums import EnumCoreErrorCode
|
|
729
|
+
from omnibase_core.errors import OnexError
|
|
730
|
+
|
|
731
|
+
correlation_id = uuid4()
|
|
732
|
+
|
|
733
|
+
# Use the validation API from omnibase_core 0.6.x directly
|
|
734
|
+
try:
|
|
735
|
+
result = validate_yaml_file(Path(contract_path))
|
|
736
|
+
except Exception as e:
|
|
737
|
+
raise OnexError(
|
|
738
|
+
message=f"YAML validation failed for {contract_path}: {e}",
|
|
739
|
+
error_code=EnumCoreErrorCode.VALIDATION_ERROR,
|
|
740
|
+
correlation_id=correlation_id,
|
|
741
|
+
contract_path=str(contract_path),
|
|
742
|
+
) from e
|
|
743
|
+
|
|
744
|
+
# Return a ModelContractValidationResult
|
|
745
|
+
# The API may return a different type, so we adapt it
|
|
746
|
+
if isinstance(result, ModelContractValidationResult):
|
|
747
|
+
return result
|
|
748
|
+
|
|
749
|
+
# If result is a different type, wrap it in ModelContractValidationResult
|
|
750
|
+
# Default to passed=False for unknown result types to avoid silently masking failures
|
|
751
|
+
# Check 'passed' first, then 'is_valid' as fallback (some validators use is_valid)
|
|
752
|
+
return ModelContractValidationResult(
|
|
753
|
+
passed=getattr(result, "passed", getattr(result, "is_valid", False)),
|
|
754
|
+
score=getattr(result, "score", 0.0),
|
|
755
|
+
errors=getattr(result, "errors", []),
|
|
756
|
+
warnings=getattr(result, "warnings", []),
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
# ==============================================================================
|
|
761
|
+
# Skip Directory Configuration
|
|
762
|
+
# ==============================================================================
|
|
763
|
+
#
|
|
764
|
+
# Skip directories are loaded from validation_exemptions.yaml for configurability.
|
|
765
|
+
# If the YAML file is missing or doesn't contain skip_directories, we fall back
|
|
766
|
+
# to a hardcoded default set.
|
|
767
|
+
#
|
|
768
|
+
# This follows the same pattern as exemption loading to keep all validation
|
|
769
|
+
# configuration in one place.
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
@lru_cache(maxsize=1)
|
|
773
|
+
def load_skip_directories_from_yaml() -> frozenset[str] | None:
|
|
774
|
+
"""
|
|
775
|
+
Load skip directory configuration from YAML.
|
|
776
|
+
|
|
777
|
+
Returns:
|
|
778
|
+
frozenset of directory names to skip, or None if not configured in YAML.
|
|
779
|
+
Returns None (not empty set) to distinguish "not configured" from
|
|
780
|
+
"explicitly empty".
|
|
781
|
+
"""
|
|
782
|
+
if not EXEMPTIONS_YAML_PATH.exists():
|
|
783
|
+
return None
|
|
784
|
+
|
|
785
|
+
try:
|
|
786
|
+
with EXEMPTIONS_YAML_PATH.open("r", encoding="utf-8") as f:
|
|
787
|
+
data = yaml.safe_load(f)
|
|
788
|
+
|
|
789
|
+
if not isinstance(data, dict):
|
|
790
|
+
return None
|
|
791
|
+
|
|
792
|
+
skip_dirs = data.get("skip_directories")
|
|
793
|
+
if skip_dirs is None:
|
|
794
|
+
return None
|
|
795
|
+
|
|
796
|
+
# Handle both list and dict formats
|
|
797
|
+
if isinstance(skip_dirs, list):
|
|
798
|
+
# Simple list format: ["archive", "examples", ...]
|
|
799
|
+
return frozenset(str(d) for d in skip_dirs if d)
|
|
800
|
+
elif isinstance(skip_dirs, dict):
|
|
801
|
+
# Dict format with categories: {historical: [...], caches: [...]}
|
|
802
|
+
all_dirs: set[str] = set()
|
|
803
|
+
for category_dirs in skip_dirs.values():
|
|
804
|
+
if isinstance(category_dirs, list):
|
|
805
|
+
all_dirs.update(str(d) for d in category_dirs if d)
|
|
806
|
+
return frozenset(all_dirs) if all_dirs else None
|
|
807
|
+
|
|
808
|
+
return None
|
|
809
|
+
|
|
810
|
+
except (yaml.YAMLError, OSError) as e:
|
|
811
|
+
logger.warning(
|
|
812
|
+
"Failed to load skip directories from %s: %s. Using defaults.",
|
|
813
|
+
EXEMPTIONS_YAML_PATH,
|
|
814
|
+
e,
|
|
815
|
+
)
|
|
816
|
+
return None
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
def get_skip_directories() -> frozenset[str]:
|
|
820
|
+
"""
|
|
821
|
+
Get the set of directory names to skip during validation.
|
|
822
|
+
|
|
823
|
+
Returns skip directories from YAML configuration if available,
|
|
824
|
+
otherwise falls back to the hardcoded SKIP_DIRECTORY_NAMES default.
|
|
825
|
+
|
|
826
|
+
Returns:
|
|
827
|
+
frozenset of directory names that should be excluded from validation.
|
|
828
|
+
"""
|
|
829
|
+
yaml_dirs = load_skip_directories_from_yaml()
|
|
830
|
+
if yaml_dirs is not None:
|
|
831
|
+
return yaml_dirs
|
|
832
|
+
return SKIP_DIRECTORY_NAMES
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def is_simple_optional(pattern: ModelUnionPattern) -> bool:
|
|
836
|
+
"""
|
|
837
|
+
Determine if a union pattern is a simple optional (`X | None`).
|
|
838
|
+
|
|
839
|
+
Simple optionals are the ONEX-preferred pattern for nullable types and should
|
|
840
|
+
NOT count toward the union complexity threshold. They represent:
|
|
841
|
+
- `str | None` - nullable string
|
|
842
|
+
- `int | None` - nullable integer
|
|
843
|
+
- `ModelFoo | None` - nullable model
|
|
844
|
+
- `list[str] | None` - nullable list
|
|
845
|
+
|
|
846
|
+
These are NOT considered complex unions because:
|
|
847
|
+
1. They are idiomatic Python (PEP 604)
|
|
848
|
+
2. They express optionality, not type ambiguity
|
|
849
|
+
3. They don't require complex type narrowing logic
|
|
850
|
+
|
|
851
|
+
Args:
|
|
852
|
+
pattern: The ModelUnionPattern to check.
|
|
853
|
+
|
|
854
|
+
Returns:
|
|
855
|
+
True if the pattern is a simple optional (`X | None`), False otherwise.
|
|
856
|
+
|
|
857
|
+
Examples:
|
|
858
|
+
>>> is_simple_optional(ModelUnionPattern(["str", "None"], 1, "test.py"))
|
|
859
|
+
True
|
|
860
|
+
>>> is_simple_optional(ModelUnionPattern(["int", "None"], 1, "test.py"))
|
|
861
|
+
True
|
|
862
|
+
>>> is_simple_optional(ModelUnionPattern(["str", "int"], 1, "test.py"))
|
|
863
|
+
False
|
|
864
|
+
>>> is_simple_optional(ModelUnionPattern(["str", "int", "None"], 1, "test.py"))
|
|
865
|
+
False
|
|
866
|
+
"""
|
|
867
|
+
# Simple optional: exactly 2 types, one of which is None
|
|
868
|
+
return len(pattern.types) == 2 and "None" in pattern.types
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
# ==============================================================================
|
|
872
|
+
# isinstance() Union Exclusion
|
|
873
|
+
# ==============================================================================
|
|
874
|
+
#
|
|
875
|
+
# Modern Python (PEP 604) allows isinstance(x, A | B) syntax instead of
|
|
876
|
+
# isinstance(x, (A, B)). These are runtime type checks, NOT type annotations.
|
|
877
|
+
# Ruff UP038 encourages this modern syntax.
|
|
878
|
+
#
|
|
879
|
+
# The union validator's goal is to limit complex TYPE ANNOTATIONS that indicate
|
|
880
|
+
# type ambiguity in function signatures. isinstance() unions are:
|
|
881
|
+
# - Runtime expressions, not type hints
|
|
882
|
+
# - Used for dynamic type checking, not static typing
|
|
883
|
+
# - Encouraged by modern Python style guides (ruff UP038)
|
|
884
|
+
#
|
|
885
|
+
# Therefore, isinstance() unions should NOT count toward the union threshold.
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
class IsinstanceUnionVisitor(ast.NodeVisitor):
|
|
889
|
+
"""
|
|
890
|
+
AST visitor to find line numbers where unions appear inside isinstance() calls.
|
|
891
|
+
|
|
892
|
+
This visitor tracks context when descending into isinstance() call arguments,
|
|
893
|
+
marking any union (BitOr) expressions found in the second argument as
|
|
894
|
+
"isinstance unions" that should be excluded from the complexity threshold.
|
|
895
|
+
|
|
896
|
+
The visitor correctly handles:
|
|
897
|
+
- isinstance(x, A | B) - simple isinstance union
|
|
898
|
+
- isinstance(x, A | B | C) - multi-type isinstance union
|
|
899
|
+
- isinstance(x, list[A | B]) - union inside generic (NOT excluded, it's a type hint)
|
|
900
|
+
|
|
901
|
+
Attributes:
|
|
902
|
+
isinstance_union_lines: Set of line numbers containing isinstance unions.
|
|
903
|
+
"""
|
|
904
|
+
|
|
905
|
+
def __init__(self) -> None:
|
|
906
|
+
"""Initialize the visitor with empty line tracking."""
|
|
907
|
+
self.isinstance_union_lines: set[int] = set()
|
|
908
|
+
self._in_isinstance_type_arg: bool = False
|
|
909
|
+
|
|
910
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
911
|
+
"""
|
|
912
|
+
Visit a Call node and check if it's an isinstance() call.
|
|
913
|
+
|
|
914
|
+
When an isinstance() call is found, we mark that we're inside its
|
|
915
|
+
second argument (the type specification) before visiting children.
|
|
916
|
+
Any BitOr (union) found in this context is tracked.
|
|
917
|
+
|
|
918
|
+
Args:
|
|
919
|
+
node: The Call AST node to visit.
|
|
920
|
+
"""
|
|
921
|
+
# Check if this is an isinstance() call
|
|
922
|
+
is_isinstance_call = (
|
|
923
|
+
isinstance(node.func, ast.Name) and node.func.id == "isinstance"
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
if is_isinstance_call and len(node.args) >= 2:
|
|
927
|
+
# Visit the first argument (the object being checked) normally
|
|
928
|
+
self.visit(node.args[0])
|
|
929
|
+
|
|
930
|
+
# Mark that we're in the type argument, then visit it
|
|
931
|
+
self._in_isinstance_type_arg = True
|
|
932
|
+
self.visit(node.args[1])
|
|
933
|
+
self._in_isinstance_type_arg = False
|
|
934
|
+
|
|
935
|
+
# Visit any remaining arguments normally
|
|
936
|
+
for arg in node.args[2:]:
|
|
937
|
+
self.visit(arg)
|
|
938
|
+
|
|
939
|
+
# Visit keyword arguments normally
|
|
940
|
+
for keyword in node.keywords:
|
|
941
|
+
self.visit(keyword)
|
|
942
|
+
else:
|
|
943
|
+
# Not isinstance(), visit normally
|
|
944
|
+
self.generic_visit(node)
|
|
945
|
+
|
|
946
|
+
def visit_BinOp(self, node: ast.BinOp) -> None:
|
|
947
|
+
"""
|
|
948
|
+
Visit a BinOp node and track if it's a union inside isinstance().
|
|
949
|
+
|
|
950
|
+
When we're inside an isinstance() type argument and find a BitOr
|
|
951
|
+
(union) operator, we record this line as an isinstance union.
|
|
952
|
+
|
|
953
|
+
Args:
|
|
954
|
+
node: The BinOp AST node to visit.
|
|
955
|
+
"""
|
|
956
|
+
if self._in_isinstance_type_arg and isinstance(node.op, ast.BitOr):
|
|
957
|
+
# This is a union inside isinstance() - track the line
|
|
958
|
+
self.isinstance_union_lines.add(node.lineno)
|
|
959
|
+
|
|
960
|
+
# Continue visiting children (for nested unions like A | B | C)
|
|
961
|
+
self.generic_visit(node)
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
@lru_cache(maxsize=128)
|
|
965
|
+
def _find_isinstance_union_lines(file_path: Path) -> frozenset[int]:
|
|
966
|
+
"""
|
|
967
|
+
Find all line numbers containing unions inside isinstance() calls.
|
|
968
|
+
|
|
969
|
+
This function parses the file once and returns all lines where unions
|
|
970
|
+
appear inside isinstance() type arguments. Results are cached to avoid
|
|
971
|
+
repeated parsing when checking multiple patterns from the same file.
|
|
972
|
+
|
|
973
|
+
Args:
|
|
974
|
+
file_path: Path to the Python file to analyze.
|
|
975
|
+
|
|
976
|
+
Returns:
|
|
977
|
+
Frozenset of line numbers (1-based) containing isinstance unions.
|
|
978
|
+
Returns empty frozenset if file cannot be parsed or doesn't exist.
|
|
979
|
+
|
|
980
|
+
Examples:
|
|
981
|
+
>>> # File with: isinstance(x, str | int) on line 5
|
|
982
|
+
>>> _find_isinstance_union_lines(Path("example.py"))
|
|
983
|
+
frozenset({5})
|
|
984
|
+
|
|
985
|
+
Note:
|
|
986
|
+
Uses lru_cache to avoid re-parsing files during the same validation run.
|
|
987
|
+
Cache should be cleared between validation runs if files may have changed.
|
|
988
|
+
"""
|
|
989
|
+
try:
|
|
990
|
+
source = file_path.read_text(encoding="utf-8")
|
|
991
|
+
tree = ast.parse(source, filename=str(file_path))
|
|
992
|
+
except (OSError, SyntaxError) as e:
|
|
993
|
+
logger.debug("Cannot parse %s for isinstance detection: %s", file_path, e)
|
|
994
|
+
return frozenset()
|
|
995
|
+
|
|
996
|
+
visitor = IsinstanceUnionVisitor()
|
|
997
|
+
visitor.visit(tree)
|
|
998
|
+
return frozenset(visitor.isinstance_union_lines)
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
def is_isinstance_union(pattern: ModelUnionPattern) -> bool:
|
|
1002
|
+
"""
|
|
1003
|
+
Determine if a union pattern is inside an isinstance() call.
|
|
1004
|
+
|
|
1005
|
+
isinstance() unions are runtime type checks, not type annotations.
|
|
1006
|
+
They should NOT count toward the union complexity threshold because:
|
|
1007
|
+
1. They are runtime expressions, not static type hints
|
|
1008
|
+
2. Modern Python (PEP 604) encourages isinstance(x, A | B) syntax
|
|
1009
|
+
3. Ruff UP038 recommends this syntax over isinstance(x, (A, B))
|
|
1010
|
+
|
|
1011
|
+
Args:
|
|
1012
|
+
pattern: The ModelUnionPattern to check.
|
|
1013
|
+
|
|
1014
|
+
Returns:
|
|
1015
|
+
True if the pattern is inside an isinstance() call, False otherwise.
|
|
1016
|
+
|
|
1017
|
+
Examples:
|
|
1018
|
+
>>> # Pattern from: isinstance(x, str | int)
|
|
1019
|
+
>>> is_isinstance_union(pattern_from_isinstance)
|
|
1020
|
+
True
|
|
1021
|
+
>>> # Pattern from: def foo(x: str | int)
|
|
1022
|
+
>>> is_isinstance_union(pattern_from_annotation)
|
|
1023
|
+
False
|
|
1024
|
+
|
|
1025
|
+
Note:
|
|
1026
|
+
This function caches file parsing results for efficiency.
|
|
1027
|
+
"""
|
|
1028
|
+
file_path = Path(pattern.file_path)
|
|
1029
|
+
isinstance_lines = _find_isinstance_union_lines(file_path)
|
|
1030
|
+
return pattern.line in isinstance_lines
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
# ==============================================================================
|
|
1034
|
+
# Path Skipping Configuration
|
|
1035
|
+
# ==============================================================================
|
|
1036
|
+
#
|
|
1037
|
+
# These directories are excluded from validation because:
|
|
1038
|
+
# - archive/archived: Historical code not subject to current validation rules
|
|
1039
|
+
# - examples: Demo code that may intentionally show anti-patterns
|
|
1040
|
+
# - __pycache__: Compiled Python bytecode, not source code
|
|
1041
|
+
# - .git: Git repository metadata
|
|
1042
|
+
# - .venv/venv: Virtual environment directories
|
|
1043
|
+
# - .tox: Tox testing directory
|
|
1044
|
+
# - .mypy_cache: mypy type checking cache
|
|
1045
|
+
# - .pytest_cache: pytest cache directory
|
|
1046
|
+
# - build/dist: Build output directories
|
|
1047
|
+
# - .eggs: setuptools eggs directory
|
|
1048
|
+
# - node_modules: Node.js dependencies (if any JS in repo)
|
|
1049
|
+
#
|
|
1050
|
+
# The set is used for O(1) lookup when checking path components.
|
|
1051
|
+
#
|
|
1052
|
+
# Note: Matching is case-sensitive (Linux standard). On case-insensitive
|
|
1053
|
+
# filesystems (macOS, Windows), "Archive" would NOT match "archive".
|
|
1054
|
+
# This is intentional for portability and consistency.
|
|
1055
|
+
SKIP_DIRECTORY_NAMES: frozenset[str] = frozenset(
|
|
1056
|
+
{
|
|
1057
|
+
# Historical/demo code
|
|
1058
|
+
"archive",
|
|
1059
|
+
"archived",
|
|
1060
|
+
"examples",
|
|
1061
|
+
# Bytecode and caches
|
|
1062
|
+
"__pycache__",
|
|
1063
|
+
".mypy_cache",
|
|
1064
|
+
".pytest_cache",
|
|
1065
|
+
# Virtual environments
|
|
1066
|
+
".venv",
|
|
1067
|
+
"venv",
|
|
1068
|
+
# Build outputs
|
|
1069
|
+
"build",
|
|
1070
|
+
"dist",
|
|
1071
|
+
".eggs",
|
|
1072
|
+
# Version control
|
|
1073
|
+
".git",
|
|
1074
|
+
# Testing
|
|
1075
|
+
".tox",
|
|
1076
|
+
# Node.js (if present)
|
|
1077
|
+
"node_modules",
|
|
1078
|
+
}
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def is_skip_directory(component: str) -> bool:
|
|
1083
|
+
"""
|
|
1084
|
+
Check if a path component is a directory that should be skipped.
|
|
1085
|
+
|
|
1086
|
+
This predicate is extracted for reuse and testability. It checks if the
|
|
1087
|
+
given component matches one of the known skip directory names exactly.
|
|
1088
|
+
|
|
1089
|
+
Uses exact string matching (case-sensitive) via set membership for O(1) lookup.
|
|
1090
|
+
This prevents false positives from substring matching.
|
|
1091
|
+
|
|
1092
|
+
Skip directories are loaded from validation_exemptions.yaml if configured,
|
|
1093
|
+
otherwise falls back to the hardcoded SKIP_DIRECTORY_NAMES default.
|
|
1094
|
+
See get_skip_directories() for the configuration loading logic.
|
|
1095
|
+
|
|
1096
|
+
Args:
|
|
1097
|
+
component: A single path component (directory or file name).
|
|
1098
|
+
|
|
1099
|
+
Returns:
|
|
1100
|
+
True if the component is a skip directory name, False otherwise.
|
|
1101
|
+
|
|
1102
|
+
Examples:
|
|
1103
|
+
Exact matches (skipped):
|
|
1104
|
+
>>> is_skip_directory("archived")
|
|
1105
|
+
True
|
|
1106
|
+
>>> is_skip_directory("archive")
|
|
1107
|
+
True
|
|
1108
|
+
>>> is_skip_directory("__pycache__")
|
|
1109
|
+
True
|
|
1110
|
+
>>> is_skip_directory(".venv")
|
|
1111
|
+
True
|
|
1112
|
+
>>> is_skip_directory(".git")
|
|
1113
|
+
True
|
|
1114
|
+
|
|
1115
|
+
Partial/similar names (NOT skipped - prevents false positives):
|
|
1116
|
+
>>> is_skip_directory("archived_feature")
|
|
1117
|
+
False
|
|
1118
|
+
>>> is_skip_directory("my_archive")
|
|
1119
|
+
False
|
|
1120
|
+
>>> is_skip_directory("Archive") # Case-sensitive
|
|
1121
|
+
False
|
|
1122
|
+
>>> is_skip_directory(".git_backup")
|
|
1123
|
+
False
|
|
1124
|
+
"""
|
|
1125
|
+
return component in get_skip_directories()
|
|
1126
|
+
|
|
1127
|
+
|
|
1128
|
+
def should_skip_path(path: Path) -> bool:
|
|
1129
|
+
"""
|
|
1130
|
+
Check if a path should be skipped for validation.
|
|
1131
|
+
|
|
1132
|
+
Uses exact path component matching to avoid false positives from substring
|
|
1133
|
+
matching. A path is skipped if ANY of its PARENT directory components match
|
|
1134
|
+
a known skip directory name exactly. The filename itself is NOT checked
|
|
1135
|
+
to avoid false positives from files that happen to share names with skip
|
|
1136
|
+
directories (e.g., `archive.py` should not be skipped).
|
|
1137
|
+
|
|
1138
|
+
This approach prevents false positives like:
|
|
1139
|
+
- /foo/archived_feature/bar.py - NOT skipped ("archived_feature" != "archived")
|
|
1140
|
+
- /foo/archive_manager.py - NOT skipped (only checks parent dirs, not filename)
|
|
1141
|
+
- /foo/examples_utils.py - NOT skipped (only checks parent dirs, not filename)
|
|
1142
|
+
- /foo/my_archive/bar.py - NOT skipped ("my_archive" != "archive")
|
|
1143
|
+
- /foo/.git_backup/bar.py - NOT skipped (".git_backup" != ".git")
|
|
1144
|
+
|
|
1145
|
+
While correctly skipping:
|
|
1146
|
+
- /foo/archived/bar.py - Skipped (has "archived" directory component)
|
|
1147
|
+
- /foo/archive/bar.py - Skipped (has "archive" directory component)
|
|
1148
|
+
- /foo/examples/bar.py - Skipped (has "examples" directory component)
|
|
1149
|
+
- /foo/__pycache__/bar.pyc - Skipped (has "__pycache__" directory component)
|
|
1150
|
+
- /foo/.venv/lib/bar.py - Skipped (has ".venv" directory component)
|
|
1151
|
+
- /foo/.git/hooks/pre-commit - Skipped (has ".git" directory component)
|
|
1152
|
+
- /foo/build/lib/bar.py - Skipped (has "build" directory component)
|
|
1153
|
+
|
|
1154
|
+
Args:
|
|
1155
|
+
path: The file path to check.
|
|
1156
|
+
|
|
1157
|
+
Returns:
|
|
1158
|
+
True if the path should be skipped, False otherwise.
|
|
1159
|
+
|
|
1160
|
+
Note:
|
|
1161
|
+
Matching is case-sensitive (Linux standard). On case-insensitive
|
|
1162
|
+
filesystems (macOS, Windows), directories like "Build" or "VENV"
|
|
1163
|
+
would NOT be skipped. This is intentional for cross-platform
|
|
1164
|
+
consistency - use lowercase directory names for skipped directories.
|
|
1165
|
+
"""
|
|
1166
|
+
# Check PARENT directory components only (exclude the filename)
|
|
1167
|
+
# This prevents false positives from files named like skip directories
|
|
1168
|
+
# (e.g., archive.py, examples.py)
|
|
1169
|
+
#
|
|
1170
|
+
# path.parts includes all components including filename:
|
|
1171
|
+
# "/foo/archived/bar.py" -> ('/', 'foo', 'archived', 'bar.py')
|
|
1172
|
+
#
|
|
1173
|
+
# path.parent.parts excludes the filename:
|
|
1174
|
+
# "/foo/archived/bar.py" -> ('/', 'foo', 'archived')
|
|
1175
|
+
#
|
|
1176
|
+
# Using parent.parts ensures we only match DIRECTORY names, not filenames
|
|
1177
|
+
return any(is_skip_directory(part) for part in path.parent.parts)
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
def _count_non_optional_unions(
|
|
1181
|
+
directory: Path,
|
|
1182
|
+
) -> tuple[int, int, int, int, list[str]]:
|
|
1183
|
+
"""
|
|
1184
|
+
Count unions in a directory, excluding simple optional and isinstance patterns.
|
|
1185
|
+
|
|
1186
|
+
This function provides accurate union counting for threshold checks by
|
|
1187
|
+
excluding:
|
|
1188
|
+
- Idiomatic `X | None` patterns (simple optionals) that are valid ONEX style
|
|
1189
|
+
- isinstance(x, A | B) patterns that are runtime type checks, not annotations
|
|
1190
|
+
|
|
1191
|
+
Args:
|
|
1192
|
+
directory: Directory to scan for Python files.
|
|
1193
|
+
|
|
1194
|
+
Returns:
|
|
1195
|
+
Tuple of (threshold_count, total_count, optional_count, isinstance_count, issues):
|
|
1196
|
+
- threshold_count: Count of unions that count toward threshold
|
|
1197
|
+
(excludes both `X | None` and isinstance patterns)
|
|
1198
|
+
- total_count: Total count of all unions (for reporting)
|
|
1199
|
+
- optional_count: Count of simple `X | None` patterns excluded
|
|
1200
|
+
- isinstance_count: Count of isinstance unions excluded
|
|
1201
|
+
- issues: List of validation issues found
|
|
1202
|
+
"""
|
|
1203
|
+
total_unions = 0
|
|
1204
|
+
threshold_unions = 0
|
|
1205
|
+
optional_unions = 0
|
|
1206
|
+
isinstance_unions = 0
|
|
1207
|
+
all_issues: list[str] = []
|
|
1208
|
+
|
|
1209
|
+
for py_file in directory.rglob("*.py"):
|
|
1210
|
+
# Filter out archived files, examples, and __pycache__
|
|
1211
|
+
if should_skip_path(py_file):
|
|
1212
|
+
continue
|
|
1213
|
+
|
|
1214
|
+
union_count, issues, patterns = validate_union_usage_file(py_file)
|
|
1215
|
+
total_unions += union_count
|
|
1216
|
+
|
|
1217
|
+
# Count and categorize patterns
|
|
1218
|
+
for pattern in patterns:
|
|
1219
|
+
if is_simple_optional(pattern):
|
|
1220
|
+
optional_unions += 1
|
|
1221
|
+
elif is_isinstance_union(pattern):
|
|
1222
|
+
isinstance_unions += 1
|
|
1223
|
+
else:
|
|
1224
|
+
threshold_unions += 1
|
|
1225
|
+
|
|
1226
|
+
# Prefix issues with file path
|
|
1227
|
+
if issues:
|
|
1228
|
+
all_issues.extend([f"{py_file}: {issue}" for issue in issues])
|
|
1229
|
+
|
|
1230
|
+
return (
|
|
1231
|
+
threshold_unions,
|
|
1232
|
+
total_unions,
|
|
1233
|
+
optional_unions,
|
|
1234
|
+
isinstance_unions,
|
|
1235
|
+
all_issues,
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
def validate_infra_union_usage(
|
|
1240
|
+
directory: PathInput = INFRA_SRC_PATH,
|
|
1241
|
+
max_unions: int = INFRA_MAX_UNIONS,
|
|
1242
|
+
strict: bool = INFRA_UNIONS_STRICT,
|
|
1243
|
+
) -> ValidationResult:
|
|
1244
|
+
"""
|
|
1245
|
+
Validate Union type usage in infrastructure code.
|
|
1246
|
+
|
|
1247
|
+
Prevents overly complex union types that complicate infrastructure code.
|
|
1248
|
+
|
|
1249
|
+
This validator EXCLUDES the following patterns from the count:
|
|
1250
|
+
- Simple optional patterns (`X | None`) - idiomatic nullable types
|
|
1251
|
+
- isinstance() unions (`isinstance(x, A | B)`) - runtime type checks
|
|
1252
|
+
|
|
1253
|
+
Only actual complex TYPE ANNOTATIONS count toward the threshold.
|
|
1254
|
+
|
|
1255
|
+
What IS counted (threshold applies to):
|
|
1256
|
+
- Multi-type unions in annotations: `def foo(x: str | int)`
|
|
1257
|
+
- Complex patterns: unions with 3+ types in annotations
|
|
1258
|
+
- Non-optional type hints: any annotation union without `None`
|
|
1259
|
+
|
|
1260
|
+
What is NOT counted (excluded from threshold):
|
|
1261
|
+
- Simple optionals: `X | None` where X is any single type
|
|
1262
|
+
- isinstance() unions: `isinstance(x, A | B)` (runtime checks, not annotations)
|
|
1263
|
+
- These are either idiomatic Python or runtime expressions, not type complexity
|
|
1264
|
+
|
|
1265
|
+
Exemptions:
|
|
1266
|
+
Exemption patterns are loaded from validation_exemptions.yaml (union_exemptions section).
|
|
1267
|
+
See that file for the complete list of exemptions with rationale.
|
|
1268
|
+
|
|
1269
|
+
Key exemption categories:
|
|
1270
|
+
- ModelNodeCapabilities.config: JSON-like configuration pattern with primitive unions
|
|
1271
|
+
|
|
1272
|
+
Args:
|
|
1273
|
+
directory: Directory to validate. Defaults to infrastructure source.
|
|
1274
|
+
max_unions: Maximum union count threshold. Defaults to INFRA_MAX_UNIONS.
|
|
1275
|
+
Note: This threshold applies only after excluding optionals and isinstance.
|
|
1276
|
+
strict: Enable strict mode. Defaults to INFRA_UNIONS_STRICT (True).
|
|
1277
|
+
|
|
1278
|
+
Returns:
|
|
1279
|
+
ModelValidationResult with validation status and any errors.
|
|
1280
|
+
The metadata includes total_unions (all unions), threshold_unions (what counts),
|
|
1281
|
+
and breakdown of excluded patterns for transparency.
|
|
1282
|
+
|
|
1283
|
+
Metadata Extension Fields:
|
|
1284
|
+
ModelValidationMetadata uses `extra="allow"` to support domain-specific fields.
|
|
1285
|
+
The following extension fields are used by this validator and are properly typed:
|
|
1286
|
+
|
|
1287
|
+
- non_optional_unions (int): Count of unions after all exclusions.
|
|
1288
|
+
This is what the threshold check applies to.
|
|
1289
|
+
- optional_unions_excluded (int): Count of simple `X | None` optionals excluded.
|
|
1290
|
+
- isinstance_unions_excluded (int): Count of isinstance() unions excluded.
|
|
1291
|
+
|
|
1292
|
+
These fields are additional to the base ModelValidationMetadata fields like
|
|
1293
|
+
total_unions and max_unions which are formally defined on the model.
|
|
1294
|
+
"""
|
|
1295
|
+
# Convert to Path if string
|
|
1296
|
+
dir_path = Path(directory) if isinstance(directory, str) else directory
|
|
1297
|
+
|
|
1298
|
+
# Count unions with exclusion of simple optionals and isinstance patterns
|
|
1299
|
+
threshold_count, total_count, optional_count, isinstance_count, issues = (
|
|
1300
|
+
_count_non_optional_unions(dir_path)
|
|
1301
|
+
)
|
|
1302
|
+
|
|
1303
|
+
# Load exemption patterns from YAML configuration
|
|
1304
|
+
exempted_patterns = get_union_exemptions()
|
|
1305
|
+
|
|
1306
|
+
# Filter errors using regex-based pattern matching
|
|
1307
|
+
filtered_issues = _filter_exempted_errors(issues, exempted_patterns)
|
|
1308
|
+
|
|
1309
|
+
# Determine validity: threshold count must be within max
|
|
1310
|
+
# and no issues in strict mode
|
|
1311
|
+
is_valid = (threshold_count <= max_unions) and (not filtered_issues or not strict)
|
|
1312
|
+
|
|
1313
|
+
# Count Python files for metadata (excluding archive, examples, __pycache__)
|
|
1314
|
+
python_files = list(dir_path.rglob("*.py"))
|
|
1315
|
+
files_processed = len([f for f in python_files if not should_skip_path(f)])
|
|
1316
|
+
|
|
1317
|
+
# Create result with enhanced metadata showing all counts
|
|
1318
|
+
# Note: ModelValidationMetadata uses extra="allow", so extension fields
|
|
1319
|
+
# are accepted as int values.
|
|
1320
|
+
# See docstring "Metadata Extension Fields" section for field documentation.
|
|
1321
|
+
return ModelValidationResult(
|
|
1322
|
+
is_valid=is_valid,
|
|
1323
|
+
errors=filtered_issues,
|
|
1324
|
+
metadata=ModelValidationMetadata(
|
|
1325
|
+
# Standard ModelValidationMetadata fields (formally defined)
|
|
1326
|
+
validation_type="union_usage",
|
|
1327
|
+
files_processed=files_processed,
|
|
1328
|
+
violations_found=len(filtered_issues),
|
|
1329
|
+
total_unions=total_count, # Base field: all unions found
|
|
1330
|
+
max_unions=max_unions, # Base field: configured threshold
|
|
1331
|
+
strict_mode=strict, # Base field: whether strict mode enabled
|
|
1332
|
+
# Extension fields (via extra="allow", typed as int)
|
|
1333
|
+
# These provide transparency into the exclusion logic:
|
|
1334
|
+
non_optional_unions=threshold_count, # What threshold actually checks
|
|
1335
|
+
optional_unions_excluded=optional_count, # X | None patterns
|
|
1336
|
+
isinstance_unions_excluded=isinstance_count, # isinstance(x, A | B) patterns
|
|
1337
|
+
),
|
|
1338
|
+
)
|
|
1339
|
+
|
|
1340
|
+
|
|
1341
|
+
def validate_infra_circular_imports(
|
|
1342
|
+
directory: PathInput = INFRA_SRC_PATH,
|
|
1343
|
+
) -> ModelModuleImportResult:
|
|
1344
|
+
"""
|
|
1345
|
+
Check for circular imports in infrastructure code.
|
|
1346
|
+
|
|
1347
|
+
Infrastructure packages have complex dependencies; circular imports
|
|
1348
|
+
cause runtime issues that are hard to debug.
|
|
1349
|
+
|
|
1350
|
+
Args:
|
|
1351
|
+
directory: Directory to check. Defaults to infrastructure source.
|
|
1352
|
+
|
|
1353
|
+
Returns:
|
|
1354
|
+
ModelModuleImportResult with detailed import validation results.
|
|
1355
|
+
Use result.has_circular_imports to check for issues.
|
|
1356
|
+
"""
|
|
1357
|
+
validator = CircularImportValidator(source_path=Path(directory))
|
|
1358
|
+
return validator.validate()
|
|
1359
|
+
|
|
1360
|
+
|
|
1361
|
+
def validate_infra_all(
|
|
1362
|
+
directory: PathInput = INFRA_SRC_PATH,
|
|
1363
|
+
nodes_directory: PathInput = INFRA_NODES_PATH,
|
|
1364
|
+
) -> dict[str, ValidationResult | ModelModuleImportResult]:
|
|
1365
|
+
"""
|
|
1366
|
+
Run all validations on infrastructure code.
|
|
1367
|
+
|
|
1368
|
+
Executes all 5 validators with infrastructure-appropriate defaults:
|
|
1369
|
+
- Architecture (strict, 0 violations)
|
|
1370
|
+
- Contracts (nodes directory)
|
|
1371
|
+
- Patterns (strict mode)
|
|
1372
|
+
- Union usage (max INFRA_MAX_UNIONS)
|
|
1373
|
+
- Circular imports
|
|
1374
|
+
|
|
1375
|
+
Args:
|
|
1376
|
+
directory: Main source directory. Defaults to infrastructure source.
|
|
1377
|
+
nodes_directory: Nodes directory for contract validation.
|
|
1378
|
+
|
|
1379
|
+
Returns:
|
|
1380
|
+
Dictionary mapping validator name to result.
|
|
1381
|
+
"""
|
|
1382
|
+
results: dict[str, ValidationResult | ModelModuleImportResult] = {}
|
|
1383
|
+
|
|
1384
|
+
# HIGH priority validators
|
|
1385
|
+
results["architecture"] = validate_infra_architecture(directory)
|
|
1386
|
+
results["contracts"] = validate_infra_contracts(nodes_directory)
|
|
1387
|
+
results["patterns"] = validate_infra_patterns(directory)
|
|
1388
|
+
|
|
1389
|
+
# MEDIUM priority validators
|
|
1390
|
+
results["union_usage"] = validate_infra_union_usage(directory)
|
|
1391
|
+
results["circular_imports"] = validate_infra_circular_imports(directory)
|
|
1392
|
+
|
|
1393
|
+
return results
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
def get_validation_summary(
|
|
1397
|
+
results: dict[str, ValidationResult | ModelModuleImportResult],
|
|
1398
|
+
) -> dict[str, int | list[str]]:
|
|
1399
|
+
"""
|
|
1400
|
+
Generate a summary of validation results.
|
|
1401
|
+
|
|
1402
|
+
Args:
|
|
1403
|
+
results: Dictionary of validation results from validate_infra_all().
|
|
1404
|
+
|
|
1405
|
+
Returns:
|
|
1406
|
+
Dictionary with summary statistics including passed/failed counts and failed validators.
|
|
1407
|
+
Returns zero counts if input is not a dictionary.
|
|
1408
|
+
"""
|
|
1409
|
+
# Defensive type check for dict input
|
|
1410
|
+
if not isinstance(results, dict):
|
|
1411
|
+
return {
|
|
1412
|
+
"total_validators": 0,
|
|
1413
|
+
"passed": 0,
|
|
1414
|
+
"failed": 0,
|
|
1415
|
+
"failed_validators": [],
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
passed = 0
|
|
1419
|
+
failed = 0
|
|
1420
|
+
failed_validators: list[str] = []
|
|
1421
|
+
|
|
1422
|
+
for name, result in results.items():
|
|
1423
|
+
# Skip entries with non-string keys
|
|
1424
|
+
if not isinstance(name, str):
|
|
1425
|
+
continue
|
|
1426
|
+
# Use duck typing to determine result API:
|
|
1427
|
+
# - ModelModuleImportResult has 'has_circular_imports' attribute
|
|
1428
|
+
# - ModelValidationResult has 'is_valid' attribute
|
|
1429
|
+
# This follows ONEX convention of duck typing over isinstance for protocols.
|
|
1430
|
+
if hasattr(result, "has_circular_imports"):
|
|
1431
|
+
# Circular import validator uses has_circular_imports
|
|
1432
|
+
if not result.has_circular_imports:
|
|
1433
|
+
passed += 1
|
|
1434
|
+
else:
|
|
1435
|
+
failed += 1
|
|
1436
|
+
failed_validators.append(name)
|
|
1437
|
+
elif hasattr(result, "is_valid"):
|
|
1438
|
+
# Standard ModelValidationResult uses is_valid
|
|
1439
|
+
if result.is_valid:
|
|
1440
|
+
passed += 1
|
|
1441
|
+
else:
|
|
1442
|
+
failed += 1
|
|
1443
|
+
failed_validators.append(name)
|
|
1444
|
+
|
|
1445
|
+
return {
|
|
1446
|
+
"total_validators": passed + failed,
|
|
1447
|
+
"passed": passed,
|
|
1448
|
+
"failed": failed,
|
|
1449
|
+
"failed_validators": failed_validators,
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
|
|
1453
|
+
__all__ = [
|
|
1454
|
+
# Constants
|
|
1455
|
+
"EXEMPTIONS_YAML_PATH", # Path to exemptions YAML file
|
|
1456
|
+
"INFRA_MAX_UNIONS", # Maximum union count threshold
|
|
1457
|
+
"INFRA_MAX_VIOLATIONS", # Maximum violations threshold
|
|
1458
|
+
"INFRA_NODES_PATH", # Nodes directory path
|
|
1459
|
+
"INFRA_PATTERNS_STRICT", # Strict pattern validation flag
|
|
1460
|
+
"INFRA_SRC_PATH", # Source directory path
|
|
1461
|
+
"INFRA_UNIONS_STRICT", # Strict union validation flag
|
|
1462
|
+
"SKIP_DIRECTORY_NAMES", # Directories to skip
|
|
1463
|
+
# Types
|
|
1464
|
+
"ExemptionPattern", # Exemption pattern TypedDict
|
|
1465
|
+
"ModelModuleImportResult", # Re-export from omnibase_core
|
|
1466
|
+
"ValidationResult", # Type alias for validation result
|
|
1467
|
+
# Exemption loaders
|
|
1468
|
+
"get_architecture_exemptions", # Architecture exemption loader
|
|
1469
|
+
"get_pattern_exemptions", # Pattern exemption loader
|
|
1470
|
+
"get_skip_directories", # Skip directory loader
|
|
1471
|
+
"get_union_exemptions", # Union exemption loader
|
|
1472
|
+
"get_validation_summary", # Validation summary generator
|
|
1473
|
+
# Path utilities
|
|
1474
|
+
"is_isinstance_union", # Check if union is in isinstance() call
|
|
1475
|
+
"is_simple_optional", # Check if union is X | None
|
|
1476
|
+
"is_skip_directory", # Check if directory should be skipped
|
|
1477
|
+
"load_skip_directories_from_yaml", # Load skip dirs from YAML
|
|
1478
|
+
"should_skip_path", # Check if path should be skipped
|
|
1479
|
+
# Validators
|
|
1480
|
+
"validate_infra_all", # Run all validators
|
|
1481
|
+
"validate_infra_architecture", # Architecture validation
|
|
1482
|
+
"validate_infra_circular_imports", # Circular import check
|
|
1483
|
+
"validate_infra_contracts", # Contract validation
|
|
1484
|
+
"validate_infra_patterns", # Pattern validation
|
|
1485
|
+
"validate_infra_union_usage", # Union usage validation
|
|
1486
|
+
]
|