ummaya 0.2.3 → 0.2.5

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 (534) hide show
  1. package/README.md +17 -3
  2. package/bin/ummaya +10 -1
  3. package/npm-shrinkwrap.json +253 -2
  4. package/package.json +5 -1
  5. package/prompts/manifest.yaml +2 -2
  6. package/prompts/session_guidance_v1.md +3 -1
  7. package/prompts/system_v1.md +9 -7
  8. package/pyproject.toml +26 -7
  9. package/specs/2803-document-production-hardening/contracts/document-tools.schema.json +1043 -0
  10. package/src/ummaya/_canonical/__init__.py +2 -0
  11. package/src/ummaya/context/builder.py +17 -11
  12. package/src/ummaya/engine/engine.py +30 -113
  13. package/src/ummaya/engine/query.py +20 -0
  14. package/src/ummaya/evidence/__init__.py +44 -0
  15. package/src/ummaya/evidence/__main__.py +7 -0
  16. package/src/ummaya/evidence/dataset_contract.py +193 -0
  17. package/src/ummaya/evidence/document_authoring_cases.py +33 -0
  18. package/src/ummaya/evidence/document_harness.py +313 -0
  19. package/src/ummaya/evidence/document_viewer_ux.py +391 -0
  20. package/src/ummaya/evidence/gates.py +70 -0
  21. package/src/ummaya/evidence/json_types.py +20 -0
  22. package/src/ummaya/evidence/models.py +145 -0
  23. package/src/ummaya/evidence/output_payload.py +89 -0
  24. package/src/ummaya/evidence/payload_documents.py +233 -0
  25. package/src/ummaya/evidence/route_contracts.py +224 -0
  26. package/src/ummaya/evidence/route_helpers.py +150 -0
  27. package/src/ummaya/evidence/runner.py +177 -0
  28. package/src/ummaya/evidence/source_provenance.py +246 -0
  29. package/src/ummaya/evidence/source_provenance_redaction.py +176 -0
  30. package/src/ummaya/evidence/task_registry.py +264 -0
  31. package/src/ummaya/evidence/tool_layer.py +39 -0
  32. package/src/ummaya/evidence/tool_layer_models.py +151 -0
  33. package/src/ummaya/ipc/adapter_manifest_emitter.py +26 -10
  34. package/src/ummaya/ipc/document_intent_normalization.py +185 -0
  35. package/src/ummaya/ipc/frame_schema.py +52 -5
  36. package/src/ummaya/ipc/route_diagnostics.py +73 -0
  37. package/src/ummaya/ipc/stdio.py +2282 -417
  38. package/src/ummaya/llm/client.py +234 -59
  39. package/src/ummaya/llm/config.py +8 -3
  40. package/src/ummaya/llm/reasoning.py +84 -0
  41. package/src/ummaya/primitives/__init__.py +6 -2
  42. package/src/ummaya/primitives/delegation.py +1 -1
  43. package/src/ummaya/primitives/document.py +28 -0
  44. package/src/ummaya/settings.py +0 -3
  45. package/src/ummaya/tools/discovery_bridge.py +34 -2
  46. package/src/ummaya/tools/documents/__init__.py +297 -0
  47. package/src/ummaya/tools/documents/adapter_registry.py +487 -0
  48. package/src/ummaya/tools/documents/archive_container_probe.py +167 -0
  49. package/src/ummaya/tools/documents/artifact_store.py +454 -0
  50. package/src/ummaya/tools/documents/authoring.py +283 -0
  51. package/src/ummaya/tools/documents/baselines.py +114 -0
  52. package/src/ummaya/tools/documents/capability.py +331 -0
  53. package/src/ummaya/tools/documents/contracts.py +112 -0
  54. package/src/ummaya/tools/documents/conversion.py +521 -0
  55. package/src/ummaya/tools/documents/diff.py +275 -0
  56. package/src/ummaya/tools/documents/engines.py +163 -0
  57. package/src/ummaya/tools/documents/evaluation.py +291 -0
  58. package/src/ummaya/tools/documents/explicit_values.py +108 -0
  59. package/src/ummaya/tools/documents/fixtures.py +174 -0
  60. package/src/ummaya/tools/documents/format_completion_audit.py +471 -0
  61. package/src/ummaya/tools/documents/formats/__init__.py +2 -0
  62. package/src/ummaya/tools/documents/formats/archive.py +528 -0
  63. package/src/ummaya/tools/documents/formats/base.py +41 -0
  64. package/src/ummaya/tools/documents/formats/code_file.py +211 -0
  65. package/src/ummaya/tools/documents/formats/data_file.py +272 -0
  66. package/src/ummaya/tools/documents/formats/hwp.py +284 -0
  67. package/src/ummaya/tools/documents/formats/hwpx.py +1837 -0
  68. package/src/ummaya/tools/documents/formats/odf.py +435 -0
  69. package/src/ummaya/tools/documents/formats/ooxml.py +1030 -0
  70. package/src/ummaya/tools/documents/formats/passive.py +766 -0
  71. package/src/ummaya/tools/documents/formats/pdf.py +702 -0
  72. package/src/ummaya/tools/documents/formats/text_web.py +268 -0
  73. package/src/ummaya/tools/documents/hwp_conversion_probe.py +178 -0
  74. package/src/ummaya/tools/documents/hwp_direct_candidate.py +141 -0
  75. package/src/ummaya/tools/documents/inspection.py +289 -0
  76. package/src/ummaya/tools/documents/intake.py +1079 -0
  77. package/src/ummaya/tools/documents/legacy_office_promotion_probe.py +366 -0
  78. package/src/ummaya/tools/documents/models.py +1598 -0
  79. package/src/ummaya/tools/documents/odf_promotion_probe.py +167 -0
  80. package/src/ummaya/tools/documents/orchestrator.py +96 -0
  81. package/src/ummaya/tools/documents/passive_capability_probe.py +251 -0
  82. package/src/ummaya/tools/documents/patch.py +170 -0
  83. package/src/ummaya/tools/documents/pdfa_conformance.py +284 -0
  84. package/src/ummaya/tools/documents/pdfa_promotion_probe.py +198 -0
  85. package/src/ummaya/tools/documents/permissions.py +110 -0
  86. package/src/ummaya/tools/documents/planner.py +616 -0
  87. package/src/ummaya/tools/documents/registry.py +2733 -0
  88. package/src/ummaya/tools/documents/render.py +978 -0
  89. package/src/ummaya/tools/documents/render_comparison.py +113 -0
  90. package/src/ummaya/tools/documents/render_comparison_models.py +74 -0
  91. package/src/ummaya/tools/documents/render_comparison_regions.py +73 -0
  92. package/src/ummaya/tools/documents/render_comparison_style.py +161 -0
  93. package/src/ummaya/tools/documents/reread.py +157 -0
  94. package/src/ummaya/tools/documents/runtime_authoring.py +244 -0
  95. package/src/ummaya/tools/documents/runtime_authoring_bundle.py +76 -0
  96. package/src/ummaya/tools/documents/scorecard.py +184 -0
  97. package/src/ummaya/tools/documents/socratic_planner.py +193 -0
  98. package/src/ummaya/tools/documents/style.py +48 -0
  99. package/src/ummaya/tools/documents/tool_defs.py +523 -0
  100. package/src/ummaya/tools/documents/validate.py +347 -0
  101. package/src/ummaya/tools/executor.py +61 -12
  102. package/src/ummaya/tools/geocoding/kakao_client.py +1 -2
  103. package/src/ummaya/tools/kma/apihub_catalog.py +984 -1
  104. package/src/ummaya/tools/kma/apihub_structured_adapter.py +86 -6
  105. package/src/ummaya/tools/kma/apihub_url_adapter.py +593 -0
  106. package/src/ummaya/tools/kma/apihub_url_catalog.py +296 -0
  107. package/src/ummaya/tools/live_proxy.py +0 -3
  108. package/src/ummaya/tools/location_adapters.py +8 -6
  109. package/src/ummaya/tools/manifest_metadata.py +16 -3
  110. package/src/ummaya/tools/models.py +5 -1
  111. package/src/ummaya/tools/mvp_surface.py +2 -2
  112. package/src/ummaya/tools/nmc/emergency_search.py +8 -6
  113. package/src/ummaya/tools/register_all.py +17 -0
  114. package/src/ummaya/tools/registry.py +10 -1
  115. package/src/ummaya/tools/resolve_location.py +4 -4
  116. package/src/ummaya/tools/routing/__init__.py +59 -0
  117. package/src/ummaya/tools/routing/builder.py +105 -0
  118. package/src/ummaya/tools/routing/cards.py +29 -0
  119. package/src/ummaya/tools/routing/decision_service.py +534 -0
  120. package/src/ummaya/tools/routing/decision_types.py +74 -0
  121. package/src/ummaya/tools/routing/feasibility.py +122 -0
  122. package/src/ummaya/tools/routing/intent.py +17 -0
  123. package/src/ummaya/tools/routing/intent_extractor.py +207 -0
  124. package/src/ummaya/tools/routing/intent_patterns.py +160 -0
  125. package/src/ummaya/tools/routing/intent_public_data.py +150 -0
  126. package/src/ummaya/tools/routing/intent_types.py +48 -0
  127. package/src/ummaya/tools/routing/lint.py +78 -0
  128. package/src/ummaya/tools/routing/metadata.py +174 -0
  129. package/src/ummaya/tools/routing/projection.py +340 -0
  130. package/src/ummaya/tools/routing/retrieval_policy.py +629 -0
  131. package/src/ummaya/tools/routing/schema.py +81 -0
  132. package/src/ummaya/tools/routing/types.py +96 -0
  133. package/src/ummaya/tools/routing_index.py +2 -2
  134. package/src/ummaya/tools/search.py +40 -106
  135. package/src/ummaya/tools/verified_data_go_kr/_manifest.py +115 -25
  136. package/src/ummaya/tools/verified_data_go_kr/airkorea_air_quality.py +109 -4
  137. package/src/ummaya/tools/verified_data_go_kr/nmc_aed_site.py +108 -2
  138. package/src/ummaya/tools/verified_data_go_kr/pps_bid_public_info.py +174 -9
  139. package/src/ummaya/tools/verified_data_go_kr/tago_bus_arrival.py +66 -3
  140. package/src/ummaya/tools/verified_data_go_kr/tago_bus_location.py +12 -2
  141. package/src/ummaya/tools/verified_data_go_kr/tago_bus_route.py +8 -2
  142. package/src/ummaya/tools/verified_data_go_kr/tago_bus_route_station.py +114 -0
  143. package/src/ummaya/tools/verified_data_go_kr/tago_bus_station.py +14 -3
  144. package/src/ummaya/tools/verify_canonical_map.py +21 -0
  145. package/tests/fixtures/documents/public_forms/baselines.yaml +113 -0
  146. package/tui/package.json +1 -2
  147. package/tui/src/.cc-byte-identical-whitelist.yaml +266 -0
  148. package/tui/src/QueryEngine.ts +12 -4
  149. package/tui/src/bridge/inboundAttachments.ts +3 -3
  150. package/tui/src/cli/handlers/auth.ts +4 -13
  151. package/tui/src/cli/handlers/mcp.tsx +3 -3
  152. package/tui/src/cli/print.ts +69 -18
  153. package/tui/src/cli/update.ts +13 -13
  154. package/tui/src/commands/copy/index.ts +1 -1
  155. package/tui/src/commands/cost/cost.ts +2 -2
  156. package/tui/src/commands/init-verifiers.ts +5 -5
  157. package/tui/src/commands/init.ts +30 -30
  158. package/tui/src/commands/insights.ts +44 -44
  159. package/tui/src/commands/install-github-app/install-github-app.tsx +2 -2
  160. package/tui/src/commands/install-github-app/setupGitHubActions.ts +3 -3
  161. package/tui/src/commands/install-github-app/types.ts +8 -30
  162. package/tui/src/commands/install.tsx +5 -5
  163. package/tui/src/commands/mcp/addCommand.ts +5 -5
  164. package/tui/src/commands/mcp/xaaIdpCommand.ts +2 -2
  165. package/tui/src/commands/plugin/ManageMarketplaces.tsx +2 -2
  166. package/tui/src/commands/plugin/types.ts +6 -28
  167. package/tui/src/commands/plugin/unifiedTypes.ts +4 -26
  168. package/tui/src/commands/reasoning/index.ts +13 -0
  169. package/tui/src/commands/reasoning/reasoning.tsx +177 -0
  170. package/tui/src/commands/rename/generateSessionName.ts +1 -1
  171. package/tui/src/commands/thinkback/thinkback.tsx +3 -3
  172. package/tui/src/commands.ts +2 -0
  173. package/tui/src/components/Feedback.tsx +1 -1
  174. package/tui/src/components/LogoV2/EmergencyTip.tsx +11 -2
  175. package/tui/src/components/LogoV2/WelcomeV2.tsx +1 -3
  176. package/tui/src/components/Messages.tsx +2 -1
  177. package/tui/src/components/ScrollKeybindingHandler.tsx +6 -6
  178. package/tui/src/components/Spinner/types.ts +6 -28
  179. package/tui/src/components/Spinner.tsx +2 -2
  180. package/tui/src/components/agents/generateAgent.ts +1 -1
  181. package/tui/src/components/agents/new-agent-creation/types.ts +4 -26
  182. package/tui/src/components/config/EnvSecretIsolatedEditor.tsx +1 -1
  183. package/tui/src/components/design-system/LoadingState.tsx +2 -2
  184. package/tui/src/components/mcp/types.ts +16 -38
  185. package/tui/src/components/messages/AssistantToolUseMessage.tsx +3 -2
  186. package/tui/src/components/messages/UserCrossSessionMessage.ts +16 -4
  187. package/tui/src/components/messages/UserForkBoilerplateMessage.ts +16 -4
  188. package/tui/src/components/messages/UserGitHubWebhookMessage.ts +16 -4
  189. package/tui/src/components/messages/UserToolResultMessage/utils.tsx +3 -2
  190. package/tui/src/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.ts +9 -4
  191. package/tui/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.ts +9 -4
  192. package/tui/src/components/primitive/DocumentSocraticReviewBlock.tsx +129 -0
  193. package/tui/src/components/primitive/DocumentToolResultCard.tsx +224 -0
  194. package/tui/src/components/primitive/documentSocraticReview.ts +215 -0
  195. package/tui/src/components/primitive/index.tsx +43 -1
  196. package/tui/src/components/primitive/types.ts +137 -0
  197. package/tui/src/components/ui/option.ts +4 -26
  198. package/tui/src/constants/common.ts +0 -2
  199. package/tui/src/constants/prompts.ts +4 -3
  200. package/tui/src/constants/querySource.ts +4 -26
  201. package/tui/src/entrypoints/sdk/controlTypes.ts +26 -48
  202. package/tui/src/entrypoints/sdk/coreTypes.generated.ts +3 -25
  203. package/tui/src/entrypoints/sdk/runtimeTypes.ts +38 -60
  204. package/tui/src/entrypoints/sdk/sdkUtilityTypes.ts +4 -26
  205. package/tui/src/entrypoints/sdk/settingsTypes.generated.ts +3 -25
  206. package/tui/src/entrypoints/sdk/toolTypes.ts +3 -25
  207. package/tui/src/hooks/toolPermission/handlers/interactiveHandler.ts +10 -0
  208. package/tui/src/hooks/useApiKeyVerification.ts +1 -1
  209. package/tui/src/hooks/useVirtualScroll.ts +1 -1
  210. package/tui/src/ink/ink.tsx +33 -14
  211. package/tui/src/ink/reconciler.ts +2 -3
  212. package/tui/src/ink/render-to-screen.ts +30 -10
  213. package/tui/src/ipc/bridge.ts +62 -15
  214. package/tui/src/ipc/bridgeSingleton.ts +5 -1
  215. package/tui/src/ipc/codec.ts +29 -3
  216. package/tui/src/ipc/frames.generated.ts +407 -312
  217. package/tui/src/ipc/llmClient.ts +279 -76
  218. package/tui/src/ipc/llmTypes.ts +16 -1
  219. package/tui/src/ipc/schema/frame.schema.json +1 -3475
  220. package/tui/src/keybindings/defaultBindings.ts +4 -0
  221. package/tui/src/main.tsx +32 -11
  222. package/tui/src/native-ts/file-index/index.ts +33 -3
  223. package/tui/src/observability/surface.ts +2 -2
  224. package/tui/src/probes/toolRegistryProbe.tsx +3 -1
  225. package/tui/src/projectOnboardingState.ts +7 -6
  226. package/tui/src/query/chatMessageTypes.ts +18 -0
  227. package/tui/src/query/chatMessagesBuilder.ts +1 -1
  228. package/tui/src/query/deps.ts +1 -1
  229. package/tui/src/query/messageGuards.ts +106 -0
  230. package/tui/src/query/publicDataTerminalRepair.ts +384 -0
  231. package/tui/src/query/run.ts +1075 -0
  232. package/tui/src/query/supportBoundary.ts +168 -0
  233. package/tui/src/query/toolResultErrors.ts +103 -0
  234. package/tui/src/query/toolRunner.ts +687 -0
  235. package/tui/src/query/unavailableToolRepair.ts +118 -0
  236. package/tui/src/query.ts +9 -1721
  237. package/tui/src/screens/REPL.tsx +42 -31
  238. package/tui/src/services/api/adapterManifest.ts +4 -0
  239. package/tui/src/services/api/backendChat/events.ts +117 -0
  240. package/tui/src/services/api/backendChat/finalMessage.ts +40 -0
  241. package/tui/src/services/api/backendChat/frame.ts +9 -0
  242. package/tui/src/services/api/backendChat/streaming.ts +430 -0
  243. package/tui/src/services/api/backendChat/types.ts +62 -0
  244. package/tui/src/services/api/backendChat.ts +1 -0
  245. package/tui/src/services/api/client.ts +98 -14
  246. package/tui/src/services/api/errorUtils.ts +5 -5
  247. package/tui/src/services/api/errors.ts +1 -1
  248. package/tui/src/services/api/logging.ts +1 -1
  249. package/tui/src/services/api/ummaya/evidence.ts +194 -0
  250. package/tui/src/services/api/ummaya/messages.ts +255 -0
  251. package/tui/src/services/api/ummaya/nonStreaming.ts +66 -0
  252. package/tui/src/services/api/ummaya/provider.ts +200 -0
  253. package/tui/src/services/api/ummaya/reasoning.ts +24 -0
  254. package/tui/src/services/api/ummaya/request.ts +200 -0
  255. package/tui/src/services/api/ummaya/selectionContext.ts +240 -0
  256. package/tui/src/services/api/ummaya/streaming.ts +365 -0
  257. package/tui/src/services/api/ummaya/streamingPayload.ts +129 -0
  258. package/tui/src/services/api/ummaya/streamingReader.ts +40 -0
  259. package/tui/src/services/api/ummaya/toolSelection.ts +217 -0
  260. package/tui/src/services/api/ummaya/types.ts +110 -0
  261. package/tui/src/services/api/ummaya/usage.ts +30 -0
  262. package/tui/src/services/api/ummaya.ts +26 -364
  263. package/tui/src/services/api/withRetry.ts +1 -1
  264. package/tui/src/services/awaySummary.ts +2 -2
  265. package/tui/src/services/claudeAiLimits.ts +1 -1
  266. package/tui/src/services/compact/autoCompact.ts +1 -1
  267. package/tui/src/services/compact/compact.ts +1 -1
  268. package/tui/src/services/lsp/types.ts +8 -30
  269. package/tui/src/services/tips/types.ts +6 -28
  270. package/tui/src/services/tokenEstimation.ts +1 -1
  271. package/tui/src/services/toolRegistry/bootGuard.ts +5 -5
  272. package/tui/src/services/toolUseSummary/toolUseSummaryGenerator.ts +1 -1
  273. package/tui/src/services/tools/toolExecution.ts +94 -1
  274. package/tui/src/skills/bundled/stuck.ts +12 -12
  275. package/tui/src/state/AppStateStore.ts +7 -0
  276. package/tui/src/store/pendingPermissionSlot.ts +1 -1
  277. package/tui/src/store/session-store.ts +10 -36
  278. package/tui/src/stubs/any-stub.ts +15 -10
  279. package/tui/src/stubs/color-diff-napi.ts +37 -23
  280. package/tui/src/stubs/globals.d.ts +3 -3
  281. package/tui/src/stubs/macro-preload.ts +23 -12
  282. package/tui/src/tools/AdapterTool/AdapterTool.ts +1239 -163
  283. package/tui/src/tools/AdapterTool/routeDiagnostics.ts +75 -0
  284. package/tui/src/tools/AgentTool/AgentTool.tsx +84 -1371
  285. package/tui/src/tools/AgentTool/agentToolHandoff.ts +114 -0
  286. package/tui/src/tools/AgentTool/agentToolPartialResult.ts +16 -0
  287. package/tui/src/tools/AgentTool/agentToolProgress.ts +32 -0
  288. package/tui/src/tools/AgentTool/agentToolResolver.ts +161 -0
  289. package/tui/src/tools/AgentTool/agentToolResult.ts +163 -0
  290. package/tui/src/tools/AgentTool/agentToolUtils.ts +14 -686
  291. package/tui/src/tools/AgentTool/asyncAgentLifecycle.ts +208 -0
  292. package/tui/src/tools/AgentTool/asyncLifecycle.ts +153 -0
  293. package/tui/src/tools/AgentTool/backgroundedCompletion.ts +126 -0
  294. package/tui/src/tools/AgentTool/backgroundedLifecycle.ts +174 -0
  295. package/tui/src/tools/AgentTool/foregroundBackground.ts +83 -0
  296. package/tui/src/tools/AgentTool/foregroundDrain.tsx +133 -0
  297. package/tui/src/tools/AgentTool/foregroundFinalize.ts +98 -0
  298. package/tui/src/tools/AgentTool/foregroundLifecycle.tsx +237 -0
  299. package/tui/src/tools/AgentTool/foregroundProgress.tsx +169 -0
  300. package/tui/src/tools/AgentTool/foregroundTask.ts +89 -0
  301. package/tui/src/tools/AgentTool/forkSubagent.ts +1 -12
  302. package/tui/src/tools/AgentTool/forkSubagentGate.ts +34 -0
  303. package/tui/src/tools/AgentTool/launchRouting.ts +203 -0
  304. package/tui/src/tools/AgentTool/lifecycle.ts +244 -0
  305. package/tui/src/tools/AgentTool/mcpRouting.ts +73 -0
  306. package/tui/src/tools/AgentTool/orchestrationSupport.ts +70 -0
  307. package/tui/src/tools/AgentTool/permissions.ts +39 -0
  308. package/tui/src/tools/AgentTool/promptSetup.ts +181 -0
  309. package/tui/src/tools/AgentTool/remoteRouting.ts +62 -0
  310. package/tui/src/tools/AgentTool/resultMapping.ts +116 -0
  311. package/tui/src/tools/AgentTool/resumeAgent.ts +39 -107
  312. package/tui/src/tools/AgentTool/resumeAgentHelpers.ts +140 -0
  313. package/tui/src/tools/AgentTool/runAgent.ts +1 -1
  314. package/tui/src/tools/AgentTool/runtimeConfig.ts +57 -0
  315. package/tui/src/tools/AgentTool/schemas.ts +196 -0
  316. package/tui/src/tools/AgentTool/sourceVerificationPropagation.ts +263 -0
  317. package/tui/src/tools/AgentTool/worktreeLifecycle.ts +105 -0
  318. package/tui/src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx +174 -202
  319. package/tui/src/tools/BashTool/BashTool.tsx +71 -1072
  320. package/tui/src/tools/BashTool/bashCommandHelpers.ts +12 -12
  321. package/tui/src/tools/BashTool/bashPermissions/astPreflight.ts +173 -0
  322. package/tui/src/tools/BashTool/bashPermissions/classifierChecks.ts +199 -0
  323. package/tui/src/tools/BashTool/bashPermissions/compoundGuards.ts +53 -0
  324. package/tui/src/tools/BashTool/bashPermissions/constants.ts +99 -0
  325. package/tui/src/tools/BashTool/bashPermissions/index.ts +38 -0
  326. package/tui/src/tools/BashTool/bashPermissions/legacyMisparsing.ts +62 -0
  327. package/tui/src/tools/BashTool/bashPermissions/main.ts +135 -0
  328. package/tui/src/tools/BashTool/bashPermissions/normalizedCommands.ts +33 -0
  329. package/tui/src/tools/BashTool/bashPermissions/operatorFlow.ts +98 -0
  330. package/tui/src/tools/BashTool/bashPermissions/permissionChecks.ts +200 -0
  331. package/tui/src/tools/BashTool/bashPermissions/prefixSuggestions.ts +88 -0
  332. package/tui/src/tools/BashTool/bashPermissions/promptClassifierRules.ts +125 -0
  333. package/tui/src/tools/BashTool/bashPermissions/ruleDelegates.ts +19 -0
  334. package/tui/src/tools/BashTool/bashPermissions/ruleMatching.ts +145 -0
  335. package/tui/src/tools/BashTool/bashPermissions/sandboxAutoAllow.ts +75 -0
  336. package/tui/src/tools/BashTool/bashPermissions/subcommandFlow.ts +205 -0
  337. package/tui/src/tools/BashTool/bashPermissions/subcommandGuards.ts +73 -0
  338. package/tui/src/tools/BashTool/bashPermissions/subcommandResultHelpers.ts +116 -0
  339. package/tui/src/tools/BashTool/bashPermissions/types.ts +26 -0
  340. package/tui/src/tools/BashTool/bashPermissions/wrapperStripping.ts +139 -0
  341. package/tui/src/tools/BashTool/bashPermissions.ts +26 -2621
  342. package/tui/src/tools/BashTool/call.ts +202 -0
  343. package/tui/src/tools/BashTool/callLoader.ts +35 -0
  344. package/tui/src/tools/BashTool/commandClassification.ts +151 -0
  345. package/tui/src/tools/BashTool/commandClassificationLoader.ts +40 -0
  346. package/tui/src/tools/BashTool/cwdReset.ts +33 -0
  347. package/tui/src/tools/BashTool/lineTruncation.ts +11 -0
  348. package/tui/src/tools/BashTool/modeValidation.ts +13 -1
  349. package/tui/src/tools/BashTool/outputPersistence.ts +42 -0
  350. package/tui/src/tools/BashTool/permissionClassification.ts +66 -0
  351. package/tui/src/tools/BashTool/permissionLoader.ts +44 -0
  352. package/tui/src/tools/BashTool/resultLoader.ts +29 -0
  353. package/tui/src/tools/BashTool/resultMapping.ts +83 -0
  354. package/tui/src/tools/BashTool/sandboxPolicy.ts +79 -0
  355. package/tui/src/tools/BashTool/schemas.ts +65 -0
  356. package/tui/src/tools/BashTool/sedEditExecution.ts +59 -0
  357. package/tui/src/tools/BashTool/shellExecution.tsx +245 -0
  358. package/tui/src/tools/BashTool/shellOutputUtils.ts +85 -0
  359. package/tui/src/tools/BashTool/shellPermissionGauntlet.ts +97 -0
  360. package/tui/src/tools/BashTool/uiLoader.ts +37 -0
  361. package/tui/src/tools/BriefTool/upload.ts +1 -1
  362. package/tui/src/tools/CalculatorTool/parser.ts +2 -2
  363. package/tui/src/tools/DocumentPrimitive/DocumentPrimitive.ts +262 -0
  364. package/tui/src/tools/DocumentPrimitive/dispatchNormalization.ts +270 -0
  365. package/tui/src/tools/DocumentPrimitive/documentDestinationPath.ts +18 -0
  366. package/tui/src/tools/DocumentPrimitive/documentMutationGuard.ts +22 -0
  367. package/tui/src/tools/DocumentPrimitive/documentPatchNormalization.ts +248 -0
  368. package/tui/src/tools/DocumentPrimitive/documentSourceVerification.ts +245 -0
  369. package/tui/src/tools/DocumentPrimitive/documentSourceVerificationFields.ts +103 -0
  370. package/tui/src/tools/DocumentPrimitive/modelVisibleOutput.ts +40 -0
  371. package/tui/src/tools/DocumentPrimitive/prompt.ts +35 -0
  372. package/tui/src/tools/FileEditTool/FileEditTool.ts +9 -507
  373. package/tui/src/tools/FileEditTool/call.ts +228 -0
  374. package/tui/src/tools/FileEditTool/validateInput.ts +196 -0
  375. package/tui/src/tools/FileReadTool/imageProcessor.ts +13 -0
  376. package/tui/src/tools/FileWriteTool/FileWriteTool.ts +7 -300
  377. package/tui/src/tools/FileWriteTool/call.ts +223 -0
  378. package/tui/src/tools/FileWriteTool/validateInput.ts +80 -0
  379. package/tui/src/tools/ListMcpResourcesTool/ListMcpResourcesTool.ts +19 -3
  380. package/tui/src/tools/LookupPrimitive/LookupPrimitive.ts +48 -29
  381. package/tui/src/tools/LookupPrimitive/prompt.ts +6 -7
  382. package/tui/src/tools/MCPTool/trustPolicy.ts +118 -0
  383. package/tui/src/tools/McpAuthTool/McpAuthTool.ts +21 -3
  384. package/tui/src/tools/NotebookEditTool/NotebookEditTool.ts +7 -326
  385. package/tui/src/tools/NotebookEditTool/call.ts +254 -0
  386. package/tui/src/tools/NotebookEditTool/notebookModel.ts +51 -0
  387. package/tui/src/tools/NotebookEditTool/validateInput.ts +142 -0
  388. package/tui/src/tools/PowerShellTool/PowerShellTool.tsx +46 -937
  389. package/tui/src/tools/PowerShellTool/acceptEditsCommandValidation.ts +162 -0
  390. package/tui/src/tools/PowerShellTool/call.ts +179 -0
  391. package/tui/src/tools/PowerShellTool/callLoader.ts +37 -0
  392. package/tui/src/tools/PowerShellTool/commandClassification.ts +86 -0
  393. package/tui/src/tools/PowerShellTool/modeValidation.ts +25 -332
  394. package/tui/src/tools/PowerShellTool/outputPersistence.ts +42 -0
  395. package/tui/src/tools/PowerShellTool/permissionClassification.ts +28 -0
  396. package/tui/src/tools/PowerShellTool/resultLoader.ts +31 -0
  397. package/tui/src/tools/PowerShellTool/resultMapping.ts +75 -0
  398. package/tui/src/tools/PowerShellTool/schemas.ts +40 -0
  399. package/tui/src/tools/PowerShellTool/shellExecution.tsx +258 -0
  400. package/tui/src/tools/PowerShellTool/symlinkModeValidation.ts +44 -0
  401. package/tui/src/tools/PowerShellTool/uiLoader.ts +37 -0
  402. package/tui/src/tools/PowerShellTool/validation.ts +39 -0
  403. package/tui/src/tools/ReadMcpResourceTool/ReadMcpResourceTool.ts +19 -3
  404. package/tui/src/tools/ResolveLocationPrimitive/ResolveLocationPrimitive.ts +30 -19
  405. package/tui/src/tools/ResolveLocationPrimitive/prompt.ts +2 -6
  406. package/tui/src/tools/SkillTool/SkillTool.ts +2 -2
  407. package/tui/src/tools/SubmitPrimitive/SubmitPrimitive.ts +51 -18
  408. package/tui/src/tools/TaskCreateTool/TaskCreateTool.ts +16 -2
  409. package/tui/src/tools/TaskGetTool/TaskGetTool.ts +23 -3
  410. package/tui/src/tools/TaskListTool/TaskListTool.ts +22 -4
  411. package/tui/src/tools/TaskOutputTool/TaskOutputTool.tsx +46 -547
  412. package/tui/src/tools/TaskOutputTool/lookup.ts +216 -0
  413. package/tui/src/tools/TaskOutputTool/render.tsx +257 -0
  414. package/tui/src/tools/TaskOutputTool/schemas.ts +55 -0
  415. package/tui/src/tools/TaskOutputTool/serialization.ts +36 -0
  416. package/tui/src/tools/TaskStopTool/TaskStopTool.ts +10 -0
  417. package/tui/src/tools/TaskUpdateTool/TaskUpdateTool.ts +14 -364
  418. package/tui/src/tools/TaskUpdateTool/completion.ts +62 -0
  419. package/tui/src/tools/TaskUpdateTool/schemas.ts +62 -0
  420. package/tui/src/tools/TaskUpdateTool/serialization.ts +46 -0
  421. package/tui/src/tools/TaskUpdateTool/statusUpdate.ts +247 -0
  422. package/tui/src/tools/TodoWriteTool/TodoWriteTool.ts +21 -2
  423. package/tui/src/tools/ToolSearchTool/ToolSearchTool.ts +21 -302
  424. package/tui/src/tools/ToolSearchTool/ccSupportTools.ts +223 -0
  425. package/tui/src/tools/ToolSearchTool/descriptionCache.ts +50 -0
  426. package/tui/src/tools/ToolSearchTool/keywordSearch.ts +216 -0
  427. package/tui/src/tools/ToolSearchTool/prompt.ts +10 -4
  428. package/tui/src/tools/ToolSearchTool/resultMapping.ts +30 -0
  429. package/tui/src/tools/ToolSearchTool/schemas.ts +30 -0
  430. package/tui/src/tools/ToolSearchTool/searchPool.ts +47 -0
  431. package/tui/src/tools/ToolSearchTool/supportIntentHints.ts +140 -0
  432. package/tui/src/tools/TranslateTool/TranslateTool.ts +1 -1
  433. package/tui/src/tools/VerifyPrimitive/VerifyPrimitive.ts +27 -10
  434. package/tui/src/tools/WebFetchTool/WebFetchTool.ts +43 -138
  435. package/tui/src/tools/WebFetchTool/call.ts +227 -0
  436. package/tui/src/tools/WebFetchTool/resolvedAddressSafety.ts +78 -0
  437. package/tui/src/tools/WebFetchTool/sourceVerification.ts +204 -0
  438. package/tui/src/tools/WebFetchTool/types.ts +23 -0
  439. package/tui/src/tools/WebFetchTool/urlSafety.ts +181 -0
  440. package/tui/src/tools/WebFetchTool/utils.ts +1 -1
  441. package/tui/src/tools/WebSearchTool/UI.tsx +0 -1
  442. package/tui/src/tools/WebSearchTool/WebSearchTool.ts +9 -313
  443. package/tui/src/tools/WebSearchTool/call.ts +33 -0
  444. package/tui/src/tools/WebSearchTool/responseMapping.ts +190 -0
  445. package/tui/src/tools/WebSearchTool/resultBlock.ts +47 -0
  446. package/tui/src/tools/WebSearchTool/schemas.ts +47 -0
  447. package/tui/src/tools/WebSearchTool/toolSchema.ts +12 -0
  448. package/tui/src/tools/WorkspaceToolAdapter/WorkspaceToolAdapter.ts +79 -0
  449. package/tui/src/tools/WorkspaceToolAdapter/allowedRootPolicy.ts +85 -0
  450. package/tui/src/tools/WorkspaceToolAdapter/documentFormatGuards.ts +73 -0
  451. package/tui/src/tools/WorkspaceToolAdapter/inputNormalization.ts +105 -0
  452. package/tui/src/tools/WorkspaceToolAdapter/mcpExposurePolicy.ts +64 -0
  453. package/tui/src/tools/WorkspaceToolAdapter/toolDefFactory.ts +215 -0
  454. package/tui/src/tools/WorkspaceToolAdapter/toolNames.ts +6 -0
  455. package/tui/src/tools/WorkspaceToolAdapter/workspacePolicy.ts +15 -0
  456. package/tui/src/tools/_shared/citizenUserText.ts +49 -0
  457. package/tui/src/tools/_shared/dispatchPrimitive.ts +6 -6
  458. package/tui/src/tools/_shared/documentChangeToPatch.ts +125 -0
  459. package/tui/src/tools/_shared/documentDispatchArguments.ts +87 -0
  460. package/tui/src/tools/_shared/documentPrimitiveTimeout.ts +13 -0
  461. package/tui/src/tools/_shared/documentToolResultRender.ts +98 -0
  462. package/tui/src/tools/_shared/locationInputRepair.ts +112 -0
  463. package/tui/src/tools/_shared/pendingCallRegistry.ts +1 -6
  464. package/tui/src/tools/_shared/rootPrimitiveInput.ts +68 -0
  465. package/tui/src/tools/_shared/toolChoiceRepair/documentCompletionPatterns.ts +58 -0
  466. package/tui/src/tools/_shared/toolChoiceRepair/documentCompletionPrompt.ts +271 -0
  467. package/tui/src/tools/_shared/toolChoiceRepair/documentRepair.ts +452 -0
  468. package/tui/src/tools/_shared/toolChoiceRepair/messageAccess.ts +80 -0
  469. package/tui/src/tools/_shared/toolChoiceRepair/publicDataRepair.ts +92 -0
  470. package/tui/src/tools/_shared/toolChoiceRepair/supportRepair.ts +135 -0
  471. package/tui/src/tools/_shared/toolChoiceRepair.ts +61 -0
  472. package/tui/src/tools/shared/mockDisclaimer.ts +1 -1
  473. package/tui/src/tools.ts +39 -190
  474. package/tui/src/types/fileSuggestion.ts +4 -26
  475. package/tui/src/types/generated/events_mono/claude_code/v1/claude_code_internal_event.ts +186 -148
  476. package/tui/src/types/generated/events_mono/common/v1/auth.ts +25 -11
  477. package/tui/src/types/generated/events_mono/growthbook/v1/growthbook_experiment_event.ts +47 -30
  478. package/tui/src/types/generated/google/protobuf/timestamp.ts +21 -7
  479. package/tui/src/types/message.ts +80 -102
  480. package/tui/src/types/messageQueueTypes.ts +6 -28
  481. package/tui/src/types/notebook.ts +16 -38
  482. package/tui/src/types/statusLine.ts +4 -26
  483. package/tui/src/types/tools.ts +24 -46
  484. package/tui/src/types/utils.ts +6 -28
  485. package/tui/src/upstreamproxy/relay.ts +7 -3
  486. package/tui/src/upstreamproxy/upstreamproxy.ts +1 -1
  487. package/tui/src/utils/assistantMessageFactories.ts +9 -3
  488. package/tui/src/utils/attachments.ts +1 -1
  489. package/tui/src/utils/auth.ts +129 -139
  490. package/tui/src/utils/bash/ast.ts +23 -23
  491. package/tui/src/utils/bash/bashParser.ts +5 -5
  492. package/tui/src/utils/billing.ts +1 -1
  493. package/tui/src/utils/collapseReadSearch.ts +3 -3
  494. package/tui/src/utils/cronTasks.ts +1 -1
  495. package/tui/src/utils/execFileNoThrow.ts +1 -1
  496. package/tui/src/utils/filePersistence/types.ts +16 -38
  497. package/tui/src/utils/forkedAgent.ts +1 -1
  498. package/tui/src/utils/gracefulShutdown.ts +4 -4
  499. package/tui/src/utils/heapDumpService.ts +12 -8
  500. package/tui/src/utils/hooks/apiQueryHookHelper.ts +1 -1
  501. package/tui/src/utils/hooks/execPromptHook.ts +1 -1
  502. package/tui/src/utils/hooks/skillImprovement.ts +1 -1
  503. package/tui/src/utils/kExaoneReasoning.ts +138 -0
  504. package/tui/src/utils/mcp/dateTimeParser.ts +1 -1
  505. package/tui/src/utils/messages.ts +19 -0
  506. package/tui/src/utils/migrateSessions.ts +3 -3
  507. package/tui/src/utils/model/model.ts +6 -6
  508. package/tui/src/utils/multiToolLayout.ts +13 -0
  509. package/tui/src/utils/permissions/yoloClassifier.ts +1 -1
  510. package/tui/src/utils/plugins/headlessPluginInstall.ts +1 -1
  511. package/tui/src/utils/plugins/mcpPluginIntegration.ts +1 -1
  512. package/tui/src/utils/plugins/mcpbHandler.ts +1 -1
  513. package/tui/src/utils/plugins/pluginLoader.ts +8 -8
  514. package/tui/src/utils/processUserInput/processSlashCommand.tsx +2 -2
  515. package/tui/src/utils/processUserInput/processUserInput.ts +26 -0
  516. package/tui/src/utils/protectedNamespace.ts +5 -3
  517. package/tui/src/utils/rawJsonToolCall.ts +242 -0
  518. package/tui/src/utils/ripgrep.ts +16 -7
  519. package/tui/src/utils/sessionTitle.ts +1 -1
  520. package/tui/src/utils/settings/applySettingsChange.ts +4 -0
  521. package/tui/src/utils/settings/permissionValidation.ts +14 -2
  522. package/tui/src/utils/settings/types.ts +9 -3
  523. package/tui/src/utils/shell/prefix.ts +1 -1
  524. package/tui/src/utils/sideQuery.ts +1 -1
  525. package/tui/src/utils/stats.ts +1 -1
  526. package/tui/src/utils/systemThemeWatcher.ts +13 -3
  527. package/tui/src/utils/teleport.tsx +1 -1
  528. package/uv.lock +394 -22
  529. package/assets/copilot-gate-logo.svg +0 -58
  530. package/assets/govon-logo.svg +0 -40
  531. package/src/ummaya/eval/__init__.py +0 -5
  532. package/src/ummaya/eval/retrieval.py +0 -713
  533. package/tui/src/services/api/claude.ts +0 -3510
  534. package/tui/src/utils/messageStream.ts +0 -186
@@ -0,0 +1,1598 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Strict Pydantic models for the Public AX document harness."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import re
7
+ from datetime import date, datetime
8
+ from decimal import Decimal
9
+ from enum import Enum, StrEnum
10
+ from pathlib import Path
11
+ from typing import Final, Literal
12
+
13
+ from pydantic import (
14
+ BaseModel,
15
+ ConfigDict,
16
+ Field,
17
+ field_serializer,
18
+ field_validator,
19
+ model_validator,
20
+ )
21
+
22
+
23
+ class StrictDocumentModel(BaseModel):
24
+ """Base model for immutable, schema-strict document harness entities."""
25
+
26
+ model_config = ConfigDict(frozen=True, extra="forbid", strict=True)
27
+
28
+
29
+ class DocumentFormat(StrEnum):
30
+ """Supported public-document artifact formats."""
31
+
32
+ hwpx = "hwpx"
33
+ hwp = "hwp"
34
+ owpml = "owpml"
35
+ docx = "docx"
36
+ doc = "doc"
37
+ pdf = "pdf"
38
+ xlsx = "xlsx"
39
+ xls = "xls"
40
+ pptx = "pptx"
41
+ ppt = "ppt"
42
+ odt = "odt"
43
+ ods = "ods"
44
+ odp = "odp"
45
+ html = "html"
46
+ htm = "htm"
47
+ txt = "txt"
48
+ rtf = "rtf"
49
+ md = "md"
50
+ epub = "epub"
51
+ csv = "csv"
52
+ tsv = "tsv"
53
+ xml = "xml"
54
+ rdf = "rdf"
55
+ ttl = "ttl"
56
+ lod = "lod"
57
+ json = "json"
58
+ jsonl = "jsonl"
59
+ yaml = "yaml"
60
+ yml = "yml"
61
+ geojson = "geojson"
62
+ gpx = "gpx"
63
+ kml = "kml"
64
+ fasta = "fasta"
65
+ sgml = "sgml"
66
+ dtd = "dtd"
67
+ python = "py"
68
+ hml = "hml"
69
+ zip = "zip"
70
+ seven_z = "7z"
71
+ tar = "tar"
72
+ gz = "gz"
73
+ etc = "etc"
74
+
75
+
76
+ class KnownDocumentFormat(StrEnum):
77
+ """Known national-infrastructure document families, promoted or blocked."""
78
+
79
+ hwpx = "hwpx"
80
+ hwp = "hwp"
81
+ hml = "hml"
82
+ owpml = "owpml"
83
+ docx = "docx"
84
+ xlsx = "xlsx"
85
+ pptx = "pptx"
86
+ doc = "doc"
87
+ xls = "xls"
88
+ ppt = "ppt"
89
+ pdf = "pdf"
90
+ pdfa = "pdfa"
91
+ odt = "odt"
92
+ ods = "ods"
93
+ odp = "odp"
94
+ html = "html"
95
+ htm = "htm"
96
+ txt = "txt"
97
+ rtf = "rtf"
98
+ md = "md"
99
+ epub = "epub"
100
+ csv = "csv"
101
+ tsv = "tsv"
102
+ xml = "xml"
103
+ rdf = "rdf"
104
+ ttl = "ttl"
105
+ lod = "lod"
106
+ json = "json"
107
+ jsonl = "jsonl"
108
+ yaml = "yaml"
109
+ yml = "yml"
110
+ geojson = "geojson"
111
+ gpx = "gpx"
112
+ kml = "kml"
113
+ fasta = "fasta"
114
+ sgml = "sgml"
115
+ dtd = "dtd"
116
+ python = "py"
117
+ png = "png"
118
+ jpg = "jpg"
119
+ jpeg = "jpeg"
120
+ gif = "gif"
121
+ tif = "tif"
122
+ tiff = "tiff"
123
+ bmp = "bmp"
124
+ webp = "webp"
125
+ shp = "shp"
126
+ shx = "shx"
127
+ dbf = "dbf"
128
+ prj = "prj"
129
+ stl = "stl"
130
+ wav = "wav"
131
+ mp3 = "mp3"
132
+ mp4 = "mp4"
133
+ zip = "zip"
134
+ seven_z = "7z"
135
+ tar = "tar"
136
+ gz = "gz"
137
+ etc = "etc"
138
+
139
+
140
+ class DocumentFormatFamily(StrEnum):
141
+ """Coarse document family used before adapter promotion."""
142
+
143
+ hwp = "hwp"
144
+ ooxml = "ooxml"
145
+ legacy_office = "legacy_office"
146
+ pdf = "pdf"
147
+ odf = "odf"
148
+ text_web_export = "text_web_export"
149
+ data_file = "data_file"
150
+ image_scan = "image_scan"
151
+ geospatial_data = "geospatial_data"
152
+ media_asset = "media_asset"
153
+ code_file = "code_file"
154
+ archive = "archive"
155
+
156
+
157
+ PROMOTED_RUNTIME_DOCUMENT_FORMATS: Final[tuple[DocumentFormat, ...]] = (
158
+ DocumentFormat.hwpx,
159
+ DocumentFormat.hwp,
160
+ DocumentFormat.owpml,
161
+ DocumentFormat.docx,
162
+ DocumentFormat.pdf,
163
+ DocumentFormat.xlsx,
164
+ DocumentFormat.pptx,
165
+ DocumentFormat.odt,
166
+ DocumentFormat.ods,
167
+ DocumentFormat.odp,
168
+ DocumentFormat.html,
169
+ DocumentFormat.htm,
170
+ DocumentFormat.txt,
171
+ DocumentFormat.rtf,
172
+ DocumentFormat.md,
173
+ DocumentFormat.epub,
174
+ DocumentFormat.csv,
175
+ DocumentFormat.tsv,
176
+ DocumentFormat.xml,
177
+ DocumentFormat.rdf,
178
+ DocumentFormat.ttl,
179
+ DocumentFormat.lod,
180
+ DocumentFormat.json,
181
+ DocumentFormat.jsonl,
182
+ DocumentFormat.yaml,
183
+ DocumentFormat.yml,
184
+ DocumentFormat.geojson,
185
+ DocumentFormat.gpx,
186
+ DocumentFormat.kml,
187
+ DocumentFormat.fasta,
188
+ DocumentFormat.sgml,
189
+ DocumentFormat.dtd,
190
+ DocumentFormat.python,
191
+ DocumentFormat.hml,
192
+ DocumentFormat.zip,
193
+ DocumentFormat.seven_z,
194
+ DocumentFormat.tar,
195
+ DocumentFormat.gz,
196
+ DocumentFormat.etc,
197
+ )
198
+
199
+ KNOWN_DOCUMENT_FORMAT_FAMILIES: Final[dict[KnownDocumentFormat, DocumentFormatFamily]] = {
200
+ KnownDocumentFormat.hwpx: DocumentFormatFamily.hwp,
201
+ KnownDocumentFormat.hwp: DocumentFormatFamily.hwp,
202
+ KnownDocumentFormat.hml: DocumentFormatFamily.data_file,
203
+ KnownDocumentFormat.owpml: DocumentFormatFamily.hwp,
204
+ KnownDocumentFormat.docx: DocumentFormatFamily.ooxml,
205
+ KnownDocumentFormat.xlsx: DocumentFormatFamily.ooxml,
206
+ KnownDocumentFormat.pptx: DocumentFormatFamily.ooxml,
207
+ KnownDocumentFormat.doc: DocumentFormatFamily.legacy_office,
208
+ KnownDocumentFormat.xls: DocumentFormatFamily.legacy_office,
209
+ KnownDocumentFormat.ppt: DocumentFormatFamily.legacy_office,
210
+ KnownDocumentFormat.pdf: DocumentFormatFamily.pdf,
211
+ KnownDocumentFormat.pdfa: DocumentFormatFamily.pdf,
212
+ KnownDocumentFormat.odt: DocumentFormatFamily.odf,
213
+ KnownDocumentFormat.ods: DocumentFormatFamily.odf,
214
+ KnownDocumentFormat.odp: DocumentFormatFamily.odf,
215
+ KnownDocumentFormat.html: DocumentFormatFamily.text_web_export,
216
+ KnownDocumentFormat.htm: DocumentFormatFamily.text_web_export,
217
+ KnownDocumentFormat.txt: DocumentFormatFamily.text_web_export,
218
+ KnownDocumentFormat.rtf: DocumentFormatFamily.text_web_export,
219
+ KnownDocumentFormat.md: DocumentFormatFamily.text_web_export,
220
+ KnownDocumentFormat.epub: DocumentFormatFamily.archive,
221
+ KnownDocumentFormat.csv: DocumentFormatFamily.data_file,
222
+ KnownDocumentFormat.tsv: DocumentFormatFamily.data_file,
223
+ KnownDocumentFormat.xml: DocumentFormatFamily.data_file,
224
+ KnownDocumentFormat.rdf: DocumentFormatFamily.data_file,
225
+ KnownDocumentFormat.ttl: DocumentFormatFamily.data_file,
226
+ KnownDocumentFormat.lod: DocumentFormatFamily.data_file,
227
+ KnownDocumentFormat.json: DocumentFormatFamily.data_file,
228
+ KnownDocumentFormat.jsonl: DocumentFormatFamily.data_file,
229
+ KnownDocumentFormat.yaml: DocumentFormatFamily.data_file,
230
+ KnownDocumentFormat.yml: DocumentFormatFamily.data_file,
231
+ KnownDocumentFormat.geojson: DocumentFormatFamily.data_file,
232
+ KnownDocumentFormat.gpx: DocumentFormatFamily.data_file,
233
+ KnownDocumentFormat.kml: DocumentFormatFamily.data_file,
234
+ KnownDocumentFormat.fasta: DocumentFormatFamily.data_file,
235
+ KnownDocumentFormat.sgml: DocumentFormatFamily.data_file,
236
+ KnownDocumentFormat.dtd: DocumentFormatFamily.data_file,
237
+ KnownDocumentFormat.python: DocumentFormatFamily.code_file,
238
+ KnownDocumentFormat.png: DocumentFormatFamily.image_scan,
239
+ KnownDocumentFormat.jpg: DocumentFormatFamily.image_scan,
240
+ KnownDocumentFormat.jpeg: DocumentFormatFamily.image_scan,
241
+ KnownDocumentFormat.gif: DocumentFormatFamily.image_scan,
242
+ KnownDocumentFormat.tif: DocumentFormatFamily.image_scan,
243
+ KnownDocumentFormat.tiff: DocumentFormatFamily.image_scan,
244
+ KnownDocumentFormat.bmp: DocumentFormatFamily.image_scan,
245
+ KnownDocumentFormat.webp: DocumentFormatFamily.image_scan,
246
+ KnownDocumentFormat.shp: DocumentFormatFamily.geospatial_data,
247
+ KnownDocumentFormat.shx: DocumentFormatFamily.geospatial_data,
248
+ KnownDocumentFormat.dbf: DocumentFormatFamily.geospatial_data,
249
+ KnownDocumentFormat.prj: DocumentFormatFamily.geospatial_data,
250
+ KnownDocumentFormat.stl: DocumentFormatFamily.geospatial_data,
251
+ KnownDocumentFormat.wav: DocumentFormatFamily.media_asset,
252
+ KnownDocumentFormat.mp3: DocumentFormatFamily.media_asset,
253
+ KnownDocumentFormat.mp4: DocumentFormatFamily.media_asset,
254
+ KnownDocumentFormat.zip: DocumentFormatFamily.archive,
255
+ KnownDocumentFormat.seven_z: DocumentFormatFamily.archive,
256
+ KnownDocumentFormat.tar: DocumentFormatFamily.archive,
257
+ KnownDocumentFormat.gz: DocumentFormatFamily.archive,
258
+ KnownDocumentFormat.etc: DocumentFormatFamily.data_file,
259
+ }
260
+
261
+
262
+ class ArtifactLineage(StrEnum):
263
+ """Artifact lifecycle category."""
264
+
265
+ source = "source"
266
+ working_copy = "working_copy"
267
+ render = "render"
268
+ validation_report = "validation_report"
269
+ export = "export"
270
+
271
+
272
+ class SecurityState(StrEnum):
273
+ """Pre-parse security decision for an artifact."""
274
+
275
+ accepted = "accepted"
276
+ blocked = "blocked"
277
+ needs_manual_review = "needs_manual_review"
278
+
279
+
280
+ class BlockedReason(StrEnum):
281
+ """Machine-readable blocked reasons shared by intake and tool results."""
282
+
283
+ unsupported_format = "unsupported_format"
284
+ extension_mismatch = "extension_mismatch"
285
+ signature_mismatch = "signature_mismatch"
286
+ mime_mismatch = "mime_mismatch"
287
+ encrypted = "encrypted"
288
+ corrupt = "corrupt"
289
+ macro_detected = "macro_detected"
290
+ external_link_detected = "external_link_detected"
291
+ oversized_raw_bytes = "oversized_raw_bytes"
292
+ oversized_expanded_bytes = "oversized_expanded_bytes"
293
+ package_entry_limit_exceeded = "package_entry_limit_exceeded"
294
+ path_traversal_detected = "path_traversal_detected"
295
+ hidden_destination = "hidden_destination"
296
+ public_root_destination = "public_root_destination"
297
+ static_pdf = "static_pdf"
298
+ scanned_pdf = "scanned_pdf"
299
+ xfa_detected = "xfa_detected"
300
+ signature_detected = "signature_detected"
301
+ unsupported_operation = "unsupported_operation"
302
+ validation_failed = "validation_failed"
303
+ permission_denied = "permission_denied"
304
+
305
+
306
+ class PromotionCapability(StrEnum):
307
+ """Capability evaluated by the format promotion gate."""
308
+
309
+ read = "read"
310
+ extract = "extract"
311
+ write = "write"
312
+ style = "style"
313
+ render = "render"
314
+ validate = "validate"
315
+
316
+
317
+ class PromotionState(StrEnum):
318
+ """Model-visible capability state after scorecard evaluation."""
319
+
320
+ blocked = "blocked"
321
+ read_only = "read_only"
322
+ write_enabled = "write_enabled"
323
+ style_enabled = "style_enabled"
324
+
325
+
326
+ class PromotionChecklistStatus(StrEnum):
327
+ """Promotion checklist status for deferred capabilities."""
328
+
329
+ required = "required"
330
+ passed = "passed"
331
+ failed = "failed"
332
+
333
+
334
+ class OperationType(StrEnum):
335
+ """Supported document patch operation kinds."""
336
+
337
+ set_field_value = "set_field_value"
338
+ set_table_cell = "set_table_cell"
339
+ replace_text = "replace_text"
340
+ insert_paragraph = "insert_paragraph"
341
+ set_paragraph_style = "set_paragraph_style"
342
+ set_run_style = "set_run_style"
343
+ set_cell_style = "set_cell_style"
344
+ set_document_metadata = "set_document_metadata"
345
+ copy_for_edit = "copy_for_edit"
346
+
347
+
348
+ class PrimitiveName(Enum):
349
+ """Existing UMMAYA root primitive family used by document tools."""
350
+
351
+ find = "find"
352
+ check = "check"
353
+ send = "send"
354
+
355
+
356
+ class PermissionState(StrEnum):
357
+ """Permission state attached to a document tool call."""
358
+
359
+ not_required = "not_required"
360
+ requested = "requested"
361
+ approved = "approved"
362
+ denied = "denied"
363
+
364
+
365
+ class ToolResultStatus(StrEnum):
366
+ """Structured tool result status."""
367
+
368
+ ok = "ok"
369
+ blocked = "blocked"
370
+ failed = "failed"
371
+ needs_input = "needs_input"
372
+
373
+
374
+ class DocumentWorkflowStepStatus(StrEnum):
375
+ """Model-visible document workflow step state."""
376
+
377
+ pending = "pending"
378
+ completed = "completed"
379
+ current = "current"
380
+ blocked = "blocked"
381
+ failed = "failed"
382
+ skipped = "skipped"
383
+
384
+
385
+ class ValidationDecision(StrEnum):
386
+ """Public-form conformance decision."""
387
+
388
+ pass_ = "pass" # noqa: S105 - public-form decision value, not a secret.
389
+ fail = "fail"
390
+ blocked = "blocked"
391
+ needs_manual_review = "needs_manual_review"
392
+
393
+
394
+ class ValidationReadiness(StrEnum):
395
+ """Machine-readable readiness state for validated public-form derivatives."""
396
+
397
+ ready_for_review = "ready_for_review"
398
+ not_ready = "not_ready"
399
+ blocked = "blocked"
400
+ unsupported = "unsupported"
401
+
402
+
403
+ class ProtectedRangeCategory(StrEnum):
404
+ """Public-document ranges that require explicit review before mutation."""
405
+
406
+ legal_text = "legal_text"
407
+ consent = "consent"
408
+ signature = "signature"
409
+ seal = "seal"
410
+ identity_number = "identity_number"
411
+ address = "address"
412
+ phone_number = "phone_number"
413
+ bank_account = "bank_account"
414
+ fixed_notice = "fixed_notice"
415
+ health_data = "health_data"
416
+ other_sensitive = "other_sensitive"
417
+
418
+
419
+ type StyleAlignment = Literal["left", "center", "right", "justify", "distributed"]
420
+ type FieldType = Literal[
421
+ "text",
422
+ "number",
423
+ "date",
424
+ "choice",
425
+ "checkbox",
426
+ "signature",
427
+ "attachment",
428
+ "unknown",
429
+ ]
430
+ type DestinationPolicy = Literal["working_copy", "export", "validation_only"]
431
+ type RuntimeKind = Literal[
432
+ "python",
433
+ "external_cli",
434
+ "node_bridge",
435
+ "rust_bridge",
436
+ "manual_reference",
437
+ ]
438
+ type SecurityFindingSeverity = Literal["blocked", "warning", "info"]
439
+ type ValidationFindingSeverity = Literal["hard_failure", "warning", "informational"]
440
+ type DocumentIntentOperation = Literal[
441
+ "inspect",
442
+ "extract",
443
+ "fill",
444
+ "style",
445
+ "validate",
446
+ "save",
447
+ "summarize",
448
+ ]
449
+ type ProtectedRangeOperation = Literal[
450
+ "autonomous_fill",
451
+ "replace_text",
452
+ "delete",
453
+ "style",
454
+ "send",
455
+ ]
456
+ type DocumentViewportAnchorStrategy = Literal[
457
+ "exact_text_run",
458
+ "table_cell",
459
+ "field_locator",
460
+ "overlay_marker",
461
+ "visual_bbox",
462
+ "unavailable",
463
+ ]
464
+ type ScalarValue = str | int | Decimal | bool | date | datetime | None
465
+ type MetadataValue = str | int | Decimal | bool | date | datetime | None
466
+ type RequestScalar = str | int | float | bool | None
467
+ type RequestValue = RequestScalar | list[RequestScalar] | dict[str, RequestScalar]
468
+
469
+
470
+ class BorderDescriptor(StrictDocumentModel):
471
+ """Portable border description for document styles."""
472
+
473
+ style: str
474
+ width_pt: Decimal | None = Field(default=None, gt=0)
475
+ color_rgb: str | None = Field(default=None, pattern=r"^[0-9A-Fa-f]{6}$")
476
+
477
+
478
+ class StyleDescriptor(StrictDocumentModel):
479
+ """Portable style representation shared by format adapters."""
480
+
481
+ style_id: str
482
+ target_path: str
483
+ font_family: str | None = None
484
+ font_size_pt: Decimal | None = Field(default=None, gt=0)
485
+ bold: bool | None = None
486
+ italic: bool | None = None
487
+ underline: bool | None = None
488
+ font_color_rgb: str | None = Field(default=None, pattern=r"^[0-9A-Fa-f]{6}$")
489
+ fill_color_rgb: str | None = Field(default=None, pattern=r"^[0-9A-Fa-f]{6}$")
490
+ alignment: StyleAlignment | None = None
491
+ line_spacing: Decimal | None = Field(default=None, gt=0)
492
+ border: BorderDescriptor | None = None
493
+ number_format: str | None = None
494
+
495
+
496
+ class FormField(StrictDocumentModel):
497
+ """Fillable or inferred public-form field."""
498
+
499
+ field_id: str
500
+ label: str
501
+ path: str
502
+ field_type: FieldType
503
+ required: bool
504
+ current_value: ScalarValue = None
505
+ allowed_values: list[ScalarValue] = Field(default_factory=list)
506
+ style_constraints: StyleDescriptor | None = None
507
+ source_confidence: Decimal = Field(ge=0, le=1)
508
+
509
+
510
+ class DocumentArtifact(StrictDocumentModel):
511
+ """User-provided source file or generated derivative metadata."""
512
+
513
+ artifact_id: str
514
+ session_id: str
515
+ source_path: Path
516
+ display_name: str
517
+ format: DocumentFormat
518
+ mime_type: str
519
+ sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
520
+ byte_size: int = Field(ge=0)
521
+ expanded_byte_size: int = Field(ge=0)
522
+ page_count: int | None = Field(default=None, ge=0)
523
+ sheet_count: int | None = Field(default=None, ge=0)
524
+ slide_count: int | None = Field(default=None, ge=0)
525
+ section_count: int | None = Field(default=None, ge=0)
526
+ created_at: datetime
527
+ lineage: ArtifactLineage
528
+ parent_artifact_id: str | None = None
529
+ security_state: SecurityState
530
+ blocked_reason: BlockedReason | None = None
531
+
532
+ @field_validator("source_path")
533
+ @classmethod
534
+ def _canonicalize_source_path(cls, value: Path) -> Path:
535
+ resolved = value.expanduser().resolve()
536
+ if not resolved.is_absolute():
537
+ raise ValueError("source_path must be absolute")
538
+ return resolved
539
+
540
+ @model_validator(mode="after")
541
+ def _enforce_lineage_and_security(self) -> DocumentArtifact:
542
+ if self.lineage is ArtifactLineage.source and self.parent_artifact_id is not None:
543
+ raise ValueError("source artifacts cannot have parent_artifact_id")
544
+ if self.lineage is not ArtifactLineage.source and self.parent_artifact_id is None:
545
+ raise ValueError("Derivative artifacts require parent_artifact_id")
546
+ if self.security_state is SecurityState.blocked and self.blocked_reason is None:
547
+ raise ValueError("blocked artifacts require blocked_reason")
548
+ if self.security_state is not SecurityState.blocked and self.blocked_reason is not None:
549
+ raise ValueError("blocked_reason is only valid for blocked artifacts")
550
+ return self
551
+
552
+
553
+ class DocumentSavedExport(StrictDocumentModel):
554
+ """A reviewed derivative written to a user-visible local path."""
555
+
556
+ export_artifact_id: str
557
+ source_artifact_id: str
558
+ local_path: Path | None = None
559
+ sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
560
+ byte_size: int = Field(ge=0)
561
+ overwrite_existing: bool
562
+
563
+ @field_validator("local_path")
564
+ @classmethod
565
+ def _canonicalize_local_path(cls, value: Path | None) -> Path | None:
566
+ if value is None:
567
+ return None
568
+ resolved = value.expanduser().resolve()
569
+ if not resolved.is_absolute():
570
+ raise ValueError("local_path must be absolute")
571
+ return resolved
572
+
573
+
574
+ class ParagraphBlock(StrictDocumentModel):
575
+ """Normalized paragraph block."""
576
+
577
+ block_id: str
578
+ text: str
579
+ source_path: str
580
+ style_id: str | None = None
581
+
582
+
583
+ class TableCell(StrictDocumentModel):
584
+ """Normalized table cell with coordinate and span metadata."""
585
+
586
+ row_index: int = Field(ge=0)
587
+ column_index: int = Field(ge=0)
588
+ text: str
589
+ row_span: int = Field(default=1, ge=1)
590
+ column_span: int = Field(default=1, ge=1)
591
+ source_path: str
592
+ field_path: str | None = None
593
+
594
+
595
+ class TableBlock(StrictDocumentModel):
596
+ """Normalized table block."""
597
+
598
+ block_id: str
599
+ source_path: str
600
+ cells: list[TableCell] = Field(default_factory=list)
601
+
602
+
603
+ class ImageReference(StrictDocumentModel):
604
+ """Reference to embedded media in a document artifact."""
605
+
606
+ image_id: str
607
+ source_path: str
608
+ content_type: str
609
+ alt_text: str | None = None
610
+
611
+
612
+ class DocumentExtraction(StrictDocumentModel):
613
+ """Normalized document content used by the LLM and validators."""
614
+
615
+ artifact_id: str
616
+ paragraphs: list[ParagraphBlock] = Field(default_factory=list)
617
+ tables: list[TableBlock] = Field(default_factory=list)
618
+ images: list[ImageReference] = Field(default_factory=list)
619
+ fields: list[FormField] = Field(default_factory=list)
620
+ metadata: dict[str, MetadataValue] = Field(default_factory=dict)
621
+ style_map: list[StyleDescriptor] = Field(default_factory=list)
622
+ warnings: list[str] = Field(default_factory=list)
623
+
624
+
625
+ class DocumentPatchOperation(StrictDocumentModel):
626
+ """One ordered mutation request against a working copy."""
627
+
628
+ operation_id: str
629
+ operation_type: OperationType
630
+ target_path: str
631
+ value: ScalarValue = None
632
+ style: StyleDescriptor | None = None
633
+ note: str | None = None
634
+
635
+ @model_validator(mode="after")
636
+ def _enforce_operation_payload(self) -> DocumentPatchOperation:
637
+ style_operations = {
638
+ OperationType.set_paragraph_style,
639
+ OperationType.set_run_style,
640
+ OperationType.set_cell_style,
641
+ }
642
+ value_operations = {
643
+ OperationType.set_field_value,
644
+ OperationType.set_table_cell,
645
+ OperationType.replace_text,
646
+ OperationType.insert_paragraph,
647
+ OperationType.set_document_metadata,
648
+ }
649
+ if self.operation_type in style_operations and self.style is None:
650
+ raise ValueError("style operations require style")
651
+ if self.operation_type in value_operations and self.value is None:
652
+ raise ValueError("value operations require value")
653
+ return self
654
+
655
+
656
+ class DocumentPatch(StrictDocumentModel):
657
+ """Requested document write operation set."""
658
+
659
+ patch_id: str
660
+ target_artifact_id: str
661
+ operations: list[DocumentPatchOperation] = Field(min_length=1)
662
+ dry_run: bool
663
+ expected_format: DocumentFormat
664
+ destination_policy: DestinationPolicy
665
+
666
+
667
+ DocumentChangeType = Literal["field", "table_cell", "text", "style", "metadata", "copy"]
668
+
669
+
670
+ class DocumentChange(StrictDocumentModel):
671
+ """One structured change derived from a document patch operation."""
672
+
673
+ change_id: str
674
+ operation_id: str
675
+ change_type: DocumentChangeType
676
+ target_path: str
677
+ display_label: str | None = None
678
+ before_value: str | None = None
679
+ after_value: str | None = None
680
+
681
+
682
+ class RenderArtifactRecord(StrictDocumentModel):
683
+ """One reviewer-readable render artifact tied to a derivative hash."""
684
+
685
+ render_artifact_id: str
686
+ source_artifact_id: str
687
+ source_sha256: str
688
+ render_sha256: str
689
+ render_path: Path
690
+ render_mime_type: str | None = None
691
+ raster_artifact_ref: str | None = None
692
+ raster_artifact_path: Path | None = None
693
+ raster_mime_type: str | None = None
694
+ page_number: int = Field(ge=1)
695
+ correlation_id: str
696
+ engine_id: str
697
+
698
+
699
+ class DocumentClipRect(StrictDocumentModel):
700
+ """Page-coordinate rectangle for a document viewport crop."""
701
+
702
+ x: Decimal = Field(ge=0)
703
+ y: Decimal = Field(ge=0)
704
+ width: Decimal = Field(gt=0)
705
+ height: Decimal = Field(gt=0)
706
+
707
+
708
+ class DocumentChangedViewport(StrictDocumentModel):
709
+ """Rendered page viewport focused on one or more structured document changes."""
710
+
711
+ viewport_id: str
712
+ change_ids: tuple[str, ...] = Field(min_length=1)
713
+ page_number: int = Field(ge=1)
714
+ source_render_artifact_id: str
715
+ clip_rect: DocumentClipRect
716
+ padding_x: Decimal = Field(default=Decimal("12"), ge=0)
717
+ padding_y: Decimal = Field(default=Decimal("18"), ge=0)
718
+ svg_artifact_ref: str | None = None
719
+ svg_artifact_path: Path | None = None
720
+ png_artifact_ref: str | None = None
721
+ png_artifact_path: Path | None = None
722
+ before_svg_artifact_ref: str | None = None
723
+ before_svg_artifact_path: Path | None = None
724
+ before_png_artifact_ref: str | None = None
725
+ before_png_artifact_path: Path | None = None
726
+ after_svg_artifact_ref: str | None = None
727
+ after_svg_artifact_path: Path | None = None
728
+ after_png_artifact_ref: str | None = None
729
+ after_png_artifact_path: Path | None = None
730
+ text_fallback: tuple[str, ...] = ()
731
+ anchor_strategy: DocumentViewportAnchorStrategy
732
+ confidence: Decimal = Field(ge=0, le=1)
733
+ warnings: tuple[str, ...] = ()
734
+
735
+
736
+ class DocumentViewportCamera(StrictDocumentModel):
737
+ """Viewport camera derived from full-page before/after render artifacts."""
738
+
739
+ source_render_artifact_id: str
740
+ baseline_render_artifact_id: str
741
+ page_index: int = Field(ge=0)
742
+ viewport_rect: DocumentClipRect
743
+ zoom: Decimal = Field(default=Decimal("1"), gt=0)
744
+ change_ids: tuple[str, ...] = Field(min_length=1)
745
+
746
+
747
+ class SourceAnchor(StrictDocumentModel):
748
+ """Stable native-format and optional layout locator for document IR items."""
749
+
750
+ format_path: str = Field(min_length=1)
751
+ page_number: int | None = Field(default=None, ge=1)
752
+ sheet_index: int | None = Field(default=None, ge=0)
753
+ slide_index: int | None = Field(default=None, ge=0)
754
+ bbox: DocumentClipRect | None = None
755
+ confidence: Decimal = Field(ge=0, le=1)
756
+ engine_id: str = Field(min_length=1)
757
+
758
+
759
+ class FormSlot(StrictDocumentModel):
760
+ """Planner-facing fillable slot anchored to native document structure."""
761
+
762
+ slot_id: str = Field(min_length=1)
763
+ label: str = Field(min_length=1)
764
+ field_type: FieldType
765
+ required: bool
766
+ protected: bool = False
767
+ source_anchor: SourceAnchor
768
+ current_value: ScalarValue = None
769
+ candidate_value: ScalarValue = None
770
+ confidence: Decimal = Field(ge=0, le=1)
771
+ evidence_text: str | None = None
772
+
773
+
774
+ class DocumentProtectedRange(StrictDocumentModel):
775
+ """Native document span that the autonomous planner must not mutate silently."""
776
+
777
+ range_id: str = Field(min_length=1)
778
+ category: ProtectedRangeCategory
779
+ label: str = Field(min_length=1)
780
+ source_anchor: SourceAnchor
781
+ reason: str = Field(min_length=1)
782
+ blocked_operations: tuple[ProtectedRangeOperation, ...] = ("autonomous_fill",)
783
+ requires_human_review: bool = True
784
+
785
+ @model_validator(mode="after")
786
+ def _enforce_protected_boundary(self) -> DocumentProtectedRange:
787
+ if not self.blocked_operations:
788
+ raise ValueError("blocked_operations must not be empty")
789
+ if not self.requires_human_review:
790
+ raise ValueError("protected ranges require human review")
791
+ return self
792
+
793
+
794
+ class DocumentIntent(StrictDocumentModel):
795
+ """Bounded natural-language intent inferred for autonomous document work."""
796
+
797
+ intent_id: str = Field(min_length=1)
798
+ operation: DocumentIntentOperation
799
+ instruction: str = Field(min_length=1)
800
+ confidence: Decimal = Field(ge=0, le=1)
801
+
802
+
803
+ class AutonomousStyleIntent(StrictDocumentModel):
804
+ intent_id: str = Field(min_length=1)
805
+ source_slot_id: str = Field(min_length=1)
806
+ target_path: str = Field(min_length=1)
807
+ style: StyleDescriptor
808
+ confidence: Decimal = Field(ge=0, le=1)
809
+
810
+ @model_validator(mode="after")
811
+ def _enforce_style_target(self) -> AutonomousStyleIntent:
812
+ if self.style.target_path != self.target_path:
813
+ raise ValueError("style target_path must match AutonomousStyleIntent target_path")
814
+ return self
815
+
816
+
817
+ class AutonomousSaveIntent(StrictDocumentModel):
818
+ destination_path: str = Field(min_length=1)
819
+ destination_display_name: str = Field(min_length=1)
820
+ confidence: Decimal = Field(ge=0, le=1)
821
+
822
+
823
+ class AutonomousFillPlan(StrictDocumentModel):
824
+ """Deterministic fill plan produced before any document mutation."""
825
+
826
+ plan_id: str = Field(min_length=1)
827
+ artifact_id: str = Field(min_length=1)
828
+ intent: DocumentIntent
829
+ slots: tuple[FormSlot, ...] = ()
830
+ style_intents: tuple[AutonomousStyleIntent, ...] = ()
831
+ save_intent: AutonomousSaveIntent | None = None
832
+ blocked_slot_ids: tuple[str, ...] = ()
833
+ requires_human_review: bool
834
+ confidence: Decimal = Field(ge=0, le=1)
835
+
836
+ @model_validator(mode="after")
837
+ def _enforce_plan_safety(self) -> AutonomousFillPlan:
838
+ slot_ids = {slot.slot_id for slot in self.slots}
839
+ if len(slot_ids) != len(self.slots):
840
+ raise ValueError("AutonomousFillPlan slot_id values must be unique")
841
+ if set(self.blocked_slot_ids) - slot_ids:
842
+ raise ValueError("blocked_slot_ids must reference known slots")
843
+ slots_by_id = {slot.slot_id: slot for slot in self.slots}
844
+ blocked_slot_ids = set(self.blocked_slot_ids)
845
+ style_intent_ids = {style_intent.intent_id for style_intent in self.style_intents}
846
+ if len(style_intent_ids) != len(self.style_intents):
847
+ raise ValueError("AutonomousFillPlan style intent_id values must be unique")
848
+ for style_intent in self.style_intents:
849
+ source_slot = slots_by_id.get(style_intent.source_slot_id)
850
+ if source_slot is None:
851
+ raise ValueError("style_intents must reference known slots")
852
+ if style_intent.source_slot_id in blocked_slot_ids or source_slot.protected:
853
+ raise ValueError("style_intents must not reference protected or blocked slots")
854
+ if style_intent.target_path != source_slot.source_anchor.format_path:
855
+ raise ValueError("style_intents must target the source slot anchor")
856
+ protected_edits = [
857
+ slot for slot in self.slots if slot.protected and slot.candidate_value is not None
858
+ ]
859
+ if protected_edits and not self.requires_human_review:
860
+ raise ValueError("protected slots require human review")
861
+ return self
862
+
863
+
864
+ class DocumentIR(StrictDocumentModel):
865
+ """Format-neutral structured document representation for planning."""
866
+
867
+ artifact_id: str = Field(min_length=1)
868
+ document_format: DocumentFormat
869
+ extraction: DocumentExtraction
870
+ source_anchors: tuple[SourceAnchor, ...] = ()
871
+ form_slots: tuple[FormSlot, ...] = ()
872
+ protected_ranges: tuple[DocumentProtectedRange, ...] = ()
873
+ metadata: dict[str, MetadataValue] = Field(default_factory=dict)
874
+
875
+ @classmethod
876
+ def from_extraction(
877
+ cls,
878
+ *,
879
+ artifact_id: str,
880
+ document_format: DocumentFormat,
881
+ extraction: DocumentExtraction,
882
+ engine_id: str,
883
+ ) -> DocumentIR:
884
+ """Create an initial IR view from existing normalized extraction output."""
885
+ anchors = _source_anchors_from_extraction(extraction, engine_id=engine_id)
886
+ slots, protected_ranges = _field_slots_and_ranges_from_extraction(
887
+ extraction,
888
+ engine_id=engine_id,
889
+ )
890
+ inferred_slots, inferred_ranges = _inferred_slots_and_ranges_from_extraction(
891
+ extraction,
892
+ engine_id=engine_id,
893
+ existing_paths={slot.source_anchor.format_path for slot in slots},
894
+ )
895
+ slots.extend(inferred_slots)
896
+ protected_ranges.extend(inferred_ranges)
897
+
898
+ return cls(
899
+ artifact_id=artifact_id,
900
+ document_format=document_format,
901
+ extraction=extraction,
902
+ source_anchors=tuple(anchors),
903
+ form_slots=tuple(slots),
904
+ protected_ranges=tuple(protected_ranges),
905
+ metadata=extraction.metadata,
906
+ )
907
+
908
+
909
+ def _source_anchors_from_extraction(
910
+ extraction: DocumentExtraction,
911
+ *,
912
+ engine_id: str,
913
+ ) -> list[SourceAnchor]:
914
+ anchors: list[SourceAnchor] = []
915
+ for paragraph in extraction.paragraphs:
916
+ anchors.append(
917
+ _source_anchor_for_path(
918
+ paragraph.source_path,
919
+ confidence=Decimal("1"),
920
+ engine_id=engine_id,
921
+ )
922
+ )
923
+ for table in extraction.tables:
924
+ anchors.append(
925
+ _source_anchor_for_path(
926
+ table.source_path,
927
+ confidence=Decimal("1"),
928
+ engine_id=engine_id,
929
+ )
930
+ )
931
+ for cell in table.cells:
932
+ anchors.append(
933
+ _source_anchor_for_path(
934
+ cell.field_path or cell.source_path,
935
+ confidence=Decimal("1"),
936
+ engine_id=engine_id,
937
+ )
938
+ )
939
+ return anchors
940
+
941
+
942
+ def _field_slots_and_ranges_from_extraction(
943
+ extraction: DocumentExtraction,
944
+ *,
945
+ engine_id: str,
946
+ ) -> tuple[list[FormSlot], list[DocumentProtectedRange]]:
947
+ slots: list[FormSlot] = []
948
+ protected_ranges: list[DocumentProtectedRange] = []
949
+ for field in extraction.fields:
950
+ slot = _form_slot_from_field(field, engine_id=engine_id)
951
+ slots.append(slot)
952
+ protected_category = _protected_category_for_slot(slot)
953
+ if protected_category is not None:
954
+ protected_ranges.append(_protected_range_for_slot(slot, category=protected_category))
955
+ return slots, protected_ranges
956
+
957
+
958
+ def _form_slot_from_field(field: FormField, *, engine_id: str) -> FormSlot:
959
+ source_anchor = _source_anchor_for_path(
960
+ field.path,
961
+ confidence=field.source_confidence,
962
+ engine_id=engine_id,
963
+ )
964
+ protected_category = _protected_range_category_for_text(
965
+ field_type=field.field_type,
966
+ identifier=field.field_id,
967
+ label=field.label,
968
+ path=field.path,
969
+ )
970
+ return FormSlot(
971
+ slot_id=field.field_id,
972
+ label=field.label,
973
+ field_type=field.field_type,
974
+ required=field.required,
975
+ protected=protected_category is not None,
976
+ source_anchor=source_anchor,
977
+ current_value=field.current_value,
978
+ confidence=field.source_confidence,
979
+ )
980
+
981
+
982
+ def _inferred_slots_and_ranges_from_extraction(
983
+ extraction: DocumentExtraction,
984
+ *,
985
+ engine_id: str,
986
+ existing_paths: set[str],
987
+ ) -> tuple[list[FormSlot], list[DocumentProtectedRange]]:
988
+ slots: list[FormSlot] = []
989
+ protected_ranges: list[DocumentProtectedRange] = []
990
+ seen_slot_paths = set(existing_paths)
991
+ inferred_slots = [
992
+ *_table_slots_from_extraction(extraction, engine_id=engine_id),
993
+ *_slide_text_slots_from_extraction(extraction, engine_id=engine_id),
994
+ ]
995
+ for slot in inferred_slots:
996
+ if slot.source_anchor.format_path in seen_slot_paths:
997
+ continue
998
+ protected_slot, protected_range = _protect_slot_if_needed(slot)
999
+ slots.append(protected_slot)
1000
+ seen_slot_paths.add(protected_slot.source_anchor.format_path)
1001
+ if protected_range is not None:
1002
+ protected_ranges.append(protected_range)
1003
+ return slots, protected_ranges
1004
+
1005
+
1006
+ def _table_slots_from_extraction(
1007
+ extraction: DocumentExtraction,
1008
+ *,
1009
+ engine_id: str,
1010
+ ) -> list[FormSlot]:
1011
+ slots: list[FormSlot] = []
1012
+ for table_index, table in enumerate(extraction.tables):
1013
+ slots.extend(
1014
+ _table_slots_from_label_value_pairs(
1015
+ table,
1016
+ table_index=table_index,
1017
+ engine_id=engine_id,
1018
+ )
1019
+ )
1020
+ return slots
1021
+
1022
+
1023
+ def _slide_text_slots_from_extraction(
1024
+ extraction: DocumentExtraction,
1025
+ *,
1026
+ engine_id: str,
1027
+ ) -> list[FormSlot]:
1028
+ slots: list[FormSlot] = []
1029
+ for paragraph_index, paragraph in enumerate(extraction.paragraphs):
1030
+ if not paragraph.source_path.startswith("/slides/"):
1031
+ continue
1032
+ slots.append(
1033
+ FormSlot(
1034
+ slot_id=f"slide_text_{paragraph_index + 1}",
1035
+ label="slide text",
1036
+ field_type="text",
1037
+ required=False,
1038
+ source_anchor=_source_anchor_for_path(
1039
+ paragraph.source_path,
1040
+ confidence=Decimal("0.80"),
1041
+ engine_id=engine_id,
1042
+ ),
1043
+ current_value=paragraph.text,
1044
+ confidence=Decimal("0.80"),
1045
+ )
1046
+ )
1047
+ return slots
1048
+
1049
+
1050
+ def _protect_slot_if_needed(
1051
+ slot: FormSlot,
1052
+ ) -> tuple[FormSlot, DocumentProtectedRange | None]:
1053
+ protected_category = _protected_category_for_slot(slot)
1054
+ if protected_category is None:
1055
+ return slot, None
1056
+ protected_slot = slot.model_copy(update={"protected": True})
1057
+ return (
1058
+ protected_slot,
1059
+ _protected_range_for_slot(protected_slot, category=protected_category),
1060
+ )
1061
+
1062
+
1063
+ def _protected_category_for_slot(slot: FormSlot) -> ProtectedRangeCategory | None:
1064
+ return _protected_range_category_for_text(
1065
+ field_type=slot.field_type,
1066
+ identifier=slot.slot_id,
1067
+ label=slot.label,
1068
+ path=slot.source_anchor.format_path,
1069
+ )
1070
+
1071
+
1072
+ def _table_slots_from_label_value_pairs(
1073
+ table: TableBlock,
1074
+ *,
1075
+ table_index: int,
1076
+ engine_id: str,
1077
+ ) -> list[FormSlot]:
1078
+ slots: list[FormSlot] = []
1079
+ rows: dict[int, list[TableCell]] = {}
1080
+ for cell in table.cells:
1081
+ rows.setdefault(cell.row_index, []).append(cell)
1082
+ for row_index, row_cells in sorted(rows.items()):
1083
+ ordered_cells = sorted(row_cells, key=lambda cell: cell.column_index)
1084
+ paired_slots = _table_slots_from_left_label_cells(
1085
+ ordered_cells,
1086
+ table_index=table_index,
1087
+ row_index=row_index,
1088
+ engine_id=engine_id,
1089
+ blank_cells_only=False,
1090
+ )
1091
+ paired_slots.extend(
1092
+ _table_slots_from_left_label_cells(
1093
+ ordered_cells,
1094
+ table_index=table_index,
1095
+ row_index=row_index,
1096
+ engine_id=engine_id,
1097
+ blank_cells_only=True,
1098
+ )
1099
+ )
1100
+ if paired_slots:
1101
+ slots.extend(paired_slots)
1102
+ continue
1103
+ adjacent_slot = _adjacent_table_slot_from_label_value_cells(
1104
+ ordered_cells,
1105
+ table_index=table_index,
1106
+ row_index=row_index,
1107
+ engine_id=engine_id,
1108
+ )
1109
+ if adjacent_slot is not None:
1110
+ slots.append(adjacent_slot)
1111
+ return slots
1112
+
1113
+
1114
+ def _table_slots_from_left_label_cells(
1115
+ ordered_cells: list[TableCell],
1116
+ *,
1117
+ table_index: int,
1118
+ row_index: int,
1119
+ engine_id: str,
1120
+ blank_cells_only: bool,
1121
+ ) -> list[FormSlot]:
1122
+ slots: list[FormSlot] = []
1123
+ for value_cell in ordered_cells:
1124
+ if blank_cells_only:
1125
+ if value_cell.text.strip() or value_cell.field_path is not None:
1126
+ continue
1127
+ elif value_cell.field_path is None:
1128
+ continue
1129
+ label_cell = _nearest_left_table_label_cell(ordered_cells, value_cell)
1130
+ if label_cell is None:
1131
+ continue
1132
+ slots.append(
1133
+ _table_slot_from_label_value_cell(
1134
+ label_cell.text.strip(),
1135
+ value_cell,
1136
+ table_index=table_index,
1137
+ row_index=row_index,
1138
+ engine_id=engine_id,
1139
+ )
1140
+ )
1141
+ return slots
1142
+
1143
+
1144
+ def _adjacent_table_slot_from_label_value_cells(
1145
+ ordered_cells: list[TableCell],
1146
+ *,
1147
+ table_index: int,
1148
+ row_index: int,
1149
+ engine_id: str,
1150
+ ) -> FormSlot | None:
1151
+ for label_cell, value_cell in zip(ordered_cells, ordered_cells[1:], strict=False):
1152
+ label = label_cell.text.strip()
1153
+ if not _meaningful_table_slot_label(label):
1154
+ continue
1155
+ return _table_slot_from_label_value_cell(
1156
+ label,
1157
+ value_cell,
1158
+ table_index=table_index,
1159
+ row_index=row_index,
1160
+ engine_id=engine_id,
1161
+ )
1162
+ return None
1163
+
1164
+
1165
+ def _nearest_left_table_label_cell(
1166
+ ordered_cells: list[TableCell],
1167
+ value_cell: TableCell,
1168
+ ) -> TableCell | None:
1169
+ left_cells = [
1170
+ candidate for candidate in ordered_cells if candidate.column_index < value_cell.column_index
1171
+ ]
1172
+ for candidate in reversed(left_cells):
1173
+ if _meaningful_table_slot_label(candidate.text.strip()):
1174
+ return candidate
1175
+ return None
1176
+
1177
+
1178
+ def _meaningful_table_slot_label(label: str) -> bool:
1179
+ normalized = _normalized_protected_field_key(label)
1180
+ return len(normalized) >= 2
1181
+
1182
+
1183
+ def _table_slot_from_label_value_cell(
1184
+ label: str,
1185
+ value_cell: TableCell,
1186
+ *,
1187
+ table_index: int,
1188
+ row_index: int,
1189
+ engine_id: str,
1190
+ ) -> FormSlot:
1191
+ cleaned_label = _clean_table_slot_label(label)
1192
+ value_path = value_cell.field_path or value_cell.source_path
1193
+ return FormSlot(
1194
+ slot_id=_form_slot_id_for_label(
1195
+ cleaned_label,
1196
+ fallback=f"table_{table_index + 1}_{row_index + 1}_{value_cell.column_index + 1}",
1197
+ ),
1198
+ label=cleaned_label,
1199
+ field_type="text",
1200
+ required=False,
1201
+ source_anchor=_source_anchor_for_path(
1202
+ value_path,
1203
+ confidence=Decimal("0.85"),
1204
+ engine_id=engine_id,
1205
+ sheet_index=table_index,
1206
+ ),
1207
+ current_value=value_cell.text,
1208
+ confidence=Decimal("0.85"),
1209
+ )
1210
+
1211
+
1212
+ def _clean_table_slot_label(label: str) -> str:
1213
+ cleaned = label.strip()
1214
+ wrappers = (("(", ")"), ("[", "]"), ("(", ")"))
1215
+ for opening, closing in wrappers:
1216
+ if cleaned.startswith(opening) and cleaned.endswith(closing):
1217
+ return cleaned[1:-1].strip()
1218
+ return cleaned
1219
+
1220
+
1221
+ def _source_anchor_for_path(
1222
+ format_path: str,
1223
+ *,
1224
+ confidence: Decimal,
1225
+ engine_id: str,
1226
+ sheet_index: int | None = None,
1227
+ ) -> SourceAnchor:
1228
+ slide_index = _slide_index_for_path(format_path)
1229
+ resolved_sheet_index = sheet_index
1230
+ if resolved_sheet_index is None and format_path.startswith("/sheets/"):
1231
+ resolved_sheet_index = 0
1232
+ return SourceAnchor(
1233
+ format_path=format_path,
1234
+ sheet_index=resolved_sheet_index,
1235
+ slide_index=slide_index,
1236
+ confidence=confidence,
1237
+ engine_id=engine_id,
1238
+ )
1239
+
1240
+
1241
+ def _slide_index_for_path(format_path: str) -> int | None:
1242
+ match = re.match(r"^/slides/(?P<slide_number>[0-9]+)(?:/|$)", format_path)
1243
+ if match is None:
1244
+ return None
1245
+ return max(int(match.group("slide_number")) - 1, 0)
1246
+
1247
+
1248
+ def _form_slot_id_for_label(label: str, *, fallback: str) -> str:
1249
+ normalized = _normalized_protected_field_key(label)
1250
+ if normalized:
1251
+ return normalized
1252
+ return fallback
1253
+
1254
+
1255
+ def _protected_range_for_slot(
1256
+ slot: FormSlot,
1257
+ *,
1258
+ category: ProtectedRangeCategory,
1259
+ ) -> DocumentProtectedRange:
1260
+ return DocumentProtectedRange(
1261
+ range_id=f"protected-{slot.slot_id}",
1262
+ category=category,
1263
+ label=slot.label,
1264
+ source_anchor=slot.source_anchor,
1265
+ reason=f"{slot.label} requires explicit human review before mutation.",
1266
+ )
1267
+
1268
+
1269
+ def _protected_range_category_for_text(
1270
+ *,
1271
+ field_type: FieldType,
1272
+ identifier: str,
1273
+ label: str,
1274
+ path: str,
1275
+ ) -> ProtectedRangeCategory | None:
1276
+ field_key = _normalized_protected_field_key(f"{identifier} {label} {path}")
1277
+ if field_type == "signature" or any(token in field_key for token in ("서명", "signature")):
1278
+ return ProtectedRangeCategory.signature
1279
+ if any(token in field_key for token in ("동의", "consent")):
1280
+ return ProtectedRangeCategory.consent
1281
+ if any(
1282
+ token in field_key
1283
+ for token in (
1284
+ "성명",
1285
+ "이름",
1286
+ "신청인",
1287
+ "applicant",
1288
+ "주민등록",
1289
+ "identity",
1290
+ "residentregistration",
1291
+ )
1292
+ ):
1293
+ return ProtectedRangeCategory.identity_number
1294
+ if any(token in field_key for token in ("주소", "address")):
1295
+ return ProtectedRangeCategory.address
1296
+ if any(token in field_key for token in ("전화", "휴대폰", "phone", "mobile")):
1297
+ return ProtectedRangeCategory.phone_number
1298
+ if any(token in field_key for token in ("계좌", "은행", "bank", "account")):
1299
+ return ProtectedRangeCategory.bank_account
1300
+ if any(token in field_key for token in ("인감", "날인", "seal")):
1301
+ return ProtectedRangeCategory.seal
1302
+ if any(token in field_key for token in ("고지", "fixednotice", "notice")):
1303
+ return ProtectedRangeCategory.fixed_notice
1304
+ if any(token in field_key for token in ("진료", "건강", "health", "medical")):
1305
+ return ProtectedRangeCategory.health_data
1306
+ return None
1307
+
1308
+
1309
+ def _normalized_protected_field_key(value: str) -> str:
1310
+ return "".join(character for character in value.casefold() if character.isalnum())
1311
+
1312
+
1313
+ class DocumentDiff(StrictDocumentModel):
1314
+ """Diff between one working artifact and its derivative."""
1315
+
1316
+ diff_id: str
1317
+ diff_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
1318
+ resource_ref: str
1319
+ source_artifact_id: str
1320
+ derivative_artifact_id: str
1321
+ changes: tuple[DocumentChange, ...]
1322
+ render_artifacts: tuple[RenderArtifactRecord, ...] = ()
1323
+ baseline_render_artifacts: tuple[RenderArtifactRecord, ...] = ()
1324
+ changed_viewports: tuple[DocumentChangedViewport, ...] = ()
1325
+ viewport_cameras: tuple[DocumentViewportCamera, ...] = ()
1326
+ inline_truncated: bool = False
1327
+ omitted_change_count: int = Field(default=0, ge=0)
1328
+
1329
+
1330
+ class DocumentWorkflowStep(StrictDocumentModel):
1331
+ """One visible step in the public-document authoring workflow."""
1332
+
1333
+ step_id: str
1334
+ label: str
1335
+ status: DocumentWorkflowStepStatus
1336
+ artifact_id: str | None = None
1337
+ artifact_sha256: str | None = Field(default=None, pattern=r"^[0-9a-f]{64}$")
1338
+ detail: str | None = None
1339
+
1340
+
1341
+ class PromotionChecklistItem(StrictDocumentModel):
1342
+ """One required evidence item before promoting a deferred capability."""
1343
+
1344
+ check_id: str
1345
+ capability: PromotionCapability
1346
+ status: PromotionChecklistStatus
1347
+ evidence_required: str
1348
+ detail: str | None = None
1349
+
1350
+
1351
+ class FormatCapabilityProfile(StrictDocumentModel):
1352
+ """Observed support for one format and one engine."""
1353
+
1354
+ profile_id: str
1355
+ format: DocumentFormat
1356
+ engine_name: str
1357
+ engine_version: str
1358
+ license: str
1359
+ runtime: RuntimeKind
1360
+ supports_read: bool
1361
+ supports_extract: bool
1362
+ supports_write: bool
1363
+ supports_style: bool
1364
+ supports_render: bool
1365
+ supports_validation: bool
1366
+ blocked_operations: list[str] = Field(default_factory=list)
1367
+ known_limitations: list[str] = Field(default_factory=list)
1368
+ fixture_results: list[str] = Field(default_factory=list)
1369
+ last_evaluated_at: datetime
1370
+
1371
+ @model_validator(mode="after")
1372
+ def _enforce_hwp_write_boundary(self) -> FormatCapabilityProfile:
1373
+ if self.format is DocumentFormat.hwp and self.supports_write:
1374
+ raise ValueError("HWP binary write is blocked")
1375
+ return self
1376
+
1377
+
1378
+ class PromotionGateResult(StrictDocumentModel):
1379
+ """Scorecard result controlling model-visible format capabilities."""
1380
+
1381
+ gate_id: str
1382
+ profile_id: str
1383
+ capability: PromotionCapability
1384
+ score_total: int = Field(ge=0, le=100)
1385
+ extraction_fidelity: int = Field(ge=0, le=20)
1386
+ write_fidelity: int = Field(ge=0, le=20)
1387
+ style_layout_control: int = Field(ge=0, le=15)
1388
+ deterministic_round_trip: int = Field(ge=0, le=15)
1389
+ public_form_validation: int = Field(ge=0, le=15)
1390
+ security_privacy: int = Field(ge=0, le=10)
1391
+ license_maintenance_tool_usability: int = Field(ge=0, le=5)
1392
+ hard_gates_passed: bool
1393
+ hard_gate_failures: list[str] = Field(default_factory=list)
1394
+ promotion_state: PromotionState
1395
+ promotion_checklist: list[PromotionChecklistItem] = Field(default_factory=list)
1396
+ evidence_record_ids: list[str] = Field(default_factory=list)
1397
+
1398
+ @model_validator(mode="after")
1399
+ def _enforce_promotion_thresholds(self) -> PromotionGateResult:
1400
+ if self.hard_gate_failures and self.promotion_state is not PromotionState.blocked:
1401
+ raise ValueError("hard_gate_failures force blocked promotion_state")
1402
+ if not self.hard_gates_passed and self.promotion_state is not PromotionState.blocked:
1403
+ raise ValueError("failed hard gates force blocked promotion_state")
1404
+ if self.promotion_state is PromotionState.read_only and self.score_total < 75:
1405
+ raise ValueError("read-only promotion requires score_total >= 75")
1406
+ if self.promotion_state is PromotionState.write_enabled and self.score_total < 85:
1407
+ raise ValueError("write promotion requires score_total >= 85")
1408
+ if self.promotion_state is PromotionState.style_enabled and self.score_total < 85:
1409
+ raise ValueError("style promotion requires score_total >= 85")
1410
+ return self
1411
+
1412
+
1413
+ class ValidationFinding(StrictDocumentModel):
1414
+ """Public-form validation finding."""
1415
+
1416
+ finding_id: str
1417
+ severity: ValidationFindingSeverity
1418
+ code: str
1419
+ message: str
1420
+ anchor: str | None = None
1421
+ remediation_hint: str | None = None
1422
+
1423
+
1424
+ class DocumentSecurityFinding(StrictDocumentModel):
1425
+ """Security finding emitted by document intake or processing."""
1426
+
1427
+ finding_id: str
1428
+ severity: SecurityFindingSeverity
1429
+ code: BlockedReason
1430
+ message: str
1431
+ anchor: str | None = None
1432
+
1433
+
1434
+ class DocumentIntakeResult(StrictDocumentModel):
1435
+ """Pre-parse intake result for one local document artifact."""
1436
+
1437
+ tool_id: str
1438
+ correlation_id: str
1439
+ status: ToolResultStatus
1440
+ artifact_refs: list[str] = Field(default_factory=list)
1441
+ source_path: Path
1442
+ display_name: str
1443
+ detected_format: DocumentFormat | None = None
1444
+ known_format: KnownDocumentFormat | None = None
1445
+ format_family: DocumentFormatFamily | None = None
1446
+ expected_format: DocumentFormat | None = None
1447
+ declared_mime_type: str | None = None
1448
+ mime_type: str | None = None
1449
+ byte_size: int = Field(ge=0)
1450
+ expanded_byte_size: int = Field(ge=0)
1451
+ sha256: str | None = Field(default=None, pattern=r"^[0-9a-f]{64}$")
1452
+ security_state: SecurityState
1453
+ blocked_reason: BlockedReason | None = None
1454
+ findings: list[DocumentSecurityFinding] = Field(default_factory=list)
1455
+ next_safe_actions: list[str] = Field(default_factory=list)
1456
+ text_summary: str
1457
+
1458
+ @field_validator("source_path")
1459
+ @classmethod
1460
+ def _canonicalize_source_path(cls, value: Path) -> Path:
1461
+ return value.expanduser().resolve()
1462
+
1463
+ @model_validator(mode="after")
1464
+ def _enforce_blocked_reason(self) -> DocumentIntakeResult:
1465
+ if self.status is ToolResultStatus.blocked and self.blocked_reason is None:
1466
+ raise ValueError("blocked intake results require blocked_reason")
1467
+ if self.status is not ToolResultStatus.blocked and self.blocked_reason is not None:
1468
+ raise ValueError("blocked_reason is only valid for blocked intake results")
1469
+ if self.security_state is SecurityState.blocked and self.blocked_reason is None:
1470
+ raise ValueError("blocked security_state requires blocked_reason")
1471
+ return self
1472
+
1473
+
1474
+ class PublicFormValidationReport(StrictDocumentModel):
1475
+ """Public-form conformance check report."""
1476
+
1477
+ report_id: str
1478
+ artifact_id: str
1479
+ template_id: str
1480
+ schema_id: str
1481
+ paragraph_block_f1: Decimal = Field(ge=0, le=1)
1482
+ table_cell_f1: Decimal = Field(ge=0, le=1)
1483
+ image_reference_f1: Decimal = Field(ge=0, le=1)
1484
+ metadata_exact_match: Decimal = Field(ge=0, le=1)
1485
+ aggregate_score: Decimal = Field(ge=0, le=1)
1486
+ round_trip_passed: bool
1487
+ render_passed: bool
1488
+ security_passed: bool
1489
+ findings: list[ValidationFinding] = Field(default_factory=list)
1490
+ decision: ValidationDecision
1491
+ readiness: ValidationReadiness = ValidationReadiness.not_ready
1492
+
1493
+ @model_validator(mode="after")
1494
+ def _enforce_decision_rules(self) -> PublicFormValidationReport:
1495
+ if not self.security_passed and self.decision is not ValidationDecision.blocked:
1496
+ raise ValueError("failed security check forces blocked decision")
1497
+ if self.decision is ValidationDecision.pass_ and self.aggregate_score < Decimal("0.85"):
1498
+ raise ValueError("pass decision requires aggregate_score >= 0.85")
1499
+ if self.decision is ValidationDecision.pass_ and not self.render_passed:
1500
+ raise ValueError("render mismatch prevents pass decision")
1501
+ if self.decision is ValidationDecision.pass_ and not self.round_trip_passed:
1502
+ raise ValueError("round-trip mismatch prevents pass decision")
1503
+ if (
1504
+ self.decision is ValidationDecision.pass_
1505
+ and self.readiness is not ValidationReadiness.ready_for_review
1506
+ ):
1507
+ raise ValueError("pass decision requires ready_for_review readiness")
1508
+ if (
1509
+ self.readiness is ValidationReadiness.ready_for_review
1510
+ and self.decision is not ValidationDecision.pass_
1511
+ ):
1512
+ raise ValueError("ready_for_review requires pass decision")
1513
+ return self
1514
+
1515
+
1516
+ class DocumentToolCall(StrictDocumentModel):
1517
+ """Tool-loop input envelope for document capabilities."""
1518
+
1519
+ tool_id: str
1520
+ primitive: PrimitiveName
1521
+ correlation_id: str
1522
+ request: dict[str, RequestValue]
1523
+ permission_state: PermissionState
1524
+
1525
+
1526
+ class DocumentToolResult(StrictDocumentModel):
1527
+ """Tool-loop output envelope for document capabilities."""
1528
+
1529
+ tool_id: str
1530
+ correlation_id: str
1531
+ status: ToolResultStatus
1532
+ artifact_refs: list[str] = Field(default_factory=list)
1533
+ extraction: DocumentExtraction | None = None
1534
+ validation_report: PublicFormValidationReport | None = None
1535
+ promotion_gate_result: PromotionGateResult | None = None
1536
+ diff: DocumentDiff | None = None
1537
+ render_artifacts: tuple[RenderArtifactRecord, ...] = ()
1538
+ saved_exports: tuple[DocumentSavedExport, ...] = ()
1539
+ workflow_steps: list[DocumentWorkflowStep] = Field(default_factory=list)
1540
+ findings: list[DocumentSecurityFinding | ValidationFinding] = Field(default_factory=list)
1541
+ text_summary: str
1542
+ blocked_reason: BlockedReason | None = None
1543
+
1544
+ @model_validator(mode="after")
1545
+ def _enforce_blocked_reason(self) -> DocumentToolResult:
1546
+ if self.status is ToolResultStatus.blocked and self.blocked_reason is None:
1547
+ raise ValueError("blocked results require blocked_reason")
1548
+ if self.status is not ToolResultStatus.blocked and self.blocked_reason is not None:
1549
+ raise ValueError("blocked_reason is only valid for blocked results")
1550
+ return self
1551
+
1552
+ @field_serializer("extraction", when_used="json")
1553
+ def _serialize_extraction_for_tool_output(
1554
+ self,
1555
+ extraction: DocumentExtraction | None,
1556
+ ) -> object | None:
1557
+ """Serialize extraction without raw local paths or document-byte markers."""
1558
+ if extraction is None:
1559
+ return None
1560
+ return _sanitize_model_visible_document_payload(extraction.model_dump(mode="json"))
1561
+
1562
+
1563
+ _MODEL_VISIBLE_FORBIDDEN_KEYS = frozenset(
1564
+ {
1565
+ "document_bytes",
1566
+ "raw_bytes",
1567
+ "source_bytes",
1568
+ "source_path",
1569
+ }
1570
+ )
1571
+
1572
+
1573
+ def _sanitize_model_visible_document_payload(value: object) -> object:
1574
+ if isinstance(value, dict):
1575
+ sanitized: dict[str, object] = {}
1576
+ for key, item in value.items():
1577
+ if key in _MODEL_VISIBLE_FORBIDDEN_KEYS:
1578
+ continue
1579
+ sanitized[key] = _sanitize_model_visible_document_payload(item)
1580
+ return sanitized
1581
+ if isinstance(value, list):
1582
+ return [_sanitize_model_visible_document_payload(item) for item in value]
1583
+ if isinstance(value, tuple):
1584
+ return tuple(_sanitize_model_visible_document_payload(item) for item in value)
1585
+ if isinstance(value, str) and _looks_like_raw_document_payload(value):
1586
+ return "[redacted-document-content]"
1587
+ return value
1588
+
1589
+
1590
+ def _looks_like_raw_document_payload(value: str) -> bool:
1591
+ lowered = value.lower()
1592
+ return (
1593
+ "%pdf" in lowered
1594
+ or "raw document bytes" in lowered
1595
+ or "/users/" in lowered
1596
+ or "\\users\\" in lowered
1597
+ or "-----begin " in lowered
1598
+ )