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,1716 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Kafka Event Bus implementation for production message streaming.
4
+
5
+ Implements ProtocolEventBus interface using Apache Kafka (via aiokafka) for
6
+ production-grade message delivery with resilience patterns including circuit
7
+ breaker, retry with exponential backoff, and dead letter queue support.
8
+
9
+ Features:
10
+ - Topic-based message routing with Kafka partitioning
11
+ - Async publish/subscribe with callback handlers
12
+ - Circuit breaker for connection failure protection
13
+ - Retry with exponential backoff on publish failures
14
+ - Dead letter queue (DLQ) for failed message processing
15
+ - Graceful degradation when Kafka is unavailable
16
+ - Support for environment/group-based routing
17
+ - Proper producer/consumer lifecycle management
18
+
19
+ Environment Variables:
20
+ Configuration can be overridden using environment variables. All variables
21
+ are optional and fall back to defaults if not set.
22
+
23
+ Connection Settings:
24
+ KAFKA_BOOTSTRAP_SERVERS: Kafka broker addresses (comma-separated)
25
+ Default: "localhost:9092"
26
+ Example: "kafka1:9092,kafka2:9092,kafka3:9092"
27
+
28
+ KAFKA_ENVIRONMENT: Environment identifier for message routing
29
+ Default: "local"
30
+ Example: "dev", "staging", "prod"
31
+
32
+ Timeout and Retry Settings:
33
+ KAFKA_TIMEOUT_SECONDS: Timeout for Kafka operations (integer seconds)
34
+ Default: 30
35
+ Range: 1-300
36
+ Example: "60"
37
+
38
+ KAFKA_MAX_RETRY_ATTEMPTS: Maximum publish retry attempts
39
+ Default: 3
40
+ Range: 0-10
41
+ Example: "5"
42
+
43
+ NOTE: This is the BUS-LEVEL retry for Kafka connection/publish failures.
44
+ This is distinct from MESSAGE-LEVEL retry tracked in ModelEventHeaders
45
+ (retry_count/max_retries), which is for application-level message
46
+ delivery tracking across services. See "Dual Retry Configuration" below.
47
+
48
+ KAFKA_RETRY_BACKOFF_BASE: Base delay for exponential backoff (float seconds)
49
+ Default: 1.0
50
+ Range: 0.1-60.0
51
+ Example: "2.0"
52
+
53
+ Circuit Breaker Settings:
54
+ KAFKA_CIRCUIT_BREAKER_THRESHOLD: Failures before circuit opens
55
+ Default: 5
56
+ Range: 1-100
57
+ Example: "10"
58
+
59
+ KAFKA_CIRCUIT_BREAKER_RESET_TIMEOUT: Seconds before circuit resets
60
+ Default: 30.0
61
+ Range: 1.0-3600.0
62
+ Example: "60.0"
63
+
64
+ Consumer Settings:
65
+ KAFKA_CONSUMER_SLEEP_INTERVAL: Sleep between poll iterations (float seconds)
66
+ Default: 0.1
67
+ Range: 0.01-10.0
68
+ Example: "0.2"
69
+
70
+ KAFKA_AUTO_OFFSET_RESET: Offset reset policy
71
+ Default: "latest"
72
+ Options: "earliest", "latest"
73
+
74
+ KAFKA_ENABLE_AUTO_COMMIT: Auto-commit consumer offsets
75
+ Default: true
76
+ Options: "true", "1", "yes", "on" (case-insensitive) = True
77
+ All other values = False
78
+ Example: "false"
79
+
80
+ Producer Settings:
81
+ KAFKA_ACKS: Producer acknowledgment policy
82
+ Default: "all"
83
+ Options: "all" (all replicas), "1" (leader only), "0" (no ack)
84
+
85
+ KAFKA_ENABLE_IDEMPOTENCE: Enable idempotent producer
86
+ Default: true
87
+ Options: "true", "1", "yes", "on" (case-insensitive) = True
88
+ All other values = False
89
+ Example: "true"
90
+
91
+ Dead Letter Queue Settings:
92
+ KAFKA_DEAD_LETTER_TOPIC: Topic name for failed messages
93
+ Default: None (DLQ disabled)
94
+ Example: "dlq-events"
95
+
96
+ When configured, messages that fail processing will be published
97
+ to this topic with comprehensive failure metadata including:
98
+ - Original topic and message
99
+ - Failure reason and timestamp
100
+ - Correlation ID for tracking
101
+ - Retry count and error type
102
+
103
+ Dual Retry Configuration:
104
+ ONEX uses TWO distinct retry mechanisms that serve different purposes:
105
+
106
+ 1. **Bus-Level Retry** (EventBusKafka internal):
107
+ - Configured via: max_retry_attempts, retry_backoff_base
108
+ - Purpose: Handle transient Kafka connection/publish failures
109
+ - Scope: Single publish operation within the event bus
110
+ - Applies to: Producer.send() failures, timeouts, connection errors
111
+ - Example: If Kafka broker is temporarily unreachable, retry 3 times
112
+ with exponential backoff before failing
113
+
114
+ 2. **Message-Level Retry** (ModelEventHeaders):
115
+ - Configured via: retry_count, max_retries in message headers
116
+ - Purpose: Track application-level message delivery attempts
117
+ - Scope: End-to-end message delivery across services
118
+ - Applies to: Business logic failures, handler exceptions
119
+ - Example: If order processing fails, increment retry_count and
120
+ republish; stop after max_retries reached
121
+
122
+ These mechanisms are INDEPENDENT and work together:
123
+ - Bus-level retry handles infrastructure failures (network, broker)
124
+ - Message-level retry handles application failures (handler errors)
125
+
126
+ A single message publish may trigger multiple bus-level retries,
127
+ while still counting as a single message-level delivery attempt.
128
+
129
+ Usage:
130
+ ```python
131
+ from omnibase_infra.event_bus.event_bus_kafka import EventBusKafka
132
+ from omnibase_infra.event_bus.models.config import ModelKafkaEventBusConfig
133
+ from omnibase_infra.models import ModelNodeIdentity
134
+
135
+ # Option 1: Use defaults with environment variable overrides
136
+ bus = EventBusKafka.default()
137
+ await bus.start()
138
+
139
+ # Option 2: Explicit configuration via config model
140
+ config = ModelKafkaEventBusConfig(
141
+ bootstrap_servers="kafka:9092",
142
+ environment="dev",
143
+ )
144
+ bus = EventBusKafka(config=config)
145
+ await bus.start()
146
+
147
+ # Subscribe to a topic with node identity
148
+ identity = ModelNodeIdentity(
149
+ env="dev",
150
+ service="my-service",
151
+ node_name="event-processor",
152
+ version="v1",
153
+ )
154
+
155
+ async def handler(msg):
156
+ print(f"Received: {msg.value}")
157
+ unsubscribe = await bus.subscribe("events", identity, handler)
158
+
159
+ # Publish a message
160
+ await bus.publish("events", b"key", b"value")
161
+
162
+ # Cleanup
163
+ await unsubscribe()
164
+ await bus.close()
165
+ ```
166
+
167
+ Protocol Compatibility:
168
+ This class implements ProtocolEventBus from omnibase_core using duck typing
169
+ (no explicit inheritance required per ONEX patterns).
170
+
171
+ TODO: Consider formalizing the EventBusKafka interface as a Protocol
172
+ (ProtocolEventBusKafka) in the future to enable better static type checking
173
+ and IDE support for consumers that depend on Kafka-specific features.
174
+ """
175
+
176
+ from __future__ import annotations
177
+
178
+ import asyncio
179
+ import logging
180
+ import random
181
+ import re
182
+ from collections import defaultdict
183
+ from collections.abc import Awaitable, Callable
184
+ from datetime import UTC, datetime
185
+ from pathlib import Path
186
+ from uuid import UUID, uuid4
187
+
188
+ from aiokafka import AIOKafkaConsumer, AIOKafkaProducer
189
+ from aiokafka.errors import KafkaError
190
+
191
+ from omnibase_infra.enums import EnumConsumerGroupPurpose, EnumInfraTransportType
192
+ from omnibase_infra.errors import (
193
+ InfraConnectionError,
194
+ InfraTimeoutError,
195
+ InfraUnavailableError,
196
+ ModelInfraErrorContext,
197
+ ModelTimeoutErrorContext,
198
+ ProtocolConfigurationError,
199
+ )
200
+ from omnibase_infra.event_bus.mixin_kafka_broadcast import MixinKafkaBroadcast
201
+ from omnibase_infra.event_bus.mixin_kafka_dlq import MixinKafkaDlq
202
+ from omnibase_infra.event_bus.models import (
203
+ ModelEventHeaders,
204
+ ModelEventMessage,
205
+ )
206
+ from omnibase_infra.event_bus.models.config import ModelKafkaEventBusConfig
207
+ from omnibase_infra.mixins import MixinAsyncCircuitBreaker
208
+ from omnibase_infra.models import ModelNodeIdentity
209
+ from omnibase_infra.utils import compute_consumer_group_id
210
+
211
+ logger = logging.getLogger(__name__)
212
+
213
+
214
+ class EventBusKafka(MixinKafkaBroadcast, MixinKafkaDlq, MixinAsyncCircuitBreaker):
215
+ """Kafka-backed event bus for production message streaming.
216
+
217
+ Implements ProtocolEventBus interface using Apache Kafka (via aiokafka)
218
+ with resilience patterns including circuit breaker, retry with exponential
219
+ backoff, dead letter queue support, and graceful degradation when Kafka
220
+ is unavailable.
221
+
222
+ Features:
223
+ - Topic-based message routing with Kafka partitioning
224
+ - Multiple subscribers per topic with callback-based delivery
225
+ - Circuit breaker for connection failure protection
226
+ - Retry with exponential backoff on publish failures
227
+ - Dead letter queue (DLQ) for failed message processing
228
+ - Environment-based message routing
229
+ - Proper async producer/consumer lifecycle management
230
+
231
+ Attributes:
232
+ environment: Environment identifier (e.g., "local", "dev", "prod")
233
+ adapter: Returns self (for protocol compatibility)
234
+
235
+ Architecture:
236
+ This class uses mixin composition to organize functionality:
237
+ - MixinKafkaBroadcast: Environment broadcast messaging, envelope publishing
238
+ - MixinKafkaDlq: Dead letter queue handling and metrics
239
+ - MixinAsyncCircuitBreaker: Circuit breaker resilience pattern
240
+
241
+ The core class provides:
242
+ - Factory methods (3): from_config, from_yaml, default
243
+ - Properties (3): config, adapter, environment
244
+ - Lifecycle methods (4): start, initialize, shutdown, close
245
+ - Pub/Sub methods (3): publish, subscribe, start_consuming
246
+ - Health check (1): health_check
247
+
248
+ Example:
249
+ ```python
250
+ from omnibase_infra.models import ModelNodeIdentity
251
+
252
+ config = ModelKafkaEventBusConfig(
253
+ bootstrap_servers="kafka:9092",
254
+ environment="dev",
255
+ )
256
+ bus = EventBusKafka(config=config)
257
+ await bus.start()
258
+
259
+ # Subscribe with node identity
260
+ identity = ModelNodeIdentity(
261
+ env="dev",
262
+ service="my-service",
263
+ node_name="event-processor",
264
+ version="v1",
265
+ )
266
+
267
+ async def handler(msg):
268
+ print(f"Received: {msg.value}")
269
+ unsubscribe = await bus.subscribe("events", identity, handler)
270
+
271
+ # Publish
272
+ await bus.publish("events", b"key", b"value")
273
+
274
+ # Cleanup
275
+ await unsubscribe()
276
+ await bus.close()
277
+ ```
278
+ """
279
+
280
+ def __init__(
281
+ self,
282
+ config: ModelKafkaEventBusConfig | None = None,
283
+ ) -> None:
284
+ """Initialize the Kafka event bus.
285
+
286
+ Args:
287
+ config: Configuration model containing all settings. If not provided,
288
+ defaults are used with environment variable overrides.
289
+
290
+ Raises:
291
+ ProtocolConfigurationError: If circuit_breaker_threshold is not a positive integer
292
+
293
+ Example:
294
+ ```python
295
+ # Using config model (recommended)
296
+ config = ModelKafkaEventBusConfig(
297
+ bootstrap_servers="kafka:9092",
298
+ environment="prod",
299
+ )
300
+ bus = EventBusKafka(config=config)
301
+
302
+ # Using factory methods
303
+ bus = EventBusKafka.default()
304
+ bus = EventBusKafka.from_yaml(Path("kafka.yaml"))
305
+ ```
306
+ """
307
+ # Use provided config or create default with environment overrides
308
+ if config is None:
309
+ config = ModelKafkaEventBusConfig.default()
310
+
311
+ # Store config reference
312
+ self._config = config
313
+
314
+ # Apply config values
315
+ self._bootstrap_servers = config.bootstrap_servers
316
+ self._environment = config.environment
317
+ self._timeout_seconds = config.timeout_seconds
318
+ self._max_retry_attempts = config.max_retry_attempts
319
+ self._retry_backoff_base = config.retry_backoff_base
320
+
321
+ # Circuit breaker configuration
322
+ if config.circuit_breaker_threshold < 1:
323
+ context = ModelInfraErrorContext(
324
+ transport_type=EnumInfraTransportType.KAFKA,
325
+ operation="init",
326
+ target_name="kafka_event_bus",
327
+ correlation_id=uuid4(),
328
+ )
329
+ raise ProtocolConfigurationError(
330
+ f"circuit_breaker_threshold must be a positive integer, got {config.circuit_breaker_threshold}",
331
+ context=context,
332
+ parameter="circuit_breaker_threshold",
333
+ value=config.circuit_breaker_threshold,
334
+ )
335
+
336
+ # Initialize circuit breaker mixin
337
+ self._init_circuit_breaker(
338
+ threshold=config.circuit_breaker_threshold,
339
+ reset_timeout=config.circuit_breaker_reset_timeout,
340
+ service_name=f"kafka.{self._environment}",
341
+ transport_type=EnumInfraTransportType.KAFKA,
342
+ )
343
+
344
+ # Kafka producer and consumer
345
+ self._producer: AIOKafkaProducer | None = None
346
+ self._consumers: dict[str, AIOKafkaConsumer] = {}
347
+
348
+ # Subscriber registry: topic -> list of (group_id, subscription_id, callback) tuples
349
+ self._subscribers: dict[
350
+ str, list[tuple[str, str, Callable[[ModelEventMessage], Awaitable[None]]]]
351
+ ] = defaultdict(list)
352
+
353
+ # Lock for coroutine safety (protects all shared state)
354
+ self._lock = asyncio.Lock()
355
+
356
+ # State flags
357
+ self._started = False
358
+ self._shutdown = False
359
+
360
+ # Background consumer tasks
361
+ self._consumer_tasks: dict[str, asyncio.Task[None]] = {}
362
+
363
+ # Producer lock for independent producer access (avoids deadlock with main lock)
364
+ self._producer_lock = asyncio.Lock()
365
+
366
+ # Initialize DLQ mixin (metrics tracking, callback hooks)
367
+ self._init_dlq()
368
+
369
+ # =========================================================================
370
+ # Factory Methods
371
+ # =========================================================================
372
+
373
+ @classmethod
374
+ def from_config(cls, config: ModelKafkaEventBusConfig) -> EventBusKafka:
375
+ """Create EventBusKafka from a configuration model.
376
+
377
+ Args:
378
+ config: Configuration model containing all settings
379
+
380
+ Returns:
381
+ EventBusKafka instance configured with the provided settings
382
+
383
+ Example:
384
+ ```python
385
+ config = ModelKafkaEventBusConfig(
386
+ bootstrap_servers="kafka:9092",
387
+ environment="prod",
388
+ timeout_seconds=60,
389
+ )
390
+ bus = EventBusKafka.from_config(config)
391
+ ```
392
+ """
393
+ return cls(config=config)
394
+
395
+ @classmethod
396
+ def from_yaml(cls, path: Path) -> EventBusKafka:
397
+ """Create EventBusKafka from a YAML configuration file.
398
+
399
+ Loads configuration from a YAML file with environment variable
400
+ overrides applied automatically.
401
+
402
+ Args:
403
+ path: Path to YAML configuration file
404
+
405
+ Returns:
406
+ EventBusKafka instance configured from the YAML file
407
+
408
+ Raises:
409
+ FileNotFoundError: If the YAML file does not exist
410
+ ValueError: If the YAML content is invalid
411
+
412
+ Example:
413
+ ```python
414
+ bus = EventBusKafka.from_yaml(Path("/etc/kafka/config.yaml"))
415
+ ```
416
+ """
417
+ config = ModelKafkaEventBusConfig.from_yaml(path)
418
+ return cls(config=config)
419
+
420
+ @classmethod
421
+ def default(cls) -> EventBusKafka:
422
+ """Create EventBusKafka with default configuration.
423
+
424
+ Creates an instance with default settings and environment variable
425
+ overrides applied automatically. This is the recommended way to
426
+ create a EventBusKafka for most use cases.
427
+
428
+ Returns:
429
+ EventBusKafka instance with default configuration
430
+
431
+ Example:
432
+ ```python
433
+ bus = EventBusKafka.default()
434
+ await bus.start()
435
+ ```
436
+ """
437
+ return cls(config=ModelKafkaEventBusConfig.default())
438
+
439
+ # =========================================================================
440
+ # Properties
441
+ # =========================================================================
442
+
443
+ @property
444
+ def config(self) -> ModelKafkaEventBusConfig:
445
+ """Get the configuration model.
446
+
447
+ Returns:
448
+ Configuration model instance used by this event bus
449
+ """
450
+ return self._config
451
+
452
+ @property
453
+ def adapter(self) -> EventBusKafka:
454
+ """Return self for protocol compatibility.
455
+
456
+ Returns:
457
+ Self reference (Kafka bus is its own adapter)
458
+ """
459
+ return self
460
+
461
+ @property
462
+ def environment(self) -> str:
463
+ """Get the environment identifier.
464
+
465
+ Returns:
466
+ Environment string (e.g., "local", "dev", "prod")
467
+ """
468
+ return self._environment
469
+
470
+ async def start(self) -> None:
471
+ """Start the event bus and connect to Kafka.
472
+
473
+ Initializes the Kafka producer with connection retry and circuit
474
+ breaker protection. If connection fails, the bus operates in
475
+ degraded mode where publishes will fail gracefully.
476
+
477
+ Raises:
478
+ InfraConnectionError: If connection fails after all retries and
479
+ circuit breaker is open
480
+ """
481
+ if self._started:
482
+ logger.debug("EventBusKafka already started")
483
+ return
484
+
485
+ correlation_id = uuid4()
486
+
487
+ async with self._lock:
488
+ if self._started:
489
+ return
490
+
491
+ # Check circuit breaker before attempting connection
492
+ # Note: Circuit breaker requires its own lock to be held
493
+ async with self._circuit_breaker_lock:
494
+ await self._check_circuit_breaker(
495
+ operation="start", correlation_id=correlation_id
496
+ )
497
+
498
+ try:
499
+ # Apply producer configuration from config model
500
+ self._producer = AIOKafkaProducer(
501
+ bootstrap_servers=self._bootstrap_servers,
502
+ acks=self._config.acks_aiokafka,
503
+ enable_idempotence=self._config.enable_idempotence,
504
+ )
505
+
506
+ await asyncio.wait_for(
507
+ self._producer.start(),
508
+ timeout=self._timeout_seconds,
509
+ )
510
+
511
+ self._started = True
512
+ self._shutdown = False
513
+
514
+ # Reset circuit breaker on success
515
+ async with self._circuit_breaker_lock:
516
+ await self._reset_circuit_breaker()
517
+
518
+ logger.info(
519
+ "EventBusKafka started",
520
+ extra={
521
+ "environment": self._environment,
522
+ "bootstrap_servers": self._sanitize_bootstrap_servers(
523
+ self._bootstrap_servers
524
+ ),
525
+ },
526
+ )
527
+
528
+ except TimeoutError as e:
529
+ # Clean up producer on failure to prevent resource leak (thread-safe)
530
+ async with self._producer_lock:
531
+ if self._producer is not None:
532
+ try:
533
+ await self._producer.stop()
534
+ except Exception as cleanup_err:
535
+ logger.warning(
536
+ "Cleanup failed for Kafka producer stop: %s",
537
+ cleanup_err,
538
+ exc_info=True,
539
+ )
540
+ self._producer = None
541
+ # Record failure (circuit breaker lock required)
542
+ async with self._circuit_breaker_lock:
543
+ await self._record_circuit_failure(
544
+ operation="start", correlation_id=correlation_id
545
+ )
546
+ # Sanitize servers for safe logging (remove credentials)
547
+ sanitized_servers = self._sanitize_bootstrap_servers(
548
+ self._bootstrap_servers
549
+ )
550
+ timeout_ctx = ModelTimeoutErrorContext(
551
+ transport_type=EnumInfraTransportType.KAFKA,
552
+ operation="start",
553
+ target_name=f"kafka.{self._environment}",
554
+ correlation_id=correlation_id,
555
+ timeout_seconds=self._timeout_seconds,
556
+ )
557
+ logger.warning(
558
+ f"Timeout connecting to Kafka after {self._timeout_seconds}s",
559
+ extra={
560
+ "environment": self._environment,
561
+ "correlation_id": str(correlation_id),
562
+ },
563
+ )
564
+ raise InfraTimeoutError(
565
+ f"Timeout connecting to Kafka after {self._timeout_seconds}s",
566
+ context=timeout_ctx,
567
+ servers=sanitized_servers,
568
+ ) from e
569
+
570
+ except Exception as e:
571
+ # Clean up producer on failure to prevent resource leak (thread-safe)
572
+ async with self._producer_lock:
573
+ if self._producer is not None:
574
+ try:
575
+ await self._producer.stop()
576
+ except Exception as cleanup_err:
577
+ logger.warning(
578
+ "Cleanup failed for Kafka producer stop: %s",
579
+ cleanup_err,
580
+ exc_info=True,
581
+ )
582
+ self._producer = None
583
+ # Record failure (circuit breaker lock required)
584
+ async with self._circuit_breaker_lock:
585
+ await self._record_circuit_failure(
586
+ operation="start", correlation_id=correlation_id
587
+ )
588
+ # Sanitize servers for safe logging (remove credentials)
589
+ sanitized_servers = self._sanitize_bootstrap_servers(
590
+ self._bootstrap_servers
591
+ )
592
+ context = ModelInfraErrorContext(
593
+ transport_type=EnumInfraTransportType.KAFKA,
594
+ operation="start",
595
+ target_name=f"kafka.{self._environment}",
596
+ correlation_id=correlation_id,
597
+ )
598
+ logger.warning(
599
+ f"Failed to connect to Kafka: {e}",
600
+ extra={
601
+ "environment": self._environment,
602
+ "error": str(e),
603
+ "correlation_id": str(correlation_id),
604
+ },
605
+ )
606
+ raise InfraConnectionError(
607
+ f"Failed to connect to Kafka: {e}",
608
+ context=context,
609
+ servers=sanitized_servers,
610
+ ) from e
611
+
612
+ async def initialize(self, config: dict[str, object]) -> None:
613
+ """Initialize the event bus with configuration.
614
+
615
+ Protocol method for compatibility with ProtocolEventBus.
616
+ Extracts configuration and delegates to start(). Config updates
617
+ are applied atomically with lock protection to prevent races.
618
+
619
+ Args:
620
+ config: Configuration dictionary with optional keys:
621
+ - environment: Override environment setting
622
+ - bootstrap_servers: Override bootstrap servers
623
+ - timeout_seconds: Override timeout setting
624
+ """
625
+ # Apply config updates atomically under lock to prevent races
626
+ async with self._lock:
627
+ if "environment" in config:
628
+ self._environment = str(config["environment"])
629
+ if "bootstrap_servers" in config:
630
+ self._bootstrap_servers = str(config["bootstrap_servers"])
631
+ if "timeout_seconds" in config:
632
+ self._timeout_seconds = int(str(config["timeout_seconds"]))
633
+
634
+ # Start after config updates are complete
635
+ await self.start()
636
+
637
+ async def shutdown(self) -> None:
638
+ """Gracefully shutdown the event bus.
639
+
640
+ Protocol method that stops consuming and closes connections.
641
+ """
642
+ await self.close()
643
+
644
+ async def close(self) -> None:
645
+ """Close the event bus and release all resources.
646
+
647
+ Stops all background consumer tasks, closes all consumers, and
648
+ stops the producer. Safe to call multiple times. Uses proper
649
+ synchronization to prevent races during shutdown.
650
+ """
651
+ # First, signal shutdown to all background tasks
652
+ async with self._lock:
653
+ if self._shutdown:
654
+ # Already shutting down or shutdown
655
+ return
656
+ self._shutdown = True
657
+ self._started = False
658
+
659
+ # Cancel all consumer tasks (outside main lock to avoid deadlock)
660
+ tasks_to_cancel = []
661
+ async with self._lock:
662
+ tasks_to_cancel = list(self._consumer_tasks.values())
663
+
664
+ for task in tasks_to_cancel:
665
+ if not task.done():
666
+ task.cancel()
667
+ try:
668
+ await task
669
+ except asyncio.CancelledError:
670
+ pass
671
+
672
+ # Clear task registry
673
+ async with self._lock:
674
+ self._consumer_tasks.clear()
675
+
676
+ # Close all consumers
677
+ consumers_to_close = []
678
+ async with self._lock:
679
+ consumers_to_close = list(self._consumers.values())
680
+ self._consumers.clear()
681
+
682
+ for consumer in consumers_to_close:
683
+ try:
684
+ await consumer.stop()
685
+ except Exception as e:
686
+ logger.warning(f"Error stopping consumer: {e}")
687
+
688
+ # Close producer with proper locking
689
+ async with self._producer_lock:
690
+ if self._producer is not None:
691
+ try:
692
+ await self._producer.stop()
693
+ except Exception as e:
694
+ logger.warning(f"Error stopping producer: {e}")
695
+ self._producer = None
696
+
697
+ # Clear subscribers
698
+ async with self._lock:
699
+ self._subscribers.clear()
700
+
701
+ logger.info(
702
+ "EventBusKafka closed",
703
+ extra={"environment": self._environment},
704
+ )
705
+
706
+ async def publish(
707
+ self,
708
+ topic: str,
709
+ key: bytes | None,
710
+ value: bytes,
711
+ headers: ModelEventHeaders | None = None,
712
+ ) -> None:
713
+ """Publish message to topic.
714
+
715
+ Publishes a message to the specified Kafka topic with retry and
716
+ circuit breaker protection.
717
+
718
+ Args:
719
+ topic: Target topic name
720
+ key: Optional message key (for partitioning)
721
+ value: Message payload as bytes
722
+ headers: Optional event headers with metadata
723
+
724
+ Raises:
725
+ InfraUnavailableError: If the bus has not been started
726
+ InfraConnectionError: If publish fails after all retries
727
+ """
728
+ if not self._started:
729
+ context = ModelInfraErrorContext(
730
+ transport_type=EnumInfraTransportType.KAFKA,
731
+ operation="publish",
732
+ target_name=f"kafka.{self._environment}",
733
+ correlation_id=(
734
+ headers.correlation_id if headers is not None else uuid4()
735
+ ),
736
+ )
737
+ raise InfraUnavailableError(
738
+ "Event bus not started. Call start() first.",
739
+ context=context,
740
+ topic=topic,
741
+ )
742
+
743
+ # Create headers if not provided
744
+ if headers is None:
745
+ headers = ModelEventHeaders(
746
+ source=self._environment,
747
+ event_type=topic,
748
+ timestamp=datetime.now(UTC),
749
+ )
750
+
751
+ # Validate topic name
752
+ self._validate_topic_name(topic, headers.correlation_id)
753
+
754
+ # Check circuit breaker - propagate correlation_id from headers (thread-safe)
755
+ async with self._circuit_breaker_lock:
756
+ await self._check_circuit_breaker(
757
+ operation="publish", correlation_id=headers.correlation_id
758
+ )
759
+
760
+ # Convert headers to Kafka format
761
+ kafka_headers = self._model_headers_to_kafka(headers)
762
+
763
+ # Publish with retry
764
+ await self._publish_with_retry(topic, key, value, kafka_headers, headers)
765
+
766
+ async def _publish_with_retry(
767
+ self,
768
+ topic: str,
769
+ key: bytes | None,
770
+ value: bytes,
771
+ kafka_headers: list[tuple[str, bytes]],
772
+ headers: ModelEventHeaders,
773
+ ) -> None:
774
+ """Publish message with exponential backoff retry.
775
+
776
+ Args:
777
+ topic: Target topic name
778
+ key: Optional message key
779
+ value: Message payload
780
+ kafka_headers: Kafka-formatted headers
781
+ headers: Original headers model
782
+
783
+ Raises:
784
+ InfraConnectionError: If publish fails after all retries
785
+ """
786
+ last_exception: Exception | None = None
787
+
788
+ for attempt in range(self._max_retry_attempts + 1):
789
+ try:
790
+ # Thread-safe producer access - acquire lock to check and use producer
791
+ async with self._producer_lock:
792
+ if self._producer is None:
793
+ raise InfraConnectionError(
794
+ "Kafka producer not initialized",
795
+ context=ModelInfraErrorContext(
796
+ transport_type=EnumInfraTransportType.KAFKA,
797
+ operation="publish",
798
+ target_name=f"kafka.{topic}",
799
+ correlation_id=headers.correlation_id,
800
+ ),
801
+ )
802
+
803
+ future = await self._producer.send(
804
+ topic,
805
+ value=value,
806
+ key=key,
807
+ headers=kafka_headers,
808
+ )
809
+
810
+ # Wait for completion outside lock to allow other operations
811
+ record_metadata = await asyncio.wait_for(
812
+ future,
813
+ timeout=self._timeout_seconds,
814
+ )
815
+
816
+ # Success - reset circuit breaker (thread-safe)
817
+ async with self._circuit_breaker_lock:
818
+ await self._reset_circuit_breaker()
819
+
820
+ logger.debug(
821
+ f"Published to topic {topic}",
822
+ extra={
823
+ "partition": record_metadata.partition,
824
+ "offset": record_metadata.offset,
825
+ "correlation_id": str(headers.correlation_id),
826
+ },
827
+ )
828
+ return
829
+
830
+ except TimeoutError as e:
831
+ # Clean up producer on timeout to prevent resource leak (thread-safe)
832
+ async with self._producer_lock:
833
+ if self._producer is not None:
834
+ try:
835
+ await self._producer.stop()
836
+ except Exception as cleanup_err:
837
+ logger.warning(
838
+ "Cleanup failed for Kafka producer stop during publish: %s",
839
+ cleanup_err,
840
+ exc_info=True,
841
+ )
842
+ self._producer = None
843
+ last_exception = e
844
+ async with self._circuit_breaker_lock:
845
+ await self._record_circuit_failure(
846
+ operation="publish", correlation_id=headers.correlation_id
847
+ )
848
+ logger.warning(
849
+ f"Publish timeout (attempt {attempt + 1}/{self._max_retry_attempts + 1})",
850
+ extra={
851
+ "topic": topic,
852
+ "correlation_id": str(headers.correlation_id),
853
+ },
854
+ )
855
+
856
+ except KafkaError as e:
857
+ last_exception = e
858
+ async with self._circuit_breaker_lock:
859
+ await self._record_circuit_failure(
860
+ operation="publish", correlation_id=headers.correlation_id
861
+ )
862
+ logger.warning(
863
+ f"Kafka error on publish (attempt {attempt + 1}/{self._max_retry_attempts + 1}): {e}",
864
+ extra={
865
+ "topic": topic,
866
+ "correlation_id": str(headers.correlation_id),
867
+ },
868
+ )
869
+
870
+ except Exception as e:
871
+ last_exception = e
872
+ async with self._circuit_breaker_lock:
873
+ await self._record_circuit_failure(
874
+ operation="publish", correlation_id=headers.correlation_id
875
+ )
876
+ logger.warning(
877
+ f"Publish error (attempt {attempt + 1}/{self._max_retry_attempts + 1}): {e}",
878
+ extra={
879
+ "topic": topic,
880
+ "correlation_id": str(headers.correlation_id),
881
+ },
882
+ )
883
+
884
+ # Calculate backoff with jitter
885
+ if attempt < self._max_retry_attempts:
886
+ delay = self._retry_backoff_base * (2**attempt)
887
+ jitter = random.uniform(0.5, 1.5)
888
+ delay *= jitter
889
+ await asyncio.sleep(delay)
890
+
891
+ # All retries exhausted - differentiate timeout vs connection errors
892
+ context = ModelInfraErrorContext(
893
+ transport_type=EnumInfraTransportType.KAFKA,
894
+ operation="publish",
895
+ target_name=f"kafka.{topic}",
896
+ correlation_id=headers.correlation_id,
897
+ )
898
+ if isinstance(last_exception, TimeoutError):
899
+ timeout_ctx = ModelTimeoutErrorContext(
900
+ transport_type=EnumInfraTransportType.KAFKA,
901
+ operation="publish",
902
+ target_name=f"kafka.{topic}",
903
+ correlation_id=headers.correlation_id,
904
+ timeout_seconds=self._timeout_seconds,
905
+ )
906
+ raise InfraTimeoutError(
907
+ f"Timeout publishing to topic {topic} after {self._max_retry_attempts + 1} attempts",
908
+ context=timeout_ctx,
909
+ topic=topic,
910
+ retry_count=self._max_retry_attempts + 1,
911
+ ) from last_exception
912
+ raise InfraConnectionError(
913
+ f"Failed to publish to topic {topic} after {self._max_retry_attempts + 1} attempts",
914
+ context=context,
915
+ topic=topic,
916
+ retry_count=self._max_retry_attempts + 1,
917
+ ) from last_exception
918
+
919
+ async def subscribe(
920
+ self,
921
+ topic: str,
922
+ node_identity: ModelNodeIdentity,
923
+ on_message: Callable[[ModelEventMessage], Awaitable[None]],
924
+ *,
925
+ purpose: EnumConsumerGroupPurpose = EnumConsumerGroupPurpose.CONSUME,
926
+ ) -> Callable[[], Awaitable[None]]:
927
+ """Subscribe to topic with callback handler.
928
+
929
+ Registers a callback to be invoked for each message received on the topic.
930
+ Returns an unsubscribe function to remove the subscription.
931
+
932
+ The consumer group ID is derived from the node identity using the canonical
933
+ format: ``{env}.{service}.{node_name}.{purpose}.{version}``.
934
+
935
+ Note: Unlike typical Kafka consumer groups, this implementation maintains
936
+ a subscriber registry and fans out messages to all registered callbacks,
937
+ matching the EventBusInmemory interface.
938
+
939
+ Args:
940
+ topic: Topic to subscribe to
941
+ node_identity: Node identity used to derive the consumer group ID.
942
+ Contains env, service, node_name, and version components.
943
+ on_message: Async callback invoked for each message
944
+ purpose: Consumer group purpose classification. Defaults to CONSUME.
945
+ Used in the consumer group ID derivation for disambiguation.
946
+
947
+ Returns:
948
+ Async unsubscribe function to remove this subscription
949
+
950
+ Example:
951
+ ```python
952
+ from omnibase_infra.models import ModelNodeIdentity
953
+ from omnibase_infra.enums import EnumConsumerGroupPurpose
954
+
955
+ identity = ModelNodeIdentity(
956
+ env="dev",
957
+ service="my-service",
958
+ node_name="event-processor",
959
+ version="v1",
960
+ )
961
+
962
+ async def handler(msg):
963
+ print(f"Received: {msg.value}")
964
+
965
+ # Standard subscription (group_id: dev.my-service.event-processor.consume.v1)
966
+ unsubscribe = await bus.subscribe("events", identity, handler)
967
+
968
+ # With explicit purpose
969
+ unsubscribe = await bus.subscribe(
970
+ "events", identity, handler,
971
+ purpose=EnumConsumerGroupPurpose.INTROSPECTION,
972
+ )
973
+
974
+ # ... later ...
975
+ await unsubscribe()
976
+ ```
977
+ """
978
+ subscription_id = str(uuid4())
979
+ correlation_id = uuid4()
980
+
981
+ # Derive consumer group ID from node identity (no overrides allowed)
982
+ effective_group_id = compute_consumer_group_id(node_identity, purpose)
983
+
984
+ # Validate topic name
985
+ self._validate_topic_name(topic, correlation_id)
986
+
987
+ async with self._lock:
988
+ # Add to subscriber registry
989
+ self._subscribers[topic].append(
990
+ (effective_group_id, subscription_id, on_message)
991
+ )
992
+
993
+ # Start consumer for this topic if not already running
994
+ if topic not in self._consumers and self._started:
995
+ await self._start_consumer_for_topic(topic, effective_group_id)
996
+
997
+ logger.debug(
998
+ "Subscriber added",
999
+ extra={
1000
+ "topic": topic,
1001
+ "group_id": effective_group_id,
1002
+ "subscription_id": subscription_id,
1003
+ },
1004
+ )
1005
+
1006
+ async def unsubscribe() -> None:
1007
+ """Remove this subscription from the topic."""
1008
+ async with self._lock:
1009
+ try:
1010
+ # Find and remove the subscription
1011
+ subs = self._subscribers.get(topic, [])
1012
+ for i, (_gid, sid, _) in enumerate(subs):
1013
+ if sid == subscription_id:
1014
+ subs.pop(i)
1015
+ break
1016
+
1017
+ logger.debug(
1018
+ "Subscriber removed",
1019
+ extra={
1020
+ "topic": topic,
1021
+ "group_id": effective_group_id,
1022
+ "subscription_id": subscription_id,
1023
+ },
1024
+ )
1025
+
1026
+ # Stop consumer if no more subscribers for this topic
1027
+ if not self._subscribers.get(topic):
1028
+ await self._stop_consumer_for_topic(topic)
1029
+
1030
+ except Exception as e:
1031
+ logger.warning(f"Error during unsubscribe: {e}")
1032
+
1033
+ return unsubscribe
1034
+
1035
+ async def _start_consumer_for_topic(self, topic: str, group_id: str) -> None:
1036
+ """Start a Kafka consumer for a specific topic.
1037
+
1038
+ This method creates and starts a Kafka consumer for the specified topic,
1039
+ then launches a background task to consume messages. All startup failures
1040
+ are logged and propagated to the caller.
1041
+
1042
+ Args:
1043
+ topic: Topic to consume from
1044
+ group_id: Fully qualified consumer group ID. This should be derived
1045
+ from ``compute_consumer_group_id()`` or an explicit override.
1046
+ The ID is used directly without any prefix modification.
1047
+
1048
+ Raises:
1049
+ ProtocolConfigurationError: If group_id is empty (must be derived from
1050
+ compute_consumer_group_id or provided as explicit override)
1051
+ InfraTimeoutError: If consumer startup times out after timeout_seconds
1052
+ InfraConnectionError: If consumer fails to connect to Kafka brokers
1053
+ """
1054
+ if topic in self._consumers:
1055
+ return
1056
+
1057
+ correlation_id = uuid4()
1058
+ sanitized_servers = self._sanitize_bootstrap_servers(self._bootstrap_servers)
1059
+
1060
+ # Use group_id directly - it's already fully qualified from compute_consumer_group_id()
1061
+ # or an explicit override. Empty group_id indicates a bug in the caller.
1062
+ effective_group_id = group_id.strip()
1063
+ if not effective_group_id:
1064
+ context = ModelInfraErrorContext.with_correlation(
1065
+ correlation_id=correlation_id,
1066
+ transport_type=EnumInfraTransportType.KAFKA,
1067
+ operation="start_consumer",
1068
+ target_name=f"kafka.{topic}",
1069
+ )
1070
+ raise ProtocolConfigurationError(
1071
+ f"Consumer group ID is required for topic '{topic}'. "
1072
+ "Internal error: compute_consumer_group_id() should have been called.",
1073
+ context=context,
1074
+ parameter="group_id",
1075
+ value=group_id,
1076
+ )
1077
+
1078
+ # Apply consumer configuration from config model
1079
+ consumer = AIOKafkaConsumer(
1080
+ topic,
1081
+ bootstrap_servers=self._bootstrap_servers,
1082
+ group_id=effective_group_id,
1083
+ auto_offset_reset=self._config.auto_offset_reset,
1084
+ enable_auto_commit=self._config.enable_auto_commit,
1085
+ )
1086
+
1087
+ try:
1088
+ await asyncio.wait_for(
1089
+ consumer.start(),
1090
+ timeout=self._timeout_seconds,
1091
+ )
1092
+
1093
+ self._consumers[topic] = consumer
1094
+
1095
+ # Start background task to consume messages with correlation tracking
1096
+ task = asyncio.create_task(self._consume_loop(topic, correlation_id))
1097
+ self._consumer_tasks[topic] = task
1098
+
1099
+ logger.info(
1100
+ f"Started consumer for topic {topic}",
1101
+ extra={
1102
+ "topic": topic,
1103
+ "group_id": effective_group_id,
1104
+ "correlation_id": str(correlation_id),
1105
+ "servers": sanitized_servers,
1106
+ },
1107
+ )
1108
+
1109
+ except TimeoutError as e:
1110
+ # Clean up consumer on failure to prevent resource leak
1111
+ try:
1112
+ await consumer.stop()
1113
+ except Exception as cleanup_err:
1114
+ logger.warning(
1115
+ "Cleanup failed for Kafka consumer stop (topic=%s): %s",
1116
+ topic,
1117
+ cleanup_err,
1118
+ exc_info=True,
1119
+ )
1120
+
1121
+ # Propagate timeout error to surface startup failures (differentiate from connection errors)
1122
+ timeout_ctx = ModelTimeoutErrorContext(
1123
+ transport_type=EnumInfraTransportType.KAFKA,
1124
+ operation="start_consumer",
1125
+ target_name=f"kafka.{topic}",
1126
+ correlation_id=correlation_id,
1127
+ timeout_seconds=self._timeout_seconds,
1128
+ )
1129
+ logger.exception(
1130
+ f"Timeout starting consumer for topic {topic} after {self._timeout_seconds}s",
1131
+ extra={
1132
+ "topic": topic,
1133
+ "group_id": group_id,
1134
+ "correlation_id": str(correlation_id),
1135
+ "timeout_seconds": self._timeout_seconds,
1136
+ "servers": sanitized_servers,
1137
+ "error_type": "timeout",
1138
+ },
1139
+ )
1140
+ raise InfraTimeoutError(
1141
+ f"Timeout starting consumer for topic {topic} after {self._timeout_seconds}s",
1142
+ context=timeout_ctx,
1143
+ topic=topic,
1144
+ servers=sanitized_servers,
1145
+ ) from e
1146
+
1147
+ except Exception as e:
1148
+ # Clean up consumer on failure to prevent resource leak
1149
+ try:
1150
+ await consumer.stop()
1151
+ except Exception as cleanup_err:
1152
+ logger.warning(
1153
+ "Cleanup failed for Kafka consumer stop (topic=%s): %s",
1154
+ topic,
1155
+ cleanup_err,
1156
+ exc_info=True,
1157
+ )
1158
+
1159
+ # Propagate connection error to surface startup failures (differentiate from timeout)
1160
+ context = ModelInfraErrorContext(
1161
+ transport_type=EnumInfraTransportType.KAFKA,
1162
+ operation="start_consumer",
1163
+ target_name=f"kafka.{topic}",
1164
+ correlation_id=correlation_id,
1165
+ )
1166
+ logger.exception(
1167
+ f"Failed to start consumer for topic {topic}: {e}",
1168
+ extra={
1169
+ "topic": topic,
1170
+ "group_id": group_id,
1171
+ "correlation_id": str(correlation_id),
1172
+ "error": str(e),
1173
+ "error_type": type(e).__name__,
1174
+ "servers": sanitized_servers,
1175
+ },
1176
+ )
1177
+ raise InfraConnectionError(
1178
+ f"Failed to start consumer for topic {topic}: {e}",
1179
+ context=context,
1180
+ topic=topic,
1181
+ servers=sanitized_servers,
1182
+ ) from e
1183
+
1184
+ async def _stop_consumer_for_topic(self, topic: str) -> None:
1185
+ """Stop the consumer for a specific topic.
1186
+
1187
+ Args:
1188
+ topic: Topic to stop consuming from
1189
+ """
1190
+ # Cancel consumer task
1191
+ if topic in self._consumer_tasks:
1192
+ task = self._consumer_tasks.pop(topic)
1193
+ if not task.done():
1194
+ task.cancel()
1195
+ try:
1196
+ await task
1197
+ except asyncio.CancelledError:
1198
+ pass
1199
+
1200
+ # Stop consumer
1201
+ if topic in self._consumers:
1202
+ consumer = self._consumers.pop(topic)
1203
+ try:
1204
+ await consumer.stop()
1205
+ except Exception as e:
1206
+ logger.warning(f"Error stopping consumer for topic {topic}: {e}")
1207
+
1208
+ async def _consume_loop(self, topic: str, correlation_id: UUID) -> None:
1209
+ """Background loop to consume messages and dispatch to subscribers.
1210
+
1211
+ This method runs in a background task and continuously polls the Kafka consumer
1212
+ for new messages. It handles graceful cancellation, dispatches messages to all
1213
+ registered subscribers, and logs all errors without terminating the loop.
1214
+
1215
+ Args:
1216
+ topic: Topic being consumed
1217
+ correlation_id: Correlation ID for tracking this consumer task
1218
+ """
1219
+ consumer = self._consumers.get(topic)
1220
+ if consumer is None:
1221
+ logger.warning(
1222
+ f"Consumer not found for topic {topic} in consume loop",
1223
+ extra={
1224
+ "topic": topic,
1225
+ "correlation_id": str(correlation_id),
1226
+ },
1227
+ )
1228
+ return
1229
+
1230
+ logger.debug(
1231
+ f"Consumer loop started for topic {topic}",
1232
+ extra={
1233
+ "topic": topic,
1234
+ "correlation_id": str(correlation_id),
1235
+ },
1236
+ )
1237
+
1238
+ try:
1239
+ async for msg in consumer:
1240
+ if self._shutdown:
1241
+ logger.debug(
1242
+ f"Consumer loop shutdown signal received for topic {topic}",
1243
+ extra={
1244
+ "topic": topic,
1245
+ "correlation_id": str(correlation_id),
1246
+ },
1247
+ )
1248
+ break
1249
+
1250
+ # Get subscribers snapshot early - needed for consumer group in DLQ
1251
+ async with self._lock:
1252
+ subscribers = list(self._subscribers.get(topic, []))
1253
+
1254
+ # Extract consumer group for DLQ traceability (all subscribers share the same consumer)
1255
+ effective_consumer_group = (
1256
+ subscribers[0][0] if subscribers else "unknown"
1257
+ )
1258
+
1259
+ # Convert Kafka message to ModelEventMessage - handle conversion errors
1260
+ try:
1261
+ event_message = self._kafka_msg_to_model(msg, topic)
1262
+ except Exception as e:
1263
+ logger.exception(
1264
+ f"Failed to convert Kafka message to event model for topic {topic}",
1265
+ extra={
1266
+ "topic": topic,
1267
+ "correlation_id": str(correlation_id),
1268
+ "error": str(e),
1269
+ "error_type": type(e).__name__,
1270
+ },
1271
+ )
1272
+ # Deserialization errors are permanent failures - route to DLQ
1273
+ # Create minimal message from raw Kafka data for DLQ context
1274
+ await self._publish_raw_to_dlq(
1275
+ original_topic=topic,
1276
+ raw_msg=msg,
1277
+ error=e,
1278
+ correlation_id=correlation_id,
1279
+ failure_type="deserialization_error",
1280
+ consumer_group=effective_consumer_group,
1281
+ )
1282
+ continue # Skip this message but continue consuming
1283
+
1284
+ # Dispatch to all subscribers
1285
+ for group_id, subscription_id, callback in subscribers:
1286
+ try:
1287
+ await callback(event_message)
1288
+ except Exception as e:
1289
+ # Check if message-level retries are exhausted
1290
+ retry_count = event_message.headers.retry_count
1291
+ max_retries = event_message.headers.max_retries
1292
+ retries_exhausted = retry_count >= max_retries
1293
+
1294
+ logger.exception(
1295
+ "Subscriber callback failed",
1296
+ extra={
1297
+ "topic": topic,
1298
+ "group_id": group_id,
1299
+ "subscription_id": subscription_id,
1300
+ "correlation_id": str(correlation_id),
1301
+ "error": str(e),
1302
+ "error_type": type(e).__name__,
1303
+ "retry_count": retry_count,
1304
+ "max_retries": max_retries,
1305
+ "retries_exhausted": retries_exhausted,
1306
+ },
1307
+ )
1308
+
1309
+ # Route to DLQ when retries exhausted (permanent failure)
1310
+ # Per ModelEventHeaders: "When retry_count >= max_retries, message should go to DLQ"
1311
+ if retries_exhausted:
1312
+ await self._publish_to_dlq(
1313
+ original_topic=topic,
1314
+ failed_message=event_message,
1315
+ error=e,
1316
+ correlation_id=correlation_id,
1317
+ consumer_group=group_id,
1318
+ )
1319
+ else:
1320
+ # Message still has retries available - log for potential republish
1321
+ # Note: Republishing logic is the responsibility of the caller/handler
1322
+ logger.warning(
1323
+ f"Handler failed but retries available ({retry_count}/{max_retries})",
1324
+ extra={
1325
+ "topic": topic,
1326
+ "correlation_id": str(correlation_id),
1327
+ "retry_count": retry_count,
1328
+ "max_retries": max_retries,
1329
+ },
1330
+ )
1331
+ # Continue dispatching to other subscribers even if one fails
1332
+
1333
+ except asyncio.CancelledError:
1334
+ # Graceful cancellation - this is expected during shutdown
1335
+ logger.info(
1336
+ f"Consumer loop cancelled for topic {topic}",
1337
+ extra={
1338
+ "topic": topic,
1339
+ "correlation_id": str(correlation_id),
1340
+ },
1341
+ )
1342
+ raise # Re-raise to properly handle task cancellation
1343
+
1344
+ except Exception as e:
1345
+ # Unexpected error in consumer loop - log with full context
1346
+ logger.exception(
1347
+ f"Consumer loop error for topic {topic}: {e}",
1348
+ extra={
1349
+ "topic": topic,
1350
+ "correlation_id": str(correlation_id),
1351
+ "error": str(e),
1352
+ "error_type": type(e).__name__,
1353
+ },
1354
+ )
1355
+ # Don't raise - allow task to complete and cleanup to proceed
1356
+
1357
+ finally:
1358
+ logger.info(
1359
+ f"Consumer loop exiting for topic {topic}",
1360
+ extra={
1361
+ "topic": topic,
1362
+ "correlation_id": str(correlation_id),
1363
+ },
1364
+ )
1365
+
1366
+ async def start_consuming(self) -> None:
1367
+ """Start the consumer loop.
1368
+
1369
+ Protocol method for ProtocolEventBus compatibility.
1370
+ Blocks until shutdown() is called.
1371
+ """
1372
+ if not self._started:
1373
+ await self.start()
1374
+
1375
+ # Collect topics that need consumers while holding lock briefly
1376
+ topics_to_start: list[tuple[str, str]] = []
1377
+ async with self._lock:
1378
+ for topic in self._subscribers:
1379
+ if topic not in self._consumers:
1380
+ subs = self._subscribers[topic]
1381
+ if subs:
1382
+ group_id = subs[0][0]
1383
+ topics_to_start.append((topic, group_id))
1384
+
1385
+ # Start consumers outside the lock to avoid blocking
1386
+ for topic, group_id in topics_to_start:
1387
+ await self._start_consumer_for_topic(topic, group_id)
1388
+
1389
+ # Block until shutdown
1390
+ while not self._shutdown:
1391
+ await asyncio.sleep(self._config.consumer_sleep_interval)
1392
+
1393
+ async def health_check(self) -> dict[str, object]:
1394
+ """Check event bus health.
1395
+
1396
+ Protocol method for ProtocolEventBus compatibility.
1397
+
1398
+ Returns:
1399
+ Dictionary with health status information:
1400
+ - healthy: Whether the bus is operational
1401
+ - started: Whether start() has been called
1402
+ - environment: Current environment
1403
+ - bootstrap_servers: Kafka bootstrap servers
1404
+ - circuit_state: Current circuit breaker state
1405
+ - subscriber_count: Total number of active subscriptions
1406
+ - topic_count: Number of topics with subscribers
1407
+ - consumer_count: Number of active consumers
1408
+ """
1409
+ async with self._lock:
1410
+ subscriber_count = sum(len(subs) for subs in self._subscribers.values())
1411
+ topic_count = len(self._subscribers)
1412
+ consumer_count = len(self._consumers)
1413
+ started = self._started
1414
+
1415
+ # Get circuit breaker state (thread-safe access)
1416
+ async with self._circuit_breaker_lock:
1417
+ circuit_state = "open" if self._circuit_breaker_open else "closed"
1418
+
1419
+ # Check if producer is healthy (thread-safe access)
1420
+ producer_healthy = False
1421
+ async with self._producer_lock:
1422
+ if self._producer is not None:
1423
+ try:
1424
+ # Check if producer client is not closed
1425
+ producer_healthy = not getattr(self._producer, "_closed", True)
1426
+ except Exception:
1427
+ producer_healthy = False
1428
+
1429
+ return {
1430
+ "healthy": started and producer_healthy,
1431
+ "started": started,
1432
+ "environment": self._environment,
1433
+ "bootstrap_servers": self._sanitize_bootstrap_servers(
1434
+ self._bootstrap_servers
1435
+ ),
1436
+ "circuit_state": circuit_state,
1437
+ "subscriber_count": subscriber_count,
1438
+ "topic_count": topic_count,
1439
+ "consumer_count": consumer_count,
1440
+ }
1441
+
1442
+ # =========================================================================
1443
+ # Helper Methods
1444
+ # =========================================================================
1445
+
1446
+ def _sanitize_bootstrap_servers(self, servers: str) -> str:
1447
+ """Sanitize bootstrap servers string to remove potential credentials.
1448
+
1449
+ Removes any authentication tokens, passwords, or sensitive data from
1450
+ the bootstrap servers string before logging or including in errors.
1451
+
1452
+ Args:
1453
+ servers: Raw bootstrap servers string (may contain credentials)
1454
+
1455
+ Returns:
1456
+ Sanitized servers string safe for logging and error messages
1457
+
1458
+ Example:
1459
+ "user:pass@kafka:9092" -> "kafka:9092"
1460
+ "kafka:9092,kafka2:9092" -> "kafka:9092,kafka2:9092"
1461
+ """
1462
+ if not servers:
1463
+ return "unknown"
1464
+
1465
+ # Split by comma for multiple servers
1466
+ server_list = [s.strip() for s in servers.split(",")]
1467
+ sanitized = []
1468
+
1469
+ for server in server_list:
1470
+ # Remove any user:pass@ prefix (credentials)
1471
+ if "@" in server:
1472
+ # Keep only the part after @
1473
+ server = server.split("@", 1)[1]
1474
+ sanitized.append(server)
1475
+
1476
+ return ",".join(sanitized)
1477
+
1478
+ def _validate_topic_name(self, topic: str, correlation_id: UUID) -> None:
1479
+ """Validate Kafka topic name according to Kafka naming rules.
1480
+
1481
+ Kafka topic names must:
1482
+ - Not be empty
1483
+ - Be 255 characters or less
1484
+ - Contain only: a-z, A-Z, 0-9, period (.), underscore (_), hyphen (-)
1485
+ - Not be "." or ".." (reserved)
1486
+
1487
+ Args:
1488
+ topic: Topic name to validate
1489
+ correlation_id: Correlation ID for error context
1490
+
1491
+ Raises:
1492
+ ProtocolConfigurationError: If topic name is invalid
1493
+
1494
+ Reference:
1495
+ https://kafka.apache.org/documentation/#topicconfigs
1496
+ """
1497
+ context = ModelInfraErrorContext(
1498
+ transport_type=EnumInfraTransportType.KAFKA,
1499
+ operation="validate_topic",
1500
+ target_name=f"kafka.{self._environment}",
1501
+ correlation_id=correlation_id,
1502
+ )
1503
+
1504
+ if not topic:
1505
+ raise ProtocolConfigurationError(
1506
+ "Topic name cannot be empty",
1507
+ context=context,
1508
+ parameter="topic",
1509
+ value=topic,
1510
+ )
1511
+
1512
+ if len(topic) > 255:
1513
+ raise ProtocolConfigurationError(
1514
+ f"Topic name '{topic}' exceeds maximum length of 255 characters",
1515
+ context=context,
1516
+ parameter="topic",
1517
+ value=topic,
1518
+ )
1519
+
1520
+ if topic in (".", ".."):
1521
+ raise ProtocolConfigurationError(
1522
+ f"Topic name '{topic}' is reserved and cannot be used",
1523
+ context=context,
1524
+ parameter="topic",
1525
+ value=topic,
1526
+ )
1527
+
1528
+ # Validate characters (a-z, A-Z, 0-9, '.', '_', '-')
1529
+ if not re.match(r"^[a-zA-Z0-9._-]+$", topic):
1530
+ raise ProtocolConfigurationError(
1531
+ f"Topic name '{topic}' contains invalid characters. "
1532
+ "Only alphanumeric characters, periods (.), underscores (_), "
1533
+ "and hyphens (-) are allowed",
1534
+ context=context,
1535
+ parameter="topic",
1536
+ value=topic,
1537
+ )
1538
+
1539
+ def _model_headers_to_kafka(
1540
+ self, headers: ModelEventHeaders
1541
+ ) -> list[tuple[str, bytes]]:
1542
+ """Convert ModelEventHeaders to Kafka header format.
1543
+
1544
+ Args:
1545
+ headers: Model headers
1546
+
1547
+ Returns:
1548
+ List of (key, value) tuples with bytes values
1549
+ """
1550
+ kafka_headers: list[tuple[str, bytes]] = [
1551
+ ("content_type", headers.content_type.encode("utf-8")),
1552
+ ("correlation_id", str(headers.correlation_id).encode("utf-8")),
1553
+ ("message_id", str(headers.message_id).encode("utf-8")),
1554
+ ("timestamp", headers.timestamp.isoformat().encode("utf-8")),
1555
+ ("source", headers.source.encode("utf-8")),
1556
+ ("event_type", headers.event_type.encode("utf-8")),
1557
+ ("schema_version", headers.schema_version.encode("utf-8")),
1558
+ ("priority", headers.priority.encode("utf-8")),
1559
+ ("retry_count", str(headers.retry_count).encode("utf-8")),
1560
+ ("max_retries", str(headers.max_retries).encode("utf-8")),
1561
+ ]
1562
+
1563
+ # Add optional headers if present
1564
+ if headers.destination:
1565
+ kafka_headers.append(("destination", headers.destination.encode("utf-8")))
1566
+ if headers.trace_id:
1567
+ kafka_headers.append(("trace_id", headers.trace_id.encode("utf-8")))
1568
+ if headers.span_id:
1569
+ kafka_headers.append(("span_id", headers.span_id.encode("utf-8")))
1570
+ if headers.parent_span_id:
1571
+ kafka_headers.append(
1572
+ ("parent_span_id", headers.parent_span_id.encode("utf-8"))
1573
+ )
1574
+ if headers.operation_name:
1575
+ kafka_headers.append(
1576
+ ("operation_name", headers.operation_name.encode("utf-8"))
1577
+ )
1578
+ if headers.routing_key:
1579
+ kafka_headers.append(("routing_key", headers.routing_key.encode("utf-8")))
1580
+ if headers.partition_key:
1581
+ kafka_headers.append(
1582
+ ("partition_key", headers.partition_key.encode("utf-8"))
1583
+ )
1584
+ if headers.ttl_seconds is not None:
1585
+ kafka_headers.append(
1586
+ ("ttl_seconds", str(headers.ttl_seconds).encode("utf-8"))
1587
+ )
1588
+
1589
+ return kafka_headers
1590
+
1591
+ def _kafka_headers_to_model(
1592
+ self, kafka_headers: list[tuple[str, bytes]] | None
1593
+ ) -> ModelEventHeaders:
1594
+ """Convert Kafka headers to ModelEventHeaders.
1595
+
1596
+ Args:
1597
+ kafka_headers: Kafka header list
1598
+
1599
+ Returns:
1600
+ ModelEventHeaders instance
1601
+ """
1602
+ if not kafka_headers:
1603
+ return ModelEventHeaders(
1604
+ source="unknown",
1605
+ event_type="unknown",
1606
+ timestamp=datetime.now(UTC),
1607
+ )
1608
+
1609
+ headers_dict: dict[str, str] = {}
1610
+ for key, value in kafka_headers:
1611
+ if value is not None:
1612
+ headers_dict[key] = value.decode("utf-8")
1613
+
1614
+ # Parse correlation_id from string to UUID (with fallback to new UUID)
1615
+ correlation_id_str = headers_dict.get("correlation_id")
1616
+ if correlation_id_str:
1617
+ try:
1618
+ correlation_id = UUID(correlation_id_str)
1619
+ except (ValueError, AttributeError):
1620
+ # Invalid UUID format - generate new one
1621
+ correlation_id = uuid4()
1622
+ else:
1623
+ correlation_id = uuid4()
1624
+
1625
+ # Parse message_id from string to UUID (with fallback to new UUID)
1626
+ message_id_str = headers_dict.get("message_id")
1627
+ if message_id_str:
1628
+ try:
1629
+ message_id = UUID(message_id_str)
1630
+ except (ValueError, AttributeError):
1631
+ # Invalid UUID format - generate new one
1632
+ message_id = uuid4()
1633
+ else:
1634
+ message_id = uuid4()
1635
+
1636
+ # Parse timestamp from ISO format string to datetime (with fallback to now)
1637
+ timestamp_str = headers_dict.get("timestamp")
1638
+ if timestamp_str:
1639
+ timestamp = datetime.fromisoformat(timestamp_str)
1640
+ else:
1641
+ timestamp = datetime.now(UTC)
1642
+
1643
+ # Parse priority with validation (default to "normal" if invalid)
1644
+ priority_str = headers_dict.get("priority", "normal")
1645
+ valid_priorities = ("low", "normal", "high", "critical")
1646
+ priority = priority_str if priority_str in valid_priorities else "normal"
1647
+
1648
+ # Parse integer fields with fallback defaults
1649
+ retry_count_str = headers_dict.get("retry_count")
1650
+ retry_count = int(retry_count_str) if retry_count_str else 0
1651
+
1652
+ max_retries_str = headers_dict.get("max_retries")
1653
+ max_retries = int(max_retries_str) if max_retries_str else 3
1654
+
1655
+ ttl_seconds_str = headers_dict.get("ttl_seconds")
1656
+ ttl_seconds = int(ttl_seconds_str) if ttl_seconds_str else None
1657
+
1658
+ return ModelEventHeaders(
1659
+ content_type=headers_dict.get("content_type", "application/json"),
1660
+ correlation_id=correlation_id,
1661
+ message_id=message_id,
1662
+ timestamp=timestamp,
1663
+ source=headers_dict.get("source", "unknown"),
1664
+ event_type=headers_dict.get("event_type", "unknown"),
1665
+ schema_version=headers_dict.get("schema_version", "1.0.0"),
1666
+ destination=headers_dict.get("destination"),
1667
+ trace_id=headers_dict.get("trace_id"),
1668
+ span_id=headers_dict.get("span_id"),
1669
+ parent_span_id=headers_dict.get("parent_span_id"),
1670
+ operation_name=headers_dict.get("operation_name"),
1671
+ priority=priority,
1672
+ routing_key=headers_dict.get("routing_key"),
1673
+ partition_key=headers_dict.get("partition_key"),
1674
+ retry_count=retry_count,
1675
+ max_retries=max_retries,
1676
+ ttl_seconds=ttl_seconds,
1677
+ )
1678
+
1679
+ def _kafka_msg_to_model(self, msg: object, topic: str) -> ModelEventMessage:
1680
+ """Convert Kafka ConsumerRecord to ModelEventMessage.
1681
+
1682
+ Args:
1683
+ msg: Kafka ConsumerRecord
1684
+ topic: Topic name
1685
+
1686
+ Returns:
1687
+ ModelEventMessage instance
1688
+ """
1689
+ # Extract fields from Kafka message
1690
+ key = getattr(msg, "key", None)
1691
+ value = getattr(msg, "value", b"")
1692
+ offset = getattr(msg, "offset", None)
1693
+ partition = getattr(msg, "partition", None)
1694
+ kafka_headers = getattr(msg, "headers", None)
1695
+
1696
+ # Convert key to bytes if it's a string
1697
+ if isinstance(key, str):
1698
+ key = key.encode("utf-8")
1699
+
1700
+ # Ensure value is bytes
1701
+ if isinstance(value, str):
1702
+ value = value.encode("utf-8")
1703
+
1704
+ headers = self._kafka_headers_to_model(kafka_headers)
1705
+
1706
+ return ModelEventMessage(
1707
+ topic=topic,
1708
+ key=key,
1709
+ value=value,
1710
+ headers=headers,
1711
+ offset=str(offset) if offset is not None else None,
1712
+ partition=partition,
1713
+ )
1714
+
1715
+
1716
+ __all__: list[str] = ["EventBusKafka"]