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,1152 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Topic Category Validator for ONEX Execution Shape Validation.
4
+
5
+ Validates that message categories match their topic naming patterns in the
6
+ ONEX event-driven architecture. This ensures architectural consistency by
7
+ enforcing topic naming conventions at both static analysis and runtime.
8
+
9
+ Topic Naming Conventions:
10
+ - EVENTs: Read from `<domain>.events` topics (e.g., `order.events`)
11
+ - COMMANDs: Read from `<domain>.commands` topics (e.g., `order.commands`)
12
+ - INTENTs: Read from `<domain>.intents` topics (e.g., `checkout.intents`)
13
+ - PROJECTIONs: Can be anywhere (internal state projections)
14
+
15
+ Validation Modes:
16
+ - Runtime: Validate messages as they flow through the system
17
+ - Static (AST): Analyze Python files for topic/category mismatches in CI
18
+
19
+ Usage:
20
+ >>> from omnibase_infra.validation import TopicCategoryValidator
21
+ >>> from omnibase_infra.enums import EnumMessageCategory
22
+ >>>
23
+ >>> validator = TopicCategoryValidator()
24
+ >>> result = validator.validate_message_topic(
25
+ ... EnumMessageCategory.EVENT,
26
+ ... "order.commands", # Wrong topic for events
27
+ ... )
28
+ >>> if result is not None:
29
+ ... print(f"Violation: {result.message}")
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import ast
35
+ import logging
36
+ import re
37
+ from pathlib import Path
38
+
39
+ from omnibase_infra.enums import (
40
+ EnumExecutionShapeViolation,
41
+ EnumMessageCategory,
42
+ EnumNodeArchetype,
43
+ EnumNodeOutputType,
44
+ EnumValidationSeverity,
45
+ )
46
+ from omnibase_infra.models.validation.model_execution_shape_violation import (
47
+ ModelExecutionShapeViolationResult,
48
+ )
49
+ from omnibase_infra.types import MessageOutputCategory
50
+
51
+ logger = logging.getLogger(__name__)
52
+
53
+ # Topic naming patterns for each message category
54
+ # Matches patterns like: order.events, user-service.commands, checkout.intents
55
+ #
56
+ # DESIGN DECISION - Regex vs Substring Matching:
57
+ # We use regex patterns instead of simple substring/suffix checks for validation
58
+ # because:
59
+ #
60
+ # 1. **Domain validation**: The pattern `^[\w-]+\.` ensures the domain portion
61
+ # contains only valid characters (alphanumeric, underscore, hyphen). A simple
62
+ # `.endswith(".events")` check would accept malformed topics like "...events"
63
+ # or topics with invalid characters in the domain.
64
+ #
65
+ # 2. **Exactness**: The `^` and `$` anchors ensure we match the ENTIRE topic name.
66
+ # This prevents false positives on topics like "order.events.dlq" or
67
+ # "prefix.order.events" which would incorrectly match a suffix check.
68
+ #
69
+ # 3. **Consistency**: All patterns use the same validation logic, making it
70
+ # easier to reason about and extend (e.g., adding new patterns for other
71
+ # topic types).
72
+ #
73
+ # Trade-off: Regex is slightly slower than substring checks, but the validation
74
+ # accuracy and correctness benefits outweigh the performance cost for this
75
+ # use case (topic names are short strings, validation happens at configuration
76
+ # time, not in hot paths).
77
+ TOPIC_CATEGORY_PATTERNS: dict[EnumMessageCategory, re.Pattern[str]] = {
78
+ EnumMessageCategory.EVENT: re.compile(r"^[\w-]+\.events$"),
79
+ EnumMessageCategory.COMMAND: re.compile(r"^[\w-]+\.commands$"),
80
+ EnumMessageCategory.INTENT: re.compile(r"^[\w-]+\.intents$"),
81
+ }
82
+
83
+ # Topic suffix mapping for each message category
84
+ # Note: PROJECTION uses EnumNodeOutputType because it's a node output type, not a message category.
85
+ # Projections are internal state outputs from REDUCER nodes, not routed messages on Kafka topics.
86
+ TOPIC_SUFFIXES: dict[MessageOutputCategory, str] = {
87
+ EnumMessageCategory.EVENT: "events",
88
+ EnumMessageCategory.COMMAND: "commands",
89
+ EnumMessageCategory.INTENT: "intents",
90
+ EnumNodeOutputType.PROJECTION: "", # Projections have no suffix requirement
91
+ }
92
+
93
+ # Node archetype to expected message categories mapping
94
+ #
95
+ # DUAL PURPOSE EXPLANATION:
96
+ # This mapping serves two purposes in validation:
97
+ # 1. INPUT VALIDATION: Which message categories each node archetype can CONSUME
98
+ # (e.g., REDUCER can consume EVENT messages from *.events topics)
99
+ # 2. OUTPUT VALIDATION: Which output types each node archetype can PRODUCE
100
+ # (e.g., REDUCER can produce PROJECTION outputs)
101
+ #
102
+ # Why the union type (MessageOutputCategory)?
103
+ # - EnumMessageCategory values (EVENT, COMMAND, INTENT) are for message routing
104
+ # - EnumNodeOutputType values (including PROJECTION) are for node output validation
105
+ # - REDUCER is unique: it consumes EVENTs (message category) and produces PROJECTIONs
106
+ # (node output type that is NOT routed as a message)
107
+ #
108
+ # See ADR: docs/decisions/adr-enum-message-category-vs-node-output-type.md
109
+ NODE_ARCHETYPE_EXPECTED_CATEGORIES: dict[
110
+ EnumNodeArchetype, list[MessageOutputCategory]
111
+ ] = {
112
+ EnumNodeArchetype.EFFECT: [
113
+ EnumMessageCategory.COMMAND,
114
+ EnumMessageCategory.EVENT,
115
+ ],
116
+ EnumNodeArchetype.COMPUTE: [
117
+ EnumMessageCategory.EVENT,
118
+ EnumMessageCategory.COMMAND,
119
+ EnumMessageCategory.INTENT,
120
+ ],
121
+ EnumNodeArchetype.REDUCER: [
122
+ EnumMessageCategory.EVENT,
123
+ EnumNodeOutputType.PROJECTION,
124
+ ],
125
+ EnumNodeArchetype.ORCHESTRATOR: [
126
+ EnumMessageCategory.EVENT,
127
+ EnumMessageCategory.COMMAND,
128
+ EnumMessageCategory.INTENT,
129
+ ],
130
+ }
131
+
132
+
133
+ class TopicCategoryValidator:
134
+ """Validator for ensuring message categories match topic patterns.
135
+
136
+ Enforces ONEX topic naming conventions by validating that:
137
+ - Events are only read from `*.events` topics
138
+ - Commands are only read from `*.commands` topics
139
+ - Intents are only read from `*.intents` topics
140
+ - Projections can exist anywhere (no naming constraint)
141
+
142
+ This validator supports both runtime validation (for message processing)
143
+ and subscription validation (for handler configuration).
144
+
145
+ Attributes:
146
+ patterns: Compiled regex patterns for topic validation.
147
+ suffixes: Expected topic suffixes for each message category.
148
+
149
+ Example:
150
+ >>> validator = TopicCategoryValidator()
151
+ >>> # Valid: Event on events topic
152
+ >>> result = validator.validate_message_topic(
153
+ ... EnumMessageCategory.EVENT, "order.events"
154
+ ... )
155
+ >>> assert result is None # No violation
156
+ >>>
157
+ >>> # Invalid: Event on commands topic
158
+ >>> result = validator.validate_message_topic(
159
+ ... EnumMessageCategory.EVENT, "order.commands"
160
+ ... )
161
+ >>> assert result is not None # Violation detected
162
+ """
163
+
164
+ def __init__(self) -> None:
165
+ """Initialize the topic category validator with default patterns."""
166
+ self.patterns = TOPIC_CATEGORY_PATTERNS
167
+ self.suffixes = TOPIC_SUFFIXES
168
+ self.archetype_categories = NODE_ARCHETYPE_EXPECTED_CATEGORIES
169
+
170
+ def validate_message_topic(
171
+ self,
172
+ message_category: MessageOutputCategory,
173
+ topic_name: str,
174
+ ) -> ModelExecutionShapeViolationResult | None:
175
+ """Validate that a message category matches its topic pattern.
176
+
177
+ Checks if the message category is being read from or written to
178
+ an appropriately named topic according to ONEX conventions.
179
+
180
+ Args:
181
+ message_category: The category of the message (EVENT, COMMAND, etc.)
182
+ or node output type (PROJECTION). Projections have no topic
183
+ naming constraint and are always valid.
184
+ topic_name: The Kafka topic name being used.
185
+
186
+ Returns:
187
+ A ModelExecutionShapeViolationResult if there's a mismatch,
188
+ or None if the message/topic combination is valid.
189
+
190
+ Example:
191
+ >>> validator = TopicCategoryValidator()
192
+ >>> # This should pass - event on events topic
193
+ >>> result = validator.validate_message_topic(
194
+ ... EnumMessageCategory.EVENT, "order.events"
195
+ ... )
196
+ >>> assert result is None
197
+ >>>
198
+ >>> # This should fail - event on commands topic
199
+ >>> result = validator.validate_message_topic(
200
+ ... EnumMessageCategory.EVENT, "order.commands"
201
+ ... )
202
+ >>> assert result is not None
203
+ >>> assert result.violation_type == EnumExecutionShapeViolation.TOPIC_CATEGORY_MISMATCH
204
+ """
205
+ # Projections have no topic naming constraint (they are node outputs, not routed messages)
206
+ if message_category == EnumNodeOutputType.PROJECTION:
207
+ return None
208
+
209
+ # Get the expected pattern for this category
210
+ # Note: patterns dict uses EnumMessageCategory keys. EnumNodeOutputType values
211
+ # won't match (different enum types even with same string values), so lookup
212
+ # will return None for any EnumNodeOutputType passed here. This is correct
213
+ # behavior - we only validate EnumMessageCategory values against topic patterns.
214
+ if isinstance(message_category, EnumMessageCategory):
215
+ expected_pattern = self.patterns.get(message_category)
216
+ else:
217
+ # EnumNodeOutputType values (other than PROJECTION which is handled above)
218
+ # don't have topic naming constraints
219
+ expected_pattern = None
220
+ if expected_pattern is None:
221
+ # Unknown category or node output type - no topic constraint
222
+ return None
223
+
224
+ # Check if topic matches the expected pattern
225
+ if expected_pattern.match(topic_name):
226
+ return None
227
+
228
+ # Violation detected - category doesn't match topic pattern
229
+ expected_suffix = self.suffixes.get(message_category, "unknown")
230
+ return ModelExecutionShapeViolationResult(
231
+ violation_type=EnumExecutionShapeViolation.TOPIC_CATEGORY_MISMATCH,
232
+ node_archetype=None, # Unknown at runtime validation without handler context
233
+ file_path="<runtime>", # Runtime validation has no file context
234
+ line_number=1,
235
+ message=(
236
+ f"Topic category mismatch: Message category '{message_category.name}' "
237
+ f"(EnumMessageCategory.{message_category.name}) requires a topic matching "
238
+ f"pattern '<domain>.{expected_suffix}'. Found topic: '{topic_name}'. "
239
+ f"Expected pattern: '*.{expected_suffix}' (e.g., 'order.{expected_suffix}')."
240
+ ),
241
+ severity=EnumValidationSeverity.ERROR,
242
+ )
243
+
244
+ def validate_subscription(
245
+ self,
246
+ node_archetype: EnumNodeArchetype,
247
+ subscribed_topics: list[str],
248
+ expected_categories: list[MessageOutputCategory],
249
+ ) -> list[ModelExecutionShapeViolationResult]:
250
+ """Validate that handler subscriptions match expected message types.
251
+
252
+ Checks if a handler is subscribed to topics that match the message
253
+ categories it should be consuming based on ONEX architecture rules.
254
+
255
+ Args:
256
+ node_archetype: The node archetype (EFFECT, COMPUTE, REDUCER, ORCHESTRATOR).
257
+ subscribed_topics: List of Kafka topics the handler subscribes to.
258
+ expected_categories: List of message categories or node output types
259
+ the handler should process (e.g., EVENT, COMMAND, PROJECTION).
260
+
261
+ Returns:
262
+ List of violations for any topic that doesn't match expected categories.
263
+ Empty list if all subscriptions are valid or if inputs are invalid types.
264
+
265
+ Example:
266
+ >>> validator = TopicCategoryValidator()
267
+ >>> violations = validator.validate_subscription(
268
+ ... EnumNodeArchetype.REDUCER,
269
+ ... ["order.events", "order.commands"], # commands not valid for reducer
270
+ ... [EnumMessageCategory.EVENT, EnumNodeOutputType.PROJECTION],
271
+ ... )
272
+ >>> assert len(violations) == 1
273
+ >>> assert "order.commands" in violations[0].message
274
+ """
275
+ violations: list[ModelExecutionShapeViolationResult] = []
276
+
277
+ # Defensive type checks for list inputs
278
+ if not isinstance(subscribed_topics, list):
279
+ return violations
280
+ if not isinstance(expected_categories, list):
281
+ return violations
282
+
283
+ for topic in subscribed_topics:
284
+ # Skip non-string topics
285
+ if not isinstance(topic, str):
286
+ continue
287
+ # Determine what category this topic implies
288
+ inferred_category = self._infer_category_from_topic(topic)
289
+
290
+ if inferred_category is None:
291
+ # Topic doesn't follow any known pattern - warning
292
+ violations.append(
293
+ ModelExecutionShapeViolationResult(
294
+ violation_type=EnumExecutionShapeViolation.TOPIC_CATEGORY_MISMATCH,
295
+ node_archetype=node_archetype,
296
+ file_path="<runtime>",
297
+ line_number=1,
298
+ message=(
299
+ f"Topic naming convention violation: Topic '{topic}' does not match "
300
+ f"ONEX naming conventions. Node archetype: '{node_archetype.name}' "
301
+ f"(EnumNodeArchetype.{node_archetype.name}). Expected topic patterns: "
302
+ f"'<domain>.events', '<domain>.commands', or '<domain>.intents'. "
303
+ f"Example valid topics: 'order.events', 'user.commands'."
304
+ ),
305
+ severity=EnumValidationSeverity.WARNING,
306
+ )
307
+ )
308
+ continue
309
+
310
+ # Check if the inferred category is in the expected categories
311
+ if inferred_category not in expected_categories:
312
+ expected_names = [c.name for c in expected_categories]
313
+ violations.append(
314
+ ModelExecutionShapeViolationResult(
315
+ violation_type=EnumExecutionShapeViolation.TOPIC_CATEGORY_MISMATCH,
316
+ node_archetype=node_archetype,
317
+ file_path="<runtime>",
318
+ line_number=1,
319
+ message=(
320
+ f"Subscription category mismatch: Node archetype '{node_archetype.name}' "
321
+ f"(EnumNodeArchetype.{node_archetype.name}) subscribed to topic '{topic}' "
322
+ f"which implies '{inferred_category.name}' messages. "
323
+ f"Expected message categories for this archetype: [{', '.join(expected_names)}]. "
324
+ f"Found: {inferred_category.name}. "
325
+ f"Review NODE_ARCHETYPE_EXPECTED_CATEGORIES for valid subscriptions."
326
+ ),
327
+ severity=EnumValidationSeverity.ERROR,
328
+ )
329
+ )
330
+
331
+ return violations
332
+
333
+ def extract_domain_from_topic(self, topic: str) -> str | None:
334
+ """Extract the domain name from a topic.
335
+
336
+ Parses a topic name and returns the domain prefix before the
337
+ category suffix (events, commands, intents).
338
+
339
+ Args:
340
+ topic: The Kafka topic name (e.g., 'order.events', 'user-service.commands').
341
+
342
+ Returns:
343
+ The domain portion of the topic name, or None if the topic
344
+ doesn't follow the expected pattern.
345
+
346
+ Example:
347
+ >>> validator = TopicCategoryValidator()
348
+ >>> validator.extract_domain_from_topic("order.events")
349
+ 'order'
350
+ >>> validator.extract_domain_from_topic("user-service.commands")
351
+ 'user-service'
352
+ >>> validator.extract_domain_from_topic("invalid-topic")
353
+ None
354
+ """
355
+ for suffix in ("events", "commands", "intents"):
356
+ if topic.endswith(f".{suffix}"):
357
+ domain = topic[: -(len(suffix) + 1)] # Remove '.' + suffix
358
+ if domain:
359
+ return domain
360
+ return None
361
+
362
+ def get_expected_topic_suffix(
363
+ self,
364
+ category: MessageOutputCategory,
365
+ ) -> str:
366
+ """Get the expected topic suffix for a message category or node output type.
367
+
368
+ Returns the topic suffix that should be used for topics containing
369
+ messages of the specified category or output type.
370
+
371
+ Args:
372
+ category: The message category (EVENT, COMMAND, INTENT) or node
373
+ output type (PROJECTION). Projections have no suffix requirement.
374
+
375
+ Returns:
376
+ The expected topic suffix ('events', 'commands', 'intents', or ''
377
+ for projections).
378
+
379
+ Example:
380
+ >>> validator = TopicCategoryValidator()
381
+ >>> validator.get_expected_topic_suffix(EnumMessageCategory.EVENT)
382
+ 'events'
383
+ >>> validator.get_expected_topic_suffix(EnumMessageCategory.COMMAND)
384
+ 'commands'
385
+ """
386
+ return self.suffixes.get(category, "")
387
+
388
+ def _infer_category_from_topic(
389
+ self,
390
+ topic: str,
391
+ ) -> EnumMessageCategory | None:
392
+ """Infer the message category from a topic name.
393
+
394
+ Internal method that determines what type of messages a topic
395
+ is expected to contain based on its naming pattern.
396
+
397
+ Args:
398
+ topic: The Kafka topic name.
399
+
400
+ Returns:
401
+ The inferred message category, or None if the topic doesn't
402
+ match any known pattern.
403
+ """
404
+ for category, pattern in self.patterns.items():
405
+ if pattern.match(topic):
406
+ return category
407
+ return None
408
+
409
+
410
+ class TopicCategoryASTVisitor(ast.NodeVisitor):
411
+ """AST visitor for detecting topic/category mismatches in Python code.
412
+
413
+ Analyzes Python source files to detect potential mismatches between
414
+ message categories and topic names used in producer/consumer calls.
415
+
416
+ This visitor looks for patterns like:
417
+ - consumer.subscribe("order.events") with handler processing commands
418
+ - producer.send("user.commands", event_data) - sending event to commands topic
419
+
420
+ Attributes:
421
+ violations: List of detected violations.
422
+ file_path: Path to the file being analyzed.
423
+ validator: TopicCategoryValidator instance for validation logic.
424
+ current_node_archetype: Inferred node archetype from class context.
425
+ """
426
+
427
+ def __init__(
428
+ self,
429
+ file_path: Path,
430
+ validator: TopicCategoryValidator,
431
+ ) -> None:
432
+ """Initialize the AST visitor.
433
+
434
+ Args:
435
+ file_path: Path to the file being analyzed.
436
+ validator: TopicCategoryValidator instance for validation logic.
437
+ """
438
+ self.violations: list[ModelExecutionShapeViolationResult] = []
439
+ self.file_path = file_path
440
+ self.validator = validator
441
+ self.current_node_archetype: EnumNodeArchetype | None = None
442
+ self.current_class_name: str | None = None
443
+
444
+ def visit_ClassDef(self, node: ast.ClassDef) -> ast.AST:
445
+ """Visit class definitions to infer node archetype from class name.
446
+
447
+ Infers the node archetype based on class name conventions:
448
+ - *Effect -> EFFECT
449
+ - *Compute -> COMPUTE
450
+ - *Reducer -> REDUCER
451
+ - *Orchestrator -> ORCHESTRATOR
452
+
453
+ PATTERN MATCHING ORDER:
454
+ The order of keyword checks (effect, compute, reducer, orchestrator)
455
+ matters when a class name contains multiple keywords. The checks are
456
+ ordered by specificity of the ONEX node archetypes:
457
+
458
+ 1. "effect" - Checked first because EFFECT nodes are most common
459
+ for I/O operations and have the most restrictive constraints
460
+ 2. "compute" - Pure computation nodes, checked second
461
+ 3. "reducer" - State projection nodes with strict determinism rules
462
+ 4. "orchestrator" - Workflow coordination nodes
463
+
464
+ Example edge cases:
465
+ - "EffectReducer" would be classified as EFFECT (first match wins)
466
+ - "ComputeOrchestrator" would be classified as COMPUTE
467
+
468
+ In practice, handler class names should be unambiguous and follow
469
+ the convention of using a single node archetype in the name suffix
470
+ (e.g., "OrderEffectHandler", not "OrderEffectReducer").
471
+
472
+ Args:
473
+ node: The AST ClassDef node.
474
+
475
+ Returns:
476
+ The visited node.
477
+ """
478
+ old_archetype = self.current_node_archetype
479
+ old_class_name = self.current_class_name
480
+
481
+ self.current_class_name = node.name
482
+
483
+ # Infer node archetype from class name.
484
+ # Order matters: first match wins for ambiguous names.
485
+ class_name = node.name.lower()
486
+ if "effect" in class_name:
487
+ self.current_node_archetype = EnumNodeArchetype.EFFECT
488
+ elif "compute" in class_name:
489
+ self.current_node_archetype = EnumNodeArchetype.COMPUTE
490
+ elif "reducer" in class_name:
491
+ self.current_node_archetype = EnumNodeArchetype.REDUCER
492
+ elif "orchestrator" in class_name:
493
+ self.current_node_archetype = EnumNodeArchetype.ORCHESTRATOR
494
+
495
+ # Visit children
496
+ self.generic_visit(node)
497
+
498
+ # Restore context
499
+ self.current_node_archetype = old_archetype
500
+ self.current_class_name = old_class_name
501
+
502
+ return node
503
+
504
+ def visit_Call(self, node: ast.Call) -> ast.AST:
505
+ """Visit function calls to detect topic usage patterns.
506
+
507
+ Looks for patterns like:
508
+ - consumer.subscribe("topic_name")
509
+ - producer.send("topic_name", data)
510
+ - event_bus.publish("topic_name", message)
511
+
512
+ Args:
513
+ node: The AST Call node.
514
+
515
+ Returns:
516
+ The visited node.
517
+ """
518
+ # Check for subscribe/send/publish method calls
519
+ if isinstance(node.func, ast.Attribute):
520
+ method_name = node.func.attr
521
+ if method_name in ("subscribe", "send", "publish", "produce"):
522
+ self._check_topic_call(node, method_name)
523
+
524
+ self.generic_visit(node)
525
+ return node
526
+
527
+ def _check_topic_call(
528
+ self,
529
+ node: ast.Call,
530
+ method_name: str,
531
+ ) -> None:
532
+ """Check a topic-related method call for category mismatches.
533
+
534
+ Args:
535
+ node: The AST Call node.
536
+ method_name: The name of the method being called.
537
+ """
538
+ # Extract topic name from first argument (if string literal)
539
+ if not node.args:
540
+ return
541
+
542
+ first_arg = node.args[0]
543
+ topic_name: str | None = None
544
+
545
+ if isinstance(first_arg, ast.Constant) and isinstance(first_arg.value, str):
546
+ topic_name = first_arg.value
547
+ elif isinstance(first_arg, ast.JoinedStr):
548
+ # f-string handling - extract and validate static parts carefully.
549
+ #
550
+ # LIMITATION: f-strings with interpolated values (e.g., f"{domain}.events")
551
+ # only yield partial static content. We must avoid false positives/negatives
552
+ # from incomplete topic names like ".events" or "order." that could falsely
553
+ # match or miss patterns.
554
+ #
555
+ # Strategy:
556
+ # 1. Extract all static parts from the f-string
557
+ # 2. Check if result forms a COMPLETE valid topic pattern (domain.suffix)
558
+ # 3. If we only get a partial fragment (starts with "." or ends with "."),
559
+ # skip validation for this f-string - we can't reliably validate it
560
+ # 4. If no static parts exist, skip validation entirely
561
+ topic_name = self._extract_topic_from_fstring(first_arg)
562
+ elif isinstance(first_arg, ast.BinOp) and isinstance(first_arg.op, ast.Add):
563
+ # String concatenation handling (e.g., "order" + ".events")
564
+ #
565
+ # LIMITATION: String concatenation with variables cannot be fully resolved
566
+ # at static analysis time. We use the same conservative approach as f-strings.
567
+ topic_name = self._extract_topic_from_binop(first_arg)
568
+
569
+ if topic_name is None:
570
+ return
571
+
572
+ # Infer the category from the topic
573
+ inferred_category = self.validator._infer_category_from_topic(topic_name)
574
+
575
+ if inferred_category is None:
576
+ # Topic doesn't follow naming convention - add warning
577
+ # Use current_node_archetype if available (from class context), otherwise None
578
+ archetype_context = (
579
+ f" Node archetype: '{self.current_node_archetype.name}' "
580
+ f"(EnumNodeArchetype.{self.current_node_archetype.name})."
581
+ if self.current_node_archetype
582
+ else ""
583
+ )
584
+ self.violations.append(
585
+ ModelExecutionShapeViolationResult(
586
+ violation_type=EnumExecutionShapeViolation.TOPIC_CATEGORY_MISMATCH,
587
+ node_archetype=self.current_node_archetype, # May be None if outside handler class
588
+ file_path=str(self.file_path.absolute()),
589
+ line_number=node.lineno,
590
+ message=(
591
+ f"Topic naming convention violation at line {node.lineno}: "
592
+ f"Topic '{topic_name}' in {method_name}() call does not match ONEX "
593
+ f"naming conventions.{archetype_context} Expected topic patterns: "
594
+ f"'<domain>.events', '<domain>.commands', or '<domain>.intents'. "
595
+ f"Example: 'order.events', 'user.commands'."
596
+ ),
597
+ severity=EnumValidationSeverity.WARNING,
598
+ )
599
+ )
600
+ return
601
+
602
+ # If we have handler context, validate the subscription makes sense
603
+ if self.current_node_archetype is not None:
604
+ expected_categories = self.validator.archetype_categories.get(
605
+ self.current_node_archetype, []
606
+ )
607
+ if inferred_category not in expected_categories:
608
+ expected_names = [c.name for c in expected_categories]
609
+ self.violations.append(
610
+ ModelExecutionShapeViolationResult(
611
+ violation_type=EnumExecutionShapeViolation.TOPIC_CATEGORY_MISMATCH,
612
+ node_archetype=self.current_node_archetype,
613
+ file_path=str(self.file_path.absolute()),
614
+ line_number=node.lineno,
615
+ message=(
616
+ f"Topic category mismatch at line {node.lineno}: "
617
+ f"Handler '{self.current_class_name or 'unknown'}' with node archetype "
618
+ f"'{self.current_node_archetype.name}' (EnumNodeArchetype.{self.current_node_archetype.name}) "
619
+ f"uses topic '{topic_name}' in {method_name}() call. Topic implies "
620
+ f"'{inferred_category.name}' messages. Expected categories for this "
621
+ f"archetype: [{', '.join(expected_names)}]. Found: {inferred_category.name}."
622
+ ),
623
+ severity=EnumValidationSeverity.ERROR,
624
+ )
625
+ )
626
+
627
+ # Check for specific anti-patterns
628
+ self._check_send_patterns(node, method_name, topic_name, inferred_category)
629
+
630
+ def _check_send_patterns(
631
+ self,
632
+ node: ast.Call,
633
+ method_name: str,
634
+ topic_name: str,
635
+ topic_category: EnumMessageCategory,
636
+ ) -> None:
637
+ """Check for anti-patterns in send/publish calls.
638
+
639
+ Looks for patterns like sending events to command topics or
640
+ sending commands to event topics.
641
+
642
+ Args:
643
+ node: The AST Call node.
644
+ method_name: The name of the method being called.
645
+ topic_name: The topic name from the call.
646
+ topic_category: The inferred category from the topic name.
647
+ """
648
+ if method_name not in ("send", "publish", "produce"):
649
+ return
650
+
651
+ if len(node.args) < 2:
652
+ return
653
+
654
+ # Try to infer message type from the second argument (the message/data)
655
+ second_arg = node.args[1]
656
+ message_hint = self._infer_message_category_from_expr(second_arg)
657
+
658
+ if message_hint is not None and message_hint != topic_category:
659
+ # Use current_node_archetype if available (from class context), otherwise None
660
+ expected_topic_suffix = self.validator.suffixes.get(message_hint, "unknown")
661
+ archetype_context = (
662
+ f" Node archetype: '{self.current_node_archetype.name}' "
663
+ f"(EnumNodeArchetype.{self.current_node_archetype.name})."
664
+ if self.current_node_archetype
665
+ else ""
666
+ )
667
+ self.violations.append(
668
+ ModelExecutionShapeViolationResult(
669
+ violation_type=EnumExecutionShapeViolation.TOPIC_CATEGORY_MISMATCH,
670
+ node_archetype=self.current_node_archetype, # May be None if outside handler class
671
+ file_path=str(self.file_path.absolute()),
672
+ line_number=node.lineno,
673
+ message=(
674
+ f"Message-topic category mismatch at line {node.lineno}: Message appears "
675
+ f"to be '{message_hint.name}' type (EnumMessageCategory.{message_hint.name}) "
676
+ f"but is being sent to topic '{topic_name}' which expects "
677
+ f"'{topic_category.name}' messages.{archetype_context} "
678
+ f"Expected topic pattern for {message_hint.name}: '*.{expected_topic_suffix}'."
679
+ ),
680
+ severity=EnumValidationSeverity.ERROR,
681
+ )
682
+ )
683
+
684
+ def _infer_message_category_from_expr(
685
+ self,
686
+ node: ast.expr,
687
+ ) -> EnumMessageCategory | None:
688
+ """Attempt to infer message category from an expression.
689
+
690
+ Uses naming conventions to guess the message category. Patterns are
691
+ checked in order of specificity to minimize false positives:
692
+
693
+ 1. **Suffix patterns** (most reliable):
694
+ - ``*Event``, ``*Created``, ``*Updated``, ``*Deleted`` -> EVENT
695
+ - ``*Command`` -> COMMAND
696
+ - ``*Intent`` -> INTENT
697
+
698
+ 2. **Prefix patterns** (for CQRS-style naming):
699
+ - ``Create*``, ``Update*``, ``Delete*``, ``Execute*`` -> COMMAND
700
+
701
+ Known Limitations:
702
+ - **False positives**: Names like ``EventEmitter`` or ``CommandLine``
703
+ may be incorrectly classified as message types.
704
+ - **Order dependence**: A name ending in both ``Created`` and
705
+ containing ``Command`` (e.g., ``CommandCreated``) will be
706
+ classified as EVENT (suffix match first).
707
+ - Substring matching on prefixes is less reliable than suffix matching.
708
+
709
+ Args:
710
+ node: The AST expression node.
711
+
712
+ Returns:
713
+ The inferred message category, or None if unable to determine.
714
+ """
715
+ name: str | None = None
716
+
717
+ if isinstance(node, ast.Name):
718
+ name = node.id
719
+ elif isinstance(node, ast.Call):
720
+ if isinstance(node.func, ast.Name):
721
+ name = node.func.id
722
+ elif isinstance(node.func, ast.Attribute):
723
+ name = node.func.attr
724
+
725
+ if name is None:
726
+ return None
727
+
728
+ # ==================================================================
729
+ # Pattern Matching Order: By Specificity (most specific first)
730
+ # ==================================================================
731
+ #
732
+ # Phase 1: Check suffix patterns (most reliable, fewest false positives)
733
+ # Suffix matching is preferred because message types conventionally
734
+ # END with their category: OrderCreatedEvent, CreateOrderCommand
735
+ #
736
+ # IMPORTANT: Suffix order matters! Longer/more-specific suffixes are
737
+ # checked first to avoid partial matches. For example:
738
+ # - "OrderCreatedEvent" should match "Event" suffix (not just "Created")
739
+ # - The suffix list is ordered by semantic specificity, not length
740
+ event_suffixes = ("Event", "Created", "Updated", "Deleted", "Occurred")
741
+ for suffix in event_suffixes:
742
+ if name.endswith(suffix):
743
+ return EnumMessageCategory.EVENT
744
+
745
+ if name.endswith("Command"):
746
+ return EnumMessageCategory.COMMAND
747
+
748
+ if name.endswith("Intent"):
749
+ return EnumMessageCategory.INTENT
750
+
751
+ # Phase 2: Check prefix patterns for CQRS-style command naming
752
+ # Commands often start with verbs: CreateOrder, UpdateUser, DeleteItem
753
+ #
754
+ # NOTE: Prefix matching is LESS reliable than suffix matching because
755
+ # many non-message types start with verbs (e.g., CreateUserService,
756
+ # UpdateHandler, DeleteButton). This phase runs after suffix matching
757
+ # to ensure names like "CreateOrderCommand" match as COMMAND via suffix.
758
+ command_prefixes = ("Create", "Update", "Delete", "Execute", "Do")
759
+ for prefix in command_prefixes:
760
+ if name.startswith(prefix):
761
+ return EnumMessageCategory.COMMAND
762
+
763
+ # Phase 3: Check for Model* prefix patterns (ONEX naming convention)
764
+ # ONEX models use "Model" prefix: ModelEvent, ModelCommand, etc.
765
+ #
766
+ # This phase is LAST because it's ONEX-specific and the generic suffix
767
+ # patterns in Phase 1 would already catch most cases (e.g., ModelOrderEvent
768
+ # ends with "Event" and would be caught in Phase 1).
769
+ if name.startswith("ModelEvent"):
770
+ return EnumMessageCategory.EVENT
771
+ if name.startswith("ModelCommand"):
772
+ return EnumMessageCategory.COMMAND
773
+ if name.startswith("ModelIntent"):
774
+ return EnumMessageCategory.INTENT
775
+
776
+ return None
777
+
778
+ def _extract_topic_from_fstring(
779
+ self,
780
+ node: ast.JoinedStr,
781
+ ) -> str | None:
782
+ """Safely extract a topic name from an f-string AST node.
783
+
784
+ f-strings with interpolated values (e.g., f"{domain}.events") only yield
785
+ partial static content when analyzed statically. This method extracts
786
+ the static parts and validates that the result forms a complete, valid
787
+ topic pattern before returning it for validation.
788
+
789
+ IMPORTANT - INCOMPLETE TOPIC NAME HANDLING:
790
+ This method intentionally skips validation for f-strings that produce
791
+ incomplete topic fragments. The result may be an incomplete topic name
792
+ for f-strings with interpolated variables. For example:
793
+
794
+ - f"{domain}.events" yields only ".events" (domain is unknown)
795
+ - f"order.{suffix}" yields only "order." (suffix is unknown)
796
+ - f"{get_prefix()}.{get_suffix()}" yields "" (all dynamic)
797
+
798
+ These incomplete fragments are NOT returned for validation because:
799
+ - False positives: ".events" could falsely match as a valid topic
800
+ - False negatives: "order." would be flagged as invalid when the full
801
+ topic might be "order.events"
802
+
803
+ This is a deliberate design decision to prefer missing violations over
804
+ incorrect violations. Runtime validation should catch what static
805
+ analysis cannot.
806
+
807
+ Args:
808
+ node: The AST JoinedStr node representing an f-string.
809
+
810
+ Returns:
811
+ The extracted topic name if it forms a complete valid pattern,
812
+ or None if the f-string cannot be reliably validated (including
813
+ when only incomplete fragments are available).
814
+
815
+ Examples:
816
+ - f"order.events" -> "order.events" (fully static, valid)
817
+ - f"{domain}.events" -> None (partial: ".events" is incomplete)
818
+ - f"order.{suffix}" -> None (partial: "order." is incomplete)
819
+ - f"{prefix}.{suffix}" -> None (no static parts)
820
+ - f"{get_topic()}" -> None (no static parts)
821
+ """
822
+ # Extract all static string parts from the f-string
823
+ static_parts: list[str] = []
824
+ has_interpolation = False
825
+
826
+ for value in node.values:
827
+ if isinstance(value, ast.Constant) and isinstance(value.value, str):
828
+ static_parts.append(value.value)
829
+ else:
830
+ # This is a FormattedValue (interpolated expression)
831
+ has_interpolation = True
832
+
833
+ # If no static parts, we can't validate anything
834
+ if not static_parts:
835
+ return None
836
+
837
+ # Join the static parts to see what we have
838
+ joined = "".join(static_parts)
839
+
840
+ # If there are interpolations, check if the result is a valid partial
841
+ if has_interpolation:
842
+ # Skip validation for incomplete fragments that could cause
843
+ # false positives or negatives:
844
+ # - Starts with "." (e.g., ".events" from f"{domain}.events")
845
+ # - Ends with "." (e.g., "order." from f"order.{suffix}")
846
+ # - Contains only a suffix without domain (e.g., ".events", ".commands")
847
+ if joined.startswith(".") or joined.endswith("."):
848
+ return None
849
+
850
+ # Check if the joined result matches a complete topic pattern.
851
+ # Only validate if we have what looks like a complete topic name.
852
+ # A complete topic should match: domain.suffix (e.g., "order.events")
853
+ for pattern in self.validator.patterns.values():
854
+ if pattern.match(joined):
855
+ # This partial f-string happens to form a valid complete topic
856
+ # This is rare but possible (e.g., f"{'order'}.events" with
857
+ # a constant expression that evaluates to a string literal)
858
+ return joined
859
+
860
+ # The static parts don't form a valid complete topic pattern.
861
+ # Skip validation to avoid false positives/negatives.
862
+ return None
863
+
864
+ # No interpolations - this is a fully static f-string (unusual but valid)
865
+ # For example: f"order.events" (no interpolated values)
866
+ return joined if joined else None
867
+
868
+ def _extract_topic_from_binop(
869
+ self,
870
+ node: ast.BinOp,
871
+ ) -> str | None:
872
+ """Safely extract a topic name from a string concatenation BinOp node.
873
+
874
+ String concatenation with variables (e.g., prefix + ".events") cannot be
875
+ fully resolved at static analysis time. This method extracts the static
876
+ string parts and applies the same conservative validation as f-strings.
877
+
878
+ Args:
879
+ node: The AST BinOp node representing string concatenation.
880
+
881
+ Returns:
882
+ The extracted topic name if it forms a complete valid pattern,
883
+ or None if the concatenation cannot be reliably validated.
884
+
885
+ Examples:
886
+ - "order" + ".events" -> "order.events" (fully static, valid)
887
+ - prefix + ".events" -> None (partial: ".events" is incomplete)
888
+ - "order." + suffix -> None (partial: "order." is incomplete)
889
+ - prefix + suffix -> None (no static parts that form valid pattern)
890
+ """
891
+ # Recursively extract static string parts from the binary operation
892
+ static_parts: list[str] = []
893
+ has_variable = False
894
+
895
+ def collect_static_parts(n: ast.expr) -> None:
896
+ nonlocal has_variable
897
+ if isinstance(n, ast.Constant) and isinstance(n.value, str):
898
+ static_parts.append(n.value)
899
+ elif isinstance(n, ast.BinOp) and isinstance(n.op, ast.Add):
900
+ # Recursively handle nested concatenations
901
+ collect_static_parts(n.left)
902
+ collect_static_parts(n.right)
903
+ else:
904
+ # This is a variable or other non-constant expression
905
+ has_variable = True
906
+
907
+ collect_static_parts(node)
908
+
909
+ # If no static parts, we can't validate anything
910
+ if not static_parts:
911
+ return None
912
+
913
+ # Join the static parts to see what we have
914
+ joined = "".join(static_parts)
915
+
916
+ # If there are variables, apply the same conservative approach as f-strings
917
+ if has_variable:
918
+ # Skip validation for incomplete fragments
919
+ if joined.startswith(".") or joined.endswith("."):
920
+ return None
921
+
922
+ # Check if the joined result matches a complete topic pattern
923
+ for pattern in self.validator.patterns.values():
924
+ if pattern.match(joined):
925
+ return joined
926
+
927
+ # Skip validation to avoid false positives/negatives
928
+ return None
929
+
930
+ # No variables - this is fully static concatenation
931
+ # (e.g., "order" + ".events")
932
+ return joined if joined else None
933
+
934
+
935
+ def validate_topic_categories_in_file(
936
+ file_path: Path,
937
+ ) -> list[ModelExecutionShapeViolationResult]:
938
+ """Analyze a Python file for topic/category mismatches using AST.
939
+
940
+ Statically analyzes the file to detect:
941
+ - Topics that don't follow ONEX naming conventions
942
+ - Handlers subscribing to inappropriate topic categories
943
+ - Messages being sent to wrong topic types
944
+
945
+ This function is designed for CI integration to catch topic
946
+ mismatches before runtime.
947
+
948
+ Uses a cached singleton TopicCategoryValidator for performance in hot paths.
949
+ The TopicCategoryASTVisitor is still created per-file since it stores
950
+ file-specific state (violations, current handler context).
951
+
952
+ Args:
953
+ file_path: Path to the Python file to analyze.
954
+
955
+ Returns:
956
+ List of violations found in the file.
957
+
958
+ Example:
959
+ >>> from pathlib import Path
960
+ >>> violations = validate_topic_categories_in_file(
961
+ ... Path("src/handlers/order_handler.py")
962
+ ... )
963
+ >>> for v in violations:
964
+ ... print(v.format_for_ci())
965
+ """
966
+ if not file_path.exists():
967
+ # File not existing is not a topic/category violation - it's a file system issue.
968
+ # Log a warning and return empty list since this function analyzes code content,
969
+ # not file existence. Callers should validate file existence if needed.
970
+ logger.warning(
971
+ "Cannot validate topic categories: file not found: %s", file_path
972
+ )
973
+ return []
974
+
975
+ if file_path.suffix != ".py":
976
+ return [] # Skip non-Python files
977
+
978
+ try:
979
+ source = file_path.read_text(encoding="utf-8")
980
+ tree = ast.parse(source, filename=str(file_path))
981
+ except SyntaxError as e:
982
+ # Syntax error is a file-level issue, not a handler-specific violation.
983
+ # Use SYNTAX_ERROR violation type for AST parse failures.
984
+ # node_archetype is None because we can't analyze the code structure.
985
+ return [
986
+ ModelExecutionShapeViolationResult(
987
+ violation_type=EnumExecutionShapeViolation.SYNTAX_ERROR,
988
+ node_archetype=None, # Cannot determine archetype from unparseable file
989
+ file_path=str(file_path.absolute()),
990
+ line_number=e.lineno or 1,
991
+ message=(
992
+ f"Validation error: Cannot parse Python source file for topic category "
993
+ f"validation. Syntax error at line {e.lineno or 1}: {e.msg}. "
994
+ f"File: {file_path.name}. Fix the syntax error to enable topic "
995
+ f"category validation."
996
+ ),
997
+ severity=EnumValidationSeverity.ERROR,
998
+ )
999
+ ]
1000
+
1001
+ # Use cached singleton validator for performance
1002
+ # TopicCategoryASTVisitor needs fresh instance per-file (stores file state)
1003
+ visitor = TopicCategoryASTVisitor(file_path, _default_validator)
1004
+ visitor.visit(tree)
1005
+
1006
+ return visitor.violations
1007
+
1008
+
1009
+ def validate_message_on_topic(
1010
+ message: object,
1011
+ topic: str,
1012
+ message_category: MessageOutputCategory,
1013
+ ) -> ModelExecutionShapeViolationResult | None:
1014
+ """Runtime validation that message category matches topic.
1015
+
1016
+ Validates at runtime that a message's category is appropriate
1017
+ for the topic it's being published to or consumed from.
1018
+
1019
+ This function should be called at message processing boundaries
1020
+ to ensure architectural consistency.
1021
+
1022
+ Uses a cached singleton TopicCategoryValidator for performance in hot paths.
1023
+
1024
+ Args:
1025
+ message: The message object (used for context in error messages).
1026
+ topic: The Kafka topic name.
1027
+ message_category: The declared category of the message or node output type.
1028
+ Projections (EnumNodeOutputType.PROJECTION) have no topic constraint.
1029
+
1030
+ Returns:
1031
+ A ModelExecutionShapeViolationResult if there's a mismatch,
1032
+ or None if valid.
1033
+
1034
+ Example:
1035
+ >>> from omnibase_infra.validation import validate_message_on_topic
1036
+ >>> from omnibase_infra.enums import EnumMessageCategory
1037
+ >>>
1038
+ >>> result = validate_message_on_topic(
1039
+ ... message=OrderCreatedEvent(...),
1040
+ ... topic="order.events",
1041
+ ... message_category=EnumMessageCategory.EVENT,
1042
+ ... )
1043
+ >>> assert result is None # Valid
1044
+ >>>
1045
+ >>> result = validate_message_on_topic(
1046
+ ... message=OrderCreatedEvent(...),
1047
+ ... topic="order.commands", # Wrong!
1048
+ ... message_category=EnumMessageCategory.EVENT,
1049
+ ... )
1050
+ >>> assert result is not None # Violation
1051
+ """
1052
+ # Use cached singleton validator for performance in hot paths
1053
+ result = _default_validator.validate_message_topic(message_category, topic)
1054
+
1055
+ if result is not None:
1056
+ # Enhance the message with message type info if available
1057
+ message_type_name = type(message).__name__
1058
+ category_name = (
1059
+ message_category.name
1060
+ if hasattr(message_category, "name")
1061
+ else str(message_category)
1062
+ )
1063
+ enhanced_message = (
1064
+ f"Runtime topic validation failure: Message type '{message_type_name}' with "
1065
+ f"category '{category_name}' (EnumMessageCategory.{category_name}) is on topic "
1066
+ f"'{topic}'. {result.message}"
1067
+ )
1068
+ return ModelExecutionShapeViolationResult(
1069
+ violation_type=result.violation_type,
1070
+ node_archetype=result.node_archetype,
1071
+ file_path=result.file_path,
1072
+ line_number=result.line_number,
1073
+ message=enhanced_message,
1074
+ severity=result.severity,
1075
+ )
1076
+
1077
+ return None
1078
+
1079
+
1080
+ def validate_topic_categories_in_directory(
1081
+ directory: Path,
1082
+ recursive: bool = True,
1083
+ ) -> list[ModelExecutionShapeViolationResult]:
1084
+ """Validate all Python files in a directory for topic/category mismatches.
1085
+
1086
+ Convenience function for CI integration that scans a directory
1087
+ and validates all Python files.
1088
+
1089
+ Args:
1090
+ directory: Path to the directory to scan.
1091
+ recursive: Whether to scan subdirectories. Defaults to True.
1092
+
1093
+ Returns:
1094
+ List of all violations found across all files.
1095
+
1096
+ Example:
1097
+ >>> from pathlib import Path
1098
+ >>> violations = validate_topic_categories_in_directory(
1099
+ ... Path("src/handlers/")
1100
+ ... )
1101
+ >>> # CI gate: fail if any blocking violations
1102
+ >>> blocking = [v for v in violations if v.is_blocking()]
1103
+ >>> if blocking:
1104
+ ... print(f"Found {len(blocking)} blocking violations")
1105
+ ... exit(1)
1106
+ """
1107
+ violations: list[ModelExecutionShapeViolationResult] = []
1108
+
1109
+ if not directory.exists():
1110
+ return violations
1111
+
1112
+ pattern = "**/*.py" if recursive else "*.py"
1113
+ for py_file in directory.glob(pattern):
1114
+ if py_file.is_file():
1115
+ violations.extend(validate_topic_categories_in_file(py_file))
1116
+
1117
+ return violations
1118
+
1119
+
1120
+ # ==============================================================================
1121
+ # Module-Level Singleton Validator
1122
+ # ==============================================================================
1123
+ #
1124
+ # Performance Optimization: The TopicCategoryValidator is stateless after
1125
+ # initialization (only stores patterns, suffixes, and archetype_categories which
1126
+ # are all module-level constants). Creating new instances on every validation
1127
+ # call is wasteful in hot paths. Instead, we use a module-level singleton.
1128
+ #
1129
+ # Why a singleton is safe here:
1130
+ # - The validator only stores references to module-level immutable constants
1131
+ # - No per-validation state is stored in the validator instance
1132
+ # - All mutable state is in TopicCategoryASTVisitor (created per-file)
1133
+ #
1134
+ # Note: TopicCategoryASTVisitor still requires per-file instantiation because
1135
+ # it stores file-specific state (violations list, current_node_archetype, etc.).
1136
+
1137
+ _default_validator = TopicCategoryValidator()
1138
+
1139
+
1140
+ __all__ = [
1141
+ "NODE_ARCHETYPE_EXPECTED_CATEGORIES",
1142
+ # Constants
1143
+ "TOPIC_CATEGORY_PATTERNS",
1144
+ "TOPIC_SUFFIXES",
1145
+ "TopicCategoryASTVisitor",
1146
+ # Classes
1147
+ "TopicCategoryValidator",
1148
+ "validate_message_on_topic",
1149
+ "validate_topic_categories_in_directory",
1150
+ # Functions
1151
+ "validate_topic_categories_in_file",
1152
+ ]