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,1559 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Registration Projection Reader Implementation.
4
+
5
+ Implements projection reads for the registration domain to support
6
+ orchestrator state queries. Orchestrators read current state using
7
+ projections only - never scanning Kafka topics.
8
+
9
+ Concurrency Safety:
10
+ This implementation is coroutine-safe for concurrent async read operations.
11
+ Uses asyncpg connection pool for connection management, and asyncio.Lock
12
+ (via MixinAsyncCircuitBreaker) for circuit breaker state protection.
13
+
14
+ Note: This is not thread-safe. For multi-threaded access, additional
15
+ synchronization would be required.
16
+
17
+ State Filtering on Capability Queries:
18
+ All capability query methods (get_by_capability_tag, get_by_intent_type,
19
+ get_by_protocol, get_by_contract_type, get_by_capability_tags_all,
20
+ get_by_capability_tags_any) support an optional `state` parameter for
21
+ filtering results by registration state.
22
+
23
+ This is particularly useful for service discovery patterns where you
24
+ typically want to find only ACTIVE nodes that provide specific capabilities:
25
+
26
+ Example - Find active Kafka consumers:
27
+ >>> active_consumers = await reader.get_by_capability_tag(
28
+ ... "kafka.consumer",
29
+ ... state=EnumRegistrationState.ACTIVE,
30
+ ... )
31
+
32
+ Example - Find active nodes implementing a protocol:
33
+ >>> active_publishers = await reader.get_by_protocol(
34
+ ... "ProtocolEventPublisher",
35
+ ... state=EnumRegistrationState.ACTIVE,
36
+ ... )
37
+
38
+ Example - Find all effect nodes (including inactive):
39
+ >>> all_effects = await reader.get_by_contract_type("effect")
40
+
41
+ When state is None (default), all registrations matching the capability
42
+ criteria are returned regardless of their current FSM state. When state
43
+ is provided, an additional WHERE clause filters on current_state.
44
+
45
+ Valid state values (EnumRegistrationState):
46
+ - PENDING_REGISTRATION: Initial state after registration initiated
47
+ - ACCEPTED: Registration accepted, awaiting acknowledgment
48
+ - AWAITING_ACK: Waiting for node to acknowledge registration
49
+ - ACK_RECEIVED: Node acknowledged registration
50
+ - ACTIVE: Fully registered and operational (most common filter for discovery)
51
+ - ACK_TIMED_OUT: Ack deadline passed (retriable)
52
+ - REJECTED: Registration rejected (terminal)
53
+ - LIVENESS_EXPIRED: Liveness check failed (terminal)
54
+
55
+ Related Tickets:
56
+ - OMN-944 (F1): Implement Registration Projection Schema
57
+ - OMN-940 (F0): Define Projector Execution Model
58
+ - OMN-930 (C0): Projection Reader Protocol
59
+ - OMN-932 (C2): Durable Timeout Handling
60
+ - OMN-1134: Registry Projection Extensions for Capabilities
61
+ """
62
+
63
+ from __future__ import annotations
64
+
65
+ import json
66
+ import logging
67
+ from datetime import datetime
68
+ from uuid import UUID, uuid4
69
+
70
+ import asyncpg
71
+
72
+ from omnibase_core.models.primitives.model_semver import ModelSemVer
73
+ from omnibase_infra.enums import EnumInfraTransportType, EnumRegistrationState
74
+ from omnibase_infra.errors import (
75
+ InfraConnectionError,
76
+ InfraTimeoutError,
77
+ ModelInfraErrorContext,
78
+ ModelTimeoutErrorContext,
79
+ ProtocolConfigurationError,
80
+ RuntimeHostError,
81
+ )
82
+ from omnibase_infra.mixins import MixinAsyncCircuitBreaker
83
+ from omnibase_infra.models.projection import ModelRegistrationProjection
84
+ from omnibase_infra.models.projection.model_registration_projection import ContractType
85
+ from omnibase_infra.models.registration.model_node_capabilities import (
86
+ ModelNodeCapabilities,
87
+ )
88
+ from omnibase_infra.models.resilience import ModelCircuitBreakerConfig
89
+
90
+ logger = logging.getLogger(__name__)
91
+
92
+
93
+ class ProjectionReaderRegistration(MixinAsyncCircuitBreaker):
94
+ """Registration projection reader implementation using asyncpg.
95
+
96
+ Provides read access to registration projections for orchestrators.
97
+ Supports entity lookups, state queries, and deadline scans for
98
+ timeout handling.
99
+
100
+ Circuit Breaker:
101
+ Uses MixinAsyncCircuitBreaker for resilience. Opens after 5 consecutive
102
+ failures and resets after 60 seconds.
103
+
104
+ Security:
105
+ All queries use parameterized statements for SQL injection protection.
106
+
107
+ Error Handling Pattern:
108
+ All public methods follow a consistent error handling structure:
109
+
110
+ 1. Create fresh ModelInfraErrorContext per operation (intentionally NOT
111
+ reused to ensure each operation has isolated context with its own
112
+ correlation ID for distributed tracing).
113
+
114
+ 2. Check circuit breaker before database operation.
115
+
116
+ 3. Map exceptions consistently:
117
+ - asyncpg.PostgresConnectionError -> InfraConnectionError
118
+ - asyncpg.QueryCanceledError -> InfraTimeoutError
119
+ - Generic Exception -> RuntimeHostError
120
+
121
+ 4. Record circuit breaker failures for all exception types.
122
+
123
+ This pattern ensures predictable error behavior and enables consistent
124
+ error handling by callers across all reader methods.
125
+
126
+ JSONB Handling:
127
+ The capabilities field is stored as JSONB in PostgreSQL. While asyncpg
128
+ typically returns JSONB as Python dicts, some connection configurations
129
+ may return strings. The _row_to_projection method handles both cases
130
+ using json.loads() for string fallback.
131
+
132
+ Example:
133
+ >>> pool = await asyncpg.create_pool(dsn)
134
+ >>> reader = ProjectionReaderRegistration(pool)
135
+ >>> proj = await reader.get_entity_state(node_id, "registration")
136
+ >>> if proj and proj.current_state.is_active():
137
+ ... print("Node is active")
138
+ """
139
+
140
+ def __init__(self, pool: asyncpg.Pool) -> None:
141
+ """Initialize reader with connection pool.
142
+
143
+ Args:
144
+ pool: asyncpg connection pool for database access.
145
+ Pool should be created by the caller (e.g., from HandlerDb).
146
+ """
147
+ self._pool = pool
148
+ config = ModelCircuitBreakerConfig.from_env(
149
+ service_name="projection_reader.registration",
150
+ transport_type=EnumInfraTransportType.DATABASE,
151
+ )
152
+ self._init_circuit_breaker_from_config(config)
153
+
154
+ def _row_to_projection(self, row: asyncpg.Record) -> ModelRegistrationProjection:
155
+ """Convert database row to projection model.
156
+
157
+ Args:
158
+ row: asyncpg Record from query result
159
+
160
+ Returns:
161
+ ModelRegistrationProjection instance
162
+
163
+ Note:
164
+ JSON parse errors in capabilities are handled gracefully by returning
165
+ an empty ModelNodeCapabilities with a warning log. This ensures
166
+ projections remain readable even if capabilities data is malformed.
167
+ """
168
+ # Parse capabilities from JSONB.
169
+ # asyncpg typically returns JSONB as Python dicts, but connection
170
+ # configuration (e.g., custom type codecs) may return strings.
171
+ # Handle both cases for robustness.
172
+ capabilities_data = row["capabilities"]
173
+ if isinstance(capabilities_data, str):
174
+ try:
175
+ capabilities_data = json.loads(capabilities_data)
176
+ except json.JSONDecodeError as e:
177
+ logger.warning(
178
+ "Failed to parse capabilities JSON for entity %s: %s. "
179
+ "Using empty capabilities.",
180
+ row["entity_id"],
181
+ str(e),
182
+ )
183
+ capabilities_data = {}
184
+ capabilities = ModelNodeCapabilities.model_validate(capabilities_data)
185
+
186
+ # Parse node_version from string if needed.
187
+ # Database stores version as string, model expects ModelSemVer.
188
+ # See util_semver.py "Database Persistence" section for pattern docs.
189
+ node_version_data = row["node_version"]
190
+ if isinstance(node_version_data, str):
191
+ node_version_data = ModelSemVer.parse(node_version_data)
192
+
193
+ return ModelRegistrationProjection(
194
+ entity_id=row["entity_id"],
195
+ domain=row["domain"],
196
+ current_state=EnumRegistrationState(row["current_state"]),
197
+ node_type=row["node_type"],
198
+ node_version=node_version_data,
199
+ capabilities=capabilities,
200
+ # Capability fields (OMN-1134)
201
+ contract_type=row.get("contract_type"),
202
+ intent_types=row.get("intent_types") or [],
203
+ protocols=row.get("protocols") or [],
204
+ capability_tags=row.get("capability_tags") or [],
205
+ contract_version=row.get("contract_version"),
206
+ # Timeout fields
207
+ ack_deadline=row["ack_deadline"],
208
+ liveness_deadline=row["liveness_deadline"],
209
+ last_heartbeat_at=row["last_heartbeat_at"],
210
+ ack_timeout_emitted_at=row["ack_timeout_emitted_at"],
211
+ liveness_timeout_emitted_at=row["liveness_timeout_emitted_at"],
212
+ last_applied_event_id=row["last_applied_event_id"],
213
+ last_applied_offset=row["last_applied_offset"],
214
+ last_applied_sequence=row["last_applied_sequence"],
215
+ last_applied_partition=row["last_applied_partition"],
216
+ registered_at=row["registered_at"],
217
+ updated_at=row["updated_at"],
218
+ correlation_id=row["correlation_id"],
219
+ )
220
+
221
+ async def get_entity_state(
222
+ self,
223
+ entity_id: UUID,
224
+ domain: str = "registration",
225
+ correlation_id: UUID | None = None,
226
+ ) -> ModelRegistrationProjection | None:
227
+ """Get current projection for entity.
228
+
229
+ Point lookup for a single entity's current state.
230
+ Primary method for orchestrators to check entity state.
231
+
232
+ Args:
233
+ entity_id: Node UUID
234
+ domain: Domain namespace (default: "registration")
235
+ correlation_id: Optional correlation ID for tracing
236
+
237
+ Returns:
238
+ Projection if exists, None otherwise
239
+
240
+ Raises:
241
+ InfraConnectionError: If database connection fails
242
+ InfraTimeoutError: If query times out
243
+ RuntimeHostError: For other database errors
244
+
245
+ Example:
246
+ >>> proj = await reader.get_entity_state(node_id)
247
+ >>> if proj and proj.current_state.is_active():
248
+ ... route_work_to_node(node_id)
249
+ """
250
+ corr_id = correlation_id or uuid4()
251
+ ctx = ModelInfraErrorContext(
252
+ transport_type=EnumInfraTransportType.DATABASE,
253
+ operation="get_entity_state",
254
+ target_name="projection_reader.registration",
255
+ correlation_id=corr_id,
256
+ )
257
+
258
+ # Check circuit breaker
259
+ async with self._circuit_breaker_lock:
260
+ await self._check_circuit_breaker("get_entity_state", corr_id)
261
+
262
+ query_sql = """
263
+ SELECT * FROM registration_projections
264
+ WHERE entity_id = $1 AND domain = $2
265
+ """
266
+
267
+ try:
268
+ async with self._pool.acquire() as conn:
269
+ row = await conn.fetchrow(query_sql, entity_id, domain)
270
+
271
+ async with self._circuit_breaker_lock:
272
+ await self._reset_circuit_breaker()
273
+
274
+ if row is None:
275
+ return None
276
+
277
+ return self._row_to_projection(row)
278
+
279
+ except asyncpg.PostgresConnectionError as e:
280
+ async with self._circuit_breaker_lock:
281
+ await self._record_circuit_failure("get_entity_state", corr_id)
282
+ raise InfraConnectionError(
283
+ "Failed to connect to database for entity state lookup",
284
+ context=ctx,
285
+ ) from e
286
+
287
+ except asyncpg.QueryCanceledError as e:
288
+ async with self._circuit_breaker_lock:
289
+ await self._record_circuit_failure("get_entity_state", corr_id)
290
+ raise InfraTimeoutError(
291
+ "Entity state lookup timed out",
292
+ context=ModelTimeoutErrorContext(
293
+ transport_type=ctx.transport_type,
294
+ operation=ctx.operation,
295
+ target_name=ctx.target_name,
296
+ correlation_id=ctx.correlation_id,
297
+ # timeout_seconds omitted - value not available in this context (defaults to None)
298
+ ),
299
+ ) from e
300
+
301
+ except Exception as e:
302
+ async with self._circuit_breaker_lock:
303
+ await self._record_circuit_failure("get_entity_state", corr_id)
304
+ raise RuntimeHostError(
305
+ f"Failed to get entity state: {type(e).__name__}",
306
+ context=ctx,
307
+ ) from e
308
+
309
+ async def get_registration_status(
310
+ self,
311
+ entity_id: UUID,
312
+ domain: str = "registration",
313
+ correlation_id: UUID | None = None,
314
+ ) -> EnumRegistrationState | None:
315
+ """Get current registration state (convenience method).
316
+
317
+ Lightweight method that returns only the FSM state without
318
+ the full projection. Useful for quick state checks.
319
+
320
+ Args:
321
+ entity_id: Node UUID
322
+ domain: Domain namespace (default: "registration")
323
+ correlation_id: Optional correlation ID for tracing
324
+
325
+ Returns:
326
+ Current FSM state if exists, None otherwise
327
+
328
+ Example:
329
+ >>> state = await reader.get_registration_status(node_id)
330
+ >>> if state == EnumRegistrationState.ACTIVE:
331
+ ... print("Node is active")
332
+ """
333
+ corr_id = correlation_id or uuid4()
334
+ ctx = ModelInfraErrorContext(
335
+ transport_type=EnumInfraTransportType.DATABASE,
336
+ operation="get_registration_status",
337
+ target_name="projection_reader.registration",
338
+ correlation_id=corr_id,
339
+ )
340
+
341
+ # Check circuit breaker
342
+ async with self._circuit_breaker_lock:
343
+ await self._check_circuit_breaker("get_registration_status", corr_id)
344
+
345
+ query_sql = """
346
+ SELECT current_state FROM registration_projections
347
+ WHERE entity_id = $1 AND domain = $2
348
+ """
349
+
350
+ try:
351
+ async with self._pool.acquire() as conn:
352
+ row = await conn.fetchrow(query_sql, entity_id, domain)
353
+
354
+ async with self._circuit_breaker_lock:
355
+ await self._reset_circuit_breaker()
356
+
357
+ if row is None:
358
+ return None
359
+
360
+ return EnumRegistrationState(row["current_state"])
361
+
362
+ except asyncpg.PostgresConnectionError as e:
363
+ async with self._circuit_breaker_lock:
364
+ await self._record_circuit_failure("get_registration_status", corr_id)
365
+ raise InfraConnectionError(
366
+ "Failed to connect to database for status lookup",
367
+ context=ctx,
368
+ ) from e
369
+
370
+ except asyncpg.QueryCanceledError as e:
371
+ async with self._circuit_breaker_lock:
372
+ await self._record_circuit_failure("get_registration_status", corr_id)
373
+ raise InfraTimeoutError(
374
+ "Registration status lookup timed out",
375
+ context=ModelTimeoutErrorContext(
376
+ transport_type=ctx.transport_type,
377
+ operation=ctx.operation,
378
+ target_name=ctx.target_name,
379
+ correlation_id=ctx.correlation_id,
380
+ # timeout_seconds omitted - value not available in this context (defaults to None)
381
+ ),
382
+ ) from e
383
+
384
+ except Exception as e:
385
+ async with self._circuit_breaker_lock:
386
+ await self._record_circuit_failure("get_registration_status", corr_id)
387
+ raise RuntimeHostError(
388
+ f"Failed to get registration status: {type(e).__name__}",
389
+ context=ctx,
390
+ ) from e
391
+
392
+ async def get_by_state(
393
+ self,
394
+ state: EnumRegistrationState,
395
+ domain: str = "registration",
396
+ limit: int = 100,
397
+ correlation_id: UUID | None = None,
398
+ ) -> list[ModelRegistrationProjection]:
399
+ """Query projections by state.
400
+
401
+ Find all projections with a specific FSM state.
402
+
403
+ Args:
404
+ state: FSM state to filter by
405
+ domain: Domain namespace (default: "registration")
406
+ limit: Maximum results to return (default: 100)
407
+ correlation_id: Optional correlation ID for tracing
408
+
409
+ Returns:
410
+ List of matching projections
411
+
412
+ Example:
413
+ >>> active = await reader.get_by_state(EnumRegistrationState.ACTIVE)
414
+ >>> for proj in active:
415
+ ... print(f"Active node: {proj.entity_id}")
416
+ """
417
+ corr_id = correlation_id or uuid4()
418
+ ctx = ModelInfraErrorContext(
419
+ transport_type=EnumInfraTransportType.DATABASE,
420
+ operation="get_by_state",
421
+ target_name="projection_reader.registration",
422
+ correlation_id=corr_id,
423
+ )
424
+
425
+ # Check circuit breaker
426
+ async with self._circuit_breaker_lock:
427
+ await self._check_circuit_breaker("get_by_state", corr_id)
428
+
429
+ query_sql = """
430
+ SELECT * FROM registration_projections
431
+ WHERE domain = $1 AND current_state = $2
432
+ ORDER BY updated_at DESC
433
+ LIMIT $3
434
+ """
435
+
436
+ try:
437
+ async with self._pool.acquire() as conn:
438
+ rows = await conn.fetch(query_sql, domain, state.value, limit)
439
+
440
+ async with self._circuit_breaker_lock:
441
+ await self._reset_circuit_breaker()
442
+
443
+ return [self._row_to_projection(row) for row in rows]
444
+
445
+ except asyncpg.PostgresConnectionError as e:
446
+ async with self._circuit_breaker_lock:
447
+ await self._record_circuit_failure("get_by_state", corr_id)
448
+ raise InfraConnectionError(
449
+ "Failed to connect to database for state query",
450
+ context=ctx,
451
+ ) from e
452
+
453
+ except asyncpg.QueryCanceledError as e:
454
+ async with self._circuit_breaker_lock:
455
+ await self._record_circuit_failure("get_by_state", corr_id)
456
+ raise InfraTimeoutError(
457
+ "State query timed out",
458
+ context=ModelTimeoutErrorContext(
459
+ transport_type=ctx.transport_type,
460
+ operation=ctx.operation,
461
+ target_name=ctx.target_name,
462
+ correlation_id=ctx.correlation_id,
463
+ # timeout_seconds omitted - value not available in this context (defaults to None)
464
+ ),
465
+ ) from e
466
+
467
+ except Exception as e:
468
+ async with self._circuit_breaker_lock:
469
+ await self._record_circuit_failure("get_by_state", corr_id)
470
+ raise RuntimeHostError(
471
+ f"Failed to query by state: {type(e).__name__}",
472
+ context=ctx,
473
+ ) from e
474
+
475
+ async def get_overdue_ack_registrations(
476
+ self,
477
+ now: datetime,
478
+ domain: str = "registration",
479
+ limit: int = 100,
480
+ correlation_id: UUID | None = None,
481
+ ) -> list[ModelRegistrationProjection]:
482
+ """Get registrations with overdue ack deadlines (not yet emitted).
483
+
484
+ Per C2: Returns entities where:
485
+ - ack_deadline < now
486
+ - ack_timeout_emitted_at IS NULL
487
+ - current_state requires ack (ACCEPTED or AWAITING_ACK)
488
+
489
+ Used by orchestrators during RuntimeTick processing to find
490
+ registrations that need ack timeout events emitted.
491
+
492
+ Args:
493
+ now: Current time (injected by runtime)
494
+ domain: Domain namespace (default: "registration")
495
+ limit: Maximum results to return (default: 100)
496
+ correlation_id: Optional correlation ID for tracing
497
+
498
+ Returns:
499
+ List of registrations needing ack timeout events
500
+
501
+ Example:
502
+ >>> overdue = await reader.get_overdue_ack_registrations(datetime.now(UTC))
503
+ >>> for proj in overdue:
504
+ ... emit_ack_timeout_event(proj.entity_id)
505
+ """
506
+ corr_id = correlation_id or uuid4()
507
+ ctx = ModelInfraErrorContext(
508
+ transport_type=EnumInfraTransportType.DATABASE,
509
+ operation="get_overdue_ack_registrations",
510
+ target_name="projection_reader.registration",
511
+ correlation_id=corr_id,
512
+ )
513
+
514
+ # Check circuit breaker
515
+ async with self._circuit_breaker_lock:
516
+ await self._check_circuit_breaker("get_overdue_ack_registrations", corr_id)
517
+
518
+ # States that require ack
519
+ ack_states = [
520
+ EnumRegistrationState.ACCEPTED.value,
521
+ EnumRegistrationState.AWAITING_ACK.value,
522
+ ]
523
+
524
+ query_sql = """
525
+ SELECT * FROM registration_projections
526
+ WHERE domain = $1
527
+ AND ack_deadline < $2
528
+ AND ack_timeout_emitted_at IS NULL
529
+ AND current_state = ANY($3)
530
+ ORDER BY ack_deadline ASC
531
+ LIMIT $4
532
+ """
533
+
534
+ try:
535
+ async with self._pool.acquire() as conn:
536
+ rows = await conn.fetch(query_sql, domain, now, ack_states, limit)
537
+
538
+ async with self._circuit_breaker_lock:
539
+ await self._reset_circuit_breaker()
540
+
541
+ return [self._row_to_projection(row) for row in rows]
542
+
543
+ except asyncpg.PostgresConnectionError as e:
544
+ async with self._circuit_breaker_lock:
545
+ await self._record_circuit_failure(
546
+ "get_overdue_ack_registrations", corr_id
547
+ )
548
+ raise InfraConnectionError(
549
+ "Failed to connect to database for overdue ack query",
550
+ context=ctx,
551
+ ) from e
552
+
553
+ except asyncpg.QueryCanceledError as e:
554
+ async with self._circuit_breaker_lock:
555
+ await self._record_circuit_failure(
556
+ "get_overdue_ack_registrations", corr_id
557
+ )
558
+ raise InfraTimeoutError(
559
+ "Overdue ack registrations query timed out",
560
+ context=ModelTimeoutErrorContext(
561
+ transport_type=ctx.transport_type,
562
+ operation=ctx.operation,
563
+ target_name=ctx.target_name,
564
+ correlation_id=ctx.correlation_id,
565
+ # timeout_seconds omitted - value not available in this context (defaults to None)
566
+ ),
567
+ ) from e
568
+
569
+ except Exception as e:
570
+ async with self._circuit_breaker_lock:
571
+ await self._record_circuit_failure(
572
+ "get_overdue_ack_registrations", corr_id
573
+ )
574
+ raise RuntimeHostError(
575
+ f"Failed to query overdue ack registrations: {type(e).__name__}",
576
+ context=ctx,
577
+ ) from e
578
+
579
+ async def get_overdue_liveness_registrations(
580
+ self,
581
+ now: datetime,
582
+ domain: str = "registration",
583
+ limit: int = 100,
584
+ correlation_id: UUID | None = None,
585
+ ) -> list[ModelRegistrationProjection]:
586
+ """Get registrations with overdue liveness deadlines (not yet emitted).
587
+
588
+ Per C2: Returns entities where:
589
+ - liveness_deadline < now
590
+ - liveness_timeout_emitted_at IS NULL
591
+ - current_state = ACTIVE
592
+
593
+ Used by orchestrators during RuntimeTick processing to find
594
+ active registrations that have missed their liveness deadline.
595
+
596
+ Args:
597
+ now: Current time (injected by runtime)
598
+ domain: Domain namespace (default: "registration")
599
+ limit: Maximum results to return (default: 100)
600
+ correlation_id: Optional correlation ID for tracing
601
+
602
+ Returns:
603
+ List of registrations needing liveness timeout events
604
+
605
+ Example:
606
+ >>> overdue = await reader.get_overdue_liveness_registrations(datetime.now(UTC))
607
+ >>> for proj in overdue:
608
+ ... emit_liveness_expired_event(proj.entity_id)
609
+ """
610
+ corr_id = correlation_id or uuid4()
611
+ ctx = ModelInfraErrorContext(
612
+ transport_type=EnumInfraTransportType.DATABASE,
613
+ operation="get_overdue_liveness_registrations",
614
+ target_name="projection_reader.registration",
615
+ correlation_id=corr_id,
616
+ )
617
+
618
+ # Check circuit breaker
619
+ async with self._circuit_breaker_lock:
620
+ await self._check_circuit_breaker(
621
+ "get_overdue_liveness_registrations", corr_id
622
+ )
623
+
624
+ query_sql = """
625
+ SELECT * FROM registration_projections
626
+ WHERE domain = $1
627
+ AND liveness_deadline < $2
628
+ AND liveness_timeout_emitted_at IS NULL
629
+ AND current_state = $3
630
+ ORDER BY liveness_deadline ASC
631
+ LIMIT $4
632
+ """
633
+
634
+ try:
635
+ async with self._pool.acquire() as conn:
636
+ rows = await conn.fetch(
637
+ query_sql,
638
+ domain,
639
+ now,
640
+ EnumRegistrationState.ACTIVE.value,
641
+ limit,
642
+ )
643
+
644
+ async with self._circuit_breaker_lock:
645
+ await self._reset_circuit_breaker()
646
+
647
+ return [self._row_to_projection(row) for row in rows]
648
+
649
+ except asyncpg.PostgresConnectionError as e:
650
+ async with self._circuit_breaker_lock:
651
+ await self._record_circuit_failure(
652
+ "get_overdue_liveness_registrations", corr_id
653
+ )
654
+ raise InfraConnectionError(
655
+ "Failed to connect to database for overdue liveness query",
656
+ context=ctx,
657
+ ) from e
658
+
659
+ except asyncpg.QueryCanceledError as e:
660
+ async with self._circuit_breaker_lock:
661
+ await self._record_circuit_failure(
662
+ "get_overdue_liveness_registrations", corr_id
663
+ )
664
+ raise InfraTimeoutError(
665
+ "Overdue liveness registrations query timed out",
666
+ context=ModelTimeoutErrorContext(
667
+ transport_type=ctx.transport_type,
668
+ operation=ctx.operation,
669
+ target_name=ctx.target_name,
670
+ correlation_id=ctx.correlation_id,
671
+ # timeout_seconds omitted - value not available in this context (defaults to None)
672
+ ),
673
+ ) from e
674
+
675
+ except Exception as e:
676
+ async with self._circuit_breaker_lock:
677
+ await self._record_circuit_failure(
678
+ "get_overdue_liveness_registrations", corr_id
679
+ )
680
+ raise RuntimeHostError(
681
+ f"Failed to query overdue liveness registrations: {type(e).__name__}",
682
+ context=ctx,
683
+ ) from e
684
+
685
+ async def count_by_state(
686
+ self,
687
+ domain: str = "registration",
688
+ correlation_id: UUID | None = None,
689
+ ) -> dict[EnumRegistrationState, int]:
690
+ """Count projections by state.
691
+
692
+ Aggregates projection counts for each FSM state.
693
+ Useful for monitoring and dashboard metrics.
694
+
695
+ Args:
696
+ domain: Domain namespace (default: "registration")
697
+ correlation_id: Optional correlation ID for tracing
698
+
699
+ Returns:
700
+ Dict mapping state to count
701
+
702
+ Example:
703
+ >>> counts = await reader.count_by_state()
704
+ >>> print(f"Active nodes: {counts.get(EnumRegistrationState.ACTIVE, 0)}")
705
+ """
706
+ corr_id = correlation_id or uuid4()
707
+ ctx = ModelInfraErrorContext(
708
+ transport_type=EnumInfraTransportType.DATABASE,
709
+ operation="count_by_state",
710
+ target_name="projection_reader.registration",
711
+ correlation_id=corr_id,
712
+ )
713
+
714
+ # Check circuit breaker
715
+ async with self._circuit_breaker_lock:
716
+ await self._check_circuit_breaker("count_by_state", corr_id)
717
+
718
+ query_sql = """
719
+ SELECT current_state, COUNT(*) as count
720
+ FROM registration_projections
721
+ WHERE domain = $1
722
+ GROUP BY current_state
723
+ """
724
+
725
+ try:
726
+ async with self._pool.acquire() as conn:
727
+ rows = await conn.fetch(query_sql, domain)
728
+
729
+ async with self._circuit_breaker_lock:
730
+ await self._reset_circuit_breaker()
731
+
732
+ result: dict[EnumRegistrationState, int] = {}
733
+ for row in rows:
734
+ state = EnumRegistrationState(row["current_state"])
735
+ result[state] = row["count"]
736
+
737
+ return result
738
+
739
+ except asyncpg.PostgresConnectionError as e:
740
+ async with self._circuit_breaker_lock:
741
+ await self._record_circuit_failure("count_by_state", corr_id)
742
+ raise InfraConnectionError(
743
+ "Failed to connect to database for state count",
744
+ context=ctx,
745
+ ) from e
746
+
747
+ except asyncpg.QueryCanceledError as e:
748
+ async with self._circuit_breaker_lock:
749
+ await self._record_circuit_failure("count_by_state", corr_id)
750
+ raise InfraTimeoutError(
751
+ "State count query timed out",
752
+ context=ModelTimeoutErrorContext(
753
+ transport_type=ctx.transport_type,
754
+ operation=ctx.operation,
755
+ target_name=ctx.target_name,
756
+ correlation_id=ctx.correlation_id,
757
+ # timeout_seconds omitted - value not available in this context (defaults to None)
758
+ ),
759
+ ) from e
760
+
761
+ except Exception as e:
762
+ async with self._circuit_breaker_lock:
763
+ await self._record_circuit_failure("count_by_state", corr_id)
764
+ raise RuntimeHostError(
765
+ f"Failed to count by state: {type(e).__name__}",
766
+ context=ctx,
767
+ ) from e
768
+
769
+ # ============================================================
770
+ # Capability Query Methods (OMN-1134)
771
+ # ============================================================
772
+
773
+ async def get_by_capability_tag(
774
+ self,
775
+ tag: str,
776
+ state: EnumRegistrationState | None = None,
777
+ domain: str = "registration",
778
+ limit: int = 100,
779
+ correlation_id: UUID | None = None,
780
+ ) -> list[ModelRegistrationProjection]:
781
+ """Find all registrations with the specified capability tag.
782
+
783
+ Uses GIN index on capability_tags column for efficient lookup.
784
+
785
+ Args:
786
+ tag: The capability tag to search for (e.g., "postgres.storage")
787
+ state: Optional state filter (e.g., EnumRegistrationState.ACTIVE)
788
+ domain: Domain namespace (default: "registration")
789
+ limit: Maximum results to return (default: 100)
790
+ correlation_id: Optional correlation ID for tracing
791
+
792
+ Returns:
793
+ List of matching registration projections
794
+
795
+ Raises:
796
+ InfraConnectionError: If database connection fails
797
+ InfraTimeoutError: If query times out
798
+ RuntimeHostError: For other database errors
799
+
800
+ Example:
801
+ >>> adapters = await reader.get_by_capability_tag("kafka.consumer")
802
+ >>> for adapter in adapters:
803
+ ... print(f"{adapter.entity_id}: {adapter.node_type}")
804
+ >>> # Filter by state
805
+ >>> active = await reader.get_by_capability_tag(
806
+ ... "postgres.storage",
807
+ ... state=EnumRegistrationState.ACTIVE,
808
+ ... )
809
+ """
810
+ corr_id = correlation_id or uuid4()
811
+ ctx = ModelInfraErrorContext(
812
+ transport_type=EnumInfraTransportType.DATABASE,
813
+ operation="get_by_capability_tag",
814
+ target_name="projection_reader.registration",
815
+ correlation_id=corr_id,
816
+ )
817
+
818
+ async with self._circuit_breaker_lock:
819
+ await self._check_circuit_breaker("get_by_capability_tag", corr_id)
820
+
821
+ # Build query with optional state filter
822
+ if state is not None:
823
+ query_sql = """
824
+ SELECT * FROM registration_projections
825
+ WHERE domain = $1
826
+ AND capability_tags @> $2::text[]
827
+ AND current_state = $3
828
+ ORDER BY updated_at DESC
829
+ LIMIT $4
830
+ """
831
+ params = [domain, [tag], state.value, limit]
832
+ else:
833
+ query_sql = """
834
+ SELECT * FROM registration_projections
835
+ WHERE domain = $1
836
+ AND capability_tags @> $2::text[]
837
+ ORDER BY updated_at DESC
838
+ LIMIT $3
839
+ """
840
+ params = [domain, [tag], limit]
841
+
842
+ try:
843
+ async with self._pool.acquire() as conn:
844
+ rows = await conn.fetch(query_sql, *params)
845
+
846
+ async with self._circuit_breaker_lock:
847
+ await self._reset_circuit_breaker()
848
+
849
+ return [self._row_to_projection(row) for row in rows]
850
+
851
+ except asyncpg.PostgresConnectionError as e:
852
+ async with self._circuit_breaker_lock:
853
+ await self._record_circuit_failure("get_by_capability_tag", corr_id)
854
+ raise InfraConnectionError(
855
+ "Failed to connect to database for capability tag query",
856
+ context=ctx,
857
+ ) from e
858
+
859
+ except asyncpg.QueryCanceledError as e:
860
+ async with self._circuit_breaker_lock:
861
+ await self._record_circuit_failure("get_by_capability_tag", corr_id)
862
+ raise InfraTimeoutError(
863
+ "Capability tag query timed out",
864
+ context=ModelTimeoutErrorContext(
865
+ transport_type=ctx.transport_type,
866
+ operation=ctx.operation,
867
+ target_name=ctx.target_name,
868
+ correlation_id=ctx.correlation_id,
869
+ # timeout_seconds omitted - value not available in this context (defaults to None)
870
+ ),
871
+ ) from e
872
+
873
+ except Exception as e:
874
+ async with self._circuit_breaker_lock:
875
+ await self._record_circuit_failure("get_by_capability_tag", corr_id)
876
+ raise RuntimeHostError(
877
+ f"Failed to query by capability tag: {type(e).__name__}",
878
+ context=ctx,
879
+ ) from e
880
+
881
+ async def get_by_intent_type(
882
+ self,
883
+ intent_type: str,
884
+ state: EnumRegistrationState | None = None,
885
+ domain: str = "registration",
886
+ limit: int = 100,
887
+ correlation_id: UUID | None = None,
888
+ ) -> list[ModelRegistrationProjection]:
889
+ """Find all registrations that handle the specified intent type.
890
+
891
+ Uses GIN index on intent_types column for efficient lookup.
892
+
893
+ Args:
894
+ intent_type: The intent type to search for (e.g., "postgres.upsert")
895
+ state: Optional state filter (e.g., EnumRegistrationState.ACTIVE)
896
+ domain: Domain namespace (default: "registration")
897
+ limit: Maximum results to return (default: 100)
898
+ correlation_id: Optional correlation ID for tracing
899
+
900
+ Returns:
901
+ List of matching registration projections
902
+
903
+ Raises:
904
+ InfraConnectionError: If database connection fails
905
+ InfraTimeoutError: If query times out
906
+ RuntimeHostError: For other database errors
907
+
908
+ Example:
909
+ >>> handlers = await reader.get_by_intent_type("postgres.query")
910
+ >>> for handler in handlers:
911
+ ... print(f"Can handle postgres.query: {handler.entity_id}")
912
+ """
913
+ corr_id = correlation_id or uuid4()
914
+ ctx = ModelInfraErrorContext(
915
+ transport_type=EnumInfraTransportType.DATABASE,
916
+ operation="get_by_intent_type",
917
+ target_name="projection_reader.registration",
918
+ correlation_id=corr_id,
919
+ )
920
+
921
+ async with self._circuit_breaker_lock:
922
+ await self._check_circuit_breaker("get_by_intent_type", corr_id)
923
+
924
+ if state is not None:
925
+ query_sql = """
926
+ SELECT * FROM registration_projections
927
+ WHERE domain = $1
928
+ AND intent_types @> $2::text[]
929
+ AND current_state = $3
930
+ ORDER BY updated_at DESC
931
+ LIMIT $4
932
+ """
933
+ params = [domain, [intent_type], state.value, limit]
934
+ else:
935
+ query_sql = """
936
+ SELECT * FROM registration_projections
937
+ WHERE domain = $1
938
+ AND intent_types @> $2::text[]
939
+ ORDER BY updated_at DESC
940
+ LIMIT $3
941
+ """
942
+ params = [domain, [intent_type], limit]
943
+
944
+ try:
945
+ async with self._pool.acquire() as conn:
946
+ rows = await conn.fetch(query_sql, *params)
947
+
948
+ async with self._circuit_breaker_lock:
949
+ await self._reset_circuit_breaker()
950
+
951
+ return [self._row_to_projection(row) for row in rows]
952
+
953
+ except asyncpg.PostgresConnectionError as e:
954
+ async with self._circuit_breaker_lock:
955
+ await self._record_circuit_failure("get_by_intent_type", corr_id)
956
+ raise InfraConnectionError(
957
+ "Failed to connect to database for intent type query",
958
+ context=ctx,
959
+ ) from e
960
+
961
+ except asyncpg.QueryCanceledError as e:
962
+ async with self._circuit_breaker_lock:
963
+ await self._record_circuit_failure("get_by_intent_type", corr_id)
964
+ raise InfraTimeoutError(
965
+ "Intent type query timed out",
966
+ context=ModelTimeoutErrorContext(
967
+ transport_type=ctx.transport_type,
968
+ operation=ctx.operation,
969
+ target_name=ctx.target_name,
970
+ correlation_id=ctx.correlation_id,
971
+ # timeout_seconds omitted - value not available in this context (defaults to None)
972
+ ),
973
+ ) from e
974
+
975
+ except Exception as e:
976
+ async with self._circuit_breaker_lock:
977
+ await self._record_circuit_failure("get_by_intent_type", corr_id)
978
+ raise RuntimeHostError(
979
+ f"Failed to query by intent type: {type(e).__name__}",
980
+ context=ctx,
981
+ ) from e
982
+
983
+ async def get_by_intent_types(
984
+ self,
985
+ intent_types: list[str],
986
+ state: EnumRegistrationState | None = None,
987
+ domain: str = "registration",
988
+ limit: int = 100,
989
+ correlation_id: UUID | None = None,
990
+ ) -> list[ModelRegistrationProjection]:
991
+ """Find all registrations that handle ANY of the specified intent types.
992
+
993
+ Bulk query method that retrieves nodes matching any intent type in a single
994
+ database call using the && (array overlap) operator. This is more efficient
995
+ than calling get_by_intent_type repeatedly for each intent type.
996
+
997
+ Performance Note:
998
+ This method reduces N database queries to 1 query when resolving
999
+ dependencies with multiple intent types. For N intent types, the
1000
+ previous approach required N separate database calls; this method
1001
+ uses a single query with SQL array overlap.
1002
+
1003
+ Args:
1004
+ intent_types: List of intent types to search for (e.g.,
1005
+ ["postgres.upsert", "postgres.query", "postgres.delete"]).
1006
+ At least one must be present in the node's intent_types.
1007
+ state: Optional state filter (e.g., EnumRegistrationState.ACTIVE)
1008
+ domain: Domain namespace (default: "registration")
1009
+ limit: Maximum results to return (default: 100)
1010
+ correlation_id: Optional correlation ID for tracing
1011
+
1012
+ Returns:
1013
+ List of matching registration projections (deduplicated by entity_id)
1014
+
1015
+ Raises:
1016
+ ProtocolConfigurationError: If intent_types list is empty
1017
+ InfraConnectionError: If database connection fails
1018
+ InfraTimeoutError: If query times out
1019
+ RuntimeHostError: For other database errors
1020
+
1021
+ Example:
1022
+ >>> # Find nodes that handle any postgres intent
1023
+ >>> handlers = await reader.get_by_intent_types(
1024
+ ... ["postgres.query", "postgres.upsert", "postgres.delete"],
1025
+ ... state=EnumRegistrationState.ACTIVE,
1026
+ ... )
1027
+ >>> for handler in handlers:
1028
+ ... print(f"Can handle postgres intents: {handler.entity_id}")
1029
+ """
1030
+ if not intent_types:
1031
+ raise ProtocolConfigurationError(
1032
+ "intent_types list cannot be empty for get_by_intent_types - "
1033
+ "use get_by_state() to query all registrations",
1034
+ context=ModelInfraErrorContext(
1035
+ transport_type=EnumInfraTransportType.DATABASE,
1036
+ operation="get_by_intent_types",
1037
+ target_name="projection_reader.registration",
1038
+ correlation_id=correlation_id or uuid4(),
1039
+ ),
1040
+ )
1041
+
1042
+ corr_id = correlation_id or uuid4()
1043
+ ctx = ModelInfraErrorContext(
1044
+ transport_type=EnumInfraTransportType.DATABASE,
1045
+ operation="get_by_intent_types",
1046
+ target_name="projection_reader.registration",
1047
+ correlation_id=corr_id,
1048
+ )
1049
+
1050
+ async with self._circuit_breaker_lock:
1051
+ await self._check_circuit_breaker("get_by_intent_types", corr_id)
1052
+
1053
+ # Use && (array overlap) operator to find nodes matching ANY intent type
1054
+ if state is not None:
1055
+ query_sql = """
1056
+ SELECT * FROM registration_projections
1057
+ WHERE domain = $1
1058
+ AND intent_types && $2::text[]
1059
+ AND current_state = $3
1060
+ ORDER BY updated_at DESC
1061
+ LIMIT $4
1062
+ """
1063
+ params = [domain, intent_types, state.value, limit]
1064
+ else:
1065
+ query_sql = """
1066
+ SELECT * FROM registration_projections
1067
+ WHERE domain = $1
1068
+ AND intent_types && $2::text[]
1069
+ ORDER BY updated_at DESC
1070
+ LIMIT $3
1071
+ """
1072
+ params = [domain, intent_types, limit]
1073
+
1074
+ try:
1075
+ async with self._pool.acquire() as conn:
1076
+ rows = await conn.fetch(query_sql, *params)
1077
+
1078
+ async with self._circuit_breaker_lock:
1079
+ await self._reset_circuit_breaker()
1080
+
1081
+ return [self._row_to_projection(row) for row in rows]
1082
+
1083
+ except asyncpg.PostgresConnectionError as e:
1084
+ async with self._circuit_breaker_lock:
1085
+ await self._record_circuit_failure("get_by_intent_types", corr_id)
1086
+ raise InfraConnectionError(
1087
+ "Failed to connect to database for intent types query",
1088
+ context=ctx,
1089
+ ) from e
1090
+
1091
+ except asyncpg.QueryCanceledError as e:
1092
+ async with self._circuit_breaker_lock:
1093
+ await self._record_circuit_failure("get_by_intent_types", corr_id)
1094
+ raise InfraTimeoutError(
1095
+ "Intent types query timed out",
1096
+ context=ModelTimeoutErrorContext(
1097
+ transport_type=ctx.transport_type,
1098
+ operation=ctx.operation,
1099
+ target_name=ctx.target_name,
1100
+ correlation_id=ctx.correlation_id,
1101
+ # timeout_seconds omitted - value not available in this context (defaults to None)
1102
+ ),
1103
+ ) from e
1104
+
1105
+ except Exception as e:
1106
+ async with self._circuit_breaker_lock:
1107
+ await self._record_circuit_failure("get_by_intent_types", corr_id)
1108
+ raise RuntimeHostError(
1109
+ f"Failed to query by intent types: {type(e).__name__}",
1110
+ context=ctx,
1111
+ ) from e
1112
+
1113
+ async def get_by_protocol(
1114
+ self,
1115
+ protocol_name: str,
1116
+ state: EnumRegistrationState | None = None,
1117
+ domain: str = "registration",
1118
+ limit: int = 100,
1119
+ correlation_id: UUID | None = None,
1120
+ ) -> list[ModelRegistrationProjection]:
1121
+ """Find all registrations implementing the specified protocol.
1122
+
1123
+ Uses GIN index on protocols column for efficient lookup.
1124
+
1125
+ Args:
1126
+ protocol_name: The protocol name (e.g., "ProtocolDatabaseAdapter")
1127
+ state: Optional state filter (e.g., EnumRegistrationState.ACTIVE)
1128
+ domain: Domain namespace (default: "registration")
1129
+ limit: Maximum results to return (default: 100)
1130
+ correlation_id: Optional correlation ID for tracing
1131
+
1132
+ Returns:
1133
+ List of matching registration projections
1134
+
1135
+ Raises:
1136
+ InfraConnectionError: If database connection fails
1137
+ InfraTimeoutError: If query times out
1138
+ RuntimeHostError: For other database errors
1139
+
1140
+ Example:
1141
+ >>> adapters = await reader.get_by_protocol("ProtocolEventPublisher")
1142
+ >>> print(f"Found {len(adapters)} event publishers")
1143
+ """
1144
+ corr_id = correlation_id or uuid4()
1145
+ ctx = ModelInfraErrorContext(
1146
+ transport_type=EnumInfraTransportType.DATABASE,
1147
+ operation="get_by_protocol",
1148
+ target_name="projection_reader.registration",
1149
+ correlation_id=corr_id,
1150
+ )
1151
+
1152
+ async with self._circuit_breaker_lock:
1153
+ await self._check_circuit_breaker("get_by_protocol", corr_id)
1154
+
1155
+ if state is not None:
1156
+ query_sql = """
1157
+ SELECT * FROM registration_projections
1158
+ WHERE domain = $1
1159
+ AND protocols @> $2::text[]
1160
+ AND current_state = $3
1161
+ ORDER BY updated_at DESC
1162
+ LIMIT $4
1163
+ """
1164
+ params = [domain, [protocol_name], state.value, limit]
1165
+ else:
1166
+ query_sql = """
1167
+ SELECT * FROM registration_projections
1168
+ WHERE domain = $1
1169
+ AND protocols @> $2::text[]
1170
+ ORDER BY updated_at DESC
1171
+ LIMIT $3
1172
+ """
1173
+ params = [domain, [protocol_name], limit]
1174
+
1175
+ try:
1176
+ async with self._pool.acquire() as conn:
1177
+ rows = await conn.fetch(query_sql, *params)
1178
+
1179
+ async with self._circuit_breaker_lock:
1180
+ await self._reset_circuit_breaker()
1181
+
1182
+ return [self._row_to_projection(row) for row in rows]
1183
+
1184
+ except asyncpg.PostgresConnectionError as e:
1185
+ async with self._circuit_breaker_lock:
1186
+ await self._record_circuit_failure("get_by_protocol", corr_id)
1187
+ raise InfraConnectionError(
1188
+ "Failed to connect to database for protocol query",
1189
+ context=ctx,
1190
+ ) from e
1191
+
1192
+ except asyncpg.QueryCanceledError as e:
1193
+ async with self._circuit_breaker_lock:
1194
+ await self._record_circuit_failure("get_by_protocol", corr_id)
1195
+ raise InfraTimeoutError(
1196
+ "Protocol query timed out",
1197
+ context=ModelTimeoutErrorContext(
1198
+ transport_type=ctx.transport_type,
1199
+ operation=ctx.operation,
1200
+ target_name=ctx.target_name,
1201
+ correlation_id=ctx.correlation_id,
1202
+ # timeout_seconds omitted - value not available in this context (defaults to None)
1203
+ ),
1204
+ ) from e
1205
+
1206
+ except Exception as e:
1207
+ async with self._circuit_breaker_lock:
1208
+ await self._record_circuit_failure("get_by_protocol", corr_id)
1209
+ raise RuntimeHostError(
1210
+ f"Failed to query by protocol: {type(e).__name__}",
1211
+ context=ctx,
1212
+ ) from e
1213
+
1214
+ async def get_by_contract_type(
1215
+ self,
1216
+ contract_type: ContractType,
1217
+ state: EnumRegistrationState | None = None,
1218
+ domain: str = "registration",
1219
+ limit: int = 100,
1220
+ correlation_id: UUID | None = None,
1221
+ ) -> list[ModelRegistrationProjection]:
1222
+ """Find all registrations of the specified contract type.
1223
+
1224
+ Uses B-tree index on contract_type column for efficient lookup.
1225
+
1226
+ Args:
1227
+ contract_type: The contract type. Must be one of: "effect", "compute",
1228
+ "reducer", or "orchestrator"
1229
+ state: Optional state filter (e.g., EnumRegistrationState.ACTIVE)
1230
+ domain: Domain namespace (default: "registration")
1231
+ limit: Maximum results to return (default: 100)
1232
+ correlation_id: Optional correlation ID for tracing
1233
+
1234
+ Returns:
1235
+ List of matching registration projections
1236
+
1237
+ Raises:
1238
+ InfraConnectionError: If database connection fails
1239
+ InfraTimeoutError: If query times out
1240
+ RuntimeHostError: For other database errors
1241
+
1242
+ Example:
1243
+ >>> effects = await reader.get_by_contract_type("effect")
1244
+ >>> print(f"Found {len(effects)} effect nodes")
1245
+ """
1246
+ corr_id = correlation_id or uuid4()
1247
+ ctx = ModelInfraErrorContext(
1248
+ transport_type=EnumInfraTransportType.DATABASE,
1249
+ operation="get_by_contract_type",
1250
+ target_name="projection_reader.registration",
1251
+ correlation_id=corr_id,
1252
+ )
1253
+
1254
+ async with self._circuit_breaker_lock:
1255
+ await self._check_circuit_breaker("get_by_contract_type", corr_id)
1256
+
1257
+ if state is not None:
1258
+ query_sql = """
1259
+ SELECT * FROM registration_projections
1260
+ WHERE domain = $1
1261
+ AND contract_type = $2
1262
+ AND current_state = $3
1263
+ ORDER BY updated_at DESC
1264
+ LIMIT $4
1265
+ """
1266
+ params = [domain, contract_type, state.value, limit]
1267
+ else:
1268
+ query_sql = """
1269
+ SELECT * FROM registration_projections
1270
+ WHERE domain = $1
1271
+ AND contract_type = $2
1272
+ ORDER BY updated_at DESC
1273
+ LIMIT $3
1274
+ """
1275
+ params = [domain, contract_type, limit]
1276
+
1277
+ try:
1278
+ async with self._pool.acquire() as conn:
1279
+ rows = await conn.fetch(query_sql, *params)
1280
+
1281
+ async with self._circuit_breaker_lock:
1282
+ await self._reset_circuit_breaker()
1283
+
1284
+ return [self._row_to_projection(row) for row in rows]
1285
+
1286
+ except asyncpg.PostgresConnectionError as e:
1287
+ async with self._circuit_breaker_lock:
1288
+ await self._record_circuit_failure("get_by_contract_type", corr_id)
1289
+ raise InfraConnectionError(
1290
+ "Failed to connect to database for contract type query",
1291
+ context=ctx,
1292
+ ) from e
1293
+
1294
+ except asyncpg.QueryCanceledError as e:
1295
+ async with self._circuit_breaker_lock:
1296
+ await self._record_circuit_failure("get_by_contract_type", corr_id)
1297
+ raise InfraTimeoutError(
1298
+ "Contract type query timed out",
1299
+ context=ModelTimeoutErrorContext(
1300
+ transport_type=ctx.transport_type,
1301
+ operation=ctx.operation,
1302
+ target_name=ctx.target_name,
1303
+ correlation_id=ctx.correlation_id,
1304
+ # timeout_seconds omitted - value not available in this context (defaults to None)
1305
+ ),
1306
+ ) from e
1307
+
1308
+ except Exception as e:
1309
+ async with self._circuit_breaker_lock:
1310
+ await self._record_circuit_failure("get_by_contract_type", corr_id)
1311
+ raise RuntimeHostError(
1312
+ f"Failed to query by contract type: {type(e).__name__}",
1313
+ context=ctx,
1314
+ ) from e
1315
+
1316
+ async def get_by_capability_tags_all(
1317
+ self,
1318
+ tags: list[str],
1319
+ state: EnumRegistrationState | None = None,
1320
+ domain: str = "registration",
1321
+ limit: int = 100,
1322
+ correlation_id: UUID | None = None,
1323
+ ) -> list[ModelRegistrationProjection]:
1324
+ """Find registrations with ALL specified capability tags.
1325
+
1326
+ Uses GIN index with @> (contains all) operator.
1327
+
1328
+ Args:
1329
+ tags: List of capability tags that must all be present
1330
+ state: Optional state filter (e.g., EnumRegistrationState.ACTIVE)
1331
+ domain: Domain namespace (default: "registration")
1332
+ limit: Maximum results to return (default: 100)
1333
+ correlation_id: Optional correlation ID for tracing
1334
+
1335
+ Returns:
1336
+ List of matching registration projections
1337
+
1338
+ Raises:
1339
+ ProtocolConfigurationError: If tags list is empty
1340
+ InfraConnectionError: If database connection fails
1341
+ InfraTimeoutError: If query times out
1342
+ RuntimeHostError: For other database errors
1343
+
1344
+ Example:
1345
+ >>> adapters = await reader.get_by_capability_tags_all(
1346
+ ... ["postgres.storage", "transactions"]
1347
+ ... )
1348
+ """
1349
+ if not tags:
1350
+ raise ProtocolConfigurationError(
1351
+ "tags list cannot be empty for get_by_capability_tags_all - "
1352
+ "use get_by_state() to query all registrations",
1353
+ context=ModelInfraErrorContext(
1354
+ transport_type=EnumInfraTransportType.DATABASE,
1355
+ operation="get_by_capability_tags_all",
1356
+ target_name="projection_reader.registration",
1357
+ correlation_id=correlation_id or uuid4(),
1358
+ ),
1359
+ )
1360
+
1361
+ corr_id = correlation_id or uuid4()
1362
+ ctx = ModelInfraErrorContext(
1363
+ transport_type=EnumInfraTransportType.DATABASE,
1364
+ operation="get_by_capability_tags_all",
1365
+ target_name="projection_reader.registration",
1366
+ correlation_id=corr_id,
1367
+ )
1368
+
1369
+ async with self._circuit_breaker_lock:
1370
+ await self._check_circuit_breaker("get_by_capability_tags_all", corr_id)
1371
+
1372
+ if state is not None:
1373
+ query_sql = """
1374
+ SELECT * FROM registration_projections
1375
+ WHERE domain = $1
1376
+ AND capability_tags @> $2::text[]
1377
+ AND current_state = $3
1378
+ ORDER BY updated_at DESC
1379
+ LIMIT $4
1380
+ """
1381
+ params = [domain, tags, state.value, limit]
1382
+ else:
1383
+ query_sql = """
1384
+ SELECT * FROM registration_projections
1385
+ WHERE domain = $1
1386
+ AND capability_tags @> $2::text[]
1387
+ ORDER BY updated_at DESC
1388
+ LIMIT $3
1389
+ """
1390
+ params = [domain, tags, limit]
1391
+
1392
+ try:
1393
+ async with self._pool.acquire() as conn:
1394
+ rows = await conn.fetch(query_sql, *params)
1395
+
1396
+ async with self._circuit_breaker_lock:
1397
+ await self._reset_circuit_breaker()
1398
+
1399
+ return [self._row_to_projection(row) for row in rows]
1400
+
1401
+ except asyncpg.PostgresConnectionError as e:
1402
+ async with self._circuit_breaker_lock:
1403
+ await self._record_circuit_failure(
1404
+ "get_by_capability_tags_all", corr_id
1405
+ )
1406
+ raise InfraConnectionError(
1407
+ "Failed to connect to database for capability tags query",
1408
+ context=ctx,
1409
+ ) from e
1410
+
1411
+ except asyncpg.QueryCanceledError as e:
1412
+ async with self._circuit_breaker_lock:
1413
+ await self._record_circuit_failure(
1414
+ "get_by_capability_tags_all", corr_id
1415
+ )
1416
+ raise InfraTimeoutError(
1417
+ "Capability tags query timed out",
1418
+ context=ModelTimeoutErrorContext(
1419
+ transport_type=ctx.transport_type,
1420
+ operation=ctx.operation,
1421
+ target_name=ctx.target_name,
1422
+ correlation_id=ctx.correlation_id,
1423
+ # timeout_seconds omitted - value not available in this context (defaults to None)
1424
+ ),
1425
+ ) from e
1426
+
1427
+ except Exception as e:
1428
+ async with self._circuit_breaker_lock:
1429
+ await self._record_circuit_failure(
1430
+ "get_by_capability_tags_all", corr_id
1431
+ )
1432
+ raise RuntimeHostError(
1433
+ f"Failed to query by capability tags: {type(e).__name__}",
1434
+ context=ctx,
1435
+ ) from e
1436
+
1437
+ async def get_by_capability_tags_any(
1438
+ self,
1439
+ tags: list[str],
1440
+ state: EnumRegistrationState | None = None,
1441
+ domain: str = "registration",
1442
+ limit: int = 100,
1443
+ correlation_id: UUID | None = None,
1444
+ ) -> list[ModelRegistrationProjection]:
1445
+ """Find registrations with ANY of the specified capability tags.
1446
+
1447
+ Uses GIN index with && (overlaps) operator.
1448
+
1449
+ Args:
1450
+ tags: List of capability tags, at least one must be present
1451
+ state: Optional state filter (e.g., EnumRegistrationState.ACTIVE)
1452
+ domain: Domain namespace (default: "registration")
1453
+ limit: Maximum results to return (default: 100)
1454
+ correlation_id: Optional correlation ID for tracing
1455
+
1456
+ Returns:
1457
+ List of matching registration projections
1458
+
1459
+ Raises:
1460
+ ProtocolConfigurationError: If tags list is empty
1461
+ InfraConnectionError: If database connection fails
1462
+ InfraTimeoutError: If query times out
1463
+ RuntimeHostError: For other database errors
1464
+
1465
+ Example:
1466
+ >>> adapters = await reader.get_by_capability_tags_any(
1467
+ ... ["postgres.storage", "mysql.storage", "sqlite.storage"]
1468
+ ... )
1469
+ """
1470
+ if not tags:
1471
+ raise ProtocolConfigurationError(
1472
+ "tags list cannot be empty for get_by_capability_tags_any - "
1473
+ "use get_by_state() to query all registrations",
1474
+ context=ModelInfraErrorContext(
1475
+ transport_type=EnumInfraTransportType.DATABASE,
1476
+ operation="get_by_capability_tags_any",
1477
+ target_name="projection_reader.registration",
1478
+ correlation_id=correlation_id or uuid4(),
1479
+ ),
1480
+ )
1481
+
1482
+ corr_id = correlation_id or uuid4()
1483
+ ctx = ModelInfraErrorContext(
1484
+ transport_type=EnumInfraTransportType.DATABASE,
1485
+ operation="get_by_capability_tags_any",
1486
+ target_name="projection_reader.registration",
1487
+ correlation_id=corr_id,
1488
+ )
1489
+
1490
+ async with self._circuit_breaker_lock:
1491
+ await self._check_circuit_breaker("get_by_capability_tags_any", corr_id)
1492
+
1493
+ if state is not None:
1494
+ query_sql = """
1495
+ SELECT * FROM registration_projections
1496
+ WHERE domain = $1
1497
+ AND capability_tags && $2::text[]
1498
+ AND current_state = $3
1499
+ ORDER BY updated_at DESC
1500
+ LIMIT $4
1501
+ """
1502
+ params = [domain, tags, state.value, limit]
1503
+ else:
1504
+ query_sql = """
1505
+ SELECT * FROM registration_projections
1506
+ WHERE domain = $1
1507
+ AND capability_tags && $2::text[]
1508
+ ORDER BY updated_at DESC
1509
+ LIMIT $3
1510
+ """
1511
+ params = [domain, tags, limit]
1512
+
1513
+ try:
1514
+ async with self._pool.acquire() as conn:
1515
+ rows = await conn.fetch(query_sql, *params)
1516
+
1517
+ async with self._circuit_breaker_lock:
1518
+ await self._reset_circuit_breaker()
1519
+
1520
+ return [self._row_to_projection(row) for row in rows]
1521
+
1522
+ except asyncpg.PostgresConnectionError as e:
1523
+ async with self._circuit_breaker_lock:
1524
+ await self._record_circuit_failure(
1525
+ "get_by_capability_tags_any", corr_id
1526
+ )
1527
+ raise InfraConnectionError(
1528
+ "Failed to connect to database for capability tags query",
1529
+ context=ctx,
1530
+ ) from e
1531
+
1532
+ except asyncpg.QueryCanceledError as e:
1533
+ async with self._circuit_breaker_lock:
1534
+ await self._record_circuit_failure(
1535
+ "get_by_capability_tags_any", corr_id
1536
+ )
1537
+ raise InfraTimeoutError(
1538
+ "Capability tags query timed out",
1539
+ context=ModelTimeoutErrorContext(
1540
+ transport_type=ctx.transport_type,
1541
+ operation=ctx.operation,
1542
+ target_name=ctx.target_name,
1543
+ correlation_id=ctx.correlation_id,
1544
+ # timeout_seconds omitted - value not available in this context (defaults to None)
1545
+ ),
1546
+ ) from e
1547
+
1548
+ except Exception as e:
1549
+ async with self._circuit_breaker_lock:
1550
+ await self._record_circuit_failure(
1551
+ "get_by_capability_tags_any", corr_id
1552
+ )
1553
+ raise RuntimeHostError(
1554
+ f"Failed to query by capability tags: {type(e).__name__}",
1555
+ context=ctx,
1556
+ ) from e
1557
+
1558
+
1559
+ __all__: list[str] = ["ProjectionReaderRegistration"]