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,923 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ # ruff: noqa: S608
4
+ # S608 disabled: All SQL f-strings use table_name which is validated via:
5
+ # 1. Pydantic regex pattern ^[a-zA-Z_][a-zA-Z0-9_]*$ in ModelPostgresIdempotencyStoreConfig
6
+ # 2. Runtime validation in _validate_table_name() (defense-in-depth)
7
+ # This defense-in-depth approach ensures only valid PostgreSQL identifiers are used,
8
+ # preventing SQL injection even if config validation is somehow bypassed.
9
+ """PostgreSQL-based Idempotency Store Implementation.
10
+
11
+ This module provides a PostgreSQL-based implementation of the
12
+ ProtocolIdempotencyStore protocol for tracking processed messages
13
+ and preventing duplicate processing in distributed systems.
14
+
15
+ The store uses atomic INSERT ... ON CONFLICT DO NOTHING for thread-safe
16
+ idempotency checking and asyncpg for async database operations.
17
+
18
+ Table Schema:
19
+ CREATE TABLE IF NOT EXISTS idempotency_records (
20
+ id UUID PRIMARY KEY,
21
+ domain VARCHAR(255),
22
+ message_id UUID NOT NULL,
23
+ correlation_id UUID,
24
+ processed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
25
+ UNIQUE (domain, message_id)
26
+ );
27
+ CREATE INDEX IF NOT EXISTS idx_idempotency_processed_at ON idempotency_records(processed_at);
28
+ CREATE INDEX IF NOT EXISTS idx_idempotency_domain ON idempotency_records(domain);
29
+ CREATE INDEX IF NOT EXISTS idx_idempotency_correlation_id ON idempotency_records(correlation_id)
30
+ WHERE correlation_id IS NOT NULL;
31
+
32
+ Clock Skew Considerations:
33
+ In distributed systems, nodes may have slightly different system clocks.
34
+ This can cause issues with TTL-based cleanup:
35
+
36
+ Problem Scenario:
37
+ - Node A processes message at 10:00:01 (its clock)
38
+ - Node B's clock is 1 second behind (shows 10:00:00)
39
+ - Cleanup runs on Node B using now() - it might delete records
40
+ that Node A considers still valid
41
+
42
+ Solution:
43
+ The store applies a clock_skew_tolerance_seconds buffer (default: 60s)
44
+ to all TTL calculations during cleanup. The effective TTL becomes:
45
+ effective_ttl = ttl_seconds + clock_skew_tolerance_seconds
46
+
47
+ This ensures records are only cleaned up after ALL nodes in the
48
+ distributed system would consider them expired.
49
+
50
+ Production Recommendations:
51
+ 1. Use NTP (Network Time Protocol) on all nodes to minimize clock drift
52
+ 2. Set clock_skew_tolerance_seconds to at least the maximum expected
53
+ clock drift between nodes (default 60s is conservative for NTP-synced systems)
54
+ 3. Monitor NTP synchronization status across your infrastructure
55
+ 4. Consider higher tolerance values (e.g., 300s) for multi-datacenter deployments
56
+
57
+ Security Note:
58
+ - DSN contains credentials - never log the raw value
59
+ - Use parameterized queries to prevent SQL injection
60
+ - Connection pool handles credential management
61
+ - Table names are validated at both config and runtime level (defense-in-depth)
62
+ """
63
+
64
+ from __future__ import annotations
65
+
66
+ import asyncio
67
+ import logging
68
+ import re
69
+ from datetime import UTC, datetime
70
+ from uuid import UUID, uuid4
71
+
72
+ import asyncpg
73
+
74
+ from omnibase_infra.enums import EnumInfraTransportType
75
+ from omnibase_infra.errors import (
76
+ InfraConnectionError,
77
+ InfraTimeoutError,
78
+ ModelInfraErrorContext,
79
+ ModelTimeoutErrorContext,
80
+ ProtocolConfigurationError,
81
+ RuntimeHostError,
82
+ )
83
+ from omnibase_infra.idempotency.models import (
84
+ ModelIdempotencyStoreHealthCheckResult,
85
+ ModelIdempotencyStoreMetrics,
86
+ ModelPostgresIdempotencyStoreConfig,
87
+ )
88
+ from omnibase_infra.idempotency.protocol_idempotency_store import (
89
+ ProtocolIdempotencyStore,
90
+ )
91
+ from omnibase_infra.utils.util_datetime import is_timezone_aware
92
+
93
+ logger = logging.getLogger(__name__)
94
+
95
+ # Regex pattern for valid PostgreSQL table names (defense-in-depth validation)
96
+ # Must start with letter or underscore, followed by letters, digits, or underscores
97
+ _TABLE_NAME_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
98
+
99
+
100
+ class StoreIdempotencyPostgres(ProtocolIdempotencyStore):
101
+ """PostgreSQL-based idempotency store using asyncpg connection pool.
102
+
103
+ This implementation provides exactly-once semantics by using PostgreSQL's
104
+ INSERT ... ON CONFLICT DO NOTHING pattern for atomic check-and-record
105
+ operations.
106
+
107
+ Features:
108
+ - Atomic check_and_record using INSERT ON CONFLICT
109
+ - Connection pooling via asyncpg
110
+ - TTL-based cleanup for expired records
111
+ - Composite key (domain, message_id) for domain-isolated deduplication
112
+ - Full correlation ID support for distributed tracing
113
+ - Defense-in-depth table name validation for SQL injection prevention
114
+
115
+ Security:
116
+ Table names are validated at two levels:
117
+ 1. Pydantic config model: regex pattern on table_name field
118
+ 2. Runtime validation: _validate_table_name() in __init__
119
+
120
+ This defense-in-depth approach ensures SQL injection prevention even
121
+ if the config validation is somehow bypassed (e.g., through direct
122
+ attribute assignment or deserialization from untrusted sources).
123
+
124
+ Concurrency Safety:
125
+ This store is coroutine-safe for asyncio concurrent access. The
126
+ underlying asyncpg pool handles connection management and concurrent
127
+ coroutine access safely. All metrics updates are protected by
128
+ ``_metrics_lock`` (asyncio.Lock) to ensure atomic read-modify-write
129
+ operations for observability counters.
130
+
131
+ Note: This is not thread-safe. For multi-threaded access, additional
132
+ synchronization would be required (e.g., threading.Lock or
133
+ thread-safe connection pooling).
134
+
135
+ Example:
136
+ >>> from uuid import uuid4
137
+ >>> config = ModelPostgresIdempotencyStoreConfig(
138
+ ... dsn="postgresql://user:pass@localhost:5432/mydb",
139
+ ... table_name="idempotency_records",
140
+ ... )
141
+ >>> store = StoreIdempotencyPostgres(config)
142
+ >>> await store.initialize()
143
+ >>> try:
144
+ ... is_new = await store.check_and_record(
145
+ ... message_id=uuid4(),
146
+ ... domain="registration",
147
+ ... )
148
+ ... if is_new:
149
+ ... print("Processing message...")
150
+ ... finally:
151
+ ... await store.shutdown()
152
+ """
153
+
154
+ def __init__(self, config: ModelPostgresIdempotencyStoreConfig) -> None:
155
+ """Initialize the PostgreSQL idempotency store.
156
+
157
+ Args:
158
+ config: Configuration model containing DSN, pool settings, and TTL options.
159
+
160
+ Raises:
161
+ ProtocolConfigurationError: If table_name contains invalid characters
162
+ (defense-in-depth validation for SQL injection prevention).
163
+ """
164
+ self._config = config
165
+ self._pool: asyncpg.Pool | None = None
166
+ self._initialized: bool = False
167
+ self._metrics = ModelIdempotencyStoreMetrics()
168
+ self._metrics_lock = asyncio.Lock()
169
+
170
+ # Defense-in-depth: Validate table name at runtime even though
171
+ # Pydantic config already validates it. This protects against:
172
+ # - Direct attribute assignment bypassing Pydantic validation
173
+ # - Deserialization from untrusted sources
174
+ # - Future code changes that might bypass config validation
175
+ self._validate_table_name(config.table_name)
176
+
177
+ @property
178
+ def is_initialized(self) -> bool:
179
+ """Return True if the store has been initialized."""
180
+ return self._initialized
181
+
182
+ async def get_metrics(self) -> ModelIdempotencyStoreMetrics:
183
+ """Get current store metrics for observability.
184
+
185
+ Returns a copy of the current metrics to prevent external mutation.
186
+ Metrics include:
187
+ - total_checks: Total check_and_record calls
188
+ - duplicate_count: Number of duplicates detected
189
+ - error_count: Number of failed checks
190
+ - duplicate_rate: Ratio of duplicates to total checks
191
+ - error_rate: Ratio of errors to total checks
192
+ - total_cleanup_deleted: Total records cleaned up
193
+ - last_cleanup_deleted: Records deleted in last cleanup
194
+ - last_cleanup_at: Timestamp of last cleanup
195
+
196
+ Concurrency Safety:
197
+ This method acquires ``_metrics_lock`` (asyncio.Lock) to return
198
+ a consistent snapshot. Safe for concurrent coroutine access.
199
+
200
+ Returns:
201
+ Copy of current metrics.
202
+ """
203
+ async with self._metrics_lock:
204
+ return self._metrics.model_copy()
205
+
206
+ def _validate_table_name(self, table_name: str) -> None:
207
+ """Validate table name for SQL injection prevention (defense-in-depth).
208
+
209
+ This method provides runtime validation of the table name pattern,
210
+ complementing the Pydantic field validation in the config model.
211
+ Together they form a defense-in-depth approach to prevent SQL injection.
212
+
213
+ Args:
214
+ table_name: The table name to validate.
215
+
216
+ Raises:
217
+ ProtocolConfigurationError: If table_name doesn't match the
218
+ expected pattern ^[a-zA-Z_][a-zA-Z0-9_]*$
219
+ """
220
+ if not _TABLE_NAME_PATTERN.match(table_name):
221
+ context = ModelInfraErrorContext.with_correlation(
222
+ transport_type=EnumInfraTransportType.DATABASE,
223
+ operation="validate_table_name",
224
+ target_name="postgres_idempotency_store",
225
+ )
226
+ raise ProtocolConfigurationError(
227
+ f"Invalid table name: {table_name}. "
228
+ "Must match pattern ^[a-zA-Z_][a-zA-Z0-9_]*$ "
229
+ "(letters, digits, underscores only, must start with letter or underscore)",
230
+ context=context,
231
+ parameter="table_name",
232
+ value=table_name,
233
+ )
234
+
235
+ async def initialize(self) -> None:
236
+ """Initialize the connection pool and ensure table exists.
237
+
238
+ Creates the asyncpg connection pool and verifies (or creates)
239
+ the idempotency_records table with proper schema.
240
+
241
+ Raises:
242
+ InfraConnectionError: If database connection fails.
243
+ RuntimeHostError: If pool creation or table setup fails.
244
+ """
245
+ if self._initialized:
246
+ return
247
+
248
+ correlation_id = uuid4()
249
+ context = ModelInfraErrorContext(
250
+ transport_type=EnumInfraTransportType.DATABASE,
251
+ operation="initialize",
252
+ target_name="postgres_idempotency_store",
253
+ correlation_id=correlation_id,
254
+ )
255
+
256
+ try:
257
+ self._pool = await asyncpg.create_pool(
258
+ dsn=self._config.dsn,
259
+ min_size=self._config.pool_min_size,
260
+ max_size=self._config.pool_max_size,
261
+ command_timeout=self._config.command_timeout,
262
+ )
263
+
264
+ # Ensure table exists with proper schema
265
+ await self._ensure_table_exists()
266
+
267
+ self._initialized = True
268
+ logger.info(
269
+ "StoreIdempotencyPostgres initialized",
270
+ extra={
271
+ "table_name": self._config.table_name,
272
+ "pool_min_size": self._config.pool_min_size,
273
+ "pool_max_size": self._config.pool_max_size,
274
+ },
275
+ )
276
+ except asyncpg.InvalidPasswordError as e:
277
+ # Clean up pool if it was created before table setup failed
278
+ if self._pool is not None:
279
+ await self._pool.close()
280
+ self._pool = None
281
+ raise InfraConnectionError(
282
+ "Database authentication failed - check credentials",
283
+ context=context,
284
+ ) from e
285
+ except asyncpg.InvalidCatalogNameError as e:
286
+ # Clean up pool if it was created before table setup failed
287
+ if self._pool is not None:
288
+ await self._pool.close()
289
+ self._pool = None
290
+ raise InfraConnectionError(
291
+ "Database not found - check database name",
292
+ context=context,
293
+ ) from e
294
+ except OSError as e:
295
+ # Clean up pool if it was created before table setup failed
296
+ if self._pool is not None:
297
+ await self._pool.close()
298
+ self._pool = None
299
+ raise InfraConnectionError(
300
+ "Failed to connect to database - check host and port",
301
+ context=context,
302
+ ) from e
303
+ except Exception as e:
304
+ # Clean up pool if it was created before table setup failed
305
+ if self._pool is not None:
306
+ await self._pool.close()
307
+ self._pool = None
308
+ raise RuntimeHostError(
309
+ f"Failed to initialize idempotency store: {type(e).__name__}",
310
+ context=context,
311
+ ) from e
312
+
313
+ async def _ensure_table_exists(self) -> None:
314
+ """Create the idempotency table if it doesn't exist.
315
+
316
+ Creates the table with:
317
+ - UUID primary key
318
+ - Composite unique constraint on (domain, message_id)
319
+ - Index on processed_at for efficient TTL cleanup
320
+ - Index on domain for efficient is_processed queries
321
+ - Partial index on correlation_id for distributed tracing queries
322
+ """
323
+ if self._pool is None:
324
+ raise RuntimeHostError(
325
+ "Pool not initialized - call initialize() first",
326
+ context=ModelInfraErrorContext.with_correlation(
327
+ transport_type=EnumInfraTransportType.DATABASE,
328
+ operation="_ensure_table_exists",
329
+ target_name="postgres_idempotency_store",
330
+ ),
331
+ )
332
+
333
+ # Note: Table name is validated in config (alphanumeric + underscore only)
334
+ # so safe to use in SQL. We still use parameterized queries for data values.
335
+ create_table_sql = f"""
336
+ CREATE TABLE IF NOT EXISTS {self._config.table_name} (
337
+ id UUID PRIMARY KEY,
338
+ domain VARCHAR(255),
339
+ message_id UUID NOT NULL,
340
+ correlation_id UUID,
341
+ processed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
342
+ UNIQUE (domain, message_id)
343
+ )
344
+ """
345
+
346
+ create_processed_at_index_sql = f"""
347
+ CREATE INDEX IF NOT EXISTS idx_{self._config.table_name}_processed_at
348
+ ON {self._config.table_name}(processed_at)
349
+ """
350
+
351
+ # Index on domain for efficient is_processed queries filtering by domain
352
+ create_domain_index_sql = f"""
353
+ CREATE INDEX IF NOT EXISTS idx_{self._config.table_name}_domain
354
+ ON {self._config.table_name}(domain)
355
+ """
356
+
357
+ # Partial index on correlation_id for distributed tracing queries
358
+ # Uses partial index (WHERE NOT NULL) to save space since correlation_id is optional
359
+ create_correlation_index_sql = f"""
360
+ CREATE INDEX IF NOT EXISTS idx_{self._config.table_name}_correlation_id
361
+ ON {self._config.table_name}(correlation_id)
362
+ WHERE correlation_id IS NOT NULL
363
+ """
364
+
365
+ async with self._pool.acquire() as conn:
366
+ await conn.execute(create_table_sql)
367
+ await conn.execute(create_processed_at_index_sql)
368
+ await conn.execute(create_domain_index_sql)
369
+ await conn.execute(create_correlation_index_sql)
370
+
371
+ async def shutdown(self) -> None:
372
+ """Close the connection pool and release resources."""
373
+ if self._pool is not None:
374
+ await self._pool.close()
375
+ self._pool = None
376
+ self._initialized = False
377
+ logger.info("StoreIdempotencyPostgres shutdown complete")
378
+
379
+ async def check_and_record(
380
+ self,
381
+ message_id: UUID,
382
+ domain: str | None = None,
383
+ correlation_id: UUID | None = None,
384
+ ) -> bool:
385
+ """Atomically check if message was processed and record if not.
386
+
387
+ Uses INSERT ... ON CONFLICT DO NOTHING for atomic operation:
388
+ - If insert succeeds, message is new -> return True
389
+ - If insert conflicts, message is duplicate -> return False
390
+
391
+ Args:
392
+ message_id: Unique identifier for the message.
393
+ domain: Optional domain namespace for isolated deduplication.
394
+ correlation_id: Optional correlation ID for tracing.
395
+
396
+ Returns:
397
+ True if message is new (should be processed).
398
+ False if message is duplicate (should be skipped).
399
+
400
+ Raises:
401
+ InfraConnectionError: If database connection fails.
402
+ InfraTimeoutError: If operation times out.
403
+ RuntimeHostError: If store is not initialized.
404
+ """
405
+ op_correlation_id = correlation_id or uuid4()
406
+ context = ModelInfraErrorContext(
407
+ transport_type=EnumInfraTransportType.DATABASE,
408
+ operation="check_and_record",
409
+ target_name="postgres_idempotency_store",
410
+ correlation_id=op_correlation_id,
411
+ )
412
+
413
+ if not self._initialized or self._pool is None:
414
+ raise RuntimeHostError(
415
+ "Store not initialized - call initialize() first",
416
+ context=context,
417
+ )
418
+
419
+ record_id = uuid4()
420
+ processed_at = datetime.now(UTC)
421
+
422
+ # INSERT ... ON CONFLICT DO NOTHING returns affected row count
423
+ # 1 = insert succeeded (new message), 0 = conflict (duplicate)
424
+ # table_name is validated via regex in ModelPostgresIdempotencyStoreConfig
425
+ insert_sql = f""" # noqa: S608
426
+ INSERT INTO {self._config.table_name}
427
+ (id, domain, message_id, correlation_id, processed_at)
428
+ VALUES ($1, $2, $3, $4, $5)
429
+ ON CONFLICT (domain, message_id) DO NOTHING
430
+ """
431
+
432
+ is_new: bool = False # Initialize before try block for finally access
433
+ metrics_updated: bool = False # Track if exception handler updated metrics
434
+ try:
435
+ async with self._pool.acquire() as conn:
436
+ result = await conn.execute(
437
+ insert_sql,
438
+ record_id,
439
+ domain,
440
+ message_id,
441
+ correlation_id,
442
+ processed_at,
443
+ )
444
+ # asyncpg returns "INSERT 0 1" for success, "INSERT 0 0" for conflict
445
+ is_new = str(result).endswith(" 1")
446
+
447
+ if is_new:
448
+ logger.debug(
449
+ "Recorded new message",
450
+ extra={
451
+ "message_id": str(message_id),
452
+ "domain": domain,
453
+ "correlation_id": str(correlation_id)
454
+ if correlation_id
455
+ else None,
456
+ },
457
+ )
458
+ else:
459
+ logger.debug(
460
+ "Duplicate message detected",
461
+ extra={
462
+ "message_id": str(message_id),
463
+ "domain": domain,
464
+ },
465
+ )
466
+
467
+ return is_new
468
+
469
+ except asyncpg.QueryCanceledError as e:
470
+ async with self._metrics_lock:
471
+ self._metrics.total_checks += 1
472
+ self._metrics.error_count += 1
473
+ metrics_updated = True
474
+ raise InfraTimeoutError(
475
+ f"Check and record timed out after {self._config.command_timeout}s",
476
+ context=ModelTimeoutErrorContext(
477
+ transport_type=context.transport_type,
478
+ operation=context.operation,
479
+ target_name=context.target_name,
480
+ correlation_id=context.correlation_id,
481
+ timeout_seconds=self._config.command_timeout,
482
+ ),
483
+ ) from e
484
+ except asyncpg.PostgresConnectionError as e:
485
+ async with self._metrics_lock:
486
+ self._metrics.total_checks += 1
487
+ self._metrics.error_count += 1
488
+ metrics_updated = True
489
+ raise InfraConnectionError(
490
+ "Database connection lost during check_and_record",
491
+ context=context,
492
+ ) from e
493
+ except asyncpg.PostgresError as e:
494
+ async with self._metrics_lock:
495
+ self._metrics.total_checks += 1
496
+ self._metrics.error_count += 1
497
+ metrics_updated = True
498
+ raise RuntimeHostError(
499
+ f"Database error during check_and_record: {type(e).__name__}",
500
+ context=context,
501
+ ) from e
502
+ finally:
503
+ # Always update metrics for success path, even if logging fails.
504
+ # Exception handlers above set metrics_updated=True before re-raising,
505
+ # so we only update here for the success path (no exception caught).
506
+ if not metrics_updated:
507
+ async with self._metrics_lock:
508
+ self._metrics.total_checks += 1
509
+ if not is_new:
510
+ self._metrics.duplicate_count += 1
511
+
512
+ async def is_processed(
513
+ self,
514
+ message_id: UUID,
515
+ domain: str | None = None,
516
+ ) -> bool:
517
+ """Check if a message was already processed (read-only).
518
+
519
+ This is a read-only query that does not modify the store.
520
+
521
+ Args:
522
+ message_id: Unique identifier for the message.
523
+ domain: Optional domain namespace.
524
+
525
+ Returns:
526
+ True if the message has been processed.
527
+ False if the message has not been processed or has expired.
528
+
529
+ Raises:
530
+ InfraConnectionError: If database connection fails.
531
+ InfraTimeoutError: If query times out.
532
+ RuntimeHostError: If store is not initialized.
533
+ """
534
+ context = ModelInfraErrorContext.with_correlation(
535
+ transport_type=EnumInfraTransportType.DATABASE,
536
+ operation="is_processed",
537
+ target_name="postgres_idempotency_store",
538
+ )
539
+
540
+ if not self._initialized or self._pool is None:
541
+ raise RuntimeHostError(
542
+ "Store not initialized - call initialize() first",
543
+ context=context,
544
+ )
545
+
546
+ # table_name is validated via regex in ModelPostgresIdempotencyStoreConfig
547
+ query_sql = f""" # noqa: S608
548
+ SELECT 1 FROM {self._config.table_name}
549
+ WHERE domain IS NOT DISTINCT FROM $1 AND message_id = $2
550
+ LIMIT 1
551
+ """
552
+
553
+ try:
554
+ async with self._pool.acquire() as conn:
555
+ row = await conn.fetchrow(query_sql, domain, message_id)
556
+ return row is not None
557
+
558
+ except asyncpg.QueryCanceledError as e:
559
+ raise InfraTimeoutError(
560
+ f"Processed check query timed out after {self._config.command_timeout}s",
561
+ context=ModelTimeoutErrorContext(
562
+ transport_type=context.transport_type,
563
+ operation=context.operation,
564
+ target_name=context.target_name,
565
+ correlation_id=context.correlation_id,
566
+ timeout_seconds=self._config.command_timeout,
567
+ ),
568
+ ) from e
569
+ except asyncpg.PostgresConnectionError as e:
570
+ raise InfraConnectionError(
571
+ "Database connection lost during processed check query",
572
+ context=context,
573
+ ) from e
574
+ except asyncpg.PostgresError as e:
575
+ raise RuntimeHostError(
576
+ f"Database error during is_processed: {type(e).__name__}",
577
+ context=context,
578
+ ) from e
579
+
580
+ async def mark_processed(
581
+ self,
582
+ message_id: UUID,
583
+ domain: str | None = None,
584
+ correlation_id: UUID | None = None,
585
+ processed_at: datetime | None = None,
586
+ ) -> None:
587
+ """Mark a message as processed (upsert).
588
+
589
+ Records a message as processed. If the record already exists,
590
+ updates the processed_at timestamp.
591
+
592
+ Args:
593
+ message_id: Unique identifier for the message.
594
+ domain: Optional domain namespace for isolated deduplication.
595
+ correlation_id: Optional correlation ID for tracing.
596
+ processed_at: Optional timestamp. If None, uses datetime.now(timezone.utc).
597
+ Must be timezone-aware.
598
+
599
+ Raises:
600
+ InfraConnectionError: If database connection fails.
601
+ InfraTimeoutError: If operation times out.
602
+ RuntimeHostError: If store is not initialized or if processed_at
603
+ is a naive (timezone-unaware) datetime.
604
+ """
605
+ op_correlation_id = correlation_id or uuid4()
606
+ context = ModelInfraErrorContext(
607
+ transport_type=EnumInfraTransportType.DATABASE,
608
+ operation="mark_processed",
609
+ target_name="postgres_idempotency_store",
610
+ correlation_id=op_correlation_id,
611
+ )
612
+
613
+ if not self._initialized or self._pool is None:
614
+ raise RuntimeHostError(
615
+ "Store not initialized - call initialize() first",
616
+ context=context,
617
+ )
618
+
619
+ # Validate timezone awareness - fail fast on naive datetime
620
+ # Note: This guards against external callers passing naive datetimes.
621
+ # Our internal datetime.now(UTC) is always timezone-aware.
622
+ if processed_at is not None and not is_timezone_aware(processed_at):
623
+ raise RuntimeHostError(
624
+ "processed_at must be timezone-aware (got naive datetime)",
625
+ context=context,
626
+ )
627
+
628
+ effective_processed_at = processed_at or datetime.now(UTC)
629
+ record_id = uuid4()
630
+
631
+ # Use ON CONFLICT ... DO UPDATE to ensure idempotent upsert
632
+ # table_name is validated via regex in ModelPostgresIdempotencyStoreConfig
633
+ upsert_sql = f""" # noqa: S608
634
+ INSERT INTO {self._config.table_name}
635
+ (id, domain, message_id, correlation_id, processed_at)
636
+ VALUES ($1, $2, $3, $4, $5)
637
+ ON CONFLICT (domain, message_id) DO UPDATE
638
+ SET processed_at = EXCLUDED.processed_at,
639
+ correlation_id = COALESCE(EXCLUDED.correlation_id, {self._config.table_name}.correlation_id)
640
+ """
641
+
642
+ try:
643
+ async with self._pool.acquire() as conn:
644
+ await conn.execute(
645
+ upsert_sql,
646
+ record_id,
647
+ domain,
648
+ message_id,
649
+ correlation_id,
650
+ effective_processed_at,
651
+ )
652
+ logger.debug(
653
+ "Marked message as processed",
654
+ extra={
655
+ "message_id": str(message_id),
656
+ "domain": domain,
657
+ "correlation_id": str(correlation_id)
658
+ if correlation_id
659
+ else None,
660
+ },
661
+ )
662
+
663
+ except asyncpg.QueryCanceledError as e:
664
+ raise InfraTimeoutError(
665
+ f"Mark processed timed out after {self._config.command_timeout}s",
666
+ context=ModelTimeoutErrorContext(
667
+ transport_type=context.transport_type,
668
+ operation=context.operation,
669
+ target_name=context.target_name,
670
+ correlation_id=context.correlation_id,
671
+ timeout_seconds=self._config.command_timeout,
672
+ ),
673
+ ) from e
674
+ except asyncpg.PostgresConnectionError as e:
675
+ raise InfraConnectionError(
676
+ "Database connection lost during mark processed",
677
+ context=context,
678
+ ) from e
679
+ except asyncpg.PostgresError as e:
680
+ raise RuntimeHostError(
681
+ f"Database error during mark_processed: {type(e).__name__}",
682
+ context=context,
683
+ ) from e
684
+
685
+ async def cleanup_expired(
686
+ self,
687
+ ttl_seconds: int,
688
+ batch_size: int | None = None,
689
+ max_iterations: int | None = None,
690
+ ) -> int:
691
+ """Remove entries older than TTL using batched deletion with clock skew tolerance.
692
+
693
+ Cleans up old idempotency records based on processed_at timestamp.
694
+ Uses batched deletion to reduce lock contention on high-volume tables.
695
+
696
+ Batched Deletion Benefits:
697
+ - Reduces lock contention by breaking large deletes into smaller
698
+ transactions
699
+ - Prevents long-running transactions that can block other operations
700
+ - Allows other database operations to interleave between batches
701
+ - Limits transaction log growth by committing in smaller chunks
702
+
703
+ Clock Skew Handling:
704
+ In distributed systems, nodes may have slightly different system clocks.
705
+ To prevent premature deletion of records that some nodes may still
706
+ consider valid, we add a clock_skew_tolerance_seconds buffer to the TTL.
707
+
708
+ Example scenario without tolerance:
709
+ - Node A processes message at 10:00:01 (its clock)
710
+ - Node B checks for duplicate at 10:00:00 (its clock is 1 second behind)
711
+ - If cleanup runs on Node B using now(), it might delete records
712
+ that Node A just created (from Node B's perspective, they're from
713
+ the "future")
714
+
715
+ With tolerance, effective_ttl = ttl_seconds + clock_skew_tolerance_seconds,
716
+ ensuring records are only cleaned up after ALL nodes would consider them
717
+ expired.
718
+
719
+ Args:
720
+ ttl_seconds: Time-to-live in seconds. Records older than
721
+ (ttl_seconds + clock_skew_tolerance_seconds) are removed.
722
+ batch_size: Number of records to delete per batch. Defaults to
723
+ config.cleanup_batch_size (10000). Use larger values for faster
724
+ cleanup at the cost of longer locks, smaller values for better
725
+ concurrency.
726
+ max_iterations: Maximum number of batch iterations. Defaults to
727
+ config.cleanup_max_iterations (100). Prevents runaway cleanup
728
+ loops. Total max records = batch_size * max_iterations.
729
+
730
+ Returns:
731
+ Total number of entries removed across all batches.
732
+
733
+ Raises:
734
+ InfraConnectionError: If database connection fails.
735
+ InfraTimeoutError: If cleanup times out.
736
+ RuntimeHostError: If store is not initialized.
737
+
738
+ Example:
739
+ >>> # Standard cleanup using config defaults
740
+ >>> removed = await store.cleanup_expired(ttl_seconds=86400)
741
+ >>> print(f"Removed {removed} expired records")
742
+
743
+ >>> # High-concurrency system: smaller batches
744
+ >>> removed = await store.cleanup_expired(
745
+ ... ttl_seconds=86400,
746
+ ... batch_size=1000,
747
+ ... )
748
+
749
+ >>> # Bulk cleanup with large batches
750
+ >>> removed = await store.cleanup_expired(
751
+ ... ttl_seconds=86400,
752
+ ... batch_size=50000,
753
+ ... max_iterations=10,
754
+ ... )
755
+ """
756
+ context = ModelInfraErrorContext.with_correlation(
757
+ transport_type=EnumInfraTransportType.DATABASE,
758
+ operation="cleanup_expired",
759
+ target_name="postgres_idempotency_store",
760
+ )
761
+
762
+ if not self._initialized or self._pool is None:
763
+ raise RuntimeHostError(
764
+ "Store not initialized - call initialize() first",
765
+ context=context,
766
+ )
767
+
768
+ # Use config defaults if not specified
769
+ effective_batch_size = batch_size or self._config.cleanup_batch_size
770
+ effective_max_iterations = max_iterations or self._config.cleanup_max_iterations
771
+
772
+ # Apply clock skew tolerance to prevent premature deletion
773
+ # effective_ttl = ttl_seconds + clock_skew_tolerance
774
+ effective_ttl = ttl_seconds + self._config.clock_skew_tolerance_seconds
775
+
776
+ # Batched delete using subquery with LIMIT
777
+ # This pattern:
778
+ # 1. Selects up to batch_size expired record IDs
779
+ # 2. Deletes only those specific records
780
+ # 3. Repeats until no more records are found or max_iterations reached
781
+ #
782
+ # table_name is validated via regex in ModelPostgresIdempotencyStoreConfig
783
+ delete_batch_sql = f""" # noqa: S608
784
+ DELETE FROM {self._config.table_name}
785
+ WHERE id IN (
786
+ SELECT id FROM {self._config.table_name}
787
+ WHERE processed_at < now() - interval '1 second' * $1
788
+ LIMIT $2
789
+ )
790
+ """
791
+
792
+ total_removed = 0
793
+ iteration = 0
794
+
795
+ try:
796
+ while iteration < effective_max_iterations:
797
+ iteration += 1
798
+
799
+ async with self._pool.acquire() as conn:
800
+ result = await conn.execute(
801
+ delete_batch_sql, effective_ttl, effective_batch_size
802
+ )
803
+ # Parse "DELETE N" to get count
804
+ batch_removed = int(result.split()[-1]) if result else 0
805
+
806
+ total_removed += batch_removed
807
+
808
+ logger.debug(
809
+ "Cleanup batch completed",
810
+ extra={
811
+ "batch_removed": batch_removed,
812
+ "total_removed": total_removed,
813
+ "iteration": iteration,
814
+ "batch_size": effective_batch_size,
815
+ },
816
+ )
817
+
818
+ # Log progress every 10 batches for visibility during large cleanups
819
+ if iteration % 10 == 0:
820
+ logger.info(
821
+ "Cleanup progress",
822
+ extra={
823
+ "total_removed": total_removed,
824
+ "iteration": iteration,
825
+ "batch_size": effective_batch_size,
826
+ },
827
+ )
828
+
829
+ # If we deleted fewer than batch_size, we're done
830
+ if batch_removed < effective_batch_size:
831
+ break
832
+
833
+ # Update cleanup metrics (protected by lock for thread safety)
834
+ async with self._metrics_lock:
835
+ self._metrics.total_cleanup_deleted += total_removed
836
+ self._metrics.last_cleanup_deleted = total_removed
837
+ self._metrics.last_cleanup_at = datetime.now(UTC)
838
+
839
+ logger.info(
840
+ "Cleaned up expired idempotency records",
841
+ extra={
842
+ "total_removed": total_removed,
843
+ "ttl_seconds": ttl_seconds,
844
+ "clock_skew_tolerance_seconds": self._config.clock_skew_tolerance_seconds,
845
+ "effective_ttl_seconds": effective_ttl,
846
+ "table_name": self._config.table_name,
847
+ "iterations": iteration,
848
+ "batch_size": effective_batch_size,
849
+ },
850
+ )
851
+
852
+ return total_removed
853
+
854
+ except asyncpg.QueryCanceledError as e:
855
+ raise InfraTimeoutError(
856
+ f"Cleanup timed out after {self._config.command_timeout}s",
857
+ context=ModelTimeoutErrorContext(
858
+ transport_type=context.transport_type,
859
+ operation=context.operation,
860
+ target_name=context.target_name,
861
+ correlation_id=context.correlation_id,
862
+ timeout_seconds=self._config.command_timeout,
863
+ ),
864
+ ) from e
865
+ except asyncpg.PostgresConnectionError as e:
866
+ raise InfraConnectionError(
867
+ "Database connection lost during cleanup",
868
+ context=context,
869
+ ) from e
870
+ except asyncpg.PostgresError as e:
871
+ raise RuntimeHostError(
872
+ f"Database error during cleanup: {type(e).__name__}",
873
+ context=context,
874
+ ) from e
875
+
876
+ async def health_check(self) -> ModelIdempotencyStoreHealthCheckResult:
877
+ """Check if the store is healthy and can accept operations.
878
+
879
+ Performs read verification and table existence check to ensure
880
+ the database is operational without writing data.
881
+
882
+ Returns:
883
+ ModelIdempotencyStoreHealthCheckResult with health status and diagnostics:
884
+ - healthy: bool - True if store is healthy
885
+ - reason: str - "ok", "not_initialized", "table_not_found", or "check_failed"
886
+ - error_type: str | None - Exception type if check failed
887
+ """
888
+ if not self._initialized or self._pool is None:
889
+ return ModelIdempotencyStoreHealthCheckResult(
890
+ healthy=False, reason="not_initialized"
891
+ )
892
+
893
+ try:
894
+ async with self._pool.acquire() as conn:
895
+ # Step 1: Verify read access
896
+ await conn.fetchval("SELECT 1")
897
+
898
+ # Step 2: Verify table exists and is accessible
899
+ # This confirms schema setup without writes
900
+ check_table_sql = """
901
+ SELECT 1 FROM information_schema.tables
902
+ WHERE table_name = $1
903
+ LIMIT 1
904
+ """
905
+ table_exists = await conn.fetchval(
906
+ check_table_sql, self._config.table_name
907
+ )
908
+
909
+ if table_exists is None:
910
+ return ModelIdempotencyStoreHealthCheckResult(
911
+ healthy=False, reason="table_not_found"
912
+ )
913
+ return ModelIdempotencyStoreHealthCheckResult(healthy=True, reason="ok")
914
+
915
+ except Exception as e:
916
+ return ModelIdempotencyStoreHealthCheckResult(
917
+ healthy=False,
918
+ reason="check_failed",
919
+ error_type=type(e).__name__,
920
+ )
921
+
922
+
923
+ __all__: list[str] = ["StoreIdempotencyPostgres"]