omnibase_infra 0.2.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (833) hide show
  1. omnibase_infra/__init__.py +101 -0
  2. omnibase_infra/adapters/adapter_onex_tool_execution.py +451 -0
  3. omnibase_infra/capabilities/__init__.py +15 -0
  4. omnibase_infra/capabilities/capability_inference_rules.py +211 -0
  5. omnibase_infra/capabilities/contract_capability_extractor.py +221 -0
  6. omnibase_infra/capabilities/intent_type_extractor.py +160 -0
  7. omnibase_infra/cli/__init__.py +1 -0
  8. omnibase_infra/cli/commands.py +216 -0
  9. omnibase_infra/clients/__init__.py +0 -0
  10. omnibase_infra/configs/widget_mapping.yaml +176 -0
  11. omnibase_infra/constants_topic_patterns.py +26 -0
  12. omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +264 -0
  13. omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +141 -0
  14. omnibase_infra/decorators/__init__.py +29 -0
  15. omnibase_infra/decorators/allow_any.py +109 -0
  16. omnibase_infra/dlq/__init__.py +90 -0
  17. omnibase_infra/dlq/constants_dlq.py +57 -0
  18. omnibase_infra/dlq/models/__init__.py +26 -0
  19. omnibase_infra/dlq/models/enum_replay_status.py +37 -0
  20. omnibase_infra/dlq/models/model_dlq_replay_record.py +135 -0
  21. omnibase_infra/dlq/models/model_dlq_tracking_config.py +184 -0
  22. omnibase_infra/dlq/service_dlq_tracking.py +611 -0
  23. omnibase_infra/enums/__init__.py +132 -0
  24. omnibase_infra/enums/enum_any_type_violation.py +104 -0
  25. omnibase_infra/enums/enum_backend_type.py +27 -0
  26. omnibase_infra/enums/enum_capture_outcome.py +42 -0
  27. omnibase_infra/enums/enum_capture_state.py +88 -0
  28. omnibase_infra/enums/enum_chain_violation_type.py +119 -0
  29. omnibase_infra/enums/enum_circuit_state.py +51 -0
  30. omnibase_infra/enums/enum_confirmation_event_type.py +27 -0
  31. omnibase_infra/enums/enum_consumer_group_purpose.py +92 -0
  32. omnibase_infra/enums/enum_contract_type.py +84 -0
  33. omnibase_infra/enums/enum_dedupe_strategy.py +46 -0
  34. omnibase_infra/enums/enum_dispatch_status.py +191 -0
  35. omnibase_infra/enums/enum_environment.py +46 -0
  36. omnibase_infra/enums/enum_execution_shape_violation.py +103 -0
  37. omnibase_infra/enums/enum_handler_error_type.py +111 -0
  38. omnibase_infra/enums/enum_handler_loader_error.py +178 -0
  39. omnibase_infra/enums/enum_handler_source_mode.py +86 -0
  40. omnibase_infra/enums/enum_handler_source_type.py +87 -0
  41. omnibase_infra/enums/enum_handler_type.py +77 -0
  42. omnibase_infra/enums/enum_handler_type_category.py +61 -0
  43. omnibase_infra/enums/enum_infra_transport_type.py +73 -0
  44. omnibase_infra/enums/enum_introspection_reason.py +154 -0
  45. omnibase_infra/enums/enum_kafka_acks.py +99 -0
  46. omnibase_infra/enums/enum_message_category.py +213 -0
  47. omnibase_infra/enums/enum_node_archetype.py +74 -0
  48. omnibase_infra/enums/enum_node_output_type.py +185 -0
  49. omnibase_infra/enums/enum_non_retryable_error_category.py +224 -0
  50. omnibase_infra/enums/enum_policy_type.py +32 -0
  51. omnibase_infra/enums/enum_registration_state.py +261 -0
  52. omnibase_infra/enums/enum_registration_status.py +33 -0
  53. omnibase_infra/enums/enum_registry_response_status.py +28 -0
  54. omnibase_infra/enums/enum_response_status.py +26 -0
  55. omnibase_infra/enums/enum_retry_error_category.py +98 -0
  56. omnibase_infra/enums/enum_security_rule_id.py +103 -0
  57. omnibase_infra/enums/enum_selection_strategy.py +91 -0
  58. omnibase_infra/enums/enum_topic_standard.py +42 -0
  59. omnibase_infra/enums/enum_validation_severity.py +78 -0
  60. omnibase_infra/errors/__init__.py +160 -0
  61. omnibase_infra/errors/error_architecture_violation.py +152 -0
  62. omnibase_infra/errors/error_binding_resolution.py +128 -0
  63. omnibase_infra/errors/error_chain_propagation.py +188 -0
  64. omnibase_infra/errors/error_compute_registry.py +95 -0
  65. omnibase_infra/errors/error_consul.py +132 -0
  66. omnibase_infra/errors/error_container_wiring.py +243 -0
  67. omnibase_infra/errors/error_event_bus_registry.py +105 -0
  68. omnibase_infra/errors/error_infra.py +610 -0
  69. omnibase_infra/errors/error_message_type_registry.py +101 -0
  70. omnibase_infra/errors/error_policy_registry.py +115 -0
  71. omnibase_infra/errors/error_vault.py +123 -0
  72. omnibase_infra/event_bus/__init__.py +72 -0
  73. omnibase_infra/event_bus/configs/kafka_event_bus_config.yaml +84 -0
  74. omnibase_infra/event_bus/event_bus_inmemory.py +797 -0
  75. omnibase_infra/event_bus/event_bus_kafka.py +1716 -0
  76. omnibase_infra/event_bus/mixin_kafka_broadcast.py +180 -0
  77. omnibase_infra/event_bus/mixin_kafka_dlq.py +771 -0
  78. omnibase_infra/event_bus/models/__init__.py +29 -0
  79. omnibase_infra/event_bus/models/config/__init__.py +20 -0
  80. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +693 -0
  81. omnibase_infra/event_bus/models/model_dlq_event.py +206 -0
  82. omnibase_infra/event_bus/models/model_dlq_metrics.py +304 -0
  83. omnibase_infra/event_bus/models/model_event_headers.py +115 -0
  84. omnibase_infra/event_bus/models/model_event_message.py +60 -0
  85. omnibase_infra/event_bus/testing/__init__.py +26 -0
  86. omnibase_infra/event_bus/testing/adapter_protocol_event_publisher_inmemory.py +418 -0
  87. omnibase_infra/event_bus/testing/model_publisher_metrics.py +64 -0
  88. omnibase_infra/event_bus/topic_constants.py +376 -0
  89. omnibase_infra/handlers/__init__.py +82 -0
  90. omnibase_infra/handlers/filesystem/__init__.py +48 -0
  91. omnibase_infra/handlers/filesystem/enum_file_system_operation.py +35 -0
  92. omnibase_infra/handlers/filesystem/model_file_system_request.py +298 -0
  93. omnibase_infra/handlers/filesystem/model_file_system_result.py +166 -0
  94. omnibase_infra/handlers/handler_consul.py +795 -0
  95. omnibase_infra/handlers/handler_db.py +1046 -0
  96. omnibase_infra/handlers/handler_filesystem.py +1478 -0
  97. omnibase_infra/handlers/handler_graph.py +2015 -0
  98. omnibase_infra/handlers/handler_http.py +926 -0
  99. omnibase_infra/handlers/handler_intent.py +387 -0
  100. omnibase_infra/handlers/handler_manifest_persistence.contract.yaml +184 -0
  101. omnibase_infra/handlers/handler_manifest_persistence.py +1539 -0
  102. omnibase_infra/handlers/handler_mcp.py +1430 -0
  103. omnibase_infra/handlers/handler_qdrant.py +1076 -0
  104. omnibase_infra/handlers/handler_vault.py +428 -0
  105. omnibase_infra/handlers/mcp/__init__.py +19 -0
  106. omnibase_infra/handlers/mcp/adapter_onex_to_mcp.py +446 -0
  107. omnibase_infra/handlers/mcp/protocols.py +178 -0
  108. omnibase_infra/handlers/mcp/transport_streamable_http.py +352 -0
  109. omnibase_infra/handlers/mixins/__init__.py +47 -0
  110. omnibase_infra/handlers/mixins/mixin_consul_initialization.py +349 -0
  111. omnibase_infra/handlers/mixins/mixin_consul_kv.py +338 -0
  112. omnibase_infra/handlers/mixins/mixin_consul_service.py +542 -0
  113. omnibase_infra/handlers/mixins/mixin_consul_topic_index.py +585 -0
  114. omnibase_infra/handlers/mixins/mixin_vault_initialization.py +338 -0
  115. omnibase_infra/handlers/mixins/mixin_vault_retry.py +412 -0
  116. omnibase_infra/handlers/mixins/mixin_vault_secrets.py +450 -0
  117. omnibase_infra/handlers/mixins/mixin_vault_token.py +365 -0
  118. omnibase_infra/handlers/models/__init__.py +286 -0
  119. omnibase_infra/handlers/models/consul/__init__.py +81 -0
  120. omnibase_infra/handlers/models/consul/enum_consul_operation_type.py +57 -0
  121. omnibase_infra/handlers/models/consul/model_consul_deregister_payload.py +51 -0
  122. omnibase_infra/handlers/models/consul/model_consul_handler_config.py +153 -0
  123. omnibase_infra/handlers/models/consul/model_consul_handler_payload.py +89 -0
  124. omnibase_infra/handlers/models/consul/model_consul_kv_get_found_payload.py +55 -0
  125. omnibase_infra/handlers/models/consul/model_consul_kv_get_not_found_payload.py +49 -0
  126. omnibase_infra/handlers/models/consul/model_consul_kv_get_recurse_payload.py +50 -0
  127. omnibase_infra/handlers/models/consul/model_consul_kv_item.py +33 -0
  128. omnibase_infra/handlers/models/consul/model_consul_kv_put_payload.py +41 -0
  129. omnibase_infra/handlers/models/consul/model_consul_register_payload.py +53 -0
  130. omnibase_infra/handlers/models/consul/model_consul_retry_config.py +66 -0
  131. omnibase_infra/handlers/models/consul/model_payload_consul.py +66 -0
  132. omnibase_infra/handlers/models/consul/registry_payload_consul.py +214 -0
  133. omnibase_infra/handlers/models/graph/__init__.py +35 -0
  134. omnibase_infra/handlers/models/graph/enum_graph_operation_type.py +20 -0
  135. omnibase_infra/handlers/models/graph/model_graph_execute_payload.py +38 -0
  136. omnibase_infra/handlers/models/graph/model_graph_handler_config.py +54 -0
  137. omnibase_infra/handlers/models/graph/model_graph_handler_payload.py +44 -0
  138. omnibase_infra/handlers/models/graph/model_graph_query_payload.py +40 -0
  139. omnibase_infra/handlers/models/graph/model_graph_record.py +22 -0
  140. omnibase_infra/handlers/models/http/__init__.py +50 -0
  141. omnibase_infra/handlers/models/http/enum_http_operation_type.py +29 -0
  142. omnibase_infra/handlers/models/http/model_http_body_content.py +45 -0
  143. omnibase_infra/handlers/models/http/model_http_get_payload.py +88 -0
  144. omnibase_infra/handlers/models/http/model_http_handler_payload.py +90 -0
  145. omnibase_infra/handlers/models/http/model_http_post_payload.py +88 -0
  146. omnibase_infra/handlers/models/http/model_payload_http.py +66 -0
  147. omnibase_infra/handlers/models/http/registry_payload_http.py +212 -0
  148. omnibase_infra/handlers/models/mcp/__init__.py +23 -0
  149. omnibase_infra/handlers/models/mcp/enum_mcp_operation_type.py +24 -0
  150. omnibase_infra/handlers/models/mcp/model_mcp_handler_config.py +40 -0
  151. omnibase_infra/handlers/models/mcp/model_mcp_tool_call.py +32 -0
  152. omnibase_infra/handlers/models/mcp/model_mcp_tool_result.py +45 -0
  153. omnibase_infra/handlers/models/model_consul_handler_response.py +96 -0
  154. omnibase_infra/handlers/models/model_db_describe_response.py +83 -0
  155. omnibase_infra/handlers/models/model_db_query_payload.py +95 -0
  156. omnibase_infra/handlers/models/model_db_query_response.py +60 -0
  157. omnibase_infra/handlers/models/model_filesystem_config.py +98 -0
  158. omnibase_infra/handlers/models/model_filesystem_delete_payload.py +54 -0
  159. omnibase_infra/handlers/models/model_filesystem_delete_result.py +77 -0
  160. omnibase_infra/handlers/models/model_filesystem_directory_entry.py +75 -0
  161. omnibase_infra/handlers/models/model_filesystem_ensure_directory_payload.py +54 -0
  162. omnibase_infra/handlers/models/model_filesystem_ensure_directory_result.py +60 -0
  163. omnibase_infra/handlers/models/model_filesystem_list_directory_payload.py +60 -0
  164. omnibase_infra/handlers/models/model_filesystem_list_directory_result.py +68 -0
  165. omnibase_infra/handlers/models/model_filesystem_read_payload.py +62 -0
  166. omnibase_infra/handlers/models/model_filesystem_read_result.py +61 -0
  167. omnibase_infra/handlers/models/model_filesystem_write_payload.py +70 -0
  168. omnibase_infra/handlers/models/model_filesystem_write_result.py +55 -0
  169. omnibase_infra/handlers/models/model_graph_handler_response.py +98 -0
  170. omnibase_infra/handlers/models/model_handler_response.py +103 -0
  171. omnibase_infra/handlers/models/model_http_handler_response.py +101 -0
  172. omnibase_infra/handlers/models/model_manifest_metadata.py +75 -0
  173. omnibase_infra/handlers/models/model_manifest_persistence_config.py +62 -0
  174. omnibase_infra/handlers/models/model_manifest_query_payload.py +90 -0
  175. omnibase_infra/handlers/models/model_manifest_query_result.py +97 -0
  176. omnibase_infra/handlers/models/model_manifest_retrieve_payload.py +44 -0
  177. omnibase_infra/handlers/models/model_manifest_retrieve_result.py +98 -0
  178. omnibase_infra/handlers/models/model_manifest_store_payload.py +47 -0
  179. omnibase_infra/handlers/models/model_manifest_store_result.py +67 -0
  180. omnibase_infra/handlers/models/model_operation_context.py +187 -0
  181. omnibase_infra/handlers/models/model_qdrant_handler_response.py +98 -0
  182. omnibase_infra/handlers/models/model_retry_state.py +162 -0
  183. omnibase_infra/handlers/models/model_vault_handler_response.py +98 -0
  184. omnibase_infra/handlers/models/qdrant/__init__.py +44 -0
  185. omnibase_infra/handlers/models/qdrant/enum_qdrant_operation_type.py +26 -0
  186. omnibase_infra/handlers/models/qdrant/model_qdrant_collection_payload.py +42 -0
  187. omnibase_infra/handlers/models/qdrant/model_qdrant_delete_payload.py +36 -0
  188. omnibase_infra/handlers/models/qdrant/model_qdrant_handler_config.py +42 -0
  189. omnibase_infra/handlers/models/qdrant/model_qdrant_handler_payload.py +54 -0
  190. omnibase_infra/handlers/models/qdrant/model_qdrant_search_payload.py +42 -0
  191. omnibase_infra/handlers/models/qdrant/model_qdrant_search_result.py +30 -0
  192. omnibase_infra/handlers/models/qdrant/model_qdrant_upsert_payload.py +36 -0
  193. omnibase_infra/handlers/models/vault/__init__.py +69 -0
  194. omnibase_infra/handlers/models/vault/enum_vault_operation_type.py +35 -0
  195. omnibase_infra/handlers/models/vault/model_payload_vault.py +66 -0
  196. omnibase_infra/handlers/models/vault/model_vault_delete_payload.py +57 -0
  197. omnibase_infra/handlers/models/vault/model_vault_handler_config.py +148 -0
  198. omnibase_infra/handlers/models/vault/model_vault_handler_payload.py +101 -0
  199. omnibase_infra/handlers/models/vault/model_vault_list_payload.py +58 -0
  200. omnibase_infra/handlers/models/vault/model_vault_renew_token_payload.py +67 -0
  201. omnibase_infra/handlers/models/vault/model_vault_retry_config.py +66 -0
  202. omnibase_infra/handlers/models/vault/model_vault_secret_payload.py +106 -0
  203. omnibase_infra/handlers/models/vault/model_vault_write_payload.py +66 -0
  204. omnibase_infra/handlers/models/vault/registry_payload_vault.py +213 -0
  205. omnibase_infra/handlers/registration_storage/__init__.py +43 -0
  206. omnibase_infra/handlers/registration_storage/handler_registration_storage_mock.py +392 -0
  207. omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +922 -0
  208. omnibase_infra/handlers/registration_storage/models/__init__.py +23 -0
  209. omnibase_infra/handlers/registration_storage/models/model_delete_registration_request.py +58 -0
  210. omnibase_infra/handlers/registration_storage/models/model_update_registration_request.py +73 -0
  211. omnibase_infra/handlers/registration_storage/protocol_registration_persistence.py +191 -0
  212. omnibase_infra/handlers/service_discovery/__init__.py +43 -0
  213. omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +1051 -0
  214. omnibase_infra/handlers/service_discovery/handler_service_discovery_mock.py +258 -0
  215. omnibase_infra/handlers/service_discovery/models/__init__.py +22 -0
  216. omnibase_infra/handlers/service_discovery/models/model_discovery_result.py +64 -0
  217. omnibase_infra/handlers/service_discovery/models/model_registration_result.py +138 -0
  218. omnibase_infra/handlers/service_discovery/models/model_service_info.py +109 -0
  219. omnibase_infra/handlers/service_discovery/protocol_discovery_operations.py +170 -0
  220. omnibase_infra/idempotency/__init__.py +94 -0
  221. omnibase_infra/idempotency/models/__init__.py +43 -0
  222. omnibase_infra/idempotency/models/model_idempotency_check_result.py +85 -0
  223. omnibase_infra/idempotency/models/model_idempotency_guard_config.py +130 -0
  224. omnibase_infra/idempotency/models/model_idempotency_record.py +86 -0
  225. omnibase_infra/idempotency/models/model_idempotency_store_health_check_result.py +81 -0
  226. omnibase_infra/idempotency/models/model_idempotency_store_metrics.py +140 -0
  227. omnibase_infra/idempotency/models/model_postgres_idempotency_store_config.py +299 -0
  228. omnibase_infra/idempotency/protocol_idempotency_store.py +184 -0
  229. omnibase_infra/idempotency/store_inmemory.py +265 -0
  230. omnibase_infra/idempotency/store_postgres.py +923 -0
  231. omnibase_infra/infrastructure/__init__.py +0 -0
  232. omnibase_infra/migrations/001_create_event_ledger.sql +166 -0
  233. omnibase_infra/migrations/001_drop_event_ledger.sql +18 -0
  234. omnibase_infra/mixins/__init__.py +71 -0
  235. omnibase_infra/mixins/mixin_async_circuit_breaker.py +656 -0
  236. omnibase_infra/mixins/mixin_dict_like_accessors.py +146 -0
  237. omnibase_infra/mixins/mixin_envelope_extraction.py +119 -0
  238. omnibase_infra/mixins/mixin_node_introspection.py +2670 -0
  239. omnibase_infra/mixins/mixin_retry_execution.py +386 -0
  240. omnibase_infra/mixins/protocol_circuit_breaker_aware.py +133 -0
  241. omnibase_infra/models/__init__.py +144 -0
  242. omnibase_infra/models/bindings/__init__.py +59 -0
  243. omnibase_infra/models/bindings/constants.py +144 -0
  244. omnibase_infra/models/bindings/model_binding_resolution_result.py +103 -0
  245. omnibase_infra/models/bindings/model_operation_binding.py +44 -0
  246. omnibase_infra/models/bindings/model_operation_bindings_subcontract.py +152 -0
  247. omnibase_infra/models/bindings/model_parsed_binding.py +52 -0
  248. omnibase_infra/models/corpus/__init__.py +17 -0
  249. omnibase_infra/models/corpus/model_capture_config.py +133 -0
  250. omnibase_infra/models/corpus/model_capture_result.py +86 -0
  251. omnibase_infra/models/discovery/__init__.py +42 -0
  252. omnibase_infra/models/discovery/model_dependency_spec.py +319 -0
  253. omnibase_infra/models/discovery/model_discovered_capabilities.py +50 -0
  254. omnibase_infra/models/discovery/model_introspection_config.py +330 -0
  255. omnibase_infra/models/discovery/model_introspection_performance_metrics.py +169 -0
  256. omnibase_infra/models/discovery/model_introspection_task_config.py +116 -0
  257. omnibase_infra/models/dispatch/__init__.py +155 -0
  258. omnibase_infra/models/dispatch/model_debug_trace_snapshot.py +114 -0
  259. omnibase_infra/models/dispatch/model_dispatch_context.py +439 -0
  260. omnibase_infra/models/dispatch/model_dispatch_error.py +336 -0
  261. omnibase_infra/models/dispatch/model_dispatch_log_context.py +400 -0
  262. omnibase_infra/models/dispatch/model_dispatch_metadata.py +228 -0
  263. omnibase_infra/models/dispatch/model_dispatch_metrics.py +496 -0
  264. omnibase_infra/models/dispatch/model_dispatch_outcome.py +317 -0
  265. omnibase_infra/models/dispatch/model_dispatch_outputs.py +231 -0
  266. omnibase_infra/models/dispatch/model_dispatch_result.py +436 -0
  267. omnibase_infra/models/dispatch/model_dispatch_route.py +279 -0
  268. omnibase_infra/models/dispatch/model_dispatcher_metrics.py +275 -0
  269. omnibase_infra/models/dispatch/model_dispatcher_registration.py +352 -0
  270. omnibase_infra/models/dispatch/model_materialized_dispatch.py +141 -0
  271. omnibase_infra/models/dispatch/model_parsed_topic.py +135 -0
  272. omnibase_infra/models/dispatch/model_topic_parser.py +725 -0
  273. omnibase_infra/models/dispatch/model_tracing_context.py +285 -0
  274. omnibase_infra/models/errors/__init__.py +45 -0
  275. omnibase_infra/models/errors/model_handler_validation_error.py +594 -0
  276. omnibase_infra/models/errors/model_infra_error_context.py +99 -0
  277. omnibase_infra/models/errors/model_message_type_registry_error_context.py +71 -0
  278. omnibase_infra/models/errors/model_timeout_error_context.py +110 -0
  279. omnibase_infra/models/handlers/__init__.py +80 -0
  280. omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
  281. omnibase_infra/models/handlers/model_contract_discovery_result.py +82 -0
  282. omnibase_infra/models/handlers/model_handler_descriptor.py +200 -0
  283. omnibase_infra/models/handlers/model_handler_identifier.py +215 -0
  284. omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
  285. omnibase_infra/models/health/__init__.py +9 -0
  286. omnibase_infra/models/health/model_health_check_result.py +40 -0
  287. omnibase_infra/models/lifecycle/__init__.py +39 -0
  288. omnibase_infra/models/logging/__init__.py +51 -0
  289. omnibase_infra/models/logging/model_log_context.py +756 -0
  290. omnibase_infra/models/mcp/__init__.py +15 -0
  291. omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
  292. omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
  293. omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
  294. omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
  295. omnibase_infra/models/model_node_identity.py +126 -0
  296. omnibase_infra/models/model_retry_error_classification.py +78 -0
  297. omnibase_infra/models/projection/__init__.py +43 -0
  298. omnibase_infra/models/projection/model_capability_fields.py +112 -0
  299. omnibase_infra/models/projection/model_registration_projection.py +434 -0
  300. omnibase_infra/models/projection/model_registration_snapshot.py +322 -0
  301. omnibase_infra/models/projection/model_sequence_info.py +182 -0
  302. omnibase_infra/models/projection/model_snapshot_topic_config.py +591 -0
  303. omnibase_infra/models/projectors/__init__.py +41 -0
  304. omnibase_infra/models/projectors/model_projector_column.py +289 -0
  305. omnibase_infra/models/projectors/model_projector_discovery_result.py +65 -0
  306. omnibase_infra/models/projectors/model_projector_index.py +270 -0
  307. omnibase_infra/models/projectors/model_projector_schema.py +415 -0
  308. omnibase_infra/models/projectors/model_projector_validation_error.py +63 -0
  309. omnibase_infra/models/projectors/util_sql_identifiers.py +115 -0
  310. omnibase_infra/models/registration/__init__.py +68 -0
  311. omnibase_infra/models/registration/commands/__init__.py +15 -0
  312. omnibase_infra/models/registration/commands/model_node_registration_acked.py +108 -0
  313. omnibase_infra/models/registration/events/__init__.py +56 -0
  314. omnibase_infra/models/registration/events/model_node_became_active.py +103 -0
  315. omnibase_infra/models/registration/events/model_node_liveness_expired.py +103 -0
  316. omnibase_infra/models/registration/events/model_node_registration_accepted.py +98 -0
  317. omnibase_infra/models/registration/events/model_node_registration_ack_received.py +98 -0
  318. omnibase_infra/models/registration/events/model_node_registration_ack_timed_out.py +112 -0
  319. omnibase_infra/models/registration/events/model_node_registration_initiated.py +107 -0
  320. omnibase_infra/models/registration/events/model_node_registration_rejected.py +104 -0
  321. omnibase_infra/models/registration/model_event_bus_topic_entry.py +59 -0
  322. omnibase_infra/models/registration/model_introspection_metrics.py +253 -0
  323. omnibase_infra/models/registration/model_node_capabilities.py +190 -0
  324. omnibase_infra/models/registration/model_node_event_bus_config.py +99 -0
  325. omnibase_infra/models/registration/model_node_heartbeat_event.py +126 -0
  326. omnibase_infra/models/registration/model_node_introspection_event.py +195 -0
  327. omnibase_infra/models/registration/model_node_metadata.py +79 -0
  328. omnibase_infra/models/registration/model_node_registration.py +162 -0
  329. omnibase_infra/models/registration/model_node_registration_record.py +162 -0
  330. omnibase_infra/models/registry/__init__.py +29 -0
  331. omnibase_infra/models/registry/model_domain_constraint.py +202 -0
  332. omnibase_infra/models/registry/model_message_type_entry.py +271 -0
  333. omnibase_infra/models/resilience/__init__.py +9 -0
  334. omnibase_infra/models/resilience/model_circuit_breaker_config.py +227 -0
  335. omnibase_infra/models/routing/__init__.py +25 -0
  336. omnibase_infra/models/routing/model_routing_entry.py +52 -0
  337. omnibase_infra/models/routing/model_routing_subcontract.py +70 -0
  338. omnibase_infra/models/runtime/__init__.py +49 -0
  339. omnibase_infra/models/runtime/model_contract_security_config.py +41 -0
  340. omnibase_infra/models/runtime/model_discovery_error.py +81 -0
  341. omnibase_infra/models/runtime/model_discovery_result.py +162 -0
  342. omnibase_infra/models/runtime/model_discovery_warning.py +74 -0
  343. omnibase_infra/models/runtime/model_failed_plugin_load.py +63 -0
  344. omnibase_infra/models/runtime/model_handler_contract.py +296 -0
  345. omnibase_infra/models/runtime/model_loaded_handler.py +129 -0
  346. omnibase_infra/models/runtime/model_plugin_load_context.py +93 -0
  347. omnibase_infra/models/runtime/model_plugin_load_summary.py +124 -0
  348. omnibase_infra/models/security/__init__.py +50 -0
  349. omnibase_infra/models/security/classification_levels.py +99 -0
  350. omnibase_infra/models/security/model_environment_policy.py +145 -0
  351. omnibase_infra/models/security/model_handler_security_policy.py +107 -0
  352. omnibase_infra/models/security/model_security_error.py +81 -0
  353. omnibase_infra/models/security/model_security_validation_result.py +328 -0
  354. omnibase_infra/models/security/model_security_warning.py +67 -0
  355. omnibase_infra/models/snapshot/__init__.py +27 -0
  356. omnibase_infra/models/snapshot/model_field_change.py +65 -0
  357. omnibase_infra/models/snapshot/model_snapshot.py +270 -0
  358. omnibase_infra/models/snapshot/model_snapshot_diff.py +203 -0
  359. omnibase_infra/models/snapshot/model_subject_ref.py +81 -0
  360. omnibase_infra/models/types/__init__.py +71 -0
  361. omnibase_infra/models/validation/__init__.py +89 -0
  362. omnibase_infra/models/validation/model_any_type_validation_result.py +118 -0
  363. omnibase_infra/models/validation/model_any_type_violation.py +141 -0
  364. omnibase_infra/models/validation/model_category_match_result.py +345 -0
  365. omnibase_infra/models/validation/model_chain_violation.py +166 -0
  366. omnibase_infra/models/validation/model_coverage_metrics.py +316 -0
  367. omnibase_infra/models/validation/model_execution_shape_rule.py +159 -0
  368. omnibase_infra/models/validation/model_execution_shape_validation.py +208 -0
  369. omnibase_infra/models/validation/model_execution_shape_validation_result.py +294 -0
  370. omnibase_infra/models/validation/model_execution_shape_violation.py +122 -0
  371. omnibase_infra/models/validation/model_localhandler_validation_result.py +139 -0
  372. omnibase_infra/models/validation/model_localhandler_violation.py +100 -0
  373. omnibase_infra/models/validation/model_output_validation_params.py +74 -0
  374. omnibase_infra/models/validation/model_validate_and_raise_params.py +84 -0
  375. omnibase_infra/models/validation/model_validation_error_params.py +84 -0
  376. omnibase_infra/models/validation/model_validation_outcome.py +287 -0
  377. omnibase_infra/nodes/__init__.py +57 -0
  378. omnibase_infra/nodes/architecture_validator/__init__.py +79 -0
  379. omnibase_infra/nodes/architecture_validator/contract.yaml +252 -0
  380. omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +203 -0
  381. omnibase_infra/nodes/architecture_validator/mixins/__init__.py +16 -0
  382. omnibase_infra/nodes/architecture_validator/mixins/mixin_file_path_rule.py +92 -0
  383. omnibase_infra/nodes/architecture_validator/models/__init__.py +36 -0
  384. omnibase_infra/nodes/architecture_validator/models/model_architecture_validation_request.py +56 -0
  385. omnibase_infra/nodes/architecture_validator/models/model_architecture_validation_result.py +311 -0
  386. omnibase_infra/nodes/architecture_validator/models/model_architecture_violation.py +163 -0
  387. omnibase_infra/nodes/architecture_validator/models/model_rule_check_result.py +265 -0
  388. omnibase_infra/nodes/architecture_validator/models/model_validation_request.py +105 -0
  389. omnibase_infra/nodes/architecture_validator/models/model_validation_result.py +314 -0
  390. omnibase_infra/nodes/architecture_validator/node.py +262 -0
  391. omnibase_infra/nodes/architecture_validator/node_architecture_validator.py +383 -0
  392. omnibase_infra/nodes/architecture_validator/protocols/__init__.py +9 -0
  393. omnibase_infra/nodes/architecture_validator/protocols/protocol_architecture_rule.py +225 -0
  394. omnibase_infra/nodes/architecture_validator/registry/__init__.py +28 -0
  395. omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +106 -0
  396. omnibase_infra/nodes/architecture_validator/validators/__init__.py +104 -0
  397. omnibase_infra/nodes/architecture_validator/validators/validator_no_direct_dispatch.py +422 -0
  398. omnibase_infra/nodes/architecture_validator/validators/validator_no_handler_publishing.py +481 -0
  399. omnibase_infra/nodes/architecture_validator/validators/validator_no_orchestrator_fsm.py +491 -0
  400. omnibase_infra/nodes/contract_registry_reducer/__init__.py +29 -0
  401. omnibase_infra/nodes/contract_registry_reducer/contract.yaml +255 -0
  402. omnibase_infra/nodes/contract_registry_reducer/models/__init__.py +38 -0
  403. omnibase_infra/nodes/contract_registry_reducer/models/model_contract_registry_state.py +266 -0
  404. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_cleanup_topic_references.py +55 -0
  405. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_deactivate_contract.py +58 -0
  406. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_mark_stale.py +49 -0
  407. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_heartbeat.py +71 -0
  408. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_topic.py +66 -0
  409. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_upsert_contract.py +92 -0
  410. omnibase_infra/nodes/contract_registry_reducer/node.py +121 -0
  411. omnibase_infra/nodes/contract_registry_reducer/reducer.py +784 -0
  412. omnibase_infra/nodes/contract_registry_reducer/registry/__init__.py +9 -0
  413. omnibase_infra/nodes/contract_registry_reducer/registry/registry_infra_contract_registry_reducer.py +101 -0
  414. omnibase_infra/nodes/effects/README.md +358 -0
  415. omnibase_infra/nodes/effects/__init__.py +26 -0
  416. omnibase_infra/nodes/effects/contract.yaml +167 -0
  417. omnibase_infra/nodes/effects/models/__init__.py +32 -0
  418. omnibase_infra/nodes/effects/models/model_backend_result.py +190 -0
  419. omnibase_infra/nodes/effects/models/model_effect_idempotency_config.py +92 -0
  420. omnibase_infra/nodes/effects/models/model_registry_request.py +132 -0
  421. omnibase_infra/nodes/effects/models/model_registry_response.py +263 -0
  422. omnibase_infra/nodes/effects/protocol_consul_client.py +89 -0
  423. omnibase_infra/nodes/effects/protocol_effect_idempotency_store.py +143 -0
  424. omnibase_infra/nodes/effects/protocol_postgres_adapter.py +96 -0
  425. omnibase_infra/nodes/effects/registry_effect.py +525 -0
  426. omnibase_infra/nodes/effects/store_effect_idempotency_inmemory.py +425 -0
  427. omnibase_infra/nodes/handlers/consul/contract.yaml +85 -0
  428. omnibase_infra/nodes/handlers/db/contract.yaml +72 -0
  429. omnibase_infra/nodes/handlers/graph/contract.yaml +127 -0
  430. omnibase_infra/nodes/handlers/http/contract.yaml +74 -0
  431. omnibase_infra/nodes/handlers/intent/contract.yaml +66 -0
  432. omnibase_infra/nodes/handlers/mcp/contract.yaml +69 -0
  433. omnibase_infra/nodes/handlers/vault/contract.yaml +91 -0
  434. omnibase_infra/nodes/node_intent_storage_effect/__init__.py +50 -0
  435. omnibase_infra/nodes/node_intent_storage_effect/contract.yaml +194 -0
  436. omnibase_infra/nodes/node_intent_storage_effect/models/__init__.py +24 -0
  437. omnibase_infra/nodes/node_intent_storage_effect/models/model_intent_storage_input.py +141 -0
  438. omnibase_infra/nodes/node_intent_storage_effect/models/model_intent_storage_output.py +130 -0
  439. omnibase_infra/nodes/node_intent_storage_effect/node.py +94 -0
  440. omnibase_infra/nodes/node_intent_storage_effect/registry/__init__.py +35 -0
  441. omnibase_infra/nodes/node_intent_storage_effect/registry/registry_infra_intent_storage.py +294 -0
  442. omnibase_infra/nodes/node_ledger_projection_compute/__init__.py +50 -0
  443. omnibase_infra/nodes/node_ledger_projection_compute/contract.yaml +104 -0
  444. omnibase_infra/nodes/node_ledger_projection_compute/node.py +284 -0
  445. omnibase_infra/nodes/node_ledger_projection_compute/registry/__init__.py +29 -0
  446. omnibase_infra/nodes/node_ledger_projection_compute/registry/registry_infra_ledger_projection.py +118 -0
  447. omnibase_infra/nodes/node_ledger_write_effect/__init__.py +82 -0
  448. omnibase_infra/nodes/node_ledger_write_effect/contract.yaml +200 -0
  449. omnibase_infra/nodes/node_ledger_write_effect/handlers/__init__.py +22 -0
  450. omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_append.py +372 -0
  451. omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_query.py +597 -0
  452. omnibase_infra/nodes/node_ledger_write_effect/models/__init__.py +31 -0
  453. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_append_result.py +54 -0
  454. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_entry.py +92 -0
  455. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query.py +53 -0
  456. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query_result.py +41 -0
  457. omnibase_infra/nodes/node_ledger_write_effect/node.py +89 -0
  458. omnibase_infra/nodes/node_ledger_write_effect/protocols/__init__.py +13 -0
  459. omnibase_infra/nodes/node_ledger_write_effect/protocols/protocol_ledger_persistence.py +127 -0
  460. omnibase_infra/nodes/node_ledger_write_effect/registry/__init__.py +9 -0
  461. omnibase_infra/nodes/node_ledger_write_effect/registry/registry_infra_ledger_write.py +121 -0
  462. omnibase_infra/nodes/node_registration_orchestrator/README.md +542 -0
  463. omnibase_infra/nodes/node_registration_orchestrator/__init__.py +120 -0
  464. omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +482 -0
  465. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/__init__.py +53 -0
  466. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_node_introspected.py +376 -0
  467. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_node_registration_acked.py +376 -0
  468. omnibase_infra/nodes/node_registration_orchestrator/dispatchers/dispatcher_runtime_tick.py +373 -0
  469. omnibase_infra/nodes/node_registration_orchestrator/handlers/__init__.py +62 -0
  470. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_heartbeat.py +376 -0
  471. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +694 -0
  472. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_registration_acked.py +458 -0
  473. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_runtime_tick.py +364 -0
  474. omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +544 -0
  475. omnibase_infra/nodes/node_registration_orchestrator/models/__init__.py +75 -0
  476. omnibase_infra/nodes/node_registration_orchestrator/models/model_consul_intent_payload.py +194 -0
  477. omnibase_infra/nodes/node_registration_orchestrator/models/model_consul_registration_intent.py +67 -0
  478. omnibase_infra/nodes/node_registration_orchestrator/models/model_intent_execution_result.py +50 -0
  479. omnibase_infra/nodes/node_registration_orchestrator/models/model_node_liveness_expired.py +107 -0
  480. omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_config.py +67 -0
  481. omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_input.py +41 -0
  482. omnibase_infra/nodes/node_registration_orchestrator/models/model_orchestrator_output.py +166 -0
  483. omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_intent_payload.py +235 -0
  484. omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_upsert_intent.py +68 -0
  485. omnibase_infra/nodes/node_registration_orchestrator/models/model_reducer_execution_result.py +384 -0
  486. omnibase_infra/nodes/node_registration_orchestrator/models/model_reducer_state.py +60 -0
  487. omnibase_infra/nodes/node_registration_orchestrator/models/model_registration_intent.py +177 -0
  488. omnibase_infra/nodes/node_registration_orchestrator/models/model_registry_intent.py +247 -0
  489. omnibase_infra/nodes/node_registration_orchestrator/node.py +195 -0
  490. omnibase_infra/nodes/node_registration_orchestrator/plugin.py +909 -0
  491. omnibase_infra/nodes/node_registration_orchestrator/protocols.py +439 -0
  492. omnibase_infra/nodes/node_registration_orchestrator/registry/__init__.py +41 -0
  493. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +528 -0
  494. omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +393 -0
  495. omnibase_infra/nodes/node_registration_orchestrator/wiring.py +743 -0
  496. omnibase_infra/nodes/node_registration_reducer/__init__.py +15 -0
  497. omnibase_infra/nodes/node_registration_reducer/contract.yaml +301 -0
  498. omnibase_infra/nodes/node_registration_reducer/models/__init__.py +38 -0
  499. omnibase_infra/nodes/node_registration_reducer/models/model_validation_result.py +113 -0
  500. omnibase_infra/nodes/node_registration_reducer/node.py +139 -0
  501. omnibase_infra/nodes/node_registration_reducer/registry/__init__.py +9 -0
  502. omnibase_infra/nodes/node_registration_reducer/registry/registry_infra_node_registration_reducer.py +79 -0
  503. omnibase_infra/nodes/node_registration_storage_effect/__init__.py +41 -0
  504. omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +220 -0
  505. omnibase_infra/nodes/node_registration_storage_effect/models/__init__.py +44 -0
  506. omnibase_infra/nodes/node_registration_storage_effect/models/model_delete_result.py +132 -0
  507. omnibase_infra/nodes/node_registration_storage_effect/models/model_registration_record.py +199 -0
  508. omnibase_infra/nodes/node_registration_storage_effect/models/model_registration_update.py +155 -0
  509. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_health_check_details.py +123 -0
  510. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_health_check_result.py +117 -0
  511. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_query.py +100 -0
  512. omnibase_infra/nodes/node_registration_storage_effect/models/model_storage_result.py +136 -0
  513. omnibase_infra/nodes/node_registration_storage_effect/models/model_upsert_result.py +127 -0
  514. omnibase_infra/nodes/node_registration_storage_effect/node.py +112 -0
  515. omnibase_infra/nodes/node_registration_storage_effect/protocols/__init__.py +22 -0
  516. omnibase_infra/nodes/node_registration_storage_effect/protocols/protocol_registration_persistence.py +333 -0
  517. omnibase_infra/nodes/node_registration_storage_effect/registry/__init__.py +23 -0
  518. omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +215 -0
  519. omnibase_infra/nodes/node_registry_effect/__init__.py +85 -0
  520. omnibase_infra/nodes/node_registry_effect/contract.yaml +677 -0
  521. omnibase_infra/nodes/node_registry_effect/handlers/__init__.py +70 -0
  522. omnibase_infra/nodes/node_registry_effect/handlers/handler_consul_deregister.py +211 -0
  523. omnibase_infra/nodes/node_registry_effect/handlers/handler_consul_register.py +212 -0
  524. omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +417 -0
  525. omnibase_infra/nodes/node_registry_effect/handlers/handler_postgres_deactivate.py +215 -0
  526. omnibase_infra/nodes/node_registry_effect/handlers/handler_postgres_upsert.py +208 -0
  527. omnibase_infra/nodes/node_registry_effect/models/__init__.py +43 -0
  528. omnibase_infra/nodes/node_registry_effect/models/model_partial_retry_request.py +92 -0
  529. omnibase_infra/nodes/node_registry_effect/node.py +165 -0
  530. omnibase_infra/nodes/node_registry_effect/registry/__init__.py +27 -0
  531. omnibase_infra/nodes/node_registry_effect/registry/registry_infra_registry_effect.py +196 -0
  532. omnibase_infra/nodes/node_service_discovery_effect/__init__.py +111 -0
  533. omnibase_infra/nodes/node_service_discovery_effect/contract.yaml +246 -0
  534. omnibase_infra/nodes/node_service_discovery_effect/models/__init__.py +67 -0
  535. omnibase_infra/nodes/node_service_discovery_effect/models/enum_health_status.py +72 -0
  536. omnibase_infra/nodes/node_service_discovery_effect/models/enum_service_discovery_operation.py +58 -0
  537. omnibase_infra/nodes/node_service_discovery_effect/models/model_discovery_query.py +99 -0
  538. omnibase_infra/nodes/node_service_discovery_effect/models/model_discovery_result.py +98 -0
  539. omnibase_infra/nodes/node_service_discovery_effect/models/model_health_check_config.py +121 -0
  540. omnibase_infra/nodes/node_service_discovery_effect/models/model_query_metadata.py +63 -0
  541. omnibase_infra/nodes/node_service_discovery_effect/models/model_registration_result.py +130 -0
  542. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_discovery_health_check_details.py +111 -0
  543. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_discovery_health_check_result.py +119 -0
  544. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_info.py +106 -0
  545. omnibase_infra/nodes/node_service_discovery_effect/models/model_service_registration.py +121 -0
  546. omnibase_infra/nodes/node_service_discovery_effect/node.py +111 -0
  547. omnibase_infra/nodes/node_service_discovery_effect/protocols/__init__.py +14 -0
  548. omnibase_infra/nodes/node_service_discovery_effect/protocols/protocol_discovery_operations.py +279 -0
  549. omnibase_infra/nodes/node_service_discovery_effect/registry/__init__.py +13 -0
  550. omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +222 -0
  551. omnibase_infra/nodes/reducers/__init__.py +30 -0
  552. omnibase_infra/nodes/reducers/models/__init__.py +37 -0
  553. omnibase_infra/nodes/reducers/models/model_payload_consul_register.py +87 -0
  554. omnibase_infra/nodes/reducers/models/model_payload_ledger_append.py +133 -0
  555. omnibase_infra/nodes/reducers/models/model_payload_postgres_upsert_registration.py +60 -0
  556. omnibase_infra/nodes/reducers/models/model_registration_confirmation.py +166 -0
  557. omnibase_infra/nodes/reducers/models/model_registration_state.py +433 -0
  558. omnibase_infra/nodes/reducers/registration_reducer.py +1138 -0
  559. omnibase_infra/observability/__init__.py +143 -0
  560. omnibase_infra/observability/constants_metrics.py +91 -0
  561. omnibase_infra/observability/factory_observability_sink.py +525 -0
  562. omnibase_infra/observability/handlers/__init__.py +118 -0
  563. omnibase_infra/observability/handlers/handler_logging_structured.py +967 -0
  564. omnibase_infra/observability/handlers/handler_metrics_prometheus.py +1120 -0
  565. omnibase_infra/observability/handlers/model_logging_handler_config.py +71 -0
  566. omnibase_infra/observability/handlers/model_logging_handler_response.py +77 -0
  567. omnibase_infra/observability/handlers/model_metrics_handler_config.py +172 -0
  568. omnibase_infra/observability/handlers/model_metrics_handler_payload.py +135 -0
  569. omnibase_infra/observability/handlers/model_metrics_handler_response.py +101 -0
  570. omnibase_infra/observability/hooks/__init__.py +74 -0
  571. omnibase_infra/observability/hooks/hook_observability.py +1223 -0
  572. omnibase_infra/observability/models/__init__.py +30 -0
  573. omnibase_infra/observability/models/enum_required_log_context_key.py +77 -0
  574. omnibase_infra/observability/models/model_buffered_log_entry.py +117 -0
  575. omnibase_infra/observability/models/model_logging_sink_config.py +73 -0
  576. omnibase_infra/observability/models/model_metrics_sink_config.py +156 -0
  577. omnibase_infra/observability/sinks/__init__.py +69 -0
  578. omnibase_infra/observability/sinks/sink_logging_structured.py +809 -0
  579. omnibase_infra/observability/sinks/sink_metrics_prometheus.py +710 -0
  580. omnibase_infra/plugins/__init__.py +27 -0
  581. omnibase_infra/plugins/examples/__init__.py +28 -0
  582. omnibase_infra/plugins/examples/plugin_json_normalizer.py +271 -0
  583. omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +210 -0
  584. omnibase_infra/plugins/models/__init__.py +21 -0
  585. omnibase_infra/plugins/models/model_plugin_context.py +76 -0
  586. omnibase_infra/plugins/models/model_plugin_input_data.py +58 -0
  587. omnibase_infra/plugins/models/model_plugin_output_data.py +62 -0
  588. omnibase_infra/plugins/plugin_compute_base.py +449 -0
  589. omnibase_infra/projectors/__init__.py +30 -0
  590. omnibase_infra/projectors/contracts/__init__.py +63 -0
  591. omnibase_infra/projectors/contracts/registration_projector.yaml +370 -0
  592. omnibase_infra/projectors/projection_reader_registration.py +1559 -0
  593. omnibase_infra/projectors/snapshot_publisher_registration.py +1329 -0
  594. omnibase_infra/protocols/__init__.py +104 -0
  595. omnibase_infra/protocols/protocol_capability_projection.py +253 -0
  596. omnibase_infra/protocols/protocol_capability_query.py +251 -0
  597. omnibase_infra/protocols/protocol_container_aware.py +200 -0
  598. omnibase_infra/protocols/protocol_dispatch_engine.py +152 -0
  599. omnibase_infra/protocols/protocol_event_bus_like.py +127 -0
  600. omnibase_infra/protocols/protocol_event_projector.py +96 -0
  601. omnibase_infra/protocols/protocol_idempotency_store.py +142 -0
  602. omnibase_infra/protocols/protocol_message_dispatcher.py +247 -0
  603. omnibase_infra/protocols/protocol_message_type_registry.py +306 -0
  604. omnibase_infra/protocols/protocol_plugin_compute.py +368 -0
  605. omnibase_infra/protocols/protocol_projector_schema_validator.py +82 -0
  606. omnibase_infra/protocols/protocol_registry_metrics.py +215 -0
  607. omnibase_infra/protocols/protocol_snapshot_publisher.py +396 -0
  608. omnibase_infra/protocols/protocol_snapshot_store.py +567 -0
  609. omnibase_infra/runtime/__init__.py +445 -0
  610. omnibase_infra/runtime/binding_config_resolver.py +2771 -0
  611. omnibase_infra/runtime/binding_resolver.py +753 -0
  612. omnibase_infra/runtime/chain_aware_dispatch.py +467 -0
  613. omnibase_infra/runtime/constants_notification.py +75 -0
  614. omnibase_infra/runtime/constants_security.py +70 -0
  615. omnibase_infra/runtime/contract_handler_discovery.py +587 -0
  616. omnibase_infra/runtime/contract_loaders/__init__.py +51 -0
  617. omnibase_infra/runtime/contract_loaders/handler_routing_loader.py +464 -0
  618. omnibase_infra/runtime/contract_loaders/operation_bindings_loader.py +789 -0
  619. omnibase_infra/runtime/dispatch_context_enforcer.py +427 -0
  620. omnibase_infra/runtime/emit_daemon/__init__.py +97 -0
  621. omnibase_infra/runtime/emit_daemon/cli.py +844 -0
  622. omnibase_infra/runtime/emit_daemon/client.py +811 -0
  623. omnibase_infra/runtime/emit_daemon/config.py +535 -0
  624. omnibase_infra/runtime/emit_daemon/daemon.py +812 -0
  625. omnibase_infra/runtime/emit_daemon/event_registry.py +477 -0
  626. omnibase_infra/runtime/emit_daemon/model_daemon_request.py +139 -0
  627. omnibase_infra/runtime/emit_daemon/model_daemon_response.py +191 -0
  628. omnibase_infra/runtime/emit_daemon/queue.py +618 -0
  629. omnibase_infra/runtime/enums/__init__.py +18 -0
  630. omnibase_infra/runtime/enums/enum_config_ref_scheme.py +33 -0
  631. omnibase_infra/runtime/enums/enum_scheduler_status.py +170 -0
  632. omnibase_infra/runtime/envelope_validator.py +179 -0
  633. omnibase_infra/runtime/event_bus_subcontract_wiring.py +466 -0
  634. omnibase_infra/runtime/handler_bootstrap_source.py +507 -0
  635. omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
  636. omnibase_infra/runtime/handler_contract_source.py +750 -0
  637. omnibase_infra/runtime/handler_identity.py +81 -0
  638. omnibase_infra/runtime/handler_plugin_loader.py +2046 -0
  639. omnibase_infra/runtime/handler_registry.py +329 -0
  640. omnibase_infra/runtime/handler_source_resolver.py +367 -0
  641. omnibase_infra/runtime/invocation_security_enforcer.py +427 -0
  642. omnibase_infra/runtime/kafka_contract_source.py +984 -0
  643. omnibase_infra/runtime/kernel.py +40 -0
  644. omnibase_infra/runtime/mixin_policy_validation.py +522 -0
  645. omnibase_infra/runtime/mixin_semver_cache.py +402 -0
  646. omnibase_infra/runtime/mixins/__init__.py +24 -0
  647. omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
  648. omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +778 -0
  649. omnibase_infra/runtime/models/__init__.py +229 -0
  650. omnibase_infra/runtime/models/model_batch_lifecycle_result.py +217 -0
  651. omnibase_infra/runtime/models/model_binding_config.py +168 -0
  652. omnibase_infra/runtime/models/model_binding_config_cache_stats.py +135 -0
  653. omnibase_infra/runtime/models/model_binding_config_resolver_config.py +329 -0
  654. omnibase_infra/runtime/models/model_cached_secret.py +138 -0
  655. omnibase_infra/runtime/models/model_compute_key.py +138 -0
  656. omnibase_infra/runtime/models/model_compute_registration.py +97 -0
  657. omnibase_infra/runtime/models/model_config_cache_entry.py +61 -0
  658. omnibase_infra/runtime/models/model_config_ref.py +331 -0
  659. omnibase_infra/runtime/models/model_config_ref_parse_result.py +125 -0
  660. omnibase_infra/runtime/models/model_contract_load_result.py +224 -0
  661. omnibase_infra/runtime/models/model_domain_plugin_config.py +92 -0
  662. omnibase_infra/runtime/models/model_domain_plugin_result.py +270 -0
  663. omnibase_infra/runtime/models/model_duplicate_response.py +54 -0
  664. omnibase_infra/runtime/models/model_enabled_protocols_config.py +61 -0
  665. omnibase_infra/runtime/models/model_event_bus_config.py +54 -0
  666. omnibase_infra/runtime/models/model_failed_component.py +55 -0
  667. omnibase_infra/runtime/models/model_health_check_response.py +168 -0
  668. omnibase_infra/runtime/models/model_health_check_result.py +229 -0
  669. omnibase_infra/runtime/models/model_lifecycle_result.py +245 -0
  670. omnibase_infra/runtime/models/model_logging_config.py +42 -0
  671. omnibase_infra/runtime/models/model_optional_correlation_id.py +167 -0
  672. omnibase_infra/runtime/models/model_optional_string.py +94 -0
  673. omnibase_infra/runtime/models/model_optional_uuid.py +110 -0
  674. omnibase_infra/runtime/models/model_policy_context.py +100 -0
  675. omnibase_infra/runtime/models/model_policy_key.py +138 -0
  676. omnibase_infra/runtime/models/model_policy_registration.py +139 -0
  677. omnibase_infra/runtime/models/model_policy_result.py +103 -0
  678. omnibase_infra/runtime/models/model_policy_type_filter.py +157 -0
  679. omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
  680. omnibase_infra/runtime/models/model_projector_plugin_loader_config.py +47 -0
  681. omnibase_infra/runtime/models/model_protocol_registration_config.py +65 -0
  682. omnibase_infra/runtime/models/model_retry_policy.py +105 -0
  683. omnibase_infra/runtime/models/model_runtime_config.py +150 -0
  684. omnibase_infra/runtime/models/model_runtime_contract_config.py +268 -0
  685. omnibase_infra/runtime/models/model_runtime_scheduler_config.py +625 -0
  686. omnibase_infra/runtime/models/model_runtime_scheduler_metrics.py +233 -0
  687. omnibase_infra/runtime/models/model_runtime_tick.py +193 -0
  688. omnibase_infra/runtime/models/model_secret_cache_stats.py +82 -0
  689. omnibase_infra/runtime/models/model_secret_mapping.py +63 -0
  690. omnibase_infra/runtime/models/model_secret_resolver_config.py +107 -0
  691. omnibase_infra/runtime/models/model_secret_resolver_metrics.py +111 -0
  692. omnibase_infra/runtime/models/model_secret_source_info.py +72 -0
  693. omnibase_infra/runtime/models/model_secret_source_spec.py +66 -0
  694. omnibase_infra/runtime/models/model_security_config.py +109 -0
  695. omnibase_infra/runtime/models/model_shutdown_batch_result.py +75 -0
  696. omnibase_infra/runtime/models/model_shutdown_config.py +94 -0
  697. omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
  698. omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
  699. omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
  700. omnibase_infra/runtime/projector_plugin_loader.py +1462 -0
  701. omnibase_infra/runtime/projector_schema_manager.py +565 -0
  702. omnibase_infra/runtime/projector_shell.py +1330 -0
  703. omnibase_infra/runtime/protocol_contract_descriptor.py +92 -0
  704. omnibase_infra/runtime/protocol_contract_source.py +92 -0
  705. omnibase_infra/runtime/protocol_domain_plugin.py +474 -0
  706. omnibase_infra/runtime/protocol_handler_discovery.py +221 -0
  707. omnibase_infra/runtime/protocol_handler_plugin_loader.py +327 -0
  708. omnibase_infra/runtime/protocol_lifecycle_executor.py +435 -0
  709. omnibase_infra/runtime/protocol_policy.py +366 -0
  710. omnibase_infra/runtime/protocols/__init__.py +37 -0
  711. omnibase_infra/runtime/protocols/protocol_runtime_scheduler.py +468 -0
  712. omnibase_infra/runtime/publisher_topic_scoped.py +294 -0
  713. omnibase_infra/runtime/registry/__init__.py +93 -0
  714. omnibase_infra/runtime/registry/mixin_message_type_query.py +326 -0
  715. omnibase_infra/runtime/registry/mixin_message_type_registration.py +354 -0
  716. omnibase_infra/runtime/registry/registry_event_bus_binding.py +268 -0
  717. omnibase_infra/runtime/registry/registry_message_type.py +542 -0
  718. omnibase_infra/runtime/registry/registry_protocol_binding.py +445 -0
  719. omnibase_infra/runtime/registry_compute.py +1143 -0
  720. omnibase_infra/runtime/registry_contract_source.py +693 -0
  721. omnibase_infra/runtime/registry_dispatcher.py +678 -0
  722. omnibase_infra/runtime/registry_policy.py +1185 -0
  723. omnibase_infra/runtime/runtime_contract_config_loader.py +406 -0
  724. omnibase_infra/runtime/runtime_scheduler.py +1070 -0
  725. omnibase_infra/runtime/secret_resolver.py +2112 -0
  726. omnibase_infra/runtime/security_metadata_validator.py +776 -0
  727. omnibase_infra/runtime/service_kernel.py +1651 -0
  728. omnibase_infra/runtime/service_message_dispatch_engine.py +2350 -0
  729. omnibase_infra/runtime/service_runtime_host_process.py +3493 -0
  730. omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
  731. omnibase_infra/runtime/transition_notification_publisher.py +765 -0
  732. omnibase_infra/runtime/util_container_wiring.py +1124 -0
  733. omnibase_infra/runtime/util_validation.py +314 -0
  734. omnibase_infra/runtime/util_version.py +98 -0
  735. omnibase_infra/runtime/util_wiring.py +723 -0
  736. omnibase_infra/schemas/schema_registration_projection.sql +320 -0
  737. omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
  738. omnibase_infra/services/__init__.py +89 -0
  739. omnibase_infra/services/corpus_capture.py +684 -0
  740. omnibase_infra/services/mcp/__init__.py +31 -0
  741. omnibase_infra/services/mcp/mcp_server_lifecycle.py +449 -0
  742. omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
  743. omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
  744. omnibase_infra/services/mcp/service_mcp_tool_sync.py +565 -0
  745. omnibase_infra/services/registry_api/__init__.py +40 -0
  746. omnibase_infra/services/registry_api/main.py +261 -0
  747. omnibase_infra/services/registry_api/models/__init__.py +66 -0
  748. omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
  749. omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
  750. omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
  751. omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
  752. omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
  753. omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
  754. omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
  755. omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
  756. omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
  757. omnibase_infra/services/registry_api/models/model_warning.py +49 -0
  758. omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
  759. omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
  760. omnibase_infra/services/registry_api/routes.py +371 -0
  761. omnibase_infra/services/registry_api/service.py +837 -0
  762. omnibase_infra/services/service_capability_query.py +945 -0
  763. omnibase_infra/services/service_health.py +898 -0
  764. omnibase_infra/services/service_node_selector.py +530 -0
  765. omnibase_infra/services/service_timeout_emitter.py +699 -0
  766. omnibase_infra/services/service_timeout_scanner.py +394 -0
  767. omnibase_infra/services/session/__init__.py +56 -0
  768. omnibase_infra/services/session/config_consumer.py +137 -0
  769. omnibase_infra/services/session/config_store.py +139 -0
  770. omnibase_infra/services/session/consumer.py +1007 -0
  771. omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
  772. omnibase_infra/services/session/store.py +997 -0
  773. omnibase_infra/services/snapshot/__init__.py +31 -0
  774. omnibase_infra/services/snapshot/service_snapshot.py +647 -0
  775. omnibase_infra/services/snapshot/store_inmemory.py +637 -0
  776. omnibase_infra/services/snapshot/store_postgres.py +1279 -0
  777. omnibase_infra/shared/__init__.py +8 -0
  778. omnibase_infra/testing/__init__.py +10 -0
  779. omnibase_infra/testing/utils.py +23 -0
  780. omnibase_infra/topics/__init__.py +45 -0
  781. omnibase_infra/topics/platform_topic_suffixes.py +140 -0
  782. omnibase_infra/topics/util_topic_composition.py +95 -0
  783. omnibase_infra/types/__init__.py +48 -0
  784. omnibase_infra/types/type_cache_info.py +49 -0
  785. omnibase_infra/types/type_dsn.py +173 -0
  786. omnibase_infra/types/type_infra_aliases.py +60 -0
  787. omnibase_infra/types/typed_dict/__init__.py +29 -0
  788. omnibase_infra/types/typed_dict/typed_dict_envelope_build_params.py +115 -0
  789. omnibase_infra/types/typed_dict/typed_dict_introspection_cache.py +128 -0
  790. omnibase_infra/types/typed_dict/typed_dict_performance_metrics_cache.py +140 -0
  791. omnibase_infra/types/typed_dict_capabilities.py +64 -0
  792. omnibase_infra/utils/__init__.py +117 -0
  793. omnibase_infra/utils/correlation.py +208 -0
  794. omnibase_infra/utils/util_atomic_file.py +261 -0
  795. omnibase_infra/utils/util_consumer_group.py +232 -0
  796. omnibase_infra/utils/util_datetime.py +372 -0
  797. omnibase_infra/utils/util_db_transaction.py +239 -0
  798. omnibase_infra/utils/util_dsn_validation.py +333 -0
  799. omnibase_infra/utils/util_env_parsing.py +264 -0
  800. omnibase_infra/utils/util_error_sanitization.py +457 -0
  801. omnibase_infra/utils/util_pydantic_validators.py +477 -0
  802. omnibase_infra/utils/util_retry_optimistic.py +281 -0
  803. omnibase_infra/utils/util_semver.py +233 -0
  804. omnibase_infra/validation/__init__.py +307 -0
  805. omnibase_infra/validation/contracts/security.validation.yaml +114 -0
  806. omnibase_infra/validation/enums/__init__.py +11 -0
  807. omnibase_infra/validation/enums/enum_contract_violation_severity.py +13 -0
  808. omnibase_infra/validation/infra_validators.py +1514 -0
  809. omnibase_infra/validation/linter_contract.py +907 -0
  810. omnibase_infra/validation/mixin_any_type_classification.py +120 -0
  811. omnibase_infra/validation/mixin_any_type_exemption.py +580 -0
  812. omnibase_infra/validation/mixin_any_type_reporting.py +106 -0
  813. omnibase_infra/validation/mixin_execution_shape_violation_checks.py +596 -0
  814. omnibase_infra/validation/mixin_node_archetype_detection.py +254 -0
  815. omnibase_infra/validation/models/__init__.py +15 -0
  816. omnibase_infra/validation/models/model_contract_lint_result.py +101 -0
  817. omnibase_infra/validation/models/model_contract_violation.py +41 -0
  818. omnibase_infra/validation/service_validation_aggregator.py +395 -0
  819. omnibase_infra/validation/validation_exemptions.yaml +2033 -0
  820. omnibase_infra/validation/validator_any_type.py +715 -0
  821. omnibase_infra/validation/validator_chain_propagation.py +839 -0
  822. omnibase_infra/validation/validator_execution_shape.py +465 -0
  823. omnibase_infra/validation/validator_localhandler.py +261 -0
  824. omnibase_infra/validation/validator_registration_security.py +410 -0
  825. omnibase_infra/validation/validator_routing_coverage.py +1020 -0
  826. omnibase_infra/validation/validator_runtime_shape.py +915 -0
  827. omnibase_infra/validation/validator_security.py +513 -0
  828. omnibase_infra/validation/validator_topic_category.py +1152 -0
  829. omnibase_infra-0.2.6.dist-info/METADATA +197 -0
  830. omnibase_infra-0.2.6.dist-info/RECORD +833 -0
  831. omnibase_infra-0.2.6.dist-info/WHEEL +4 -0
  832. omnibase_infra-0.2.6.dist-info/entry_points.txt +5 -0
  833. omnibase_infra-0.2.6.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,2015 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Graph Database Handler - Implements ProtocolGraphDatabaseHandler from omnibase_spi.
4
+
5
+ Provides backend-agnostic graph database operations via the neo4j async driver,
6
+ supporting Memgraph and Neo4j through Bolt protocol with Cypher queries.
7
+
8
+ Protocol Implementation:
9
+ Implements ProtocolGraphDatabaseHandler from omnibase_spi.protocols.storage,
10
+ providing typed graph operations with models from omnibase_core.models.graph.
11
+
12
+ Supported Operations:
13
+ - execute_query: Execute parameterized Cypher queries
14
+ - execute_query_batch: Transactional batch query execution
15
+ - create_node: Create nodes with labels and properties
16
+ - create_relationship: Create typed relationships between nodes
17
+ - delete_node: Delete nodes with optional cascade (DETACH DELETE)
18
+ - delete_relationship: Delete relationships by ID
19
+ - traverse: Graph traversal with configurable depth and filters
20
+ - health_check: Connection health monitoring
21
+ - describe: Handler metadata introspection
22
+
23
+ Security:
24
+ - All queries use parameterization to prevent injection attacks
25
+ - Credentials are treated as secrets and never logged
26
+ - Health check responses sanitize error messages
27
+
28
+ Circuit Breaker Pattern:
29
+ Uses MixinAsyncCircuitBreaker for fault tolerance with automatic
30
+ recovery from transient failures.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import logging
36
+ import re
37
+ import time
38
+ from collections.abc import Mapping
39
+ from uuid import UUID, uuid4
40
+
41
+ from neo4j import AsyncDriver, AsyncGraphDatabase
42
+ from neo4j.exceptions import (
43
+ AuthError,
44
+ ConstraintError,
45
+ Neo4jError,
46
+ ServiceUnavailable,
47
+ TransactionError,
48
+ )
49
+
50
+ from omnibase_core.container import ModelONEXContainer
51
+ from omnibase_core.models.dispatch import ModelHandlerOutput
52
+ from omnibase_core.models.graph import (
53
+ ModelGraphBatchResult,
54
+ ModelGraphDatabaseNode,
55
+ ModelGraphDeleteResult,
56
+ ModelGraphHandlerMetadata,
57
+ ModelGraphHealthStatus,
58
+ ModelGraphQueryCounters,
59
+ ModelGraphQueryResult,
60
+ ModelGraphQuerySummary,
61
+ ModelGraphRelationship,
62
+ ModelGraphTraversalFilters,
63
+ ModelGraphTraversalResult,
64
+ )
65
+ from omnibase_core.types import JsonType
66
+ from omnibase_infra.enums import EnumInfraTransportType, EnumResponseStatus
67
+ from omnibase_infra.errors import (
68
+ InfraAuthenticationError,
69
+ InfraConnectionError,
70
+ ModelInfraErrorContext,
71
+ RuntimeHostError,
72
+ )
73
+ from omnibase_infra.handlers.models.graph import (
74
+ ModelGraphExecutePayload,
75
+ ModelGraphHandlerPayload,
76
+ ModelGraphQueryPayload,
77
+ ModelGraphRecord,
78
+ )
79
+ from omnibase_infra.handlers.models.model_graph_handler_response import (
80
+ ModelGraphHandlerResponse,
81
+ )
82
+ from omnibase_infra.mixins import MixinAsyncCircuitBreaker, MixinEnvelopeExtraction
83
+ from omnibase_infra.utils.util_env_parsing import parse_env_float
84
+ from omnibase_spi.protocols.storage import ProtocolGraphDatabaseHandler
85
+
86
+ logger = logging.getLogger(__name__)
87
+
88
+ HANDLER_ID_GRAPH: str = "graph-handler"
89
+
90
+ SUPPORTED_OPERATIONS: frozenset[str] = frozenset(
91
+ {
92
+ "graph.execute_query",
93
+ "graph.execute_query_batch",
94
+ "graph.create_node",
95
+ "graph.create_relationship",
96
+ "graph.delete_node",
97
+ "graph.delete_relationship",
98
+ "graph.traverse",
99
+ }
100
+ )
101
+
102
+ _DEFAULT_TIMEOUT_SECONDS: float = parse_env_float(
103
+ "ONEX_GRAPH_TIMEOUT",
104
+ 30.0,
105
+ min_value=0.1,
106
+ max_value=3600.0,
107
+ transport_type=EnumInfraTransportType.GRAPH,
108
+ service_name="graph_handler",
109
+ )
110
+ _DEFAULT_POOL_SIZE: int = 50
111
+ _HEALTH_CACHE_SECONDS: float = 10.0
112
+
113
+ # Cypher label validation: alphanumeric and underscore only
114
+ # This prevents injection attacks via malicious label values
115
+ _CYPHER_LABEL_PATTERN: re.Pattern[str] = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
116
+
117
+
118
+ class HandlerGraph(
119
+ MixinAsyncCircuitBreaker, MixinEnvelopeExtraction, ProtocolGraphDatabaseHandler
120
+ ):
121
+ """Graph database handler implementing ProtocolGraphDatabaseHandler.
122
+
123
+ Provides typed graph database operations using neo4j async driver,
124
+ supporting Memgraph and Neo4j via Bolt protocol with Cypher queries.
125
+
126
+ Protocol Compliance:
127
+ Implements all methods from ProtocolGraphDatabaseHandler:
128
+ - handler_type property returning "graph_database"
129
+ - supports_transactions property returning True
130
+ - initialize(), shutdown() lifecycle methods
131
+ - execute_query(), execute_query_batch() query methods
132
+ - create_node(), create_relationship() creation methods
133
+ - delete_node(), delete_relationship() deletion methods
134
+ - traverse() graph traversal method
135
+ - health_check(), describe() introspection methods
136
+
137
+ Security Policy:
138
+ Credentials are treated as secrets and never logged or exposed in errors.
139
+
140
+ Circuit Breaker Pattern:
141
+ Uses MixinAsyncCircuitBreaker for fault tolerance with automatic
142
+ recovery after transient failures.
143
+
144
+ Example:
145
+ ```python
146
+ handler = HandlerGraph()
147
+ await handler.initialize(
148
+ connection_uri="bolt://localhost:7687",
149
+ auth=("neo4j", "password"),
150
+ )
151
+
152
+ result = await handler.execute_query(
153
+ query="MATCH (n:Person {name: $name}) RETURN n",
154
+ parameters={"name": "Alice"},
155
+ )
156
+
157
+ await handler.shutdown()
158
+ ```
159
+ """
160
+
161
+ def __init__(self, container: ModelONEXContainer) -> None:
162
+ """Initialize HandlerGraph with ONEX container for dependency injection.
163
+
164
+ Args:
165
+ container: ONEX container providing dependency injection for
166
+ services, configuration, and runtime context.
167
+
168
+ Note:
169
+ The container is stored for interface compliance with the standard ONEX
170
+ handler pattern (def __init__(self, container: ModelONEXContainer)) and
171
+ to enable future DI-based service resolution. Currently, the handler
172
+ operates independently but the container parameter ensures API
173
+ consistency across all handlers.
174
+ """
175
+ self._container = container
176
+ self._driver: AsyncDriver | None = None
177
+ self._connection_uri: str = ""
178
+ self._database: str = "memgraph"
179
+ self._timeout: float = _DEFAULT_TIMEOUT_SECONDS
180
+ self._pool_size: int = _DEFAULT_POOL_SIZE
181
+ self._initialized: bool = False
182
+ self._cached_health: ModelGraphHealthStatus | None = None
183
+ self._health_cache_time: float = 0.0
184
+
185
+ @property
186
+ def handler_type(self) -> str:
187
+ """Return the handler type identifier.
188
+
189
+ Returns:
190
+ String "graph_database" as defined by ProtocolGraphDatabaseHandler.
191
+ """
192
+ return "graph_database"
193
+
194
+ @property
195
+ def supports_transactions(self) -> bool:
196
+ """Return whether this handler supports transactional operations.
197
+
198
+ Returns:
199
+ True - Neo4j/Memgraph support ACID transactions.
200
+ """
201
+ return True
202
+
203
+ async def initialize( # type: ignore[override]
204
+ self,
205
+ connection_uri: str,
206
+ auth: tuple[str, str] | None = None,
207
+ *,
208
+ options: Mapping[str, JsonType] | None = None,
209
+ ) -> None:
210
+ """Initialize the graph database connection.
211
+
212
+ Establishes connection to the graph database using the provided URI
213
+ and authentication credentials. Configures connection pools and
214
+ validates connectivity.
215
+
216
+ Args:
217
+ connection_uri: Database connection URI (e.g., "bolt://localhost:7687").
218
+ auth: Optional tuple of (username, password) for authentication.
219
+ options: Additional connection parameters:
220
+ - max_connection_pool_size: Maximum connections in pool (default: 50)
221
+ - database: Database name (default: "memgraph")
222
+ - timeout_seconds: Operation timeout (default: 30.0)
223
+ - encrypted: Whether to use TLS/SSL encryption
224
+
225
+ Raises:
226
+ RuntimeHostError: If configuration is invalid.
227
+ InfraConnectionError: If connection to graph database fails.
228
+ InfraAuthenticationError: If authentication fails.
229
+ """
230
+ init_correlation_id = uuid4()
231
+ logger.info(
232
+ "Initializing %s",
233
+ self.__class__.__name__,
234
+ extra={
235
+ "handler": self.__class__.__name__,
236
+ "correlation_id": str(init_correlation_id),
237
+ },
238
+ )
239
+
240
+ self._connection_uri = connection_uri
241
+ opts = dict(options) if options else {}
242
+
243
+ # Extract configuration options
244
+ pool_raw = opts.get("max_connection_pool_size", _DEFAULT_POOL_SIZE)
245
+ self._pool_size = (
246
+ int(pool_raw)
247
+ if isinstance(pool_raw, int | float | str)
248
+ else _DEFAULT_POOL_SIZE
249
+ )
250
+
251
+ timeout_raw = opts.get("timeout_seconds", _DEFAULT_TIMEOUT_SECONDS)
252
+ self._timeout = (
253
+ float(timeout_raw)
254
+ if isinstance(timeout_raw, int | float | str)
255
+ else _DEFAULT_TIMEOUT_SECONDS
256
+ )
257
+
258
+ database_raw = opts.get("database", "memgraph")
259
+ self._database = str(database_raw) if database_raw else "memgraph"
260
+
261
+ encrypted = opts.get("encrypted", False)
262
+
263
+ # Create async driver
264
+ try:
265
+ self._driver = AsyncGraphDatabase.driver(
266
+ connection_uri,
267
+ auth=auth,
268
+ max_connection_pool_size=self._pool_size,
269
+ encrypted=bool(encrypted) if encrypted else False,
270
+ )
271
+ # Verify connectivity
272
+ await self._driver.verify_connectivity()
273
+ self._initialized = True
274
+ logger.info(
275
+ "%s initialized successfully",
276
+ self.__class__.__name__,
277
+ extra={
278
+ "handler": self.__class__.__name__,
279
+ "correlation_id": str(init_correlation_id),
280
+ },
281
+ )
282
+ except AuthError as e:
283
+ ctx = ModelInfraErrorContext.with_correlation(
284
+ transport_type=EnumInfraTransportType.GRAPH,
285
+ operation="initialize",
286
+ target_name="graph_handler",
287
+ correlation_id=init_correlation_id,
288
+ )
289
+ raise InfraAuthenticationError(
290
+ "Graph database authentication failed", context=ctx
291
+ ) from e
292
+ except ServiceUnavailable as e:
293
+ ctx = ModelInfraErrorContext.with_correlation(
294
+ transport_type=EnumInfraTransportType.GRAPH,
295
+ operation="initialize",
296
+ target_name="graph_handler",
297
+ correlation_id=init_correlation_id,
298
+ )
299
+ raise InfraConnectionError(
300
+ "Failed to connect to graph database", context=ctx
301
+ ) from e
302
+ except Exception as e:
303
+ ctx = ModelInfraErrorContext.with_correlation(
304
+ transport_type=EnumInfraTransportType.GRAPH,
305
+ operation="initialize",
306
+ target_name="graph_handler",
307
+ correlation_id=init_correlation_id,
308
+ )
309
+ raise InfraConnectionError(
310
+ f"Connection failed: {type(e).__name__}", context=ctx
311
+ ) from e
312
+
313
+ # Initialize circuit breaker
314
+ self._init_circuit_breaker(
315
+ threshold=5,
316
+ reset_timeout=60.0,
317
+ service_name="graph",
318
+ transport_type=EnumInfraTransportType.GRAPH,
319
+ )
320
+
321
+ async def shutdown(self, timeout_seconds: float = 30.0) -> None:
322
+ """Close database connections and release resources.
323
+
324
+ Gracefully shuts down the handler by closing all active connections
325
+ and releasing resources.
326
+
327
+ Args:
328
+ timeout_seconds: Maximum time to wait for shutdown. Defaults to 30.0.
329
+ """
330
+ correlation_id = uuid4()
331
+ logger.info(
332
+ "Shutting down %s",
333
+ self.__class__.__name__,
334
+ extra={
335
+ "handler": self.__class__.__name__,
336
+ "correlation_id": str(correlation_id),
337
+ "timeout_seconds": timeout_seconds,
338
+ },
339
+ )
340
+
341
+ if self._driver is not None:
342
+ try:
343
+ await self._driver.close()
344
+ except Exception as e:
345
+ logger.warning(
346
+ "Error during driver close: %s",
347
+ type(e).__name__,
348
+ extra={"correlation_id": str(correlation_id)},
349
+ )
350
+ self._driver = None
351
+
352
+ self._initialized = False
353
+ self._cached_health = None
354
+ logger.info(
355
+ "%s shutdown complete",
356
+ self.__class__.__name__,
357
+ extra={"correlation_id": str(correlation_id)},
358
+ )
359
+
360
+ def _ensure_initialized(
361
+ self, operation: str, correlation_id: object
362
+ ) -> AsyncDriver:
363
+ """Ensure handler is initialized and return driver.
364
+
365
+ Args:
366
+ operation: Name of the operation being performed.
367
+ correlation_id: Correlation ID for error context.
368
+
369
+ Returns:
370
+ The initialized AsyncDriver.
371
+
372
+ Raises:
373
+ RuntimeHostError: If handler is not initialized.
374
+ """
375
+ if not self._initialized or self._driver is None:
376
+ ctx = ModelInfraErrorContext.with_correlation(
377
+ transport_type=EnumInfraTransportType.GRAPH,
378
+ operation=operation,
379
+ target_name="graph_handler",
380
+ correlation_id=correlation_id
381
+ if isinstance(correlation_id, UUID)
382
+ else None,
383
+ )
384
+ raise RuntimeHostError(
385
+ "HandlerGraph not initialized. Call initialize() first.", context=ctx
386
+ )
387
+ return self._driver
388
+
389
+ async def execute_query(
390
+ self,
391
+ query: str,
392
+ parameters: Mapping[str, JsonType] | None = None,
393
+ ) -> ModelGraphQueryResult:
394
+ """Execute a Cypher query and return typed results.
395
+
396
+ Security:
397
+ Uses parameterized queries to prevent injection attacks.
398
+ NEVER construct queries via string concatenation with user input.
399
+
400
+ Args:
401
+ query: The Cypher query string.
402
+ parameters: Optional mapping of query parameters.
403
+
404
+ Returns:
405
+ ModelGraphQueryResult with records, summary, counters, and execution time.
406
+
407
+ Raises:
408
+ RuntimeHostError: If handler not initialized or query invalid.
409
+ InfraConnectionError: If query execution fails.
410
+ """
411
+ correlation_id = uuid4()
412
+ driver = self._ensure_initialized("execute_query", correlation_id)
413
+
414
+ # Check circuit breaker
415
+ async with self._circuit_breaker_lock:
416
+ await self._check_circuit_breaker("execute_query", correlation_id)
417
+
418
+ params = dict(parameters) if parameters else {}
419
+ start_time = time.perf_counter()
420
+
421
+ try:
422
+ async with driver.session(database=self._database) as session:
423
+ result = await session.run(query, params)
424
+ records_data = await result.data()
425
+ summary = await result.consume()
426
+
427
+ execution_time_ms = (time.perf_counter() - start_time) * 1000
428
+
429
+ return ModelGraphQueryResult(
430
+ records=list(records_data),
431
+ summary=ModelGraphQuerySummary(
432
+ query_type=summary.query_type or "unknown",
433
+ database=self._database,
434
+ contains_updates=summary.counters.contains_updates,
435
+ ),
436
+ counters=ModelGraphQueryCounters(
437
+ nodes_created=summary.counters.nodes_created,
438
+ nodes_deleted=summary.counters.nodes_deleted,
439
+ relationships_created=summary.counters.relationships_created,
440
+ relationships_deleted=summary.counters.relationships_deleted,
441
+ properties_set=summary.counters.properties_set,
442
+ labels_added=summary.counters.labels_added,
443
+ labels_removed=summary.counters.labels_removed,
444
+ ),
445
+ execution_time_ms=execution_time_ms,
446
+ )
447
+ except Neo4jError as e:
448
+ ctx = ModelInfraErrorContext.with_correlation(
449
+ transport_type=EnumInfraTransportType.GRAPH,
450
+ operation="execute_query",
451
+ target_name="graph_handler",
452
+ correlation_id=correlation_id,
453
+ )
454
+ async with self._circuit_breaker_lock:
455
+ await self._record_circuit_failure("execute_query")
456
+ raise InfraConnectionError(
457
+ f"Query execution failed: {type(e).__name__}", context=ctx
458
+ ) from e
459
+
460
+ async def execute_query_batch( # type: ignore[override]
461
+ self,
462
+ queries: list[tuple[str, Mapping[str, JsonType] | None]],
463
+ transaction: bool = True,
464
+ ) -> ModelGraphBatchResult:
465
+ """Execute multiple queries, optionally within a transaction.
466
+
467
+ Args:
468
+ queries: List of (query, parameters) tuples to execute.
469
+ transaction: If True, execute all queries atomically. Defaults to True.
470
+
471
+ Returns:
472
+ ModelGraphBatchResult with individual results and success status.
473
+
474
+ Raises:
475
+ RuntimeHostError: If handler not initialized.
476
+ InfraConnectionError: If batch execution fails.
477
+ """
478
+ correlation_id = uuid4()
479
+ driver = self._ensure_initialized("execute_query_batch", correlation_id)
480
+
481
+ # Check circuit breaker
482
+ async with self._circuit_breaker_lock:
483
+ await self._check_circuit_breaker("execute_query_batch", correlation_id)
484
+
485
+ results: list[ModelGraphQueryResult] = []
486
+ rollback_occurred = False
487
+ start_time = time.perf_counter()
488
+
489
+ try:
490
+ if transaction and self.supports_transactions:
491
+ # Execute all queries in a single transaction
492
+ async with driver.session(database=self._database) as session:
493
+ tx = await session.begin_transaction()
494
+ try:
495
+ for query, params in queries:
496
+ query_start = time.perf_counter()
497
+ tx_result = await tx.run(
498
+ query, dict(params) if params else {}
499
+ )
500
+ records_data = await tx_result.data()
501
+ summary = await tx_result.consume()
502
+ query_time_ms = (time.perf_counter() - query_start) * 1000
503
+
504
+ results.append(
505
+ ModelGraphQueryResult(
506
+ records=list(records_data),
507
+ summary=ModelGraphQuerySummary(
508
+ query_type=summary.query_type or "unknown",
509
+ database=self._database,
510
+ contains_updates=summary.counters.contains_updates,
511
+ ),
512
+ counters=ModelGraphQueryCounters(
513
+ nodes_created=summary.counters.nodes_created,
514
+ nodes_deleted=summary.counters.nodes_deleted,
515
+ relationships_created=summary.counters.relationships_created,
516
+ relationships_deleted=summary.counters.relationships_deleted,
517
+ properties_set=summary.counters.properties_set,
518
+ labels_added=summary.counters.labels_added,
519
+ labels_removed=summary.counters.labels_removed,
520
+ ),
521
+ execution_time_ms=query_time_ms,
522
+ )
523
+ )
524
+ await tx.commit()
525
+ except Exception:
526
+ await tx.rollback()
527
+ rollback_occurred = True
528
+ raise
529
+ else:
530
+ # Execute queries individually without transaction
531
+ for query, params in queries:
532
+ # Type assertion: execute_query returns ModelGraphQueryResult in this handler
533
+ query_result: ModelGraphQueryResult = await self.execute_query(
534
+ query, params
535
+ ) # type: ignore[assignment]
536
+ results.append(query_result)
537
+
538
+ return ModelGraphBatchResult(
539
+ results=results,
540
+ success=True,
541
+ transaction_id=correlation_id if transaction else None,
542
+ rollback_occurred=rollback_occurred,
543
+ )
544
+ except TransactionError as e:
545
+ ctx = ModelInfraErrorContext.with_correlation(
546
+ transport_type=EnumInfraTransportType.GRAPH,
547
+ operation="execute_query_batch",
548
+ target_name="graph_handler",
549
+ correlation_id=correlation_id,
550
+ )
551
+ async with self._circuit_breaker_lock:
552
+ await self._record_circuit_failure("execute_query_batch")
553
+ raise InfraConnectionError(
554
+ f"Batch transaction failed: {type(e).__name__}", context=ctx
555
+ ) from e
556
+ except Neo4jError as e:
557
+ ctx = ModelInfraErrorContext.with_correlation(
558
+ transport_type=EnumInfraTransportType.GRAPH,
559
+ operation="execute_query_batch",
560
+ target_name="graph_handler",
561
+ correlation_id=correlation_id,
562
+ )
563
+ async with self._circuit_breaker_lock:
564
+ await self._record_circuit_failure("execute_query_batch")
565
+ raise InfraConnectionError(
566
+ f"Batch execution failed: {type(e).__name__}", context=ctx
567
+ ) from e
568
+
569
+ def _validate_cypher_labels(
570
+ self, labels: list[str], operation: str, correlation_id: UUID
571
+ ) -> None:
572
+ """Validate that all labels are safe for Cypher queries.
573
+
574
+ Labels are embedded directly in Cypher queries (not parameterized),
575
+ so they must be validated to prevent injection attacks.
576
+
577
+ Args:
578
+ labels: List of labels to validate.
579
+ operation: Operation name for error context.
580
+ correlation_id: Correlation ID for error context.
581
+
582
+ Raises:
583
+ RuntimeHostError: If any label contains unsafe characters.
584
+ """
585
+ for label in labels:
586
+ if not _CYPHER_LABEL_PATTERN.match(label):
587
+ ctx = ModelInfraErrorContext.with_correlation(
588
+ transport_type=EnumInfraTransportType.GRAPH,
589
+ operation=operation,
590
+ target_name="graph_handler",
591
+ correlation_id=correlation_id,
592
+ )
593
+ raise RuntimeHostError(
594
+ f"Invalid label '{label}': labels must start with a letter or "
595
+ f"underscore and contain only alphanumeric characters and underscores",
596
+ context=ctx,
597
+ )
598
+
599
+ async def create_node(
600
+ self,
601
+ labels: list[str],
602
+ properties: Mapping[str, JsonType],
603
+ ) -> ModelGraphDatabaseNode:
604
+ """Create a new node in the graph.
605
+
606
+ Args:
607
+ labels: List of labels to assign to the node.
608
+ properties: Mapping of property key-value pairs.
609
+
610
+ Returns:
611
+ ModelGraphDatabaseNode with the created node's details.
612
+
613
+ Raises:
614
+ RuntimeHostError: If handler not initialized or invalid input.
615
+ InfraConnectionError: If node creation fails.
616
+ """
617
+ correlation_id = uuid4()
618
+ driver = self._ensure_initialized("create_node", correlation_id)
619
+
620
+ # Check circuit breaker
621
+ async with self._circuit_breaker_lock:
622
+ await self._check_circuit_breaker("create_node", correlation_id)
623
+
624
+ # Validate labels to prevent Cypher injection
625
+ self._validate_cypher_labels(labels, "create_node", correlation_id)
626
+
627
+ # Build Cypher query with labels
628
+ labels_str = ":".join(labels) if labels else ""
629
+ label_clause = f":{labels_str}" if labels_str else ""
630
+ query = f"CREATE (n{label_clause} $props) RETURN n, elementId(n) as eid, id(n) as nid"
631
+
632
+ try:
633
+ async with driver.session(database=self._database) as session:
634
+ result = await session.run(query, {"props": dict(properties)})
635
+ record = await result.single()
636
+ await result.consume()
637
+
638
+ if record is None:
639
+ ctx = ModelInfraErrorContext.with_correlation(
640
+ transport_type=EnumInfraTransportType.GRAPH,
641
+ operation="create_node",
642
+ target_name="graph_handler",
643
+ correlation_id=correlation_id,
644
+ )
645
+ raise RuntimeHostError(
646
+ "Node creation returned no result", context=ctx
647
+ )
648
+
649
+ node = record["n"]
650
+ element_id = str(record["eid"])
651
+ node_id = str(record["nid"])
652
+
653
+ return ModelGraphDatabaseNode(
654
+ id=node_id,
655
+ element_id=element_id,
656
+ labels=list(node.labels),
657
+ properties=dict(node.items()),
658
+ )
659
+ except ConstraintError as e:
660
+ ctx = ModelInfraErrorContext.with_correlation(
661
+ transport_type=EnumInfraTransportType.GRAPH,
662
+ operation="create_node",
663
+ target_name="graph_handler",
664
+ correlation_id=correlation_id,
665
+ )
666
+ raise RuntimeHostError(
667
+ "Node creation failed: constraint violation", context=ctx
668
+ ) from e
669
+ except Neo4jError as e:
670
+ ctx = ModelInfraErrorContext.with_correlation(
671
+ transport_type=EnumInfraTransportType.GRAPH,
672
+ operation="create_node",
673
+ target_name="graph_handler",
674
+ correlation_id=correlation_id,
675
+ )
676
+ async with self._circuit_breaker_lock:
677
+ await self._record_circuit_failure("create_node")
678
+ raise InfraConnectionError(
679
+ f"Node creation failed: {type(e).__name__}", context=ctx
680
+ ) from e
681
+
682
+ async def create_relationship(
683
+ self,
684
+ from_node_id: str | int,
685
+ to_node_id: str | int,
686
+ relationship_type: str,
687
+ properties: Mapping[str, JsonType] | None = None,
688
+ ) -> ModelGraphRelationship:
689
+ """Create a relationship between two nodes.
690
+
691
+ Args:
692
+ from_node_id: Identifier of the source node.
693
+ to_node_id: Identifier of the target node.
694
+ relationship_type: Type of the relationship (e.g., "KNOWS").
695
+ properties: Optional relationship properties.
696
+
697
+ Returns:
698
+ ModelGraphRelationship with the created relationship's details.
699
+
700
+ Raises:
701
+ RuntimeHostError: If handler not initialized or nodes don't exist.
702
+ InfraConnectionError: If relationship creation fails.
703
+ """
704
+ correlation_id = uuid4()
705
+ driver = self._ensure_initialized("create_relationship", correlation_id)
706
+
707
+ # Check circuit breaker
708
+ async with self._circuit_breaker_lock:
709
+ await self._check_circuit_breaker("create_relationship", correlation_id)
710
+
711
+ # Validate relationship type to prevent Cypher injection
712
+ if not _CYPHER_LABEL_PATTERN.match(relationship_type):
713
+ ctx = ModelInfraErrorContext.with_correlation(
714
+ transport_type=EnumInfraTransportType.GRAPH,
715
+ operation="create_relationship",
716
+ target_name="graph_handler",
717
+ correlation_id=correlation_id,
718
+ )
719
+ raise RuntimeHostError(
720
+ f"Invalid relationship_type '{relationship_type}': must start with a "
721
+ f"letter or underscore and contain only alphanumeric characters and underscores",
722
+ context=ctx,
723
+ )
724
+
725
+ # Determine if IDs are element IDs (strings with colons) or internal IDs
726
+ from_is_element_id = isinstance(from_node_id, str) and ":" in from_node_id
727
+ to_is_element_id = isinstance(to_node_id, str) and ":" in to_node_id
728
+
729
+ # Build appropriate match clauses
730
+ if from_is_element_id:
731
+ from_match = "MATCH (a) WHERE elementId(a) = $from_id"
732
+ else:
733
+ from_match = "MATCH (a) WHERE id(a) = $from_id"
734
+
735
+ if to_is_element_id:
736
+ to_match = "MATCH (b) WHERE elementId(b) = $to_id"
737
+ else:
738
+ to_match = "MATCH (b) WHERE id(b) = $to_id"
739
+
740
+ props = dict(properties) if properties else {}
741
+ query = f"""
742
+ {from_match}
743
+ {to_match}
744
+ CREATE (a)-[r:{relationship_type} $props]->(b)
745
+ RETURN r, elementId(r) as eid, id(r) as rid,
746
+ elementId(a) as start_eid, elementId(b) as end_eid
747
+ """
748
+
749
+ params: dict[str, object] = {
750
+ "from_id": int(from_node_id) if not from_is_element_id else from_node_id,
751
+ "to_id": int(to_node_id) if not to_is_element_id else to_node_id,
752
+ "props": props,
753
+ }
754
+
755
+ try:
756
+ async with driver.session(database=self._database) as session:
757
+ result = await session.run(query, params)
758
+ record = await result.single()
759
+ await result.consume()
760
+
761
+ if record is None:
762
+ ctx = ModelInfraErrorContext.with_correlation(
763
+ transport_type=EnumInfraTransportType.GRAPH,
764
+ operation="create_relationship",
765
+ target_name="graph_handler",
766
+ correlation_id=correlation_id,
767
+ )
768
+ raise RuntimeHostError(
769
+ "Relationship creation failed: nodes not found", context=ctx
770
+ )
771
+
772
+ rel = record["r"]
773
+ element_id = str(record["eid"])
774
+ rel_id = str(record["rid"])
775
+ start_eid = str(record["start_eid"])
776
+ end_eid = str(record["end_eid"])
777
+
778
+ return ModelGraphRelationship(
779
+ id=rel_id,
780
+ element_id=element_id,
781
+ type=rel.type,
782
+ properties=dict(rel.items()),
783
+ start_node_id=start_eid,
784
+ end_node_id=end_eid,
785
+ )
786
+ except Neo4jError as e:
787
+ ctx = ModelInfraErrorContext.with_correlation(
788
+ transport_type=EnumInfraTransportType.GRAPH,
789
+ operation="create_relationship",
790
+ target_name="graph_handler",
791
+ correlation_id=correlation_id,
792
+ )
793
+ async with self._circuit_breaker_lock:
794
+ await self._record_circuit_failure("create_relationship")
795
+ raise InfraConnectionError(
796
+ f"Relationship creation failed: {type(e).__name__}", context=ctx
797
+ ) from e
798
+
799
+ async def delete_node(
800
+ self,
801
+ node_id: str | int,
802
+ detach: bool = False,
803
+ ) -> ModelGraphDeleteResult:
804
+ """Delete a node from the graph.
805
+
806
+ Args:
807
+ node_id: Identifier of the node to delete.
808
+ detach: If True, delete all relationships first (DETACH DELETE).
809
+
810
+ Returns:
811
+ ModelGraphDeleteResult with deletion status and counts.
812
+
813
+ Raises:
814
+ RuntimeHostError: If handler not initialized or node has relationships.
815
+ InfraConnectionError: If deletion fails.
816
+ """
817
+ correlation_id = uuid4()
818
+ driver = self._ensure_initialized("delete_node", correlation_id)
819
+
820
+ # Check circuit breaker
821
+ async with self._circuit_breaker_lock:
822
+ await self._check_circuit_breaker("delete_node", correlation_id)
823
+
824
+ is_element_id = isinstance(node_id, str) and ":" in str(node_id)
825
+ start_time = time.perf_counter()
826
+
827
+ if is_element_id:
828
+ match_clause = "MATCH (n) WHERE elementId(n) = $node_id"
829
+ else:
830
+ match_clause = "MATCH (n) WHERE id(n) = $node_id"
831
+
832
+ # Count relationships before delete if detaching
833
+ rel_count = 0
834
+ if detach:
835
+ count_query = (
836
+ f"{match_clause} OPTIONAL MATCH (n)-[r]-() RETURN count(r) as cnt"
837
+ )
838
+ try:
839
+ async with driver.session(database=self._database) as session:
840
+ result = await session.run(
841
+ count_query,
842
+ {"node_id": node_id if is_element_id else int(node_id)},
843
+ )
844
+ record = await result.single()
845
+ await result.consume()
846
+ if record:
847
+ rel_count = record["cnt"]
848
+ except Neo4jError:
849
+ pass # Best effort count
850
+
851
+ delete_keyword = "DETACH DELETE" if detach else "DELETE"
852
+ query = f"{match_clause} {delete_keyword} n RETURN count(n) as deleted"
853
+
854
+ try:
855
+ async with driver.session(database=self._database) as session:
856
+ result = await session.run(
857
+ query,
858
+ {"node_id": node_id if is_element_id else int(node_id)},
859
+ )
860
+ record = await result.single()
861
+ await result.consume()
862
+
863
+ execution_time_ms = (time.perf_counter() - start_time) * 1000
864
+ deleted = record["deleted"] if record else 0
865
+
866
+ return ModelGraphDeleteResult(
867
+ success=deleted > 0,
868
+ node_id=str(node_id),
869
+ relationships_deleted=rel_count if detach else 0,
870
+ execution_time_ms=execution_time_ms,
871
+ )
872
+ except ConstraintError as e:
873
+ ctx = ModelInfraErrorContext.with_correlation(
874
+ transport_type=EnumInfraTransportType.GRAPH,
875
+ operation="delete_node",
876
+ target_name="graph_handler",
877
+ correlation_id=correlation_id,
878
+ )
879
+ raise RuntimeHostError(
880
+ "Cannot delete node with relationships. Use detach=True.", context=ctx
881
+ ) from e
882
+ except Neo4jError as e:
883
+ ctx = ModelInfraErrorContext.with_correlation(
884
+ transport_type=EnumInfraTransportType.GRAPH,
885
+ operation="delete_node",
886
+ target_name="graph_handler",
887
+ correlation_id=correlation_id,
888
+ )
889
+ async with self._circuit_breaker_lock:
890
+ await self._record_circuit_failure("delete_node")
891
+ raise InfraConnectionError(
892
+ f"Node deletion failed: {type(e).__name__}", context=ctx
893
+ ) from e
894
+
895
+ async def delete_relationship(
896
+ self,
897
+ relationship_id: str | int,
898
+ ) -> ModelGraphDeleteResult:
899
+ """Delete a relationship from the graph.
900
+
901
+ Args:
902
+ relationship_id: Identifier of the relationship to delete.
903
+
904
+ Returns:
905
+ ModelGraphDeleteResult with deletion status.
906
+
907
+ Raises:
908
+ RuntimeHostError: If handler not initialized.
909
+ InfraConnectionError: If deletion fails.
910
+ """
911
+ correlation_id = uuid4()
912
+ driver = self._ensure_initialized("delete_relationship", correlation_id)
913
+
914
+ # Check circuit breaker
915
+ async with self._circuit_breaker_lock:
916
+ await self._check_circuit_breaker("delete_relationship", correlation_id)
917
+
918
+ is_element_id = isinstance(relationship_id, str) and ":" in str(relationship_id)
919
+ start_time = time.perf_counter()
920
+
921
+ if is_element_id:
922
+ query = """
923
+ MATCH ()-[r]-()
924
+ WHERE elementId(r) = $rel_id
925
+ DELETE r
926
+ RETURN count(r) as deleted
927
+ """
928
+ else:
929
+ query = """
930
+ MATCH ()-[r]-()
931
+ WHERE id(r) = $rel_id
932
+ DELETE r
933
+ RETURN count(r) as deleted
934
+ """
935
+
936
+ try:
937
+ async with driver.session(database=self._database) as session:
938
+ result = await session.run(
939
+ query,
940
+ {
941
+ "rel_id": relationship_id
942
+ if is_element_id
943
+ else int(relationship_id)
944
+ },
945
+ )
946
+ record = await result.single()
947
+ await result.consume()
948
+
949
+ execution_time_ms = (time.perf_counter() - start_time) * 1000
950
+ deleted = record["deleted"] if record else 0
951
+
952
+ return ModelGraphDeleteResult(
953
+ success=deleted > 0,
954
+ node_id=None, # This is relationship deletion, not node
955
+ relationships_deleted=deleted,
956
+ execution_time_ms=execution_time_ms,
957
+ )
958
+ except Neo4jError as e:
959
+ ctx = ModelInfraErrorContext.with_correlation(
960
+ transport_type=EnumInfraTransportType.GRAPH,
961
+ operation="delete_relationship",
962
+ target_name="graph_handler",
963
+ correlation_id=correlation_id,
964
+ )
965
+ async with self._circuit_breaker_lock:
966
+ await self._record_circuit_failure("delete_relationship")
967
+ raise InfraConnectionError(
968
+ f"Relationship deletion failed: {type(e).__name__}", context=ctx
969
+ ) from e
970
+
971
+ async def traverse(
972
+ self,
973
+ start_node_id: str | int,
974
+ relationship_types: list[str] | None = None,
975
+ direction: str = "outgoing",
976
+ max_depth: int = 1,
977
+ filters: ModelGraphTraversalFilters | None = None,
978
+ ) -> ModelGraphTraversalResult:
979
+ """Traverse the graph from a starting node.
980
+
981
+ Args:
982
+ start_node_id: Identifier of the node to start from.
983
+ relationship_types: Optional list of relationship types to follow.
984
+ direction: Direction to traverse ("outgoing", "incoming", "both").
985
+ max_depth: Maximum traversal depth. Defaults to 1.
986
+ filters: Optional traversal filters for labels and properties.
987
+
988
+ Returns:
989
+ ModelGraphTraversalResult with discovered nodes, relationships, and paths.
990
+
991
+ Raises:
992
+ RuntimeHostError: If handler not initialized or invalid parameters.
993
+ InfraConnectionError: If traversal fails.
994
+ """
995
+ correlation_id = uuid4()
996
+ driver = self._ensure_initialized("traverse", correlation_id)
997
+
998
+ # Check circuit breaker
999
+ async with self._circuit_breaker_lock:
1000
+ await self._check_circuit_breaker("traverse", correlation_id)
1001
+
1002
+ is_element_id = isinstance(start_node_id, str) and ":" in str(start_node_id)
1003
+ start_time = time.perf_counter()
1004
+
1005
+ # Validate relationship types to prevent Cypher injection
1006
+ if relationship_types:
1007
+ for rel_type in relationship_types:
1008
+ if not _CYPHER_LABEL_PATTERN.match(rel_type):
1009
+ ctx = ModelInfraErrorContext.with_correlation(
1010
+ transport_type=EnumInfraTransportType.GRAPH,
1011
+ operation="traverse",
1012
+ target_name="graph_handler",
1013
+ correlation_id=correlation_id,
1014
+ )
1015
+ raise RuntimeHostError(
1016
+ f"Invalid relationship_type '{rel_type}': must start with a "
1017
+ f"letter or underscore and contain only alphanumeric characters "
1018
+ f"and underscores",
1019
+ context=ctx,
1020
+ )
1021
+
1022
+ # Validate filter labels to prevent Cypher injection
1023
+ if filters and filters.node_labels:
1024
+ self._validate_cypher_labels(
1025
+ filters.node_labels, "traverse", correlation_id
1026
+ )
1027
+
1028
+ # Build match clause for start node
1029
+ if is_element_id:
1030
+ start_match = "MATCH (start) WHERE elementId(start) = $start_id"
1031
+ else:
1032
+ start_match = "MATCH (start) WHERE id(start) = $start_id"
1033
+
1034
+ # Build relationship pattern
1035
+ rel_types_pattern = ""
1036
+ if relationship_types:
1037
+ rel_types_pattern = ":" + "|".join(relationship_types)
1038
+
1039
+ # Direction patterns
1040
+ if direction == "incoming":
1041
+ rel_pattern = f"<-[r{rel_types_pattern}*1..{max_depth}]-"
1042
+ elif direction == "both":
1043
+ rel_pattern = f"-[r{rel_types_pattern}*1..{max_depth}]-"
1044
+ else: # outgoing (default)
1045
+ rel_pattern = f"-[r{rel_types_pattern}*1..{max_depth}]->"
1046
+
1047
+ # Build filter conditions
1048
+ filter_conditions: list[str] = []
1049
+ if filters:
1050
+ if filters.node_labels:
1051
+ label_checks = " OR ".join(
1052
+ f"'{lbl}' IN labels(n)" for lbl in filters.node_labels
1053
+ )
1054
+ filter_conditions.append(f"({label_checks})")
1055
+ if filters.node_properties:
1056
+ for key, value in filters.node_properties.items():
1057
+ filter_conditions.append(f"n.{key} = ${key}")
1058
+
1059
+ where_clause = ""
1060
+ if filter_conditions:
1061
+ where_clause = "WHERE " + " AND ".join(filter_conditions)
1062
+
1063
+ query = f"""
1064
+ {start_match}
1065
+ MATCH p = (start){rel_pattern}(n)
1066
+ {where_clause}
1067
+ WITH DISTINCT n, relationships(p) as rels, [node in nodes(p) | elementId(node)] as path_ids
1068
+ RETURN n, elementId(n) as eid, id(n) as nid, rels, path_ids
1069
+ LIMIT 1000
1070
+ """
1071
+
1072
+ params: dict[str, object] = {
1073
+ "start_id": start_node_id if is_element_id else int(start_node_id),
1074
+ }
1075
+ if filters and filters.node_properties:
1076
+ params.update(filters.node_properties)
1077
+
1078
+ try:
1079
+ async with driver.session(database=self._database) as session:
1080
+ result = await session.run(query, params)
1081
+ records = await result.data()
1082
+ await result.consume()
1083
+
1084
+ nodes: list[ModelGraphDatabaseNode] = []
1085
+ relationships: list[ModelGraphRelationship] = []
1086
+ paths: list[list[str]] = []
1087
+ seen_node_ids: set[str] = set()
1088
+ seen_rel_ids: set[str] = set()
1089
+ max_depth_reached = 0
1090
+
1091
+ for record in records:
1092
+ node = record["n"]
1093
+ element_id = str(record["eid"])
1094
+ node_id = str(record["nid"])
1095
+
1096
+ if element_id not in seen_node_ids:
1097
+ seen_node_ids.add(element_id)
1098
+ nodes.append(
1099
+ ModelGraphDatabaseNode(
1100
+ id=node_id,
1101
+ element_id=element_id,
1102
+ labels=list(node.labels),
1103
+ properties=dict(node.items()),
1104
+ )
1105
+ )
1106
+
1107
+ # Process relationships
1108
+ for rel in record.get("rels", []):
1109
+ rel_eid = rel.element_id
1110
+ if rel_eid not in seen_rel_ids:
1111
+ seen_rel_ids.add(rel_eid)
1112
+ relationships.append(
1113
+ ModelGraphRelationship(
1114
+ id=str(rel.id),
1115
+ element_id=rel_eid,
1116
+ type=rel.type,
1117
+ properties=dict(rel.items()),
1118
+ start_node_id=rel.start_node.element_id,
1119
+ end_node_id=rel.end_node.element_id,
1120
+ )
1121
+ )
1122
+
1123
+ # Process path
1124
+ path_ids = record.get("path_ids", [])
1125
+ if path_ids:
1126
+ paths.append(path_ids)
1127
+ max_depth_reached = max(max_depth_reached, len(path_ids) - 1)
1128
+
1129
+ execution_time_ms = (time.perf_counter() - start_time) * 1000
1130
+
1131
+ return ModelGraphTraversalResult(
1132
+ nodes=nodes,
1133
+ relationships=relationships,
1134
+ paths=paths,
1135
+ depth_reached=max_depth_reached,
1136
+ execution_time_ms=execution_time_ms,
1137
+ )
1138
+ except Neo4jError as e:
1139
+ ctx = ModelInfraErrorContext.with_correlation(
1140
+ transport_type=EnumInfraTransportType.GRAPH,
1141
+ operation="traverse",
1142
+ target_name="graph_handler",
1143
+ correlation_id=correlation_id,
1144
+ )
1145
+ async with self._circuit_breaker_lock:
1146
+ await self._record_circuit_failure("traverse")
1147
+ raise InfraConnectionError(
1148
+ f"Traversal failed: {type(e).__name__}", context=ctx
1149
+ ) from e
1150
+
1151
+ async def health_check(self) -> ModelGraphHealthStatus:
1152
+ """Check handler health and database connectivity.
1153
+
1154
+ Returns cached results for rapid repeated calls to prevent
1155
+ overwhelming the backend.
1156
+
1157
+ Returns:
1158
+ ModelGraphHealthStatus with health status and latency.
1159
+
1160
+ Raises:
1161
+ RuntimeHostError: If called before initialize().
1162
+ """
1163
+ correlation_id = uuid4()
1164
+
1165
+ # Return cached result if recent
1166
+ current_time = time.time()
1167
+ if (
1168
+ self._cached_health is not None
1169
+ and current_time - self._health_cache_time < _HEALTH_CACHE_SECONDS
1170
+ ):
1171
+ return self._cached_health
1172
+
1173
+ if not self._initialized or self._driver is None:
1174
+ return ModelGraphHealthStatus(
1175
+ healthy=False,
1176
+ latency_ms=0.0,
1177
+ database_version=None,
1178
+ connection_count=0,
1179
+ )
1180
+
1181
+ start_time = time.perf_counter()
1182
+
1183
+ try:
1184
+ async with self._driver.session(database=self._database) as session:
1185
+ result = await session.run("RETURN 1 as n")
1186
+ await result.consume()
1187
+
1188
+ latency_ms = (time.perf_counter() - start_time) * 1000
1189
+
1190
+ # Try to get server info
1191
+ version = None
1192
+ try:
1193
+ server_info = await self._driver.get_server_info()
1194
+ version = server_info.agent if server_info else None
1195
+ except Exception:
1196
+ pass
1197
+
1198
+ health = ModelGraphHealthStatus(
1199
+ healthy=True,
1200
+ latency_ms=latency_ms,
1201
+ database_version=version,
1202
+ connection_count=0, # Neo4j driver doesn't expose pool stats easily
1203
+ )
1204
+
1205
+ # Cache the result
1206
+ self._cached_health = health
1207
+ self._health_cache_time = current_time
1208
+
1209
+ return health
1210
+ except Exception as e:
1211
+ logger.warning(
1212
+ "Health check failed: %s",
1213
+ type(e).__name__,
1214
+ extra={"correlation_id": str(correlation_id)},
1215
+ )
1216
+ return ModelGraphHealthStatus(
1217
+ healthy=False,
1218
+ latency_ms=0.0,
1219
+ database_version=None,
1220
+ connection_count=0,
1221
+ )
1222
+
1223
+ async def describe(self) -> ModelGraphHandlerMetadata: # type: ignore[override]
1224
+ """Return handler metadata and capabilities.
1225
+
1226
+ Returns:
1227
+ ModelGraphHandlerMetadata with handler information.
1228
+
1229
+ Note:
1230
+ This method is async per protocol specification (v0.5.0+).
1231
+ """
1232
+ # Determine database type based on connection URI
1233
+ database_type = "memgraph"
1234
+ if self._connection_uri:
1235
+ uri_lower = self._connection_uri.lower()
1236
+ if "neo4j" in uri_lower:
1237
+ database_type = "neo4j"
1238
+ elif "neptune" in uri_lower:
1239
+ database_type = "neptune"
1240
+
1241
+ capabilities = [
1242
+ "cypher",
1243
+ "parameterized_queries",
1244
+ "transactions",
1245
+ "node_crud",
1246
+ "relationship_crud",
1247
+ "traversal",
1248
+ "batch_operations",
1249
+ ]
1250
+
1251
+ return ModelGraphHandlerMetadata(
1252
+ handler_type=self.handler_type,
1253
+ capabilities=capabilities,
1254
+ database_type=database_type,
1255
+ supports_transactions=self.supports_transactions,
1256
+ )
1257
+
1258
+ async def execute(
1259
+ self, envelope: dict[str, object]
1260
+ ) -> ModelHandlerOutput[ModelGraphHandlerResponse]:
1261
+ """Execute graph operation from envelope.
1262
+
1263
+ Dispatches to specialized methods based on operation field.
1264
+ This method enables contract-based handler discovery via HandlerPluginLoader.
1265
+
1266
+ Supported operations:
1267
+ - graph.execute_query: Execute a Cypher query
1268
+ - graph.execute_query_batch: Execute multiple queries in transaction
1269
+ - graph.create_node: Create a node with labels and properties
1270
+ - graph.create_relationship: Create relationship between nodes
1271
+ - graph.delete_node: Delete a node (optionally with DETACH)
1272
+ - graph.delete_relationship: Delete a relationship
1273
+ - graph.traverse: Traverse graph from starting node
1274
+
1275
+ Args:
1276
+ envelope: Request envelope containing:
1277
+ - operation: Graph operation (graph.execute_query, etc.)
1278
+ - payload: dict with operation-specific parameters
1279
+ - correlation_id: Optional correlation ID for tracing
1280
+ - envelope_id: Optional envelope ID for causality tracking
1281
+
1282
+ Returns:
1283
+ ModelHandlerOutput wrapping the operation result with correlation tracking.
1284
+
1285
+ Raises:
1286
+ RuntimeHostError: If handler not initialized or invalid input.
1287
+ InfraConnectionError: If graph database connection fails.
1288
+ InfraAuthenticationError: If authentication fails.
1289
+
1290
+ Envelope-Based Routing:
1291
+ This handler uses envelope-based operation routing. See CLAUDE.md section
1292
+ "Intent Model Architecture > Envelope-Based Handler Routing" for the full
1293
+ design pattern and how orchestrators translate intents to handler envelopes.
1294
+ """
1295
+ correlation_id = self._extract_correlation_id(envelope)
1296
+ input_envelope_id = self._extract_envelope_id(envelope)
1297
+
1298
+ if not self._initialized or self._driver is None:
1299
+ raise RuntimeHostError(
1300
+ "HandlerGraph not initialized. Call initialize() first.",
1301
+ context=self._error_context("execute", correlation_id),
1302
+ )
1303
+
1304
+ operation = envelope.get("operation")
1305
+ if not isinstance(operation, str):
1306
+ raise RuntimeHostError(
1307
+ "Missing or invalid 'operation' in envelope",
1308
+ context=self._error_context("execute", correlation_id),
1309
+ )
1310
+
1311
+ payload = envelope.get("payload")
1312
+ if not isinstance(payload, dict):
1313
+ raise RuntimeHostError(
1314
+ "Missing or invalid 'payload' in envelope",
1315
+ context=self._error_context(operation, correlation_id),
1316
+ )
1317
+
1318
+ # Dispatch table maps operation strings to handler methods
1319
+ dispatch_table = {
1320
+ "graph.execute_query": self._execute_query_operation,
1321
+ "graph.execute_query_batch": self._execute_query_batch_operation,
1322
+ "graph.create_node": self._create_node_operation,
1323
+ "graph.create_relationship": self._create_relationship_operation,
1324
+ "graph.delete_node": self._delete_node_operation,
1325
+ "graph.delete_relationship": self._delete_relationship_operation,
1326
+ "graph.traverse": self._traverse_operation,
1327
+ }
1328
+
1329
+ handler = dispatch_table.get(operation)
1330
+ if handler is None:
1331
+ raise RuntimeHostError(
1332
+ f"Operation '{operation}' not supported. "
1333
+ f"Available: {', '.join(sorted(dispatch_table.keys()))}",
1334
+ context=self._error_context(operation, correlation_id),
1335
+ )
1336
+
1337
+ return await handler(payload, correlation_id, input_envelope_id)
1338
+
1339
+ async def _execute_query_operation(
1340
+ self,
1341
+ payload: dict[str, object],
1342
+ correlation_id: UUID,
1343
+ input_envelope_id: UUID,
1344
+ ) -> ModelHandlerOutput[ModelGraphHandlerResponse]:
1345
+ """Execute graph.execute_query operation.
1346
+
1347
+ Validates that payload contains required 'query' field and optional
1348
+ 'parameters' dict, then delegates to execute_query() and converts
1349
+ the result to ModelHandlerOutput format.
1350
+
1351
+ Args:
1352
+ payload: Request payload with 'query' and optional 'parameters'.
1353
+ correlation_id: Correlation ID for tracing.
1354
+ input_envelope_id: Input envelope ID for causality tracking.
1355
+
1356
+ Returns:
1357
+ ModelHandlerOutput wrapping query results.
1358
+
1359
+ Raises:
1360
+ RuntimeHostError: If query field missing or parameters invalid.
1361
+ """
1362
+ query = payload.get("query")
1363
+ if not isinstance(query, str):
1364
+ raise RuntimeHostError(
1365
+ "Missing or invalid 'query' in payload",
1366
+ context=self._error_context("graph.execute_query", correlation_id),
1367
+ )
1368
+
1369
+ parameters = payload.get("parameters")
1370
+ params_dict: Mapping[str, JsonType] | None = None
1371
+ if parameters is not None:
1372
+ if not isinstance(parameters, dict):
1373
+ raise RuntimeHostError(
1374
+ "Invalid 'parameters' in payload - must be a dict",
1375
+ context=self._error_context("graph.execute_query", correlation_id),
1376
+ )
1377
+ # Type ignore: dict variance - dict[str, object] to Mapping[str, JsonType]
1378
+ params_dict = parameters # type: ignore[assignment]
1379
+
1380
+ try:
1381
+ result = await self.execute_query(query, params_dict)
1382
+ except (InfraConnectionError, InfraAuthenticationError, RuntimeHostError):
1383
+ # Already has proper context, re-raise as-is
1384
+ raise
1385
+ except Exception as e:
1386
+ raise RuntimeHostError(
1387
+ f"Query execution failed: {e}",
1388
+ context=self._error_context("graph.execute_query", correlation_id),
1389
+ ) from e
1390
+
1391
+ # Convert records to ModelGraphRecord format
1392
+ # Note: Type ignore needed due to dict variance - dict[str, JsonType] vs dict[str, object]
1393
+ records = [
1394
+ ModelGraphRecord(data=record) # type: ignore[arg-type]
1395
+ for record in result.records
1396
+ ]
1397
+
1398
+ query_payload = ModelGraphQueryPayload(
1399
+ cypher=query,
1400
+ records=records,
1401
+ summary={
1402
+ "query_type": result.summary.query_type,
1403
+ "database": result.summary.database,
1404
+ "contains_updates": result.summary.contains_updates,
1405
+ "execution_time_ms": result.execution_time_ms,
1406
+ "nodes_created": result.counters.nodes_created,
1407
+ "nodes_deleted": result.counters.nodes_deleted,
1408
+ "relationships_created": result.counters.relationships_created,
1409
+ "relationships_deleted": result.counters.relationships_deleted,
1410
+ },
1411
+ )
1412
+
1413
+ return self._build_graph_response(
1414
+ query_payload, correlation_id, input_envelope_id
1415
+ )
1416
+
1417
+ async def _execute_query_batch_operation(
1418
+ self,
1419
+ payload: dict[str, object],
1420
+ correlation_id: UUID,
1421
+ input_envelope_id: UUID,
1422
+ ) -> ModelHandlerOutput[ModelGraphHandlerResponse]:
1423
+ """Execute graph.execute_query_batch operation.
1424
+
1425
+ Validates batch query structure with fail-fast semantics:
1426
+ - 'queries' must be a list of dicts
1427
+ - Each query dict must have 'query' (str) and optional 'parameters' (dict)
1428
+ - 'transaction' must be boolean if provided
1429
+
1430
+ Args:
1431
+ payload: Request payload with 'queries' list and optional 'transaction'.
1432
+ correlation_id: Correlation ID for tracing.
1433
+ input_envelope_id: Input envelope ID for causality tracking.
1434
+
1435
+ Returns:
1436
+ ModelHandlerOutput wrapping batch execution results.
1437
+
1438
+ Raises:
1439
+ RuntimeHostError: If queries list invalid or any query malformed.
1440
+ """
1441
+ queries_raw = payload.get("queries")
1442
+ if not isinstance(queries_raw, list):
1443
+ raise RuntimeHostError(
1444
+ "Missing or invalid 'queries' in payload - must be a list",
1445
+ context=self._error_context(
1446
+ "graph.execute_query_batch", correlation_id
1447
+ ),
1448
+ )
1449
+
1450
+ # Convert to list of tuples with fail-fast validation
1451
+ queries: list[tuple[str, Mapping[str, JsonType] | None]] = []
1452
+ for idx, q in enumerate(queries_raw):
1453
+ if not isinstance(q, dict):
1454
+ raise RuntimeHostError(
1455
+ f"Query at index {idx} must be a dict, got {type(q).__name__}",
1456
+ context=self._error_context(
1457
+ "graph.execute_query_batch", correlation_id
1458
+ ),
1459
+ )
1460
+ query_str = q.get("query")
1461
+ if not isinstance(query_str, str):
1462
+ raise RuntimeHostError(
1463
+ f"Query at index {idx} missing or invalid 'query' field",
1464
+ context=self._error_context(
1465
+ "graph.execute_query_batch", correlation_id
1466
+ ),
1467
+ )
1468
+ params = q.get("parameters")
1469
+ if params is not None and not isinstance(params, dict):
1470
+ raise RuntimeHostError(
1471
+ f"Query at index {idx} has invalid 'parameters' - must be dict or null",
1472
+ context=self._error_context(
1473
+ "graph.execute_query_batch", correlation_id
1474
+ ),
1475
+ )
1476
+ # Type ignore: dict variance - dict[str, object] to Mapping[str, JsonType]
1477
+ queries.append(
1478
+ (query_str, params if isinstance(params, dict) else None) # type: ignore[arg-type]
1479
+ )
1480
+
1481
+ # Validate transaction is boolean - don't silently coerce other types
1482
+ transaction_raw = payload.get("transaction", True)
1483
+ if not isinstance(transaction_raw, bool):
1484
+ raise RuntimeHostError(
1485
+ f"Invalid 'transaction' in payload - must be boolean, "
1486
+ f"got {type(transaction_raw).__name__}",
1487
+ context=self._error_context(
1488
+ "graph.execute_query_batch", correlation_id
1489
+ ),
1490
+ )
1491
+ transaction = transaction_raw
1492
+
1493
+ try:
1494
+ result = await self.execute_query_batch(queries, transaction=transaction)
1495
+ except (InfraConnectionError, InfraAuthenticationError, RuntimeHostError):
1496
+ # Already has proper context, re-raise as-is
1497
+ raise
1498
+ except Exception as e:
1499
+ raise RuntimeHostError(
1500
+ f"Batch query execution failed: {e}",
1501
+ context=self._error_context(
1502
+ "graph.execute_query_batch", correlation_id
1503
+ ),
1504
+ ) from e
1505
+
1506
+ execute_payload = ModelGraphExecutePayload(
1507
+ cypher="BATCH",
1508
+ counters={
1509
+ "success": result.success,
1510
+ "rollback_occurred": result.rollback_occurred,
1511
+ "query_count": len(result.results),
1512
+ "transaction_id": str(result.transaction_id)
1513
+ if result.transaction_id
1514
+ else None,
1515
+ },
1516
+ success=result.success,
1517
+ )
1518
+
1519
+ return self._build_graph_response(
1520
+ execute_payload, correlation_id, input_envelope_id
1521
+ )
1522
+
1523
+ async def _create_node_operation(
1524
+ self,
1525
+ payload: dict[str, object],
1526
+ correlation_id: UUID,
1527
+ input_envelope_id: UUID,
1528
+ ) -> ModelHandlerOutput[ModelGraphHandlerResponse]:
1529
+ """Execute graph.create_node operation.
1530
+
1531
+ Validates optional 'labels' (list of strings) and 'properties' (dict),
1532
+ then delegates to create_node() method.
1533
+
1534
+ Args:
1535
+ payload: Request payload with optional 'labels' and 'properties'.
1536
+ correlation_id: Correlation ID for tracing.
1537
+ input_envelope_id: Input envelope ID for causality tracking.
1538
+
1539
+ Returns:
1540
+ ModelHandlerOutput wrapping created node details.
1541
+
1542
+ Raises:
1543
+ RuntimeHostError: If labels or properties have invalid types.
1544
+ """
1545
+ labels_raw = payload.get("labels")
1546
+ if labels_raw is not None:
1547
+ if not isinstance(labels_raw, list):
1548
+ raise RuntimeHostError(
1549
+ "Invalid 'labels' in payload - must be a list of strings",
1550
+ context=self._error_context("graph.create_node", correlation_id),
1551
+ )
1552
+ labels_list: list[str] = [str(lbl) for lbl in labels_raw]
1553
+ else:
1554
+ labels_list = []
1555
+
1556
+ properties_raw = payload.get("properties")
1557
+ if properties_raw is not None:
1558
+ if not isinstance(properties_raw, dict):
1559
+ raise RuntimeHostError(
1560
+ "Invalid 'properties' in payload - must be a dict",
1561
+ context=self._error_context("graph.create_node", correlation_id),
1562
+ )
1563
+ # Type ignore: dict variance - dict[str, object] to Mapping[str, JsonType]
1564
+ props_dict: Mapping[str, JsonType] = properties_raw # type: ignore[assignment]
1565
+ else:
1566
+ props_dict = {}
1567
+
1568
+ try:
1569
+ result = await self.create_node(labels_list, props_dict)
1570
+ except (InfraConnectionError, InfraAuthenticationError, RuntimeHostError):
1571
+ # Already has proper context, re-raise as-is
1572
+ raise
1573
+ except Exception as e:
1574
+ raise RuntimeHostError(
1575
+ f"Node creation failed: {e}",
1576
+ context=self._error_context("graph.create_node", correlation_id),
1577
+ ) from e
1578
+
1579
+ labels_str = ":" + ":".join(labels_list) if labels_list else "n"
1580
+ execute_payload = ModelGraphExecutePayload(
1581
+ cypher=f"CREATE ({labels_str} ...)",
1582
+ counters={
1583
+ "nodes_created": 1,
1584
+ "node_id": result.id,
1585
+ "element_id": result.element_id,
1586
+ "labels": result.labels,
1587
+ "properties": result.properties,
1588
+ },
1589
+ success=True,
1590
+ )
1591
+
1592
+ return self._build_graph_response(
1593
+ execute_payload, correlation_id, input_envelope_id
1594
+ )
1595
+
1596
+ async def _create_relationship_operation(
1597
+ self,
1598
+ payload: dict[str, object],
1599
+ correlation_id: UUID,
1600
+ input_envelope_id: UUID,
1601
+ ) -> ModelHandlerOutput[ModelGraphHandlerResponse]:
1602
+ """Execute graph.create_relationship operation.
1603
+
1604
+ Validates required fields 'from_node_id', 'to_node_id', 'relationship_type'
1605
+ and optional 'properties' dict, then delegates to create_relationship().
1606
+
1607
+ Args:
1608
+ payload: Request payload with node IDs, relationship type, and properties.
1609
+ correlation_id: Correlation ID for tracing.
1610
+ input_envelope_id: Input envelope ID for causality tracking.
1611
+
1612
+ Returns:
1613
+ ModelHandlerOutput wrapping created relationship details.
1614
+
1615
+ Raises:
1616
+ RuntimeHostError: If required fields missing or properties invalid.
1617
+ """
1618
+ from_node_id = payload.get("from_node_id")
1619
+ to_node_id = payload.get("to_node_id")
1620
+ relationship_type = payload.get("relationship_type")
1621
+
1622
+ if from_node_id is None or to_node_id is None or relationship_type is None:
1623
+ raise RuntimeHostError(
1624
+ "Missing required fields: from_node_id, to_node_id, relationship_type",
1625
+ context=self._error_context(
1626
+ "graph.create_relationship", correlation_id
1627
+ ),
1628
+ )
1629
+
1630
+ properties = payload.get("properties")
1631
+ props_dict: Mapping[str, JsonType] | None = None
1632
+ if properties is not None:
1633
+ if not isinstance(properties, dict):
1634
+ raise RuntimeHostError(
1635
+ "Invalid 'properties' in payload - must be a dict",
1636
+ context=self._error_context(
1637
+ "graph.create_relationship", correlation_id
1638
+ ),
1639
+ )
1640
+ # Type ignore: dict variance - dict[str, object] to Mapping[str, JsonType]
1641
+ props_dict = properties # type: ignore[assignment]
1642
+
1643
+ try:
1644
+ result = await self.create_relationship(
1645
+ from_node_id=str(from_node_id),
1646
+ to_node_id=str(to_node_id),
1647
+ relationship_type=str(relationship_type),
1648
+ properties=props_dict,
1649
+ )
1650
+ except (InfraConnectionError, InfraAuthenticationError, RuntimeHostError):
1651
+ # Already has proper context, re-raise as-is
1652
+ raise
1653
+ except Exception as e:
1654
+ raise RuntimeHostError(
1655
+ f"Relationship creation failed: {e}",
1656
+ context=self._error_context(
1657
+ "graph.create_relationship", correlation_id
1658
+ ),
1659
+ ) from e
1660
+
1661
+ execute_payload = ModelGraphExecutePayload(
1662
+ cypher=f"CREATE ()-[:{relationship_type}]->()",
1663
+ counters={
1664
+ "relationships_created": 1,
1665
+ "relationship_id": result.id,
1666
+ "element_id": result.element_id,
1667
+ "type": result.type,
1668
+ "start_node_id": result.start_node_id,
1669
+ "end_node_id": result.end_node_id,
1670
+ },
1671
+ success=True,
1672
+ )
1673
+
1674
+ return self._build_graph_response(
1675
+ execute_payload, correlation_id, input_envelope_id
1676
+ )
1677
+
1678
+ async def _delete_node_operation(
1679
+ self,
1680
+ payload: dict[str, object],
1681
+ correlation_id: UUID,
1682
+ input_envelope_id: UUID,
1683
+ ) -> ModelHandlerOutput[ModelGraphHandlerResponse]:
1684
+ """Execute graph.delete_node operation.
1685
+
1686
+ Validates required 'node_id' and optional 'detach' boolean. The detach
1687
+ flag must be explicitly boolean to prevent accidental cascade deletes.
1688
+
1689
+ Args:
1690
+ payload: Request payload with 'node_id' and optional 'detach'.
1691
+ correlation_id: Correlation ID for tracing.
1692
+ input_envelope_id: Input envelope ID for causality tracking.
1693
+
1694
+ Returns:
1695
+ ModelHandlerOutput wrapping deletion result.
1696
+
1697
+ Raises:
1698
+ RuntimeHostError: If node_id missing or detach is non-boolean.
1699
+ """
1700
+ node_id = payload.get("node_id")
1701
+ if node_id is None:
1702
+ raise RuntimeHostError(
1703
+ "Missing required field: node_id",
1704
+ context=self._error_context("graph.delete_node", correlation_id),
1705
+ )
1706
+
1707
+ # Validate detach is boolean - don't silently coerce to prevent accidental deletes
1708
+ detach_raw = payload.get("detach", False)
1709
+ if not isinstance(detach_raw, bool):
1710
+ raise RuntimeHostError(
1711
+ f"Invalid 'detach' in payload - must be boolean, "
1712
+ f"got {type(detach_raw).__name__}",
1713
+ context=self._error_context("graph.delete_node", correlation_id),
1714
+ )
1715
+ detach = detach_raw
1716
+
1717
+ try:
1718
+ result = await self.delete_node(str(node_id), detach=detach)
1719
+ except (InfraConnectionError, InfraAuthenticationError, RuntimeHostError):
1720
+ # Already has proper context, re-raise as-is
1721
+ raise
1722
+ except Exception as e:
1723
+ raise RuntimeHostError(
1724
+ f"Node deletion failed: {e}",
1725
+ context=self._error_context("graph.delete_node", correlation_id),
1726
+ ) from e
1727
+
1728
+ execute_payload = ModelGraphExecutePayload(
1729
+ cypher=f"{'DETACH ' if detach else ''}DELETE (n)",
1730
+ counters={
1731
+ "nodes_deleted": 1 if result.success else 0,
1732
+ "relationships_deleted": result.relationships_deleted,
1733
+ "execution_time_ms": result.execution_time_ms,
1734
+ },
1735
+ success=result.success,
1736
+ )
1737
+
1738
+ return self._build_graph_response(
1739
+ execute_payload, correlation_id, input_envelope_id
1740
+ )
1741
+
1742
+ async def _delete_relationship_operation(
1743
+ self,
1744
+ payload: dict[str, object],
1745
+ correlation_id: UUID,
1746
+ input_envelope_id: UUID,
1747
+ ) -> ModelHandlerOutput[ModelGraphHandlerResponse]:
1748
+ """Execute graph.delete_relationship operation.
1749
+
1750
+ Validates required 'relationship_id' field, then delegates to
1751
+ delete_relationship() method.
1752
+
1753
+ Args:
1754
+ payload: Request payload with 'relationship_id'.
1755
+ correlation_id: Correlation ID for tracing.
1756
+ input_envelope_id: Input envelope ID for causality tracking.
1757
+
1758
+ Returns:
1759
+ ModelHandlerOutput wrapping deletion result.
1760
+
1761
+ Raises:
1762
+ RuntimeHostError: If relationship_id field is missing.
1763
+ """
1764
+ relationship_id = payload.get("relationship_id")
1765
+ if relationship_id is None:
1766
+ raise RuntimeHostError(
1767
+ "Missing required field: relationship_id",
1768
+ context=self._error_context(
1769
+ "graph.delete_relationship", correlation_id
1770
+ ),
1771
+ )
1772
+
1773
+ try:
1774
+ result = await self.delete_relationship(str(relationship_id))
1775
+ except (InfraConnectionError, InfraAuthenticationError, RuntimeHostError):
1776
+ # Already has proper context, re-raise as-is
1777
+ raise
1778
+ except Exception as e:
1779
+ raise RuntimeHostError(
1780
+ f"Relationship deletion failed: {e}",
1781
+ context=self._error_context(
1782
+ "graph.delete_relationship", correlation_id
1783
+ ),
1784
+ ) from e
1785
+
1786
+ execute_payload = ModelGraphExecutePayload(
1787
+ cypher="DELETE [r]",
1788
+ counters={
1789
+ "relationships_deleted": result.relationships_deleted,
1790
+ "execution_time_ms": result.execution_time_ms,
1791
+ },
1792
+ success=result.success,
1793
+ )
1794
+
1795
+ return self._build_graph_response(
1796
+ execute_payload, correlation_id, input_envelope_id
1797
+ )
1798
+
1799
+ async def _traverse_operation(
1800
+ self,
1801
+ payload: dict[str, object],
1802
+ correlation_id: UUID,
1803
+ input_envelope_id: UUID,
1804
+ ) -> ModelHandlerOutput[ModelGraphHandlerResponse]:
1805
+ """Execute graph.traverse operation.
1806
+
1807
+ Validates traversal parameters with strict type checking:
1808
+ - 'start_node_id': required
1809
+ - 'relationship_types': optional list of strings
1810
+ - 'direction': optional, must be 'outgoing', 'incoming', or 'both'
1811
+ - 'max_depth': optional, must be positive integer
1812
+ - 'filters': optional dict with 'node_labels' (list) and 'node_properties' (dict)
1813
+
1814
+ Args:
1815
+ payload: Request payload with traversal configuration.
1816
+ correlation_id: Correlation ID for tracing.
1817
+ input_envelope_id: Input envelope ID for causality tracking.
1818
+
1819
+ Returns:
1820
+ ModelHandlerOutput wrapping traversal results (nodes, relationships, paths).
1821
+
1822
+ Raises:
1823
+ RuntimeHostError: If start_node_id missing or parameters have invalid types.
1824
+ """
1825
+ start_node_id = payload.get("start_node_id")
1826
+ if start_node_id is None:
1827
+ raise RuntimeHostError(
1828
+ "Missing required field: start_node_id",
1829
+ context=self._error_context("graph.traverse", correlation_id),
1830
+ )
1831
+
1832
+ # relationship_types - optional, but if provided must be list
1833
+ relationship_types = payload.get("relationship_types")
1834
+ rel_types: list[str] | None = None
1835
+ if relationship_types is not None:
1836
+ if not isinstance(relationship_types, list):
1837
+ raise RuntimeHostError(
1838
+ f"Invalid 'relationship_types' - must be list, "
1839
+ f"got {type(relationship_types).__name__}",
1840
+ context=self._error_context("graph.traverse", correlation_id),
1841
+ )
1842
+ rel_types = [str(rt) for rt in relationship_types]
1843
+
1844
+ # direction - optional with default, but if provided must be valid string
1845
+ direction_raw = payload.get("direction")
1846
+ if direction_raw is None:
1847
+ direction = "outgoing"
1848
+ elif not isinstance(direction_raw, str):
1849
+ raise RuntimeHostError(
1850
+ f"Invalid 'direction' - must be string, "
1851
+ f"got {type(direction_raw).__name__}",
1852
+ context=self._error_context("graph.traverse", correlation_id),
1853
+ )
1854
+ elif direction_raw not in ("outgoing", "incoming", "both"):
1855
+ raise RuntimeHostError(
1856
+ f"Invalid 'direction' value '{direction_raw}' - "
1857
+ f"must be 'outgoing', 'incoming', or 'both'",
1858
+ context=self._error_context("graph.traverse", correlation_id),
1859
+ )
1860
+ else:
1861
+ direction = direction_raw
1862
+
1863
+ # max_depth - optional with default, but if provided must be positive integer
1864
+ max_depth_raw = payload.get("max_depth")
1865
+ if max_depth_raw is None:
1866
+ max_depth = 1
1867
+ elif not isinstance(max_depth_raw, int | float):
1868
+ raise RuntimeHostError(
1869
+ f"Invalid 'max_depth' - must be int or float, "
1870
+ f"got {type(max_depth_raw).__name__}",
1871
+ context=self._error_context("graph.traverse", correlation_id),
1872
+ )
1873
+ else:
1874
+ max_depth = int(max_depth_raw)
1875
+ if max_depth <= 0:
1876
+ raise RuntimeHostError(
1877
+ f"Invalid 'max_depth' value {max_depth} - must be a positive integer",
1878
+ context=self._error_context("graph.traverse", correlation_id),
1879
+ )
1880
+
1881
+ # filters - optional, but if provided must be dict with validated fields
1882
+ filters = None
1883
+ filters_raw = payload.get("filters")
1884
+ if filters_raw is not None:
1885
+ if not isinstance(filters_raw, dict):
1886
+ raise RuntimeHostError(
1887
+ f"Invalid 'filters' - must be dict, "
1888
+ f"got {type(filters_raw).__name__}",
1889
+ context=self._error_context("graph.traverse", correlation_id),
1890
+ )
1891
+
1892
+ # Validate node_labels - must be list or None
1893
+ node_labels = filters_raw.get("node_labels")
1894
+ if node_labels is not None and not isinstance(node_labels, list):
1895
+ raise RuntimeHostError(
1896
+ f"Invalid 'filters.node_labels' - must be list, "
1897
+ f"got {type(node_labels).__name__}",
1898
+ context=self._error_context("graph.traverse", correlation_id),
1899
+ )
1900
+
1901
+ # Validate node_properties - must be dict or None
1902
+ node_properties = filters_raw.get("node_properties")
1903
+ if node_properties is not None and not isinstance(node_properties, dict):
1904
+ raise RuntimeHostError(
1905
+ f"Invalid 'filters.node_properties' - must be dict, "
1906
+ f"got {type(node_properties).__name__}",
1907
+ context=self._error_context("graph.traverse", correlation_id),
1908
+ )
1909
+
1910
+ # Type ignore: list[object] to list[str] - validated above as list
1911
+ # Type ignore: dict[str, object] to dict[str, JsonType] - validated above as dict
1912
+ filters = ModelGraphTraversalFilters(
1913
+ node_labels=node_labels, # type: ignore[arg-type]
1914
+ node_properties=node_properties, # type: ignore[arg-type]
1915
+ )
1916
+
1917
+ try:
1918
+ result = await self.traverse(
1919
+ start_node_id=str(start_node_id),
1920
+ relationship_types=rel_types,
1921
+ direction=direction,
1922
+ max_depth=max_depth,
1923
+ filters=filters,
1924
+ )
1925
+ except (InfraConnectionError, InfraAuthenticationError, RuntimeHostError):
1926
+ # Already has proper context, re-raise as-is
1927
+ raise
1928
+ except Exception as e:
1929
+ raise RuntimeHostError(
1930
+ f"Traversal failed: {e}",
1931
+ context=self._error_context("graph.traverse", correlation_id),
1932
+ ) from e
1933
+
1934
+ # Convert nodes to records
1935
+ records = []
1936
+ for node in result.nodes:
1937
+ records.append(
1938
+ ModelGraphRecord(
1939
+ data={
1940
+ "id": node.id,
1941
+ "element_id": node.element_id,
1942
+ "labels": node.labels,
1943
+ "properties": node.properties,
1944
+ }
1945
+ )
1946
+ )
1947
+
1948
+ query_payload = ModelGraphQueryPayload(
1949
+ cypher=f"TRAVERSE from {start_node_id}",
1950
+ records=records,
1951
+ summary={
1952
+ "depth_reached": result.depth_reached,
1953
+ "nodes_found": len(result.nodes),
1954
+ "relationships_found": len(result.relationships),
1955
+ "paths_found": len(result.paths),
1956
+ "execution_time_ms": result.execution_time_ms,
1957
+ },
1958
+ )
1959
+
1960
+ return self._build_graph_response(
1961
+ query_payload, correlation_id, input_envelope_id
1962
+ )
1963
+
1964
+ def _error_context(
1965
+ self, operation: str, correlation_id: UUID
1966
+ ) -> ModelInfraErrorContext:
1967
+ """Create standardized error context for graph operations.
1968
+
1969
+ Args:
1970
+ operation: The operation name (e.g., "graph.execute_query").
1971
+ correlation_id: Correlation ID for tracing.
1972
+
1973
+ Returns:
1974
+ ModelInfraErrorContext configured for graph handler.
1975
+ """
1976
+ return ModelInfraErrorContext.with_correlation(
1977
+ transport_type=EnumInfraTransportType.GRAPH,
1978
+ operation=operation,
1979
+ target_name="graph_handler",
1980
+ correlation_id=correlation_id,
1981
+ )
1982
+
1983
+ def _build_graph_response(
1984
+ self,
1985
+ typed_payload: ModelGraphQueryPayload | ModelGraphExecutePayload,
1986
+ correlation_id: UUID,
1987
+ input_envelope_id: UUID,
1988
+ ) -> ModelHandlerOutput[ModelGraphHandlerResponse]:
1989
+ """Build standardized ModelGraphHandlerResponse wrapped in ModelHandlerOutput.
1990
+
1991
+ This helper method ensures consistent response formatting across all
1992
+ graph operations, matching the pattern used by HandlerDb and HandlerConsul.
1993
+
1994
+ Args:
1995
+ typed_payload: Strongly-typed payload (query or execute).
1996
+ correlation_id: Correlation ID for tracing.
1997
+ input_envelope_id: Input envelope ID for causality tracking.
1998
+
1999
+ Returns:
2000
+ ModelHandlerOutput wrapping ModelGraphHandlerResponse.
2001
+ """
2002
+ response = ModelGraphHandlerResponse(
2003
+ status=EnumResponseStatus.SUCCESS,
2004
+ payload=ModelGraphHandlerPayload(data=typed_payload),
2005
+ correlation_id=correlation_id,
2006
+ )
2007
+ return ModelHandlerOutput.for_compute(
2008
+ input_envelope_id=input_envelope_id,
2009
+ correlation_id=correlation_id,
2010
+ handler_id=HANDLER_ID_GRAPH,
2011
+ result=response,
2012
+ )
2013
+
2014
+
2015
+ __all__: list[str] = ["HandlerGraph", "HANDLER_ID_GRAPH", "SUPPORTED_OPERATIONS"]