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,1330 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Generic Contract-Driven Projector Shell.
4
+
5
+ Implements ProtocolEventProjector for contract-based event-to-state projection.
6
+ All behavior is driven by ModelProjectorContract - NO domain-specific logic.
7
+
8
+ Features:
9
+ - Event type matching via envelope metadata, payload attribute, or classname
10
+ - Dynamic column value extraction from nested event payloads
11
+ - Three projection modes: upsert, insert_only, append
12
+ - Parameterized SQL queries for injection protection
13
+ - Bulk state queries for N+1 optimization
14
+ - Configurable query timeouts
15
+ - Optional state transition notification publishing
16
+
17
+ Notification Publishing:
18
+ When configured with a notification_publisher and notification_config,
19
+ ProjectorShell will automatically publish state transition notifications
20
+ after successful projection commits. This enables the Observer pattern
21
+ for orchestrator coordination without tight coupling to reducers.
22
+
23
+ Example:
24
+ >>> from omnibase_infra.runtime import ProjectorShell, TransitionNotificationPublisher
25
+ >>> from omnibase_infra.runtime.models import ModelProjectorNotificationConfig
26
+ >>>
27
+ >>> publisher = TransitionNotificationPublisher(event_bus, topic="transitions.v1")
28
+ >>> config = ModelProjectorNotificationConfig(
29
+ ... topic="transitions.v1",
30
+ ... state_column="current_state",
31
+ ... aggregate_id_column="entity_id",
32
+ ... version_column="version",
33
+ ... )
34
+ >>> projector = ProjectorShell(
35
+ ... contract=contract,
36
+ ... pool=pool,
37
+ ... notification_publisher=publisher,
38
+ ... notification_config=config,
39
+ ... )
40
+
41
+ See Also:
42
+ - ProtocolEventProjector: Protocol definition from omnibase_infra.protocols
43
+ - ModelProjectorContract: Contract model from omnibase_core
44
+ - ProjectorPluginLoader: Loader that instantiates ProjectorShell
45
+ - TransitionNotificationPublisher: Publisher for state transition notifications
46
+
47
+ Related Tickets:
48
+ - OMN-1169: ProjectorShell contract-driven projections (implemented)
49
+ - OMN-1139: TransitionNotificationPublisher integration (implemented)
50
+
51
+ .. versionadded:: 0.7.0
52
+ Created as part of OMN-1169 projector shell implementation.
53
+
54
+ .. versionchanged:: 0.8.0
55
+ Added notification publishing support as part of OMN-1139.
56
+ """
57
+
58
+ from __future__ import annotations
59
+
60
+ import logging
61
+ from typing import TYPE_CHECKING
62
+ from uuid import UUID
63
+
64
+ import asyncpg
65
+ from pydantic import BaseModel
66
+
67
+ from omnibase_core.models.events.model_event_envelope import ModelEventEnvelope
68
+ from omnibase_core.models.projectors import (
69
+ ModelProjectionResult,
70
+ ModelProjectorContract,
71
+ )
72
+ from omnibase_core.protocols.notifications import (
73
+ ProtocolTransitionNotificationPublisher,
74
+ )
75
+ from omnibase_infra.enums import EnumInfraTransportType
76
+ from omnibase_infra.errors import (
77
+ InfraConnectionError,
78
+ InfraTimeoutError,
79
+ ModelInfraErrorContext,
80
+ ModelTimeoutErrorContext,
81
+ ProtocolConfigurationError,
82
+ RuntimeHostError,
83
+ )
84
+ from omnibase_infra.models.projectors.util_sql_identifiers import quote_identifier
85
+ from omnibase_infra.runtime.mixins import MixinProjectorSqlOperations
86
+ from omnibase_infra.runtime.mixins.mixin_projector_notification_publishing import (
87
+ MixinProjectorNotificationPublishing,
88
+ )
89
+ from omnibase_infra.runtime.models.model_projector_notification_config import (
90
+ ModelProjectorNotificationConfig,
91
+ )
92
+
93
+ logger = logging.getLogger(__name__)
94
+
95
+
96
+ class ProjectorShell(MixinProjectorNotificationPublishing, MixinProjectorSqlOperations):
97
+ """Generic contract-driven projector implementation.
98
+
99
+ Transforms events into persistent state projections based on a
100
+ ModelProjectorContract definition. All behavior is declarative -
101
+ no domain-specific logic in this class.
102
+
103
+ The projector supports three projection modes:
104
+ - upsert: INSERT or UPDATE based on upsert_key (default)
105
+ - insert_only: INSERT only, fail on conflict
106
+ - append: Always INSERT, event-log style
107
+
108
+ Notification Publishing:
109
+ When configured with a notification_publisher and notification_config,
110
+ the projector will automatically publish state transition notifications
111
+ after successful projection commits. The notification includes:
112
+
113
+ - aggregate_type: From the projector contract
114
+ - aggregate_id: Extracted from projection values
115
+ - from_state: Previous state (fetched before projection)
116
+ - to_state: New state (extracted from projection values)
117
+ - projection_version: Version from projection values (if configured)
118
+ - correlation_id: Propagated from the incoming event
119
+ - causation_id: The envelope_id of the triggering event
120
+
121
+ Notifications are best-effort - publishing failures are logged but
122
+ do not cause the projection to fail.
123
+
124
+ UniqueViolationError Handling:
125
+ The projector handles ``asyncpg.UniqueViolationError`` differently based
126
+ on the projection mode. Understanding these semantics is critical for
127
+ correct schema design and error handling.
128
+
129
+ **insert_only mode**:
130
+ A unique violation indicates duplicate event processing (idempotency).
131
+ This is expected behavior when replaying events or when at-least-once
132
+ delivery causes duplicates. Returns ``ModelProjectionResult(success=False)``
133
+ with an error message. The caller can decide whether to log, retry,
134
+ or ignore based on their requirements.
135
+
136
+ **upsert mode**:
137
+ Unique violations should NEVER occur because the generated SQL uses
138
+ ``ON CONFLICT ... DO UPDATE``. If a violation is raised, it indicates
139
+ a schema mismatch (e.g., the ``upsert_key`` in the contract doesn't
140
+ match the actual unique constraint). Raises ``RuntimeHostError`` to
141
+ signal a configuration error that needs investigation.
142
+
143
+ **append mode**:
144
+ This mode assumes **event-driven primary keys** where each row is
145
+ uniquely identified by event-specific data (e.g., ``envelope_id``,
146
+ ``event_sequence``, or composite keys including timestamp). With this
147
+ assumption, a unique violation indicates duplicate event processing -
148
+ the same event was projected twice.
149
+
150
+ **IMPORTANT ASSUMPTION**: Append mode primary key design must ensure
151
+ that each event produces a unique key. Common patterns:
152
+ - ``envelope_id`` (UUID from event envelope)
153
+ - ``(aggregate_id, event_sequence)`` composite key
154
+ - ``(aggregate_id, event_type, timestamp)`` composite key
155
+
156
+ If your schema uses **non-event primary keys** (e.g., auto-increment,
157
+ domain-specific business keys), a ``UniqueViolationError`` in append
158
+ mode may indicate a **legitimate conflict** rather than a duplicate
159
+ event. In such cases, you should either:
160
+ 1. Switch to ``upsert`` mode with appropriate conflict resolution
161
+ 2. Redesign the primary key to be event-driven
162
+ 3. Implement custom projection logic outside ProjectorShell
163
+
164
+ When a violation occurs in append mode, ``RuntimeHostError`` is raised
165
+ to fail fast and signal the need for investigation.
166
+
167
+ Composite Primary Key Handling:
168
+ The contract model (``ModelProjectorContract``) only supports single-column
169
+ ``primary_key`` and ``upsert_key`` values (type: ``str``). For schemas with
170
+ composite primary keys, there are two approaches:
171
+
172
+ **Approach 1: Add UNIQUE constraint (Recommended)**
173
+ Add a UNIQUE constraint on the single column specified in the contract.
174
+ This allows ``ON CONFLICT (column)`` to work even with composite PKs.
175
+
176
+ Example SQL schema::
177
+
178
+ PRIMARY KEY (entity_id, domain),
179
+ UNIQUE (entity_id) -- Enables ON CONFLICT (entity_id)
180
+
181
+ **Approach 2: Use upsert_partial() with explicit conflict_columns**
182
+ For operations requiring composite key semantics, use ``upsert_partial()``
183
+ with the ``conflict_columns`` parameter instead of ``project()``.
184
+
185
+ Example::
186
+
187
+ await projector.upsert_partial(
188
+ aggregate_id=entity_id,
189
+ values={"entity_id": entity_id, "domain": domain, ...},
190
+ correlation_id=correlation_id,
191
+ conflict_columns=["entity_id", "domain"], # Composite key
192
+ )
193
+
194
+ **get_state() / get_states() behavior with composite keys**:
195
+ These methods use only the first column of the primary key for lookups.
196
+ For schemas where the single-column lookup is not unique, the results
197
+ may be ambiguous. Consider querying the database directly in such cases.
198
+
199
+ Thread Safety:
200
+ This implementation is coroutine-safe for concurrent async calls.
201
+ Uses asyncpg connection pool for connection management.
202
+
203
+ Security:
204
+ All queries use parameterized statements for SQL injection protection.
205
+ Table and column names are validated by the contract model validators
206
+ and quoted using ``quote_identifier()`` for safe SQL generation.
207
+
208
+ Query Timeout:
209
+ Configurable via ``query_timeout_seconds`` parameter. Defaults to 30
210
+ seconds. Set to None to disable timeout (not recommended for production).
211
+
212
+ Default Values:
213
+ Column defaults specified in the contract (``column.default``) are treated
214
+ as **runtime literal values**, not SQL expressions. They are inserted as
215
+ parameter values, not embedded in SQL. For database-level defaults (e.g.,
216
+ ``CURRENT_TIMESTAMP``), use PostgreSQL column defaults instead.
217
+
218
+ Example:
219
+ >>> from omnibase_core.models.projectors import ModelProjectorContract
220
+ >>> contract = ModelProjectorContract.model_validate(yaml_data)
221
+ >>> pool = await asyncpg.create_pool(dsn)
222
+ >>> projector = ProjectorShell(contract, pool, query_timeout_seconds=10.0)
223
+ >>> result = await projector.project(event_envelope, correlation_id)
224
+ >>> if result.success:
225
+ ... print(f"Projected {result.rows_affected} rows")
226
+
227
+ Related:
228
+ - OMN-1169: ProjectorShell contract-driven projections (implemented)
229
+ - OMN-1168: ProjectorPluginLoader contract discovery
230
+ """
231
+
232
+ # Default query timeout in seconds (30s is reasonable for projections)
233
+ DEFAULT_QUERY_TIMEOUT_SECONDS: float = 30.0
234
+
235
+ def __init__(
236
+ self,
237
+ contract: ModelProjectorContract,
238
+ pool: asyncpg.Pool,
239
+ query_timeout_seconds: float | None = None,
240
+ notification_publisher: ProtocolTransitionNotificationPublisher | None = None,
241
+ notification_config: ModelProjectorNotificationConfig | None = None,
242
+ ) -> None:
243
+ """Initialize projector shell with contract and database pool.
244
+
245
+ Args:
246
+ contract: The projector contract defining projection behavior.
247
+ All projection rules (table, columns, modes) come from this.
248
+ pool: asyncpg connection pool for database access.
249
+ Pool should be created by the caller (e.g., from container).
250
+ query_timeout_seconds: Timeout for individual database queries in
251
+ seconds. Defaults to 30.0 seconds. Set to None to disable
252
+ timeout (not recommended for production).
253
+ notification_publisher: Optional publisher for state transition
254
+ notifications. If provided along with notification_config,
255
+ notifications will be published after successful projections.
256
+ notification_config: Optional configuration for notification
257
+ publishing. Specifies which columns contain state, aggregate ID,
258
+ and version information for notification creation.
259
+
260
+ Note:
261
+ Both notification_publisher and notification_config must be provided
262
+ for notification publishing to be enabled. If only one is provided,
263
+ notifications will not be published (silent no-op).
264
+
265
+ **Topic Consistency**: When both notification_publisher and
266
+ notification_config are provided, be aware that:
267
+
268
+ - The ``notification_config.expected_topic`` field is for
269
+ **documentation and validation purposes only**
270
+ - The **actual topic** used for publishing is determined solely by
271
+ the ``notification_publisher``'s internal configuration (the topic
272
+ passed to ``TransitionNotificationPublisher.__init__``)
273
+ - A **warning is logged** if these values differ to catch configuration
274
+ errors early
275
+ - **Users should ensure these match** when configuring both components
276
+
277
+ Example of consistent configuration::
278
+
279
+ # Publisher determines actual destination
280
+ publisher = TransitionNotificationPublisher(event_bus, "transitions.v1")
281
+
282
+ # Config expected_topic should match for consistency validation
283
+ config = ModelProjectorNotificationConfig(
284
+ expected_topic="transitions.v1", # Match publisher's topic
285
+ state_column="current_state",
286
+ ...
287
+ )
288
+
289
+ Example:
290
+ >>> from omnibase_infra.runtime import TransitionNotificationPublisher
291
+ >>> from omnibase_infra.runtime.models import ModelProjectorNotificationConfig
292
+ >>>
293
+ >>> publisher = TransitionNotificationPublisher(event_bus, "transitions.v1")
294
+ >>> config = ModelProjectorNotificationConfig(
295
+ ... expected_topic="transitions.v1",
296
+ ... state_column="current_state",
297
+ ... aggregate_id_column="entity_id",
298
+ ... version_column="version",
299
+ ... )
300
+ >>> projector = ProjectorShell(
301
+ ... contract=contract,
302
+ ... pool=pool,
303
+ ... notification_publisher=publisher,
304
+ ... notification_config=config,
305
+ ... )
306
+ """
307
+ self._contract = contract
308
+ self._pool = pool
309
+ self._query_timeout = (
310
+ query_timeout_seconds
311
+ if query_timeout_seconds is not None
312
+ else self.DEFAULT_QUERY_TIMEOUT_SECONDS
313
+ )
314
+ self._notification_publisher = notification_publisher
315
+ self._notification_config = notification_config
316
+
317
+ # Validate notification config against contract schema if provided
318
+ if notification_config is not None:
319
+ self._validate_notification_config(notification_config)
320
+
321
+ # Warn if notification config expected_topic differs from publisher topic
322
+ # The config.expected_topic is for validation only; publisher determines destination
323
+ if (
324
+ notification_config is not None
325
+ and notification_publisher is not None
326
+ and hasattr(notification_publisher, "topic")
327
+ and notification_config.expected_topic != notification_publisher.topic
328
+ ):
329
+ logger.warning(
330
+ "Notification config expected_topic differs from publisher topic - "
331
+ "expected_topic is for validation only, publisher determines actual destination",
332
+ extra={
333
+ "projector_id": contract.projector_id,
334
+ "config_expected_topic": notification_config.expected_topic,
335
+ "publisher_topic": notification_publisher.topic,
336
+ },
337
+ )
338
+
339
+ logger.debug(
340
+ "ProjectorShell initialized for projector '%s'",
341
+ contract.projector_id,
342
+ extra={
343
+ "projector_id": contract.projector_id,
344
+ "aggregate_type": contract.aggregate_type,
345
+ "consumed_events": contract.consumed_events,
346
+ "mode": contract.behavior.mode,
347
+ "query_timeout_seconds": self._query_timeout,
348
+ "notifications_enabled": self._is_notification_enabled(),
349
+ },
350
+ )
351
+
352
+ def _validate_notification_config(
353
+ self,
354
+ config: ModelProjectorNotificationConfig,
355
+ ) -> None:
356
+ """Validate notification config against the contract schema.
357
+
358
+ Ensures that the configured column names exist in the projection schema.
359
+
360
+ Args:
361
+ config: The notification configuration to validate.
362
+
363
+ Raises:
364
+ ProtocolConfigurationError: If any configured column does not exist
365
+ in the projection schema.
366
+ """
367
+ schema = self._contract.projection_schema
368
+ column_names = {col.name for col in schema.columns}
369
+
370
+ # Check state_column
371
+ if config.state_column not in column_names:
372
+ context = ModelInfraErrorContext(
373
+ transport_type=EnumInfraTransportType.RUNTIME,
374
+ operation="validate_notification_config",
375
+ target_name=f"projector.{self._contract.projector_id}",
376
+ )
377
+ raise ProtocolConfigurationError(
378
+ f"notification_config.state_column '{config.state_column}' "
379
+ f"not found in projection schema. "
380
+ f"Available columns: {sorted(column_names)}",
381
+ context=context,
382
+ )
383
+
384
+ # Check aggregate_id_column
385
+ if config.aggregate_id_column not in column_names:
386
+ context = ModelInfraErrorContext(
387
+ transport_type=EnumInfraTransportType.RUNTIME,
388
+ operation="validate_notification_config",
389
+ target_name=f"projector.{self._contract.projector_id}",
390
+ )
391
+ raise ProtocolConfigurationError(
392
+ f"notification_config.aggregate_id_column '{config.aggregate_id_column}' "
393
+ f"not found in projection schema. "
394
+ f"Available columns: {sorted(column_names)}",
395
+ context=context,
396
+ )
397
+
398
+ # Check version_column if specified
399
+ if (
400
+ config.version_column is not None
401
+ and config.version_column not in column_names
402
+ ):
403
+ context = ModelInfraErrorContext(
404
+ transport_type=EnumInfraTransportType.RUNTIME,
405
+ operation="validate_notification_config",
406
+ target_name=f"projector.{self._contract.projector_id}",
407
+ )
408
+ raise ProtocolConfigurationError(
409
+ f"notification_config.version_column '{config.version_column}' "
410
+ f"not found in projection schema. "
411
+ f"Available columns: {sorted(column_names)}",
412
+ context=context,
413
+ )
414
+
415
+ @property
416
+ def projector_id(self) -> str:
417
+ """Unique identifier from contract."""
418
+ return str(self._contract.projector_id)
419
+
420
+ @property
421
+ def aggregate_type(self) -> str:
422
+ """Aggregate type from contract."""
423
+ return str(self._contract.aggregate_type)
424
+
425
+ @property
426
+ def consumed_events(self) -> list[str]:
427
+ """Event types from contract."""
428
+ return list(self._contract.consumed_events)
429
+
430
+ @property
431
+ def contract(self) -> ModelProjectorContract:
432
+ """Access the underlying contract."""
433
+ return self._contract
434
+
435
+ @property
436
+ def is_placeholder(self) -> bool:
437
+ """Whether this is a placeholder implementation.
438
+
439
+ Returns:
440
+ False, as this is a full implementation.
441
+ """
442
+ return False
443
+
444
+ async def project(
445
+ self,
446
+ event: ModelEventEnvelope[object],
447
+ correlation_id: UUID,
448
+ ) -> ModelProjectionResult:
449
+ """Project event to persistence store.
450
+
451
+ Transforms the event into a database row based on the contract
452
+ configuration. The projection mode (upsert, insert_only, append)
453
+ determines how conflicts are handled.
454
+
455
+ Args:
456
+ event: The event envelope to project. The payload is accessed
457
+ via dot-notation paths defined in the contract columns.
458
+ correlation_id: Correlation ID for distributed tracing.
459
+
460
+ Returns:
461
+ ModelProjectionResult with success status and rows affected.
462
+ Returns skipped=True if event type is not in consumed_events.
463
+
464
+ Raises:
465
+ InfraConnectionError: If database connection fails.
466
+ InfraTimeoutError: If projection times out.
467
+ RuntimeHostError: For other database errors.
468
+ """
469
+ ctx = ModelInfraErrorContext(
470
+ transport_type=EnumInfraTransportType.DATABASE,
471
+ operation="project",
472
+ target_name=f"projector.{self.projector_id}",
473
+ correlation_id=correlation_id,
474
+ )
475
+
476
+ # Get event type from envelope
477
+ event_type = self._get_event_type(event)
478
+
479
+ # Check if this projector consumes this event type
480
+ if event_type not in self._contract.consumed_events:
481
+ logger.debug(
482
+ "Skipping event type '%s' not in consumed_events",
483
+ event_type,
484
+ extra={
485
+ "projector_id": self.projector_id,
486
+ "event_type": event_type,
487
+ "consumed_events": self._contract.consumed_events,
488
+ "correlation_id": str(correlation_id),
489
+ },
490
+ )
491
+ return ModelProjectionResult(success=True, skipped=True, rows_affected=0)
492
+
493
+ # Extract column values from event
494
+ values = self._extract_values(event, event_type)
495
+
496
+ # Extract notification-related values before projection
497
+ # These are needed for both fetching previous state and publishing notification
498
+ aggregate_id_for_notification = self._extract_aggregate_id_from_values(values)
499
+ from_state: str | None = None
500
+
501
+ # Fetch previous state if notifications are enabled
502
+ if (
503
+ self._is_notification_enabled()
504
+ and aggregate_id_for_notification is not None
505
+ ):
506
+ from_state = await self._fetch_current_state_for_notification(
507
+ aggregate_id_for_notification, correlation_id
508
+ )
509
+
510
+ # Execute projection based on mode
511
+ try:
512
+ rows_affected = await self._execute_projection(
513
+ values, correlation_id, event_type
514
+ )
515
+
516
+ logger.debug(
517
+ "Projection completed",
518
+ extra={
519
+ "projector_id": self.projector_id,
520
+ "event_type": event_type,
521
+ "rows_affected": rows_affected,
522
+ "correlation_id": str(correlation_id),
523
+ },
524
+ )
525
+
526
+ # Publish transition notification if configured and projection succeeded
527
+ if rows_affected > 0 and aggregate_id_for_notification is not None:
528
+ to_state = self._extract_state_from_values(values)
529
+ if to_state is not None:
530
+ projection_version = self._extract_version_from_values(values)
531
+ await self._publish_transition_notification(
532
+ event=event,
533
+ from_state=from_state,
534
+ to_state=to_state,
535
+ projection_version=projection_version,
536
+ aggregate_id=aggregate_id_for_notification,
537
+ correlation_id=correlation_id,
538
+ )
539
+
540
+ return ModelProjectionResult(
541
+ success=True,
542
+ skipped=False,
543
+ rows_affected=rows_affected,
544
+ )
545
+
546
+ except asyncpg.PostgresConnectionError as e:
547
+ raise InfraConnectionError(
548
+ f"Failed to connect to database for projection: {self.projector_id}",
549
+ context=ctx,
550
+ ) from e
551
+
552
+ except asyncpg.QueryCanceledError as e:
553
+ timeout_ctx = ModelTimeoutErrorContext(
554
+ transport_type=EnumInfraTransportType.DATABASE,
555
+ operation="project",
556
+ target_name=f"projector.{self.projector_id}",
557
+ correlation_id=correlation_id,
558
+ # timeout_seconds omitted - database timeout is connection-pool level, not available here
559
+ )
560
+ raise InfraTimeoutError(
561
+ f"Projection timed out for: {self.projector_id}",
562
+ context=timeout_ctx,
563
+ ) from e
564
+
565
+ except asyncpg.UniqueViolationError as e:
566
+ # ============================================================
567
+ # UniqueViolationError Handling by Projection Mode
568
+ # ============================================================
569
+ #
570
+ # Different projection modes have different semantics for unique
571
+ # constraint violations. See class docstring for full documentation.
572
+ #
573
+ # insert_only mode:
574
+ # - EXPECTED: Duplicate events (idempotency, at-least-once delivery)
575
+ # - BEHAVIOR: Return failure result, let caller decide how to handle
576
+ # - RATIONALE: insert_only is designed for idempotent projections
577
+ # where duplicates are tolerated but should be reported
578
+ #
579
+ # upsert mode:
580
+ # - UNEXPECTED: Should NEVER occur (ON CONFLICT handles duplicates)
581
+ # - BEHAVIOR: Raise RuntimeHostError
582
+ # - RATIONALE: If we get here, the upsert_key in the contract doesn't
583
+ # match the actual unique constraint in the database schema
584
+ # - ACTION REQUIRED: Verify contract.upsert_key matches DB constraint
585
+ #
586
+ # append mode:
587
+ # - UNEXPECTED: Assumes event-driven primary keys (envelope_id, etc.)
588
+ # - BEHAVIOR: Raise RuntimeHostError
589
+ # - RATIONALE: With event-driven PKs, duplicates indicate either:
590
+ # (a) Same event processed twice (infrastructure issue), or
591
+ # (b) Schema uses non-event PKs (design mismatch)
592
+ # - ACTION REQUIRED:
593
+ # * If (a): Investigate event delivery/replay logic
594
+ # * If (b): Consider switching to upsert mode or redesigning PK
595
+ #
596
+ # IMPORTANT ASSUMPTION for append mode:
597
+ # The primary key MUST be derived from event data (e.g., envelope_id,
598
+ # event_sequence) such that each unique event produces a unique key.
599
+ # If using auto-increment or business keys, append mode violations
600
+ # may indicate legitimate conflicts, not duplicates.
601
+ #
602
+ # ============================================================
603
+
604
+ if self._contract.behavior.mode == "insert_only":
605
+ # insert_only: Expected duplicate - report as failure, don't raise
606
+ logger.warning(
607
+ "Unique constraint violation during insert_only projection "
608
+ "(likely duplicate event - expected for idempotent processing)",
609
+ extra={
610
+ "projector_id": self.projector_id,
611
+ "event_type": event_type,
612
+ "correlation_id": str(correlation_id),
613
+ "mode": "insert_only",
614
+ "hint": "This is expected behavior for at-least-once delivery",
615
+ },
616
+ )
617
+ return ModelProjectionResult(
618
+ success=False,
619
+ skipped=False,
620
+ rows_affected=0,
621
+ error="Unique constraint violation: duplicate key for insert_only mode",
622
+ )
623
+
624
+ # upsert/append modes: Unexpected violation - fail fast with error
625
+ # For upsert: indicates contract.upsert_key doesn't match DB constraint
626
+ # For append: indicates either duplicate event or non-event-driven PK
627
+ mode = self._contract.behavior.mode
628
+ if mode == "upsert":
629
+ hint = (
630
+ "Verify that contract.behavior.upsert_key matches the actual "
631
+ "unique constraint in the database schema"
632
+ )
633
+ else: # append mode
634
+ hint = (
635
+ "Append mode assumes event-driven primary keys (e.g., envelope_id). "
636
+ "If using non-event PKs, consider switching to upsert mode or "
637
+ "redesigning the primary key to be event-derived"
638
+ )
639
+
640
+ logger.exception(
641
+ "Unexpected unique constraint violation in %s mode",
642
+ mode,
643
+ extra={
644
+ "projector_id": self.projector_id,
645
+ "event_type": event_type,
646
+ "correlation_id": str(correlation_id),
647
+ "mode": mode,
648
+ "hint": hint,
649
+ },
650
+ )
651
+
652
+ raise RuntimeHostError(
653
+ f"Unexpected unique constraint violation in {mode} mode "
654
+ f"for projector: {self.projector_id}. {hint}",
655
+ context=ctx,
656
+ ) from e
657
+
658
+ except Exception as e:
659
+ raise RuntimeHostError(
660
+ f"Failed to execute projection: {type(e).__name__}",
661
+ context=ctx,
662
+ ) from e
663
+
664
+ async def get_state(
665
+ self,
666
+ aggregate_id: UUID,
667
+ correlation_id: UUID,
668
+ ) -> dict[str, object] | None:
669
+ """Get current projected state for an aggregate.
670
+
671
+ Queries the projection table for the current state of the
672
+ specified aggregate. Uses configurable query timeout.
673
+
674
+ Args:
675
+ aggregate_id: The unique identifier of the aggregate.
676
+ correlation_id: Correlation ID for distributed tracing.
677
+
678
+ Returns:
679
+ Dictionary mapping column names (str) to their values if found,
680
+ None if no state exists. Values are typed as ``object`` because
681
+ asyncpg can return various PostgreSQL types (str, int, float,
682
+ datetime, UUID, etc.) and the schema is defined at runtime via
683
+ the projector contract.
684
+
685
+ Raises:
686
+ InfraConnectionError: If database connection fails.
687
+ InfraTimeoutError: If query times out.
688
+ RuntimeHostError: For other database errors.
689
+
690
+ Note:
691
+ **Composite Primary Key Handling**: For schemas with composite primary
692
+ keys (e.g., ``(entity_id, domain)``), this method queries using only
693
+ the first key column. This works correctly when the first column is
694
+ globally unique (e.g., UUID), but may return arbitrary results if
695
+ multiple rows share the same first-column value. For composite key
696
+ queries, use a direct database query with all key columns.
697
+ """
698
+ ctx = ModelInfraErrorContext(
699
+ transport_type=EnumInfraTransportType.DATABASE,
700
+ operation="get_state",
701
+ target_name=f"projector.{self.projector_id}",
702
+ correlation_id=correlation_id,
703
+ )
704
+
705
+ schema = self._contract.projection_schema
706
+ table_quoted = quote_identifier(schema.table)
707
+ # primary_key can be str | list[str] - normalize to first column for single-value queries
708
+ pk_column = (
709
+ schema.primary_key[0]
710
+ if isinstance(schema.primary_key, list)
711
+ else schema.primary_key
712
+ )
713
+ pk_quoted = quote_identifier(pk_column)
714
+
715
+ # Build SELECT query - table/column names from trusted contract
716
+ # S608: Safe - identifiers quoted via quote_identifier(), not user input
717
+ query = f"SELECT * FROM {table_quoted} WHERE {pk_quoted} = $1" # noqa: S608
718
+
719
+ try:
720
+ async with self._pool.acquire() as conn:
721
+ row = await conn.fetchrow(
722
+ query, aggregate_id, timeout=self._query_timeout
723
+ )
724
+
725
+ if row is None:
726
+ logger.debug(
727
+ "No state found for aggregate",
728
+ extra={
729
+ "projector_id": self.projector_id,
730
+ "aggregate_id": str(aggregate_id),
731
+ "correlation_id": str(correlation_id),
732
+ },
733
+ )
734
+ return None
735
+
736
+ # Convert asyncpg Record to dict
737
+ result: dict[str, object] = dict(row)
738
+ logger.debug(
739
+ "State retrieved for aggregate",
740
+ extra={
741
+ "projector_id": self.projector_id,
742
+ "aggregate_id": str(aggregate_id),
743
+ "correlation_id": str(correlation_id),
744
+ },
745
+ )
746
+ return result
747
+
748
+ except asyncpg.PostgresConnectionError as e:
749
+ raise InfraConnectionError(
750
+ f"Failed to connect to database for state query: {self.projector_id}",
751
+ context=ctx,
752
+ ) from e
753
+
754
+ except asyncpg.QueryCanceledError as e:
755
+ timeout_ctx = ModelTimeoutErrorContext(
756
+ transport_type=EnumInfraTransportType.DATABASE,
757
+ operation="get_state",
758
+ target_name=f"projector.{self.projector_id}",
759
+ correlation_id=correlation_id,
760
+ # timeout_seconds omitted - database timeout is connection-pool level, not available here
761
+ )
762
+ raise InfraTimeoutError(
763
+ f"State query timed out for: {self.projector_id}",
764
+ context=timeout_ctx,
765
+ ) from e
766
+
767
+ except Exception as e:
768
+ raise RuntimeHostError(
769
+ f"Failed to get state: {type(e).__name__}",
770
+ context=ctx,
771
+ ) from e
772
+
773
+ async def get_states(
774
+ self,
775
+ aggregate_ids: list[UUID],
776
+ correlation_id: UUID,
777
+ ) -> dict[UUID, dict[str, object]]:
778
+ """Get current projected states for multiple aggregates.
779
+
780
+ Bulk query for N+1 optimization. Fetches states for all provided
781
+ aggregate IDs in a single database query.
782
+
783
+ Args:
784
+ aggregate_ids: List of unique aggregate identifiers to query.
785
+ correlation_id: Correlation ID for distributed tracing.
786
+
787
+ Returns:
788
+ Dictionary mapping aggregate_id (UUID) to its state (dict).
789
+ Aggregates with no state are omitted from the result.
790
+ Empty dict if no aggregate_ids provided or none found.
791
+
792
+ Raises:
793
+ InfraConnectionError: If database connection fails.
794
+ InfraTimeoutError: If query times out.
795
+ RuntimeHostError: For other database errors.
796
+
797
+ Note:
798
+ **Composite Primary Key Handling**: For schemas with composite primary
799
+ keys, this method queries using only the first key column. See the
800
+ note on ``get_state()`` for implications and alternatives.
801
+
802
+ Example:
803
+ >>> states = await projector.get_states(
804
+ ... [order_id_1, order_id_2, order_id_3],
805
+ ... correlation_id,
806
+ ... )
807
+ >>> for order_id, state in states.items():
808
+ ... print(f"Order {order_id}: {state['status']}")
809
+ """
810
+ if not aggregate_ids:
811
+ return {}
812
+
813
+ ctx = ModelInfraErrorContext(
814
+ transport_type=EnumInfraTransportType.DATABASE,
815
+ operation="get_states",
816
+ target_name=f"projector.{self.projector_id}",
817
+ correlation_id=correlation_id,
818
+ )
819
+
820
+ schema = self._contract.projection_schema
821
+ table_quoted = quote_identifier(schema.table)
822
+ # primary_key can be str | list[str] - normalize to first column for single-value queries
823
+ pk_column = (
824
+ schema.primary_key[0]
825
+ if isinstance(schema.primary_key, list)
826
+ else schema.primary_key
827
+ )
828
+ pk_quoted = quote_identifier(pk_column)
829
+
830
+ # Build SELECT query with IN clause for bulk fetch
831
+ # S608: Safe - identifiers quoted via quote_identifier(), not user input
832
+ query = f"SELECT * FROM {table_quoted} WHERE {pk_quoted} = ANY($1)" # noqa: S608
833
+
834
+ try:
835
+ async with self._pool.acquire() as conn:
836
+ rows = await conn.fetch(
837
+ query, aggregate_ids, timeout=self._query_timeout
838
+ )
839
+
840
+ # Build result dict keyed by aggregate ID
841
+ result: dict[UUID, dict[str, object]] = {}
842
+ for row in rows:
843
+ row_dict: dict[str, object] = dict(row)
844
+ aggregate_id = row_dict.get(pk_column)
845
+ if isinstance(aggregate_id, UUID):
846
+ result[aggregate_id] = row_dict
847
+
848
+ logger.debug(
849
+ "Bulk state retrieval completed",
850
+ extra={
851
+ "projector_id": self.projector_id,
852
+ "requested_count": len(aggregate_ids),
853
+ "found_count": len(result),
854
+ "correlation_id": str(correlation_id),
855
+ },
856
+ )
857
+ return result
858
+
859
+ except asyncpg.PostgresConnectionError as e:
860
+ raise InfraConnectionError(
861
+ f"Failed to connect to database for bulk state query: {self.projector_id}",
862
+ context=ctx,
863
+ ) from e
864
+
865
+ except asyncpg.QueryCanceledError as e:
866
+ timeout_ctx = ModelTimeoutErrorContext(
867
+ transport_type=EnumInfraTransportType.DATABASE,
868
+ operation="get_states",
869
+ target_name=f"projector.{self.projector_id}",
870
+ correlation_id=correlation_id,
871
+ # timeout_seconds omitted - database timeout is connection-pool level, not available here
872
+ )
873
+ raise InfraTimeoutError(
874
+ f"Bulk state query timed out for: {self.projector_id}",
875
+ context=timeout_ctx,
876
+ ) from e
877
+
878
+ except Exception as e:
879
+ raise RuntimeHostError(
880
+ f"Failed to get states: {type(e).__name__}",
881
+ context=ctx,
882
+ ) from e
883
+
884
+ async def partial_update(
885
+ self,
886
+ aggregate_id: UUID,
887
+ updates: dict[str, object],
888
+ correlation_id: UUID,
889
+ ) -> bool:
890
+ """Perform a partial update on specific columns.
891
+
892
+ Updates only the specified columns for the row matching the aggregate_id,
893
+ without requiring a full event envelope or event-driven value extraction.
894
+ This is useful for lightweight operations like:
895
+
896
+ - Updating heartbeat timestamps (last_heartbeat_at, liveness_deadline)
897
+ - Setting timeout marker columns (ack_timeout_emitted_at)
898
+ - Updating tracking fields (updated_at)
899
+
900
+ Unlike project() which performs full event-driven projection based on
901
+ consumed_events and column source paths, partial_update() directly updates
902
+ the specified columns using the provided values.
903
+
904
+ Args:
905
+ aggregate_id: The primary key value identifying the row to update.
906
+ Must match the contract's primary_key column type (typically UUID).
907
+ updates: Dictionary mapping column names to their new values.
908
+ Column names are quoted for SQL safety. Values are passed as
909
+ parameterized query arguments for injection protection.
910
+ correlation_id: Correlation ID for distributed tracing.
911
+
912
+ Returns:
913
+ True if a row was updated (entity found and modified).
914
+ False if no row was found matching the aggregate_id.
915
+
916
+ Raises:
917
+ ProtocolConfigurationError: If updates dict is empty.
918
+ InfraConnectionError: If database connection fails.
919
+ InfraTimeoutError: If update times out.
920
+ RuntimeHostError: For other database errors.
921
+
922
+ Security:
923
+ - Column names are quoted using quote_identifier() for SQL safety
924
+ - Values use parameterized queries ($1, $2, etc.) to prevent injection
925
+ - Table and primary key names come from the trusted contract definition
926
+
927
+ Example:
928
+ >>> from datetime import UTC, datetime, timedelta
929
+ >>> # Update heartbeat tracking fields
930
+ >>> updated = await projector.partial_update(
931
+ ... aggregate_id=node_id,
932
+ ... updates={
933
+ ... "last_heartbeat_at": datetime.now(UTC),
934
+ ... "liveness_deadline": datetime.now(UTC) + timedelta(seconds=90),
935
+ ... "updated_at": datetime.now(UTC),
936
+ ... },
937
+ ... correlation_id=correlation_id,
938
+ ... )
939
+ >>> if not updated:
940
+ ... logger.warning("Entity not found for heartbeat update")
941
+
942
+ >>> # Set a timeout marker
943
+ >>> await projector.partial_update(
944
+ ... aggregate_id=node_id,
945
+ ... updates={"ack_timeout_emitted_at": datetime.now(UTC)},
946
+ ... correlation_id=correlation_id,
947
+ ... )
948
+
949
+ Related:
950
+ - OMN-1170: Converting ProjectorRegistration to declarative contracts
951
+ - project(): Full event-driven projection (uses event envelopes)
952
+ - get_state(): Read current projected state
953
+ """
954
+ ctx = ModelInfraErrorContext(
955
+ transport_type=EnumInfraTransportType.DATABASE,
956
+ operation="partial_update",
957
+ target_name=f"projector.{self.projector_id}",
958
+ correlation_id=correlation_id,
959
+ )
960
+
961
+ try:
962
+ return await self._partial_update(aggregate_id, updates, correlation_id)
963
+
964
+ except asyncpg.PostgresConnectionError as e:
965
+ raise InfraConnectionError(
966
+ f"Failed to connect to database for partial update: {self.projector_id}",
967
+ context=ctx,
968
+ ) from e
969
+
970
+ except asyncpg.QueryCanceledError as e:
971
+ timeout_ctx = ModelTimeoutErrorContext(
972
+ transport_type=EnumInfraTransportType.DATABASE,
973
+ operation="partial_update",
974
+ target_name=f"projector.{self.projector_id}",
975
+ correlation_id=correlation_id,
976
+ # timeout_seconds omitted - database timeout is connection-pool level, not available here
977
+ )
978
+ raise InfraTimeoutError(
979
+ f"Partial update timed out for: {self.projector_id}",
980
+ context=timeout_ctx,
981
+ ) from e
982
+
983
+ except ProtocolConfigurationError:
984
+ # Re-raise ProtocolConfigurationError (empty updates) as-is
985
+ raise
986
+
987
+ except Exception as e:
988
+ raise RuntimeHostError(
989
+ f"Failed to execute partial update: {type(e).__name__}",
990
+ context=ctx,
991
+ ) from e
992
+
993
+ async def upsert_partial(
994
+ self,
995
+ aggregate_id: UUID,
996
+ values: dict[str, object],
997
+ correlation_id: UUID,
998
+ conflict_columns: list[str] | None = None,
999
+ ) -> bool:
1000
+ """Perform a partial UPSERT (INSERT ON CONFLICT DO UPDATE) on specific columns.
1001
+
1002
+ Inserts a new row if no row exists with the given conflict key(s), or updates
1003
+ only the specified columns if the row already exists. This is useful for
1004
+ state transition operations where:
1005
+
1006
+ - A new entity may be created if it doesn't exist (e.g., first registration)
1007
+ - An existing entity should be updated with new state
1008
+
1009
+ Unlike partial_update() which only does UPDATE, upsert_partial() handles
1010
+ both INSERT and UPDATE cases atomically using PostgreSQL's
1011
+ INSERT ON CONFLICT DO UPDATE.
1012
+
1013
+ Supports composite conflict keys for tables with unique constraints on
1014
+ multiple columns (e.g., ``(entity_id, domain)``).
1015
+
1016
+ Args:
1017
+ aggregate_id: The primary aggregate identifier (for logging/tracing).
1018
+ values: Dictionary mapping column names to their values.
1019
+ MUST include all conflict columns specified.
1020
+ Column names are quoted for SQL safety. Values are passed as
1021
+ parameterized query arguments for injection protection.
1022
+ correlation_id: Correlation ID for distributed tracing.
1023
+ conflict_columns: Optional list of column names for ON CONFLICT clause.
1024
+ If not provided, defaults to the contract's primary_key.
1025
+ Use this for composite unique constraints (e.g., ["entity_id", "domain"]).
1026
+
1027
+ Returns:
1028
+ True if a row was inserted or updated successfully.
1029
+
1030
+ Raises:
1031
+ ProtocolConfigurationError: If values dict is empty or missing required conflict columns.
1032
+ InfraConnectionError: If database connection fails.
1033
+ InfraTimeoutError: If upsert times out.
1034
+ RuntimeHostError: For other database errors.
1035
+
1036
+ Security:
1037
+ - Column names are quoted using quote_identifier() for SQL safety
1038
+ - Values use parameterized queries ($1, $2, etc.) to prevent injection
1039
+ - Table and primary key names come from the trusted contract definition
1040
+
1041
+ Example:
1042
+ >>> from datetime import UTC, datetime
1043
+ >>> # Upsert with composite conflict key (creates row if not exists)
1044
+ >>> result = await projector.upsert_partial(
1045
+ ... aggregate_id=node_id,
1046
+ ... values={
1047
+ ... "entity_id": node_id,
1048
+ ... "domain": "registration",
1049
+ ... "current_state": "pending_registration",
1050
+ ... "node_type": "effect",
1051
+ ... "node_version": "1.0.0",
1052
+ ... "capabilities": "{}",
1053
+ ... "registered_at": datetime.now(UTC),
1054
+ ... "updated_at": datetime.now(UTC),
1055
+ ... },
1056
+ ... correlation_id=correlation_id,
1057
+ ... conflict_columns=["entity_id", "domain"], # Composite key
1058
+ ... )
1059
+
1060
+ Related:
1061
+ - OMN-1170: Converting ProjectorRegistration to declarative contracts
1062
+ - partial_update(): UPDATE only (row must exist)
1063
+ - project(): Full event-driven projection (uses event envelopes)
1064
+ """
1065
+ ctx = ModelInfraErrorContext(
1066
+ transport_type=EnumInfraTransportType.DATABASE,
1067
+ operation="upsert_partial",
1068
+ target_name=f"projector.{self.projector_id}",
1069
+ correlation_id=correlation_id,
1070
+ )
1071
+
1072
+ try:
1073
+ return await self._partial_upsert(
1074
+ aggregate_id, values, correlation_id, conflict_columns
1075
+ )
1076
+
1077
+ except asyncpg.PostgresConnectionError as e:
1078
+ raise InfraConnectionError(
1079
+ f"Failed to connect to database for partial upsert: {self.projector_id}",
1080
+ context=ctx,
1081
+ ) from e
1082
+
1083
+ except asyncpg.QueryCanceledError as e:
1084
+ timeout_ctx = ModelTimeoutErrorContext(
1085
+ transport_type=EnumInfraTransportType.DATABASE,
1086
+ operation="upsert_partial",
1087
+ target_name=f"projector.{self.projector_id}",
1088
+ correlation_id=correlation_id,
1089
+ # timeout_seconds omitted - database timeout is connection-pool level, not available here
1090
+ )
1091
+ raise InfraTimeoutError(
1092
+ f"Partial upsert timed out for: {self.projector_id}",
1093
+ context=timeout_ctx,
1094
+ ) from e
1095
+
1096
+ except ProtocolConfigurationError:
1097
+ # Re-raise ProtocolConfigurationError (empty values or missing PK) as-is
1098
+ raise
1099
+
1100
+ except Exception as e:
1101
+ raise RuntimeHostError(
1102
+ f"Failed to execute partial upsert: {type(e).__name__}",
1103
+ context=ctx,
1104
+ ) from e
1105
+
1106
+ def _get_event_type(self, event: ModelEventEnvelope[object]) -> str:
1107
+ """Extract event type from envelope.
1108
+
1109
+ Event type is resolved in the following order:
1110
+ 1. envelope.metadata.tags['event_type'] if present
1111
+ 2. payload.event_type attribute if present
1112
+ 3. payload class name
1113
+
1114
+ Args:
1115
+ event: The event envelope to extract type from.
1116
+
1117
+ Returns:
1118
+ Event type string.
1119
+ """
1120
+ # Check metadata tags first
1121
+ if event.metadata and event.metadata.tags:
1122
+ event_type_tag = event.metadata.tags.get("event_type")
1123
+ if event_type_tag is not None:
1124
+ return str(event_type_tag)
1125
+
1126
+ # Check payload attribute
1127
+ payload = event.payload
1128
+ if hasattr(payload, "event_type"):
1129
+ event_type_attr = payload.event_type
1130
+ if event_type_attr:
1131
+ return str(event_type_attr)
1132
+
1133
+ # Fall back to class name
1134
+ return type(payload).__name__
1135
+
1136
+ def _extract_values(
1137
+ self,
1138
+ event: ModelEventEnvelope[object],
1139
+ event_type: str,
1140
+ ) -> dict[str, object]:
1141
+ """Extract column values from event based on contract schema.
1142
+
1143
+ Iterates through the contract's column definitions and resolves
1144
+ each column's source path to extract the value from the event.
1145
+
1146
+ Path Resolution Failures:
1147
+ When path resolution fails (returns None), a WARNING is logged
1148
+ to alert operators of potential contract configuration issues.
1149
+ This is critical for production monitoring as silent None values
1150
+ could indicate typos in contract source paths.
1151
+
1152
+ Args:
1153
+ event: The event envelope containing the data.
1154
+ event_type: The resolved event type for filtering.
1155
+
1156
+ Returns:
1157
+ Dictionary mapping column names to their extracted values.
1158
+ """
1159
+ values: dict[str, object] = {}
1160
+ schema = self._contract.projection_schema
1161
+
1162
+ for column in schema.columns:
1163
+ # Skip columns with on_event filter that doesn't match
1164
+ if column.on_event is not None and column.on_event != event_type:
1165
+ continue
1166
+
1167
+ # Resolve the source path
1168
+ value = self._resolve_path(event, column.source)
1169
+
1170
+ # Log warning for path resolution failures
1171
+ if value is None:
1172
+ if column.default is not None:
1173
+ # Default will be applied - less critical but still noteworthy
1174
+ logger.warning(
1175
+ "Path resolution failed for column '%s' with source '%s' on "
1176
+ "event type '%s'. Using default value '%s'. "
1177
+ "Check contract source path for typos.",
1178
+ column.name,
1179
+ column.source,
1180
+ event_type,
1181
+ column.default,
1182
+ extra={
1183
+ "projector_id": self.projector_id,
1184
+ "column_name": column.name,
1185
+ "source_path": column.source,
1186
+ "event_type": event_type,
1187
+ "default_applied": True,
1188
+ "default_value": column.default,
1189
+ },
1190
+ )
1191
+ value = column.default
1192
+ else:
1193
+ # No default - value will be None, potentially risky
1194
+ logger.warning(
1195
+ "Path resolution failed for column '%s' with source '%s' on "
1196
+ "event type '%s'. Value will be None. "
1197
+ "Check contract source path for typos.",
1198
+ column.name,
1199
+ column.source,
1200
+ event_type,
1201
+ extra={
1202
+ "projector_id": self.projector_id,
1203
+ "column_name": column.name,
1204
+ "source_path": column.source,
1205
+ "event_type": event_type,
1206
+ "default_applied": False,
1207
+ },
1208
+ )
1209
+
1210
+ values[column.name] = value
1211
+
1212
+ return values
1213
+
1214
+ def _resolve_path(
1215
+ self,
1216
+ root: object,
1217
+ path: str,
1218
+ ) -> object | None:
1219
+ """Resolve a dot-notation path to extract a value.
1220
+
1221
+ Supports navigation through:
1222
+ - Dictionary keys
1223
+ - Object attributes
1224
+ - Pydantic model fields (via model_dump())
1225
+
1226
+ Args:
1227
+ root: The root object to start navigation from.
1228
+ path: Dot-notation path (e.g., "event.payload.node_name").
1229
+
1230
+ Returns:
1231
+ The resolved value, or None if path resolution fails.
1232
+
1233
+ Example:
1234
+ >>> event = ModelEventEnvelope(payload={"node_name": "test"})
1235
+ >>> self._resolve_path(event, "payload.node_name")
1236
+ 'test'
1237
+ """
1238
+ parts = path.split(".")
1239
+ current: object = root
1240
+
1241
+ for part in parts:
1242
+ if current is None:
1243
+ return None
1244
+
1245
+ # Try dictionary access
1246
+ if isinstance(current, dict):
1247
+ current = current.get(part)
1248
+ continue
1249
+
1250
+ # Try attribute access first (avoids Pydantic model_dump side effects)
1251
+ if hasattr(current, part):
1252
+ current = getattr(current, part)
1253
+ continue
1254
+
1255
+ # Fall back to Pydantic model_dump for nested access
1256
+ if isinstance(current, BaseModel):
1257
+ dumped = current.model_dump()
1258
+ current = dumped.get(part)
1259
+ continue
1260
+
1261
+ # Path resolution failed
1262
+ logger.debug(
1263
+ "Path resolution failed at part '%s'",
1264
+ part,
1265
+ extra={
1266
+ "path": path,
1267
+ "current_type": type(current).__name__,
1268
+ },
1269
+ )
1270
+ return None
1271
+
1272
+ return current
1273
+
1274
+ async def _execute_projection(
1275
+ self,
1276
+ values: dict[str, object],
1277
+ correlation_id: UUID,
1278
+ event_type: str,
1279
+ ) -> int:
1280
+ """Execute the projection based on behavior mode.
1281
+
1282
+ Dispatches to the appropriate SQL execution method based on
1283
+ the contract's behavior.mode setting.
1284
+
1285
+ Args:
1286
+ values: Column name to value mapping.
1287
+ correlation_id: Correlation ID for tracing.
1288
+ event_type: The event type being projected (for logging context).
1289
+
1290
+ Returns:
1291
+ Number of rows affected.
1292
+
1293
+ Raises:
1294
+ asyncpg exceptions on database errors.
1295
+ """
1296
+ mode = self._contract.behavior.mode
1297
+
1298
+ if mode == "upsert":
1299
+ return await self._upsert(values, correlation_id, event_type)
1300
+ elif mode == "insert_only":
1301
+ return await self._insert(values, correlation_id, event_type)
1302
+ elif mode == "append":
1303
+ return await self._append(values, correlation_id, event_type)
1304
+ else:
1305
+ # This should never happen due to contract validation
1306
+ context = ModelInfraErrorContext(
1307
+ transport_type=EnumInfraTransportType.RUNTIME,
1308
+ operation="execute_projection",
1309
+ target_name=f"projector.{self.projector_id}",
1310
+ correlation_id=correlation_id,
1311
+ )
1312
+ raise ProtocolConfigurationError(
1313
+ f"Unknown projection mode: {mode}",
1314
+ context=context,
1315
+ )
1316
+
1317
+ def __repr__(self) -> str:
1318
+ """Return string representation."""
1319
+ return (
1320
+ f"ProjectorShell("
1321
+ f"id={self.projector_id!r}, "
1322
+ f"aggregate_type={self.aggregate_type!r}, "
1323
+ f"events={len(self.consumed_events)}, "
1324
+ f"mode={self._contract.behavior.mode!r})"
1325
+ )
1326
+
1327
+
1328
+ __all__ = [
1329
+ "ProjectorShell",
1330
+ ]