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,1539 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Manifest Persistence Handler - Stores execution manifests to filesystem.
4
+
5
+ This handler persists ModelExecutionManifest objects to the filesystem with
6
+ date-based partitioning, atomic writes, and query support.
7
+
8
+ Supported Operations:
9
+ - manifest.store: Store a manifest (idempotent by manifest_id)
10
+ - manifest.retrieve: Retrieve a manifest by ID
11
+ - manifest.query: Query manifests with filters (correlation_id, node_id, date range)
12
+
13
+ Storage Structure:
14
+ manifests/
15
+ 2025/
16
+ 01/
17
+ 14/
18
+ {manifest_id}.json
19
+
20
+ Security Features:
21
+ - Atomic writes using temp file + rename (prevents partial writes)
22
+ - Idempotent storage (existing manifests are not overwritten)
23
+ - Circuit breaker for resilient I/O operations
24
+
25
+ TOCTOU Race Condition Behavior:
26
+ This handler has inherent Time-Of-Check-Time-Of-Use (TOCTOU) race conditions
27
+ due to filesystem operations. These are documented for transparency:
28
+
29
+ **manifest.store (idempotency check)**:
30
+ The check ``file_path.exists()`` and subsequent write are not atomic.
31
+ Between the existence check and the write, another process could:
32
+ - Create the same file (harmless: atomic rename will overwrite or fail)
33
+ - Delete the file (harmless: write will succeed)
34
+
35
+ Mitigation: Atomic writes use temp file + rename. On POSIX systems, rename()
36
+ is atomic within the same filesystem. The worst case is two concurrent writes
37
+ for the same manifest_id both succeed, but they write identical content.
38
+
39
+ **manifest.retrieve/query (directory scan)**:
40
+ Directory iteration via ``iterdir()`` returns a point-in-time snapshot.
41
+ Files may be added or removed during iteration. This is acceptable because:
42
+ - Manifests are append-only (never deleted during normal operation)
43
+ - Query results are best-effort snapshots, not transactional reads
44
+
45
+ **Deployment Considerations**:
46
+ - For multi-process deployments writing to shared storage, use a database
47
+ backend instead of filesystem storage for strong consistency guarantees.
48
+ - Single-process deployments (typical ONEX node) have no TOCTOU concerns.
49
+ - NFS and network filesystems may have weaker atomicity guarantees than
50
+ local filesystems; test rename behavior on your target storage.
51
+
52
+ Performance Characteristics (O(n) Directory Scan):
53
+ This handler uses O(n) directory scanning for retrieve and query operations,
54
+ where n is the total number of manifest files across all date partitions.
55
+
56
+ **Why O(n) is acceptable for current use case**:
57
+ - Manifest operations are low-frequency (debugging, auditing, troubleshooting)
58
+ - Date-based partitioning enables manual pruning of old directories
59
+ - Typical deployments have <10,000 manifests
60
+ - Recent manifests (most common access pattern) are found quickly due to
61
+ reverse-chronological iteration
62
+
63
+ **Scaling recommendations for high-volume deployments**:
64
+ - **>10k manifests**: Consider adding an index file (manifest_id -> path mapping)
65
+ - **>100k manifests**: Consider SQLite or PostgreSQL backend with indexed queries
66
+ - **>1M manifests**: Use dedicated manifest storage service with sharding
67
+
68
+ **Alternative approaches not implemented**:
69
+ - Bloom filter for fast negative lookups (adds complexity, marginal benefit)
70
+ - In-memory manifest_id index (memory overhead, persistence complexity)
71
+ - Filename encoding of creation date (breaks existing storage format)
72
+
73
+ Datetime Handling:
74
+ All datetime values (created_at, created_after, created_before) should be
75
+ timezone-aware for accurate comparisons. ISO 8601 strings with timezone info
76
+ (e.g., "2025-01-14T12:00:00+00:00" or "2025-01-14T12:00:00Z") are parsed
77
+ correctly. Naive datetimes may cause comparison issues when filtering.
78
+
79
+ Timezone Awareness:
80
+ - ISO strings with "Z" suffix are converted to UTC (+00:00)
81
+ - ISO strings with explicit offset (e.g., "+05:00") are preserved
82
+ - Naive datetime objects passed directly are accepted but logged as warnings
83
+ - Comparisons between aware and naive datetimes will raise TypeError in Python 3
84
+ - Best practice: Always use timezone-aware datetimes (e.g., datetime.now(timezone.utc))
85
+
86
+ Note:
87
+ Environment variable configuration (ONEX_MANIFEST_MAX_FILE_SIZE) is parsed
88
+ at module import time, not at handler instantiation. This means:
89
+
90
+ - Changes to environment variables require application restart to take effect
91
+ - Tests should use ``unittest.mock.patch.dict(os.environ, ...)`` before importing,
92
+ or use ``importlib.reload()`` to re-import the module after patching
93
+ - This is an intentional design choice for startup-time validation
94
+ """
95
+
96
+ from __future__ import annotations
97
+
98
+ import asyncio
99
+ import json
100
+ import logging
101
+ import os
102
+ import tempfile
103
+ import time
104
+ from collections.abc import Awaitable, Callable
105
+ from datetime import datetime
106
+ from pathlib import Path
107
+ from typing import TypeVar
108
+ from uuid import UUID, uuid4
109
+
110
+ from omnibase_core.container import ModelONEXContainer
111
+ from omnibase_core.models.dispatch import ModelHandlerOutput
112
+ from omnibase_infra.enums import (
113
+ EnumHandlerType,
114
+ EnumHandlerTypeCategory,
115
+ EnumInfraTransportType,
116
+ EnumRetryErrorCategory,
117
+ )
118
+ from omnibase_infra.errors import (
119
+ InfraUnavailableError,
120
+ ModelInfraErrorContext,
121
+ ProtocolConfigurationError,
122
+ RuntimeHostError,
123
+ )
124
+ from omnibase_infra.handlers.models import ModelRetryState
125
+ from omnibase_infra.handlers.models.model_manifest_metadata import ModelManifestMetadata
126
+ from omnibase_infra.handlers.models.model_manifest_query_result import (
127
+ ModelManifestQueryResult,
128
+ )
129
+ from omnibase_infra.handlers.models.model_manifest_retrieve_result import (
130
+ ModelManifestRetrieveResult,
131
+ )
132
+ from omnibase_infra.handlers.models.model_manifest_store_result import (
133
+ ModelManifestStoreResult,
134
+ )
135
+ from omnibase_infra.mixins import MixinAsyncCircuitBreaker, MixinEnvelopeExtraction
136
+ from omnibase_infra.mixins.mixin_retry_execution import MixinRetryExecution
137
+ from omnibase_infra.models.model_retry_error_classification import (
138
+ ModelRetryErrorClassification,
139
+ )
140
+ from omnibase_infra.utils import parse_env_int, warn_if_naive_datetime
141
+
142
+ logger = logging.getLogger(__name__)
143
+
144
+ # Default configuration from environment
145
+ _DEFAULT_MAX_FILE_SIZE: int = parse_env_int(
146
+ "ONEX_MANIFEST_MAX_FILE_SIZE",
147
+ 50 * 1024 * 1024, # 50 MB
148
+ min_value=1024,
149
+ max_value=500 * 1024 * 1024, # 500 MB
150
+ transport_type=EnumInfraTransportType.FILESYSTEM,
151
+ service_name="manifest_persistence_handler",
152
+ )
153
+
154
+ _SUPPORTED_OPERATIONS: frozenset[str] = frozenset(
155
+ {
156
+ "manifest.store",
157
+ "manifest.retrieve",
158
+ "manifest.query",
159
+ }
160
+ )
161
+
162
+ HANDLER_ID_MANIFEST_PERSISTENCE: str = "manifest-persistence-handler"
163
+
164
+
165
+ class HandlerManifestPersistence(
166
+ MixinEnvelopeExtraction, MixinAsyncCircuitBreaker, MixinRetryExecution
167
+ ):
168
+ """Manifest persistence handler for storing/retrieving ModelExecutionManifest.
169
+
170
+ This handler stores ModelExecutionManifest objects to the filesystem with:
171
+ - Date-based partitioning (year/month/day directories)
172
+ - Atomic writes (write to temp, then rename)
173
+ - Idempotent storage (same manifest_id = no duplicate)
174
+ - Query support with filters
175
+ - Circuit breaker for resilient I/O operations
176
+ - Retry with exponential backoff for transient I/O errors
177
+
178
+ Storage Pattern:
179
+ {storage_path}/{year}/{month}/{day}/{manifest_id}.json
180
+
181
+ Example: /data/manifests/2025/01/14/550e8400-e29b-41d4-a716-446655440000.json
182
+
183
+ Attributes:
184
+ handler_type: Returns INFRA_HANDLER (infrastructure protocol handler)
185
+ handler_category: Returns EFFECT (side-effecting I/O)
186
+
187
+ Example:
188
+ >>> handler = HandlerManifestPersistence(container)
189
+ >>> await handler.initialize({"storage_path": "/data/manifests"})
190
+ >>> result = await handler.execute({
191
+ ... "operation": "manifest.store",
192
+ ... "payload": {"manifest": manifest.model_dump()},
193
+ ... })
194
+ """
195
+
196
+ def __init__(self, container: ModelONEXContainer) -> None:
197
+ """Initialize HandlerManifestPersistence with required container injection.
198
+
199
+ Args:
200
+ container: ONEX container for dependency injection. Required per ONEX
201
+ pattern (``def __init__(self, container: ModelONEXContainer)``).
202
+ Enables full ONEX integration (logging, metrics, service discovery).
203
+
204
+ See Also:
205
+ - CLAUDE.md "Container-Based Dependency Injection" section for the
206
+ standard ONEX container injection pattern.
207
+ - docs/patterns/container_dependency_injection.md for detailed DI patterns.
208
+ """
209
+ self._container = container
210
+ self._storage_path: Path | None = None
211
+ self._max_file_size: int = _DEFAULT_MAX_FILE_SIZE
212
+ self._initialized: bool = False
213
+
214
+ # Retry configuration (populated from contract in initialize())
215
+ self._retry_config: dict[str, float] = {
216
+ "max_retries": 3,
217
+ "initial_delay_seconds": 0.1, # 100ms
218
+ "max_delay_seconds": 5.0, # 5000ms
219
+ "exponential_base": 2.0,
220
+ }
221
+
222
+ # Required by MixinRetryExecution (no thread pool needed for async I/O)
223
+ self._executor = None
224
+
225
+ # Required by MixinRetryExecution for circuit breaker integration check
226
+ # Set to True after _init_circuit_breaker() is called in initialize()
227
+ self._circuit_breaker_initialized: bool = False
228
+
229
+ @property
230
+ def handler_type(self) -> EnumHandlerType:
231
+ """Return the architectural role of this handler.
232
+
233
+ Returns:
234
+ EnumHandlerType.INFRA_HANDLER - This handler is an infrastructure
235
+ protocol/transport handler for manifest persistence operations.
236
+
237
+ Note:
238
+ handler_type determines lifecycle, protocol selection, and runtime
239
+ invocation patterns. It answers "what is this handler in the architecture?"
240
+
241
+ See Also:
242
+ - handler_category: Behavioral classification (EFFECT/COMPUTE)
243
+ """
244
+ return EnumHandlerType.INFRA_HANDLER
245
+
246
+ @property
247
+ def handler_category(self) -> EnumHandlerTypeCategory:
248
+ """Return the behavioral classification of this handler.
249
+
250
+ Returns:
251
+ EnumHandlerTypeCategory.EFFECT - This handler performs side-effecting
252
+ I/O operations (filesystem read/write). EFFECT handlers are not
253
+ deterministic and interact with external systems.
254
+
255
+ Note:
256
+ handler_category determines security rules, determinism guarantees,
257
+ replay safety, and permissions. It answers "how does this handler
258
+ behave at runtime?"
259
+
260
+ Categories:
261
+ - COMPUTE: Pure, deterministic transformations (no side effects)
262
+ - EFFECT: Side-effecting I/O (database, HTTP, filesystem)
263
+ - NONDETERMINISTIC_COMPUTE: Pure but not deterministic (UUID, random)
264
+
265
+ See Also:
266
+ - handler_type: Architectural role (INFRA_HANDLER/NODE_HANDLER/etc.)
267
+ """
268
+ return EnumHandlerTypeCategory.EFFECT
269
+
270
+ # =========================================================================
271
+ # MixinRetryExecution Abstract Method Implementations
272
+ # =========================================================================
273
+
274
+ def _classify_error(
275
+ self, error: Exception, operation: str
276
+ ) -> ModelRetryErrorClassification:
277
+ """Classify filesystem errors for retry handling.
278
+
279
+ This method determines whether an error is retriable and how it should
280
+ affect the circuit breaker state.
281
+
282
+ Args:
283
+ error: The exception to classify.
284
+ operation: The operation name for context.
285
+
286
+ Returns:
287
+ ModelRetryErrorClassification with retry decision and error details.
288
+
289
+ Error Classification:
290
+ - TimeoutError: TIMEOUT category, retriable, records circuit failure
291
+ - BlockingIOError: TIMEOUT category, retriable, records circuit failure
292
+ (EAGAIN/EWOULDBLOCK - resource temporarily unavailable)
293
+ - FileNotFoundError: NOT_FOUND category, NOT retriable, NO circuit failure
294
+ - PermissionError: AUTHENTICATION category, NOT retriable, records circuit failure
295
+ - OSError/IOError: CONNECTION category, retriable, records circuit failure
296
+ - Other: UNKNOWN category, retriable, records circuit failure
297
+
298
+ Note:
299
+ BlockingIOError must be checked BEFORE OSError since it's a subclass.
300
+ We classify BlockingIOError as TIMEOUT rather than CONNECTION because
301
+ it indicates "resource temporarily unavailable" which is semantically
302
+ closer to a timeout condition than a connection error.
303
+ """
304
+ if isinstance(error, TimeoutError):
305
+ return ModelRetryErrorClassification(
306
+ category=EnumRetryErrorCategory.TIMEOUT,
307
+ should_retry=True,
308
+ record_circuit_failure=True,
309
+ error_message=f"Filesystem operation timed out: {operation}",
310
+ )
311
+
312
+ # BlockingIOError indicates EAGAIN/EWOULDBLOCK (resource temporarily unavailable)
313
+ # Must be checked before OSError since it's a subclass
314
+ if isinstance(error, BlockingIOError):
315
+ return ModelRetryErrorClassification(
316
+ category=EnumRetryErrorCategory.TIMEOUT,
317
+ should_retry=True,
318
+ record_circuit_failure=True,
319
+ error_message=f"Resource temporarily unavailable: {operation}",
320
+ )
321
+
322
+ if isinstance(error, FileNotFoundError):
323
+ # File not found is a user/logic error, not infrastructure failure
324
+ return ModelRetryErrorClassification(
325
+ category=EnumRetryErrorCategory.NOT_FOUND,
326
+ should_retry=False,
327
+ record_circuit_failure=False,
328
+ error_message=f"File not found: {error}",
329
+ )
330
+
331
+ if isinstance(error, PermissionError):
332
+ # Permission errors are not retriable and indicate config issues
333
+ return ModelRetryErrorClassification(
334
+ category=EnumRetryErrorCategory.AUTHENTICATION,
335
+ should_retry=False,
336
+ record_circuit_failure=True,
337
+ error_message=f"Permission denied: {operation}",
338
+ )
339
+
340
+ if isinstance(error, OSError | IOError):
341
+ # General I/O errors are retriable (disk full, temp unavailable, etc.)
342
+ return ModelRetryErrorClassification(
343
+ category=EnumRetryErrorCategory.CONNECTION,
344
+ should_retry=True,
345
+ record_circuit_failure=True,
346
+ error_message=f"Filesystem I/O error: {type(error).__name__}",
347
+ )
348
+
349
+ # Unknown errors - retry but record failure
350
+ return ModelRetryErrorClassification(
351
+ category=EnumRetryErrorCategory.UNKNOWN,
352
+ should_retry=True,
353
+ record_circuit_failure=True,
354
+ error_message=f"Unexpected error: {type(error).__name__}",
355
+ )
356
+
357
+ def _get_transport_type(self) -> EnumInfraTransportType:
358
+ """Return the transport type for error context.
359
+
360
+ Returns:
361
+ EnumInfraTransportType.FILESYSTEM for filesystem operations.
362
+ """
363
+ return EnumInfraTransportType.FILESYSTEM
364
+
365
+ def _get_target_name(self) -> str:
366
+ """Return the target name for error context.
367
+
368
+ Returns:
369
+ The handler identifier for error context and logging.
370
+ """
371
+ return "manifest_persistence_handler"
372
+
373
+ async def initialize(self, config: dict[str, object]) -> None:
374
+ """Initialize manifest persistence handler with storage path.
375
+
376
+ Args:
377
+ config: Configuration dict containing:
378
+ - storage_path: Required path to manifest storage directory
379
+ - max_file_size: Optional max file size in bytes (default: 50 MB)
380
+ - correlation_id: Optional UUID or string for error tracing
381
+
382
+ Raises:
383
+ ProtocolConfigurationError: If storage_path is missing or invalid.
384
+
385
+ Security:
386
+ - Storage directory is created if it doesn't exist
387
+ - Non-writable paths are logged as warnings
388
+ """
389
+ init_correlation_id = uuid4()
390
+
391
+ logger.info(
392
+ "Initializing %s",
393
+ self.__class__.__name__,
394
+ extra={
395
+ "handler": self.__class__.__name__,
396
+ "correlation_id": str(init_correlation_id),
397
+ },
398
+ )
399
+
400
+ ctx = ModelInfraErrorContext(
401
+ transport_type=EnumInfraTransportType.FILESYSTEM,
402
+ operation="initialize",
403
+ target_name="manifest_persistence_handler",
404
+ correlation_id=init_correlation_id,
405
+ )
406
+
407
+ # Extract and validate storage_path (required)
408
+ storage_path_raw = config.get("storage_path")
409
+ if storage_path_raw is None:
410
+ raise ProtocolConfigurationError(
411
+ "Missing required 'storage_path' configuration - manifest persistence "
412
+ "handler requires a storage directory path",
413
+ context=ctx,
414
+ )
415
+
416
+ if not isinstance(storage_path_raw, str) or not storage_path_raw:
417
+ raise ProtocolConfigurationError(
418
+ "Configuration 'storage_path' must be a non-empty string",
419
+ context=ctx,
420
+ )
421
+
422
+ # Resolve to absolute path
423
+ storage_path = Path(storage_path_raw).resolve()
424
+
425
+ # Create storage directory if it doesn't exist
426
+ if not storage_path.exists():
427
+ try:
428
+ storage_path.mkdir(parents=True, exist_ok=True)
429
+ logger.info(
430
+ "Created manifest storage directory: %s",
431
+ storage_path,
432
+ extra={
433
+ "path": str(storage_path),
434
+ "correlation_id": str(init_correlation_id),
435
+ },
436
+ )
437
+ except OSError as e:
438
+ raise ProtocolConfigurationError(
439
+ f"Failed to create storage directory: {e}",
440
+ context=ctx,
441
+ ) from e
442
+
443
+ if not storage_path.is_dir():
444
+ raise ProtocolConfigurationError(
445
+ f"Storage path exists but is not a directory: {storage_path}",
446
+ context=ctx,
447
+ )
448
+
449
+ self._storage_path = storage_path
450
+
451
+ # Verify storage path is writable (fail fast on permission issues)
452
+ test_file = self._storage_path / ".write_test"
453
+ try:
454
+ test_file.touch()
455
+ test_file.unlink()
456
+ except (PermissionError, OSError) as e:
457
+ raise ProtocolConfigurationError(
458
+ f"Storage path is not writable: {self._storage_path}",
459
+ context=ModelInfraErrorContext(
460
+ transport_type=EnumInfraTransportType.FILESYSTEM,
461
+ operation="initialize",
462
+ target_name="manifest_persistence_handler",
463
+ correlation_id=init_correlation_id,
464
+ ),
465
+ ) from e
466
+
467
+ # Extract optional max_file_size
468
+ max_file_size_raw = config.get("max_file_size")
469
+ if max_file_size_raw is not None:
470
+ if isinstance(max_file_size_raw, int) and max_file_size_raw > 0:
471
+ self._max_file_size = max_file_size_raw
472
+ else:
473
+ logger.warning(
474
+ "Invalid max_file_size config value ignored, using default",
475
+ extra={
476
+ "provided_value": max_file_size_raw,
477
+ "default_value": self._max_file_size,
478
+ },
479
+ )
480
+
481
+ # Initialize circuit breaker for resilient I/O operations
482
+ self._init_circuit_breaker(
483
+ threshold=5,
484
+ reset_timeout=60.0,
485
+ service_name="manifest_persistence_handler",
486
+ transport_type=EnumInfraTransportType.FILESYSTEM,
487
+ )
488
+ # Mark circuit breaker as initialized for MixinRetryExecution integration
489
+ self._circuit_breaker_initialized = True
490
+
491
+ # Parse retry_policy from configuration (matches contract defaults)
492
+ # Fail-fast: Invalid retry config raises ProtocolConfigurationError
493
+ retry_policy = config.get("retry_policy")
494
+ if isinstance(retry_policy, dict):
495
+ # max_retries (default: 3) - must be positive integer
496
+ max_retries = retry_policy.get("max_retries")
497
+ if max_retries is not None:
498
+ if not isinstance(max_retries, int) or max_retries <= 0:
499
+ raise ProtocolConfigurationError(
500
+ "Invalid retry_policy.max_retries: must be a positive "
501
+ f"integer, got {type(max_retries).__name__}={max_retries!r}",
502
+ context=ctx,
503
+ )
504
+ self._retry_config["max_retries"] = max_retries
505
+
506
+ # initial_delay_ms -> convert to seconds (default: 100ms = 0.1s)
507
+ initial_delay_ms = retry_policy.get("initial_delay_ms")
508
+ if initial_delay_ms is not None:
509
+ is_valid_type = isinstance(initial_delay_ms, int | float)
510
+ if not is_valid_type or initial_delay_ms <= 0:
511
+ raise ProtocolConfigurationError(
512
+ "Invalid retry_policy.initial_delay_ms: must be a "
513
+ f"positive number, got "
514
+ f"{type(initial_delay_ms).__name__}={initial_delay_ms!r}",
515
+ context=ctx,
516
+ )
517
+ self._retry_config["initial_delay_seconds"] = initial_delay_ms / 1000.0
518
+
519
+ # max_delay_ms -> convert to seconds (default: 5000ms = 5.0s)
520
+ max_delay_ms = retry_policy.get("max_delay_ms")
521
+ if max_delay_ms is not None:
522
+ if not isinstance(max_delay_ms, int | float) or max_delay_ms <= 0:
523
+ raise ProtocolConfigurationError(
524
+ "Invalid retry_policy.max_delay_ms: must be a positive "
525
+ f"number, got {type(max_delay_ms).__name__}={max_delay_ms!r}",
526
+ context=ctx,
527
+ )
528
+ self._retry_config["max_delay_seconds"] = max_delay_ms / 1000.0
529
+
530
+ # exponential_base (default: 2.0) - must be >= 1.0
531
+ exponential_base = retry_policy.get("exponential_base")
532
+ if exponential_base is not None:
533
+ is_valid_type = isinstance(exponential_base, int | float)
534
+ if not is_valid_type or exponential_base < 1.0:
535
+ raise ProtocolConfigurationError(
536
+ "Invalid retry_policy.exponential_base: must be a "
537
+ f"number >= 1.0, got "
538
+ f"{type(exponential_base).__name__}={exponential_base!r}",
539
+ context=ctx,
540
+ )
541
+ self._retry_config["exponential_base"] = float(exponential_base)
542
+
543
+ logger.debug(
544
+ "Retry policy configured",
545
+ extra={
546
+ "max_retries": self._retry_config["max_retries"],
547
+ "initial_delay_seconds": self._retry_config[
548
+ "initial_delay_seconds"
549
+ ],
550
+ "max_delay_seconds": self._retry_config["max_delay_seconds"],
551
+ "exponential_base": self._retry_config["exponential_base"],
552
+ "correlation_id": str(init_correlation_id),
553
+ },
554
+ )
555
+
556
+ self._initialized = True
557
+
558
+ logger.info(
559
+ "%s initialized successfully",
560
+ self.__class__.__name__,
561
+ extra={
562
+ "handler": self.__class__.__name__,
563
+ "storage_path": str(self._storage_path),
564
+ "max_file_size_bytes": self._max_file_size,
565
+ "correlation_id": str(init_correlation_id),
566
+ },
567
+ )
568
+
569
+ async def shutdown(self) -> None:
570
+ """Shutdown manifest persistence handler and clear configuration."""
571
+ self._storage_path = None
572
+ self._initialized = False
573
+ logger.info("HandlerManifestPersistence shutdown complete")
574
+
575
+ # =========================================================================
576
+ # Retry Logic Helper
577
+ # =========================================================================
578
+
579
+ _T = TypeVar("_T")
580
+
581
+ async def _execute_with_retry(
582
+ self,
583
+ operation: str,
584
+ func: Callable[[], Awaitable[_T]],
585
+ correlation_id: UUID,
586
+ ) -> _T:
587
+ """Execute an async operation with exponential backoff retry logic.
588
+
589
+ This method wraps I/O operations with retry logic, integrating with the
590
+ circuit breaker for resilient operations.
591
+
592
+ Args:
593
+ operation: Operation name for logging and error context.
594
+ func: Async callable to execute (returns the result).
595
+ correlation_id: Correlation ID for distributed tracing.
596
+
597
+ Returns:
598
+ The result from func().
599
+
600
+ Raises:
601
+ InfraTimeoutError: If operation times out after retries exhausted.
602
+ InfraConnectionError: If connection fails after retries exhausted.
603
+ InfraAuthenticationError: If authentication fails (not retriable).
604
+ InfraUnavailableError: If circuit breaker is OPEN.
605
+
606
+ Circuit Breaker Integration:
607
+ - Checks circuit state before execution (raises if OPEN)
608
+ - Records success/failure for circuit state management
609
+ - Failure recorded only when retries are exhausted
610
+
611
+ Retry Logic:
612
+ - Uses exponential backoff with configurable parameters
613
+ - Classifies errors to determine retry eligibility
614
+ - Logs retry attempts with correlation tracking
615
+ """
616
+ # Check circuit breaker before execution
617
+ await self._check_circuit_if_enabled(operation, correlation_id)
618
+
619
+ # Initialize retry state from configuration
620
+ retry_state = ModelRetryState(
621
+ attempt=0,
622
+ max_attempts=int(self._retry_config["max_retries"]) + 1, # +1 for initial
623
+ delay_seconds=float(self._retry_config["initial_delay_seconds"]),
624
+ backoff_multiplier=float(self._retry_config["exponential_base"]),
625
+ )
626
+
627
+ max_delay_seconds = float(self._retry_config["max_delay_seconds"])
628
+ last_error: Exception | None = None
629
+
630
+ while retry_state.is_retriable():
631
+ try:
632
+ result = await func()
633
+ # Reset circuit breaker on success
634
+ await self._reset_circuit_if_enabled()
635
+ return result
636
+
637
+ except Exception as e:
638
+ last_error = e
639
+ classification = self._classify_error(e, operation)
640
+
641
+ if not classification.should_retry:
642
+ # Non-retriable error - record failure and raise immediately
643
+ if classification.record_circuit_failure:
644
+ await self._record_circuit_failure_if_enabled(
645
+ operation, correlation_id
646
+ )
647
+ raise
648
+
649
+ # Update retry state for next attempt
650
+ retry_state = retry_state.next_attempt(
651
+ error_message=classification.error_message,
652
+ max_delay_seconds=max_delay_seconds,
653
+ )
654
+
655
+ if not retry_state.is_retriable():
656
+ # Retries exhausted - record failure and raise
657
+ if classification.record_circuit_failure:
658
+ await self._record_circuit_failure_if_enabled(
659
+ operation, correlation_id
660
+ )
661
+ raise
662
+
663
+ # Log retry attempt
664
+ await self._log_retry_attempt(operation, retry_state, correlation_id)
665
+
666
+ # Wait before next attempt
667
+ await asyncio.sleep(retry_state.delay_seconds)
668
+
669
+ # Should never reach here, but satisfy type checker
670
+ if last_error is not None:
671
+ raise last_error
672
+ ctx = ModelInfraErrorContext(
673
+ transport_type=EnumInfraTransportType.FILESYSTEM,
674
+ operation=operation,
675
+ target_name="manifest_persistence_handler",
676
+ correlation_id=correlation_id,
677
+ )
678
+ raise InfraUnavailableError(
679
+ f"Retry loop completed without result for {operation}",
680
+ context=ctx,
681
+ )
682
+
683
+ def _get_manifest_path(self, manifest_id: UUID, created_at: datetime) -> Path:
684
+ """Get the file path for a manifest based on ID and creation date.
685
+
686
+ Args:
687
+ manifest_id: Unique identifier of the manifest
688
+ created_at: Creation timestamp for date partitioning
689
+
690
+ Returns:
691
+ Path: {storage_path}/{year}/{month:02d}/{day:02d}/{manifest_id}.json
692
+ """
693
+ if self._storage_path is None:
694
+ raise RuntimeHostError(
695
+ "Handler not initialized - storage_path is None",
696
+ context=ModelInfraErrorContext(
697
+ transport_type=EnumInfraTransportType.FILESYSTEM,
698
+ operation="get_manifest_path",
699
+ target_name="manifest_persistence_handler",
700
+ ),
701
+ )
702
+
703
+ return (
704
+ self._storage_path
705
+ / str(created_at.year)
706
+ / f"{created_at.month:02d}"
707
+ / f"{created_at.day:02d}"
708
+ / f"{manifest_id}.json"
709
+ )
710
+
711
+ async def execute(
712
+ self, envelope: dict[str, object]
713
+ ) -> ModelHandlerOutput[dict[str, object]]:
714
+ """Execute manifest persistence operation from envelope.
715
+
716
+ Args:
717
+ envelope: Request envelope containing:
718
+ - operation: One of the supported manifest operations
719
+ - payload: Operation-specific payload
720
+ - correlation_id: Optional correlation ID for tracing
721
+ - envelope_id: Optional envelope ID for causality tracking
722
+
723
+ Returns:
724
+ ModelHandlerOutput[dict[str, object]] containing operation result
725
+
726
+ Raises:
727
+ RuntimeHostError: If handler not initialized
728
+ ProtocolConfigurationError: If operation or payload is invalid
729
+ """
730
+ correlation_id = self._extract_correlation_id(envelope)
731
+ input_envelope_id = self._extract_envelope_id(envelope)
732
+
733
+ if not self._initialized:
734
+ ctx = ModelInfraErrorContext(
735
+ transport_type=EnumInfraTransportType.FILESYSTEM,
736
+ operation="execute",
737
+ target_name="manifest_persistence_handler",
738
+ correlation_id=correlation_id,
739
+ )
740
+ raise RuntimeHostError(
741
+ "HandlerManifestPersistence not initialized. Call initialize() first.",
742
+ context=ctx,
743
+ )
744
+
745
+ operation = envelope.get("operation")
746
+ if not isinstance(operation, str):
747
+ ctx = ModelInfraErrorContext(
748
+ transport_type=EnumInfraTransportType.FILESYSTEM,
749
+ operation="execute",
750
+ target_name="manifest_persistence_handler",
751
+ correlation_id=correlation_id,
752
+ )
753
+ raise ProtocolConfigurationError(
754
+ "Missing or invalid 'operation' in envelope", context=ctx
755
+ )
756
+
757
+ if operation not in _SUPPORTED_OPERATIONS:
758
+ ctx = ModelInfraErrorContext(
759
+ transport_type=EnumInfraTransportType.FILESYSTEM,
760
+ operation=operation,
761
+ target_name="manifest_persistence_handler",
762
+ correlation_id=correlation_id,
763
+ )
764
+ raise ProtocolConfigurationError(
765
+ f"Operation '{operation}' not supported. Available: {', '.join(sorted(_SUPPORTED_OPERATIONS))}",
766
+ context=ctx,
767
+ )
768
+
769
+ payload = envelope.get("payload")
770
+ if not isinstance(payload, dict):
771
+ ctx = ModelInfraErrorContext(
772
+ transport_type=EnumInfraTransportType.FILESYSTEM,
773
+ operation=operation,
774
+ target_name="manifest_persistence_handler",
775
+ correlation_id=correlation_id,
776
+ )
777
+ raise ProtocolConfigurationError(
778
+ "Missing or invalid 'payload' in envelope", context=ctx
779
+ )
780
+
781
+ # Route to appropriate operation handler
782
+ if operation == "manifest.store":
783
+ return await self._execute_store(payload, correlation_id, input_envelope_id)
784
+ elif operation == "manifest.retrieve":
785
+ return await self._execute_retrieve(
786
+ payload, correlation_id, input_envelope_id
787
+ )
788
+ else: # manifest.query
789
+ return await self._execute_query(payload, correlation_id, input_envelope_id)
790
+
791
+ async def _execute_store(
792
+ self,
793
+ payload: dict[str, object],
794
+ correlation_id: UUID,
795
+ input_envelope_id: UUID,
796
+ ) -> ModelHandlerOutput[dict[str, object]]:
797
+ """Execute manifest.store operation with retry logic.
798
+
799
+ Stores a manifest with atomic write (temp file + rename) and
800
+ idempotent behavior (existing manifests are not overwritten).
801
+
802
+ Payload:
803
+ - manifest: dict (required) - Serialized ModelExecutionManifest
804
+
805
+ Returns:
806
+ Result with manifest_id, file_path, created, and bytes_written.
807
+
808
+ Raises:
809
+ InfraConnectionError: If write fails after retries exhausted
810
+ InfraUnavailableError: If circuit breaker is open
811
+ """
812
+ operation = "manifest.store"
813
+
814
+ # Extract manifest (required)
815
+ manifest_raw = payload.get("manifest")
816
+ if not isinstance(manifest_raw, dict):
817
+ ctx = ModelInfraErrorContext(
818
+ transport_type=EnumInfraTransportType.FILESYSTEM,
819
+ operation=operation,
820
+ target_name="manifest_persistence_handler",
821
+ correlation_id=correlation_id,
822
+ )
823
+ raise ProtocolConfigurationError(
824
+ "Missing or invalid 'manifest' in payload - must be a dictionary",
825
+ context=ctx,
826
+ )
827
+
828
+ # Extract required fields from manifest
829
+ manifest_id_raw = manifest_raw.get("manifest_id")
830
+ created_at_raw = manifest_raw.get("created_at")
831
+
832
+ ctx = ModelInfraErrorContext(
833
+ transport_type=EnumInfraTransportType.FILESYSTEM,
834
+ operation=operation,
835
+ target_name="manifest_persistence_handler",
836
+ correlation_id=correlation_id,
837
+ )
838
+
839
+ # Parse manifest_id
840
+ try:
841
+ if isinstance(manifest_id_raw, UUID):
842
+ manifest_id = manifest_id_raw
843
+ elif isinstance(manifest_id_raw, str):
844
+ manifest_id = UUID(manifest_id_raw)
845
+ else:
846
+ raise ProtocolConfigurationError(
847
+ "Manifest missing required 'manifest_id' field or invalid type",
848
+ context=ctx,
849
+ )
850
+ except ValueError as e:
851
+ raise ProtocolConfigurationError(
852
+ f"Invalid manifest_id format: {e}",
853
+ context=ctx,
854
+ ) from e
855
+
856
+ # Parse created_at
857
+ try:
858
+ if isinstance(created_at_raw, datetime):
859
+ created_at = created_at_raw
860
+ warn_if_naive_datetime(
861
+ created_at, field_name="created_at", correlation_id=correlation_id
862
+ )
863
+ elif isinstance(created_at_raw, str):
864
+ # Try ISO format parsing (Z suffix converted to +00:00)
865
+ created_at = datetime.fromisoformat(
866
+ created_at_raw.replace("Z", "+00:00")
867
+ )
868
+ else:
869
+ raise ProtocolConfigurationError(
870
+ "Manifest missing required 'created_at' field or invalid type",
871
+ context=ctx,
872
+ )
873
+ except ValueError as e:
874
+ raise ProtocolConfigurationError(
875
+ f"Invalid created_at format: {e}",
876
+ context=ctx,
877
+ ) from e
878
+
879
+ # Get file path
880
+ file_path = self._get_manifest_path(manifest_id, created_at)
881
+
882
+ async def _do_store_io() -> ModelHandlerOutput[dict[str, object]]:
883
+ """Inner function containing I/O operations (wrapped with retry)."""
884
+ # Check if manifest already exists (idempotent behavior)
885
+ if file_path.exists():
886
+ logger.debug(
887
+ "Manifest already exists, skipping write (idempotent)",
888
+ extra={
889
+ "manifest_id": str(manifest_id),
890
+ "path": str(file_path),
891
+ "correlation_id": str(correlation_id),
892
+ },
893
+ )
894
+
895
+ result = ModelManifestStoreResult(
896
+ manifest_id=manifest_id,
897
+ file_path=str(file_path),
898
+ created=False,
899
+ bytes_written=0,
900
+ )
901
+
902
+ return ModelHandlerOutput.for_compute(
903
+ input_envelope_id=input_envelope_id,
904
+ correlation_id=correlation_id,
905
+ handler_id=HANDLER_ID_MANIFEST_PERSISTENCE,
906
+ result={
907
+ "status": "success",
908
+ "payload": result.model_dump(mode="json"),
909
+ "correlation_id": str(correlation_id),
910
+ },
911
+ )
912
+
913
+ # Create parent directories
914
+ file_path.parent.mkdir(parents=True, exist_ok=True)
915
+
916
+ # Serialize manifest to JSON
917
+ manifest_json = json.dumps(manifest_raw, indent=2, default=str)
918
+ manifest_bytes = manifest_json.encode("utf-8")
919
+
920
+ # Atomic write: write to temp file, then rename
921
+ temp_fd, temp_path = tempfile.mkstemp(
922
+ suffix=".tmp",
923
+ prefix=f"{manifest_id}_",
924
+ dir=file_path.parent,
925
+ )
926
+ try:
927
+ with os.fdopen(temp_fd, "wb") as f:
928
+ f.write(manifest_bytes)
929
+ # Atomic rename
930
+ temp_path_obj = Path(temp_path)
931
+ temp_path_obj.rename(file_path)
932
+ except OSError:
933
+ # Clean up temp file on failure
934
+ temp_path_obj = Path(temp_path)
935
+ if temp_path_obj.exists():
936
+ temp_path_obj.unlink()
937
+ raise
938
+
939
+ bytes_written = len(manifest_bytes)
940
+
941
+ logger.debug(
942
+ "Manifest stored successfully",
943
+ extra={
944
+ "manifest_id": str(manifest_id),
945
+ "path": str(file_path),
946
+ "bytes_written": bytes_written,
947
+ "correlation_id": str(correlation_id),
948
+ },
949
+ )
950
+
951
+ result = ModelManifestStoreResult(
952
+ manifest_id=manifest_id,
953
+ file_path=str(file_path),
954
+ created=True,
955
+ bytes_written=bytes_written,
956
+ )
957
+
958
+ return ModelHandlerOutput.for_compute(
959
+ input_envelope_id=input_envelope_id,
960
+ correlation_id=correlation_id,
961
+ handler_id=HANDLER_ID_MANIFEST_PERSISTENCE,
962
+ result={
963
+ "status": "success",
964
+ "payload": result.model_dump(mode="json"),
965
+ "correlation_id": str(correlation_id),
966
+ },
967
+ )
968
+
969
+ # Execute with retry logic (handles circuit breaker and backoff)
970
+ return await self._execute_with_retry(operation, _do_store_io, correlation_id)
971
+
972
+ async def _execute_retrieve(
973
+ self,
974
+ payload: dict[str, object],
975
+ correlation_id: UUID,
976
+ input_envelope_id: UUID,
977
+ ) -> ModelHandlerOutput[dict[str, object]]:
978
+ """Execute manifest.retrieve operation with retry logic.
979
+
980
+ Retrieves a manifest by scanning date directories.
981
+
982
+ Complexity:
983
+ O(d) where d is the number of date directories (year/month/day).
984
+ This is a full directory scan because manifest_id does not encode
985
+ the creation date, requiring us to search all partitions. This is
986
+ acceptable for the current use case (low query volume, typically
987
+ recent manifests). For high-volume retrieval patterns, consider
988
+ maintaining a separate index file or using the query operation
989
+ with correlation_id filter.
990
+
991
+ Payload:
992
+ - manifest_id: UUID or string (required) - Manifest to retrieve
993
+
994
+ Returns:
995
+ Result with manifest_id, manifest data, file_path, and found flag.
996
+
997
+ Raises:
998
+ InfraConnectionError: If read fails after retries exhausted
999
+ InfraUnavailableError: If circuit breaker is open
1000
+ """
1001
+ operation = "manifest.retrieve"
1002
+
1003
+ # Extract manifest_id (required)
1004
+ manifest_id_raw = payload.get("manifest_id")
1005
+ ctx = ModelInfraErrorContext(
1006
+ transport_type=EnumInfraTransportType.FILESYSTEM,
1007
+ operation=operation,
1008
+ target_name="manifest_persistence_handler",
1009
+ correlation_id=correlation_id,
1010
+ )
1011
+
1012
+ try:
1013
+ if isinstance(manifest_id_raw, UUID):
1014
+ manifest_id = manifest_id_raw
1015
+ elif isinstance(manifest_id_raw, str):
1016
+ manifest_id = UUID(manifest_id_raw)
1017
+ else:
1018
+ raise ProtocolConfigurationError(
1019
+ "Missing or invalid 'manifest_id' in payload",
1020
+ context=ctx,
1021
+ )
1022
+ except ValueError as e:
1023
+ raise ProtocolConfigurationError(
1024
+ f"Invalid manifest_id format: {e}",
1025
+ context=ctx,
1026
+ ) from e
1027
+
1028
+ async def _do_retrieve_io() -> ModelHandlerOutput[dict[str, object]]:
1029
+ """Inner function containing I/O operations (wrapped with retry)."""
1030
+ # Search for manifest in date directories
1031
+ found_path: Path | None = None
1032
+ manifest_data: dict[str, object] | None = None
1033
+
1034
+ if self._storage_path is None:
1035
+ raise RuntimeHostError(
1036
+ "Handler not initialized - storage_path is None",
1037
+ context=ctx,
1038
+ )
1039
+
1040
+ # Scan year/month/day directories with performance tracking
1041
+ scan_start_time = time.monotonic()
1042
+ directories_scanned = 0
1043
+
1044
+ for year_dir in sorted(self._storage_path.iterdir(), reverse=True):
1045
+ if not year_dir.is_dir():
1046
+ continue
1047
+ directories_scanned += 1
1048
+ for month_dir in sorted(year_dir.iterdir(), reverse=True):
1049
+ if not month_dir.is_dir():
1050
+ continue
1051
+ directories_scanned += 1
1052
+ for day_dir in sorted(month_dir.iterdir(), reverse=True):
1053
+ if not day_dir.is_dir():
1054
+ continue
1055
+ directories_scanned += 1
1056
+ manifest_file = day_dir / f"{manifest_id}.json"
1057
+ if manifest_file.exists():
1058
+ found_path = manifest_file
1059
+ break
1060
+ if found_path:
1061
+ break
1062
+ if found_path:
1063
+ break
1064
+
1065
+ scan_duration = time.monotonic() - scan_start_time
1066
+ logger.debug(
1067
+ "Directory scan completed for retrieve",
1068
+ extra={
1069
+ "duration_seconds": round(scan_duration, 6),
1070
+ "directories_scanned": directories_scanned,
1071
+ "manifest_found": found_path is not None,
1072
+ "manifest_id": str(manifest_id),
1073
+ "correlation_id": str(correlation_id),
1074
+ },
1075
+ )
1076
+
1077
+ if found_path:
1078
+ # Check file size before reading
1079
+ file_size = found_path.stat().st_size
1080
+ if file_size > self._max_file_size:
1081
+ raise InfraUnavailableError(
1082
+ "Manifest file size exceeds configured limit",
1083
+ context=ctx,
1084
+ )
1085
+
1086
+ # Read and parse manifest
1087
+ manifest_json = found_path.read_text(encoding="utf-8")
1088
+ manifest_data = json.loads(manifest_json)
1089
+
1090
+ result = ModelManifestRetrieveResult(
1091
+ manifest_id=manifest_id,
1092
+ manifest=manifest_data,
1093
+ file_path=str(found_path) if found_path else None,
1094
+ found=found_path is not None,
1095
+ )
1096
+
1097
+ logger.debug(
1098
+ "Manifest retrieve completed",
1099
+ extra={
1100
+ "manifest_id": str(manifest_id),
1101
+ "found": result.found,
1102
+ "path": str(found_path) if found_path else None,
1103
+ "correlation_id": str(correlation_id),
1104
+ },
1105
+ )
1106
+
1107
+ return ModelHandlerOutput.for_compute(
1108
+ input_envelope_id=input_envelope_id,
1109
+ correlation_id=correlation_id,
1110
+ handler_id=HANDLER_ID_MANIFEST_PERSISTENCE,
1111
+ result={
1112
+ "status": "success",
1113
+ "payload": result.model_dump(mode="json"),
1114
+ "correlation_id": str(correlation_id),
1115
+ },
1116
+ )
1117
+
1118
+ # Execute with retry logic (handles circuit breaker and backoff)
1119
+ return await self._execute_with_retry(
1120
+ operation, _do_retrieve_io, correlation_id
1121
+ )
1122
+
1123
+ async def _execute_query(
1124
+ self,
1125
+ payload: dict[str, object],
1126
+ correlation_id: UUID,
1127
+ input_envelope_id: UUID,
1128
+ ) -> ModelHandlerOutput[dict[str, object]]:
1129
+ """Execute manifest.query operation with retry logic.
1130
+
1131
+ Queries manifests with filters and respects metadata_only flag.
1132
+
1133
+ Complexity:
1134
+ O(n) where n is the total number of manifest files. Each file must
1135
+ be read and parsed to apply filters. The limit parameter provides
1136
+ early termination but worst case (few matches) scans all files.
1137
+ This is acceptable for the current use case where:
1138
+ - Query operations are infrequent (debugging, auditing)
1139
+ - Date-based partitioning enables manual pruning of old directories
1140
+ - Typical deployments have <10k manifests
1141
+
1142
+ Payload:
1143
+ - correlation_id: UUID or string (optional) - Filter by correlation_id
1144
+ - node_id: string (optional) - Filter by node_id
1145
+ - created_after: datetime or ISO string (optional) - Filter by creation time
1146
+ - created_before: datetime or ISO string (optional) - Filter by creation time
1147
+ - metadata_only: bool (optional, default False) - Return only metadata
1148
+ - limit: int (optional, default 100) - Maximum results
1149
+
1150
+ Returns:
1151
+ Result with manifests list, total_count, and metadata_only flag.
1152
+
1153
+ Raises:
1154
+ InfraConnectionError: If read fails after retries exhausted
1155
+ InfraUnavailableError: If circuit breaker is open
1156
+ """
1157
+ operation = "manifest.query"
1158
+
1159
+ ctx = ModelInfraErrorContext(
1160
+ transport_type=EnumInfraTransportType.FILESYSTEM,
1161
+ operation=operation,
1162
+ target_name="manifest_persistence_handler",
1163
+ correlation_id=correlation_id,
1164
+ )
1165
+
1166
+ # Extract filter parameters
1167
+ filter_correlation_id: UUID | None = None
1168
+ correlation_id_raw = payload.get("correlation_id")
1169
+ if correlation_id_raw is not None:
1170
+ try:
1171
+ if isinstance(correlation_id_raw, UUID):
1172
+ filter_correlation_id = correlation_id_raw
1173
+ elif isinstance(correlation_id_raw, str):
1174
+ filter_correlation_id = UUID(correlation_id_raw)
1175
+ else:
1176
+ logger.warning(
1177
+ "Invalid correlation_id filter type, ignoring filter",
1178
+ extra={
1179
+ "provided_type": type(correlation_id_raw).__name__,
1180
+ "correlation_id": str(correlation_id),
1181
+ },
1182
+ )
1183
+ except ValueError as e:
1184
+ logger.warning(
1185
+ "Invalid correlation_id filter format, ignoring filter",
1186
+ extra={
1187
+ "provided_value": str(correlation_id_raw)[:100],
1188
+ "error": str(e),
1189
+ "correlation_id": str(correlation_id),
1190
+ },
1191
+ )
1192
+
1193
+ filter_node_id: str | None = None
1194
+ node_id_raw = payload.get("node_id")
1195
+ if node_id_raw is not None:
1196
+ if isinstance(node_id_raw, str):
1197
+ filter_node_id = node_id_raw
1198
+ else:
1199
+ logger.warning(
1200
+ "Invalid node_id filter type, ignoring filter",
1201
+ extra={
1202
+ "provided_type": type(node_id_raw).__name__,
1203
+ "correlation_id": str(correlation_id),
1204
+ },
1205
+ )
1206
+
1207
+ filter_created_after: datetime | None = None
1208
+ created_after_raw = payload.get("created_after")
1209
+ if created_after_raw is not None:
1210
+ try:
1211
+ if isinstance(created_after_raw, datetime):
1212
+ filter_created_after = created_after_raw
1213
+ warn_if_naive_datetime(
1214
+ filter_created_after,
1215
+ field_name="created_after",
1216
+ correlation_id=correlation_id,
1217
+ )
1218
+ elif isinstance(created_after_raw, str):
1219
+ filter_created_after = datetime.fromisoformat(
1220
+ created_after_raw.replace("Z", "+00:00")
1221
+ )
1222
+ else:
1223
+ logger.warning(
1224
+ "Invalid created_after filter type, ignoring filter",
1225
+ extra={
1226
+ "provided_type": type(created_after_raw).__name__,
1227
+ "correlation_id": str(correlation_id),
1228
+ },
1229
+ )
1230
+ except ValueError as e:
1231
+ logger.warning(
1232
+ "Invalid created_after filter format, ignoring filter",
1233
+ extra={
1234
+ "provided_value": str(created_after_raw)[:100],
1235
+ "error": str(e),
1236
+ "correlation_id": str(correlation_id),
1237
+ },
1238
+ )
1239
+
1240
+ filter_created_before: datetime | None = None
1241
+ created_before_raw = payload.get("created_before")
1242
+ if created_before_raw is not None:
1243
+ try:
1244
+ if isinstance(created_before_raw, datetime):
1245
+ filter_created_before = created_before_raw
1246
+ warn_if_naive_datetime(
1247
+ filter_created_before,
1248
+ field_name="created_before",
1249
+ correlation_id=correlation_id,
1250
+ )
1251
+ elif isinstance(created_before_raw, str):
1252
+ filter_created_before = datetime.fromisoformat(
1253
+ created_before_raw.replace("Z", "+00:00")
1254
+ )
1255
+ else:
1256
+ logger.warning(
1257
+ "Invalid created_before filter type, ignoring filter",
1258
+ extra={
1259
+ "provided_type": type(created_before_raw).__name__,
1260
+ "correlation_id": str(correlation_id),
1261
+ },
1262
+ )
1263
+ except ValueError as e:
1264
+ logger.warning(
1265
+ "Invalid created_before filter format, ignoring filter",
1266
+ extra={
1267
+ "provided_value": str(created_before_raw)[:100],
1268
+ "error": str(e),
1269
+ "correlation_id": str(correlation_id),
1270
+ },
1271
+ )
1272
+
1273
+ metadata_only_raw = payload.get("metadata_only", False)
1274
+ if isinstance(metadata_only_raw, bool):
1275
+ metadata_only = metadata_only_raw
1276
+ else:
1277
+ logger.warning(
1278
+ "Invalid metadata_only filter type, using default False",
1279
+ extra={
1280
+ "provided_type": type(metadata_only_raw).__name__,
1281
+ "provided_value": str(metadata_only_raw)[:100],
1282
+ "correlation_id": str(correlation_id),
1283
+ },
1284
+ )
1285
+ metadata_only = False
1286
+
1287
+ limit_raw = payload.get("limit", 100)
1288
+ if isinstance(limit_raw, int) and limit_raw >= 1:
1289
+ limit = min(limit_raw, 10000) # Cap at 10000
1290
+ else:
1291
+ logger.warning(
1292
+ "Invalid limit filter value, using default 100",
1293
+ extra={
1294
+ "provided_type": type(limit_raw).__name__,
1295
+ "provided_value": str(limit_raw)[:100],
1296
+ "correlation_id": str(correlation_id),
1297
+ },
1298
+ )
1299
+ limit = 100
1300
+
1301
+ async def _do_query_io() -> ModelHandlerOutput[dict[str, object]]:
1302
+ """Inner function containing I/O operations (wrapped with retry)."""
1303
+ if self._storage_path is None:
1304
+ raise RuntimeHostError(
1305
+ "Handler not initialized - storage_path is None",
1306
+ context=ctx,
1307
+ )
1308
+
1309
+ manifests_metadata: list[ModelManifestMetadata] = []
1310
+ manifests_data: list[dict[str, object]] = []
1311
+ count = 0
1312
+
1313
+ # Scan date directories with performance tracking
1314
+ scan_start_time = time.monotonic()
1315
+ files_scanned = 0
1316
+ directories_scanned = 0
1317
+
1318
+ for year_dir in sorted(self._storage_path.iterdir(), reverse=True):
1319
+ if not year_dir.is_dir() or count >= limit:
1320
+ continue
1321
+ directories_scanned += 1
1322
+ for month_dir in sorted(year_dir.iterdir(), reverse=True):
1323
+ if not month_dir.is_dir() or count >= limit:
1324
+ continue
1325
+ directories_scanned += 1
1326
+ for day_dir in sorted(month_dir.iterdir(), reverse=True):
1327
+ if not day_dir.is_dir() or count >= limit:
1328
+ continue
1329
+ directories_scanned += 1
1330
+ for manifest_file in sorted(
1331
+ day_dir.glob("*.json"), reverse=True
1332
+ ):
1333
+ if count >= limit:
1334
+ break
1335
+ files_scanned += 1
1336
+
1337
+ try:
1338
+ file_stat = manifest_file.stat()
1339
+ file_size = file_stat.st_size
1340
+
1341
+ # Skip files that are too large
1342
+ if file_size > self._max_file_size:
1343
+ continue
1344
+
1345
+ # Full deserialization required to access filter
1346
+ # fields (correlation_id, node_id, created_at)
1347
+ # stored within the manifest JSON.
1348
+ #
1349
+ # The `metadata_only` flag controls the RETURN
1350
+ # format (full manifest vs. summary), not the
1351
+ # read pattern. This is a limitation of
1352
+ # filesystem storage: filter fields are not
1353
+ # available as external file metadata.
1354
+ manifest_json = manifest_file.read_text(
1355
+ encoding="utf-8"
1356
+ )
1357
+ manifest_data = json.loads(manifest_json)
1358
+
1359
+ # Extract fields for filtering
1360
+ manifest_id_str = manifest_data.get("manifest_id")
1361
+ if not manifest_id_str:
1362
+ continue
1363
+
1364
+ try:
1365
+ manifest_id = UUID(str(manifest_id_str))
1366
+ except ValueError:
1367
+ continue
1368
+
1369
+ created_at_str = manifest_data.get("created_at")
1370
+ try:
1371
+ if isinstance(created_at_str, str):
1372
+ manifest_created_at = datetime.fromisoformat(
1373
+ created_at_str.replace("Z", "+00:00")
1374
+ )
1375
+ else:
1376
+ continue
1377
+ except ValueError:
1378
+ continue
1379
+
1380
+ manifest_correlation_id: UUID | None = None
1381
+ manifest_corr_id_raw = manifest_data.get(
1382
+ "correlation_id"
1383
+ )
1384
+ if manifest_corr_id_raw:
1385
+ try:
1386
+ manifest_correlation_id = UUID(
1387
+ str(manifest_corr_id_raw)
1388
+ )
1389
+ except ValueError:
1390
+ pass
1391
+
1392
+ node_identity = manifest_data.get("node_identity", {})
1393
+ manifest_node_id = (
1394
+ node_identity.get("node_id")
1395
+ if isinstance(node_identity, dict)
1396
+ else None
1397
+ )
1398
+
1399
+ # Apply filters
1400
+ if filter_correlation_id is not None:
1401
+ if manifest_correlation_id != filter_correlation_id:
1402
+ continue
1403
+
1404
+ if filter_node_id is not None:
1405
+ if manifest_node_id != filter_node_id:
1406
+ continue
1407
+
1408
+ if filter_created_after is not None:
1409
+ if manifest_created_at < filter_created_after:
1410
+ continue
1411
+
1412
+ if filter_created_before is not None:
1413
+ if manifest_created_at > filter_created_before:
1414
+ continue
1415
+
1416
+ # Manifest passes filters
1417
+ if metadata_only:
1418
+ metadata = ModelManifestMetadata(
1419
+ manifest_id=manifest_id,
1420
+ created_at=manifest_created_at,
1421
+ correlation_id=manifest_correlation_id,
1422
+ node_id=manifest_node_id,
1423
+ file_path=str(manifest_file),
1424
+ file_size=file_size,
1425
+ )
1426
+ manifests_metadata.append(metadata)
1427
+ else:
1428
+ manifests_data.append(manifest_data)
1429
+
1430
+ count += 1
1431
+
1432
+ except (OSError, json.JSONDecodeError) as e:
1433
+ logger.warning(
1434
+ "Failed to read manifest file: %s - %s",
1435
+ manifest_file,
1436
+ e,
1437
+ extra={
1438
+ "path": str(manifest_file),
1439
+ "error": str(e),
1440
+ "correlation_id": str(correlation_id),
1441
+ },
1442
+ )
1443
+ continue
1444
+
1445
+ scan_duration = time.monotonic() - scan_start_time
1446
+ logger.debug(
1447
+ "Directory scan completed for query",
1448
+ extra={
1449
+ "duration_seconds": round(scan_duration, 6),
1450
+ "directories_scanned": directories_scanned,
1451
+ "files_scanned": files_scanned,
1452
+ "matches_found": count,
1453
+ "limit": limit,
1454
+ "correlation_id": str(correlation_id),
1455
+ },
1456
+ )
1457
+
1458
+ result = ModelManifestQueryResult(
1459
+ manifests=manifests_metadata,
1460
+ manifest_data=manifests_data,
1461
+ total_count=count,
1462
+ metadata_only=metadata_only,
1463
+ )
1464
+
1465
+ logger.debug(
1466
+ "Manifest query completed",
1467
+ extra={
1468
+ "total_count": count,
1469
+ "metadata_only": metadata_only,
1470
+ "correlation_id": str(correlation_id),
1471
+ },
1472
+ )
1473
+
1474
+ return ModelHandlerOutput.for_compute(
1475
+ input_envelope_id=input_envelope_id,
1476
+ correlation_id=correlation_id,
1477
+ handler_id=HANDLER_ID_MANIFEST_PERSISTENCE,
1478
+ result={
1479
+ "status": "success",
1480
+ "payload": result.model_dump(mode="json"),
1481
+ "correlation_id": str(correlation_id),
1482
+ },
1483
+ )
1484
+
1485
+ # Execute with retry logic (handles circuit breaker and backoff)
1486
+ return await self._execute_with_retry(operation, _do_query_io, correlation_id)
1487
+
1488
+ def describe(self) -> dict[str, object]:
1489
+ """Return handler metadata and capabilities for introspection.
1490
+
1491
+ This method exposes the handler's type classification along with
1492
+ its operational configuration and capabilities, including detailed
1493
+ circuit breaker state for operational observability.
1494
+
1495
+ Returns:
1496
+ dict containing:
1497
+ - handler_type: Architectural role from handler_type property
1498
+ - handler_category: Behavioral classification
1499
+ - supported_operations: List of supported operations
1500
+ - storage_path: Storage directory path (when initialized)
1501
+ - initialized: Whether the handler is initialized
1502
+ - version: Handler version string
1503
+ - circuit_breaker: Circuit breaker state for observability
1504
+ - initialized: Whether circuit breaker is initialized
1505
+ - state: Current state ("closed", "open", or "half_open")
1506
+ - failures: Current failure count
1507
+ - threshold: Configured failure threshold before opening
1508
+ - reset_timeout_seconds: Configured timeout before half_open transition
1509
+ - seconds_until_half_open: Seconds remaining until half_open (only when open)
1510
+
1511
+ Circuit Breaker States:
1512
+ - **closed**: Normal operation, requests allowed. Failures tracked.
1513
+ - **open**: Circuit tripped after threshold failures. Requests blocked.
1514
+ Will transition to half_open after reset_timeout_seconds.
1515
+ - **half_open**: Recovery testing phase. Next success closes circuit,
1516
+ next failure reopens it. This state is transient and detected when
1517
+ the circuit is marked open but the reset timeout has elapsed.
1518
+ """
1519
+ # Get circuit breaker state from mixin (encapsulated access)
1520
+ circuit_breaker_info = self._get_circuit_breaker_state()
1521
+
1522
+ # Override initialized with handler's own flag for precise tracking
1523
+ # (handler may track initialization more granularly than mixin detection)
1524
+ circuit_breaker_info["initialized"] = self._circuit_breaker_initialized
1525
+
1526
+ result: dict[str, object] = {
1527
+ "handler_type": self.handler_type.value,
1528
+ "handler_category": self.handler_category.value,
1529
+ "supported_operations": sorted(_SUPPORTED_OPERATIONS),
1530
+ "storage_path": str(self._storage_path) if self._storage_path else None,
1531
+ "initialized": self._initialized,
1532
+ "version": "0.1.0",
1533
+ "circuit_breaker": circuit_breaker_info,
1534
+ }
1535
+
1536
+ return result
1537
+
1538
+
1539
+ __all__: list[str] = ["HandlerManifestPersistence", "HANDLER_ID_MANIFEST_PERSISTENCE"]