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.
- package/README.md +17 -3
- package/bin/ummaya +10 -1
- package/npm-shrinkwrap.json +253 -2
- package/package.json +5 -1
- package/prompts/manifest.yaml +2 -2
- package/prompts/session_guidance_v1.md +3 -1
- package/prompts/system_v1.md +9 -7
- package/pyproject.toml +26 -7
- package/specs/2803-document-production-hardening/contracts/document-tools.schema.json +1043 -0
- package/src/ummaya/_canonical/__init__.py +2 -0
- package/src/ummaya/context/builder.py +17 -11
- package/src/ummaya/engine/engine.py +30 -113
- package/src/ummaya/engine/query.py +20 -0
- package/src/ummaya/evidence/__init__.py +44 -0
- package/src/ummaya/evidence/__main__.py +7 -0
- package/src/ummaya/evidence/dataset_contract.py +193 -0
- package/src/ummaya/evidence/document_authoring_cases.py +33 -0
- package/src/ummaya/evidence/document_harness.py +313 -0
- package/src/ummaya/evidence/document_viewer_ux.py +391 -0
- package/src/ummaya/evidence/gates.py +70 -0
- package/src/ummaya/evidence/json_types.py +20 -0
- package/src/ummaya/evidence/models.py +145 -0
- package/src/ummaya/evidence/output_payload.py +89 -0
- package/src/ummaya/evidence/payload_documents.py +233 -0
- package/src/ummaya/evidence/route_contracts.py +224 -0
- package/src/ummaya/evidence/route_helpers.py +150 -0
- package/src/ummaya/evidence/runner.py +177 -0
- package/src/ummaya/evidence/source_provenance.py +246 -0
- package/src/ummaya/evidence/source_provenance_redaction.py +176 -0
- package/src/ummaya/evidence/task_registry.py +264 -0
- package/src/ummaya/evidence/tool_layer.py +39 -0
- package/src/ummaya/evidence/tool_layer_models.py +151 -0
- package/src/ummaya/ipc/adapter_manifest_emitter.py +26 -10
- package/src/ummaya/ipc/document_intent_normalization.py +185 -0
- package/src/ummaya/ipc/frame_schema.py +52 -5
- package/src/ummaya/ipc/route_diagnostics.py +73 -0
- package/src/ummaya/ipc/stdio.py +2282 -417
- package/src/ummaya/llm/client.py +234 -59
- package/src/ummaya/llm/config.py +8 -3
- package/src/ummaya/llm/reasoning.py +84 -0
- package/src/ummaya/primitives/__init__.py +6 -2
- package/src/ummaya/primitives/delegation.py +1 -1
- package/src/ummaya/primitives/document.py +28 -0
- package/src/ummaya/settings.py +0 -3
- package/src/ummaya/tools/discovery_bridge.py +34 -2
- package/src/ummaya/tools/documents/__init__.py +297 -0
- package/src/ummaya/tools/documents/adapter_registry.py +487 -0
- package/src/ummaya/tools/documents/archive_container_probe.py +167 -0
- package/src/ummaya/tools/documents/artifact_store.py +454 -0
- package/src/ummaya/tools/documents/authoring.py +283 -0
- package/src/ummaya/tools/documents/baselines.py +114 -0
- package/src/ummaya/tools/documents/capability.py +331 -0
- package/src/ummaya/tools/documents/contracts.py +112 -0
- package/src/ummaya/tools/documents/conversion.py +521 -0
- package/src/ummaya/tools/documents/diff.py +275 -0
- package/src/ummaya/tools/documents/engines.py +163 -0
- package/src/ummaya/tools/documents/evaluation.py +291 -0
- package/src/ummaya/tools/documents/explicit_values.py +108 -0
- package/src/ummaya/tools/documents/fixtures.py +174 -0
- package/src/ummaya/tools/documents/format_completion_audit.py +471 -0
- package/src/ummaya/tools/documents/formats/__init__.py +2 -0
- package/src/ummaya/tools/documents/formats/archive.py +528 -0
- package/src/ummaya/tools/documents/formats/base.py +41 -0
- package/src/ummaya/tools/documents/formats/code_file.py +211 -0
- package/src/ummaya/tools/documents/formats/data_file.py +272 -0
- package/src/ummaya/tools/documents/formats/hwp.py +284 -0
- package/src/ummaya/tools/documents/formats/hwpx.py +1837 -0
- package/src/ummaya/tools/documents/formats/odf.py +435 -0
- package/src/ummaya/tools/documents/formats/ooxml.py +1030 -0
- package/src/ummaya/tools/documents/formats/passive.py +766 -0
- package/src/ummaya/tools/documents/formats/pdf.py +702 -0
- package/src/ummaya/tools/documents/formats/text_web.py +268 -0
- package/src/ummaya/tools/documents/hwp_conversion_probe.py +178 -0
- package/src/ummaya/tools/documents/hwp_direct_candidate.py +141 -0
- package/src/ummaya/tools/documents/inspection.py +289 -0
- package/src/ummaya/tools/documents/intake.py +1079 -0
- package/src/ummaya/tools/documents/legacy_office_promotion_probe.py +366 -0
- package/src/ummaya/tools/documents/models.py +1598 -0
- package/src/ummaya/tools/documents/odf_promotion_probe.py +167 -0
- package/src/ummaya/tools/documents/orchestrator.py +96 -0
- package/src/ummaya/tools/documents/passive_capability_probe.py +251 -0
- package/src/ummaya/tools/documents/patch.py +170 -0
- package/src/ummaya/tools/documents/pdfa_conformance.py +284 -0
- package/src/ummaya/tools/documents/pdfa_promotion_probe.py +198 -0
- package/src/ummaya/tools/documents/permissions.py +110 -0
- package/src/ummaya/tools/documents/planner.py +616 -0
- package/src/ummaya/tools/documents/registry.py +2733 -0
- package/src/ummaya/tools/documents/render.py +978 -0
- package/src/ummaya/tools/documents/render_comparison.py +113 -0
- package/src/ummaya/tools/documents/render_comparison_models.py +74 -0
- package/src/ummaya/tools/documents/render_comparison_regions.py +73 -0
- package/src/ummaya/tools/documents/render_comparison_style.py +161 -0
- package/src/ummaya/tools/documents/reread.py +157 -0
- package/src/ummaya/tools/documents/runtime_authoring.py +244 -0
- package/src/ummaya/tools/documents/runtime_authoring_bundle.py +76 -0
- package/src/ummaya/tools/documents/scorecard.py +184 -0
- package/src/ummaya/tools/documents/socratic_planner.py +193 -0
- package/src/ummaya/tools/documents/style.py +48 -0
- package/src/ummaya/tools/documents/tool_defs.py +523 -0
- package/src/ummaya/tools/documents/validate.py +347 -0
- package/src/ummaya/tools/executor.py +61 -12
- package/src/ummaya/tools/geocoding/kakao_client.py +1 -2
- package/src/ummaya/tools/kma/apihub_catalog.py +984 -1
- package/src/ummaya/tools/kma/apihub_structured_adapter.py +86 -6
- package/src/ummaya/tools/kma/apihub_url_adapter.py +593 -0
- package/src/ummaya/tools/kma/apihub_url_catalog.py +296 -0
- package/src/ummaya/tools/live_proxy.py +0 -3
- package/src/ummaya/tools/location_adapters.py +8 -6
- package/src/ummaya/tools/manifest_metadata.py +16 -3
- package/src/ummaya/tools/models.py +5 -1
- package/src/ummaya/tools/mvp_surface.py +2 -2
- package/src/ummaya/tools/nmc/emergency_search.py +8 -6
- package/src/ummaya/tools/register_all.py +17 -0
- package/src/ummaya/tools/registry.py +10 -1
- package/src/ummaya/tools/resolve_location.py +4 -4
- package/src/ummaya/tools/routing/__init__.py +59 -0
- package/src/ummaya/tools/routing/builder.py +105 -0
- package/src/ummaya/tools/routing/cards.py +29 -0
- package/src/ummaya/tools/routing/decision_service.py +534 -0
- package/src/ummaya/tools/routing/decision_types.py +74 -0
- package/src/ummaya/tools/routing/feasibility.py +122 -0
- package/src/ummaya/tools/routing/intent.py +17 -0
- package/src/ummaya/tools/routing/intent_extractor.py +207 -0
- package/src/ummaya/tools/routing/intent_patterns.py +160 -0
- package/src/ummaya/tools/routing/intent_public_data.py +150 -0
- package/src/ummaya/tools/routing/intent_types.py +48 -0
- package/src/ummaya/tools/routing/lint.py +78 -0
- package/src/ummaya/tools/routing/metadata.py +174 -0
- package/src/ummaya/tools/routing/projection.py +340 -0
- package/src/ummaya/tools/routing/retrieval_policy.py +629 -0
- package/src/ummaya/tools/routing/schema.py +81 -0
- package/src/ummaya/tools/routing/types.py +96 -0
- package/src/ummaya/tools/routing_index.py +2 -2
- package/src/ummaya/tools/search.py +40 -106
- package/src/ummaya/tools/verified_data_go_kr/_manifest.py +115 -25
- package/src/ummaya/tools/verified_data_go_kr/airkorea_air_quality.py +109 -4
- package/src/ummaya/tools/verified_data_go_kr/nmc_aed_site.py +108 -2
- package/src/ummaya/tools/verified_data_go_kr/pps_bid_public_info.py +174 -9
- package/src/ummaya/tools/verified_data_go_kr/tago_bus_arrival.py +66 -3
- package/src/ummaya/tools/verified_data_go_kr/tago_bus_location.py +12 -2
- package/src/ummaya/tools/verified_data_go_kr/tago_bus_route.py +8 -2
- package/src/ummaya/tools/verified_data_go_kr/tago_bus_route_station.py +114 -0
- package/src/ummaya/tools/verified_data_go_kr/tago_bus_station.py +14 -3
- package/src/ummaya/tools/verify_canonical_map.py +21 -0
- package/tests/fixtures/documents/public_forms/baselines.yaml +113 -0
- package/tui/package.json +1 -2
- package/tui/src/.cc-byte-identical-whitelist.yaml +266 -0
- package/tui/src/QueryEngine.ts +12 -4
- package/tui/src/bridge/inboundAttachments.ts +3 -3
- package/tui/src/cli/handlers/auth.ts +4 -13
- package/tui/src/cli/handlers/mcp.tsx +3 -3
- package/tui/src/cli/print.ts +69 -18
- package/tui/src/cli/update.ts +13 -13
- package/tui/src/commands/copy/index.ts +1 -1
- package/tui/src/commands/cost/cost.ts +2 -2
- package/tui/src/commands/init-verifiers.ts +5 -5
- package/tui/src/commands/init.ts +30 -30
- package/tui/src/commands/insights.ts +44 -44
- package/tui/src/commands/install-github-app/install-github-app.tsx +2 -2
- package/tui/src/commands/install-github-app/setupGitHubActions.ts +3 -3
- package/tui/src/commands/install-github-app/types.ts +8 -30
- package/tui/src/commands/install.tsx +5 -5
- package/tui/src/commands/mcp/addCommand.ts +5 -5
- package/tui/src/commands/mcp/xaaIdpCommand.ts +2 -2
- package/tui/src/commands/plugin/ManageMarketplaces.tsx +2 -2
- package/tui/src/commands/plugin/types.ts +6 -28
- package/tui/src/commands/plugin/unifiedTypes.ts +4 -26
- package/tui/src/commands/reasoning/index.ts +13 -0
- package/tui/src/commands/reasoning/reasoning.tsx +177 -0
- package/tui/src/commands/rename/generateSessionName.ts +1 -1
- package/tui/src/commands/thinkback/thinkback.tsx +3 -3
- package/tui/src/commands.ts +2 -0
- package/tui/src/components/Feedback.tsx +1 -1
- package/tui/src/components/LogoV2/EmergencyTip.tsx +11 -2
- package/tui/src/components/LogoV2/WelcomeV2.tsx +1 -3
- package/tui/src/components/Messages.tsx +2 -1
- package/tui/src/components/ScrollKeybindingHandler.tsx +6 -6
- package/tui/src/components/Spinner/types.ts +6 -28
- package/tui/src/components/Spinner.tsx +2 -2
- package/tui/src/components/agents/generateAgent.ts +1 -1
- package/tui/src/components/agents/new-agent-creation/types.ts +4 -26
- package/tui/src/components/config/EnvSecretIsolatedEditor.tsx +1 -1
- package/tui/src/components/design-system/LoadingState.tsx +2 -2
- package/tui/src/components/mcp/types.ts +16 -38
- package/tui/src/components/messages/AssistantToolUseMessage.tsx +3 -2
- package/tui/src/components/messages/UserCrossSessionMessage.ts +16 -4
- package/tui/src/components/messages/UserForkBoilerplateMessage.ts +16 -4
- package/tui/src/components/messages/UserGitHubWebhookMessage.ts +16 -4
- package/tui/src/components/messages/UserToolResultMessage/utils.tsx +3 -2
- package/tui/src/components/permissions/MonitorPermissionRequest/MonitorPermissionRequest.ts +9 -4
- package/tui/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.ts +9 -4
- package/tui/src/components/primitive/DocumentSocraticReviewBlock.tsx +129 -0
- package/tui/src/components/primitive/DocumentToolResultCard.tsx +224 -0
- package/tui/src/components/primitive/documentSocraticReview.ts +215 -0
- package/tui/src/components/primitive/index.tsx +43 -1
- package/tui/src/components/primitive/types.ts +137 -0
- package/tui/src/components/ui/option.ts +4 -26
- package/tui/src/constants/common.ts +0 -2
- package/tui/src/constants/prompts.ts +4 -3
- package/tui/src/constants/querySource.ts +4 -26
- package/tui/src/entrypoints/sdk/controlTypes.ts +26 -48
- package/tui/src/entrypoints/sdk/coreTypes.generated.ts +3 -25
- package/tui/src/entrypoints/sdk/runtimeTypes.ts +38 -60
- package/tui/src/entrypoints/sdk/sdkUtilityTypes.ts +4 -26
- package/tui/src/entrypoints/sdk/settingsTypes.generated.ts +3 -25
- package/tui/src/entrypoints/sdk/toolTypes.ts +3 -25
- package/tui/src/hooks/toolPermission/handlers/interactiveHandler.ts +10 -0
- package/tui/src/hooks/useApiKeyVerification.ts +1 -1
- package/tui/src/hooks/useVirtualScroll.ts +1 -1
- package/tui/src/ink/ink.tsx +33 -14
- package/tui/src/ink/reconciler.ts +2 -3
- package/tui/src/ink/render-to-screen.ts +30 -10
- package/tui/src/ipc/bridge.ts +62 -15
- package/tui/src/ipc/bridgeSingleton.ts +5 -1
- package/tui/src/ipc/codec.ts +29 -3
- package/tui/src/ipc/frames.generated.ts +407 -312
- package/tui/src/ipc/llmClient.ts +279 -76
- package/tui/src/ipc/llmTypes.ts +16 -1
- package/tui/src/ipc/schema/frame.schema.json +1 -3475
- package/tui/src/keybindings/defaultBindings.ts +4 -0
- package/tui/src/main.tsx +32 -11
- package/tui/src/native-ts/file-index/index.ts +33 -3
- package/tui/src/observability/surface.ts +2 -2
- package/tui/src/probes/toolRegistryProbe.tsx +3 -1
- package/tui/src/projectOnboardingState.ts +7 -6
- package/tui/src/query/chatMessageTypes.ts +18 -0
- package/tui/src/query/chatMessagesBuilder.ts +1 -1
- package/tui/src/query/deps.ts +1 -1
- package/tui/src/query/messageGuards.ts +106 -0
- package/tui/src/query/publicDataTerminalRepair.ts +384 -0
- package/tui/src/query/run.ts +1075 -0
- package/tui/src/query/supportBoundary.ts +168 -0
- package/tui/src/query/toolResultErrors.ts +103 -0
- package/tui/src/query/toolRunner.ts +687 -0
- package/tui/src/query/unavailableToolRepair.ts +118 -0
- package/tui/src/query.ts +9 -1721
- package/tui/src/screens/REPL.tsx +42 -31
- package/tui/src/services/api/adapterManifest.ts +4 -0
- package/tui/src/services/api/backendChat/events.ts +117 -0
- package/tui/src/services/api/backendChat/finalMessage.ts +40 -0
- package/tui/src/services/api/backendChat/frame.ts +9 -0
- package/tui/src/services/api/backendChat/streaming.ts +430 -0
- package/tui/src/services/api/backendChat/types.ts +62 -0
- package/tui/src/services/api/backendChat.ts +1 -0
- package/tui/src/services/api/client.ts +98 -14
- package/tui/src/services/api/errorUtils.ts +5 -5
- package/tui/src/services/api/errors.ts +1 -1
- package/tui/src/services/api/logging.ts +1 -1
- package/tui/src/services/api/ummaya/evidence.ts +194 -0
- package/tui/src/services/api/ummaya/messages.ts +255 -0
- package/tui/src/services/api/ummaya/nonStreaming.ts +66 -0
- package/tui/src/services/api/ummaya/provider.ts +200 -0
- package/tui/src/services/api/ummaya/reasoning.ts +24 -0
- package/tui/src/services/api/ummaya/request.ts +200 -0
- package/tui/src/services/api/ummaya/selectionContext.ts +240 -0
- package/tui/src/services/api/ummaya/streaming.ts +365 -0
- package/tui/src/services/api/ummaya/streamingPayload.ts +129 -0
- package/tui/src/services/api/ummaya/streamingReader.ts +40 -0
- package/tui/src/services/api/ummaya/toolSelection.ts +217 -0
- package/tui/src/services/api/ummaya/types.ts +110 -0
- package/tui/src/services/api/ummaya/usage.ts +30 -0
- package/tui/src/services/api/ummaya.ts +26 -364
- package/tui/src/services/api/withRetry.ts +1 -1
- package/tui/src/services/awaySummary.ts +2 -2
- package/tui/src/services/claudeAiLimits.ts +1 -1
- package/tui/src/services/compact/autoCompact.ts +1 -1
- package/tui/src/services/compact/compact.ts +1 -1
- package/tui/src/services/lsp/types.ts +8 -30
- package/tui/src/services/tips/types.ts +6 -28
- package/tui/src/services/tokenEstimation.ts +1 -1
- package/tui/src/services/toolRegistry/bootGuard.ts +5 -5
- package/tui/src/services/toolUseSummary/toolUseSummaryGenerator.ts +1 -1
- package/tui/src/services/tools/toolExecution.ts +94 -1
- package/tui/src/skills/bundled/stuck.ts +12 -12
- package/tui/src/state/AppStateStore.ts +7 -0
- package/tui/src/store/pendingPermissionSlot.ts +1 -1
- package/tui/src/store/session-store.ts +10 -36
- package/tui/src/stubs/any-stub.ts +15 -10
- package/tui/src/stubs/color-diff-napi.ts +37 -23
- package/tui/src/stubs/globals.d.ts +3 -3
- package/tui/src/stubs/macro-preload.ts +23 -12
- package/tui/src/tools/AdapterTool/AdapterTool.ts +1239 -163
- package/tui/src/tools/AdapterTool/routeDiagnostics.ts +75 -0
- package/tui/src/tools/AgentTool/AgentTool.tsx +84 -1371
- package/tui/src/tools/AgentTool/agentToolHandoff.ts +114 -0
- package/tui/src/tools/AgentTool/agentToolPartialResult.ts +16 -0
- package/tui/src/tools/AgentTool/agentToolProgress.ts +32 -0
- package/tui/src/tools/AgentTool/agentToolResolver.ts +161 -0
- package/tui/src/tools/AgentTool/agentToolResult.ts +163 -0
- package/tui/src/tools/AgentTool/agentToolUtils.ts +14 -686
- package/tui/src/tools/AgentTool/asyncAgentLifecycle.ts +208 -0
- package/tui/src/tools/AgentTool/asyncLifecycle.ts +153 -0
- package/tui/src/tools/AgentTool/backgroundedCompletion.ts +126 -0
- package/tui/src/tools/AgentTool/backgroundedLifecycle.ts +174 -0
- package/tui/src/tools/AgentTool/foregroundBackground.ts +83 -0
- package/tui/src/tools/AgentTool/foregroundDrain.tsx +133 -0
- package/tui/src/tools/AgentTool/foregroundFinalize.ts +98 -0
- package/tui/src/tools/AgentTool/foregroundLifecycle.tsx +237 -0
- package/tui/src/tools/AgentTool/foregroundProgress.tsx +169 -0
- package/tui/src/tools/AgentTool/foregroundTask.ts +89 -0
- package/tui/src/tools/AgentTool/forkSubagent.ts +1 -12
- package/tui/src/tools/AgentTool/forkSubagentGate.ts +34 -0
- package/tui/src/tools/AgentTool/launchRouting.ts +203 -0
- package/tui/src/tools/AgentTool/lifecycle.ts +244 -0
- package/tui/src/tools/AgentTool/mcpRouting.ts +73 -0
- package/tui/src/tools/AgentTool/orchestrationSupport.ts +70 -0
- package/tui/src/tools/AgentTool/permissions.ts +39 -0
- package/tui/src/tools/AgentTool/promptSetup.ts +181 -0
- package/tui/src/tools/AgentTool/remoteRouting.ts +62 -0
- package/tui/src/tools/AgentTool/resultMapping.ts +116 -0
- package/tui/src/tools/AgentTool/resumeAgent.ts +39 -107
- package/tui/src/tools/AgentTool/resumeAgentHelpers.ts +140 -0
- package/tui/src/tools/AgentTool/runAgent.ts +1 -1
- package/tui/src/tools/AgentTool/runtimeConfig.ts +57 -0
- package/tui/src/tools/AgentTool/schemas.ts +196 -0
- package/tui/src/tools/AgentTool/sourceVerificationPropagation.ts +263 -0
- package/tui/src/tools/AgentTool/worktreeLifecycle.ts +105 -0
- package/tui/src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx +174 -202
- package/tui/src/tools/BashTool/BashTool.tsx +71 -1072
- package/tui/src/tools/BashTool/bashCommandHelpers.ts +12 -12
- package/tui/src/tools/BashTool/bashPermissions/astPreflight.ts +173 -0
- package/tui/src/tools/BashTool/bashPermissions/classifierChecks.ts +199 -0
- package/tui/src/tools/BashTool/bashPermissions/compoundGuards.ts +53 -0
- package/tui/src/tools/BashTool/bashPermissions/constants.ts +99 -0
- package/tui/src/tools/BashTool/bashPermissions/index.ts +38 -0
- package/tui/src/tools/BashTool/bashPermissions/legacyMisparsing.ts +62 -0
- package/tui/src/tools/BashTool/bashPermissions/main.ts +135 -0
- package/tui/src/tools/BashTool/bashPermissions/normalizedCommands.ts +33 -0
- package/tui/src/tools/BashTool/bashPermissions/operatorFlow.ts +98 -0
- package/tui/src/tools/BashTool/bashPermissions/permissionChecks.ts +200 -0
- package/tui/src/tools/BashTool/bashPermissions/prefixSuggestions.ts +88 -0
- package/tui/src/tools/BashTool/bashPermissions/promptClassifierRules.ts +125 -0
- package/tui/src/tools/BashTool/bashPermissions/ruleDelegates.ts +19 -0
- package/tui/src/tools/BashTool/bashPermissions/ruleMatching.ts +145 -0
- package/tui/src/tools/BashTool/bashPermissions/sandboxAutoAllow.ts +75 -0
- package/tui/src/tools/BashTool/bashPermissions/subcommandFlow.ts +205 -0
- package/tui/src/tools/BashTool/bashPermissions/subcommandGuards.ts +73 -0
- package/tui/src/tools/BashTool/bashPermissions/subcommandResultHelpers.ts +116 -0
- package/tui/src/tools/BashTool/bashPermissions/types.ts +26 -0
- package/tui/src/tools/BashTool/bashPermissions/wrapperStripping.ts +139 -0
- package/tui/src/tools/BashTool/bashPermissions.ts +26 -2621
- package/tui/src/tools/BashTool/call.ts +202 -0
- package/tui/src/tools/BashTool/callLoader.ts +35 -0
- package/tui/src/tools/BashTool/commandClassification.ts +151 -0
- package/tui/src/tools/BashTool/commandClassificationLoader.ts +40 -0
- package/tui/src/tools/BashTool/cwdReset.ts +33 -0
- package/tui/src/tools/BashTool/lineTruncation.ts +11 -0
- package/tui/src/tools/BashTool/modeValidation.ts +13 -1
- package/tui/src/tools/BashTool/outputPersistence.ts +42 -0
- package/tui/src/tools/BashTool/permissionClassification.ts +66 -0
- package/tui/src/tools/BashTool/permissionLoader.ts +44 -0
- package/tui/src/tools/BashTool/resultLoader.ts +29 -0
- package/tui/src/tools/BashTool/resultMapping.ts +83 -0
- package/tui/src/tools/BashTool/sandboxPolicy.ts +79 -0
- package/tui/src/tools/BashTool/schemas.ts +65 -0
- package/tui/src/tools/BashTool/sedEditExecution.ts +59 -0
- package/tui/src/tools/BashTool/shellExecution.tsx +245 -0
- package/tui/src/tools/BashTool/shellOutputUtils.ts +85 -0
- package/tui/src/tools/BashTool/shellPermissionGauntlet.ts +97 -0
- package/tui/src/tools/BashTool/uiLoader.ts +37 -0
- package/tui/src/tools/BriefTool/upload.ts +1 -1
- package/tui/src/tools/CalculatorTool/parser.ts +2 -2
- package/tui/src/tools/DocumentPrimitive/DocumentPrimitive.ts +262 -0
- package/tui/src/tools/DocumentPrimitive/dispatchNormalization.ts +270 -0
- package/tui/src/tools/DocumentPrimitive/documentDestinationPath.ts +18 -0
- package/tui/src/tools/DocumentPrimitive/documentMutationGuard.ts +22 -0
- package/tui/src/tools/DocumentPrimitive/documentPatchNormalization.ts +248 -0
- package/tui/src/tools/DocumentPrimitive/documentSourceVerification.ts +245 -0
- package/tui/src/tools/DocumentPrimitive/documentSourceVerificationFields.ts +103 -0
- package/tui/src/tools/DocumentPrimitive/modelVisibleOutput.ts +40 -0
- package/tui/src/tools/DocumentPrimitive/prompt.ts +35 -0
- package/tui/src/tools/FileEditTool/FileEditTool.ts +9 -507
- package/tui/src/tools/FileEditTool/call.ts +228 -0
- package/tui/src/tools/FileEditTool/validateInput.ts +196 -0
- package/tui/src/tools/FileReadTool/imageProcessor.ts +13 -0
- package/tui/src/tools/FileWriteTool/FileWriteTool.ts +7 -300
- package/tui/src/tools/FileWriteTool/call.ts +223 -0
- package/tui/src/tools/FileWriteTool/validateInput.ts +80 -0
- package/tui/src/tools/ListMcpResourcesTool/ListMcpResourcesTool.ts +19 -3
- package/tui/src/tools/LookupPrimitive/LookupPrimitive.ts +48 -29
- package/tui/src/tools/LookupPrimitive/prompt.ts +6 -7
- package/tui/src/tools/MCPTool/trustPolicy.ts +118 -0
- package/tui/src/tools/McpAuthTool/McpAuthTool.ts +21 -3
- package/tui/src/tools/NotebookEditTool/NotebookEditTool.ts +7 -326
- package/tui/src/tools/NotebookEditTool/call.ts +254 -0
- package/tui/src/tools/NotebookEditTool/notebookModel.ts +51 -0
- package/tui/src/tools/NotebookEditTool/validateInput.ts +142 -0
- package/tui/src/tools/PowerShellTool/PowerShellTool.tsx +46 -937
- package/tui/src/tools/PowerShellTool/acceptEditsCommandValidation.ts +162 -0
- package/tui/src/tools/PowerShellTool/call.ts +179 -0
- package/tui/src/tools/PowerShellTool/callLoader.ts +37 -0
- package/tui/src/tools/PowerShellTool/commandClassification.ts +86 -0
- package/tui/src/tools/PowerShellTool/modeValidation.ts +25 -332
- package/tui/src/tools/PowerShellTool/outputPersistence.ts +42 -0
- package/tui/src/tools/PowerShellTool/permissionClassification.ts +28 -0
- package/tui/src/tools/PowerShellTool/resultLoader.ts +31 -0
- package/tui/src/tools/PowerShellTool/resultMapping.ts +75 -0
- package/tui/src/tools/PowerShellTool/schemas.ts +40 -0
- package/tui/src/tools/PowerShellTool/shellExecution.tsx +258 -0
- package/tui/src/tools/PowerShellTool/symlinkModeValidation.ts +44 -0
- package/tui/src/tools/PowerShellTool/uiLoader.ts +37 -0
- package/tui/src/tools/PowerShellTool/validation.ts +39 -0
- package/tui/src/tools/ReadMcpResourceTool/ReadMcpResourceTool.ts +19 -3
- package/tui/src/tools/ResolveLocationPrimitive/ResolveLocationPrimitive.ts +30 -19
- package/tui/src/tools/ResolveLocationPrimitive/prompt.ts +2 -6
- package/tui/src/tools/SkillTool/SkillTool.ts +2 -2
- package/tui/src/tools/SubmitPrimitive/SubmitPrimitive.ts +51 -18
- package/tui/src/tools/TaskCreateTool/TaskCreateTool.ts +16 -2
- package/tui/src/tools/TaskGetTool/TaskGetTool.ts +23 -3
- package/tui/src/tools/TaskListTool/TaskListTool.ts +22 -4
- package/tui/src/tools/TaskOutputTool/TaskOutputTool.tsx +46 -547
- package/tui/src/tools/TaskOutputTool/lookup.ts +216 -0
- package/tui/src/tools/TaskOutputTool/render.tsx +257 -0
- package/tui/src/tools/TaskOutputTool/schemas.ts +55 -0
- package/tui/src/tools/TaskOutputTool/serialization.ts +36 -0
- package/tui/src/tools/TaskStopTool/TaskStopTool.ts +10 -0
- package/tui/src/tools/TaskUpdateTool/TaskUpdateTool.ts +14 -364
- package/tui/src/tools/TaskUpdateTool/completion.ts +62 -0
- package/tui/src/tools/TaskUpdateTool/schemas.ts +62 -0
- package/tui/src/tools/TaskUpdateTool/serialization.ts +46 -0
- package/tui/src/tools/TaskUpdateTool/statusUpdate.ts +247 -0
- package/tui/src/tools/TodoWriteTool/TodoWriteTool.ts +21 -2
- package/tui/src/tools/ToolSearchTool/ToolSearchTool.ts +21 -302
- package/tui/src/tools/ToolSearchTool/ccSupportTools.ts +223 -0
- package/tui/src/tools/ToolSearchTool/descriptionCache.ts +50 -0
- package/tui/src/tools/ToolSearchTool/keywordSearch.ts +216 -0
- package/tui/src/tools/ToolSearchTool/prompt.ts +10 -4
- package/tui/src/tools/ToolSearchTool/resultMapping.ts +30 -0
- package/tui/src/tools/ToolSearchTool/schemas.ts +30 -0
- package/tui/src/tools/ToolSearchTool/searchPool.ts +47 -0
- package/tui/src/tools/ToolSearchTool/supportIntentHints.ts +140 -0
- package/tui/src/tools/TranslateTool/TranslateTool.ts +1 -1
- package/tui/src/tools/VerifyPrimitive/VerifyPrimitive.ts +27 -10
- package/tui/src/tools/WebFetchTool/WebFetchTool.ts +43 -138
- package/tui/src/tools/WebFetchTool/call.ts +227 -0
- package/tui/src/tools/WebFetchTool/resolvedAddressSafety.ts +78 -0
- package/tui/src/tools/WebFetchTool/sourceVerification.ts +204 -0
- package/tui/src/tools/WebFetchTool/types.ts +23 -0
- package/tui/src/tools/WebFetchTool/urlSafety.ts +181 -0
- package/tui/src/tools/WebFetchTool/utils.ts +1 -1
- package/tui/src/tools/WebSearchTool/UI.tsx +0 -1
- package/tui/src/tools/WebSearchTool/WebSearchTool.ts +9 -313
- package/tui/src/tools/WebSearchTool/call.ts +33 -0
- package/tui/src/tools/WebSearchTool/responseMapping.ts +190 -0
- package/tui/src/tools/WebSearchTool/resultBlock.ts +47 -0
- package/tui/src/tools/WebSearchTool/schemas.ts +47 -0
- package/tui/src/tools/WebSearchTool/toolSchema.ts +12 -0
- package/tui/src/tools/WorkspaceToolAdapter/WorkspaceToolAdapter.ts +79 -0
- package/tui/src/tools/WorkspaceToolAdapter/allowedRootPolicy.ts +85 -0
- package/tui/src/tools/WorkspaceToolAdapter/documentFormatGuards.ts +73 -0
- package/tui/src/tools/WorkspaceToolAdapter/inputNormalization.ts +105 -0
- package/tui/src/tools/WorkspaceToolAdapter/mcpExposurePolicy.ts +64 -0
- package/tui/src/tools/WorkspaceToolAdapter/toolDefFactory.ts +215 -0
- package/tui/src/tools/WorkspaceToolAdapter/toolNames.ts +6 -0
- package/tui/src/tools/WorkspaceToolAdapter/workspacePolicy.ts +15 -0
- package/tui/src/tools/_shared/citizenUserText.ts +49 -0
- package/tui/src/tools/_shared/dispatchPrimitive.ts +6 -6
- package/tui/src/tools/_shared/documentChangeToPatch.ts +125 -0
- package/tui/src/tools/_shared/documentDispatchArguments.ts +87 -0
- package/tui/src/tools/_shared/documentPrimitiveTimeout.ts +13 -0
- package/tui/src/tools/_shared/documentToolResultRender.ts +98 -0
- package/tui/src/tools/_shared/locationInputRepair.ts +112 -0
- package/tui/src/tools/_shared/pendingCallRegistry.ts +1 -6
- package/tui/src/tools/_shared/rootPrimitiveInput.ts +68 -0
- package/tui/src/tools/_shared/toolChoiceRepair/documentCompletionPatterns.ts +58 -0
- package/tui/src/tools/_shared/toolChoiceRepair/documentCompletionPrompt.ts +271 -0
- package/tui/src/tools/_shared/toolChoiceRepair/documentRepair.ts +452 -0
- package/tui/src/tools/_shared/toolChoiceRepair/messageAccess.ts +80 -0
- package/tui/src/tools/_shared/toolChoiceRepair/publicDataRepair.ts +92 -0
- package/tui/src/tools/_shared/toolChoiceRepair/supportRepair.ts +135 -0
- package/tui/src/tools/_shared/toolChoiceRepair.ts +61 -0
- package/tui/src/tools/shared/mockDisclaimer.ts +1 -1
- package/tui/src/tools.ts +39 -190
- package/tui/src/types/fileSuggestion.ts +4 -26
- package/tui/src/types/generated/events_mono/claude_code/v1/claude_code_internal_event.ts +186 -148
- package/tui/src/types/generated/events_mono/common/v1/auth.ts +25 -11
- package/tui/src/types/generated/events_mono/growthbook/v1/growthbook_experiment_event.ts +47 -30
- package/tui/src/types/generated/google/protobuf/timestamp.ts +21 -7
- package/tui/src/types/message.ts +80 -102
- package/tui/src/types/messageQueueTypes.ts +6 -28
- package/tui/src/types/notebook.ts +16 -38
- package/tui/src/types/statusLine.ts +4 -26
- package/tui/src/types/tools.ts +24 -46
- package/tui/src/types/utils.ts +6 -28
- package/tui/src/upstreamproxy/relay.ts +7 -3
- package/tui/src/upstreamproxy/upstreamproxy.ts +1 -1
- package/tui/src/utils/assistantMessageFactories.ts +9 -3
- package/tui/src/utils/attachments.ts +1 -1
- package/tui/src/utils/auth.ts +129 -139
- package/tui/src/utils/bash/ast.ts +23 -23
- package/tui/src/utils/bash/bashParser.ts +5 -5
- package/tui/src/utils/billing.ts +1 -1
- package/tui/src/utils/collapseReadSearch.ts +3 -3
- package/tui/src/utils/cronTasks.ts +1 -1
- package/tui/src/utils/execFileNoThrow.ts +1 -1
- package/tui/src/utils/filePersistence/types.ts +16 -38
- package/tui/src/utils/forkedAgent.ts +1 -1
- package/tui/src/utils/gracefulShutdown.ts +4 -4
- package/tui/src/utils/heapDumpService.ts +12 -8
- package/tui/src/utils/hooks/apiQueryHookHelper.ts +1 -1
- package/tui/src/utils/hooks/execPromptHook.ts +1 -1
- package/tui/src/utils/hooks/skillImprovement.ts +1 -1
- package/tui/src/utils/kExaoneReasoning.ts +138 -0
- package/tui/src/utils/mcp/dateTimeParser.ts +1 -1
- package/tui/src/utils/messages.ts +19 -0
- package/tui/src/utils/migrateSessions.ts +3 -3
- package/tui/src/utils/model/model.ts +6 -6
- package/tui/src/utils/multiToolLayout.ts +13 -0
- package/tui/src/utils/permissions/yoloClassifier.ts +1 -1
- package/tui/src/utils/plugins/headlessPluginInstall.ts +1 -1
- package/tui/src/utils/plugins/mcpPluginIntegration.ts +1 -1
- package/tui/src/utils/plugins/mcpbHandler.ts +1 -1
- package/tui/src/utils/plugins/pluginLoader.ts +8 -8
- package/tui/src/utils/processUserInput/processSlashCommand.tsx +2 -2
- package/tui/src/utils/processUserInput/processUserInput.ts +26 -0
- package/tui/src/utils/protectedNamespace.ts +5 -3
- package/tui/src/utils/rawJsonToolCall.ts +242 -0
- package/tui/src/utils/ripgrep.ts +16 -7
- package/tui/src/utils/sessionTitle.ts +1 -1
- package/tui/src/utils/settings/applySettingsChange.ts +4 -0
- package/tui/src/utils/settings/permissionValidation.ts +14 -2
- package/tui/src/utils/settings/types.ts +9 -3
- package/tui/src/utils/shell/prefix.ts +1 -1
- package/tui/src/utils/sideQuery.ts +1 -1
- package/tui/src/utils/stats.ts +1 -1
- package/tui/src/utils/systemThemeWatcher.ts +13 -3
- package/tui/src/utils/teleport.tsx +1 -1
- package/uv.lock +394 -22
- package/assets/copilot-gate-logo.svg +0 -58
- package/assets/govon-logo.svg +0 -40
- package/src/ummaya/eval/__init__.py +0 -5
- package/src/ummaya/eval/retrieval.py +0 -713
- package/tui/src/services/api/claude.ts +0 -3510
- 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
|
+
)
|