omnibase_infra 0.2.1__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 (675) hide show
  1. omnibase_infra/__init__.py +101 -0
  2. omnibase_infra/cli/__init__.py +1 -0
  3. omnibase_infra/cli/commands.py +216 -0
  4. omnibase_infra/clients/__init__.py +0 -0
  5. omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +261 -0
  6. omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +138 -0
  7. omnibase_infra/decorators/__init__.py +29 -0
  8. omnibase_infra/decorators/allow_any.py +109 -0
  9. omnibase_infra/dlq/__init__.py +90 -0
  10. omnibase_infra/dlq/constants_dlq.py +57 -0
  11. omnibase_infra/dlq/models/__init__.py +26 -0
  12. omnibase_infra/dlq/models/enum_replay_status.py +37 -0
  13. omnibase_infra/dlq/models/model_dlq_replay_record.py +135 -0
  14. omnibase_infra/dlq/models/model_dlq_tracking_config.py +184 -0
  15. omnibase_infra/dlq/service_dlq_tracking.py +611 -0
  16. omnibase_infra/enums/__init__.py +123 -0
  17. omnibase_infra/enums/enum_any_type_violation.py +104 -0
  18. omnibase_infra/enums/enum_backend_type.py +27 -0
  19. omnibase_infra/enums/enum_capture_outcome.py +42 -0
  20. omnibase_infra/enums/enum_capture_state.py +88 -0
  21. omnibase_infra/enums/enum_chain_violation_type.py +119 -0
  22. omnibase_infra/enums/enum_circuit_state.py +51 -0
  23. omnibase_infra/enums/enum_confirmation_event_type.py +27 -0
  24. omnibase_infra/enums/enum_contract_type.py +84 -0
  25. omnibase_infra/enums/enum_dedupe_strategy.py +46 -0
  26. omnibase_infra/enums/enum_dispatch_status.py +191 -0
  27. omnibase_infra/enums/enum_environment.py +46 -0
  28. omnibase_infra/enums/enum_execution_shape_violation.py +103 -0
  29. omnibase_infra/enums/enum_handler_error_type.py +101 -0
  30. omnibase_infra/enums/enum_handler_loader_error.py +178 -0
  31. omnibase_infra/enums/enum_handler_source_type.py +87 -0
  32. omnibase_infra/enums/enum_handler_type.py +77 -0
  33. omnibase_infra/enums/enum_handler_type_category.py +61 -0
  34. omnibase_infra/enums/enum_infra_transport_type.py +73 -0
  35. omnibase_infra/enums/enum_introspection_reason.py +154 -0
  36. omnibase_infra/enums/enum_message_category.py +213 -0
  37. omnibase_infra/enums/enum_node_archetype.py +74 -0
  38. omnibase_infra/enums/enum_node_output_type.py +185 -0
  39. omnibase_infra/enums/enum_non_retryable_error_category.py +224 -0
  40. omnibase_infra/enums/enum_policy_type.py +32 -0
  41. omnibase_infra/enums/enum_registration_state.py +261 -0
  42. omnibase_infra/enums/enum_registration_status.py +33 -0
  43. omnibase_infra/enums/enum_registry_response_status.py +28 -0
  44. omnibase_infra/enums/enum_response_status.py +26 -0
  45. omnibase_infra/enums/enum_retry_error_category.py +98 -0
  46. omnibase_infra/enums/enum_security_rule_id.py +103 -0
  47. omnibase_infra/enums/enum_selection_strategy.py +91 -0
  48. omnibase_infra/enums/enum_topic_standard.py +42 -0
  49. omnibase_infra/enums/enum_validation_severity.py +78 -0
  50. omnibase_infra/errors/__init__.py +156 -0
  51. omnibase_infra/errors/error_architecture_violation.py +152 -0
  52. omnibase_infra/errors/error_chain_propagation.py +188 -0
  53. omnibase_infra/errors/error_compute_registry.py +92 -0
  54. omnibase_infra/errors/error_consul.py +132 -0
  55. omnibase_infra/errors/error_container_wiring.py +243 -0
  56. omnibase_infra/errors/error_event_bus_registry.py +102 -0
  57. omnibase_infra/errors/error_infra.py +608 -0
  58. omnibase_infra/errors/error_message_type_registry.py +101 -0
  59. omnibase_infra/errors/error_policy_registry.py +112 -0
  60. omnibase_infra/errors/error_vault.py +123 -0
  61. omnibase_infra/event_bus/__init__.py +72 -0
  62. omnibase_infra/event_bus/configs/kafka_event_bus_config.yaml +86 -0
  63. omnibase_infra/event_bus/event_bus_inmemory.py +743 -0
  64. omnibase_infra/event_bus/event_bus_kafka.py +1658 -0
  65. omnibase_infra/event_bus/mixin_kafka_broadcast.py +184 -0
  66. omnibase_infra/event_bus/mixin_kafka_dlq.py +765 -0
  67. omnibase_infra/event_bus/models/__init__.py +29 -0
  68. omnibase_infra/event_bus/models/config/__init__.py +20 -0
  69. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +725 -0
  70. omnibase_infra/event_bus/models/model_dlq_event.py +206 -0
  71. omnibase_infra/event_bus/models/model_dlq_metrics.py +304 -0
  72. omnibase_infra/event_bus/models/model_event_headers.py +115 -0
  73. omnibase_infra/event_bus/models/model_event_message.py +60 -0
  74. omnibase_infra/event_bus/topic_constants.py +376 -0
  75. omnibase_infra/handlers/__init__.py +75 -0
  76. omnibase_infra/handlers/filesystem/__init__.py +48 -0
  77. omnibase_infra/handlers/filesystem/enum_file_system_operation.py +35 -0
  78. omnibase_infra/handlers/filesystem/model_file_system_request.py +298 -0
  79. omnibase_infra/handlers/filesystem/model_file_system_result.py +166 -0
  80. omnibase_infra/handlers/handler_consul.py +787 -0
  81. omnibase_infra/handlers/handler_db.py +1039 -0
  82. omnibase_infra/handlers/handler_filesystem.py +1478 -0
  83. omnibase_infra/handlers/handler_graph.py +1154 -0
  84. omnibase_infra/handlers/handler_http.py +920 -0
  85. omnibase_infra/handlers/handler_manifest_persistence.contract.yaml +184 -0
  86. omnibase_infra/handlers/handler_manifest_persistence.py +1539 -0
  87. omnibase_infra/handlers/handler_mcp.py +748 -0
  88. omnibase_infra/handlers/handler_qdrant.py +1076 -0
  89. omnibase_infra/handlers/handler_vault.py +422 -0
  90. omnibase_infra/handlers/mcp/__init__.py +19 -0
  91. omnibase_infra/handlers/mcp/adapter_onex_to_mcp.py +446 -0
  92. omnibase_infra/handlers/mcp/protocols.py +178 -0
  93. omnibase_infra/handlers/mcp/transport_streamable_http.py +352 -0
  94. omnibase_infra/handlers/mixins/__init__.py +42 -0
  95. omnibase_infra/handlers/mixins/mixin_consul_initialization.py +349 -0
  96. omnibase_infra/handlers/mixins/mixin_consul_kv.py +337 -0
  97. omnibase_infra/handlers/mixins/mixin_consul_service.py +277 -0
  98. omnibase_infra/handlers/mixins/mixin_vault_initialization.py +338 -0
  99. omnibase_infra/handlers/mixins/mixin_vault_retry.py +412 -0
  100. omnibase_infra/handlers/mixins/mixin_vault_secrets.py +450 -0
  101. omnibase_infra/handlers/mixins/mixin_vault_token.py +365 -0
  102. omnibase_infra/handlers/models/__init__.py +286 -0
  103. omnibase_infra/handlers/models/consul/__init__.py +81 -0
  104. omnibase_infra/handlers/models/consul/enum_consul_operation_type.py +57 -0
  105. omnibase_infra/handlers/models/consul/model_consul_deregister_payload.py +51 -0
  106. omnibase_infra/handlers/models/consul/model_consul_handler_config.py +153 -0
  107. omnibase_infra/handlers/models/consul/model_consul_handler_payload.py +89 -0
  108. omnibase_infra/handlers/models/consul/model_consul_kv_get_found_payload.py +55 -0
  109. omnibase_infra/handlers/models/consul/model_consul_kv_get_not_found_payload.py +49 -0
  110. omnibase_infra/handlers/models/consul/model_consul_kv_get_recurse_payload.py +50 -0
  111. omnibase_infra/handlers/models/consul/model_consul_kv_item.py +33 -0
  112. omnibase_infra/handlers/models/consul/model_consul_kv_put_payload.py +41 -0
  113. omnibase_infra/handlers/models/consul/model_consul_register_payload.py +53 -0
  114. omnibase_infra/handlers/models/consul/model_consul_retry_config.py +66 -0
  115. omnibase_infra/handlers/models/consul/model_payload_consul.py +66 -0
  116. omnibase_infra/handlers/models/consul/registry_payload_consul.py +214 -0
  117. omnibase_infra/handlers/models/graph/__init__.py +35 -0
  118. omnibase_infra/handlers/models/graph/enum_graph_operation_type.py +20 -0
  119. omnibase_infra/handlers/models/graph/model_graph_execute_payload.py +38 -0
  120. omnibase_infra/handlers/models/graph/model_graph_handler_config.py +54 -0
  121. omnibase_infra/handlers/models/graph/model_graph_handler_payload.py +44 -0
  122. omnibase_infra/handlers/models/graph/model_graph_query_payload.py +40 -0
  123. omnibase_infra/handlers/models/graph/model_graph_record.py +22 -0
  124. omnibase_infra/handlers/models/http/__init__.py +50 -0
  125. omnibase_infra/handlers/models/http/enum_http_operation_type.py +29 -0
  126. omnibase_infra/handlers/models/http/model_http_body_content.py +45 -0
  127. omnibase_infra/handlers/models/http/model_http_get_payload.py +88 -0
  128. omnibase_infra/handlers/models/http/model_http_handler_payload.py +90 -0
  129. omnibase_infra/handlers/models/http/model_http_post_payload.py +88 -0
  130. omnibase_infra/handlers/models/http/model_payload_http.py +66 -0
  131. omnibase_infra/handlers/models/http/registry_payload_http.py +212 -0
  132. omnibase_infra/handlers/models/mcp/__init__.py +23 -0
  133. omnibase_infra/handlers/models/mcp/enum_mcp_operation_type.py +24 -0
  134. omnibase_infra/handlers/models/mcp/model_mcp_handler_config.py +40 -0
  135. omnibase_infra/handlers/models/mcp/model_mcp_tool_call.py +32 -0
  136. omnibase_infra/handlers/models/mcp/model_mcp_tool_result.py +45 -0
  137. omnibase_infra/handlers/models/model_consul_handler_response.py +96 -0
  138. omnibase_infra/handlers/models/model_db_describe_response.py +83 -0
  139. omnibase_infra/handlers/models/model_db_query_payload.py +95 -0
  140. omnibase_infra/handlers/models/model_db_query_response.py +60 -0
  141. omnibase_infra/handlers/models/model_filesystem_config.py +98 -0
  142. omnibase_infra/handlers/models/model_filesystem_delete_payload.py +54 -0
  143. omnibase_infra/handlers/models/model_filesystem_delete_result.py +77 -0
  144. omnibase_infra/handlers/models/model_filesystem_directory_entry.py +75 -0
  145. omnibase_infra/handlers/models/model_filesystem_ensure_directory_payload.py +54 -0
  146. omnibase_infra/handlers/models/model_filesystem_ensure_directory_result.py +60 -0
  147. omnibase_infra/handlers/models/model_filesystem_list_directory_payload.py +60 -0
  148. omnibase_infra/handlers/models/model_filesystem_list_directory_result.py +68 -0
  149. omnibase_infra/handlers/models/model_filesystem_read_payload.py +62 -0
  150. omnibase_infra/handlers/models/model_filesystem_read_result.py +61 -0
  151. omnibase_infra/handlers/models/model_filesystem_write_payload.py +70 -0
  152. omnibase_infra/handlers/models/model_filesystem_write_result.py +55 -0
  153. omnibase_infra/handlers/models/model_graph_handler_response.py +98 -0
  154. omnibase_infra/handlers/models/model_handler_response.py +103 -0
  155. omnibase_infra/handlers/models/model_http_handler_response.py +101 -0
  156. omnibase_infra/handlers/models/model_manifest_metadata.py +75 -0
  157. omnibase_infra/handlers/models/model_manifest_persistence_config.py +62 -0
  158. omnibase_infra/handlers/models/model_manifest_query_payload.py +90 -0
  159. omnibase_infra/handlers/models/model_manifest_query_result.py +97 -0
  160. omnibase_infra/handlers/models/model_manifest_retrieve_payload.py +44 -0
  161. omnibase_infra/handlers/models/model_manifest_retrieve_result.py +98 -0
  162. omnibase_infra/handlers/models/model_manifest_store_payload.py +47 -0
  163. omnibase_infra/handlers/models/model_manifest_store_result.py +67 -0
  164. omnibase_infra/handlers/models/model_operation_context.py +187 -0
  165. omnibase_infra/handlers/models/model_qdrant_handler_response.py +98 -0
  166. omnibase_infra/handlers/models/model_retry_state.py +162 -0
  167. omnibase_infra/handlers/models/model_vault_handler_response.py +98 -0
  168. omnibase_infra/handlers/models/qdrant/__init__.py +44 -0
  169. omnibase_infra/handlers/models/qdrant/enum_qdrant_operation_type.py +26 -0
  170. omnibase_infra/handlers/models/qdrant/model_qdrant_collection_payload.py +42 -0
  171. omnibase_infra/handlers/models/qdrant/model_qdrant_delete_payload.py +36 -0
  172. omnibase_infra/handlers/models/qdrant/model_qdrant_handler_config.py +42 -0
  173. omnibase_infra/handlers/models/qdrant/model_qdrant_handler_payload.py +54 -0
  174. omnibase_infra/handlers/models/qdrant/model_qdrant_search_payload.py +42 -0
  175. omnibase_infra/handlers/models/qdrant/model_qdrant_search_result.py +30 -0
  176. omnibase_infra/handlers/models/qdrant/model_qdrant_upsert_payload.py +36 -0
  177. omnibase_infra/handlers/models/vault/__init__.py +69 -0
  178. omnibase_infra/handlers/models/vault/enum_vault_operation_type.py +35 -0
  179. omnibase_infra/handlers/models/vault/model_payload_vault.py +66 -0
  180. omnibase_infra/handlers/models/vault/model_vault_delete_payload.py +57 -0
  181. omnibase_infra/handlers/models/vault/model_vault_handler_config.py +148 -0
  182. omnibase_infra/handlers/models/vault/model_vault_handler_payload.py +101 -0
  183. omnibase_infra/handlers/models/vault/model_vault_list_payload.py +58 -0
  184. omnibase_infra/handlers/models/vault/model_vault_renew_token_payload.py +67 -0
  185. omnibase_infra/handlers/models/vault/model_vault_retry_config.py +66 -0
  186. omnibase_infra/handlers/models/vault/model_vault_secret_payload.py +106 -0
  187. omnibase_infra/handlers/models/vault/model_vault_write_payload.py +66 -0
  188. omnibase_infra/handlers/models/vault/registry_payload_vault.py +213 -0
  189. omnibase_infra/handlers/registration_storage/__init__.py +43 -0
  190. omnibase_infra/handlers/registration_storage/handler_registration_storage_mock.py +392 -0
  191. omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +915 -0
  192. omnibase_infra/handlers/registration_storage/models/__init__.py +23 -0
  193. omnibase_infra/handlers/registration_storage/models/model_delete_registration_request.py +58 -0
  194. omnibase_infra/handlers/registration_storage/models/model_update_registration_request.py +73 -0
  195. omnibase_infra/handlers/registration_storage/protocol_registration_persistence.py +191 -0
  196. omnibase_infra/handlers/service_discovery/__init__.py +43 -0
  197. omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +747 -0
  198. omnibase_infra/handlers/service_discovery/handler_service_discovery_mock.py +258 -0
  199. omnibase_infra/handlers/service_discovery/models/__init__.py +22 -0
  200. omnibase_infra/handlers/service_discovery/models/model_discovery_result.py +64 -0
  201. omnibase_infra/handlers/service_discovery/models/model_registration_result.py +138 -0
  202. omnibase_infra/handlers/service_discovery/models/model_service_info.py +99 -0
  203. omnibase_infra/handlers/service_discovery/protocol_discovery_operations.py +170 -0
  204. omnibase_infra/idempotency/__init__.py +94 -0
  205. omnibase_infra/idempotency/models/__init__.py +43 -0
  206. omnibase_infra/idempotency/models/model_idempotency_check_result.py +85 -0
  207. omnibase_infra/idempotency/models/model_idempotency_guard_config.py +130 -0
  208. omnibase_infra/idempotency/models/model_idempotency_record.py +86 -0
  209. omnibase_infra/idempotency/models/model_idempotency_store_health_check_result.py +81 -0
  210. omnibase_infra/idempotency/models/model_idempotency_store_metrics.py +140 -0
  211. omnibase_infra/idempotency/models/model_postgres_idempotency_store_config.py +299 -0
  212. omnibase_infra/idempotency/protocol_idempotency_store.py +184 -0
  213. omnibase_infra/idempotency/store_inmemory.py +265 -0
  214. omnibase_infra/idempotency/store_postgres.py +923 -0
  215. omnibase_infra/infrastructure/__init__.py +0 -0
  216. omnibase_infra/mixins/__init__.py +71 -0
  217. omnibase_infra/mixins/mixin_async_circuit_breaker.py +655 -0
  218. omnibase_infra/mixins/mixin_dict_like_accessors.py +146 -0
  219. omnibase_infra/mixins/mixin_envelope_extraction.py +119 -0
  220. omnibase_infra/mixins/mixin_node_introspection.py +2465 -0
  221. omnibase_infra/mixins/mixin_retry_execution.py +386 -0
  222. omnibase_infra/mixins/protocol_circuit_breaker_aware.py +133 -0
  223. omnibase_infra/models/__init__.py +136 -0
  224. omnibase_infra/models/corpus/__init__.py +17 -0
  225. omnibase_infra/models/corpus/model_capture_config.py +133 -0
  226. omnibase_infra/models/corpus/model_capture_result.py +86 -0
  227. omnibase_infra/models/discovery/__init__.py +42 -0
  228. omnibase_infra/models/discovery/model_dependency_spec.py +319 -0
  229. omnibase_infra/models/discovery/model_discovered_capabilities.py +50 -0
  230. omnibase_infra/models/discovery/model_introspection_config.py +311 -0
  231. omnibase_infra/models/discovery/model_introspection_performance_metrics.py +169 -0
  232. omnibase_infra/models/discovery/model_introspection_task_config.py +116 -0
  233. omnibase_infra/models/dispatch/__init__.py +147 -0
  234. omnibase_infra/models/dispatch/model_dispatch_context.py +439 -0
  235. omnibase_infra/models/dispatch/model_dispatch_error.py +336 -0
  236. omnibase_infra/models/dispatch/model_dispatch_log_context.py +400 -0
  237. omnibase_infra/models/dispatch/model_dispatch_metadata.py +228 -0
  238. omnibase_infra/models/dispatch/model_dispatch_metrics.py +496 -0
  239. omnibase_infra/models/dispatch/model_dispatch_outcome.py +317 -0
  240. omnibase_infra/models/dispatch/model_dispatch_outputs.py +231 -0
  241. omnibase_infra/models/dispatch/model_dispatch_result.py +436 -0
  242. omnibase_infra/models/dispatch/model_dispatch_route.py +279 -0
  243. omnibase_infra/models/dispatch/model_dispatcher_metrics.py +275 -0
  244. omnibase_infra/models/dispatch/model_dispatcher_registration.py +352 -0
  245. omnibase_infra/models/dispatch/model_parsed_topic.py +135 -0
  246. omnibase_infra/models/dispatch/model_topic_parser.py +725 -0
  247. omnibase_infra/models/dispatch/model_tracing_context.py +285 -0
  248. omnibase_infra/models/errors/__init__.py +45 -0
  249. omnibase_infra/models/errors/model_handler_validation_error.py +594 -0
  250. omnibase_infra/models/errors/model_infra_error_context.py +99 -0
  251. omnibase_infra/models/errors/model_message_type_registry_error_context.py +71 -0
  252. omnibase_infra/models/errors/model_timeout_error_context.py +110 -0
  253. omnibase_infra/models/handlers/__init__.py +37 -0
  254. omnibase_infra/models/handlers/model_contract_discovery_result.py +80 -0
  255. omnibase_infra/models/handlers/model_handler_descriptor.py +185 -0
  256. omnibase_infra/models/handlers/model_handler_identifier.py +215 -0
  257. omnibase_infra/models/health/__init__.py +9 -0
  258. omnibase_infra/models/health/model_health_check_result.py +40 -0
  259. omnibase_infra/models/lifecycle/__init__.py +39 -0
  260. omnibase_infra/models/logging/__init__.py +51 -0
  261. omnibase_infra/models/logging/model_log_context.py +756 -0
  262. omnibase_infra/models/model_retry_error_classification.py +78 -0
  263. omnibase_infra/models/projection/__init__.py +43 -0
  264. omnibase_infra/models/projection/model_capability_fields.py +112 -0
  265. omnibase_infra/models/projection/model_registration_projection.py +434 -0
  266. omnibase_infra/models/projection/model_registration_snapshot.py +322 -0
  267. omnibase_infra/models/projection/model_sequence_info.py +182 -0
  268. omnibase_infra/models/projection/model_snapshot_topic_config.py +590 -0
  269. omnibase_infra/models/projectors/__init__.py +41 -0
  270. omnibase_infra/models/projectors/model_projector_column.py +289 -0
  271. omnibase_infra/models/projectors/model_projector_discovery_result.py +65 -0
  272. omnibase_infra/models/projectors/model_projector_index.py +270 -0
  273. omnibase_infra/models/projectors/model_projector_schema.py +415 -0
  274. omnibase_infra/models/projectors/model_projector_validation_error.py +63 -0
  275. omnibase_infra/models/projectors/util_sql_identifiers.py +115 -0
  276. omnibase_infra/models/registration/__init__.py +59 -0
  277. omnibase_infra/models/registration/commands/__init__.py +15 -0
  278. omnibase_infra/models/registration/commands/model_node_registration_acked.py +108 -0
  279. omnibase_infra/models/registration/events/__init__.py +56 -0
  280. omnibase_infra/models/registration/events/model_node_became_active.py +103 -0
  281. omnibase_infra/models/registration/events/model_node_liveness_expired.py +103 -0
  282. omnibase_infra/models/registration/events/model_node_registration_accepted.py +98 -0
  283. omnibase_infra/models/registration/events/model_node_registration_ack_received.py +98 -0
  284. omnibase_infra/models/registration/events/model_node_registration_ack_timed_out.py +112 -0
  285. omnibase_infra/models/registration/events/model_node_registration_initiated.py +107 -0
  286. omnibase_infra/models/registration/events/model_node_registration_rejected.py +104 -0
  287. omnibase_infra/models/registration/model_introspection_metrics.py +253 -0
  288. omnibase_infra/models/registration/model_node_capabilities.py +179 -0
  289. omnibase_infra/models/registration/model_node_heartbeat_event.py +126 -0
  290. omnibase_infra/models/registration/model_node_introspection_event.py +175 -0
  291. omnibase_infra/models/registration/model_node_metadata.py +79 -0
  292. omnibase_infra/models/registration/model_node_registration.py +162 -0
  293. omnibase_infra/models/registration/model_node_registration_record.py +162 -0
  294. omnibase_infra/models/registry/__init__.py +29 -0
  295. omnibase_infra/models/registry/model_domain_constraint.py +202 -0
  296. omnibase_infra/models/registry/model_message_type_entry.py +271 -0
  297. omnibase_infra/models/resilience/__init__.py +9 -0
  298. omnibase_infra/models/resilience/model_circuit_breaker_config.py +227 -0
  299. omnibase_infra/models/routing/__init__.py +25 -0
  300. omnibase_infra/models/routing/model_routing_entry.py +52 -0
  301. omnibase_infra/models/routing/model_routing_subcontract.py +70 -0
  302. omnibase_infra/models/runtime/__init__.py +40 -0
  303. omnibase_infra/models/runtime/model_contract_security_config.py +41 -0
  304. omnibase_infra/models/runtime/model_discovery_error.py +81 -0
  305. omnibase_infra/models/runtime/model_discovery_result.py +162 -0
  306. omnibase_infra/models/runtime/model_discovery_warning.py +74 -0
  307. omnibase_infra/models/runtime/model_failed_plugin_load.py +63 -0
  308. omnibase_infra/models/runtime/model_handler_contract.py +280 -0
  309. omnibase_infra/models/runtime/model_loaded_handler.py +120 -0
  310. omnibase_infra/models/runtime/model_plugin_load_context.py +93 -0
  311. omnibase_infra/models/runtime/model_plugin_load_summary.py +124 -0
  312. omnibase_infra/models/security/__init__.py +50 -0
  313. omnibase_infra/models/security/classification_levels.py +99 -0
  314. omnibase_infra/models/security/model_environment_policy.py +145 -0
  315. omnibase_infra/models/security/model_handler_security_policy.py +107 -0
  316. omnibase_infra/models/security/model_security_error.py +81 -0
  317. omnibase_infra/models/security/model_security_validation_result.py +328 -0
  318. omnibase_infra/models/security/model_security_warning.py +67 -0
  319. omnibase_infra/models/snapshot/__init__.py +27 -0
  320. omnibase_infra/models/snapshot/model_field_change.py +65 -0
  321. omnibase_infra/models/snapshot/model_snapshot.py +270 -0
  322. omnibase_infra/models/snapshot/model_snapshot_diff.py +203 -0
  323. omnibase_infra/models/snapshot/model_subject_ref.py +81 -0
  324. omnibase_infra/models/types/__init__.py +71 -0
  325. omnibase_infra/models/validation/__init__.py +89 -0
  326. omnibase_infra/models/validation/model_any_type_validation_result.py +118 -0
  327. omnibase_infra/models/validation/model_any_type_violation.py +141 -0
  328. omnibase_infra/models/validation/model_category_match_result.py +345 -0
  329. omnibase_infra/models/validation/model_chain_violation.py +166 -0
  330. omnibase_infra/models/validation/model_coverage_metrics.py +316 -0
  331. omnibase_infra/models/validation/model_execution_shape_rule.py +159 -0
  332. omnibase_infra/models/validation/model_execution_shape_validation.py +208 -0
  333. omnibase_infra/models/validation/model_execution_shape_validation_result.py +294 -0
  334. omnibase_infra/models/validation/model_execution_shape_violation.py +122 -0
  335. omnibase_infra/models/validation/model_localhandler_validation_result.py +139 -0
  336. omnibase_infra/models/validation/model_localhandler_violation.py +100 -0
  337. omnibase_infra/models/validation/model_output_validation_params.py +74 -0
  338. omnibase_infra/models/validation/model_validate_and_raise_params.py +84 -0
  339. omnibase_infra/models/validation/model_validation_error_params.py +84 -0
  340. omnibase_infra/models/validation/model_validation_outcome.py +287 -0
  341. omnibase_infra/nodes/__init__.py +48 -0
  342. omnibase_infra/nodes/architecture_validator/__init__.py +79 -0
  343. omnibase_infra/nodes/architecture_validator/contract.yaml +252 -0
  344. omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +208 -0
  345. omnibase_infra/nodes/architecture_validator/mixins/__init__.py +16 -0
  346. omnibase_infra/nodes/architecture_validator/mixins/mixin_file_path_rule.py +92 -0
  347. omnibase_infra/nodes/architecture_validator/models/__init__.py +36 -0
  348. omnibase_infra/nodes/architecture_validator/models/model_architecture_validation_request.py +56 -0
  349. omnibase_infra/nodes/architecture_validator/models/model_architecture_validation_result.py +311 -0
  350. omnibase_infra/nodes/architecture_validator/models/model_architecture_violation.py +163 -0
  351. omnibase_infra/nodes/architecture_validator/models/model_rule_check_result.py +265 -0
  352. omnibase_infra/nodes/architecture_validator/models/model_validation_request.py +105 -0
  353. omnibase_infra/nodes/architecture_validator/models/model_validation_result.py +314 -0
  354. omnibase_infra/nodes/architecture_validator/node.py +262 -0
  355. omnibase_infra/nodes/architecture_validator/node_architecture_validator.py +383 -0
  356. omnibase_infra/nodes/architecture_validator/protocols/__init__.py +9 -0
  357. omnibase_infra/nodes/architecture_validator/protocols/protocol_architecture_rule.py +225 -0
  358. omnibase_infra/nodes/architecture_validator/registry/__init__.py +28 -0
  359. omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +99 -0
  360. omnibase_infra/nodes/architecture_validator/validators/__init__.py +104 -0
  361. omnibase_infra/nodes/architecture_validator/validators/validator_no_direct_dispatch.py +422 -0
  362. omnibase_infra/nodes/architecture_validator/validators/validator_no_handler_publishing.py +481 -0
  363. omnibase_infra/nodes/architecture_validator/validators/validator_no_orchestrator_fsm.py +491 -0
  364. omnibase_infra/nodes/effects/README.md +358 -0
  365. omnibase_infra/nodes/effects/__init__.py +26 -0
  366. omnibase_infra/nodes/effects/contract.yaml +172 -0
  367. omnibase_infra/nodes/effects/models/__init__.py +32 -0
  368. omnibase_infra/nodes/effects/models/model_backend_result.py +190 -0
  369. omnibase_infra/nodes/effects/models/model_effect_idempotency_config.py +92 -0
  370. omnibase_infra/nodes/effects/models/model_registry_request.py +132 -0
  371. omnibase_infra/nodes/effects/models/model_registry_response.py +263 -0
  372. omnibase_infra/nodes/effects/protocol_consul_client.py +89 -0
  373. omnibase_infra/nodes/effects/protocol_effect_idempotency_store.py +143 -0
  374. omnibase_infra/nodes/effects/protocol_postgres_adapter.py +96 -0
  375. omnibase_infra/nodes/effects/registry_effect.py +525 -0
  376. omnibase_infra/nodes/effects/store_effect_idempotency_inmemory.py +425 -0
  377. omnibase_infra/nodes/node_registration_orchestrator/README.md +542 -0
  378. omnibase_infra/nodes/node_registration_orchestrator/__init__.py +120 -0
  379. omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +475 -0
  380. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/__init__.py +53 -0
  381. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_node_introspected.py +376 -0
  382. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_node_registration_acked.py +376 -0
  383. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_runtime_tick.py +373 -0
  384. omnibase_infra/nodes/node_registration_orchestrator/handlers/__init__.py +62 -0
  385. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_heartbeat.py +376 -0
  386. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +609 -0
  387. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_registration_acked.py +458 -0
  388. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_runtime_tick.py +364 -0
  389. omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +544 -0
  390. omnibase_infra/nodes/node_registration_orchestrator/models/__init__.py +75 -0
  391. omnibase_infra/nodes/node_registration_orchestrator/models/model_consul_intent_payload.py +194 -0
  392. omnibase_infra/nodes/node_registration_orchestrator/models/model_consul_registration_intent.py +67 -0
  393. omnibase_infra/nodes/node_registration_orchestrator/models/model_intent_execution_result.py +50 -0
  394. omnibase_infra/nodes/node_registration_orchestrator/models/model_node_liveness_expired.py +107 -0
  395. omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_config.py +67 -0
  396. omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_input.py +41 -0
  397. omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_output.py +166 -0
  398. omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_intent_payload.py +235 -0
  399. omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_upsert_intent.py +68 -0
  400. omnibase_infra/nodes/node_registration_orchestrator/models/model_reducer_execution_result.py +384 -0
  401. omnibase_infra/nodes/node_registration_orchestrator/models/model_reducer_state.py +60 -0
  402. omnibase_infra/nodes/node_registration_orchestrator/models/model_registration_intent.py +177 -0
  403. omnibase_infra/nodes/node_registration_orchestrator/models/model_registry_intent.py +247 -0
  404. omnibase_infra/nodes/node_registration_orchestrator/node.py +195 -0
  405. omnibase_infra/nodes/node_registration_orchestrator/plugin.py +909 -0
  406. omnibase_infra/nodes/node_registration_orchestrator/protocols.py +439 -0
  407. omnibase_infra/nodes/node_registration_orchestrator/registry/__init__.py +41 -0
  408. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +525 -0
  409. omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +392 -0
  410. omnibase_infra/nodes/node_registration_orchestrator/wiring.py +742 -0
  411. omnibase_infra/nodes/node_registration_reducer/__init__.py +15 -0
  412. omnibase_infra/nodes/node_registration_reducer/contract.yaml +301 -0
  413. omnibase_infra/nodes/node_registration_reducer/models/__init__.py +38 -0
  414. omnibase_infra/nodes/node_registration_reducer/models/model_validation_result.py +113 -0
  415. omnibase_infra/nodes/node_registration_reducer/node.py +139 -0
  416. omnibase_infra/nodes/node_registration_reducer/registry/__init__.py +9 -0
  417. omnibase_infra/nodes/node_registration_reducer/registry/registry_infra_node_registration_reducer.py +79 -0
  418. omnibase_infra/nodes/node_registration_storage_effect/__init__.py +41 -0
  419. omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +225 -0
  420. omnibase_infra/nodes/node_registration_storage_effect/models/__init__.py +44 -0
  421. omnibase_infra/nodes/node_registration_storage_effect/models/model_delete_result.py +132 -0
  422. omnibase_infra/nodes/node_registration_storage_effect/models/model_registration_record.py +199 -0
  423. omnibase_infra/nodes/node_registration_storage_effect/models/model_registration_update.py +155 -0
  424. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_health_check_details.py +123 -0
  425. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_health_check_result.py +117 -0
  426. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_query.py +100 -0
  427. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_result.py +136 -0
  428. omnibase_infra/nodes/node_registration_storage_effect/models/model_upsert_result.py +127 -0
  429. omnibase_infra/nodes/node_registration_storage_effect/node.py +109 -0
  430. omnibase_infra/nodes/node_registration_storage_effect/protocols/__init__.py +22 -0
  431. omnibase_infra/nodes/node_registration_storage_effect/protocols/protocol_registration_persistence.py +333 -0
  432. omnibase_infra/nodes/node_registration_storage_effect/registry/__init__.py +23 -0
  433. omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +194 -0
  434. omnibase_infra/nodes/node_registry_effect/__init__.py +85 -0
  435. omnibase_infra/nodes/node_registry_effect/contract.yaml +682 -0
  436. omnibase_infra/nodes/node_registry_effect/handlers/__init__.py +70 -0
  437. omnibase_infra/nodes/node_registry_effect/handlers/handler_consul_deregister.py +211 -0
  438. omnibase_infra/nodes/node_registry_effect/handlers/handler_consul_register.py +212 -0
  439. omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +416 -0
  440. omnibase_infra/nodes/node_registry_effect/handlers/handler_postgres_deactivate.py +215 -0
  441. omnibase_infra/nodes/node_registry_effect/handlers/handler_postgres_upsert.py +208 -0
  442. omnibase_infra/nodes/node_registry_effect/models/__init__.py +43 -0
  443. omnibase_infra/nodes/node_registry_effect/models/model_partial_retry_request.py +92 -0
  444. omnibase_infra/nodes/node_registry_effect/node.py +165 -0
  445. omnibase_infra/nodes/node_registry_effect/registry/__init__.py +27 -0
  446. omnibase_infra/nodes/node_registry_effect/registry/registry_infra_registry_effect.py +196 -0
  447. omnibase_infra/nodes/node_service_discovery_effect/__init__.py +111 -0
  448. omnibase_infra/nodes/node_service_discovery_effect/contract.yaml +246 -0
  449. omnibase_infra/nodes/node_service_discovery_effect/models/__init__.py +67 -0
  450. omnibase_infra/nodes/node_service_discovery_effect/models/enum_health_status.py +72 -0
  451. omnibase_infra/nodes/node_service_discovery_effect/models/enum_service_discovery_operation.py +58 -0
  452. omnibase_infra/nodes/node_service_discovery_effect/models/model_discovery_query.py +99 -0
  453. omnibase_infra/nodes/node_service_discovery_effect/models/model_discovery_result.py +98 -0
  454. omnibase_infra/nodes/node_service_discovery_effect/models/model_health_check_config.py +121 -0
  455. omnibase_infra/nodes/node_service_discovery_effect/models/model_query_metadata.py +63 -0
  456. omnibase_infra/nodes/node_service_discovery_effect/models/model_registration_result.py +130 -0
  457. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_discovery_health_check_details.py +111 -0
  458. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_discovery_health_check_result.py +119 -0
  459. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_info.py +106 -0
  460. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_registration.py +121 -0
  461. omnibase_infra/nodes/node_service_discovery_effect/node.py +111 -0
  462. omnibase_infra/nodes/node_service_discovery_effect/protocols/__init__.py +14 -0
  463. omnibase_infra/nodes/node_service_discovery_effect/protocols/protocol_discovery_operations.py +279 -0
  464. omnibase_infra/nodes/node_service_discovery_effect/registry/__init__.py +13 -0
  465. omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +214 -0
  466. omnibase_infra/nodes/reducers/__init__.py +30 -0
  467. omnibase_infra/nodes/reducers/models/__init__.py +32 -0
  468. omnibase_infra/nodes/reducers/models/model_payload_consul_register.py +76 -0
  469. omnibase_infra/nodes/reducers/models/model_payload_postgres_upsert_registration.py +60 -0
  470. omnibase_infra/nodes/reducers/models/model_registration_confirmation.py +166 -0
  471. omnibase_infra/nodes/reducers/models/model_registration_state.py +433 -0
  472. omnibase_infra/nodes/reducers/registration_reducer.py +1137 -0
  473. omnibase_infra/observability/__init__.py +143 -0
  474. omnibase_infra/observability/constants_metrics.py +91 -0
  475. omnibase_infra/observability/factory_observability_sink.py +525 -0
  476. omnibase_infra/observability/handlers/__init__.py +118 -0
  477. omnibase_infra/observability/handlers/handler_logging_structured.py +967 -0
  478. omnibase_infra/observability/handlers/handler_metrics_prometheus.py +1120 -0
  479. omnibase_infra/observability/handlers/model_logging_handler_config.py +71 -0
  480. omnibase_infra/observability/handlers/model_logging_handler_response.py +77 -0
  481. omnibase_infra/observability/handlers/model_metrics_handler_config.py +172 -0
  482. omnibase_infra/observability/handlers/model_metrics_handler_payload.py +135 -0
  483. omnibase_infra/observability/handlers/model_metrics_handler_response.py +101 -0
  484. omnibase_infra/observability/hooks/__init__.py +74 -0
  485. omnibase_infra/observability/hooks/hook_observability.py +1223 -0
  486. omnibase_infra/observability/models/__init__.py +30 -0
  487. omnibase_infra/observability/models/enum_required_log_context_key.py +77 -0
  488. omnibase_infra/observability/models/model_buffered_log_entry.py +117 -0
  489. omnibase_infra/observability/models/model_logging_sink_config.py +73 -0
  490. omnibase_infra/observability/models/model_metrics_sink_config.py +156 -0
  491. omnibase_infra/observability/sinks/__init__.py +69 -0
  492. omnibase_infra/observability/sinks/sink_logging_structured.py +809 -0
  493. omnibase_infra/observability/sinks/sink_metrics_prometheus.py +710 -0
  494. omnibase_infra/plugins/__init__.py +27 -0
  495. omnibase_infra/plugins/examples/__init__.py +28 -0
  496. omnibase_infra/plugins/examples/plugin_json_normalizer.py +271 -0
  497. omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +210 -0
  498. omnibase_infra/plugins/models/__init__.py +21 -0
  499. omnibase_infra/plugins/models/model_plugin_context.py +76 -0
  500. omnibase_infra/plugins/models/model_plugin_input_data.py +58 -0
  501. omnibase_infra/plugins/models/model_plugin_output_data.py +62 -0
  502. omnibase_infra/plugins/plugin_compute_base.py +435 -0
  503. omnibase_infra/projectors/__init__.py +30 -0
  504. omnibase_infra/projectors/contracts/__init__.py +63 -0
  505. omnibase_infra/projectors/contracts/registration_projector.yaml +370 -0
  506. omnibase_infra/projectors/projection_reader_registration.py +1559 -0
  507. omnibase_infra/projectors/snapshot_publisher_registration.py +1329 -0
  508. omnibase_infra/protocols/__init__.py +99 -0
  509. omnibase_infra/protocols/protocol_capability_projection.py +253 -0
  510. omnibase_infra/protocols/protocol_capability_query.py +251 -0
  511. omnibase_infra/protocols/protocol_event_bus_like.py +127 -0
  512. omnibase_infra/protocols/protocol_event_projector.py +96 -0
  513. omnibase_infra/protocols/protocol_idempotency_store.py +142 -0
  514. omnibase_infra/protocols/protocol_message_dispatcher.py +247 -0
  515. omnibase_infra/protocols/protocol_message_type_registry.py +306 -0
  516. omnibase_infra/protocols/protocol_plugin_compute.py +368 -0
  517. omnibase_infra/protocols/protocol_projector_schema_validator.py +82 -0
  518. omnibase_infra/protocols/protocol_registry_metrics.py +215 -0
  519. omnibase_infra/protocols/protocol_snapshot_publisher.py +396 -0
  520. omnibase_infra/protocols/protocol_snapshot_store.py +567 -0
  521. omnibase_infra/runtime/__init__.py +296 -0
  522. omnibase_infra/runtime/binding_config_resolver.py +2706 -0
  523. omnibase_infra/runtime/chain_aware_dispatch.py +467 -0
  524. omnibase_infra/runtime/contract_handler_discovery.py +582 -0
  525. omnibase_infra/runtime/contract_loaders/__init__.py +42 -0
  526. omnibase_infra/runtime/contract_loaders/handler_routing_loader.py +464 -0
  527. omnibase_infra/runtime/dispatch_context_enforcer.py +427 -0
  528. omnibase_infra/runtime/enums/__init__.py +18 -0
  529. omnibase_infra/runtime/enums/enum_config_ref_scheme.py +33 -0
  530. omnibase_infra/runtime/enums/enum_scheduler_status.py +170 -0
  531. omnibase_infra/runtime/envelope_validator.py +179 -0
  532. omnibase_infra/runtime/handler_contract_source.py +669 -0
  533. omnibase_infra/runtime/handler_plugin_loader.py +2029 -0
  534. omnibase_infra/runtime/handler_registry.py +321 -0
  535. omnibase_infra/runtime/invocation_security_enforcer.py +427 -0
  536. omnibase_infra/runtime/kernel.py +40 -0
  537. omnibase_infra/runtime/mixin_policy_validation.py +522 -0
  538. omnibase_infra/runtime/mixin_semver_cache.py +378 -0
  539. omnibase_infra/runtime/mixins/__init__.py +17 -0
  540. omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +757 -0
  541. omnibase_infra/runtime/models/__init__.py +192 -0
  542. omnibase_infra/runtime/models/model_batch_lifecycle_result.py +217 -0
  543. omnibase_infra/runtime/models/model_binding_config.py +168 -0
  544. omnibase_infra/runtime/models/model_binding_config_cache_stats.py +135 -0
  545. omnibase_infra/runtime/models/model_binding_config_resolver_config.py +329 -0
  546. omnibase_infra/runtime/models/model_cached_secret.py +138 -0
  547. omnibase_infra/runtime/models/model_compute_key.py +138 -0
  548. omnibase_infra/runtime/models/model_compute_registration.py +97 -0
  549. omnibase_infra/runtime/models/model_config_cache_entry.py +61 -0
  550. omnibase_infra/runtime/models/model_config_ref.py +331 -0
  551. omnibase_infra/runtime/models/model_config_ref_parse_result.py +125 -0
  552. omnibase_infra/runtime/models/model_domain_plugin_config.py +92 -0
  553. omnibase_infra/runtime/models/model_domain_plugin_result.py +270 -0
  554. omnibase_infra/runtime/models/model_duplicate_response.py +54 -0
  555. omnibase_infra/runtime/models/model_enabled_protocols_config.py +61 -0
  556. omnibase_infra/runtime/models/model_event_bus_config.py +54 -0
  557. omnibase_infra/runtime/models/model_failed_component.py +55 -0
  558. omnibase_infra/runtime/models/model_health_check_response.py +168 -0
  559. omnibase_infra/runtime/models/model_health_check_result.py +228 -0
  560. omnibase_infra/runtime/models/model_lifecycle_result.py +245 -0
  561. omnibase_infra/runtime/models/model_logging_config.py +42 -0
  562. omnibase_infra/runtime/models/model_optional_correlation_id.py +167 -0
  563. omnibase_infra/runtime/models/model_optional_string.py +94 -0
  564. omnibase_infra/runtime/models/model_optional_uuid.py +110 -0
  565. omnibase_infra/runtime/models/model_policy_context.py +100 -0
  566. omnibase_infra/runtime/models/model_policy_key.py +138 -0
  567. omnibase_infra/runtime/models/model_policy_registration.py +139 -0
  568. omnibase_infra/runtime/models/model_policy_result.py +103 -0
  569. omnibase_infra/runtime/models/model_policy_type_filter.py +157 -0
  570. omnibase_infra/runtime/models/model_projector_plugin_loader_config.py +47 -0
  571. omnibase_infra/runtime/models/model_protocol_registration_config.py +65 -0
  572. omnibase_infra/runtime/models/model_retry_policy.py +105 -0
  573. omnibase_infra/runtime/models/model_runtime_config.py +150 -0
  574. omnibase_infra/runtime/models/model_runtime_scheduler_config.py +624 -0
  575. omnibase_infra/runtime/models/model_runtime_scheduler_metrics.py +233 -0
  576. omnibase_infra/runtime/models/model_runtime_tick.py +193 -0
  577. omnibase_infra/runtime/models/model_secret_cache_stats.py +82 -0
  578. omnibase_infra/runtime/models/model_secret_mapping.py +63 -0
  579. omnibase_infra/runtime/models/model_secret_resolver_config.py +107 -0
  580. omnibase_infra/runtime/models/model_secret_resolver_metrics.py +111 -0
  581. omnibase_infra/runtime/models/model_secret_source_info.py +72 -0
  582. omnibase_infra/runtime/models/model_secret_source_spec.py +66 -0
  583. omnibase_infra/runtime/models/model_shutdown_batch_result.py +75 -0
  584. omnibase_infra/runtime/models/model_shutdown_config.py +94 -0
  585. omnibase_infra/runtime/projector_plugin_loader.py +1462 -0
  586. omnibase_infra/runtime/projector_schema_manager.py +565 -0
  587. omnibase_infra/runtime/projector_shell.py +1102 -0
  588. omnibase_infra/runtime/protocol_contract_descriptor.py +92 -0
  589. omnibase_infra/runtime/protocol_contract_source.py +92 -0
  590. omnibase_infra/runtime/protocol_domain_plugin.py +474 -0
  591. omnibase_infra/runtime/protocol_handler_discovery.py +221 -0
  592. omnibase_infra/runtime/protocol_handler_plugin_loader.py +327 -0
  593. omnibase_infra/runtime/protocol_lifecycle_executor.py +435 -0
  594. omnibase_infra/runtime/protocol_policy.py +366 -0
  595. omnibase_infra/runtime/protocols/__init__.py +27 -0
  596. omnibase_infra/runtime/protocols/protocol_runtime_scheduler.py +468 -0
  597. omnibase_infra/runtime/registry/__init__.py +93 -0
  598. omnibase_infra/runtime/registry/mixin_message_type_query.py +326 -0
  599. omnibase_infra/runtime/registry/mixin_message_type_registration.py +354 -0
  600. omnibase_infra/runtime/registry/registry_event_bus_binding.py +268 -0
  601. omnibase_infra/runtime/registry/registry_message_type.py +542 -0
  602. omnibase_infra/runtime/registry/registry_protocol_binding.py +444 -0
  603. omnibase_infra/runtime/registry_compute.py +1143 -0
  604. omnibase_infra/runtime/registry_dispatcher.py +678 -0
  605. omnibase_infra/runtime/registry_policy.py +1502 -0
  606. omnibase_infra/runtime/runtime_scheduler.py +1070 -0
  607. omnibase_infra/runtime/secret_resolver.py +2110 -0
  608. omnibase_infra/runtime/security_metadata_validator.py +776 -0
  609. omnibase_infra/runtime/service_kernel.py +1573 -0
  610. omnibase_infra/runtime/service_message_dispatch_engine.py +1805 -0
  611. omnibase_infra/runtime/service_runtime_host_process.py +2260 -0
  612. omnibase_infra/runtime/util_container_wiring.py +1123 -0
  613. omnibase_infra/runtime/util_validation.py +314 -0
  614. omnibase_infra/runtime/util_version.py +98 -0
  615. omnibase_infra/runtime/util_wiring.py +566 -0
  616. omnibase_infra/schemas/schema_registration_projection.sql +320 -0
  617. omnibase_infra/services/__init__.py +68 -0
  618. omnibase_infra/services/corpus_capture.py +678 -0
  619. omnibase_infra/services/service_capability_query.py +945 -0
  620. omnibase_infra/services/service_health.py +897 -0
  621. omnibase_infra/services/service_node_selector.py +530 -0
  622. omnibase_infra/services/service_timeout_emitter.py +682 -0
  623. omnibase_infra/services/service_timeout_scanner.py +390 -0
  624. omnibase_infra/services/snapshot/__init__.py +31 -0
  625. omnibase_infra/services/snapshot/service_snapshot.py +647 -0
  626. omnibase_infra/services/snapshot/store_inmemory.py +637 -0
  627. omnibase_infra/services/snapshot/store_postgres.py +1279 -0
  628. omnibase_infra/shared/__init__.py +8 -0
  629. omnibase_infra/testing/__init__.py +10 -0
  630. omnibase_infra/testing/utils.py +23 -0
  631. omnibase_infra/types/__init__.py +48 -0
  632. omnibase_infra/types/type_cache_info.py +49 -0
  633. omnibase_infra/types/type_dsn.py +173 -0
  634. omnibase_infra/types/type_infra_aliases.py +60 -0
  635. omnibase_infra/types/typed_dict/__init__.py +21 -0
  636. omnibase_infra/types/typed_dict/typed_dict_introspection_cache.py +128 -0
  637. omnibase_infra/types/typed_dict/typed_dict_performance_metrics_cache.py +140 -0
  638. omnibase_infra/types/typed_dict_capabilities.py +64 -0
  639. omnibase_infra/utils/__init__.py +89 -0
  640. omnibase_infra/utils/correlation.py +208 -0
  641. omnibase_infra/utils/util_datetime.py +372 -0
  642. omnibase_infra/utils/util_dsn_validation.py +333 -0
  643. omnibase_infra/utils/util_env_parsing.py +264 -0
  644. omnibase_infra/utils/util_error_sanitization.py +457 -0
  645. omnibase_infra/utils/util_pydantic_validators.py +477 -0
  646. omnibase_infra/utils/util_semver.py +233 -0
  647. omnibase_infra/validation/__init__.py +307 -0
  648. omnibase_infra/validation/enums/__init__.py +11 -0
  649. omnibase_infra/validation/enums/enum_contract_violation_severity.py +13 -0
  650. omnibase_infra/validation/infra_validators.py +1486 -0
  651. omnibase_infra/validation/linter_contract.py +907 -0
  652. omnibase_infra/validation/mixin_any_type_classification.py +120 -0
  653. omnibase_infra/validation/mixin_any_type_exemption.py +580 -0
  654. omnibase_infra/validation/mixin_any_type_reporting.py +106 -0
  655. omnibase_infra/validation/mixin_execution_shape_violation_checks.py +596 -0
  656. omnibase_infra/validation/mixin_node_archetype_detection.py +254 -0
  657. omnibase_infra/validation/models/__init__.py +15 -0
  658. omnibase_infra/validation/models/model_contract_lint_result.py +101 -0
  659. omnibase_infra/validation/models/model_contract_violation.py +41 -0
  660. omnibase_infra/validation/service_validation_aggregator.py +395 -0
  661. omnibase_infra/validation/validation_exemptions.yaml +1710 -0
  662. omnibase_infra/validation/validator_any_type.py +715 -0
  663. omnibase_infra/validation/validator_chain_propagation.py +839 -0
  664. omnibase_infra/validation/validator_execution_shape.py +465 -0
  665. omnibase_infra/validation/validator_localhandler.py +261 -0
  666. omnibase_infra/validation/validator_registration_security.py +410 -0
  667. omnibase_infra/validation/validator_routing_coverage.py +1020 -0
  668. omnibase_infra/validation/validator_runtime_shape.py +915 -0
  669. omnibase_infra/validation/validator_security.py +410 -0
  670. omnibase_infra/validation/validator_topic_category.py +1152 -0
  671. omnibase_infra-0.2.1.dist-info/METADATA +197 -0
  672. omnibase_infra-0.2.1.dist-info/RECORD +675 -0
  673. omnibase_infra-0.2.1.dist-info/WHEEL +4 -0
  674. omnibase_infra-0.2.1.dist-info/entry_points.txt +4 -0
  675. omnibase_infra-0.2.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1223 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Pipeline hook for cross-cutting observability concerns.
4
+
5
+ This module provides the HookObservability class, a pipeline hook that enables
6
+ cross-cutting observability instrumentation for infrastructure components.
7
+ The hook tracks operation timing, emits metrics, and maintains execution context
8
+ across async boundaries.
9
+
10
+ CRITICAL: Concurrency Safety via contextvars
11
+ --------------------------------------------
12
+ This implementation uses contextvars exclusively for all timing and operation
13
+ state. This is a CRITICAL design decision to prevent concurrency bugs in async
14
+ code. Each async task gets its own isolated context, preventing race conditions
15
+ where multiple concurrent operations would corrupt shared timing state.
16
+
17
+ Why NOT use instance variables:
18
+ # WRONG - Race condition in async code!
19
+ class BadHook:
20
+ def __init__(self):
21
+ self._start_time = 0.0 # Shared across all concurrent operations!
22
+
23
+ def before_operation(self, operation: str):
24
+ self._start_time = time.perf_counter() # Overwrites previous!
25
+
26
+ def after_operation(self):
27
+ return time.perf_counter() - self._start_time # Wrong value!
28
+
29
+ Why contextvars ARE correct:
30
+ # CORRECT - Each async task has isolated state
31
+ _start_time: ContextVar[float | None] = ContextVar("start_time", default=None)
32
+
33
+ class GoodHook:
34
+ def before_operation(self, operation: str):
35
+ _start_time.set(time.perf_counter()) # Isolated per-task
36
+
37
+ def after_operation(self):
38
+ return time.perf_counter() - _start_time.get() # Correct per-task
39
+
40
+ Thread-Safety Guarantees
41
+ ------------------------
42
+ This class is thread-safe with the following guarantees:
43
+
44
+ 1. **Timing State**: All timing and operation state is stored in contextvars,
45
+ which provide per-task isolation in async code and per-thread isolation in
46
+ threaded code. Concurrent operations NEVER share timing state.
47
+
48
+ 2. **Metrics Sink**: The metrics_sink is accessed via a read-only property
49
+ from multiple async tasks. The metrics_sink implementation MUST be
50
+ thread-safe. The built-in SinkMetricsPrometheus satisfies this requirement
51
+ via internal locking. If using a custom sink, ensure it is thread-safe.
52
+ The metrics_sink is set once during __init__ and exposed as read-only to
53
+ prevent accidental modification.
54
+
55
+ 3. **Class Constants**: _HIGH_CARDINALITY_KEYS is a frozenset (immutable),
56
+ ensuring thread-safe read access.
57
+
58
+ 4. **Instance Variables**: The only instance variable (__metrics_sink) is set
59
+ once during __init__ and exposed via a read-only property, ensuring
60
+ thread-safe reads and preventing modification after construction.
61
+
62
+ 5. **Module-Level Singleton**: The global singleton hook instance is protected
63
+ by a threading.Lock for thread-safe initialization across threads.
64
+
65
+ Singleton Support
66
+ -----------------
67
+ This module provides optional singleton support via get_global_hook() and
68
+ configure_global_hook(). The singleton pattern is useful when:
69
+
70
+ - Multiple components need to share the same observability infrastructure
71
+ - You want centralized metrics collection across the application
72
+ - Resource efficiency is important
73
+
74
+ Important: When a singleton already exists, calling configure_global_hook()
75
+ with different configuration will log a warning and return the existing
76
+ instance. Use clear_global_hook() to reset the singleton if reconfiguration
77
+ is needed.
78
+
79
+ Usage Example:
80
+ ```python
81
+ from omnibase_infra.observability.hooks import HookObservability
82
+ from omnibase_spi.protocols.observability import ProtocolHotPathMetricsSink
83
+
84
+ # Create hook with optional metrics sink
85
+ sink: ProtocolHotPathMetricsSink = get_metrics_sink()
86
+ hook = HookObservability(metrics_sink=sink)
87
+
88
+ # Use in handler execution
89
+ hook.before_operation("handler.execute", correlation_id="abc-123")
90
+ try:
91
+ result = await handler.execute(payload)
92
+ hook.record_success()
93
+ except Exception as e:
94
+ hook.record_failure(str(type(e).__name__))
95
+ raise
96
+ finally:
97
+ duration_ms = hook.after_operation()
98
+ logger.info(f"Operation took {duration_ms:.2f}ms")
99
+
100
+ # Or use the global singleton
101
+ from omnibase_infra.observability.hooks import get_global_hook
102
+ hook = get_global_hook() # Returns singleton, creates if needed
103
+ ```
104
+
105
+ See Also:
106
+ - ProtocolHotPathMetricsSink: Metrics collection interface
107
+ - correlation.py: Correlation ID context management pattern
108
+ - docs/patterns/observability_patterns.md: Observability guidelines
109
+ """
110
+
111
+ from __future__ import annotations
112
+
113
+ import logging
114
+ import threading
115
+ import time
116
+ from contextvars import ContextVar, Token
117
+ from typing import TYPE_CHECKING
118
+ from uuid import UUID
119
+
120
+ _logger = logging.getLogger(__name__)
121
+
122
+ # =============================================================================
123
+ # MODULE-LEVEL SINGLETON STATE
124
+ # =============================================================================
125
+ #
126
+ # Thread-safe singleton support for global hook instance. Protected by
127
+ # _global_hook_lock for safe initialization from multiple threads.
128
+ #
129
+ # =============================================================================
130
+
131
+ # Forward reference resolved by `from __future__ import annotations`
132
+ _global_hook_instance: HookObservability | None = None
133
+ _global_hook_lock = threading.Lock()
134
+
135
+ if TYPE_CHECKING:
136
+ from types import TracebackType
137
+
138
+ from omnibase_spi.protocols.observability import ProtocolHotPathMetricsSink
139
+
140
+ # =============================================================================
141
+ # CONTEXT VARIABLES FOR CONCURRENCY-SAFE OPERATION TRACKING
142
+ # =============================================================================
143
+ #
144
+ # These ContextVars provide per-async-task isolation for operation state.
145
+ # Each concurrent operation gets its own isolated copy of these values,
146
+ # preventing race conditions in high-concurrency environments.
147
+ #
148
+ # DO NOT convert these to instance variables - that would break concurrency!
149
+ # =============================================================================
150
+
151
+ # Operation start time in perf_counter units (high-resolution monotonic clock)
152
+ _start_time: ContextVar[float | None] = ContextVar("hook_start_time", default=None)
153
+
154
+ # Current operation name (e.g., "handler.execute", "retry.attempt")
155
+ _operation_name: ContextVar[str | None] = ContextVar(
156
+ "hook_operation_name", default=None
157
+ )
158
+
159
+ # Correlation ID for distributed tracing (propagated from request context)
160
+ _correlation_id: ContextVar[str | None] = ContextVar(
161
+ "hook_correlation_id", default=None
162
+ )
163
+
164
+ # Additional operation labels for metrics (e.g., handler name, status)
165
+ # Note: ContextVar doesn't support default_factory, so we use None and handle it
166
+ _operation_labels: ContextVar[dict[str, str] | None] = ContextVar(
167
+ "hook_operation_labels", default=None
168
+ )
169
+
170
+
171
+ class HookObservability:
172
+ """Pipeline hook for cross-cutting observability instrumentation.
173
+
174
+ This hook provides timing, metrics, and context management for infrastructure
175
+ operations. It uses contextvars for all state to ensure concurrency safety
176
+ in async code paths.
177
+
178
+ Key Features:
179
+ - Concurrency-safe timing via contextvars (NOT instance variables)
180
+ - Metrics emission via ProtocolHotPathMetricsSink
181
+ - Operation context propagation across async boundaries
182
+ - Support for nested operation tracking via context manager
183
+ - High-cardinality label filtering to prevent metric explosion
184
+
185
+ Thread-Safety Guarantees:
186
+ This class is safe for concurrent use from multiple async tasks.
187
+ All timing and operation state is stored in contextvars, which provide
188
+ per-task isolation. The metrics sink (if provided) MUST be thread-safe.
189
+
190
+ Specific guarantees:
191
+ 1. Timing state (start_time, operation_name, etc.) is isolated per-task
192
+ 2. The metrics_sink is set once in __init__ and never modified
193
+ 3. _HIGH_CARDINALITY_KEYS is immutable (frozenset)
194
+ 4. No mutable shared state exists between concurrent operations
195
+
196
+ High-Cardinality Label Filtering:
197
+ Labels containing high-cardinality keys (correlation_id, request_id,
198
+ trace_id, span_id, session_id, user_id) are automatically filtered
199
+ from metrics to prevent cardinality explosion. These values remain
200
+ available via get_current_context() for logging and tracing.
201
+
202
+ When labels are filtered, a debug log is emitted to aid troubleshooting.
203
+ Metrics are NEVER dropped entirely - only high-cardinality labels are
204
+ removed from the label set.
205
+
206
+ Metrics Emitted:
207
+ - `operation_started_total`: Counter incremented when operation starts
208
+ - `operation_completed_total`: Counter incremented when operation completes
209
+ - `operation_failed_total`: Counter incremented on failure
210
+ - `operation_duration_seconds`: Histogram of operation durations
211
+ - `retry_attempt_total`: Counter for retry attempts
212
+ - `circuit_breaker_state_change_total`: Counter for circuit state changes
213
+
214
+ Attributes:
215
+ metrics_sink: Read-only property for the metrics sink. Returns None if
216
+ no sink was provided. The sink MUST be thread-safe for concurrent
217
+ access from multiple async tasks.
218
+
219
+ Example:
220
+ ```python
221
+ hook = HookObservability(metrics_sink=sink)
222
+
223
+ # Manual instrumentation
224
+ hook.before_operation("db.query", correlation_id="req-123")
225
+ try:
226
+ result = await db.execute(query)
227
+ hook.record_success()
228
+ except Exception:
229
+ hook.record_failure("DatabaseError")
230
+ raise
231
+ finally:
232
+ duration = hook.after_operation()
233
+
234
+ # Context manager for automatic timing
235
+ with hook.operation_context("http.request", correlation_id="req-456"):
236
+ response = await http_client.get(url)
237
+ ```
238
+ """
239
+
240
+ # Use __slots__ to prevent accidental attribute addition and improve memory
241
+ # _metrics_lock provides defense-in-depth thread safety for metrics operations
242
+ __slots__ = ("__metrics_sink", "_metrics_lock")
243
+
244
+ def __init__(
245
+ self,
246
+ metrics_sink: ProtocolHotPathMetricsSink | None = None,
247
+ ) -> None:
248
+ """Initialize the observability hook.
249
+
250
+ Args:
251
+ metrics_sink: Optional metrics sink for emitting observability data.
252
+ If None, the hook operates in no-op mode for metrics (timing
253
+ is still tracked). This allows the hook to be used even when
254
+ metrics infrastructure is not available.
255
+
256
+ Thread-Safety Requirements:
257
+ The metrics_sink (if provided) MUST be thread-safe for concurrent
258
+ access from multiple async tasks. The built-in SinkMetricsPrometheus
259
+ satisfies this requirement. If using a custom sink implementation,
260
+ ensure all methods (increment_counter, set_gauge, observe_histogram)
261
+ are thread-safe.
262
+
263
+ Note:
264
+ The metrics_sink is stored as a private instance variable and
265
+ exposed via a read-only property. This prevents accidental
266
+ modification after construction, ensuring thread-safe reads.
267
+ This is intentionally different from timing state which MUST be
268
+ in contextvars for per-task isolation.
269
+
270
+ Thread-Safety Implementation:
271
+ A threading.Lock (_metrics_lock) is used for defense-in-depth protection
272
+ of all metrics sink operations. While the metrics_sink is expected to be
273
+ thread-safe, this lock ensures atomic operation sequences and protects
274
+ against potential subtle thread-safety issues in sink implementations.
275
+ """
276
+ # Use name mangling (__) to prevent external modification
277
+ # Access via the metrics_sink property
278
+ self.__metrics_sink = metrics_sink
279
+ # Defense-in-depth lock for metrics operations
280
+ # Ensures atomic counter increments and metric emissions even if sink
281
+ # has subtle thread-safety issues
282
+ self._metrics_lock = threading.Lock()
283
+
284
+ @property
285
+ def metrics_sink(self) -> ProtocolHotPathMetricsSink | None:
286
+ """Read-only access to the metrics sink.
287
+
288
+ Returns:
289
+ The metrics sink provided during construction, or None if no
290
+ sink was provided.
291
+
292
+ Thread-Safety:
293
+ This property is thread-safe. The underlying value is set once
294
+ during __init__ and never modified.
295
+ """
296
+ return self.__metrics_sink
297
+
298
+ # =========================================================================
299
+ # CORE TIMING API
300
+ # =========================================================================
301
+
302
+ def before_operation(
303
+ self,
304
+ operation: str,
305
+ correlation_id: str | UUID | None = None,
306
+ labels: dict[str, str] | None = None,
307
+ ) -> None:
308
+ """Mark the start of an operation for timing.
309
+
310
+ Sets up the timing context for the current async task. This method
311
+ MUST be called before after_operation() to establish the start time.
312
+
313
+ Concurrency Safety:
314
+ Uses contextvars to store timing state, ensuring each async task
315
+ has its own isolated start time. Multiple concurrent operations
316
+ will not interfere with each other.
317
+
318
+ Args:
319
+ operation: Name of the operation being tracked. Should follow
320
+ a dotted naming convention (e.g., "handler.execute",
321
+ "db.query", "http.request"). This is used as the metric name.
322
+ correlation_id: Optional correlation ID for distributed tracing.
323
+ Can be a string or UUID. If UUID, it will be converted to string.
324
+ labels: Optional additional labels to attach to metrics. Keys and
325
+ values must be strings. Common labels: "handler", "status".
326
+
327
+ Side Effects:
328
+ - Sets _start_time contextvar to current perf_counter value
329
+ - Sets _operation_name contextvar to operation parameter
330
+ - Sets _correlation_id contextvar if provided
331
+ - Sets _operation_labels contextvar if provided
332
+ - Increments "operation_started_total" counter if metrics sink present
333
+
334
+ Example:
335
+ ```python
336
+ hook.before_operation(
337
+ "handler.process",
338
+ correlation_id="abc-123",
339
+ labels={"handler": "UserHandler"},
340
+ )
341
+ ```
342
+ """
343
+ # Store timing state in contextvars for concurrency safety
344
+ _start_time.set(time.perf_counter())
345
+ _operation_name.set(operation)
346
+
347
+ # Convert UUID to string if needed
348
+ if correlation_id is not None:
349
+ _correlation_id.set(str(correlation_id))
350
+ else:
351
+ _correlation_id.set(None)
352
+
353
+ # Store labels (or empty dict if none provided)
354
+ _operation_labels.set(labels.copy() if labels else {})
355
+
356
+ # Emit start metric if sink is available
357
+ # Lock ensures atomic counter increment across concurrent calls
358
+ if self.metrics_sink is not None:
359
+ metric_labels = self._build_metric_labels(operation)
360
+ with self._metrics_lock:
361
+ self.metrics_sink.increment_counter(
362
+ name="operation_started_total",
363
+ labels=metric_labels,
364
+ increment=1,
365
+ )
366
+
367
+ def after_operation(self) -> float:
368
+ """Mark the end of an operation and calculate duration.
369
+
370
+ Calculates the elapsed time since before_operation() was called and
371
+ optionally emits the duration as a histogram observation.
372
+
373
+ Concurrency Safety:
374
+ Reads timing state from contextvars, which are isolated per async
375
+ task. The returned duration is specific to the current task's
376
+ operation timing.
377
+
378
+ Returns:
379
+ Duration in milliseconds since before_operation() was called.
380
+ Returns 0.0 if before_operation() was not called (start_time is None).
381
+
382
+ Side Effects:
383
+ - Observes "operation_duration_seconds" histogram if metrics sink present
384
+ - Clears _start_time contextvar (sets to None)
385
+ - Clears _operation_name contextvar (sets to None)
386
+ - Does NOT clear correlation_id (may be needed for error handling)
387
+
388
+ Example:
389
+ ```python
390
+ hook.before_operation("db.query")
391
+ result = await db.execute(query)
392
+ duration_ms = hook.after_operation() # e.g., 42.5
393
+ logger.info(f"Query took {duration_ms:.2f}ms")
394
+ ```
395
+ """
396
+ start = _start_time.get()
397
+ operation = _operation_name.get()
398
+
399
+ # Handle case where before_operation was not called
400
+ if start is None:
401
+ return 0.0
402
+
403
+ # Calculate duration
404
+ end = time.perf_counter()
405
+ duration_seconds = end - start
406
+ duration_ms = duration_seconds * 1000.0
407
+
408
+ # Emit duration metric if sink is available
409
+ # Lock ensures atomic histogram observation across concurrent calls
410
+ if self.metrics_sink is not None and operation is not None:
411
+ metric_labels = self._build_metric_labels(operation)
412
+ with self._metrics_lock:
413
+ self.metrics_sink.observe_histogram(
414
+ name="operation_duration_seconds",
415
+ labels=metric_labels,
416
+ value=duration_seconds,
417
+ )
418
+
419
+ # Clear timing state (but keep correlation_id for potential error handling)
420
+ _start_time.set(None)
421
+ _operation_name.set(None)
422
+ _operation_labels.set(None)
423
+
424
+ return duration_ms
425
+
426
+ def get_current_context(self) -> dict[str, str | None]:
427
+ """Get the current operation context from contextvars.
428
+
429
+ Returns the current operation context including operation name,
430
+ correlation ID, and any additional labels. Useful for logging
431
+ and debugging.
432
+
433
+ Concurrency Safety:
434
+ Reads from contextvars, returning context specific to the
435
+ current async task.
436
+
437
+ Returns:
438
+ Dictionary containing:
439
+ - "operation": Current operation name or None
440
+ - "correlation_id": Current correlation ID or None
441
+ - Plus any additional labels from _operation_labels
442
+
443
+ Example:
444
+ ```python
445
+ hook.before_operation("handler.process", correlation_id="abc-123")
446
+ ctx = hook.get_current_context()
447
+ # ctx = {"operation": "handler.process", "correlation_id": "abc-123"}
448
+ logger.info("Processing", extra=ctx)
449
+ ```
450
+ """
451
+ result: dict[str, str | None] = {
452
+ "operation": _operation_name.get(),
453
+ "correlation_id": _correlation_id.get(),
454
+ }
455
+
456
+ # Add any additional labels (they're already string -> string)
457
+ labels = _operation_labels.get()
458
+ if labels is not None:
459
+ for key, value in labels.items():
460
+ result[key] = value
461
+
462
+ return result
463
+
464
+ # =========================================================================
465
+ # SUCCESS/FAILURE TRACKING
466
+ # =========================================================================
467
+
468
+ def record_success(self, labels: dict[str, str] | None = None) -> None:
469
+ """Record a successful operation completion.
470
+
471
+ Increments the operation_completed_total counter with success status.
472
+ Should be called after the operation completes successfully, before
473
+ after_operation().
474
+
475
+ Args:
476
+ labels: Optional additional labels to merge with operation labels.
477
+
478
+ Side Effects:
479
+ - Increments "operation_completed_total" counter with status="success"
480
+
481
+ Example:
482
+ ```python
483
+ hook.before_operation("handler.process")
484
+ result = await handler.execute()
485
+ hook.record_success()
486
+ duration = hook.after_operation()
487
+ ```
488
+ """
489
+ if self.metrics_sink is None:
490
+ return
491
+
492
+ operation = _operation_name.get()
493
+ if operation is None:
494
+ return
495
+
496
+ metric_labels = self._build_metric_labels(operation, labels)
497
+ metric_labels["status"] = "success"
498
+
499
+ # Lock ensures atomic counter increment across concurrent calls
500
+ with self._metrics_lock:
501
+ self.metrics_sink.increment_counter(
502
+ name="operation_completed_total",
503
+ labels=metric_labels,
504
+ increment=1,
505
+ )
506
+
507
+ def record_failure(
508
+ self,
509
+ error_type: str,
510
+ labels: dict[str, str] | None = None,
511
+ ) -> None:
512
+ """Record a failed operation.
513
+
514
+ Increments the operation_failed_total counter with the error type.
515
+ Should be called when an operation fails, before after_operation().
516
+
517
+ Args:
518
+ error_type: Type/class name of the error that occurred.
519
+ Should be a stable identifier (e.g., "TimeoutError",
520
+ "DatabaseConnectionError"), not the error message.
521
+ labels: Optional additional labels to merge with operation labels.
522
+
523
+ Side Effects:
524
+ - Increments "operation_failed_total" counter with error_type label
525
+
526
+ Example:
527
+ ```python
528
+ hook.before_operation("db.query")
529
+ try:
530
+ result = await db.execute(query)
531
+ hook.record_success()
532
+ except DatabaseError as e:
533
+ hook.record_failure("DatabaseError")
534
+ raise
535
+ finally:
536
+ hook.after_operation()
537
+ ```
538
+ """
539
+ if self.metrics_sink is None:
540
+ return
541
+
542
+ operation = _operation_name.get()
543
+ if operation is None:
544
+ return
545
+
546
+ metric_labels = self._build_metric_labels(operation, labels)
547
+ metric_labels["status"] = "failure"
548
+ metric_labels["error_type"] = error_type
549
+
550
+ # Lock ensures atomic counter increment across concurrent calls
551
+ with self._metrics_lock:
552
+ self.metrics_sink.increment_counter(
553
+ name="operation_failed_total",
554
+ labels=metric_labels,
555
+ increment=1,
556
+ )
557
+
558
+ # =========================================================================
559
+ # SPECIALIZED TRACKING METHODS
560
+ # =========================================================================
561
+
562
+ def record_retry_attempt(
563
+ self,
564
+ attempt_number: int,
565
+ reason: str,
566
+ labels: dict[str, str] | None = None,
567
+ ) -> None:
568
+ """Record a retry attempt for an operation.
569
+
570
+ Tracks retry attempts with attempt number and reason. Useful for
571
+ monitoring retry behavior and identifying flaky operations.
572
+
573
+ Args:
574
+ attempt_number: The current attempt number (1-based). First attempt
575
+ is 1, first retry is 2, etc.
576
+ reason: Reason for the retry (e.g., "timeout", "connection_reset",
577
+ "rate_limited"). Should be a stable identifier.
578
+ labels: Optional additional labels to merge with operation labels.
579
+
580
+ Side Effects:
581
+ - Increments "retry_attempt_total" counter
582
+
583
+ Example:
584
+ ```python
585
+ for attempt in range(1, max_retries + 1):
586
+ try:
587
+ result = await operation()
588
+ break
589
+ except RetryableError as e:
590
+ hook.record_retry_attempt(attempt, "transient_error")
591
+ if attempt == max_retries:
592
+ raise
593
+ ```
594
+ """
595
+ if self.metrics_sink is None:
596
+ return
597
+
598
+ operation = _operation_name.get() or "unknown"
599
+ metric_labels = self._build_metric_labels(operation, labels)
600
+ metric_labels["attempt"] = str(attempt_number)
601
+ metric_labels["reason"] = reason
602
+
603
+ # Lock ensures atomic counter increment across concurrent calls
604
+ with self._metrics_lock:
605
+ self.metrics_sink.increment_counter(
606
+ name="retry_attempt_total",
607
+ labels=metric_labels,
608
+ increment=1,
609
+ )
610
+
611
+ def record_circuit_breaker_state_change(
612
+ self,
613
+ service_name: str,
614
+ from_state: str,
615
+ to_state: str,
616
+ labels: dict[str, str] | None = None,
617
+ ) -> None:
618
+ """Record a circuit breaker state transition.
619
+
620
+ Tracks circuit breaker state changes for monitoring circuit health
621
+ and identifying unstable services.
622
+
623
+ Args:
624
+ service_name: Name of the service protected by the circuit breaker.
625
+ from_state: Previous circuit state (e.g., "CLOSED", "OPEN", "HALF_OPEN").
626
+ to_state: New circuit state after transition.
627
+ labels: Optional additional labels.
628
+
629
+ Side Effects:
630
+ - Increments "circuit_breaker_state_change_total" counter
631
+
632
+ Example:
633
+ ```python
634
+ # In circuit breaker implementation
635
+ hook.record_circuit_breaker_state_change(
636
+ service_name="database",
637
+ from_state="CLOSED",
638
+ to_state="OPEN",
639
+ )
640
+ ```
641
+ """
642
+ if self.metrics_sink is None:
643
+ return
644
+
645
+ metric_labels: dict[str, str] = {
646
+ "service": service_name,
647
+ "from_state": from_state,
648
+ "to_state": to_state,
649
+ }
650
+
651
+ # Merge additional labels
652
+ if labels:
653
+ for key, value in labels.items():
654
+ if key not in metric_labels: # Don't overwrite required labels
655
+ metric_labels[key] = value
656
+
657
+ # Lock ensures atomic counter increment across concurrent calls
658
+ with self._metrics_lock:
659
+ self.metrics_sink.increment_counter(
660
+ name="circuit_breaker_state_change_total",
661
+ labels=metric_labels,
662
+ increment=1,
663
+ )
664
+
665
+ def set_gauge(
666
+ self,
667
+ name: str,
668
+ value: float,
669
+ labels: dict[str, str] | None = None,
670
+ ) -> None:
671
+ """Set a gauge metric value.
672
+
673
+ Convenience method for setting gauge values through the hook.
674
+ Useful for tracking current state like queue depths or active connections.
675
+
676
+ Note:
677
+ Gauges can legitimately be negative (e.g., temperature, delta values).
678
+ For buffer/queue metrics that should never be negative, use
679
+ set_buffer_gauge() instead, which enforces non-negative values.
680
+
681
+ Args:
682
+ name: Metric name following Prometheus conventions.
683
+ value: Current gauge value. Can be negative for appropriate metrics.
684
+ labels: Optional labels for the metric.
685
+
686
+ Side Effects:
687
+ - Sets gauge metric via metrics sink
688
+
689
+ Example:
690
+ ```python
691
+ hook.set_gauge(
692
+ "active_handlers",
693
+ value=len(active_handlers),
694
+ labels={"handler_type": "http"},
695
+ )
696
+ ```
697
+ """
698
+ if self.metrics_sink is None:
699
+ return
700
+
701
+ # Lock ensures atomic gauge update across concurrent calls
702
+ with self._metrics_lock:
703
+ self.metrics_sink.set_gauge(
704
+ name=name,
705
+ labels=labels or {},
706
+ value=value,
707
+ )
708
+
709
+ def set_buffer_gauge(
710
+ self,
711
+ name: str,
712
+ value: float,
713
+ labels: dict[str, str] | None = None,
714
+ ) -> None:
715
+ """Set a gauge metric value for buffer/queue metrics (non-negative).
716
+
717
+ Similar to set_gauge(), but enforces non-negative values. Use this for
718
+ metrics that represent counts, sizes, or capacities that logically
719
+ cannot be negative (e.g., queue depth, buffer size, connection pool size).
720
+
721
+ The value is clamped to 0.0 if negative, preventing invalid metric values
722
+ that could occur from race conditions or calculation errors. A warning
723
+ is logged when clamping occurs to aid debugging.
724
+
725
+ Args:
726
+ name: Metric name following Prometheus conventions.
727
+ value: Current buffer/queue value. Clamped to 0.0 if negative.
728
+ labels: Optional labels for the metric.
729
+
730
+ Side Effects:
731
+ - Sets gauge metric via metrics sink with max(0.0, value)
732
+ - Logs warning if negative value is clamped
733
+
734
+ Example:
735
+ ```python
736
+ # Queue depth can't go negative even with concurrent updates
737
+ hook.set_buffer_gauge(
738
+ "message_queue_depth",
739
+ value=queue.qsize(),
740
+ labels={"queue_name": "events"},
741
+ )
742
+
743
+ # Safe even if calculation error produces negative
744
+ hook.set_buffer_gauge(
745
+ "buffer_available_slots",
746
+ value=total_slots - used_slots, # Clamped to 0 if oversubscribed
747
+ labels={"buffer": "write"},
748
+ )
749
+ ```
750
+ """
751
+ if self.metrics_sink is None:
752
+ return
753
+
754
+ # Enforce non-negative values for buffer/count metrics
755
+ if value < 0.0:
756
+ _logger.warning(
757
+ "Buffer gauge received negative value; clamping to 0.0",
758
+ extra={
759
+ "metric_name": name,
760
+ "original_value": value,
761
+ "labels": labels,
762
+ },
763
+ )
764
+ safe_value = 0.0
765
+ else:
766
+ safe_value = value
767
+
768
+ # Lock ensures atomic gauge update across concurrent calls
769
+ with self._metrics_lock:
770
+ self.metrics_sink.set_gauge(
771
+ name=name,
772
+ labels=labels or {},
773
+ value=safe_value,
774
+ )
775
+
776
+ # =========================================================================
777
+ # CONTEXT MANAGER SUPPORT
778
+ # =========================================================================
779
+
780
+ def operation_context(
781
+ self,
782
+ operation: str,
783
+ correlation_id: str | UUID | None = None,
784
+ labels: dict[str, str] | None = None,
785
+ ) -> OperationScope:
786
+ """Create a context manager for operation timing.
787
+
788
+ Returns a context manager that automatically calls before_operation()
789
+ on entry and after_operation() on exit. This is the recommended way
790
+ to instrument operations as it ensures proper cleanup even on exceptions.
791
+
792
+ Args:
793
+ operation: Name of the operation to track.
794
+ correlation_id: Optional correlation ID for tracing.
795
+ labels: Optional additional labels for metrics.
796
+
797
+ Returns:
798
+ A context manager that yields the duration in milliseconds on exit.
799
+
800
+ Example:
801
+ ```python
802
+ # Basic usage
803
+ with hook.operation_context("handler.process") as ctx:
804
+ result = await handler.execute()
805
+ print(f"Operation took {ctx.duration_ms:.2f}ms")
806
+
807
+ # With correlation ID
808
+ with hook.operation_context(
809
+ "db.query",
810
+ correlation_id=request_id,
811
+ labels={"table": "users"},
812
+ ):
813
+ rows = await db.fetch_all(query)
814
+ ```
815
+ """
816
+ return OperationScope(
817
+ hook=self,
818
+ operation=operation,
819
+ correlation_id=correlation_id,
820
+ labels=labels,
821
+ )
822
+
823
+ # =========================================================================
824
+ # INTERNAL HELPERS
825
+ # =========================================================================
826
+
827
+ # High-cardinality label keys that must be excluded from metrics.
828
+ # These would cause metrics cardinality explosion and be dropped by
829
+ # ModelMetricsPolicy when on_violation is WARN_AND_DROP or DROP_SILENT.
830
+ _HIGH_CARDINALITY_KEYS: frozenset[str] = frozenset(
831
+ {
832
+ "correlation_id",
833
+ "request_id",
834
+ "trace_id",
835
+ "span_id",
836
+ "session_id",
837
+ "user_id",
838
+ }
839
+ )
840
+
841
+ def _build_metric_labels(
842
+ self,
843
+ operation: str,
844
+ extra_labels: dict[str, str] | None = None,
845
+ ) -> dict[str, str]:
846
+ """Build the complete label set for a metric.
847
+
848
+ Combines operation name, stored operation labels, and any extra labels
849
+ into a single label dictionary. High-cardinality keys are automatically
850
+ filtered out to prevent metrics from being dropped by the policy.
851
+
852
+ CRITICAL - Metric Recording Guarantee:
853
+ This method NEVER drops or suppresses metric recording. When called,
854
+ the metric WILL be recorded with the returned labels. The only
855
+ filtering that occurs is removal of high-cardinality label KEYS
856
+ from the label dictionary - the metric itself is ALWAYS recorded.
857
+
858
+ Example with correlation_id:
859
+ - Input: operation="db.query", labels={"correlation_id": "abc-123"}
860
+ - Output: {"operation": "db.query"} # correlation_id filtered
861
+ - Metric: RECORDED with {"operation": "db.query"}
862
+
863
+ The correlation_id and other high-cardinality values remain available
864
+ via the _correlation_id contextvar for structured logging and
865
+ distributed tracing - they are just excluded from Prometheus labels
866
+ to prevent cardinality explosion.
867
+
868
+ Why Filter High-Cardinality Labels:
869
+ High-cardinality values (correlation_id, request_id, trace_id, etc.)
870
+ are unique per request. Including them in Prometheus labels would:
871
+ 1. Create millions of unique time series (cardinality explosion)
872
+ 2. Cause metrics to be dropped by ModelMetricsPolicy (WARN_AND_DROP)
873
+ 3. Overwhelm Prometheus storage and query performance
874
+
875
+ By filtering these keys early, we ensure metrics are ALWAYS recorded
876
+ with stable, low-cardinality labels.
877
+
878
+ Args:
879
+ operation: Operation name to include. Always present in output.
880
+ extra_labels: Optional additional labels to merge.
881
+
882
+ Returns:
883
+ Complete label dictionary for metric emission, with high-cardinality
884
+ keys filtered out. GUARANTEED to contain at least {"operation": operation}.
885
+ Never returns empty dict. Never returns None.
886
+ """
887
+ # INVARIANT: labels always contains at least {"operation": operation}
888
+ # This ensures metrics are NEVER dropped due to empty labels
889
+ labels: dict[str, str] = {"operation": operation}
890
+ filtered_keys: list[str] = []
891
+
892
+ # Merge stored operation labels, filtering out high-cardinality keys
893
+ # Note: stored_labels dict is a copy made in before_operation(), safe to iterate
894
+ stored_labels = _operation_labels.get()
895
+ if stored_labels is not None:
896
+ for key, value in stored_labels.items():
897
+ if key in self._HIGH_CARDINALITY_KEYS:
898
+ filtered_keys.append(key)
899
+ else:
900
+ labels[key] = value
901
+
902
+ # Merge extra labels (overrides stored if same key), filtering high-cardinality
903
+ if extra_labels:
904
+ for key, value in extra_labels.items():
905
+ if key in self._HIGH_CARDINALITY_KEYS:
906
+ # Only add to filtered_keys if not already tracked
907
+ if key not in filtered_keys:
908
+ filtered_keys.append(key)
909
+ else:
910
+ labels[key] = value
911
+
912
+ # Log when high-cardinality keys are filtered (debug level)
913
+ # IMPORTANT: This is informational logging only - the metric IS being recorded
914
+ # We only remove specific label KEYS, NOT the entire metric
915
+ # The log includes correlation_id for tracing even though it's filtered from labels
916
+ if filtered_keys:
917
+ # Include correlation_id in log for tracing purposes
918
+ correlation_id = _correlation_id.get()
919
+ _logger.debug(
920
+ "Removed high-cardinality keys from metric labels - "
921
+ "metric WILL be recorded with %d remaining labels (keys removed: %s)",
922
+ len(labels),
923
+ filtered_keys,
924
+ extra={
925
+ "operation": operation,
926
+ "filtered_keys": filtered_keys,
927
+ "remaining_labels": list(labels.keys()),
928
+ "correlation_id": correlation_id, # Available for log correlation
929
+ },
930
+ )
931
+
932
+ # CRITICAL: Defense-in-depth guarantee that metrics are NEVER dropped.
933
+ # The invariant at line 889 ensures "operation" is always present, but this
934
+ # explicit check provides runtime safety if the invariant is ever violated.
935
+ # This protects against data loss from unexpected edge cases.
936
+ if "operation" not in labels:
937
+ _logger.error(
938
+ "BUG: operation key missing from labels after filtering; "
939
+ "restoring to prevent metric data loss",
940
+ extra={"operation": operation, "labels_keys": list(labels.keys())},
941
+ )
942
+ labels["operation"] = operation
943
+
944
+ # GUARANTEE: This method ALWAYS returns a non-empty dict containing at least
945
+ # {"operation": operation}. Metrics are NEVER dropped - only high-cardinality
946
+ # label keys (correlation_id, request_id, etc.) are removed from the label set.
947
+ # The metric itself is ALWAYS recorded with the remaining labels.
948
+ return labels
949
+
950
+
951
+ class OperationScope:
952
+ """Context manager for scoped operation timing.
953
+
954
+ This internal class provides context manager support for HookObservability.
955
+ It automatically calls before_operation() on entry and after_operation()
956
+ on exit, ensuring proper cleanup even when exceptions occur.
957
+
958
+ Attributes:
959
+ duration_ms: Duration of the operation in milliseconds, available
960
+ after the context exits. Will be 0.0 if accessed before exit.
961
+
962
+ Note:
963
+ This class stores tokens for contextvar restoration, enabling proper
964
+ nesting of operation contexts. Each context saves and restores the
965
+ previous contextvar values.
966
+ """
967
+
968
+ def __init__(
969
+ self,
970
+ hook: HookObservability,
971
+ operation: str,
972
+ correlation_id: str | UUID | None = None,
973
+ labels: dict[str, str] | None = None,
974
+ ) -> None:
975
+ """Initialize the context manager.
976
+
977
+ Args:
978
+ hook: The HookObservability instance to use.
979
+ operation: Operation name to track.
980
+ correlation_id: Optional correlation ID.
981
+ labels: Optional additional labels.
982
+ """
983
+ self._hook = hook
984
+ self._operation = operation
985
+ self._correlation_id = correlation_id
986
+ self._labels = labels
987
+ self.duration_ms: float = 0.0
988
+
989
+ # Tokens for restoring previous contextvar values (for nesting support)
990
+ self._start_time_token: Token[float | None] | None = None
991
+ self._operation_name_token: Token[str | None] | None = None
992
+ self._correlation_id_token: Token[str | None] | None = None
993
+ self._labels_token: Token[dict[str, str] | None] | None = None
994
+
995
+ def __enter__(self) -> OperationScope:
996
+ """Enter the operation context.
997
+
998
+ Saves current contextvar values and calls before_operation().
999
+
1000
+ Returns:
1001
+ Self, for accessing duration_ms after exit.
1002
+ """
1003
+ # Save current values for restoration on exit (nesting support)
1004
+ self._start_time_token = _start_time.set(_start_time.get())
1005
+ self._operation_name_token = _operation_name.set(_operation_name.get())
1006
+ self._correlation_id_token = _correlation_id.set(_correlation_id.get())
1007
+ current_labels = _operation_labels.get()
1008
+ # NOTE: Shallow copy is sufficient here because:
1009
+ # 1. Labels dict is typed as dict[str, str] (string keys and values)
1010
+ # 2. Strings are immutable in Python, so no aliasing issues can occur
1011
+ # 3. We only need isolation of the dict structure, not deep cloning of values
1012
+ self._labels_token = _operation_labels.set(
1013
+ current_labels.copy() if current_labels is not None else None
1014
+ )
1015
+
1016
+ # Now start the new operation
1017
+ self._hook.before_operation(
1018
+ operation=self._operation,
1019
+ correlation_id=self._correlation_id,
1020
+ labels=self._labels,
1021
+ )
1022
+
1023
+ return self
1024
+
1025
+ def __exit__(
1026
+ self,
1027
+ exc_type: type[BaseException] | None,
1028
+ exc_val: BaseException | None,
1029
+ exc_tb: TracebackType | None,
1030
+ ) -> None:
1031
+ """Exit the operation context.
1032
+
1033
+ Calls after_operation() and restores previous contextvar values.
1034
+ Records success or failure based on whether an exception occurred.
1035
+
1036
+ Concurrency Safety:
1037
+ Uses try/finally to ensure contextvar tokens are ALWAYS restored,
1038
+ even if record_failure/record_success/after_operation raise exceptions.
1039
+ This prevents contextvar state leakage in error scenarios.
1040
+
1041
+ Args:
1042
+ exc_type: Exception type if an exception was raised.
1043
+ exc_val: Exception value if an exception was raised.
1044
+ exc_tb: Traceback if an exception was raised.
1045
+ """
1046
+ try:
1047
+ # Record success or failure before timing
1048
+ if exc_type is not None:
1049
+ self._hook.record_failure(exc_type.__name__)
1050
+ else:
1051
+ self._hook.record_success()
1052
+
1053
+ # Get duration
1054
+ self.duration_ms = self._hook.after_operation()
1055
+ finally:
1056
+ # CRITICAL: Always restore previous contextvar values (for nesting support)
1057
+ # This must happen even if the above code raises an exception to prevent
1058
+ # contextvar state leakage.
1059
+ if self._start_time_token is not None:
1060
+ _start_time.reset(self._start_time_token)
1061
+ if self._operation_name_token is not None:
1062
+ _operation_name.reset(self._operation_name_token)
1063
+ if self._correlation_id_token is not None:
1064
+ _correlation_id.reset(self._correlation_id_token)
1065
+ if self._labels_token is not None:
1066
+ _operation_labels.reset(self._labels_token)
1067
+
1068
+
1069
+ # =============================================================================
1070
+ # MODULE-LEVEL SINGLETON FUNCTIONS
1071
+ # =============================================================================
1072
+ #
1073
+ # These functions provide optional singleton access to a global HookObservability
1074
+ # instance. The singleton is thread-safe and lazily initialized.
1075
+ #
1076
+ # =============================================================================
1077
+
1078
+
1079
+ def get_global_hook(
1080
+ metrics_sink: ProtocolHotPathMetricsSink | None = None,
1081
+ ) -> HookObservability:
1082
+ """Get or create the global singleton HookObservability instance.
1083
+
1084
+ Returns the cached singleton hook if one exists, otherwise creates a new
1085
+ one with the provided configuration and caches it. This is the recommended
1086
+ way to access a shared observability hook across the application.
1087
+
1088
+ Thread Safety:
1089
+ This function is thread-safe. Multiple concurrent calls will receive
1090
+ the same singleton instance. A threading.Lock ensures only one thread
1091
+ creates the singleton.
1092
+
1093
+ Note:
1094
+ The metrics_sink parameter is only used on first call when the singleton
1095
+ is created. Subsequent calls ignore this parameter and return the
1096
+ existing instance. A warning is logged when configuration is provided
1097
+ but ignored due to an existing singleton.
1098
+
1099
+ To reconfigure the singleton, call clear_global_hook() first.
1100
+
1101
+ Args:
1102
+ metrics_sink: Optional metrics sink for the hook. Only used if no
1103
+ singleton exists yet. Subsequent calls ignore this parameter.
1104
+
1105
+ Returns:
1106
+ The global singleton HookObservability instance.
1107
+
1108
+ Example:
1109
+ ```python
1110
+ # First call creates the singleton with the provided sink
1111
+ hook1 = get_global_hook(metrics_sink=my_sink)
1112
+
1113
+ # Subsequent calls return the same instance
1114
+ hook2 = get_global_hook() # metrics_sink parameter ignored
1115
+ assert hook1 is hook2
1116
+
1117
+ # Reconfigure if needed
1118
+ clear_global_hook()
1119
+ hook3 = get_global_hook(metrics_sink=new_sink) # New singleton
1120
+ ```
1121
+ """
1122
+ global _global_hook_instance # noqa: PLW0603 - Standard singleton pattern
1123
+
1124
+ with _global_hook_lock:
1125
+ if _global_hook_instance is None:
1126
+ _global_hook_instance = HookObservability(metrics_sink=metrics_sink)
1127
+ _logger.debug(
1128
+ "Created global singleton HookObservability",
1129
+ extra={"has_metrics_sink": metrics_sink is not None},
1130
+ )
1131
+ elif metrics_sink is not None:
1132
+ # Log warning when config is provided but ignored for existing singleton
1133
+ _logger.warning(
1134
+ "Global HookObservability singleton already exists; "
1135
+ "provided metrics_sink configuration ignored. "
1136
+ "Call clear_global_hook() first to reconfigure.",
1137
+ extra={
1138
+ "existing_has_metrics_sink": (
1139
+ _global_hook_instance.metrics_sink is not None
1140
+ ),
1141
+ "provided_metrics_sink_type": type(metrics_sink).__name__,
1142
+ },
1143
+ )
1144
+ return _global_hook_instance
1145
+
1146
+
1147
+ def configure_global_hook(
1148
+ metrics_sink: ProtocolHotPathMetricsSink | None = None,
1149
+ ) -> HookObservability:
1150
+ """Configure the global singleton HookObservability instance.
1151
+
1152
+ This is an alias for get_global_hook() that makes the intent clearer when
1153
+ you want to explicitly configure the singleton on first use.
1154
+
1155
+ Args:
1156
+ metrics_sink: Optional metrics sink for the hook.
1157
+
1158
+ Returns:
1159
+ The global singleton HookObservability instance.
1160
+
1161
+ See Also:
1162
+ get_global_hook(): Primary singleton access function.
1163
+ clear_global_hook(): Reset the singleton for reconfiguration.
1164
+ """
1165
+ return get_global_hook(metrics_sink=metrics_sink)
1166
+
1167
+
1168
+ def clear_global_hook() -> None:
1169
+ """Clear the global singleton HookObservability instance.
1170
+
1171
+ Removes the reference to the cached singleton hook, allowing it to be
1172
+ garbage collected if no other references exist. Subsequent calls to
1173
+ get_global_hook() will create a new instance.
1174
+
1175
+ Use Cases:
1176
+ - Testing: Reset singleton state between tests
1177
+ - Reconfiguration: Allow new singleton with different metrics_sink
1178
+ - Shutdown: Release resources before application exit
1179
+
1180
+ Thread Safety:
1181
+ This function is thread-safe.
1182
+
1183
+ Warning:
1184
+ Existing references to the old singleton remain valid. Only future
1185
+ get_global_hook() calls will create a new instance.
1186
+
1187
+ Example:
1188
+ ```python
1189
+ hook1 = get_global_hook(metrics_sink=sink1)
1190
+ clear_global_hook()
1191
+ hook2 = get_global_hook(metrics_sink=sink2)
1192
+ assert hook1 is not hook2 # Different instances
1193
+ # hook1 still works but is no longer the singleton
1194
+ ```
1195
+ """
1196
+ global _global_hook_instance # noqa: PLW0603 - Standard singleton pattern
1197
+
1198
+ with _global_hook_lock:
1199
+ if _global_hook_instance is not None:
1200
+ _logger.debug("Cleared global HookObservability singleton")
1201
+ _global_hook_instance = None
1202
+
1203
+
1204
+ def has_global_hook() -> bool:
1205
+ """Check if a global singleton HookObservability exists.
1206
+
1207
+ Returns:
1208
+ True if a singleton hook has been created, False otherwise.
1209
+
1210
+ Thread Safety:
1211
+ This function is thread-safe.
1212
+ """
1213
+ with _global_hook_lock:
1214
+ return _global_hook_instance is not None
1215
+
1216
+
1217
+ __all__ = [
1218
+ "HookObservability",
1219
+ "get_global_hook",
1220
+ "configure_global_hook",
1221
+ "clear_global_hook",
1222
+ "has_global_hook",
1223
+ ]