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,1478 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Filesystem Handler - Secure filesystem operations with path whitelisting.
4
+
5
+ Provides secure filesystem operations including read, write, list, ensure directory,
6
+ and delete operations with comprehensive security features.
7
+
8
+ Security Features:
9
+ - Path whitelist validation: Only allowed directories can be accessed
10
+ - File size limits: Configurable max sizes for read/write operations
11
+ - Symlink protection: Symlinks are resolved and validated against allowed paths
12
+ - Path traversal prevention: Prevents escape from allowed directories via ../
13
+
14
+ Supported Operations:
15
+ - filesystem.read_file: Read file contents (text or binary)
16
+ - filesystem.write_file: Write content to file
17
+ - filesystem.list_directory: List directory contents with optional glob filtering
18
+ - filesystem.ensure_directory: Create directory structure
19
+ - filesystem.delete_file: Delete file with safety checks
20
+
21
+ Note:
22
+ Environment variable configuration (ONEX_FS_MAX_READ_SIZE, ONEX_FS_MAX_WRITE_SIZE)
23
+ is parsed at module import time, not at handler instantiation. This means:
24
+
25
+ - Changes to environment variables require application restart to take effect
26
+ - Tests should use ``unittest.mock.patch.dict(os.environ, ...)`` before importing,
27
+ or use ``importlib.reload()`` to re-import the module after patching
28
+ - This is an intentional design choice for startup-time validation
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import base64
34
+ import binascii
35
+ import errno
36
+ import fnmatch
37
+ import logging
38
+ import os
39
+ from pathlib import Path
40
+ from uuid import UUID, uuid4
41
+
42
+ from omnibase_core.container import ModelONEXContainer
43
+ from omnibase_core.models.dispatch import ModelHandlerOutput
44
+ from omnibase_infra.enums import (
45
+ EnumHandlerType,
46
+ EnumHandlerTypeCategory,
47
+ EnumInfraTransportType,
48
+ )
49
+ from omnibase_infra.errors import (
50
+ InfraConnectionError,
51
+ InfraUnavailableError,
52
+ ModelInfraErrorContext,
53
+ ProtocolConfigurationError,
54
+ RuntimeHostError,
55
+ )
56
+ from omnibase_infra.mixins import MixinAsyncCircuitBreaker, MixinEnvelopeExtraction
57
+ from omnibase_infra.utils import parse_env_int
58
+
59
+ logger = logging.getLogger(__name__)
60
+
61
+ # Default configuration from environment
62
+ _DEFAULT_MAX_READ_SIZE: int = parse_env_int(
63
+ "ONEX_FS_MAX_READ_SIZE",
64
+ 100 * 1024 * 1024, # 100 MB
65
+ min_value=1024,
66
+ max_value=1024 * 1024 * 1024, # 1 GB
67
+ transport_type=EnumInfraTransportType.FILESYSTEM,
68
+ service_name="filesystem_handler",
69
+ )
70
+ _DEFAULT_MAX_WRITE_SIZE: int = parse_env_int(
71
+ "ONEX_FS_MAX_WRITE_SIZE",
72
+ 50 * 1024 * 1024, # 50 MB
73
+ min_value=1024,
74
+ max_value=500 * 1024 * 1024, # 500 MB
75
+ transport_type=EnumInfraTransportType.FILESYSTEM,
76
+ service_name="filesystem_handler",
77
+ )
78
+
79
+ _SUPPORTED_OPERATIONS: frozenset[str] = frozenset(
80
+ {
81
+ "filesystem.read_file",
82
+ "filesystem.write_file",
83
+ "filesystem.list_directory",
84
+ "filesystem.ensure_directory",
85
+ "filesystem.delete_file",
86
+ }
87
+ )
88
+
89
+ # Handler ID for ModelHandlerOutput
90
+ HANDLER_ID_FILESYSTEM: str = "filesystem-handler"
91
+
92
+ # Size category thresholds for sanitized logging
93
+ _SIZE_THRESHOLD_KB: int = 1024 # 1 KB
94
+ _SIZE_THRESHOLD_MB: int = 1024 * 1024 # 1 MB
95
+ _SIZE_THRESHOLD_10MB: int = 10 * 1024 * 1024 # 10 MB
96
+
97
+
98
+ def _categorize_size(size: int) -> str:
99
+ """Categorize byte size into security-safe categories.
100
+
101
+ This prevents exact payload sizes from being exposed in error messages
102
+ and logs, which could help attackers probe size limits.
103
+
104
+ Args:
105
+ size: Size in bytes
106
+
107
+ Returns:
108
+ Size category: "small", "medium", "large", or "very_large"
109
+ """
110
+ if size < _SIZE_THRESHOLD_KB:
111
+ return "small"
112
+ elif size < _SIZE_THRESHOLD_MB:
113
+ return "medium"
114
+ elif size < _SIZE_THRESHOLD_10MB:
115
+ return "large"
116
+ else:
117
+ return "very_large"
118
+
119
+
120
+ class HandlerFileSystem(MixinEnvelopeExtraction, MixinAsyncCircuitBreaker):
121
+ """Filesystem handler with security features for ONEX infrastructure.
122
+
123
+ Security Features:
124
+ - Path whitelist validation to restrict file access to allowed directories
125
+ - File size limits to prevent DoS attacks via memory exhaustion
126
+ - Symlink resolution and validation to prevent path traversal attacks
127
+ - All paths are resolved to absolute canonical paths before validation
128
+ - Circuit breaker for resilient operation
129
+
130
+ Configuration:
131
+ Initialize with allowed_paths to define accessible directories.
132
+ Configure max_read_size and max_write_size to control memory usage.
133
+ """
134
+
135
+ def __init__(self, container: ModelONEXContainer | None = None) -> None:
136
+ """Initialize HandlerFileSystem with optional container injection.
137
+
138
+ Args:
139
+ container: Optional ONEX container for dependency injection.
140
+ When provided, enables full ONEX integration. When None,
141
+ handler operates in standalone mode for testing.
142
+
143
+ Note:
144
+ The container is stored for interface compliance with the standard ONEX
145
+ handler pattern and to enable future DI-based service resolution (e.g.,
146
+ metrics, logging, observability integration). Currently, the handler
147
+ operates independently for filesystem operations, but storing the container
148
+ ensures API consistency and enables future enhancements without breaking
149
+ changes.
150
+ """
151
+ self._container = container
152
+ self._allowed_paths: tuple[Path, ...] = ()
153
+ self._max_read_size: int = _DEFAULT_MAX_READ_SIZE
154
+ self._max_write_size: int = _DEFAULT_MAX_WRITE_SIZE
155
+ self._initialized: bool = False
156
+
157
+ @property
158
+ def handler_type(self) -> EnumHandlerType:
159
+ """Return the architectural role of this handler.
160
+
161
+ Returns:
162
+ EnumHandlerType.INFRA_HANDLER - This handler is an infrastructure
163
+ protocol/transport handler for filesystem operations.
164
+
165
+ Note:
166
+ handler_type determines lifecycle, protocol selection, and runtime
167
+ invocation patterns. It answers "what is this handler in the architecture?"
168
+
169
+ See Also:
170
+ - handler_category: Behavioral classification (EFFECT/COMPUTE)
171
+ - transport_type: Specific transport protocol (FILESYSTEM)
172
+ """
173
+ return EnumHandlerType.INFRA_HANDLER
174
+
175
+ @property
176
+ def handler_category(self) -> EnumHandlerTypeCategory:
177
+ """Return the behavioral classification of this handler.
178
+
179
+ Returns:
180
+ EnumHandlerTypeCategory.EFFECT - This handler performs side-effecting
181
+ I/O operations (filesystem read/write). EFFECT handlers are not
182
+ deterministic and interact with external systems.
183
+
184
+ Note:
185
+ handler_category determines security rules, determinism guarantees,
186
+ replay safety, and permissions. It answers "how does this handler
187
+ behave at runtime?"
188
+
189
+ Categories:
190
+ - COMPUTE: Pure, deterministic transformations (no side effects)
191
+ - EFFECT: Side-effecting I/O (database, HTTP, filesystem)
192
+ - NONDETERMINISTIC_COMPUTE: Pure but not deterministic (UUID, random)
193
+
194
+ See Also:
195
+ - handler_type: Architectural role (INFRA_HANDLER/NODE_HANDLER/etc.)
196
+ - transport_type: Specific transport protocol (FILESYSTEM)
197
+ """
198
+ return EnumHandlerTypeCategory.EFFECT
199
+
200
+ @property
201
+ def transport_type(self) -> EnumInfraTransportType:
202
+ """Return the transport protocol identifier for this handler.
203
+
204
+ Returns:
205
+ EnumInfraTransportType.FILESYSTEM - This handler uses local filesystem.
206
+
207
+ Note:
208
+ transport_type identifies the specific transport/protocol this handler
209
+ uses. It is the third dimension of the handler type system, alongside
210
+ handler_type (architectural role) and handler_category (behavioral
211
+ classification).
212
+
213
+ The three dimensions together form a complete handler classification:
214
+ - handler_type: INFRA_HANDLER (what it is architecturally)
215
+ - handler_category: EFFECT (how it behaves at runtime)
216
+ - transport_type: FILESYSTEM (what protocol it uses)
217
+
218
+ See Also:
219
+ - handler_type: Architectural role
220
+ - handler_category: Behavioral classification
221
+ """
222
+ return EnumInfraTransportType.FILESYSTEM
223
+
224
+ @transport_type.setter
225
+ def transport_type(self, value: EnumInfraTransportType) -> None:
226
+ """Prevent modification of transport_type after initialization.
227
+
228
+ The transport_type is immutable for this handler - it is always FILESYSTEM.
229
+ This setter raises an AttributeError if modification is attempted.
230
+
231
+ Args:
232
+ value: The transport type value (assignment always raises error)
233
+
234
+ Raises:
235
+ AttributeError: Always raised - transport_type is read-only.
236
+ """
237
+ raise AttributeError(
238
+ "transport_type is read-only; it is set during handler initialization"
239
+ )
240
+
241
+ async def initialize(self, config: dict[str, object]) -> None:
242
+ """Initialize filesystem handler with path whitelist and size limits.
243
+
244
+ Args:
245
+ config: Configuration dict containing:
246
+ - allowed_paths: Required list of allowed directory paths (strings)
247
+ - max_read_size: Optional max read size in bytes (default: 100 MB)
248
+ - max_write_size: Optional max write size in bytes (default: 50 MB)
249
+ - correlation_id: Optional UUID or string for error tracing
250
+
251
+ Raises:
252
+ ProtocolConfigurationError: If allowed_paths is missing, empty, or invalid.
253
+
254
+ Security:
255
+ - All allowed_paths are resolved to absolute canonical paths
256
+ - Non-existent paths are logged as warnings but not rejected
257
+ - Empty allowed_paths list is rejected for security reasons
258
+ """
259
+ init_correlation_id = uuid4()
260
+
261
+ logger.info(
262
+ "Initializing %s",
263
+ self.__class__.__name__,
264
+ extra={
265
+ "handler": self.__class__.__name__,
266
+ "correlation_id": str(init_correlation_id),
267
+ },
268
+ )
269
+
270
+ ctx = ModelInfraErrorContext(
271
+ transport_type=EnumInfraTransportType.FILESYSTEM,
272
+ operation="initialize",
273
+ target_name="filesystem_handler",
274
+ correlation_id=init_correlation_id,
275
+ )
276
+
277
+ # Extract and validate allowed_paths (required)
278
+ allowed_paths_raw = config.get("allowed_paths")
279
+ if allowed_paths_raw is None:
280
+ raise ProtocolConfigurationError(
281
+ "Missing required 'allowed_paths' configuration - filesystem handler "
282
+ "requires explicit path whitelist for security",
283
+ context=ctx,
284
+ )
285
+
286
+ if (
287
+ not isinstance(allowed_paths_raw, list | tuple)
288
+ or len(allowed_paths_raw) == 0
289
+ ):
290
+ raise ProtocolConfigurationError(
291
+ "Configuration 'allowed_paths' must be a non-empty list or tuple of directory paths",
292
+ context=ctx,
293
+ )
294
+
295
+ # Resolve and validate each allowed path
296
+ resolved_paths: list[Path] = []
297
+ for path_str in allowed_paths_raw:
298
+ if not isinstance(path_str, str):
299
+ raise ProtocolConfigurationError(
300
+ f"Invalid path in allowed_paths: expected string, got {type(path_str).__name__}",
301
+ context=ctx,
302
+ )
303
+
304
+ path = Path(path_str).resolve()
305
+
306
+ if not path.exists():
307
+ logger.warning(
308
+ "Allowed path does not exist (will be created on first use): %s",
309
+ path,
310
+ extra={
311
+ "path": str(path),
312
+ "correlation_id": str(init_correlation_id),
313
+ },
314
+ )
315
+
316
+ resolved_paths.append(path)
317
+
318
+ # Store as immutable tuple after initialization
319
+ self._allowed_paths = tuple(resolved_paths)
320
+
321
+ # Extract optional size limits
322
+ max_read_raw = config.get("max_read_size")
323
+ if max_read_raw is not None:
324
+ if isinstance(max_read_raw, int) and max_read_raw > 0:
325
+ self._max_read_size = max_read_raw
326
+ else:
327
+ logger.warning(
328
+ "Invalid max_read_size config value ignored, using default",
329
+ extra={
330
+ "provided_value": max_read_raw,
331
+ "default_value": self._max_read_size,
332
+ },
333
+ )
334
+
335
+ max_write_raw = config.get("max_write_size")
336
+ if max_write_raw is not None:
337
+ if isinstance(max_write_raw, int) and max_write_raw > 0:
338
+ self._max_write_size = max_write_raw
339
+ else:
340
+ logger.warning(
341
+ "Invalid max_write_size config value ignored, using default",
342
+ extra={
343
+ "provided_value": max_write_raw,
344
+ "default_value": self._max_write_size,
345
+ },
346
+ )
347
+
348
+ # Initialize circuit breaker for resilient I/O operations
349
+ self._init_circuit_breaker(
350
+ threshold=5,
351
+ reset_timeout=60.0,
352
+ service_name="filesystem_handler",
353
+ transport_type=EnumInfraTransportType.FILESYSTEM,
354
+ )
355
+
356
+ self._initialized = True
357
+
358
+ logger.info(
359
+ "%s initialized successfully",
360
+ self.__class__.__name__,
361
+ extra={
362
+ "handler": self.__class__.__name__,
363
+ "allowed_paths_count": len(self._allowed_paths),
364
+ "max_read_size_bytes": self._max_read_size,
365
+ "max_write_size_bytes": self._max_write_size,
366
+ "correlation_id": str(init_correlation_id),
367
+ },
368
+ )
369
+
370
+ async def shutdown(self) -> None:
371
+ """Shutdown filesystem handler and clear configuration."""
372
+ self._allowed_paths = ()
373
+ self._initialized = False
374
+ logger.info("HandlerFileSystem shutdown complete")
375
+
376
+ def _validate_path_in_whitelist(
377
+ self, path: Path, correlation_id: UUID, operation: str
378
+ ) -> Path:
379
+ """Validate that path is within allowed directories.
380
+
381
+ This method resolves the path to its canonical form (following symlinks)
382
+ and verifies it is within one of the allowed directories.
383
+
384
+ Args:
385
+ path: Path to validate
386
+ correlation_id: Correlation ID for error context
387
+ operation: Operation name for error context
388
+
389
+ Returns:
390
+ The resolved canonical path
391
+
392
+ Raises:
393
+ ProtocolConfigurationError: If path is outside allowed directories
394
+ or symlink points outside allowed directories
395
+ """
396
+ ctx = ModelInfraErrorContext(
397
+ transport_type=EnumInfraTransportType.FILESYSTEM,
398
+ operation=operation,
399
+ target_name=str(path),
400
+ correlation_id=correlation_id,
401
+ )
402
+
403
+ # Resolve to canonical path using Path.resolve()
404
+ # Note: In Python 3.6+, resolve() defaults to strict=False, meaning it
405
+ # works for both existing and non-existing paths by resolving as much
406
+ # of the path as possible without requiring the full path to exist.
407
+ try:
408
+ resolved = path.resolve()
409
+ except OSError as e:
410
+ raise ProtocolConfigurationError(
411
+ f"Cannot resolve path: {e}",
412
+ context=ctx,
413
+ ) from e
414
+
415
+ # Check if resolved path is within any allowed directory
416
+ for allowed in self._allowed_paths:
417
+ try:
418
+ resolved.relative_to(allowed)
419
+ return resolved
420
+ except ValueError:
421
+ continue
422
+
423
+ # Path is not within any allowed directory
424
+ raise ProtocolConfigurationError(
425
+ f"Path '{path}' is outside allowed directories - access denied",
426
+ context=ctx,
427
+ )
428
+
429
+ def _validate_symlink_target(
430
+ self, path: Path, correlation_id: UUID, operation: str
431
+ ) -> None:
432
+ """Validate symlink target is within allowed directories.
433
+
434
+ If the path is a symlink, this method validates that the target
435
+ is within the allowed directories to prevent symlink escape attacks.
436
+
437
+ Args:
438
+ path: Path that may be a symlink
439
+ correlation_id: Correlation ID for error context
440
+ operation: Operation name for error context
441
+
442
+ Raises:
443
+ ProtocolConfigurationError: If symlink target is outside allowed directories
444
+ """
445
+ if not path.is_symlink():
446
+ return
447
+
448
+ ctx = ModelInfraErrorContext(
449
+ transport_type=EnumInfraTransportType.FILESYSTEM,
450
+ operation=operation,
451
+ target_name=str(path),
452
+ correlation_id=correlation_id,
453
+ )
454
+
455
+ try:
456
+ target = path.resolve()
457
+ except OSError as e:
458
+ raise ProtocolConfigurationError(
459
+ f"Cannot resolve symlink target: {e}",
460
+ context=ctx,
461
+ ) from e
462
+
463
+ # Verify symlink target is within allowed directories
464
+ for allowed in self._allowed_paths:
465
+ try:
466
+ target.relative_to(allowed)
467
+ return
468
+ except ValueError:
469
+ continue
470
+
471
+ raise ProtocolConfigurationError(
472
+ f"Symlink '{path}' points outside allowed directories - access denied",
473
+ context=ctx,
474
+ )
475
+
476
+ async def execute(
477
+ self, envelope: dict[str, object]
478
+ ) -> ModelHandlerOutput[dict[str, object]]:
479
+ """Execute filesystem operation from envelope.
480
+
481
+ Args:
482
+ envelope: Request envelope containing:
483
+ - operation: One of the supported filesystem operations
484
+ - payload: Operation-specific payload
485
+ - correlation_id: Optional correlation ID for tracing
486
+ - envelope_id: Optional envelope ID for causality tracking
487
+
488
+ Returns:
489
+ ModelHandlerOutput[dict[str, object]] containing operation result
490
+
491
+ Raises:
492
+ RuntimeHostError: If handler not initialized
493
+ ProtocolConfigurationError: If operation or payload is invalid
494
+ """
495
+ correlation_id = self._extract_correlation_id(envelope)
496
+ input_envelope_id = self._extract_envelope_id(envelope)
497
+
498
+ if not self._initialized:
499
+ ctx = ModelInfraErrorContext(
500
+ transport_type=EnumInfraTransportType.FILESYSTEM,
501
+ operation="execute",
502
+ target_name="filesystem_handler",
503
+ correlation_id=correlation_id,
504
+ )
505
+ raise RuntimeHostError(
506
+ "HandlerFileSystem not initialized. Call initialize() first.",
507
+ context=ctx,
508
+ )
509
+
510
+ operation = envelope.get("operation")
511
+ if not isinstance(operation, str):
512
+ ctx = ModelInfraErrorContext(
513
+ transport_type=EnumInfraTransportType.FILESYSTEM,
514
+ operation="execute",
515
+ target_name="filesystem_handler",
516
+ correlation_id=correlation_id,
517
+ )
518
+ raise ProtocolConfigurationError(
519
+ "Missing or invalid 'operation' in envelope", context=ctx
520
+ )
521
+
522
+ if operation not in _SUPPORTED_OPERATIONS:
523
+ ctx = ModelInfraErrorContext(
524
+ transport_type=EnumInfraTransportType.FILESYSTEM,
525
+ operation=operation,
526
+ target_name="filesystem_handler",
527
+ correlation_id=correlation_id,
528
+ )
529
+ raise ProtocolConfigurationError(
530
+ f"Operation '{operation}' not supported. Available: {', '.join(sorted(_SUPPORTED_OPERATIONS))}",
531
+ context=ctx,
532
+ )
533
+
534
+ payload = envelope.get("payload")
535
+ if not isinstance(payload, dict):
536
+ ctx = ModelInfraErrorContext(
537
+ transport_type=EnumInfraTransportType.FILESYSTEM,
538
+ operation=operation,
539
+ target_name="filesystem_handler",
540
+ correlation_id=correlation_id,
541
+ )
542
+ raise ProtocolConfigurationError(
543
+ "Missing or invalid 'payload' in envelope", context=ctx
544
+ )
545
+
546
+ # Route to appropriate operation handler
547
+ if operation == "filesystem.read_file":
548
+ return await self._execute_read_file(
549
+ payload, correlation_id, input_envelope_id
550
+ )
551
+ elif operation == "filesystem.write_file":
552
+ return await self._execute_write_file(
553
+ payload, correlation_id, input_envelope_id
554
+ )
555
+ elif operation == "filesystem.list_directory":
556
+ return await self._execute_list_directory(
557
+ payload, correlation_id, input_envelope_id
558
+ )
559
+ elif operation == "filesystem.ensure_directory":
560
+ return await self._execute_ensure_directory(
561
+ payload, correlation_id, input_envelope_id
562
+ )
563
+ else: # filesystem.delete_file
564
+ return await self._execute_delete_file(
565
+ payload, correlation_id, input_envelope_id
566
+ )
567
+
568
+ async def _execute_read_file(
569
+ self,
570
+ payload: dict[str, object],
571
+ correlation_id: UUID,
572
+ input_envelope_id: UUID,
573
+ ) -> ModelHandlerOutput[dict[str, object]]:
574
+ """Execute filesystem.read_file operation.
575
+
576
+ Payload:
577
+ - path: str (required) - File path to read
578
+ - binary: bool (optional, default False) - Read as binary (returns base64)
579
+ - encoding: str (optional, default "utf-8") - Text encoding
580
+
581
+ Returns:
582
+ Result with content, size, path, and binary flag.
583
+ For binary=True, content is base64-encoded string.
584
+ For binary=False, content is the text string.
585
+
586
+ Raises:
587
+ InfraConnectionError: If file not found or read fails
588
+ InfraUnavailableError: If file size exceeds limit or circuit breaker is open
589
+ """
590
+ operation = "filesystem.read_file"
591
+
592
+ # Extract path (required)
593
+ path_raw = payload.get("path")
594
+ if not isinstance(path_raw, str) or not path_raw:
595
+ ctx = ModelInfraErrorContext(
596
+ transport_type=EnumInfraTransportType.FILESYSTEM,
597
+ operation=operation,
598
+ target_name="filesystem_handler",
599
+ correlation_id=correlation_id,
600
+ )
601
+ raise ProtocolConfigurationError(
602
+ "Missing or invalid 'path' in payload", context=ctx
603
+ )
604
+
605
+ path = Path(path_raw)
606
+ resolved_path = self._validate_path_in_whitelist(
607
+ path, correlation_id, operation
608
+ )
609
+ self._validate_symlink_target(resolved_path, correlation_id, operation)
610
+
611
+ # Extract options
612
+ binary = payload.get("binary", False)
613
+ if not isinstance(binary, bool):
614
+ logger.warning(
615
+ "Invalid binary parameter type ignored, using default",
616
+ extra={
617
+ "provided_value": binary,
618
+ "provided_type": type(binary).__name__,
619
+ "default_value": False,
620
+ "correlation_id": str(correlation_id),
621
+ },
622
+ )
623
+ binary = False
624
+
625
+ encoding = payload.get("encoding", "utf-8")
626
+ if not isinstance(encoding, str):
627
+ logger.warning(
628
+ "Invalid encoding parameter type ignored, using default",
629
+ extra={
630
+ "provided_value": encoding,
631
+ "provided_type": type(encoding).__name__,
632
+ "default_value": "utf-8",
633
+ "correlation_id": str(correlation_id),
634
+ },
635
+ )
636
+ encoding = "utf-8"
637
+
638
+ ctx = ModelInfraErrorContext(
639
+ transport_type=EnumInfraTransportType.FILESYSTEM,
640
+ operation=operation,
641
+ target_name=str(resolved_path),
642
+ correlation_id=correlation_id,
643
+ )
644
+
645
+ # Check circuit breaker before I/O operation
646
+ async with self._circuit_breaker_lock:
647
+ await self._check_circuit_breaker(operation, correlation_id)
648
+
649
+ try:
650
+ # Check file exists
651
+ if not resolved_path.exists():
652
+ raise InfraConnectionError(
653
+ f"File not found: {resolved_path.name}",
654
+ context=ctx,
655
+ )
656
+
657
+ if not resolved_path.is_file():
658
+ raise InfraConnectionError(
659
+ f"Path is not a file: {resolved_path.name}",
660
+ context=ctx,
661
+ )
662
+
663
+ # Check file size before reading
664
+ try:
665
+ file_size = resolved_path.stat().st_size
666
+ except OSError as e:
667
+ raise InfraConnectionError(
668
+ f"Cannot stat file: {e}",
669
+ context=ctx,
670
+ ) from e
671
+
672
+ if file_size > self._max_read_size:
673
+ raise InfraUnavailableError(
674
+ f"File size ({_categorize_size(file_size)}) exceeds configured read limit",
675
+ context=ctx,
676
+ )
677
+
678
+ # Read file content
679
+ content: str
680
+ try:
681
+ if binary:
682
+ raw_bytes = resolved_path.read_bytes()
683
+ # Encode bytes as base64 string for JSON safety
684
+ content = base64.b64encode(raw_bytes).decode("ascii")
685
+ else:
686
+ content = resolved_path.read_text(encoding=encoding)
687
+ except OSError as e:
688
+ raise InfraConnectionError(
689
+ f"Failed to read file: {e}",
690
+ context=ctx,
691
+ ) from e
692
+ except UnicodeDecodeError as e:
693
+ raise InfraConnectionError(
694
+ f"Failed to decode file with encoding '{encoding}': {e}",
695
+ context=ctx,
696
+ ) from e
697
+
698
+ # Reset circuit breaker on success
699
+ async with self._circuit_breaker_lock:
700
+ await self._reset_circuit_breaker()
701
+
702
+ logger.debug(
703
+ "File read successfully",
704
+ extra={
705
+ "path": str(resolved_path),
706
+ "size_category": _categorize_size(file_size),
707
+ "binary": binary,
708
+ "correlation_id": str(correlation_id),
709
+ },
710
+ )
711
+
712
+ return ModelHandlerOutput.for_compute(
713
+ input_envelope_id=input_envelope_id,
714
+ correlation_id=correlation_id,
715
+ handler_id=HANDLER_ID_FILESYSTEM,
716
+ result={
717
+ "status": "success",
718
+ "payload": {
719
+ "content": content,
720
+ "size": file_size,
721
+ "path": str(resolved_path),
722
+ "binary": binary,
723
+ },
724
+ "correlation_id": str(correlation_id),
725
+ },
726
+ )
727
+
728
+ except (InfraConnectionError, InfraUnavailableError):
729
+ # Record failure for circuit breaker (infra-level failures only)
730
+ async with self._circuit_breaker_lock:
731
+ await self._record_circuit_failure(operation, correlation_id)
732
+ raise
733
+
734
+ async def _execute_write_file(
735
+ self,
736
+ payload: dict[str, object],
737
+ correlation_id: UUID,
738
+ input_envelope_id: UUID,
739
+ ) -> ModelHandlerOutput[dict[str, object]]:
740
+ """Execute filesystem.write_file operation.
741
+
742
+ Payload:
743
+ - path: str (required) - File path to write
744
+ - content: str (required) - Content to write.
745
+ For binary=True, content should be base64-encoded string.
746
+ - binary: bool (optional, default False) - Write as binary (expects base64 content)
747
+ - create_dirs: bool (optional, default False) - Create parent directories
748
+
749
+ Returns:
750
+ Result with path, bytes_written, and created flag
751
+
752
+ Raises:
753
+ InfraConnectionError: If write fails
754
+ InfraUnavailableError: If content size exceeds limit or circuit breaker is open
755
+ ProtocolConfigurationError: If base64 decoding fails for binary mode
756
+ """
757
+ operation = "filesystem.write_file"
758
+
759
+ # Extract path (required)
760
+ path_raw = payload.get("path")
761
+ if not isinstance(path_raw, str) or not path_raw:
762
+ ctx = ModelInfraErrorContext(
763
+ transport_type=EnumInfraTransportType.FILESYSTEM,
764
+ operation=operation,
765
+ target_name="filesystem_handler",
766
+ correlation_id=correlation_id,
767
+ )
768
+ raise ProtocolConfigurationError(
769
+ "Missing or invalid 'path' in payload", context=ctx
770
+ )
771
+
772
+ # Extract content (required)
773
+ content_raw = payload.get("content")
774
+ if content_raw is None:
775
+ ctx = ModelInfraErrorContext(
776
+ transport_type=EnumInfraTransportType.FILESYSTEM,
777
+ operation=operation,
778
+ target_name="filesystem_handler",
779
+ correlation_id=correlation_id,
780
+ )
781
+ raise ProtocolConfigurationError(
782
+ "Missing 'content' in payload", context=ctx
783
+ )
784
+
785
+ # Extract binary flag (consistent with read_file API)
786
+ binary = payload.get("binary", False)
787
+ if not isinstance(binary, bool):
788
+ logger.warning(
789
+ "Invalid binary parameter type ignored, using default",
790
+ extra={
791
+ "provided_value": binary,
792
+ "provided_type": type(binary).__name__,
793
+ "default_value": False,
794
+ "correlation_id": str(correlation_id),
795
+ },
796
+ )
797
+ binary = False
798
+
799
+ ctx = ModelInfraErrorContext(
800
+ transport_type=EnumInfraTransportType.FILESYSTEM,
801
+ operation=operation,
802
+ target_name=str(path_raw),
803
+ correlation_id=correlation_id,
804
+ )
805
+
806
+ # Process content based on binary flag
807
+ content_bytes: bytes
808
+ content_str: str
809
+
810
+ if binary:
811
+ # Binary mode: expect base64-encoded string or raw bytes
812
+ if isinstance(content_raw, str):
813
+ try:
814
+ content_bytes = base64.b64decode(content_raw, validate=True)
815
+ except binascii.Error as e:
816
+ raise ProtocolConfigurationError(
817
+ f"Invalid base64 content for binary mode: {e}",
818
+ context=ctx,
819
+ ) from e
820
+ elif isinstance(content_raw, bytes):
821
+ content_bytes = content_raw
822
+ else:
823
+ raise ProtocolConfigurationError(
824
+ f"Invalid content type for binary mode: expected str (base64) or bytes, got {type(content_raw).__name__}",
825
+ context=ctx,
826
+ )
827
+ content_size = len(content_bytes)
828
+ else:
829
+ # Text mode: expect string
830
+ if isinstance(content_raw, str):
831
+ content_str = content_raw
832
+ elif isinstance(content_raw, bytes):
833
+ try:
834
+ content_str = content_raw.decode("utf-8")
835
+ except UnicodeDecodeError as e:
836
+ raise ProtocolConfigurationError(
837
+ "Invalid UTF-8 bytes for text content",
838
+ context=ctx,
839
+ ) from e
840
+ else:
841
+ raise ProtocolConfigurationError(
842
+ f"Invalid content type: expected str or bytes, got {type(content_raw).__name__}",
843
+ context=ctx,
844
+ )
845
+ content_size = len(content_str.encode("utf-8"))
846
+
847
+ create_dirs = payload.get("create_dirs", False)
848
+ if not isinstance(create_dirs, bool):
849
+ create_dirs = False
850
+
851
+ path = Path(path_raw)
852
+
853
+ # For write operations, validate parent directory is in whitelist
854
+ parent = path.parent
855
+ if parent.exists():
856
+ self._validate_path_in_whitelist(parent, correlation_id, operation)
857
+ else:
858
+ # For non-existent parents, validate the path structure
859
+ # Find the first existing ancestor and validate from there
860
+ current = parent
861
+ while not current.exists() and current != current.parent:
862
+ current = current.parent
863
+
864
+ if current.exists():
865
+ self._validate_path_in_whitelist(current, correlation_id, operation)
866
+ else:
867
+ raise ProtocolConfigurationError(
868
+ f"Path '{path}' is outside allowed directories - access denied",
869
+ context=ctx,
870
+ )
871
+
872
+ # Compute resolved_path for validation and return value, but preserve
873
+ # write_path for the actual write operation to enable O_NOFOLLOW check.
874
+ # We use parent.resolve() / name to avoid resolving symlinks in the final component.
875
+ resolved_parent = path.parent.resolve()
876
+ write_path = resolved_parent / path.name
877
+ resolved_path = path.resolve() if path.exists() else write_path
878
+
879
+ # Check circuit breaker before I/O operation
880
+ async with self._circuit_breaker_lock:
881
+ await self._check_circuit_breaker(operation, correlation_id)
882
+
883
+ try:
884
+ # Check content size
885
+ if content_size > self._max_write_size:
886
+ raise InfraUnavailableError(
887
+ f"Content size ({_categorize_size(content_size)}) exceeds configured write limit",
888
+ context=ctx,
889
+ )
890
+
891
+ # Check if file exists (for return value)
892
+ file_existed = write_path.exists()
893
+
894
+ # Create parent directories if requested
895
+ if create_dirs and not resolved_parent.exists():
896
+ try:
897
+ resolved_parent.mkdir(parents=True, exist_ok=True)
898
+ except OSError as e:
899
+ raise InfraConnectionError(
900
+ f"Failed to create parent directories: {e}",
901
+ context=ctx,
902
+ ) from e
903
+
904
+ # Check if symlink exists and validate target is within allowed paths.
905
+ # This provides a helpful error message for symlinks pointing outside.
906
+ # The O_NOFOLLOW check below will reject ALL symlinks for security.
907
+ if write_path.is_symlink():
908
+ self._validate_symlink_target(write_path, correlation_id, operation)
909
+
910
+ # Write file content using O_NOFOLLOW to prevent symlink following.
911
+ # This eliminates the TOCTOU race condition where an attacker could
912
+ # replace the file with a symlink between validation and write.
913
+ # We use write_path (not resolved_path) to detect symlinks.
914
+ try:
915
+ # O_NOFOLLOW causes the open to fail with ELOOP if the path is a symlink
916
+ # This is atomic and cannot be raced, unlike checking is_symlink() first
917
+ flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | os.O_NOFOLLOW
918
+ fd = os.open(str(write_path), flags, 0o644)
919
+ try:
920
+ if binary:
921
+ with os.fdopen(fd, "wb") as f:
922
+ f.write(content_bytes)
923
+ else:
924
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
925
+ f.write(content_str)
926
+ except Exception:
927
+ # fdopen takes ownership of fd, but if write fails after fdopen
928
+ # the context manager will close it. Re-raise to outer handler.
929
+ raise
930
+ except OSError as e:
931
+ if e.errno == errno.ELOOP:
932
+ # ELOOP indicates the path is a symlink - reject the write
933
+ raise ProtocolConfigurationError(
934
+ f"Cannot write to symlink: {write_path.name}",
935
+ context=ctx,
936
+ ) from e
937
+ raise InfraConnectionError(
938
+ f"Failed to write file: {e}",
939
+ context=ctx,
940
+ ) from e
941
+
942
+ # Reset circuit breaker on success
943
+ async with self._circuit_breaker_lock:
944
+ await self._reset_circuit_breaker()
945
+
946
+ logger.debug(
947
+ "File written successfully",
948
+ extra={
949
+ "path": str(resolved_path),
950
+ "size_category": _categorize_size(content_size),
951
+ "file_created": not file_existed,
952
+ "correlation_id": str(correlation_id),
953
+ },
954
+ )
955
+
956
+ return ModelHandlerOutput.for_compute(
957
+ input_envelope_id=input_envelope_id,
958
+ correlation_id=correlation_id,
959
+ handler_id=HANDLER_ID_FILESYSTEM,
960
+ result={
961
+ "status": "success",
962
+ "payload": {
963
+ "path": str(resolved_path),
964
+ "bytes_written": content_size,
965
+ "created": not file_existed,
966
+ },
967
+ "correlation_id": str(correlation_id),
968
+ },
969
+ )
970
+
971
+ except (InfraConnectionError, InfraUnavailableError):
972
+ # Record failure for circuit breaker (infra-level failures only)
973
+ async with self._circuit_breaker_lock:
974
+ await self._record_circuit_failure(operation, correlation_id)
975
+ raise
976
+
977
+ async def _execute_list_directory(
978
+ self,
979
+ payload: dict[str, object],
980
+ correlation_id: UUID,
981
+ input_envelope_id: UUID,
982
+ ) -> ModelHandlerOutput[dict[str, object]]:
983
+ """Execute filesystem.list_directory operation.
984
+
985
+ Payload:
986
+ - path: str (required) - Directory path to list
987
+ - recursive: bool (optional, default False) - List recursively
988
+ - pattern: str (optional) - Glob pattern to filter entries
989
+
990
+ Returns:
991
+ Result with entries list, count, and path
992
+
993
+ Raises:
994
+ InfraConnectionError: If directory not found or list fails
995
+ InfraUnavailableError: If circuit breaker is open
996
+ """
997
+ operation = "filesystem.list_directory"
998
+
999
+ # Extract path (required)
1000
+ path_raw = payload.get("path")
1001
+ if not isinstance(path_raw, str) or not path_raw:
1002
+ ctx = ModelInfraErrorContext(
1003
+ transport_type=EnumInfraTransportType.FILESYSTEM,
1004
+ operation=operation,
1005
+ target_name="filesystem_handler",
1006
+ correlation_id=correlation_id,
1007
+ )
1008
+ raise ProtocolConfigurationError(
1009
+ "Missing or invalid 'path' in payload", context=ctx
1010
+ )
1011
+
1012
+ path = Path(path_raw)
1013
+ resolved_path = self._validate_path_in_whitelist(
1014
+ path, correlation_id, operation
1015
+ )
1016
+ self._validate_symlink_target(resolved_path, correlation_id, operation)
1017
+
1018
+ ctx = ModelInfraErrorContext(
1019
+ transport_type=EnumInfraTransportType.FILESYSTEM,
1020
+ operation=operation,
1021
+ target_name=str(resolved_path),
1022
+ correlation_id=correlation_id,
1023
+ )
1024
+
1025
+ # Extract options
1026
+ recursive = payload.get("recursive", False)
1027
+ if not isinstance(recursive, bool):
1028
+ recursive = False
1029
+
1030
+ pattern = payload.get("pattern")
1031
+ if pattern is not None and not isinstance(pattern, str):
1032
+ pattern = None
1033
+
1034
+ # Check circuit breaker before I/O operation
1035
+ async with self._circuit_breaker_lock:
1036
+ await self._check_circuit_breaker(operation, correlation_id)
1037
+
1038
+ try:
1039
+ # Check directory exists
1040
+ if not resolved_path.exists():
1041
+ raise InfraConnectionError(
1042
+ f"Directory not found: {resolved_path.name}",
1043
+ context=ctx,
1044
+ )
1045
+
1046
+ if not resolved_path.is_dir():
1047
+ raise InfraConnectionError(
1048
+ f"Path is not a directory: {resolved_path.name}",
1049
+ context=ctx,
1050
+ )
1051
+
1052
+ # List directory contents
1053
+ entries: list[dict[str, object]] = []
1054
+ try:
1055
+ if recursive:
1056
+ iterator = resolved_path.rglob("*")
1057
+ else:
1058
+ iterator = resolved_path.iterdir()
1059
+
1060
+ for entry in iterator:
1061
+ # Apply pattern filter if specified
1062
+ if pattern and not fnmatch.fnmatch(entry.name, pattern):
1063
+ continue
1064
+
1065
+ # Get entry metadata - use lstat() to not follow symlinks
1066
+ # This prevents exposing metadata from files outside the whitelist
1067
+ try:
1068
+ is_symlink = entry.is_symlink()
1069
+
1070
+ # For symlinks, check if target is within allowed paths
1071
+ # Skip symlinks pointing outside allowed directories to prevent
1072
+ # information disclosure about files outside the whitelist
1073
+ if is_symlink:
1074
+ try:
1075
+ resolved_target = entry.resolve()
1076
+ # Check if target is within any allowed directory
1077
+ target_allowed = False
1078
+ for allowed in self._allowed_paths:
1079
+ try:
1080
+ resolved_target.relative_to(allowed)
1081
+ target_allowed = True
1082
+ break
1083
+ except ValueError:
1084
+ continue
1085
+ if not target_allowed:
1086
+ # Skip symlinks pointing outside allowed paths
1087
+ continue
1088
+ except OSError:
1089
+ # Skip broken or unresolvable symlinks
1090
+ continue
1091
+
1092
+ # Use lstat() to get symlink's own metadata, not target's
1093
+ stat_info = entry.lstat()
1094
+
1095
+ # For is_file/is_dir, report the actual entry type
1096
+ # If it's a symlink, is_file()/is_dir() follow the link,
1097
+ # so we report based on the symlink itself
1098
+ entry_data: dict[str, object] = {
1099
+ "name": entry.name,
1100
+ "path": str(entry),
1101
+ "is_file": entry.is_file() and not is_symlink,
1102
+ "is_dir": entry.is_dir() and not is_symlink,
1103
+ "is_symlink": is_symlink,
1104
+ "size": stat_info.st_size,
1105
+ "modified": stat_info.st_mtime,
1106
+ }
1107
+ entries.append(entry_data)
1108
+ except OSError:
1109
+ # Skip entries we can't stat
1110
+ continue
1111
+
1112
+ except OSError as e:
1113
+ raise InfraConnectionError(
1114
+ f"Failed to list directory: {e}",
1115
+ context=ctx,
1116
+ ) from e
1117
+
1118
+ # Reset circuit breaker on success
1119
+ async with self._circuit_breaker_lock:
1120
+ await self._reset_circuit_breaker()
1121
+
1122
+ logger.debug(
1123
+ "Directory listed successfully",
1124
+ extra={
1125
+ "path": str(resolved_path),
1126
+ "entry_count": len(entries),
1127
+ "recursive": recursive,
1128
+ "pattern": pattern,
1129
+ "correlation_id": str(correlation_id),
1130
+ },
1131
+ )
1132
+
1133
+ return ModelHandlerOutput.for_compute(
1134
+ input_envelope_id=input_envelope_id,
1135
+ correlation_id=correlation_id,
1136
+ handler_id=HANDLER_ID_FILESYSTEM,
1137
+ result={
1138
+ "status": "success",
1139
+ "payload": {
1140
+ "entries": entries,
1141
+ "count": len(entries),
1142
+ "path": str(resolved_path),
1143
+ },
1144
+ "correlation_id": str(correlation_id),
1145
+ },
1146
+ )
1147
+
1148
+ except (InfraConnectionError, InfraUnavailableError):
1149
+ # Record failure for circuit breaker (infra-level failures only)
1150
+ async with self._circuit_breaker_lock:
1151
+ await self._record_circuit_failure(operation, correlation_id)
1152
+ raise
1153
+
1154
+ async def _execute_ensure_directory(
1155
+ self,
1156
+ payload: dict[str, object],
1157
+ correlation_id: UUID,
1158
+ input_envelope_id: UUID,
1159
+ ) -> ModelHandlerOutput[dict[str, object]]:
1160
+ """Execute filesystem.ensure_directory operation.
1161
+
1162
+ Payload:
1163
+ - path: str (required) - Directory path to create
1164
+ - exist_ok: bool (optional, default True) - Don't error if exists
1165
+
1166
+ Returns:
1167
+ Result with path, created, and already_existed flags
1168
+
1169
+ Raises:
1170
+ InfraConnectionError: If directory creation fails
1171
+ InfraUnavailableError: If circuit breaker is open
1172
+ """
1173
+ operation = "filesystem.ensure_directory"
1174
+
1175
+ # Extract path (required)
1176
+ path_raw = payload.get("path")
1177
+ if not isinstance(path_raw, str) or not path_raw:
1178
+ ctx = ModelInfraErrorContext(
1179
+ transport_type=EnumInfraTransportType.FILESYSTEM,
1180
+ operation=operation,
1181
+ target_name="filesystem_handler",
1182
+ correlation_id=correlation_id,
1183
+ )
1184
+ raise ProtocolConfigurationError(
1185
+ "Missing or invalid 'path' in payload", context=ctx
1186
+ )
1187
+
1188
+ path = Path(path_raw)
1189
+
1190
+ # Validate the target path is within allowed directories
1191
+ # _validate_path_in_whitelist handles non-existent paths via resolve()
1192
+ self._validate_path_in_whitelist(path, correlation_id, operation)
1193
+
1194
+ ctx = ModelInfraErrorContext(
1195
+ transport_type=EnumInfraTransportType.FILESYSTEM,
1196
+ operation=operation,
1197
+ target_name=str(path),
1198
+ correlation_id=correlation_id,
1199
+ )
1200
+
1201
+ # Extract options
1202
+ exist_ok = payload.get("exist_ok", True)
1203
+ if not isinstance(exist_ok, bool):
1204
+ exist_ok = True
1205
+
1206
+ # Check circuit breaker before I/O operation
1207
+ async with self._circuit_breaker_lock:
1208
+ await self._check_circuit_breaker(operation, correlation_id)
1209
+
1210
+ try:
1211
+ # Check if already exists
1212
+ already_existed = path.exists()
1213
+
1214
+ if already_existed and not path.is_dir():
1215
+ raise InfraConnectionError(
1216
+ f"Path exists but is not a directory: {path.name}",
1217
+ context=ctx,
1218
+ )
1219
+
1220
+ # If directory exists and exist_ok=False, raise error
1221
+ if already_existed and not exist_ok:
1222
+ raise InfraConnectionError(
1223
+ f"Directory already exists: {path.name}",
1224
+ context=ctx,
1225
+ )
1226
+
1227
+ # Create directory
1228
+ created = False
1229
+ if not already_existed:
1230
+ try:
1231
+ # Final symlink check immediately before I/O to minimize TOCTOU window.
1232
+ # An attacker could create a symlink at the target path between earlier
1233
+ # validation and this point. Re-checking here reduces the window to
1234
+ # the minimum possible (between this check and the actual mkdir).
1235
+ # For mkdir, we also check parent directories in case a symlink was
1236
+ # inserted in the path hierarchy.
1237
+ if path.is_symlink():
1238
+ self._validate_symlink_target(path, correlation_id, operation)
1239
+ # Also check if any parent became a symlink
1240
+ for parent in path.parents:
1241
+ if parent.is_symlink():
1242
+ self._validate_symlink_target(
1243
+ parent, correlation_id, operation
1244
+ )
1245
+ # Stop at allowed paths boundary
1246
+ if parent in self._allowed_paths:
1247
+ break
1248
+
1249
+ path.mkdir(parents=True, exist_ok=exist_ok)
1250
+ created = True
1251
+ except FileExistsError:
1252
+ # FileExistsError is only raised when exist_ok=False
1253
+ # (when exist_ok=True, mkdir() silently succeeds)
1254
+ raise InfraConnectionError(
1255
+ f"Directory already exists: {path.name}",
1256
+ context=ctx,
1257
+ ) from None
1258
+ except OSError as e:
1259
+ raise InfraConnectionError(
1260
+ f"Failed to create directory: {e}",
1261
+ context=ctx,
1262
+ ) from e
1263
+
1264
+ resolved_path = path.resolve()
1265
+
1266
+ # Reset circuit breaker on success
1267
+ async with self._circuit_breaker_lock:
1268
+ await self._reset_circuit_breaker()
1269
+
1270
+ logger.debug(
1271
+ "Directory ensured",
1272
+ extra={
1273
+ "path": str(resolved_path),
1274
+ "dir_created": created,
1275
+ "already_existed": already_existed,
1276
+ "correlation_id": str(correlation_id),
1277
+ },
1278
+ )
1279
+
1280
+ return ModelHandlerOutput.for_compute(
1281
+ input_envelope_id=input_envelope_id,
1282
+ correlation_id=correlation_id,
1283
+ handler_id=HANDLER_ID_FILESYSTEM,
1284
+ result={
1285
+ "status": "success",
1286
+ "payload": {
1287
+ "path": str(resolved_path),
1288
+ "created": created,
1289
+ "already_existed": already_existed,
1290
+ },
1291
+ "correlation_id": str(correlation_id),
1292
+ },
1293
+ )
1294
+
1295
+ except (InfraConnectionError, InfraUnavailableError):
1296
+ # Record failure for circuit breaker (infra-level failures only)
1297
+ async with self._circuit_breaker_lock:
1298
+ await self._record_circuit_failure(operation, correlation_id)
1299
+ raise
1300
+
1301
+ async def _execute_delete_file(
1302
+ self,
1303
+ payload: dict[str, object],
1304
+ correlation_id: UUID,
1305
+ input_envelope_id: UUID,
1306
+ ) -> ModelHandlerOutput[dict[str, object]]:
1307
+ """Execute filesystem.delete_file operation.
1308
+
1309
+ Payload:
1310
+ - path: str (required) - File path to delete
1311
+ - missing_ok: bool (optional, default False) - Don't error if missing
1312
+
1313
+ Returns:
1314
+ Result with path, deleted, and was_missing flags
1315
+
1316
+ Raises:
1317
+ InfraConnectionError: If delete fails or file not found (when missing_ok=False)
1318
+ InfraUnavailableError: If circuit breaker is open
1319
+ """
1320
+ operation = "filesystem.delete_file"
1321
+
1322
+ # Extract path (required)
1323
+ path_raw = payload.get("path")
1324
+ if not isinstance(path_raw, str) or not path_raw:
1325
+ ctx = ModelInfraErrorContext(
1326
+ transport_type=EnumInfraTransportType.FILESYSTEM,
1327
+ operation=operation,
1328
+ target_name="filesystem_handler",
1329
+ correlation_id=correlation_id,
1330
+ )
1331
+ raise ProtocolConfigurationError(
1332
+ "Missing or invalid 'path' in payload", context=ctx
1333
+ )
1334
+
1335
+ path = Path(path_raw)
1336
+
1337
+ # For delete, we need to validate the parent directory is in whitelist
1338
+ if path.parent.exists():
1339
+ self._validate_path_in_whitelist(path.parent, correlation_id, operation)
1340
+ else:
1341
+ ctx = ModelInfraErrorContext(
1342
+ transport_type=EnumInfraTransportType.FILESYSTEM,
1343
+ operation=operation,
1344
+ target_name=str(path),
1345
+ correlation_id=correlation_id,
1346
+ )
1347
+ raise ProtocolConfigurationError(
1348
+ f"Path '{path}' is outside allowed directories - access denied",
1349
+ context=ctx,
1350
+ )
1351
+
1352
+ ctx = ModelInfraErrorContext(
1353
+ transport_type=EnumInfraTransportType.FILESYSTEM,
1354
+ operation=operation,
1355
+ target_name=str(path),
1356
+ correlation_id=correlation_id,
1357
+ )
1358
+
1359
+ # Extract options
1360
+ missing_ok = payload.get("missing_ok", False)
1361
+ if not isinstance(missing_ok, bool):
1362
+ missing_ok = False
1363
+
1364
+ # Check circuit breaker before I/O operation
1365
+ async with self._circuit_breaker_lock:
1366
+ await self._check_circuit_breaker(operation, correlation_id)
1367
+
1368
+ try:
1369
+ # Check if file exists
1370
+ was_missing = not path.exists()
1371
+
1372
+ if was_missing:
1373
+ if not missing_ok:
1374
+ raise InfraConnectionError(
1375
+ f"File not found: {path.name}",
1376
+ context=ctx,
1377
+ )
1378
+ resolved_path = path.parent.resolve() / path.name
1379
+ deleted = False
1380
+ else:
1381
+ # Validate full path and symlink
1382
+ resolved_path = self._validate_path_in_whitelist(
1383
+ path, correlation_id, operation
1384
+ )
1385
+ self._validate_symlink_target(resolved_path, correlation_id, operation)
1386
+
1387
+ if resolved_path.is_dir():
1388
+ raise InfraConnectionError(
1389
+ f"Path is a directory, use rmdir for directories: {path.name}",
1390
+ context=ctx,
1391
+ )
1392
+
1393
+ # Delete file
1394
+ try:
1395
+ # Final symlink check immediately before I/O to minimize TOCTOU window.
1396
+ # An attacker could replace the file with a symlink between earlier
1397
+ # validation and this point. Re-checking here reduces the window to
1398
+ # the minimum possible (between this check and the actual unlink).
1399
+ if resolved_path.is_symlink():
1400
+ self._validate_symlink_target(
1401
+ resolved_path, correlation_id, operation
1402
+ )
1403
+
1404
+ resolved_path.unlink()
1405
+ deleted = True
1406
+ except OSError as e:
1407
+ raise InfraConnectionError(
1408
+ f"Failed to delete file: {e}",
1409
+ context=ctx,
1410
+ ) from e
1411
+
1412
+ # Reset circuit breaker on success
1413
+ async with self._circuit_breaker_lock:
1414
+ await self._reset_circuit_breaker()
1415
+
1416
+ logger.debug(
1417
+ "File delete operation completed",
1418
+ extra={
1419
+ "path": str(resolved_path),
1420
+ "deleted": deleted if not was_missing else False,
1421
+ "was_missing": was_missing,
1422
+ "correlation_id": str(correlation_id),
1423
+ },
1424
+ )
1425
+
1426
+ return ModelHandlerOutput.for_compute(
1427
+ input_envelope_id=input_envelope_id,
1428
+ correlation_id=correlation_id,
1429
+ handler_id=HANDLER_ID_FILESYSTEM,
1430
+ result={
1431
+ "status": "success",
1432
+ "payload": {
1433
+ "path": str(resolved_path),
1434
+ "deleted": deleted if not was_missing else False,
1435
+ "was_missing": was_missing,
1436
+ },
1437
+ "correlation_id": str(correlation_id),
1438
+ },
1439
+ )
1440
+
1441
+ except (InfraConnectionError, InfraUnavailableError):
1442
+ # Record failure for circuit breaker (infra-level failures only)
1443
+ async with self._circuit_breaker_lock:
1444
+ await self._record_circuit_failure(operation, correlation_id)
1445
+ raise
1446
+
1447
+ def describe(self) -> dict[str, object]:
1448
+ """Return handler metadata and capabilities for introspection.
1449
+
1450
+ This method exposes the handler's three-dimensional type classification
1451
+ along with its operational configuration and capabilities.
1452
+
1453
+ Returns:
1454
+ dict containing:
1455
+ - handler_type: Architectural role from handler_type property
1456
+ - handler_category: Behavioral classification from handler_category property
1457
+ - transport_type: Protocol identifier from transport_type property
1458
+ - supported_operations: List of supported operations
1459
+ - allowed_paths: List of allowed directory paths (when initialized)
1460
+ - max_read_size: Maximum read size in bytes
1461
+ - max_write_size: Maximum write size in bytes
1462
+ - initialized: Whether the handler is initialized
1463
+ - version: Handler version string
1464
+ """
1465
+ return {
1466
+ "handler_type": self.handler_type.value,
1467
+ "handler_category": self.handler_category.value,
1468
+ "transport_type": self.transport_type.value,
1469
+ "supported_operations": sorted(_SUPPORTED_OPERATIONS),
1470
+ "allowed_paths": [str(p) for p in self._allowed_paths],
1471
+ "max_read_size": self._max_read_size,
1472
+ "max_write_size": self._max_write_size,
1473
+ "initialized": self._initialized,
1474
+ "version": "0.1.0",
1475
+ }
1476
+
1477
+
1478
+ __all__: list[str] = ["HandlerFileSystem"]