omnibase_infra 0.2.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (833) hide show
  1. omnibase_infra/__init__.py +101 -0
  2. omnibase_infra/adapters/adapter_onex_tool_execution.py +451 -0
  3. omnibase_infra/capabilities/__init__.py +15 -0
  4. omnibase_infra/capabilities/capability_inference_rules.py +211 -0
  5. omnibase_infra/capabilities/contract_capability_extractor.py +221 -0
  6. omnibase_infra/capabilities/intent_type_extractor.py +160 -0
  7. omnibase_infra/cli/__init__.py +1 -0
  8. omnibase_infra/cli/commands.py +216 -0
  9. omnibase_infra/clients/__init__.py +0 -0
  10. omnibase_infra/configs/widget_mapping.yaml +176 -0
  11. omnibase_infra/constants_topic_patterns.py +26 -0
  12. omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +264 -0
  13. omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +141 -0
  14. omnibase_infra/decorators/__init__.py +29 -0
  15. omnibase_infra/decorators/allow_any.py +109 -0
  16. omnibase_infra/dlq/__init__.py +90 -0
  17. omnibase_infra/dlq/constants_dlq.py +57 -0
  18. omnibase_infra/dlq/models/__init__.py +26 -0
  19. omnibase_infra/dlq/models/enum_replay_status.py +37 -0
  20. omnibase_infra/dlq/models/model_dlq_replay_record.py +135 -0
  21. omnibase_infra/dlq/models/model_dlq_tracking_config.py +184 -0
  22. omnibase_infra/dlq/service_dlq_tracking.py +611 -0
  23. omnibase_infra/enums/__init__.py +132 -0
  24. omnibase_infra/enums/enum_any_type_violation.py +104 -0
  25. omnibase_infra/enums/enum_backend_type.py +27 -0
  26. omnibase_infra/enums/enum_capture_outcome.py +42 -0
  27. omnibase_infra/enums/enum_capture_state.py +88 -0
  28. omnibase_infra/enums/enum_chain_violation_type.py +119 -0
  29. omnibase_infra/enums/enum_circuit_state.py +51 -0
  30. omnibase_infra/enums/enum_confirmation_event_type.py +27 -0
  31. omnibase_infra/enums/enum_consumer_group_purpose.py +92 -0
  32. omnibase_infra/enums/enum_contract_type.py +84 -0
  33. omnibase_infra/enums/enum_dedupe_strategy.py +46 -0
  34. omnibase_infra/enums/enum_dispatch_status.py +191 -0
  35. omnibase_infra/enums/enum_environment.py +46 -0
  36. omnibase_infra/enums/enum_execution_shape_violation.py +103 -0
  37. omnibase_infra/enums/enum_handler_error_type.py +111 -0
  38. omnibase_infra/enums/enum_handler_loader_error.py +178 -0
  39. omnibase_infra/enums/enum_handler_source_mode.py +86 -0
  40. omnibase_infra/enums/enum_handler_source_type.py +87 -0
  41. omnibase_infra/enums/enum_handler_type.py +77 -0
  42. omnibase_infra/enums/enum_handler_type_category.py +61 -0
  43. omnibase_infra/enums/enum_infra_transport_type.py +73 -0
  44. omnibase_infra/enums/enum_introspection_reason.py +154 -0
  45. omnibase_infra/enums/enum_kafka_acks.py +99 -0
  46. omnibase_infra/enums/enum_message_category.py +213 -0
  47. omnibase_infra/enums/enum_node_archetype.py +74 -0
  48. omnibase_infra/enums/enum_node_output_type.py +185 -0
  49. omnibase_infra/enums/enum_non_retryable_error_category.py +224 -0
  50. omnibase_infra/enums/enum_policy_type.py +32 -0
  51. omnibase_infra/enums/enum_registration_state.py +261 -0
  52. omnibase_infra/enums/enum_registration_status.py +33 -0
  53. omnibase_infra/enums/enum_registry_response_status.py +28 -0
  54. omnibase_infra/enums/enum_response_status.py +26 -0
  55. omnibase_infra/enums/enum_retry_error_category.py +98 -0
  56. omnibase_infra/enums/enum_security_rule_id.py +103 -0
  57. omnibase_infra/enums/enum_selection_strategy.py +91 -0
  58. omnibase_infra/enums/enum_topic_standard.py +42 -0
  59. omnibase_infra/enums/enum_validation_severity.py +78 -0
  60. omnibase_infra/errors/__init__.py +160 -0
  61. omnibase_infra/errors/error_architecture_violation.py +152 -0
  62. omnibase_infra/errors/error_binding_resolution.py +128 -0
  63. omnibase_infra/errors/error_chain_propagation.py +188 -0
  64. omnibase_infra/errors/error_compute_registry.py +95 -0
  65. omnibase_infra/errors/error_consul.py +132 -0
  66. omnibase_infra/errors/error_container_wiring.py +243 -0
  67. omnibase_infra/errors/error_event_bus_registry.py +105 -0
  68. omnibase_infra/errors/error_infra.py +610 -0
  69. omnibase_infra/errors/error_message_type_registry.py +101 -0
  70. omnibase_infra/errors/error_policy_registry.py +115 -0
  71. omnibase_infra/errors/error_vault.py +123 -0
  72. omnibase_infra/event_bus/__init__.py +72 -0
  73. omnibase_infra/event_bus/configs/kafka_event_bus_config.yaml +84 -0
  74. omnibase_infra/event_bus/event_bus_inmemory.py +797 -0
  75. omnibase_infra/event_bus/event_bus_kafka.py +1716 -0
  76. omnibase_infra/event_bus/mixin_kafka_broadcast.py +180 -0
  77. omnibase_infra/event_bus/mixin_kafka_dlq.py +771 -0
  78. omnibase_infra/event_bus/models/__init__.py +29 -0
  79. omnibase_infra/event_bus/models/config/__init__.py +20 -0
  80. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +693 -0
  81. omnibase_infra/event_bus/models/model_dlq_event.py +206 -0
  82. omnibase_infra/event_bus/models/model_dlq_metrics.py +304 -0
  83. omnibase_infra/event_bus/models/model_event_headers.py +115 -0
  84. omnibase_infra/event_bus/models/model_event_message.py +60 -0
  85. omnibase_infra/event_bus/testing/__init__.py +26 -0
  86. omnibase_infra/event_bus/testing/adapter_protocol_event_publisher_inmemory.py +418 -0
  87. omnibase_infra/event_bus/testing/model_publisher_metrics.py +64 -0
  88. omnibase_infra/event_bus/topic_constants.py +376 -0
  89. omnibase_infra/handlers/__init__.py +82 -0
  90. omnibase_infra/handlers/filesystem/__init__.py +48 -0
  91. omnibase_infra/handlers/filesystem/enum_file_system_operation.py +35 -0
  92. omnibase_infra/handlers/filesystem/model_file_system_request.py +298 -0
  93. omnibase_infra/handlers/filesystem/model_file_system_result.py +166 -0
  94. omnibase_infra/handlers/handler_consul.py +795 -0
  95. omnibase_infra/handlers/handler_db.py +1046 -0
  96. omnibase_infra/handlers/handler_filesystem.py +1478 -0
  97. omnibase_infra/handlers/handler_graph.py +2015 -0
  98. omnibase_infra/handlers/handler_http.py +926 -0
  99. omnibase_infra/handlers/handler_intent.py +387 -0
  100. omnibase_infra/handlers/handler_manifest_persistence.contract.yaml +184 -0
  101. omnibase_infra/handlers/handler_manifest_persistence.py +1539 -0
  102. omnibase_infra/handlers/handler_mcp.py +1430 -0
  103. omnibase_infra/handlers/handler_qdrant.py +1076 -0
  104. omnibase_infra/handlers/handler_vault.py +428 -0
  105. omnibase_infra/handlers/mcp/__init__.py +19 -0
  106. omnibase_infra/handlers/mcp/adapter_onex_to_mcp.py +446 -0
  107. omnibase_infra/handlers/mcp/protocols.py +178 -0
  108. omnibase_infra/handlers/mcp/transport_streamable_http.py +352 -0
  109. omnibase_infra/handlers/mixins/__init__.py +47 -0
  110. omnibase_infra/handlers/mixins/mixin_consul_initialization.py +349 -0
  111. omnibase_infra/handlers/mixins/mixin_consul_kv.py +338 -0
  112. omnibase_infra/handlers/mixins/mixin_consul_service.py +542 -0
  113. omnibase_infra/handlers/mixins/mixin_consul_topic_index.py +585 -0
  114. omnibase_infra/handlers/mixins/mixin_vault_initialization.py +338 -0
  115. omnibase_infra/handlers/mixins/mixin_vault_retry.py +412 -0
  116. omnibase_infra/handlers/mixins/mixin_vault_secrets.py +450 -0
  117. omnibase_infra/handlers/mixins/mixin_vault_token.py +365 -0
  118. omnibase_infra/handlers/models/__init__.py +286 -0
  119. omnibase_infra/handlers/models/consul/__init__.py +81 -0
  120. omnibase_infra/handlers/models/consul/enum_consul_operation_type.py +57 -0
  121. omnibase_infra/handlers/models/consul/model_consul_deregister_payload.py +51 -0
  122. omnibase_infra/handlers/models/consul/model_consul_handler_config.py +153 -0
  123. omnibase_infra/handlers/models/consul/model_consul_handler_payload.py +89 -0
  124. omnibase_infra/handlers/models/consul/model_consul_kv_get_found_payload.py +55 -0
  125. omnibase_infra/handlers/models/consul/model_consul_kv_get_not_found_payload.py +49 -0
  126. omnibase_infra/handlers/models/consul/model_consul_kv_get_recurse_payload.py +50 -0
  127. omnibase_infra/handlers/models/consul/model_consul_kv_item.py +33 -0
  128. omnibase_infra/handlers/models/consul/model_consul_kv_put_payload.py +41 -0
  129. omnibase_infra/handlers/models/consul/model_consul_register_payload.py +53 -0
  130. omnibase_infra/handlers/models/consul/model_consul_retry_config.py +66 -0
  131. omnibase_infra/handlers/models/consul/model_payload_consul.py +66 -0
  132. omnibase_infra/handlers/models/consul/registry_payload_consul.py +214 -0
  133. omnibase_infra/handlers/models/graph/__init__.py +35 -0
  134. omnibase_infra/handlers/models/graph/enum_graph_operation_type.py +20 -0
  135. omnibase_infra/handlers/models/graph/model_graph_execute_payload.py +38 -0
  136. omnibase_infra/handlers/models/graph/model_graph_handler_config.py +54 -0
  137. omnibase_infra/handlers/models/graph/model_graph_handler_payload.py +44 -0
  138. omnibase_infra/handlers/models/graph/model_graph_query_payload.py +40 -0
  139. omnibase_infra/handlers/models/graph/model_graph_record.py +22 -0
  140. omnibase_infra/handlers/models/http/__init__.py +50 -0
  141. omnibase_infra/handlers/models/http/enum_http_operation_type.py +29 -0
  142. omnibase_infra/handlers/models/http/model_http_body_content.py +45 -0
  143. omnibase_infra/handlers/models/http/model_http_get_payload.py +88 -0
  144. omnibase_infra/handlers/models/http/model_http_handler_payload.py +90 -0
  145. omnibase_infra/handlers/models/http/model_http_post_payload.py +88 -0
  146. omnibase_infra/handlers/models/http/model_payload_http.py +66 -0
  147. omnibase_infra/handlers/models/http/registry_payload_http.py +212 -0
  148. omnibase_infra/handlers/models/mcp/__init__.py +23 -0
  149. omnibase_infra/handlers/models/mcp/enum_mcp_operation_type.py +24 -0
  150. omnibase_infra/handlers/models/mcp/model_mcp_handler_config.py +40 -0
  151. omnibase_infra/handlers/models/mcp/model_mcp_tool_call.py +32 -0
  152. omnibase_infra/handlers/models/mcp/model_mcp_tool_result.py +45 -0
  153. omnibase_infra/handlers/models/model_consul_handler_response.py +96 -0
  154. omnibase_infra/handlers/models/model_db_describe_response.py +83 -0
  155. omnibase_infra/handlers/models/model_db_query_payload.py +95 -0
  156. omnibase_infra/handlers/models/model_db_query_response.py +60 -0
  157. omnibase_infra/handlers/models/model_filesystem_config.py +98 -0
  158. omnibase_infra/handlers/models/model_filesystem_delete_payload.py +54 -0
  159. omnibase_infra/handlers/models/model_filesystem_delete_result.py +77 -0
  160. omnibase_infra/handlers/models/model_filesystem_directory_entry.py +75 -0
  161. omnibase_infra/handlers/models/model_filesystem_ensure_directory_payload.py +54 -0
  162. omnibase_infra/handlers/models/model_filesystem_ensure_directory_result.py +60 -0
  163. omnibase_infra/handlers/models/model_filesystem_list_directory_payload.py +60 -0
  164. omnibase_infra/handlers/models/model_filesystem_list_directory_result.py +68 -0
  165. omnibase_infra/handlers/models/model_filesystem_read_payload.py +62 -0
  166. omnibase_infra/handlers/models/model_filesystem_read_result.py +61 -0
  167. omnibase_infra/handlers/models/model_filesystem_write_payload.py +70 -0
  168. omnibase_infra/handlers/models/model_filesystem_write_result.py +55 -0
  169. omnibase_infra/handlers/models/model_graph_handler_response.py +98 -0
  170. omnibase_infra/handlers/models/model_handler_response.py +103 -0
  171. omnibase_infra/handlers/models/model_http_handler_response.py +101 -0
  172. omnibase_infra/handlers/models/model_manifest_metadata.py +75 -0
  173. omnibase_infra/handlers/models/model_manifest_persistence_config.py +62 -0
  174. omnibase_infra/handlers/models/model_manifest_query_payload.py +90 -0
  175. omnibase_infra/handlers/models/model_manifest_query_result.py +97 -0
  176. omnibase_infra/handlers/models/model_manifest_retrieve_payload.py +44 -0
  177. omnibase_infra/handlers/models/model_manifest_retrieve_result.py +98 -0
  178. omnibase_infra/handlers/models/model_manifest_store_payload.py +47 -0
  179. omnibase_infra/handlers/models/model_manifest_store_result.py +67 -0
  180. omnibase_infra/handlers/models/model_operation_context.py +187 -0
  181. omnibase_infra/handlers/models/model_qdrant_handler_response.py +98 -0
  182. omnibase_infra/handlers/models/model_retry_state.py +162 -0
  183. omnibase_infra/handlers/models/model_vault_handler_response.py +98 -0
  184. omnibase_infra/handlers/models/qdrant/__init__.py +44 -0
  185. omnibase_infra/handlers/models/qdrant/enum_qdrant_operation_type.py +26 -0
  186. omnibase_infra/handlers/models/qdrant/model_qdrant_collection_payload.py +42 -0
  187. omnibase_infra/handlers/models/qdrant/model_qdrant_delete_payload.py +36 -0
  188. omnibase_infra/handlers/models/qdrant/model_qdrant_handler_config.py +42 -0
  189. omnibase_infra/handlers/models/qdrant/model_qdrant_handler_payload.py +54 -0
  190. omnibase_infra/handlers/models/qdrant/model_qdrant_search_payload.py +42 -0
  191. omnibase_infra/handlers/models/qdrant/model_qdrant_search_result.py +30 -0
  192. omnibase_infra/handlers/models/qdrant/model_qdrant_upsert_payload.py +36 -0
  193. omnibase_infra/handlers/models/vault/__init__.py +69 -0
  194. omnibase_infra/handlers/models/vault/enum_vault_operation_type.py +35 -0
  195. omnibase_infra/handlers/models/vault/model_payload_vault.py +66 -0
  196. omnibase_infra/handlers/models/vault/model_vault_delete_payload.py +57 -0
  197. omnibase_infra/handlers/models/vault/model_vault_handler_config.py +148 -0
  198. omnibase_infra/handlers/models/vault/model_vault_handler_payload.py +101 -0
  199. omnibase_infra/handlers/models/vault/model_vault_list_payload.py +58 -0
  200. omnibase_infra/handlers/models/vault/model_vault_renew_token_payload.py +67 -0
  201. omnibase_infra/handlers/models/vault/model_vault_retry_config.py +66 -0
  202. omnibase_infra/handlers/models/vault/model_vault_secret_payload.py +106 -0
  203. omnibase_infra/handlers/models/vault/model_vault_write_payload.py +66 -0
  204. omnibase_infra/handlers/models/vault/registry_payload_vault.py +213 -0
  205. omnibase_infra/handlers/registration_storage/__init__.py +43 -0
  206. omnibase_infra/handlers/registration_storage/handler_registration_storage_mock.py +392 -0
  207. omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +922 -0
  208. omnibase_infra/handlers/registration_storage/models/__init__.py +23 -0
  209. omnibase_infra/handlers/registration_storage/models/model_delete_registration_request.py +58 -0
  210. omnibase_infra/handlers/registration_storage/models/model_update_registration_request.py +73 -0
  211. omnibase_infra/handlers/registration_storage/protocol_registration_persistence.py +191 -0
  212. omnibase_infra/handlers/service_discovery/__init__.py +43 -0
  213. omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +1051 -0
  214. omnibase_infra/handlers/service_discovery/handler_service_discovery_mock.py +258 -0
  215. omnibase_infra/handlers/service_discovery/models/__init__.py +22 -0
  216. omnibase_infra/handlers/service_discovery/models/model_discovery_result.py +64 -0
  217. omnibase_infra/handlers/service_discovery/models/model_registration_result.py +138 -0
  218. omnibase_infra/handlers/service_discovery/models/model_service_info.py +109 -0
  219. omnibase_infra/handlers/service_discovery/protocol_discovery_operations.py +170 -0
  220. omnibase_infra/idempotency/__init__.py +94 -0
  221. omnibase_infra/idempotency/models/__init__.py +43 -0
  222. omnibase_infra/idempotency/models/model_idempotency_check_result.py +85 -0
  223. omnibase_infra/idempotency/models/model_idempotency_guard_config.py +130 -0
  224. omnibase_infra/idempotency/models/model_idempotency_record.py +86 -0
  225. omnibase_infra/idempotency/models/model_idempotency_store_health_check_result.py +81 -0
  226. omnibase_infra/idempotency/models/model_idempotency_store_metrics.py +140 -0
  227. omnibase_infra/idempotency/models/model_postgres_idempotency_store_config.py +299 -0
  228. omnibase_infra/idempotency/protocol_idempotency_store.py +184 -0
  229. omnibase_infra/idempotency/store_inmemory.py +265 -0
  230. omnibase_infra/idempotency/store_postgres.py +923 -0
  231. omnibase_infra/infrastructure/__init__.py +0 -0
  232. omnibase_infra/migrations/001_create_event_ledger.sql +166 -0
  233. omnibase_infra/migrations/001_drop_event_ledger.sql +18 -0
  234. omnibase_infra/mixins/__init__.py +71 -0
  235. omnibase_infra/mixins/mixin_async_circuit_breaker.py +656 -0
  236. omnibase_infra/mixins/mixin_dict_like_accessors.py +146 -0
  237. omnibase_infra/mixins/mixin_envelope_extraction.py +119 -0
  238. omnibase_infra/mixins/mixin_node_introspection.py +2670 -0
  239. omnibase_infra/mixins/mixin_retry_execution.py +386 -0
  240. omnibase_infra/mixins/protocol_circuit_breaker_aware.py +133 -0
  241. omnibase_infra/models/__init__.py +144 -0
  242. omnibase_infra/models/bindings/__init__.py +59 -0
  243. omnibase_infra/models/bindings/constants.py +144 -0
  244. omnibase_infra/models/bindings/model_binding_resolution_result.py +103 -0
  245. omnibase_infra/models/bindings/model_operation_binding.py +44 -0
  246. omnibase_infra/models/bindings/model_operation_bindings_subcontract.py +152 -0
  247. omnibase_infra/models/bindings/model_parsed_binding.py +52 -0
  248. omnibase_infra/models/corpus/__init__.py +17 -0
  249. omnibase_infra/models/corpus/model_capture_config.py +133 -0
  250. omnibase_infra/models/corpus/model_capture_result.py +86 -0
  251. omnibase_infra/models/discovery/__init__.py +42 -0
  252. omnibase_infra/models/discovery/model_dependency_spec.py +319 -0
  253. omnibase_infra/models/discovery/model_discovered_capabilities.py +50 -0
  254. omnibase_infra/models/discovery/model_introspection_config.py +330 -0
  255. omnibase_infra/models/discovery/model_introspection_performance_metrics.py +169 -0
  256. omnibase_infra/models/discovery/model_introspection_task_config.py +116 -0
  257. omnibase_infra/models/dispatch/__init__.py +155 -0
  258. omnibase_infra/models/dispatch/model_debug_trace_snapshot.py +114 -0
  259. omnibase_infra/models/dispatch/model_dispatch_context.py +439 -0
  260. omnibase_infra/models/dispatch/model_dispatch_error.py +336 -0
  261. omnibase_infra/models/dispatch/model_dispatch_log_context.py +400 -0
  262. omnibase_infra/models/dispatch/model_dispatch_metadata.py +228 -0
  263. omnibase_infra/models/dispatch/model_dispatch_metrics.py +496 -0
  264. omnibase_infra/models/dispatch/model_dispatch_outcome.py +317 -0
  265. omnibase_infra/models/dispatch/model_dispatch_outputs.py +231 -0
  266. omnibase_infra/models/dispatch/model_dispatch_result.py +436 -0
  267. omnibase_infra/models/dispatch/model_dispatch_route.py +279 -0
  268. omnibase_infra/models/dispatch/model_dispatcher_metrics.py +275 -0
  269. omnibase_infra/models/dispatch/model_dispatcher_registration.py +352 -0
  270. omnibase_infra/models/dispatch/model_materialized_dispatch.py +141 -0
  271. omnibase_infra/models/dispatch/model_parsed_topic.py +135 -0
  272. omnibase_infra/models/dispatch/model_topic_parser.py +725 -0
  273. omnibase_infra/models/dispatch/model_tracing_context.py +285 -0
  274. omnibase_infra/models/errors/__init__.py +45 -0
  275. omnibase_infra/models/errors/model_handler_validation_error.py +594 -0
  276. omnibase_infra/models/errors/model_infra_error_context.py +99 -0
  277. omnibase_infra/models/errors/model_message_type_registry_error_context.py +71 -0
  278. omnibase_infra/models/errors/model_timeout_error_context.py +110 -0
  279. omnibase_infra/models/handlers/__init__.py +80 -0
  280. omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
  281. omnibase_infra/models/handlers/model_contract_discovery_result.py +82 -0
  282. omnibase_infra/models/handlers/model_handler_descriptor.py +200 -0
  283. omnibase_infra/models/handlers/model_handler_identifier.py +215 -0
  284. omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
  285. omnibase_infra/models/health/__init__.py +9 -0
  286. omnibase_infra/models/health/model_health_check_result.py +40 -0
  287. omnibase_infra/models/lifecycle/__init__.py +39 -0
  288. omnibase_infra/models/logging/__init__.py +51 -0
  289. omnibase_infra/models/logging/model_log_context.py +756 -0
  290. omnibase_infra/models/mcp/__init__.py +15 -0
  291. omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
  292. omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
  293. omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
  294. omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
  295. omnibase_infra/models/model_node_identity.py +126 -0
  296. omnibase_infra/models/model_retry_error_classification.py +78 -0
  297. omnibase_infra/models/projection/__init__.py +43 -0
  298. omnibase_infra/models/projection/model_capability_fields.py +112 -0
  299. omnibase_infra/models/projection/model_registration_projection.py +434 -0
  300. omnibase_infra/models/projection/model_registration_snapshot.py +322 -0
  301. omnibase_infra/models/projection/model_sequence_info.py +182 -0
  302. omnibase_infra/models/projection/model_snapshot_topic_config.py +591 -0
  303. omnibase_infra/models/projectors/__init__.py +41 -0
  304. omnibase_infra/models/projectors/model_projector_column.py +289 -0
  305. omnibase_infra/models/projectors/model_projector_discovery_result.py +65 -0
  306. omnibase_infra/models/projectors/model_projector_index.py +270 -0
  307. omnibase_infra/models/projectors/model_projector_schema.py +415 -0
  308. omnibase_infra/models/projectors/model_projector_validation_error.py +63 -0
  309. omnibase_infra/models/projectors/util_sql_identifiers.py +115 -0
  310. omnibase_infra/models/registration/__init__.py +68 -0
  311. omnibase_infra/models/registration/commands/__init__.py +15 -0
  312. omnibase_infra/models/registration/commands/model_node_registration_acked.py +108 -0
  313. omnibase_infra/models/registration/events/__init__.py +56 -0
  314. omnibase_infra/models/registration/events/model_node_became_active.py +103 -0
  315. omnibase_infra/models/registration/events/model_node_liveness_expired.py +103 -0
  316. omnibase_infra/models/registration/events/model_node_registration_accepted.py +98 -0
  317. omnibase_infra/models/registration/events/model_node_registration_ack_received.py +98 -0
  318. omnibase_infra/models/registration/events/model_node_registration_ack_timed_out.py +112 -0
  319. omnibase_infra/models/registration/events/model_node_registration_initiated.py +107 -0
  320. omnibase_infra/models/registration/events/model_node_registration_rejected.py +104 -0
  321. omnibase_infra/models/registration/model_event_bus_topic_entry.py +59 -0
  322. omnibase_infra/models/registration/model_introspection_metrics.py +253 -0
  323. omnibase_infra/models/registration/model_node_capabilities.py +190 -0
  324. omnibase_infra/models/registration/model_node_event_bus_config.py +99 -0
  325. omnibase_infra/models/registration/model_node_heartbeat_event.py +126 -0
  326. omnibase_infra/models/registration/model_node_introspection_event.py +195 -0
  327. omnibase_infra/models/registration/model_node_metadata.py +79 -0
  328. omnibase_infra/models/registration/model_node_registration.py +162 -0
  329. omnibase_infra/models/registration/model_node_registration_record.py +162 -0
  330. omnibase_infra/models/registry/__init__.py +29 -0
  331. omnibase_infra/models/registry/model_domain_constraint.py +202 -0
  332. omnibase_infra/models/registry/model_message_type_entry.py +271 -0
  333. omnibase_infra/models/resilience/__init__.py +9 -0
  334. omnibase_infra/models/resilience/model_circuit_breaker_config.py +227 -0
  335. omnibase_infra/models/routing/__init__.py +25 -0
  336. omnibase_infra/models/routing/model_routing_entry.py +52 -0
  337. omnibase_infra/models/routing/model_routing_subcontract.py +70 -0
  338. omnibase_infra/models/runtime/__init__.py +49 -0
  339. omnibase_infra/models/runtime/model_contract_security_config.py +41 -0
  340. omnibase_infra/models/runtime/model_discovery_error.py +81 -0
  341. omnibase_infra/models/runtime/model_discovery_result.py +162 -0
  342. omnibase_infra/models/runtime/model_discovery_warning.py +74 -0
  343. omnibase_infra/models/runtime/model_failed_plugin_load.py +63 -0
  344. omnibase_infra/models/runtime/model_handler_contract.py +296 -0
  345. omnibase_infra/models/runtime/model_loaded_handler.py +129 -0
  346. omnibase_infra/models/runtime/model_plugin_load_context.py +93 -0
  347. omnibase_infra/models/runtime/model_plugin_load_summary.py +124 -0
  348. omnibase_infra/models/security/__init__.py +50 -0
  349. omnibase_infra/models/security/classification_levels.py +99 -0
  350. omnibase_infra/models/security/model_environment_policy.py +145 -0
  351. omnibase_infra/models/security/model_handler_security_policy.py +107 -0
  352. omnibase_infra/models/security/model_security_error.py +81 -0
  353. omnibase_infra/models/security/model_security_validation_result.py +328 -0
  354. omnibase_infra/models/security/model_security_warning.py +67 -0
  355. omnibase_infra/models/snapshot/__init__.py +27 -0
  356. omnibase_infra/models/snapshot/model_field_change.py +65 -0
  357. omnibase_infra/models/snapshot/model_snapshot.py +270 -0
  358. omnibase_infra/models/snapshot/model_snapshot_diff.py +203 -0
  359. omnibase_infra/models/snapshot/model_subject_ref.py +81 -0
  360. omnibase_infra/models/types/__init__.py +71 -0
  361. omnibase_infra/models/validation/__init__.py +89 -0
  362. omnibase_infra/models/validation/model_any_type_validation_result.py +118 -0
  363. omnibase_infra/models/validation/model_any_type_violation.py +141 -0
  364. omnibase_infra/models/validation/model_category_match_result.py +345 -0
  365. omnibase_infra/models/validation/model_chain_violation.py +166 -0
  366. omnibase_infra/models/validation/model_coverage_metrics.py +316 -0
  367. omnibase_infra/models/validation/model_execution_shape_rule.py +159 -0
  368. omnibase_infra/models/validation/model_execution_shape_validation.py +208 -0
  369. omnibase_infra/models/validation/model_execution_shape_validation_result.py +294 -0
  370. omnibase_infra/models/validation/model_execution_shape_violation.py +122 -0
  371. omnibase_infra/models/validation/model_localhandler_validation_result.py +139 -0
  372. omnibase_infra/models/validation/model_localhandler_violation.py +100 -0
  373. omnibase_infra/models/validation/model_output_validation_params.py +74 -0
  374. omnibase_infra/models/validation/model_validate_and_raise_params.py +84 -0
  375. omnibase_infra/models/validation/model_validation_error_params.py +84 -0
  376. omnibase_infra/models/validation/model_validation_outcome.py +287 -0
  377. omnibase_infra/nodes/__init__.py +57 -0
  378. omnibase_infra/nodes/architecture_validator/__init__.py +79 -0
  379. omnibase_infra/nodes/architecture_validator/contract.yaml +252 -0
  380. omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +203 -0
  381. omnibase_infra/nodes/architecture_validator/mixins/__init__.py +16 -0
  382. omnibase_infra/nodes/architecture_validator/mixins/mixin_file_path_rule.py +92 -0
  383. omnibase_infra/nodes/architecture_validator/models/__init__.py +36 -0
  384. omnibase_infra/nodes/architecture_validator/models/model_architecture_validation_request.py +56 -0
  385. omnibase_infra/nodes/architecture_validator/models/model_architecture_validation_result.py +311 -0
  386. omnibase_infra/nodes/architecture_validator/models/model_architecture_violation.py +163 -0
  387. omnibase_infra/nodes/architecture_validator/models/model_rule_check_result.py +265 -0
  388. omnibase_infra/nodes/architecture_validator/models/model_validation_request.py +105 -0
  389. omnibase_infra/nodes/architecture_validator/models/model_validation_result.py +314 -0
  390. omnibase_infra/nodes/architecture_validator/node.py +262 -0
  391. omnibase_infra/nodes/architecture_validator/node_architecture_validator.py +383 -0
  392. omnibase_infra/nodes/architecture_validator/protocols/__init__.py +9 -0
  393. omnibase_infra/nodes/architecture_validator/protocols/protocol_architecture_rule.py +225 -0
  394. omnibase_infra/nodes/architecture_validator/registry/__init__.py +28 -0
  395. omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +106 -0
  396. omnibase_infra/nodes/architecture_validator/validators/__init__.py +104 -0
  397. omnibase_infra/nodes/architecture_validator/validators/validator_no_direct_dispatch.py +422 -0
  398. omnibase_infra/nodes/architecture_validator/validators/validator_no_handler_publishing.py +481 -0
  399. omnibase_infra/nodes/architecture_validator/validators/validator_no_orchestrator_fsm.py +491 -0
  400. omnibase_infra/nodes/contract_registry_reducer/__init__.py +29 -0
  401. omnibase_infra/nodes/contract_registry_reducer/contract.yaml +255 -0
  402. omnibase_infra/nodes/contract_registry_reducer/models/__init__.py +38 -0
  403. omnibase_infra/nodes/contract_registry_reducer/models/model_contract_registry_state.py +266 -0
  404. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_cleanup_topic_references.py +55 -0
  405. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_deactivate_contract.py +58 -0
  406. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_mark_stale.py +49 -0
  407. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_heartbeat.py +71 -0
  408. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_topic.py +66 -0
  409. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_upsert_contract.py +92 -0
  410. omnibase_infra/nodes/contract_registry_reducer/node.py +121 -0
  411. omnibase_infra/nodes/contract_registry_reducer/reducer.py +784 -0
  412. omnibase_infra/nodes/contract_registry_reducer/registry/__init__.py +9 -0
  413. omnibase_infra/nodes/contract_registry_reducer/registry/registry_infra_contract_registry_reducer.py +101 -0
  414. omnibase_infra/nodes/effects/README.md +358 -0
  415. omnibase_infra/nodes/effects/__init__.py +26 -0
  416. omnibase_infra/nodes/effects/contract.yaml +167 -0
  417. omnibase_infra/nodes/effects/models/__init__.py +32 -0
  418. omnibase_infra/nodes/effects/models/model_backend_result.py +190 -0
  419. omnibase_infra/nodes/effects/models/model_effect_idempotency_config.py +92 -0
  420. omnibase_infra/nodes/effects/models/model_registry_request.py +132 -0
  421. omnibase_infra/nodes/effects/models/model_registry_response.py +263 -0
  422. omnibase_infra/nodes/effects/protocol_consul_client.py +89 -0
  423. omnibase_infra/nodes/effects/protocol_effect_idempotency_store.py +143 -0
  424. omnibase_infra/nodes/effects/protocol_postgres_adapter.py +96 -0
  425. omnibase_infra/nodes/effects/registry_effect.py +525 -0
  426. omnibase_infra/nodes/effects/store_effect_idempotency_inmemory.py +425 -0
  427. omnibase_infra/nodes/handlers/consul/contract.yaml +85 -0
  428. omnibase_infra/nodes/handlers/db/contract.yaml +72 -0
  429. omnibase_infra/nodes/handlers/graph/contract.yaml +127 -0
  430. omnibase_infra/nodes/handlers/http/contract.yaml +74 -0
  431. omnibase_infra/nodes/handlers/intent/contract.yaml +66 -0
  432. omnibase_infra/nodes/handlers/mcp/contract.yaml +69 -0
  433. omnibase_infra/nodes/handlers/vault/contract.yaml +91 -0
  434. omnibase_infra/nodes/node_intent_storage_effect/__init__.py +50 -0
  435. omnibase_infra/nodes/node_intent_storage_effect/contract.yaml +194 -0
  436. omnibase_infra/nodes/node_intent_storage_effect/models/__init__.py +24 -0
  437. omnibase_infra/nodes/node_intent_storage_effect/models/model_intent_storage_input.py +141 -0
  438. omnibase_infra/nodes/node_intent_storage_effect/models/model_intent_storage_output.py +130 -0
  439. omnibase_infra/nodes/node_intent_storage_effect/node.py +94 -0
  440. omnibase_infra/nodes/node_intent_storage_effect/registry/__init__.py +35 -0
  441. omnibase_infra/nodes/node_intent_storage_effect/registry/registry_infra_intent_storage.py +294 -0
  442. omnibase_infra/nodes/node_ledger_projection_compute/__init__.py +50 -0
  443. omnibase_infra/nodes/node_ledger_projection_compute/contract.yaml +104 -0
  444. omnibase_infra/nodes/node_ledger_projection_compute/node.py +284 -0
  445. omnibase_infra/nodes/node_ledger_projection_compute/registry/__init__.py +29 -0
  446. omnibase_infra/nodes/node_ledger_projection_compute/registry/registry_infra_ledger_projection.py +118 -0
  447. omnibase_infra/nodes/node_ledger_write_effect/__init__.py +82 -0
  448. omnibase_infra/nodes/node_ledger_write_effect/contract.yaml +200 -0
  449. omnibase_infra/nodes/node_ledger_write_effect/handlers/__init__.py +22 -0
  450. omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_append.py +372 -0
  451. omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_query.py +597 -0
  452. omnibase_infra/nodes/node_ledger_write_effect/models/__init__.py +31 -0
  453. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_append_result.py +54 -0
  454. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_entry.py +92 -0
  455. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query.py +53 -0
  456. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query_result.py +41 -0
  457. omnibase_infra/nodes/node_ledger_write_effect/node.py +89 -0
  458. omnibase_infra/nodes/node_ledger_write_effect/protocols/__init__.py +13 -0
  459. omnibase_infra/nodes/node_ledger_write_effect/protocols/protocol_ledger_persistence.py +127 -0
  460. omnibase_infra/nodes/node_ledger_write_effect/registry/__init__.py +9 -0
  461. omnibase_infra/nodes/node_ledger_write_effect/registry/registry_infra_ledger_write.py +121 -0
  462. omnibase_infra/nodes/node_registration_orchestrator/README.md +542 -0
  463. omnibase_infra/nodes/node_registration_orchestrator/__init__.py +120 -0
  464. omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +482 -0
  465. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/__init__.py +53 -0
  466. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_node_introspected.py +376 -0
  467. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_node_registration_acked.py +376 -0
  468. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_runtime_tick.py +373 -0
  469. omnibase_infra/nodes/node_registration_orchestrator/handlers/__init__.py +62 -0
  470. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_heartbeat.py +376 -0
  471. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +694 -0
  472. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_registration_acked.py +458 -0
  473. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_runtime_tick.py +364 -0
  474. omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +544 -0
  475. omnibase_infra/nodes/node_registration_orchestrator/models/__init__.py +75 -0
  476. omnibase_infra/nodes/node_registration_orchestrator/models/model_consul_intent_payload.py +194 -0
  477. omnibase_infra/nodes/node_registration_orchestrator/models/model_consul_registration_intent.py +67 -0
  478. omnibase_infra/nodes/node_registration_orchestrator/models/model_intent_execution_result.py +50 -0
  479. omnibase_infra/nodes/node_registration_orchestrator/models/model_node_liveness_expired.py +107 -0
  480. omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_config.py +67 -0
  481. omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_input.py +41 -0
  482. omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_output.py +166 -0
  483. omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_intent_payload.py +235 -0
  484. omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_upsert_intent.py +68 -0
  485. omnibase_infra/nodes/node_registration_orchestrator/models/model_reducer_execution_result.py +384 -0
  486. omnibase_infra/nodes/node_registration_orchestrator/models/model_reducer_state.py +60 -0
  487. omnibase_infra/nodes/node_registration_orchestrator/models/model_registration_intent.py +177 -0
  488. omnibase_infra/nodes/node_registration_orchestrator/models/model_registry_intent.py +247 -0
  489. omnibase_infra/nodes/node_registration_orchestrator/node.py +195 -0
  490. omnibase_infra/nodes/node_registration_orchestrator/plugin.py +909 -0
  491. omnibase_infra/nodes/node_registration_orchestrator/protocols.py +439 -0
  492. omnibase_infra/nodes/node_registration_orchestrator/registry/__init__.py +41 -0
  493. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +528 -0
  494. omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +393 -0
  495. omnibase_infra/nodes/node_registration_orchestrator/wiring.py +743 -0
  496. omnibase_infra/nodes/node_registration_reducer/__init__.py +15 -0
  497. omnibase_infra/nodes/node_registration_reducer/contract.yaml +301 -0
  498. omnibase_infra/nodes/node_registration_reducer/models/__init__.py +38 -0
  499. omnibase_infra/nodes/node_registration_reducer/models/model_validation_result.py +113 -0
  500. omnibase_infra/nodes/node_registration_reducer/node.py +139 -0
  501. omnibase_infra/nodes/node_registration_reducer/registry/__init__.py +9 -0
  502. omnibase_infra/nodes/node_registration_reducer/registry/registry_infra_node_registration_reducer.py +79 -0
  503. omnibase_infra/nodes/node_registration_storage_effect/__init__.py +41 -0
  504. omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +220 -0
  505. omnibase_infra/nodes/node_registration_storage_effect/models/__init__.py +44 -0
  506. omnibase_infra/nodes/node_registration_storage_effect/models/model_delete_result.py +132 -0
  507. omnibase_infra/nodes/node_registration_storage_effect/models/model_registration_record.py +199 -0
  508. omnibase_infra/nodes/node_registration_storage_effect/models/model_registration_update.py +155 -0
  509. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_health_check_details.py +123 -0
  510. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_health_check_result.py +117 -0
  511. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_query.py +100 -0
  512. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_result.py +136 -0
  513. omnibase_infra/nodes/node_registration_storage_effect/models/model_upsert_result.py +127 -0
  514. omnibase_infra/nodes/node_registration_storage_effect/node.py +112 -0
  515. omnibase_infra/nodes/node_registration_storage_effect/protocols/__init__.py +22 -0
  516. omnibase_infra/nodes/node_registration_storage_effect/protocols/protocol_registration_persistence.py +333 -0
  517. omnibase_infra/nodes/node_registration_storage_effect/registry/__init__.py +23 -0
  518. omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +215 -0
  519. omnibase_infra/nodes/node_registry_effect/__init__.py +85 -0
  520. omnibase_infra/nodes/node_registry_effect/contract.yaml +677 -0
  521. omnibase_infra/nodes/node_registry_effect/handlers/__init__.py +70 -0
  522. omnibase_infra/nodes/node_registry_effect/handlers/handler_consul_deregister.py +211 -0
  523. omnibase_infra/nodes/node_registry_effect/handlers/handler_consul_register.py +212 -0
  524. omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +417 -0
  525. omnibase_infra/nodes/node_registry_effect/handlers/handler_postgres_deactivate.py +215 -0
  526. omnibase_infra/nodes/node_registry_effect/handlers/handler_postgres_upsert.py +208 -0
  527. omnibase_infra/nodes/node_registry_effect/models/__init__.py +43 -0
  528. omnibase_infra/nodes/node_registry_effect/models/model_partial_retry_request.py +92 -0
  529. omnibase_infra/nodes/node_registry_effect/node.py +165 -0
  530. omnibase_infra/nodes/node_registry_effect/registry/__init__.py +27 -0
  531. omnibase_infra/nodes/node_registry_effect/registry/registry_infra_registry_effect.py +196 -0
  532. omnibase_infra/nodes/node_service_discovery_effect/__init__.py +111 -0
  533. omnibase_infra/nodes/node_service_discovery_effect/contract.yaml +246 -0
  534. omnibase_infra/nodes/node_service_discovery_effect/models/__init__.py +67 -0
  535. omnibase_infra/nodes/node_service_discovery_effect/models/enum_health_status.py +72 -0
  536. omnibase_infra/nodes/node_service_discovery_effect/models/enum_service_discovery_operation.py +58 -0
  537. omnibase_infra/nodes/node_service_discovery_effect/models/model_discovery_query.py +99 -0
  538. omnibase_infra/nodes/node_service_discovery_effect/models/model_discovery_result.py +98 -0
  539. omnibase_infra/nodes/node_service_discovery_effect/models/model_health_check_config.py +121 -0
  540. omnibase_infra/nodes/node_service_discovery_effect/models/model_query_metadata.py +63 -0
  541. omnibase_infra/nodes/node_service_discovery_effect/models/model_registration_result.py +130 -0
  542. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_discovery_health_check_details.py +111 -0
  543. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_discovery_health_check_result.py +119 -0
  544. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_info.py +106 -0
  545. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_registration.py +121 -0
  546. omnibase_infra/nodes/node_service_discovery_effect/node.py +111 -0
  547. omnibase_infra/nodes/node_service_discovery_effect/protocols/__init__.py +14 -0
  548. omnibase_infra/nodes/node_service_discovery_effect/protocols/protocol_discovery_operations.py +279 -0
  549. omnibase_infra/nodes/node_service_discovery_effect/registry/__init__.py +13 -0
  550. omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +222 -0
  551. omnibase_infra/nodes/reducers/__init__.py +30 -0
  552. omnibase_infra/nodes/reducers/models/__init__.py +37 -0
  553. omnibase_infra/nodes/reducers/models/model_payload_consul_register.py +87 -0
  554. omnibase_infra/nodes/reducers/models/model_payload_ledger_append.py +133 -0
  555. omnibase_infra/nodes/reducers/models/model_payload_postgres_upsert_registration.py +60 -0
  556. omnibase_infra/nodes/reducers/models/model_registration_confirmation.py +166 -0
  557. omnibase_infra/nodes/reducers/models/model_registration_state.py +433 -0
  558. omnibase_infra/nodes/reducers/registration_reducer.py +1138 -0
  559. omnibase_infra/observability/__init__.py +143 -0
  560. omnibase_infra/observability/constants_metrics.py +91 -0
  561. omnibase_infra/observability/factory_observability_sink.py +525 -0
  562. omnibase_infra/observability/handlers/__init__.py +118 -0
  563. omnibase_infra/observability/handlers/handler_logging_structured.py +967 -0
  564. omnibase_infra/observability/handlers/handler_metrics_prometheus.py +1120 -0
  565. omnibase_infra/observability/handlers/model_logging_handler_config.py +71 -0
  566. omnibase_infra/observability/handlers/model_logging_handler_response.py +77 -0
  567. omnibase_infra/observability/handlers/model_metrics_handler_config.py +172 -0
  568. omnibase_infra/observability/handlers/model_metrics_handler_payload.py +135 -0
  569. omnibase_infra/observability/handlers/model_metrics_handler_response.py +101 -0
  570. omnibase_infra/observability/hooks/__init__.py +74 -0
  571. omnibase_infra/observability/hooks/hook_observability.py +1223 -0
  572. omnibase_infra/observability/models/__init__.py +30 -0
  573. omnibase_infra/observability/models/enum_required_log_context_key.py +77 -0
  574. omnibase_infra/observability/models/model_buffered_log_entry.py +117 -0
  575. omnibase_infra/observability/models/model_logging_sink_config.py +73 -0
  576. omnibase_infra/observability/models/model_metrics_sink_config.py +156 -0
  577. omnibase_infra/observability/sinks/__init__.py +69 -0
  578. omnibase_infra/observability/sinks/sink_logging_structured.py +809 -0
  579. omnibase_infra/observability/sinks/sink_metrics_prometheus.py +710 -0
  580. omnibase_infra/plugins/__init__.py +27 -0
  581. omnibase_infra/plugins/examples/__init__.py +28 -0
  582. omnibase_infra/plugins/examples/plugin_json_normalizer.py +271 -0
  583. omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +210 -0
  584. omnibase_infra/plugins/models/__init__.py +21 -0
  585. omnibase_infra/plugins/models/model_plugin_context.py +76 -0
  586. omnibase_infra/plugins/models/model_plugin_input_data.py +58 -0
  587. omnibase_infra/plugins/models/model_plugin_output_data.py +62 -0
  588. omnibase_infra/plugins/plugin_compute_base.py +449 -0
  589. omnibase_infra/projectors/__init__.py +30 -0
  590. omnibase_infra/projectors/contracts/__init__.py +63 -0
  591. omnibase_infra/projectors/contracts/registration_projector.yaml +370 -0
  592. omnibase_infra/projectors/projection_reader_registration.py +1559 -0
  593. omnibase_infra/projectors/snapshot_publisher_registration.py +1329 -0
  594. omnibase_infra/protocols/__init__.py +104 -0
  595. omnibase_infra/protocols/protocol_capability_projection.py +253 -0
  596. omnibase_infra/protocols/protocol_capability_query.py +251 -0
  597. omnibase_infra/protocols/protocol_container_aware.py +200 -0
  598. omnibase_infra/protocols/protocol_dispatch_engine.py +152 -0
  599. omnibase_infra/protocols/protocol_event_bus_like.py +127 -0
  600. omnibase_infra/protocols/protocol_event_projector.py +96 -0
  601. omnibase_infra/protocols/protocol_idempotency_store.py +142 -0
  602. omnibase_infra/protocols/protocol_message_dispatcher.py +247 -0
  603. omnibase_infra/protocols/protocol_message_type_registry.py +306 -0
  604. omnibase_infra/protocols/protocol_plugin_compute.py +368 -0
  605. omnibase_infra/protocols/protocol_projector_schema_validator.py +82 -0
  606. omnibase_infra/protocols/protocol_registry_metrics.py +215 -0
  607. omnibase_infra/protocols/protocol_snapshot_publisher.py +396 -0
  608. omnibase_infra/protocols/protocol_snapshot_store.py +567 -0
  609. omnibase_infra/runtime/__init__.py +445 -0
  610. omnibase_infra/runtime/binding_config_resolver.py +2771 -0
  611. omnibase_infra/runtime/binding_resolver.py +753 -0
  612. omnibase_infra/runtime/chain_aware_dispatch.py +467 -0
  613. omnibase_infra/runtime/constants_notification.py +75 -0
  614. omnibase_infra/runtime/constants_security.py +70 -0
  615. omnibase_infra/runtime/contract_handler_discovery.py +587 -0
  616. omnibase_infra/runtime/contract_loaders/__init__.py +51 -0
  617. omnibase_infra/runtime/contract_loaders/handler_routing_loader.py +464 -0
  618. omnibase_infra/runtime/contract_loaders/operation_bindings_loader.py +789 -0
  619. omnibase_infra/runtime/dispatch_context_enforcer.py +427 -0
  620. omnibase_infra/runtime/emit_daemon/__init__.py +97 -0
  621. omnibase_infra/runtime/emit_daemon/cli.py +844 -0
  622. omnibase_infra/runtime/emit_daemon/client.py +811 -0
  623. omnibase_infra/runtime/emit_daemon/config.py +535 -0
  624. omnibase_infra/runtime/emit_daemon/daemon.py +812 -0
  625. omnibase_infra/runtime/emit_daemon/event_registry.py +477 -0
  626. omnibase_infra/runtime/emit_daemon/model_daemon_request.py +139 -0
  627. omnibase_infra/runtime/emit_daemon/model_daemon_response.py +191 -0
  628. omnibase_infra/runtime/emit_daemon/queue.py +618 -0
  629. omnibase_infra/runtime/enums/__init__.py +18 -0
  630. omnibase_infra/runtime/enums/enum_config_ref_scheme.py +33 -0
  631. omnibase_infra/runtime/enums/enum_scheduler_status.py +170 -0
  632. omnibase_infra/runtime/envelope_validator.py +179 -0
  633. omnibase_infra/runtime/event_bus_subcontract_wiring.py +466 -0
  634. omnibase_infra/runtime/handler_bootstrap_source.py +507 -0
  635. omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
  636. omnibase_infra/runtime/handler_contract_source.py +750 -0
  637. omnibase_infra/runtime/handler_identity.py +81 -0
  638. omnibase_infra/runtime/handler_plugin_loader.py +2046 -0
  639. omnibase_infra/runtime/handler_registry.py +329 -0
  640. omnibase_infra/runtime/handler_source_resolver.py +367 -0
  641. omnibase_infra/runtime/invocation_security_enforcer.py +427 -0
  642. omnibase_infra/runtime/kafka_contract_source.py +984 -0
  643. omnibase_infra/runtime/kernel.py +40 -0
  644. omnibase_infra/runtime/mixin_policy_validation.py +522 -0
  645. omnibase_infra/runtime/mixin_semver_cache.py +402 -0
  646. omnibase_infra/runtime/mixins/__init__.py +24 -0
  647. omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
  648. omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +778 -0
  649. omnibase_infra/runtime/models/__init__.py +229 -0
  650. omnibase_infra/runtime/models/model_batch_lifecycle_result.py +217 -0
  651. omnibase_infra/runtime/models/model_binding_config.py +168 -0
  652. omnibase_infra/runtime/models/model_binding_config_cache_stats.py +135 -0
  653. omnibase_infra/runtime/models/model_binding_config_resolver_config.py +329 -0
  654. omnibase_infra/runtime/models/model_cached_secret.py +138 -0
  655. omnibase_infra/runtime/models/model_compute_key.py +138 -0
  656. omnibase_infra/runtime/models/model_compute_registration.py +97 -0
  657. omnibase_infra/runtime/models/model_config_cache_entry.py +61 -0
  658. omnibase_infra/runtime/models/model_config_ref.py +331 -0
  659. omnibase_infra/runtime/models/model_config_ref_parse_result.py +125 -0
  660. omnibase_infra/runtime/models/model_contract_load_result.py +224 -0
  661. omnibase_infra/runtime/models/model_domain_plugin_config.py +92 -0
  662. omnibase_infra/runtime/models/model_domain_plugin_result.py +270 -0
  663. omnibase_infra/runtime/models/model_duplicate_response.py +54 -0
  664. omnibase_infra/runtime/models/model_enabled_protocols_config.py +61 -0
  665. omnibase_infra/runtime/models/model_event_bus_config.py +54 -0
  666. omnibase_infra/runtime/models/model_failed_component.py +55 -0
  667. omnibase_infra/runtime/models/model_health_check_response.py +168 -0
  668. omnibase_infra/runtime/models/model_health_check_result.py +229 -0
  669. omnibase_infra/runtime/models/model_lifecycle_result.py +245 -0
  670. omnibase_infra/runtime/models/model_logging_config.py +42 -0
  671. omnibase_infra/runtime/models/model_optional_correlation_id.py +167 -0
  672. omnibase_infra/runtime/models/model_optional_string.py +94 -0
  673. omnibase_infra/runtime/models/model_optional_uuid.py +110 -0
  674. omnibase_infra/runtime/models/model_policy_context.py +100 -0
  675. omnibase_infra/runtime/models/model_policy_key.py +138 -0
  676. omnibase_infra/runtime/models/model_policy_registration.py +139 -0
  677. omnibase_infra/runtime/models/model_policy_result.py +103 -0
  678. omnibase_infra/runtime/models/model_policy_type_filter.py +157 -0
  679. omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
  680. omnibase_infra/runtime/models/model_projector_plugin_loader_config.py +47 -0
  681. omnibase_infra/runtime/models/model_protocol_registration_config.py +65 -0
  682. omnibase_infra/runtime/models/model_retry_policy.py +105 -0
  683. omnibase_infra/runtime/models/model_runtime_config.py +150 -0
  684. omnibase_infra/runtime/models/model_runtime_contract_config.py +268 -0
  685. omnibase_infra/runtime/models/model_runtime_scheduler_config.py +625 -0
  686. omnibase_infra/runtime/models/model_runtime_scheduler_metrics.py +233 -0
  687. omnibase_infra/runtime/models/model_runtime_tick.py +193 -0
  688. omnibase_infra/runtime/models/model_secret_cache_stats.py +82 -0
  689. omnibase_infra/runtime/models/model_secret_mapping.py +63 -0
  690. omnibase_infra/runtime/models/model_secret_resolver_config.py +107 -0
  691. omnibase_infra/runtime/models/model_secret_resolver_metrics.py +111 -0
  692. omnibase_infra/runtime/models/model_secret_source_info.py +72 -0
  693. omnibase_infra/runtime/models/model_secret_source_spec.py +66 -0
  694. omnibase_infra/runtime/models/model_security_config.py +109 -0
  695. omnibase_infra/runtime/models/model_shutdown_batch_result.py +75 -0
  696. omnibase_infra/runtime/models/model_shutdown_config.py +94 -0
  697. omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
  698. omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
  699. omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
  700. omnibase_infra/runtime/projector_plugin_loader.py +1462 -0
  701. omnibase_infra/runtime/projector_schema_manager.py +565 -0
  702. omnibase_infra/runtime/projector_shell.py +1330 -0
  703. omnibase_infra/runtime/protocol_contract_descriptor.py +92 -0
  704. omnibase_infra/runtime/protocol_contract_source.py +92 -0
  705. omnibase_infra/runtime/protocol_domain_plugin.py +474 -0
  706. omnibase_infra/runtime/protocol_handler_discovery.py +221 -0
  707. omnibase_infra/runtime/protocol_handler_plugin_loader.py +327 -0
  708. omnibase_infra/runtime/protocol_lifecycle_executor.py +435 -0
  709. omnibase_infra/runtime/protocol_policy.py +366 -0
  710. omnibase_infra/runtime/protocols/__init__.py +37 -0
  711. omnibase_infra/runtime/protocols/protocol_runtime_scheduler.py +468 -0
  712. omnibase_infra/runtime/publisher_topic_scoped.py +294 -0
  713. omnibase_infra/runtime/registry/__init__.py +93 -0
  714. omnibase_infra/runtime/registry/mixin_message_type_query.py +326 -0
  715. omnibase_infra/runtime/registry/mixin_message_type_registration.py +354 -0
  716. omnibase_infra/runtime/registry/registry_event_bus_binding.py +268 -0
  717. omnibase_infra/runtime/registry/registry_message_type.py +542 -0
  718. omnibase_infra/runtime/registry/registry_protocol_binding.py +445 -0
  719. omnibase_infra/runtime/registry_compute.py +1143 -0
  720. omnibase_infra/runtime/registry_contract_source.py +693 -0
  721. omnibase_infra/runtime/registry_dispatcher.py +678 -0
  722. omnibase_infra/runtime/registry_policy.py +1185 -0
  723. omnibase_infra/runtime/runtime_contract_config_loader.py +406 -0
  724. omnibase_infra/runtime/runtime_scheduler.py +1070 -0
  725. omnibase_infra/runtime/secret_resolver.py +2112 -0
  726. omnibase_infra/runtime/security_metadata_validator.py +776 -0
  727. omnibase_infra/runtime/service_kernel.py +1651 -0
  728. omnibase_infra/runtime/service_message_dispatch_engine.py +2350 -0
  729. omnibase_infra/runtime/service_runtime_host_process.py +3493 -0
  730. omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
  731. omnibase_infra/runtime/transition_notification_publisher.py +765 -0
  732. omnibase_infra/runtime/util_container_wiring.py +1124 -0
  733. omnibase_infra/runtime/util_validation.py +314 -0
  734. omnibase_infra/runtime/util_version.py +98 -0
  735. omnibase_infra/runtime/util_wiring.py +723 -0
  736. omnibase_infra/schemas/schema_registration_projection.sql +320 -0
  737. omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
  738. omnibase_infra/services/__init__.py +89 -0
  739. omnibase_infra/services/corpus_capture.py +684 -0
  740. omnibase_infra/services/mcp/__init__.py +31 -0
  741. omnibase_infra/services/mcp/mcp_server_lifecycle.py +449 -0
  742. omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
  743. omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
  744. omnibase_infra/services/mcp/service_mcp_tool_sync.py +565 -0
  745. omnibase_infra/services/registry_api/__init__.py +40 -0
  746. omnibase_infra/services/registry_api/main.py +261 -0
  747. omnibase_infra/services/registry_api/models/__init__.py +66 -0
  748. omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
  749. omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
  750. omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
  751. omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
  752. omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
  753. omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
  754. omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
  755. omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
  756. omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
  757. omnibase_infra/services/registry_api/models/model_warning.py +49 -0
  758. omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
  759. omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
  760. omnibase_infra/services/registry_api/routes.py +371 -0
  761. omnibase_infra/services/registry_api/service.py +837 -0
  762. omnibase_infra/services/service_capability_query.py +945 -0
  763. omnibase_infra/services/service_health.py +898 -0
  764. omnibase_infra/services/service_node_selector.py +530 -0
  765. omnibase_infra/services/service_timeout_emitter.py +699 -0
  766. omnibase_infra/services/service_timeout_scanner.py +394 -0
  767. omnibase_infra/services/session/__init__.py +56 -0
  768. omnibase_infra/services/session/config_consumer.py +137 -0
  769. omnibase_infra/services/session/config_store.py +139 -0
  770. omnibase_infra/services/session/consumer.py +1007 -0
  771. omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
  772. omnibase_infra/services/session/store.py +997 -0
  773. omnibase_infra/services/snapshot/__init__.py +31 -0
  774. omnibase_infra/services/snapshot/service_snapshot.py +647 -0
  775. omnibase_infra/services/snapshot/store_inmemory.py +637 -0
  776. omnibase_infra/services/snapshot/store_postgres.py +1279 -0
  777. omnibase_infra/shared/__init__.py +8 -0
  778. omnibase_infra/testing/__init__.py +10 -0
  779. omnibase_infra/testing/utils.py +23 -0
  780. omnibase_infra/topics/__init__.py +45 -0
  781. omnibase_infra/topics/platform_topic_suffixes.py +140 -0
  782. omnibase_infra/topics/util_topic_composition.py +95 -0
  783. omnibase_infra/types/__init__.py +48 -0
  784. omnibase_infra/types/type_cache_info.py +49 -0
  785. omnibase_infra/types/type_dsn.py +173 -0
  786. omnibase_infra/types/type_infra_aliases.py +60 -0
  787. omnibase_infra/types/typed_dict/__init__.py +29 -0
  788. omnibase_infra/types/typed_dict/typed_dict_envelope_build_params.py +115 -0
  789. omnibase_infra/types/typed_dict/typed_dict_introspection_cache.py +128 -0
  790. omnibase_infra/types/typed_dict/typed_dict_performance_metrics_cache.py +140 -0
  791. omnibase_infra/types/typed_dict_capabilities.py +64 -0
  792. omnibase_infra/utils/__init__.py +117 -0
  793. omnibase_infra/utils/correlation.py +208 -0
  794. omnibase_infra/utils/util_atomic_file.py +261 -0
  795. omnibase_infra/utils/util_consumer_group.py +232 -0
  796. omnibase_infra/utils/util_datetime.py +372 -0
  797. omnibase_infra/utils/util_db_transaction.py +239 -0
  798. omnibase_infra/utils/util_dsn_validation.py +333 -0
  799. omnibase_infra/utils/util_env_parsing.py +264 -0
  800. omnibase_infra/utils/util_error_sanitization.py +457 -0
  801. omnibase_infra/utils/util_pydantic_validators.py +477 -0
  802. omnibase_infra/utils/util_retry_optimistic.py +281 -0
  803. omnibase_infra/utils/util_semver.py +233 -0
  804. omnibase_infra/validation/__init__.py +307 -0
  805. omnibase_infra/validation/contracts/security.validation.yaml +114 -0
  806. omnibase_infra/validation/enums/__init__.py +11 -0
  807. omnibase_infra/validation/enums/enum_contract_violation_severity.py +13 -0
  808. omnibase_infra/validation/infra_validators.py +1514 -0
  809. omnibase_infra/validation/linter_contract.py +907 -0
  810. omnibase_infra/validation/mixin_any_type_classification.py +120 -0
  811. omnibase_infra/validation/mixin_any_type_exemption.py +580 -0
  812. omnibase_infra/validation/mixin_any_type_reporting.py +106 -0
  813. omnibase_infra/validation/mixin_execution_shape_violation_checks.py +596 -0
  814. omnibase_infra/validation/mixin_node_archetype_detection.py +254 -0
  815. omnibase_infra/validation/models/__init__.py +15 -0
  816. omnibase_infra/validation/models/model_contract_lint_result.py +101 -0
  817. omnibase_infra/validation/models/model_contract_violation.py +41 -0
  818. omnibase_infra/validation/service_validation_aggregator.py +395 -0
  819. omnibase_infra/validation/validation_exemptions.yaml +2033 -0
  820. omnibase_infra/validation/validator_any_type.py +715 -0
  821. omnibase_infra/validation/validator_chain_propagation.py +839 -0
  822. omnibase_infra/validation/validator_execution_shape.py +465 -0
  823. omnibase_infra/validation/validator_localhandler.py +261 -0
  824. omnibase_infra/validation/validator_registration_security.py +410 -0
  825. omnibase_infra/validation/validator_routing_coverage.py +1020 -0
  826. omnibase_infra/validation/validator_runtime_shape.py +915 -0
  827. omnibase_infra/validation/validator_security.py +513 -0
  828. omnibase_infra/validation/validator_topic_category.py +1152 -0
  829. omnibase_infra-0.2.6.dist-info/METADATA +197 -0
  830. omnibase_infra-0.2.6.dist-info/RECORD +833 -0
  831. omnibase_infra-0.2.6.dist-info/WHEEL +4 -0
  832. omnibase_infra-0.2.6.dist-info/entry_points.txt +5 -0
  833. omnibase_infra-0.2.6.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1462 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Projector Plugin Loader for Contract-Based Discovery.
4
+
5
+ This module provides ProjectorPluginLoader, which discovers and loads projectors
6
+ from YAML contract definitions. It implements ProtocolProjectorLoader and supports
7
+ two operation modes:
8
+ - Strict mode (default): Raises on first error encountered
9
+ - Graceful mode: Collects errors, continues discovery
10
+
11
+ Part of OMN-1168: ProjectorPluginLoader contract discovery loading.
12
+
13
+ Security Features:
14
+ - File size validation (max 10MB) to prevent memory exhaustion
15
+ - Symlink protection to prevent path traversal attacks
16
+ - Path sanitization for safe logging
17
+ - YAML safe_load to prevent arbitrary code execution
18
+
19
+ See Also:
20
+ - ProtocolProjectorLoader: Protocol definition from omnibase_spi
21
+ - ModelProjectorContract: Contract model from omnibase_core
22
+ - ProtocolEventProjector: Protocol for loaded projectors
23
+
24
+ .. versionadded:: 0.7.0
25
+ Created as part of OMN-1168 projector contract discovery.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import logging
31
+ import time
32
+ from pathlib import Path
33
+ from typing import TYPE_CHECKING
34
+ from uuid import UUID, uuid4
35
+
36
+ import asyncpg
37
+ import yaml
38
+ from pydantic import ValidationError
39
+
40
+ from omnibase_core.container import ModelONEXContainer
41
+ from omnibase_core.enums.enum_core_error_code import EnumCoreErrorCode
42
+ from omnibase_core.models.errors.model_onex_error import ModelOnexError
43
+ from omnibase_core.models.projectors import ModelProjectorContract
44
+ from omnibase_infra.models.projectors import (
45
+ ModelProjectorDiscoveryResult,
46
+ ModelProjectorValidationError,
47
+ )
48
+ from omnibase_infra.protocols import (
49
+ ProtocolEventProjector,
50
+ ProtocolProjectorSchemaValidator,
51
+ )
52
+ from omnibase_infra.runtime.models import ModelProjectorPluginLoaderConfig
53
+
54
+ if TYPE_CHECKING:
55
+ from omnibase_core.models.events.model_event_envelope import ModelEventEnvelope
56
+ from omnibase_core.models.projectors import ModelProjectionResult
57
+
58
+ logger = logging.getLogger(__name__)
59
+
60
+ # Contract file patterns for projector discovery
61
+ PROJECTOR_CONTRACT_PATTERNS = ("*_projector.yaml", "projector_contract.yaml")
62
+
63
+ # Maximum contract file size (10MB) to prevent memory exhaustion
64
+ MAX_CONTRACT_SIZE = 10 * 1024 * 1024
65
+
66
+ # Maximum files to discover in a single operation to prevent DoS via filesystem-wide glob scans
67
+ MAX_DISCOVERY_FILES = 10000
68
+
69
+
70
+ # =============================================================================
71
+ # ProjectorShell Import (OMN-1169)
72
+ # =============================================================================
73
+ from omnibase_infra.runtime.projector_shell import ProjectorShell
74
+
75
+ # =============================================================================
76
+ # Placeholder Projector (used when no database pool is provided)
77
+ # =============================================================================
78
+
79
+
80
+ class ProjectorShellPlaceholder:
81
+ """Placeholder projector shell used when no database pool is provided.
82
+
83
+ This class provides a stub implementation that holds contract metadata
84
+ but cannot actually project events. It is used by the loader for
85
+ discovery-only scenarios where database access is not needed.
86
+
87
+ For full projection functionality, provide a database pool to the
88
+ ProjectorPluginLoader constructor, which will instantiate ProjectorShell.
89
+
90
+ Note:
91
+ The project() and get_state() methods will raise NotImplementedError.
92
+ Use ProjectorShell with a database pool for actual projections.
93
+ """
94
+
95
+ def __init__(self, contract: ModelProjectorContract) -> None:
96
+ """Initialize placeholder with contract metadata.
97
+
98
+ Args:
99
+ contract: The parsed and validated projector contract.
100
+ """
101
+ self._contract = contract
102
+ logger.debug(
103
+ "ProjectorShellPlaceholder loaded for '%s' - "
104
+ "no database pool provided, projection methods will raise NotImplementedError",
105
+ contract.projector_id,
106
+ )
107
+
108
+ @property
109
+ def projector_id(self) -> str:
110
+ """Unique identifier from contract."""
111
+ return str(self._contract.projector_id)
112
+
113
+ @property
114
+ def aggregate_type(self) -> str:
115
+ """Aggregate type from contract."""
116
+ return str(self._contract.aggregate_type)
117
+
118
+ @property
119
+ def consumed_events(self) -> list[str]:
120
+ """Event types from contract."""
121
+ return list(self._contract.consumed_events)
122
+
123
+ @property
124
+ def contract(self) -> ModelProjectorContract:
125
+ """Access the underlying contract."""
126
+ return self._contract
127
+
128
+ @property
129
+ def is_placeholder(self) -> bool:
130
+ """Whether this is a placeholder implementation.
131
+
132
+ Returns:
133
+ True, as this is a placeholder that will raise NotImplementedError
134
+ on projection methods until OMN-1169 is implemented.
135
+ """
136
+ return True
137
+
138
+ async def project(
139
+ self,
140
+ event: ModelEventEnvelope,
141
+ correlation_id: UUID,
142
+ ) -> ModelProjectionResult:
143
+ """Placeholder - requires database pool for actual projections.
144
+
145
+ Args:
146
+ event: The event envelope to project.
147
+ correlation_id: Correlation ID for distributed tracing.
148
+
149
+ Raises:
150
+ NotImplementedError: Always, as this is a placeholder without DB access.
151
+ """
152
+ raise NotImplementedError(
153
+ f"ProjectorShellPlaceholder for '{self.projector_id}' cannot project events. "
154
+ "Provide a database pool to ProjectorPluginLoader to use ProjectorShell."
155
+ )
156
+
157
+ async def get_state(
158
+ self,
159
+ aggregate_id: UUID,
160
+ correlation_id: UUID,
161
+ ) -> object | None:
162
+ """Placeholder - requires database pool for state queries.
163
+
164
+ Args:
165
+ aggregate_id: The unique identifier of the aggregate.
166
+ correlation_id: Correlation ID for distributed tracing.
167
+
168
+ Raises:
169
+ NotImplementedError: Always, as this is a placeholder without DB access.
170
+ """
171
+ raise NotImplementedError(
172
+ f"ProjectorShellPlaceholder for '{self.projector_id}' cannot query state. "
173
+ "Provide a database pool to ProjectorPluginLoader to use ProjectorShell."
174
+ )
175
+
176
+ def __repr__(self) -> str:
177
+ """Return string representation."""
178
+ return (
179
+ f"ProjectorShellPlaceholder("
180
+ f"id={self.projector_id!r}, "
181
+ f"aggregate_type={self.aggregate_type!r}, "
182
+ f"events={len(self.consumed_events)})"
183
+ )
184
+
185
+
186
+ # =============================================================================
187
+ # ProjectorPluginLoader Implementation
188
+ # Note: ONEX-PATTERN-001 exemption - loader requires multiple discovery methods
189
+ # =============================================================================
190
+
191
+
192
+ class ProjectorPluginLoader:
193
+ """Projector loader that discovers contracts from the filesystem.
194
+
195
+ This class implements the projector loader protocol by recursively scanning
196
+ configured paths for projector contract files, parsing them with YAML and
197
+ validating against ModelProjectorContract from omnibase_core.
198
+
199
+ Protocol Compliance:
200
+ This class implements the ProtocolProjectorLoader interface with
201
+ all required methods: load_from_contract(), load_from_directory(),
202
+ and discover_and_load().
203
+
204
+ The loader supports two operation modes:
205
+ - Strict mode (default): Raises ModelOnexError on first error
206
+ - Graceful mode: Collects all errors, continues discovery
207
+
208
+ Both modes return results for a consistent interface. In strict mode,
209
+ errors raise exceptions instead of being collected.
210
+
211
+ Security Features:
212
+ - File size validation (max 10MB) to prevent memory exhaustion
213
+ - Symlink protection to prevent path traversal attacks
214
+ - Path sanitization for safe logging (no full paths exposed)
215
+ - YAML safe_load to prevent arbitrary code execution
216
+
217
+ Example:
218
+ >>> # Strict mode loading (raises on error)
219
+ >>> loader = ProjectorPluginLoader(schema_manager=schema_mgr)
220
+ >>> projector = await loader.load_from_contract(Path("./orders_projector.yaml"))
221
+ >>> print(f"Loaded projector: {projector.projector_id}")
222
+
223
+ >>> # Graceful mode with error collection
224
+ >>> config = ModelProjectorPluginLoaderConfig(graceful_mode=True)
225
+ >>> loader = ProjectorPluginLoader(config=config, schema_manager=schema_mgr)
226
+ >>> result = await loader.discover_with_errors(Path("./projectors"))
227
+ >>> print(f"Found {result.success_count} projectors")
228
+ >>> print(f"Encountered {result.error_count} errors")
229
+
230
+ Performance Characteristics:
231
+ - File system scanning is O(n) where n is total files in paths
232
+ - YAML parsing is synchronous (consider aiofiles for high-throughput)
233
+ - Typical performance: 100-500 contracts/second on SSD
234
+ - Memory: ~1KB per contract retained
235
+
236
+ .. versionadded:: 0.7.0
237
+ Created as part of OMN-1168 projector contract discovery.
238
+ """
239
+
240
+ def __init__(
241
+ self,
242
+ config: ModelProjectorPluginLoaderConfig | None = None,
243
+ container: ModelONEXContainer | None = None,
244
+ schema_manager: ProtocolProjectorSchemaValidator | None = None,
245
+ pool: asyncpg.Pool | None = None,
246
+ ) -> None:
247
+ """Initialize the projector plugin loader.
248
+
249
+ Args:
250
+ config: Configuration for the loader. If None, uses default settings.
251
+ container: ONEX container for dependency injection. If provided,
252
+ can be used to resolve dependencies like schema_manager.
253
+ schema_manager: Schema validator for validating database schemas.
254
+ If None, schema validation is skipped during loading.
255
+ pool: Optional asyncpg connection pool for database access.
256
+ If provided, loaded projectors will be full ProjectorShell
257
+ instances capable of actual projections. If None (default),
258
+ loaded projectors will be ProjectorShellPlaceholder instances
259
+ that hold contract metadata but cannot perform projections.
260
+ """
261
+ self._config = config or ModelProjectorPluginLoaderConfig()
262
+ self._container = container
263
+ self._schema_manager = schema_manager
264
+ if schema_manager is not None:
265
+ logger.debug("Schema manager provided - will be used for schema validation")
266
+ self._pool = pool
267
+ if pool is not None:
268
+ logger.debug(
269
+ "Database pool provided - will create ProjectorShell instances"
270
+ )
271
+ else:
272
+ logger.debug(
273
+ "No database pool provided - will create placeholder instances"
274
+ )
275
+ self._graceful_mode = self._config.graceful_mode
276
+
277
+ # Security: Validate base_paths don't contain filesystem root
278
+ # to prevent DoS via filesystem-wide glob scanning
279
+ base_paths = self._config.base_paths
280
+ if base_paths:
281
+ for base_path in base_paths:
282
+ resolved = base_path.resolve()
283
+ # Reject root paths (/, /root, C:\, etc.)
284
+ if resolved == Path("/").resolve() or len(resolved.parts) <= 1:
285
+ raise ModelOnexError(
286
+ "Root or near-root path not allowed as base_path - "
287
+ "would allow filesystem-wide scanning",
288
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
289
+ )
290
+
291
+ self._base_paths = base_paths or []
292
+
293
+ def _create_projector(
294
+ self,
295
+ contract: ModelProjectorContract,
296
+ ) -> ProtocolEventProjector:
297
+ """Create a projector instance from a contract.
298
+
299
+ If a database pool was provided to the loader, creates a full
300
+ ProjectorShell instance. Otherwise, creates a placeholder.
301
+
302
+ Args:
303
+ contract: The validated projector contract.
304
+
305
+ Returns:
306
+ Either ProjectorShell (if pool available) or ProjectorShellPlaceholder.
307
+ """
308
+ if self._pool is not None:
309
+ return ProjectorShell(contract, self._pool)
310
+ return ProjectorShellPlaceholder(contract)
311
+
312
+ def _sanitize_path_for_logging(self, path: Path) -> str:
313
+ """Sanitize a file path for safe inclusion in logs and error messages.
314
+
315
+ In production environments, full paths may leak sensitive information
316
+ about directory structure. This method returns only the filename and
317
+ parent directory to provide context without exposing full paths.
318
+
319
+ Args:
320
+ path: The full path to sanitize.
321
+
322
+ Returns:
323
+ Sanitized path string showing only parent/filename.
324
+ For example: "/home/user/code/projectors/orders_projector.yaml"
325
+ becomes "projectors/orders_projector.yaml".
326
+ """
327
+ try:
328
+ return str(Path(path.parent.name) / path.name)
329
+ except (ValueError, AttributeError):
330
+ return path.name
331
+
332
+ def _format_file_size(self, size_bytes: int) -> str:
333
+ """Format file size with KB/MB granularity for safe logging.
334
+
335
+ Avoids exposing exact byte counts in error messages which could
336
+ leak storage implementation details.
337
+
338
+ Args:
339
+ size_bytes: The file size in bytes.
340
+
341
+ Returns:
342
+ Human-readable size string with KB/MB granularity.
343
+ """
344
+ if size_bytes < 1024:
345
+ return "less than 1 KB"
346
+ elif size_bytes < 1024 * 1024:
347
+ return f"{size_bytes // 1024} KB"
348
+ else:
349
+ return f"{size_bytes / (1024 * 1024):.1f} MB"
350
+
351
+ def _is_projector_contract(self, filename: str) -> bool:
352
+ """Check if a filename matches projector contract patterns.
353
+
354
+ Args:
355
+ filename: The filename to check.
356
+
357
+ Returns:
358
+ True if filename matches any projector contract pattern.
359
+ """
360
+ for pattern in PROJECTOR_CONTRACT_PATTERNS:
361
+ if pattern.startswith("*"):
362
+ suffix = pattern[1:]
363
+ if filename.endswith(suffix):
364
+ return True
365
+ elif filename == pattern:
366
+ return True
367
+ return False
368
+
369
+ def _validate_file_security(
370
+ self,
371
+ contract_path: Path,
372
+ allowed_bases: list[Path],
373
+ ) -> tuple[bool, str | None]:
374
+ """Validate file security constraints.
375
+
376
+ Checks file size limits and symlink containment.
377
+
378
+ Args:
379
+ contract_path: Path to the contract file.
380
+ allowed_bases: List of allowed base directories.
381
+
382
+ Returns:
383
+ Tuple of (is_valid, error_message).
384
+ If valid, error_message is None.
385
+ """
386
+ resolved_path = contract_path.resolve()
387
+
388
+ # Symlink protection: verify resolved path is within allowed paths
389
+ if allowed_bases:
390
+ is_within_allowed = any(
391
+ resolved_path.is_relative_to(base.resolve()) for base in allowed_bases
392
+ )
393
+ if not is_within_allowed:
394
+ return (
395
+ False,
396
+ f"Contract file resolves outside allowed paths: {self._sanitize_path_for_logging(contract_path)}",
397
+ )
398
+
399
+ # File size check
400
+ try:
401
+ file_size = contract_path.stat().st_size
402
+ if file_size > MAX_CONTRACT_SIZE:
403
+ return (
404
+ False,
405
+ f"Contract file exceeds size limit: {self._format_file_size(file_size)} (max: {MAX_CONTRACT_SIZE // (1024 * 1024)} MB)",
406
+ )
407
+ except OSError as e:
408
+ # Use strerror to avoid leaking full path in error message
409
+ error_msg = e.strerror or "unknown error"
410
+ return (False, f"Failed to stat file: {error_msg}")
411
+
412
+ return (True, None)
413
+
414
+ def _load_contract(self, contract_path: Path) -> ModelProjectorContract:
415
+ """Parse and validate a contract file.
416
+
417
+ Args:
418
+ contract_path: Path to the projector contract YAML file.
419
+
420
+ Returns:
421
+ Validated ModelProjectorContract instance.
422
+
423
+ Raises:
424
+ ModelOnexError: If file exceeds size limit.
425
+ yaml.YAMLError: If YAML parsing fails.
426
+ ValidationError: If contract validation fails.
427
+ OSError: If file I/O fails.
428
+ """
429
+ # Validate file size before reading
430
+ file_size = contract_path.stat().st_size
431
+ if file_size > MAX_CONTRACT_SIZE:
432
+ raise ModelOnexError(
433
+ f"Contract file exceeds size limit: {self._format_file_size(file_size)} "
434
+ f"(max: {MAX_CONTRACT_SIZE // (1024 * 1024)} MB)",
435
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
436
+ )
437
+
438
+ with contract_path.open("r", encoding="utf-8") as f:
439
+ raw_data = yaml.safe_load(f)
440
+
441
+ # Strip extension fields not in base ModelProjectorContract.
442
+ # partial_updates is an extension field for OMN-1170 that defines
443
+ # partial update operations. It is used by the runtime for optimized
444
+ # UPDATE statements but is not part of the core contract schema.
445
+ if isinstance(raw_data, dict):
446
+ raw_data.pop("partial_updates", None)
447
+
448
+ # Handle composite key fields: ModelProjectorContract expects strings,
449
+ # but the contract YAML uses lists for composite primary/upsert keys.
450
+ # Convert first element of list to string for model validation.
451
+ # The full composite key information is preserved in the SQL schema.
452
+ if isinstance(
453
+ raw_data.get("projection_schema", {}).get("primary_key"), list
454
+ ):
455
+ pk_list = raw_data["projection_schema"]["primary_key"]
456
+ raw_data["projection_schema"]["primary_key"] = (
457
+ pk_list[0] if pk_list else "entity_id"
458
+ )
459
+
460
+ if isinstance(raw_data.get("behavior", {}).get("upsert_key"), list):
461
+ upsert_list = raw_data["behavior"]["upsert_key"]
462
+ raw_data["behavior"]["upsert_key"] = (
463
+ upsert_list[0] if upsert_list else None
464
+ )
465
+
466
+ # Validate against ModelProjectorContract
467
+ contract = ModelProjectorContract.model_validate(raw_data)
468
+ return contract
469
+
470
+ def _create_parse_error(
471
+ self,
472
+ contract_path: Path,
473
+ error: yaml.YAMLError,
474
+ correlation_id: UUID | None = None,
475
+ ) -> ModelProjectorValidationError:
476
+ """Create a validation error for YAML parse failures.
477
+
478
+ Args:
479
+ contract_path: Path to the failing contract file.
480
+ error: The YAML parsing error.
481
+ correlation_id: Correlation ID for distributed tracing.
482
+
483
+ Returns:
484
+ ModelProjectorValidationError with parse error details.
485
+ """
486
+ return ModelProjectorValidationError(
487
+ error_type="CONTRACT_PARSE_ERROR",
488
+ contract_path=self._sanitize_path_for_logging(contract_path),
489
+ message=f"Failed to parse YAML in {self._sanitize_path_for_logging(contract_path)}: {error}",
490
+ remediation_hint="Check YAML syntax and ensure proper indentation",
491
+ correlation_id=correlation_id,
492
+ )
493
+
494
+ def _create_validation_error(
495
+ self,
496
+ contract_path: Path,
497
+ error: ValidationError,
498
+ correlation_id: UUID | None = None,
499
+ ) -> ModelProjectorValidationError:
500
+ """Create a validation error for contract validation failures.
501
+
502
+ Args:
503
+ contract_path: Path to the failing contract file.
504
+ error: The Pydantic validation error.
505
+ correlation_id: Correlation ID for distributed tracing.
506
+
507
+ Returns:
508
+ ModelProjectorValidationError with validation details.
509
+ """
510
+ error_details = error.errors()
511
+ if error_details:
512
+ first_error = error_details[0]
513
+ field_loc = " -> ".join(str(x) for x in first_error.get("loc", ()))
514
+ error_msg = str(first_error.get("msg", "validation failed"))
515
+ else:
516
+ field_loc = "unknown"
517
+ error_msg = "validation failed"
518
+
519
+ return ModelProjectorValidationError(
520
+ error_type="CONTRACT_VALIDATION_ERROR",
521
+ contract_path=self._sanitize_path_for_logging(contract_path),
522
+ message=f"Contract validation failed in {self._sanitize_path_for_logging(contract_path)}: {error_msg} at {field_loc}",
523
+ remediation_hint=f"Check the '{field_loc}' field in the contract",
524
+ correlation_id=correlation_id,
525
+ )
526
+
527
+ def _create_size_limit_error(
528
+ self,
529
+ contract_path: Path,
530
+ file_size: int,
531
+ correlation_id: UUID | None = None,
532
+ ) -> ModelProjectorValidationError:
533
+ """Create a validation error for file size limit violations.
534
+
535
+ Args:
536
+ contract_path: Path to the oversized contract file.
537
+ file_size: The actual file size in bytes.
538
+ correlation_id: Correlation ID for distributed tracing.
539
+
540
+ Returns:
541
+ ModelProjectorValidationError with size limit details.
542
+ """
543
+ return ModelProjectorValidationError(
544
+ error_type="SIZE_LIMIT_ERROR",
545
+ contract_path=self._sanitize_path_for_logging(contract_path),
546
+ message=(
547
+ f"Contract file {self._sanitize_path_for_logging(contract_path)} exceeds size limit: "
548
+ f"{self._format_file_size(file_size)} (max: {MAX_CONTRACT_SIZE // (1024 * 1024)} MB)"
549
+ ),
550
+ remediation_hint=(
551
+ f"Reduce contract file size to under {MAX_CONTRACT_SIZE // (1024 * 1024)} MB. "
552
+ "Consider splitting into multiple contracts if needed."
553
+ ),
554
+ correlation_id=correlation_id,
555
+ )
556
+
557
+ def _create_io_error(
558
+ self,
559
+ contract_path: Path,
560
+ error: OSError,
561
+ correlation_id: UUID | None = None,
562
+ ) -> ModelProjectorValidationError:
563
+ """Create a validation error for I/O failures.
564
+
565
+ Args:
566
+ contract_path: Path to the contract file that failed to read.
567
+ error: The I/O error encountered.
568
+ correlation_id: Correlation ID for distributed tracing.
569
+
570
+ Returns:
571
+ ModelProjectorValidationError with I/O error details.
572
+ """
573
+ # Use strerror to avoid leaking full paths, fallback to generic message
574
+ error_message = error.strerror or "I/O error occurred"
575
+
576
+ return ModelProjectorValidationError(
577
+ error_type="IO_ERROR",
578
+ contract_path=self._sanitize_path_for_logging(contract_path),
579
+ message=f"Failed to read contract file {self._sanitize_path_for_logging(contract_path)}: {error_message}",
580
+ remediation_hint="Check file permissions and ensure the file exists",
581
+ correlation_id=correlation_id,
582
+ )
583
+
584
+ def _create_security_error(
585
+ self,
586
+ contract_path: Path,
587
+ message: str,
588
+ correlation_id: UUID | None = None,
589
+ ) -> ModelProjectorValidationError:
590
+ """Create a validation error for security violations.
591
+
592
+ Args:
593
+ contract_path: Path to the contract file.
594
+ message: Security violation message.
595
+ correlation_id: Correlation ID for distributed tracing.
596
+
597
+ Returns:
598
+ ModelProjectorValidationError with security error details.
599
+ """
600
+ return ModelProjectorValidationError(
601
+ error_type="SECURITY_ERROR",
602
+ contract_path=self._sanitize_path_for_logging(contract_path),
603
+ message=message,
604
+ remediation_hint="Ensure contract files are within allowed directories and not symlinks to external locations",
605
+ correlation_id=correlation_id,
606
+ )
607
+
608
+ def _find_contract_files(self, base_path: Path) -> list[Path]:
609
+ """Find all projector contract files under a base path.
610
+
611
+ Uses specific glob patterns (e.g., "*_projector.yaml") to directly
612
+ discover contract files, avoiding post-filtering overhead. Patterns
613
+ are already specific enough that additional name validation is
614
+ redundant after rglob matching.
615
+
616
+ Args:
617
+ base_path: Directory to scan recursively.
618
+
619
+ Returns:
620
+ List of paths to projector contract files, deduplicated.
621
+ """
622
+ if base_path.is_file():
623
+ if self._is_projector_contract(base_path.name):
624
+ return [base_path]
625
+ return []
626
+
627
+ # Use a set to deduplicate files matched by multiple patterns
628
+ # (e.g., symlinks or overlapping patterns)
629
+ discovered: set[Path] = set()
630
+ for pattern in PROJECTOR_CONTRACT_PATTERNS:
631
+ # rglob with specific patterns like "*_projector.yaml" already
632
+ # filters to matching files - no need for redundant name filtering
633
+ discovered.update(base_path.rglob(pattern))
634
+
635
+ return list(discovered)
636
+
637
+ def _log_discovery_results(
638
+ self,
639
+ discovered_count: int,
640
+ failure_count: int,
641
+ duration_seconds: float,
642
+ ) -> None:
643
+ """Log discovery results with structured telemetry.
644
+
645
+ Args:
646
+ discovered_count: Number of successfully discovered contracts.
647
+ failure_count: Number of validation failures.
648
+ duration_seconds: Total discovery duration in seconds.
649
+ """
650
+ contracts_per_sec = (
651
+ discovered_count / duration_seconds if duration_seconds > 0 else 0.0
652
+ )
653
+
654
+ logger.info(
655
+ "Projector contract discovery completed: "
656
+ "discovered_count=%d, failure_count=%d, "
657
+ "graceful_mode=%s, duration_seconds=%.3f, contracts_per_second=%.1f",
658
+ discovered_count,
659
+ failure_count,
660
+ self._graceful_mode,
661
+ duration_seconds,
662
+ contracts_per_sec,
663
+ extra={
664
+ "discovered_count": discovered_count,
665
+ "failure_count": failure_count,
666
+ "graceful_mode": self._graceful_mode,
667
+ "duration_seconds": duration_seconds,
668
+ "contracts_per_second": contracts_per_sec,
669
+ },
670
+ )
671
+
672
+ async def load_from_contract(
673
+ self,
674
+ contract_path: Path,
675
+ ) -> ProtocolEventProjector:
676
+ """Load a projector from a YAML contract file.
677
+
678
+ Parses the contract, validates its structure and semantics,
679
+ and returns a configured projector instance.
680
+
681
+ Args:
682
+ contract_path: Path to the YAML contract file. Must exist
683
+ and be readable.
684
+
685
+ Returns:
686
+ A configured ProtocolEventProjector instance.
687
+ Note: Currently returns ProjectorShellPlaceholder until
688
+ OMN-1169 implements the full ProjectorShell.
689
+
690
+ Raises:
691
+ ModelOnexError: If contract parsing or validation fails.
692
+ FileNotFoundError: If contract_path does not exist.
693
+ PermissionError: If contract_path is not readable.
694
+ """
695
+ if not contract_path.exists():
696
+ raise FileNotFoundError(
697
+ f"Contract file does not exist: {self._sanitize_path_for_logging(contract_path)}"
698
+ )
699
+
700
+ # Validate security constraints
701
+ allowed_bases = self._base_paths if self._base_paths else [contract_path.parent]
702
+ is_valid, error_msg = self._validate_file_security(contract_path, allowed_bases)
703
+ if not is_valid:
704
+ raise ModelOnexError(
705
+ error_msg or "Security validation failed",
706
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
707
+ )
708
+
709
+ try:
710
+ contract = self._load_contract(contract_path)
711
+ except yaml.YAMLError as e:
712
+ raise ModelOnexError(
713
+ f"Failed to parse YAML contract at {self._sanitize_path_for_logging(contract_path)}: {e}",
714
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
715
+ ) from e
716
+ except ValidationError as e:
717
+ raise ModelOnexError(
718
+ f"Contract validation failed at {self._sanitize_path_for_logging(contract_path)}: {e}",
719
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
720
+ ) from e
721
+ except OSError as e:
722
+ raise ModelOnexError(
723
+ f"Failed to read contract file at {self._sanitize_path_for_logging(contract_path)}: {e.strerror or e}",
724
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
725
+ ) from e
726
+
727
+ logger.debug(
728
+ "Successfully loaded projector contract: %s",
729
+ self._sanitize_path_for_logging(contract_path),
730
+ extra={
731
+ "contract_path": self._sanitize_path_for_logging(contract_path),
732
+ "projector_id": contract.projector_id,
733
+ "aggregate_type": contract.aggregate_type,
734
+ "consumed_events": contract.consumed_events,
735
+ },
736
+ )
737
+
738
+ # Return placeholder until OMN-1169 implements ProjectorShell
739
+ return self._create_projector(contract)
740
+
741
+ async def load_from_directory(
742
+ self,
743
+ directory: Path,
744
+ ) -> list[ProtocolEventProjector]:
745
+ """Load all projectors from contracts in a directory.
746
+
747
+ Discovers all projector contract files in the specified directory
748
+ (recursively) and loads each as a projector.
749
+
750
+ Args:
751
+ directory: Directory containing contract files. Must exist
752
+ and be a directory.
753
+
754
+ Returns:
755
+ List of configured ProtocolEventProjector instances, one for
756
+ each valid contract file found.
757
+
758
+ Raises:
759
+ FileNotFoundError: If directory does not exist.
760
+ NotADirectoryError: If directory is not a directory.
761
+ ModelOnexError: If any contract file is invalid (in strict mode).
762
+ """
763
+ if not directory.exists():
764
+ raise FileNotFoundError(
765
+ f"Directory does not exist: {self._sanitize_path_for_logging(directory)}"
766
+ )
767
+
768
+ if not directory.is_dir():
769
+ raise NotADirectoryError(
770
+ f"Path is not a directory: {self._sanitize_path_for_logging(directory)}"
771
+ )
772
+
773
+ # Generate correlation_id for this discovery operation
774
+ discovery_correlation_id = uuid4()
775
+
776
+ start_time = time.perf_counter()
777
+ projectors: list[ProtocolEventProjector] = []
778
+ validation_errors: list[ModelProjectorValidationError] = []
779
+ discovered_paths: set[Path] = set()
780
+
781
+ allowed_bases = self._base_paths if self._base_paths else [directory]
782
+ contract_files = self._find_contract_files(directory)
783
+
784
+ logger.debug(
785
+ "Scanning directory for projector contracts: %s",
786
+ self._sanitize_path_for_logging(directory),
787
+ extra={
788
+ "directory": self._sanitize_path_for_logging(directory),
789
+ "contracts_found": len(contract_files),
790
+ "graceful_mode": self._graceful_mode,
791
+ "correlation_id": str(discovery_correlation_id),
792
+ },
793
+ )
794
+
795
+ for contract_file in contract_files:
796
+ resolved_path = contract_file.resolve()
797
+ if resolved_path in discovered_paths:
798
+ continue
799
+
800
+ discovered_paths.add(resolved_path)
801
+
802
+ # Security validation
803
+ is_valid, error_msg = self._validate_file_security(
804
+ contract_file, allowed_bases
805
+ )
806
+ if not is_valid:
807
+ if not self._graceful_mode:
808
+ raise ModelOnexError(
809
+ error_msg or "Security validation failed",
810
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
811
+ )
812
+ logger.warning(
813
+ "Security validation failed for %s, skipping",
814
+ self._sanitize_path_for_logging(contract_file),
815
+ extra={
816
+ "contract_file": self._sanitize_path_for_logging(contract_file),
817
+ "graceful_mode": self._graceful_mode,
818
+ "correlation_id": str(discovery_correlation_id),
819
+ },
820
+ )
821
+ validation_errors.append(
822
+ self._create_security_error(
823
+ contract_file, error_msg or "", discovery_correlation_id
824
+ )
825
+ )
826
+ continue
827
+
828
+ try:
829
+ contract = self._load_contract(contract_file)
830
+ projector = self._create_projector(contract)
831
+ projectors.append(projector)
832
+ logger.debug(
833
+ "Successfully parsed contract: %s",
834
+ self._sanitize_path_for_logging(contract_file),
835
+ extra={
836
+ "contract_file": self._sanitize_path_for_logging(contract_file),
837
+ "projector_id": contract.projector_id,
838
+ "aggregate_type": contract.aggregate_type,
839
+ },
840
+ )
841
+ except yaml.YAMLError as e:
842
+ error = self._create_parse_error(
843
+ contract_file, e, discovery_correlation_id
844
+ )
845
+ if not self._graceful_mode:
846
+ raise ModelOnexError(
847
+ f"Failed to parse YAML contract at {self._sanitize_path_for_logging(contract_file)}: {e}",
848
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
849
+ ) from e
850
+ logger.warning(
851
+ "Failed to parse YAML contract in %s, continuing in graceful mode",
852
+ self._sanitize_path_for_logging(contract_file),
853
+ extra={
854
+ "contract_file": self._sanitize_path_for_logging(contract_file),
855
+ "error_type": "yaml_parse_error",
856
+ "graceful_mode": self._graceful_mode,
857
+ "correlation_id": str(discovery_correlation_id),
858
+ },
859
+ )
860
+ validation_errors.append(error)
861
+ except ValidationError as e:
862
+ error = self._create_validation_error(
863
+ contract_file, e, discovery_correlation_id
864
+ )
865
+ if not self._graceful_mode:
866
+ raise ModelOnexError(
867
+ f"Contract validation failed at {self._sanitize_path_for_logging(contract_file)}: {e}",
868
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
869
+ ) from e
870
+ logger.warning(
871
+ "Contract validation failed in %s, continuing in graceful mode",
872
+ self._sanitize_path_for_logging(contract_file),
873
+ extra={
874
+ "contract_file": self._sanitize_path_for_logging(contract_file),
875
+ "error_type": "validation_error",
876
+ "error_count": len(e.errors()),
877
+ "graceful_mode": self._graceful_mode,
878
+ "correlation_id": str(discovery_correlation_id),
879
+ },
880
+ )
881
+ validation_errors.append(error)
882
+ except ModelOnexError as e:
883
+ # In strict mode, always re-raise
884
+ if not self._graceful_mode:
885
+ raise
886
+
887
+ # Graceful mode: collect all ONEX errors
888
+ error_code = getattr(e, "error_code", None)
889
+ error_message = str(e)
890
+ # Check specifically for size limit errors by both error_code AND message
891
+ is_size_limit_error = (
892
+ error_code == EnumCoreErrorCode.VALIDATION_FAILED
893
+ and "size limit" in error_message.lower()
894
+ )
895
+ if is_size_limit_error:
896
+ # File size limit error
897
+ try:
898
+ file_size = contract_file.stat().st_size
899
+ except OSError:
900
+ file_size = 0
901
+ error = self._create_size_limit_error(
902
+ contract_file, file_size, discovery_correlation_id
903
+ )
904
+ logger.warning(
905
+ "Contract file %s exceeds size limit, continuing in graceful mode",
906
+ self._sanitize_path_for_logging(contract_file),
907
+ extra={
908
+ "contract_file": self._sanitize_path_for_logging(
909
+ contract_file
910
+ ),
911
+ "error_type": "size_limit_error",
912
+ "graceful_mode": self._graceful_mode,
913
+ "correlation_id": str(discovery_correlation_id),
914
+ },
915
+ )
916
+ validation_errors.append(error)
917
+ else:
918
+ # Other ONEX errors - collect in graceful mode
919
+ error = ModelProjectorValidationError(
920
+ error_type="ONEX_ERROR",
921
+ contract_path=self._sanitize_path_for_logging(contract_file),
922
+ message=error_message,
923
+ remediation_hint="Check the contract file for issues",
924
+ correlation_id=discovery_correlation_id,
925
+ )
926
+ logger.warning(
927
+ "ONEX error processing %s, continuing in graceful mode",
928
+ self._sanitize_path_for_logging(contract_file),
929
+ extra={
930
+ "contract_file": self._sanitize_path_for_logging(
931
+ contract_file
932
+ ),
933
+ "error_type": "onex_error",
934
+ "graceful_mode": self._graceful_mode,
935
+ "correlation_id": str(discovery_correlation_id),
936
+ },
937
+ )
938
+ validation_errors.append(error)
939
+ except OSError as e:
940
+ if not self._graceful_mode:
941
+ raise ModelOnexError(
942
+ f"Failed to read contract file at {self._sanitize_path_for_logging(contract_file)}: {e}",
943
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
944
+ ) from e
945
+ error = self._create_io_error(
946
+ contract_file, e, discovery_correlation_id
947
+ )
948
+ logger.warning(
949
+ "Failed to read contract file, continuing in graceful mode: %s",
950
+ self._sanitize_path_for_logging(contract_file),
951
+ extra={
952
+ "contract_file": self._sanitize_path_for_logging(contract_file),
953
+ "error_type": "io_error",
954
+ # Use strerror to avoid leaking full paths
955
+ "error_message": e.strerror or "unknown error",
956
+ "graceful_mode": self._graceful_mode,
957
+ "correlation_id": str(discovery_correlation_id),
958
+ },
959
+ )
960
+ validation_errors.append(error)
961
+
962
+ duration_seconds = time.perf_counter() - start_time
963
+ self._log_discovery_results(
964
+ len(projectors), len(validation_errors), duration_seconds
965
+ )
966
+
967
+ return projectors
968
+
969
+ async def discover_and_load(
970
+ self,
971
+ patterns: list[str],
972
+ ) -> list[ProtocolEventProjector]:
973
+ """Discover contracts matching glob patterns and load projectors.
974
+
975
+ Supports flexible contract discovery using glob patterns,
976
+ enabling contracts to be organized in various directory structures.
977
+
978
+ Args:
979
+ patterns: List of glob patterns to match contract files.
980
+ Patterns are relative to the current working directory
981
+ unless absolute. Supports recursive patterns (**).
982
+
983
+ Examples:
984
+ - "contracts/*.yaml" - all YAML in contracts/
985
+ - "**/projectors/*.yaml" - recursive projector discovery
986
+ - "modules/*/projections.yml" - per-module contracts
987
+
988
+ Returns:
989
+ List of configured ProtocolEventProjector instances for all
990
+ contracts matching any of the patterns. Duplicates (same
991
+ file matched by multiple patterns) are deduplicated.
992
+
993
+ Raises:
994
+ ModelOnexError: If any matched contract is invalid (in strict mode).
995
+ """
996
+ # Generate correlation_id for this discovery operation
997
+ discovery_correlation_id = uuid4()
998
+
999
+ start_time = time.perf_counter()
1000
+ projectors: list[ProtocolEventProjector] = []
1001
+ validation_errors: list[ModelProjectorValidationError] = []
1002
+ discovered_paths: set[Path] = set()
1003
+
1004
+ # Determine base paths from patterns for security validation
1005
+ cwd = Path.cwd()
1006
+ allowed_bases = self._base_paths if self._base_paths else [cwd]
1007
+
1008
+ logger.debug(
1009
+ "Starting projector discovery with patterns",
1010
+ extra={
1011
+ "patterns": patterns,
1012
+ "graceful_mode": self._graceful_mode,
1013
+ "cwd": self._sanitize_path_for_logging(cwd),
1014
+ "correlation_id": str(discovery_correlation_id),
1015
+ },
1016
+ )
1017
+
1018
+ # Phase 1: Collect all matched files from all patterns (for count-based DoS prevention)
1019
+ unique_contract_files: list[Path] = []
1020
+ seen_resolved: set[Path] = set()
1021
+
1022
+ for pattern in patterns:
1023
+ pattern_path = Path(pattern)
1024
+
1025
+ # Use glob from cwd for relative patterns
1026
+ if not pattern_path.is_absolute():
1027
+ # Security: If base_paths is configured, ensure cwd is within allowed paths
1028
+ # to prevent bypassing path restrictions with relative patterns
1029
+ if self._base_paths:
1030
+ cwd_resolved = cwd.resolve()
1031
+ cwd_allowed = any(
1032
+ cwd_resolved.is_relative_to(base.resolve())
1033
+ for base in self._base_paths
1034
+ )
1035
+ if not cwd_allowed:
1036
+ raise ModelOnexError(
1037
+ "Relative patterns require cwd to be within allowed base_paths",
1038
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
1039
+ )
1040
+ matched_files = list(cwd.glob(pattern))
1041
+ else:
1042
+ # Reject absolute glob patterns unless explicit base_paths configured
1043
+ if not self._base_paths:
1044
+ raise ModelOnexError(
1045
+ "Absolute glob patterns are not allowed without explicit base_paths",
1046
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
1047
+ )
1048
+
1049
+ # Security: Explicitly reject patterns starting from root
1050
+ # to prevent DoS via filesystem-wide glob scanning (e.g., "/**/foo.yaml")
1051
+ pattern_resolved = pattern_path.resolve()
1052
+ if (
1053
+ pattern_resolved == Path("/").resolve()
1054
+ or len(pattern_resolved.parts) <= 1
1055
+ ):
1056
+ raise ModelOnexError(
1057
+ "Root-level absolute patterns are not allowed - "
1058
+ "would cause filesystem-wide scanning",
1059
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
1060
+ )
1061
+
1062
+ # Security: Validate absolute pattern is under an allowed base path
1063
+ # to prevent DoS via filesystem-wide glob scanning
1064
+ allowed_base = None
1065
+ for base in self._base_paths:
1066
+ try:
1067
+ base_resolved = base.resolve()
1068
+ # Check if pattern starts within this base (for glob patterns,
1069
+ # check the non-glob prefix)
1070
+ pattern_prefix = pattern_resolved
1071
+ # Find the first glob component to get the concrete prefix
1072
+ pattern_parts = pattern_path.parts
1073
+ concrete_parts: list[str] = []
1074
+ for part in pattern_parts:
1075
+ if "*" in part or "?" in part or "[" in part:
1076
+ break
1077
+ concrete_parts.append(part)
1078
+ if concrete_parts:
1079
+ pattern_prefix = Path(*concrete_parts).resolve()
1080
+
1081
+ if pattern_prefix.is_relative_to(base_resolved):
1082
+ allowed_base = base_resolved
1083
+ break
1084
+ except (ValueError, OSError):
1085
+ continue
1086
+
1087
+ if allowed_base is None:
1088
+ raise ModelOnexError(
1089
+ "Absolute pattern not under allowed base_paths",
1090
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
1091
+ )
1092
+
1093
+ # Glob from the allowed base, using relative pattern portion
1094
+ try:
1095
+ # Use the resolved pattern_prefix (non-glob prefix) to compute
1096
+ # the relative path, then reconstruct the pattern
1097
+ relative_prefix = pattern_prefix.relative_to(allowed_base)
1098
+ # Get the glob suffix (parts after the concrete prefix)
1099
+ glob_suffix_parts = pattern_path.parts[len(concrete_parts) :]
1100
+ if glob_suffix_parts:
1101
+ relative_pattern = str(
1102
+ relative_prefix / Path(*glob_suffix_parts)
1103
+ )
1104
+ else:
1105
+ relative_pattern = str(relative_prefix)
1106
+ except ValueError:
1107
+ # If relative_to fails even after is_relative_to succeeded,
1108
+ # this indicates a path resolution issue (e.g., symlinks)
1109
+ # Reject the pattern to prevent potential security bypass
1110
+ raise ModelOnexError(
1111
+ "Absolute pattern path resolution failed - "
1112
+ "possible symlink security issue",
1113
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
1114
+ ) from None
1115
+
1116
+ matched_files = list(allowed_base.glob(relative_pattern))
1117
+
1118
+ # Filter to projector contracts
1119
+ matched_files = [
1120
+ f for f in matched_files if self._is_projector_contract(f.name)
1121
+ ]
1122
+
1123
+ # Collect unique files (deduplicate by resolved path)
1124
+ for contract_file in matched_files:
1125
+ resolved_path = contract_file.resolve()
1126
+ if resolved_path not in seen_resolved:
1127
+ seen_resolved.add(resolved_path)
1128
+ unique_contract_files.append(contract_file)
1129
+
1130
+ # Phase 2: Check file count to prevent DoS via expensive glob patterns
1131
+ total_matched = len(unique_contract_files)
1132
+ if total_matched > MAX_DISCOVERY_FILES:
1133
+ raise ModelOnexError(
1134
+ f"Discovery aborted: matched {total_matched} files (limit: {MAX_DISCOVERY_FILES}). "
1135
+ "Use more specific patterns to reduce scope.",
1136
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
1137
+ )
1138
+
1139
+ logger.debug(
1140
+ "Discovery matched %d unique contract files",
1141
+ total_matched,
1142
+ extra={
1143
+ "total_matched": total_matched,
1144
+ "max_allowed": MAX_DISCOVERY_FILES,
1145
+ "correlation_id": str(discovery_correlation_id),
1146
+ },
1147
+ )
1148
+
1149
+ # Phase 3: Process each collected file
1150
+ for contract_file in unique_contract_files:
1151
+ resolved_path = contract_file.resolve()
1152
+ discovered_paths.add(resolved_path)
1153
+
1154
+ # Security validation
1155
+ is_valid, error_msg = self._validate_file_security(
1156
+ contract_file, allowed_bases
1157
+ )
1158
+ if not is_valid:
1159
+ if not self._graceful_mode:
1160
+ raise ModelOnexError(
1161
+ error_msg or "Security validation failed",
1162
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
1163
+ )
1164
+ logger.warning(
1165
+ "Security validation failed for %s, skipping",
1166
+ self._sanitize_path_for_logging(contract_file),
1167
+ extra={"correlation_id": str(discovery_correlation_id)},
1168
+ )
1169
+ validation_errors.append(
1170
+ self._create_security_error(
1171
+ contract_file, error_msg or "", discovery_correlation_id
1172
+ )
1173
+ )
1174
+ continue
1175
+
1176
+ try:
1177
+ contract = self._load_contract(contract_file)
1178
+ projector = self._create_projector(contract)
1179
+ projectors.append(projector)
1180
+ logger.debug(
1181
+ "Successfully loaded contract from pattern: %s",
1182
+ self._sanitize_path_for_logging(contract_file),
1183
+ extra={
1184
+ "contract_file": self._sanitize_path_for_logging(contract_file),
1185
+ "projector_id": contract.projector_id,
1186
+ "correlation_id": str(discovery_correlation_id),
1187
+ },
1188
+ )
1189
+ except yaml.YAMLError as e:
1190
+ error = self._create_parse_error(
1191
+ contract_file, e, discovery_correlation_id
1192
+ )
1193
+ if not self._graceful_mode:
1194
+ raise ModelOnexError(
1195
+ f"Failed to parse YAML contract at {self._sanitize_path_for_logging(contract_file)}: {e}",
1196
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
1197
+ ) from e
1198
+ validation_errors.append(error)
1199
+ except ValidationError as e:
1200
+ error = self._create_validation_error(
1201
+ contract_file, e, discovery_correlation_id
1202
+ )
1203
+ if not self._graceful_mode:
1204
+ raise ModelOnexError(
1205
+ f"Contract validation failed at {self._sanitize_path_for_logging(contract_file)}: {e}",
1206
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
1207
+ ) from e
1208
+ validation_errors.append(error)
1209
+ except ModelOnexError as e:
1210
+ # In strict mode, always re-raise
1211
+ if not self._graceful_mode:
1212
+ raise
1213
+
1214
+ # Graceful mode: collect all ONEX errors
1215
+ error_code = getattr(e, "error_code", None)
1216
+ error_message = str(e)
1217
+ # Check specifically for size limit errors by both error_code AND message
1218
+ is_size_limit_error = (
1219
+ error_code == EnumCoreErrorCode.VALIDATION_FAILED
1220
+ and "size limit" in error_message.lower()
1221
+ )
1222
+ if is_size_limit_error:
1223
+ try:
1224
+ file_size = contract_file.stat().st_size
1225
+ except OSError:
1226
+ file_size = 0
1227
+ validation_errors.append(
1228
+ self._create_size_limit_error(
1229
+ contract_file, file_size, discovery_correlation_id
1230
+ )
1231
+ )
1232
+ else:
1233
+ # Other ONEX errors - collect in graceful mode
1234
+ validation_errors.append(
1235
+ ModelProjectorValidationError(
1236
+ error_type="ONEX_ERROR",
1237
+ contract_path=self._sanitize_path_for_logging(
1238
+ contract_file
1239
+ ),
1240
+ message=error_message,
1241
+ remediation_hint="Check the contract file for issues",
1242
+ correlation_id=discovery_correlation_id,
1243
+ )
1244
+ )
1245
+ except OSError as e:
1246
+ if not self._graceful_mode:
1247
+ raise ModelOnexError(
1248
+ f"Failed to read contract file at {self._sanitize_path_for_logging(contract_file)}: {e}",
1249
+ error_code=EnumCoreErrorCode.VALIDATION_FAILED,
1250
+ ) from e
1251
+ validation_errors.append(
1252
+ self._create_io_error(contract_file, e, discovery_correlation_id)
1253
+ )
1254
+
1255
+ duration_seconds = time.perf_counter() - start_time
1256
+ self._log_discovery_results(
1257
+ len(projectors), len(validation_errors), duration_seconds
1258
+ )
1259
+
1260
+ return projectors
1261
+
1262
+ async def discover_with_errors(
1263
+ self,
1264
+ directory: Path,
1265
+ ) -> ModelProjectorDiscoveryResult:
1266
+ """Discover projectors and return both successes and errors.
1267
+
1268
+ This method always operates in graceful mode, collecting all
1269
+ errors rather than raising on the first failure.
1270
+
1271
+ Args:
1272
+ directory: Directory to scan for contract files.
1273
+
1274
+ Returns:
1275
+ ModelProjectorDiscoveryResult containing both loaded projectors
1276
+ and validation errors.
1277
+
1278
+ Raises:
1279
+ FileNotFoundError: If directory does not exist.
1280
+ NotADirectoryError: If directory is not a directory.
1281
+ """
1282
+ if not directory.exists():
1283
+ raise FileNotFoundError(
1284
+ f"Directory does not exist: {self._sanitize_path_for_logging(directory)}"
1285
+ )
1286
+
1287
+ if not directory.is_dir():
1288
+ raise NotADirectoryError(
1289
+ f"Path is not a directory: {self._sanitize_path_for_logging(directory)}"
1290
+ )
1291
+
1292
+ # Generate correlation_id for this discovery operation
1293
+ discovery_correlation_id = uuid4()
1294
+
1295
+ start_time = time.perf_counter()
1296
+ projectors: list[ProtocolEventProjector] = []
1297
+ validation_errors: list[ModelProjectorValidationError] = []
1298
+ discovered_paths: set[Path] = set()
1299
+
1300
+ allowed_bases = self._base_paths if self._base_paths else [directory]
1301
+ contract_files = self._find_contract_files(directory)
1302
+
1303
+ logger.debug(
1304
+ "Scanning directory for projector contracts with error collection: %s",
1305
+ self._sanitize_path_for_logging(directory),
1306
+ extra={
1307
+ "directory": self._sanitize_path_for_logging(directory),
1308
+ "contracts_found": len(contract_files),
1309
+ "correlation_id": str(discovery_correlation_id),
1310
+ },
1311
+ )
1312
+
1313
+ for contract_file in contract_files:
1314
+ resolved_path = contract_file.resolve()
1315
+ if resolved_path in discovered_paths:
1316
+ continue
1317
+
1318
+ discovered_paths.add(resolved_path)
1319
+
1320
+ # Security validation
1321
+ is_valid, error_msg = self._validate_file_security(
1322
+ contract_file, allowed_bases
1323
+ )
1324
+ if not is_valid:
1325
+ logger.warning(
1326
+ "Security validation failed for %s, collecting error",
1327
+ self._sanitize_path_for_logging(contract_file),
1328
+ extra={
1329
+ "contract_file": self._sanitize_path_for_logging(contract_file),
1330
+ "correlation_id": str(discovery_correlation_id),
1331
+ },
1332
+ )
1333
+ validation_errors.append(
1334
+ self._create_security_error(
1335
+ contract_file, error_msg or "", discovery_correlation_id
1336
+ )
1337
+ )
1338
+ continue
1339
+
1340
+ try:
1341
+ contract = self._load_contract(contract_file)
1342
+ projector = self._create_projector(contract)
1343
+ projectors.append(projector)
1344
+ logger.debug(
1345
+ "Successfully parsed contract: %s",
1346
+ self._sanitize_path_for_logging(contract_file),
1347
+ extra={
1348
+ "contract_file": self._sanitize_path_for_logging(contract_file),
1349
+ "projector_id": contract.projector_id,
1350
+ "aggregate_type": contract.aggregate_type,
1351
+ "correlation_id": str(discovery_correlation_id),
1352
+ },
1353
+ )
1354
+ except yaml.YAMLError as e:
1355
+ error = self._create_parse_error(
1356
+ contract_file, e, discovery_correlation_id
1357
+ )
1358
+ logger.warning(
1359
+ "Failed to parse YAML contract in %s, collecting error",
1360
+ self._sanitize_path_for_logging(contract_file),
1361
+ extra={
1362
+ "contract_file": self._sanitize_path_for_logging(contract_file),
1363
+ "error_type": "yaml_parse_error",
1364
+ "correlation_id": str(discovery_correlation_id),
1365
+ },
1366
+ )
1367
+ validation_errors.append(error)
1368
+ except ValidationError as e:
1369
+ error = self._create_validation_error(
1370
+ contract_file, e, discovery_correlation_id
1371
+ )
1372
+ logger.warning(
1373
+ "Contract validation failed in %s, collecting error",
1374
+ self._sanitize_path_for_logging(contract_file),
1375
+ extra={
1376
+ "contract_file": self._sanitize_path_for_logging(contract_file),
1377
+ "error_type": "validation_error",
1378
+ "error_count": len(e.errors()),
1379
+ "correlation_id": str(discovery_correlation_id),
1380
+ },
1381
+ )
1382
+ validation_errors.append(error)
1383
+ except ModelOnexError as e:
1384
+ error_code = getattr(e, "error_code", None)
1385
+ error_message = str(e)
1386
+ # Check specifically for size limit errors by both error_code AND message
1387
+ is_size_limit_error = (
1388
+ error_code == EnumCoreErrorCode.VALIDATION_FAILED
1389
+ and "size limit" in error_message.lower()
1390
+ )
1391
+ if is_size_limit_error:
1392
+ # File size limit error
1393
+ try:
1394
+ file_size = contract_file.stat().st_size
1395
+ except OSError:
1396
+ file_size = 0
1397
+ error = self._create_size_limit_error(
1398
+ contract_file, file_size, discovery_correlation_id
1399
+ )
1400
+ logger.warning(
1401
+ "Contract file %s exceeds size limit, collecting error",
1402
+ self._sanitize_path_for_logging(contract_file),
1403
+ extra={
1404
+ "contract_file": self._sanitize_path_for_logging(
1405
+ contract_file
1406
+ ),
1407
+ "error_type": "size_limit_error",
1408
+ "correlation_id": str(discovery_correlation_id),
1409
+ },
1410
+ )
1411
+ validation_errors.append(error)
1412
+ else:
1413
+ # For other ModelOnexError types, create a generic validation error
1414
+ error = ModelProjectorValidationError(
1415
+ error_type="ONEX_ERROR",
1416
+ contract_path=self._sanitize_path_for_logging(contract_file),
1417
+ message=error_message,
1418
+ remediation_hint="Check the contract file for issues",
1419
+ correlation_id=discovery_correlation_id,
1420
+ )
1421
+ validation_errors.append(error)
1422
+ except OSError as e:
1423
+ error = self._create_io_error(
1424
+ contract_file, e, discovery_correlation_id
1425
+ )
1426
+ logger.warning(
1427
+ "Failed to read contract file, collecting error: %s",
1428
+ self._sanitize_path_for_logging(contract_file),
1429
+ extra={
1430
+ "contract_file": self._sanitize_path_for_logging(contract_file),
1431
+ "error_type": "io_error",
1432
+ # Use strerror to avoid leaking full paths
1433
+ "error_message": e.strerror or "unknown error",
1434
+ "correlation_id": str(discovery_correlation_id),
1435
+ },
1436
+ )
1437
+ validation_errors.append(error)
1438
+
1439
+ duration_seconds = time.perf_counter() - start_time
1440
+ self._log_discovery_results(
1441
+ len(projectors), len(validation_errors), duration_seconds
1442
+ )
1443
+
1444
+ return ModelProjectorDiscoveryResult(
1445
+ projectors=projectors,
1446
+ validation_errors=validation_errors,
1447
+ discovery_correlation_id=discovery_correlation_id,
1448
+ )
1449
+
1450
+
1451
+ __all__ = [
1452
+ "MAX_CONTRACT_SIZE",
1453
+ "MAX_DISCOVERY_FILES",
1454
+ "ModelProjectorDiscoveryResult", # Re-exported from omnibase_infra.models.projectors
1455
+ "ModelProjectorValidationError", # Re-exported from omnibase_infra.models.projectors
1456
+ "PROJECTOR_CONTRACT_PATTERNS",
1457
+ "ProjectorPluginLoader",
1458
+ "ProjectorShell", # Full implementation with database access (OMN-1169)
1459
+ "ProjectorShellPlaceholder", # Placeholder when no pool is provided
1460
+ "ProtocolEventProjector", # Re-exported from omnibase_infra.protocols
1461
+ "ProtocolProjectorSchemaValidator", # Re-exported from omnibase_infra.protocols
1462
+ ]