ummaya 0.2.4 → 0.2.6
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 +15 -2
- package/bin/ummaya +10 -1
- package/bun.lock +180 -244
- package/npm-shrinkwrap.json +760 -1760
- package/package.json +39 -22
- package/prompts/manifest.yaml +1 -1
- package/prompts/system_v1.md +1 -0
- package/pyproject.toml +27 -2
- package/specs/2803-document-production-hardening/contracts/document-tools.schema.json +1043 -0
- package/src/ummaya/_canonical/__init__.py +2 -0
- package/src/ummaya/_canonical/baselines.yaml +113 -0
- package/src/ummaya/engine/engine.py +29 -132
- package/src/ummaya/evidence/__init__.py +21 -2
- 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 +88 -1
- 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 +81 -212
- package/src/ummaya/evidence/source_provenance.py +246 -0
- package/src/ummaya/evidence/source_provenance_redaction.py +176 -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 +5 -5
- package/src/ummaya/ipc/route_diagnostics.py +73 -0
- package/src/ummaya/ipc/stdio.py +1109 -477
- package/src/ummaya/llm/client.py +102 -3
- package/src/ummaya/llm/config.py +8 -3
- 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 +17 -1
- 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 +132 -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 +29 -0
- package/src/ummaya/tools/live_proxy.py +0 -3
- package/src/ummaya/tools/models.py +5 -1
- package/src/ummaya/tools/register_all.py +8 -0
- package/src/ummaya/tools/registry.py +10 -1
- 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 +34 -746
- package/tests/fixtures/documents/public_forms/baselines.yaml +113 -0
- package/tui/bun.lock +126 -305
- package/tui/package.json +35 -22
- package/tui/src/.cc-byte-identical-whitelist.yaml +266 -0
- package/tui/src/QueryEngine.ts +12 -8
- package/tui/src/bridge/inboundAttachments.ts +3 -3
- package/tui/src/cli/handlers/auth.ts +3 -12
- package/tui/src/cli/handlers/mcp.tsx +0 -1
- package/tui/src/cli/print.ts +8 -9
- package/tui/src/commands/insights.ts +1 -1
- package/tui/src/commands/install-github-app/types.ts +8 -30
- package/tui/src/commands/plugin/types.ts +6 -28
- package/tui/src/commands/plugin/unifiedTypes.ts +4 -26
- package/tui/src/commands/rename/generateSessionName.ts +1 -1
- 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/ScrollKeybindingHandler.tsx +6 -6
- package/tui/src/components/Spinner/types.ts +6 -28
- 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/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 +3 -3
- package/tui/src/ipc/frames.generated.ts +12 -12
- package/tui/src/ipc/llmClient.ts +151 -27
- package/tui/src/ipc/schema/frame.schema.json +1 -1
- package/tui/src/keybindings/defaultBindings.ts +4 -0
- package/tui/src/main.tsx +32 -15
- 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 -2186
- package/tui/src/screens/REPL.tsx +40 -29
- 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 +65 -2
- 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 -418
- 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/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 +1207 -714
- 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 +25 -32
- package/tui/src/tools/LookupPrimitive/prompt.ts +0 -2
- 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 +1 -11
- 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 +27 -10
- 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 +2 -1
- 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/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/pendingCallRegistry.ts +1 -6
- package/tui/src/tools/_shared/rootPrimitiveInput.ts +1 -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 +55 -860
- 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/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/claudeDesktop.ts +4 -4
- 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/mcp/dateTimeParser.ts +1 -1
- package/tui/src/utils/messages.ts +18 -0
- package/tui/src/utils/migrateSessions.ts +3 -3
- package/tui/src/utils/model/model.ts +6 -6
- 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/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/permissionValidation.ts +14 -2
- package/tui/src/utils/shell/prefix.ts +1 -1
- package/tui/src/utils/sideQuery.ts +1 -1
- package/tui/src/utils/systemThemeWatcher.ts +13 -3
- package/tui/src/utils/teleport.tsx +1 -1
- package/uv.lock +426 -45
- package/tui/src/services/api/claude.ts +0 -3540
- package/tui/src/tools/_shared/directPublicDataGuard.ts +0 -362
- package/tui/src/tools/_shared/kmaAnalysisGuard.ts +0 -197
- package/tui/src/tools/_shared/kmaAviationGuard.ts +0 -70
- package/tui/src/tools/_shared/nmcAedGuard.ts +0 -234
- package/tui/src/tools/_shared/protectedCheckGuard.ts +0 -207
- package/tui/src/tools/_shared/textToolCallGuard.ts +0 -91
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Document harness evidence records for Evidence Fabric v2.
|
|
3
|
+
|
|
4
|
+
This module stores only join metadata for document reports. Document bytes,
|
|
5
|
+
extracted text, and filled field values stay in the local artifact/report store
|
|
6
|
+
and are referenced here by opaque IDs plus SHA-256 hashes.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator
|
|
17
|
+
|
|
18
|
+
from ummaya.evidence.document_authoring_cases import DocumentAuthoringCase
|
|
19
|
+
from ummaya.evidence.json_types import JsonObject, parse_json_object
|
|
20
|
+
from ummaya.evidence.models import RunEvidence
|
|
21
|
+
from ummaya.tools.documents.models import DocumentFormatFamily, KnownDocumentFormat
|
|
22
|
+
|
|
23
|
+
_REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
24
|
+
_DEFAULT_DOCUMENT_SCENARIO_PATH = _REPO_ROOT / "evidence/scenarios/document_harness_v1.yaml"
|
|
25
|
+
|
|
26
|
+
DocumentHarnessReadiness = Literal[
|
|
27
|
+
"ready_for_review",
|
|
28
|
+
"not_ready",
|
|
29
|
+
"blocked",
|
|
30
|
+
"unsupported",
|
|
31
|
+
]
|
|
32
|
+
DocumentHarnessFormat = Literal["hwpx", "docx", "xlsx", "pdf", "pptx"]
|
|
33
|
+
DocumentHarnessLifecycleStage = Literal[
|
|
34
|
+
"intake",
|
|
35
|
+
"classification",
|
|
36
|
+
"capability",
|
|
37
|
+
"adapter_selection",
|
|
38
|
+
"permission",
|
|
39
|
+
"mutation",
|
|
40
|
+
"render",
|
|
41
|
+
"reread",
|
|
42
|
+
"validation",
|
|
43
|
+
"diff",
|
|
44
|
+
"tui_frame",
|
|
45
|
+
]
|
|
46
|
+
DocumentHarnessLifecycleStatus = Literal[
|
|
47
|
+
"pass",
|
|
48
|
+
"blocked",
|
|
49
|
+
"needs_input",
|
|
50
|
+
"unsupported",
|
|
51
|
+
"ready_for_review",
|
|
52
|
+
]
|
|
53
|
+
DocumentHarnessBetaDomain = Literal[
|
|
54
|
+
"weekly_log",
|
|
55
|
+
"contest_proposal",
|
|
56
|
+
"consent",
|
|
57
|
+
"pledge",
|
|
58
|
+
"spreadsheet",
|
|
59
|
+
"pdf_form",
|
|
60
|
+
"presentation",
|
|
61
|
+
"public_data_csv_json",
|
|
62
|
+
"static_pdf",
|
|
63
|
+
"scanned_image",
|
|
64
|
+
"archive_bundle",
|
|
65
|
+
]
|
|
66
|
+
DocumentHarnessBetaOutcome = Literal[
|
|
67
|
+
"ready_for_review",
|
|
68
|
+
"read_only",
|
|
69
|
+
"blocked",
|
|
70
|
+
"needs_input",
|
|
71
|
+
"unsupported",
|
|
72
|
+
]
|
|
73
|
+
DocumentHarnessNegativeTrigger = Literal[
|
|
74
|
+
"missing_file",
|
|
75
|
+
"ambiguous_file_candidates",
|
|
76
|
+
"unsupported_known_format",
|
|
77
|
+
"blocked_hwp_write",
|
|
78
|
+
"static_pdf_fill",
|
|
79
|
+
"macro_active_content",
|
|
80
|
+
"path_traversal",
|
|
81
|
+
"oversized_archive",
|
|
82
|
+
"external_link",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class DocumentHarnessEvidenceError(ValueError):
|
|
87
|
+
"""Raised when document harness evidence metadata is invalid."""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class DocumentEvidenceRecord(BaseModel):
|
|
91
|
+
"""Joinable evidence record for one generated document derivative."""
|
|
92
|
+
|
|
93
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
94
|
+
|
|
95
|
+
record_id: str
|
|
96
|
+
scenario_id: str
|
|
97
|
+
correlation_id: str
|
|
98
|
+
source_artifact_id: str
|
|
99
|
+
source_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
100
|
+
derivative_artifact_id: str
|
|
101
|
+
derivative_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
102
|
+
structured_diff_id: str
|
|
103
|
+
structured_diff_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
104
|
+
render_artifact_ids: tuple[str, ...] = Field(min_length=1)
|
|
105
|
+
validation_report_id: str
|
|
106
|
+
validation_report_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
107
|
+
readiness: DocumentHarnessReadiness
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class DocumentHarnessEvidenceEnvelope(BaseModel):
|
|
111
|
+
"""Evidence Fabric runner output plus document-specific evidence records."""
|
|
112
|
+
|
|
113
|
+
model_config = ConfigDict(frozen=True, extra="forbid", strict=True)
|
|
114
|
+
|
|
115
|
+
run_evidence: RunEvidence
|
|
116
|
+
document_evidence_records: tuple[DocumentEvidenceRecord, ...]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class DocumentLifecycleEvidenceRecord(BaseModel):
|
|
120
|
+
"""Join-only evidence for one document workflow stage."""
|
|
121
|
+
|
|
122
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
123
|
+
|
|
124
|
+
record_id: str
|
|
125
|
+
scenario_id: str
|
|
126
|
+
correlation_id: str
|
|
127
|
+
stage: DocumentHarnessLifecycleStage
|
|
128
|
+
status: DocumentHarnessLifecycleStatus
|
|
129
|
+
known_format: KnownDocumentFormat
|
|
130
|
+
format_family: DocumentFormatFamily
|
|
131
|
+
support_component_id: str | None = None
|
|
132
|
+
artifact_id: str | None = None
|
|
133
|
+
artifact_sha256: str | None = Field(default=None, pattern=r"^[0-9a-f]{64}$")
|
|
134
|
+
document_diff_id: str | None = None
|
|
135
|
+
frame_hash: str | None = Field(default=None, pattern=r"^sha256:[0-9a-f]{64}$")
|
|
136
|
+
evidence_ref: str
|
|
137
|
+
|
|
138
|
+
@model_validator(mode="after")
|
|
139
|
+
def _tui_frame_requires_frame_hash(self) -> DocumentLifecycleEvidenceRecord:
|
|
140
|
+
if self.stage == "tui_frame" and self.frame_hash is None:
|
|
141
|
+
raise ValueError("tui_frame lifecycle evidence requires frame_hash")
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class DocumentBetaCase(BaseModel):
|
|
146
|
+
"""Representative beta scenario for one Public AX document domain."""
|
|
147
|
+
|
|
148
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
149
|
+
|
|
150
|
+
case_id: str
|
|
151
|
+
domain: DocumentHarnessBetaDomain
|
|
152
|
+
known_format: KnownDocumentFormat
|
|
153
|
+
format_family: DocumentFormatFamily
|
|
154
|
+
expected_outcome: DocumentHarnessBetaOutcome
|
|
155
|
+
expected_operation: str
|
|
156
|
+
fixture_id: str | None = None
|
|
157
|
+
evidence_ref: str
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class DocumentNegativeCase(BaseModel):
|
|
161
|
+
"""Negative beta scenario that must fail closed."""
|
|
162
|
+
|
|
163
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
164
|
+
|
|
165
|
+
case_id: str
|
|
166
|
+
trigger: DocumentHarnessNegativeTrigger
|
|
167
|
+
expected_status: Literal["blocked", "needs_input"]
|
|
168
|
+
expected_reason: str
|
|
169
|
+
derivative_save: Literal["forbidden"]
|
|
170
|
+
evidence_ref: str
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class DocumentHarnessAcceptanceGates(BaseModel):
|
|
174
|
+
"""US4 acceptance gates for the document harness scenario."""
|
|
175
|
+
|
|
176
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
177
|
+
|
|
178
|
+
original_mutation: Literal["blocked"]
|
|
179
|
+
derivative_lineage: Literal["required"]
|
|
180
|
+
structured_result_schema: Literal["required"]
|
|
181
|
+
render_reread_evidence: Literal["required"]
|
|
182
|
+
live_government_calls: Literal["forbidden"]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class DocumentHarnessFixture(BaseModel):
|
|
186
|
+
"""Offline fixture metadata used to prove document evidence joins."""
|
|
187
|
+
|
|
188
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
189
|
+
|
|
190
|
+
fixture_id: str
|
|
191
|
+
format: DocumentHarnessFormat
|
|
192
|
+
source_artifact_id: str
|
|
193
|
+
source_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
194
|
+
derivative_artifact_id: str
|
|
195
|
+
derivative_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
196
|
+
structured_diff_id: str
|
|
197
|
+
structured_diff_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
198
|
+
render_artifact_ids: tuple[str, ...] = Field(min_length=1)
|
|
199
|
+
validation_report_id: str
|
|
200
|
+
validation_report_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
201
|
+
expected_correlation_id: str
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class DocumentHarnessScenario(BaseModel):
|
|
205
|
+
"""Dedicated US4 scenario metadata for document harness evidence."""
|
|
206
|
+
|
|
207
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
208
|
+
|
|
209
|
+
version: int
|
|
210
|
+
scenario_id: Literal["document_harness_v1"]
|
|
211
|
+
created_at: str
|
|
212
|
+
source_basis: Literal["document_production_hardening_spec"]
|
|
213
|
+
target_system: str
|
|
214
|
+
network_policy: Literal["offline_only"]
|
|
215
|
+
required_sequence: tuple[Literal["document"], ...]
|
|
216
|
+
acceptance_gates: DocumentHarnessAcceptanceGates
|
|
217
|
+
fixtures: tuple[DocumentHarnessFixture, ...] = Field(min_length=1)
|
|
218
|
+
lifecycle_records: tuple[DocumentLifecycleEvidenceRecord, ...] = Field(min_length=1)
|
|
219
|
+
beta_cases: tuple[DocumentBetaCase, ...] = Field(min_length=1)
|
|
220
|
+
negative_cases: tuple[DocumentNegativeCase, ...] = Field(min_length=1)
|
|
221
|
+
authoring_cases: tuple[DocumentAuthoringCase, ...] = Field(min_length=1)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def load_document_harness_scenario(
|
|
225
|
+
path: Path = _DEFAULT_DOCUMENT_SCENARIO_PATH,
|
|
226
|
+
) -> DocumentHarnessScenario:
|
|
227
|
+
"""Load the offline document harness Evidence Fabric scenario."""
|
|
228
|
+
|
|
229
|
+
raw = _load_yaml_mapping(path)
|
|
230
|
+
try:
|
|
231
|
+
return DocumentHarnessScenario.model_validate(raw)
|
|
232
|
+
except ValidationError as exc:
|
|
233
|
+
raise DocumentHarnessEvidenceError(str(exc)) from exc
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def attach_document_evidence_records(
|
|
237
|
+
run_evidence: RunEvidence,
|
|
238
|
+
records: Sequence[DocumentEvidenceRecord],
|
|
239
|
+
) -> DocumentHarnessEvidenceEnvelope:
|
|
240
|
+
"""Attach document records to an Evidence Fabric runner result."""
|
|
241
|
+
|
|
242
|
+
return DocumentHarnessEvidenceEnvelope(
|
|
243
|
+
run_evidence=run_evidence,
|
|
244
|
+
document_evidence_records=tuple(records),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def records_from_scenario(
|
|
249
|
+
scenario: DocumentHarnessScenario,
|
|
250
|
+
) -> tuple[DocumentEvidenceRecord, ...]:
|
|
251
|
+
"""Build join-only document evidence records from scenario fixture metadata."""
|
|
252
|
+
|
|
253
|
+
return tuple(
|
|
254
|
+
DocumentEvidenceRecord(
|
|
255
|
+
record_id=f"doc-evidence-{fixture.fixture_id}",
|
|
256
|
+
scenario_id=scenario.scenario_id,
|
|
257
|
+
correlation_id=fixture.expected_correlation_id,
|
|
258
|
+
source_artifact_id=fixture.source_artifact_id,
|
|
259
|
+
source_sha256=fixture.source_sha256,
|
|
260
|
+
derivative_artifact_id=fixture.derivative_artifact_id,
|
|
261
|
+
derivative_sha256=fixture.derivative_sha256,
|
|
262
|
+
structured_diff_id=fixture.structured_diff_id,
|
|
263
|
+
structured_diff_sha256=fixture.structured_diff_sha256,
|
|
264
|
+
render_artifact_ids=fixture.render_artifact_ids,
|
|
265
|
+
validation_report_id=fixture.validation_report_id,
|
|
266
|
+
validation_report_sha256=fixture.validation_report_sha256,
|
|
267
|
+
readiness="ready_for_review",
|
|
268
|
+
)
|
|
269
|
+
for fixture in scenario.fixtures
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def lifecycle_records_from_scenario(
|
|
274
|
+
scenario: DocumentHarnessScenario,
|
|
275
|
+
) -> tuple[DocumentLifecycleEvidenceRecord, ...]:
|
|
276
|
+
"""Return workflow lifecycle records from scenario metadata."""
|
|
277
|
+
|
|
278
|
+
return scenario.lifecycle_records
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def beta_cases_from_scenario(
|
|
282
|
+
scenario: DocumentHarnessScenario,
|
|
283
|
+
) -> tuple[DocumentBetaCase, ...]:
|
|
284
|
+
"""Return beta-matrix cases from scenario metadata."""
|
|
285
|
+
|
|
286
|
+
return scenario.beta_cases
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def negative_cases_from_scenario(
|
|
290
|
+
scenario: DocumentHarnessScenario,
|
|
291
|
+
) -> tuple[DocumentNegativeCase, ...]:
|
|
292
|
+
"""Return fail-closed beta-matrix cases from scenario metadata."""
|
|
293
|
+
|
|
294
|
+
return scenario.negative_cases
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def authoring_cases_from_scenario(
|
|
298
|
+
scenario: DocumentHarnessScenario,
|
|
299
|
+
) -> tuple[DocumentAuthoringCase, ...]:
|
|
300
|
+
return scenario.authoring_cases
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _load_yaml_mapping(path: Path) -> JsonObject:
|
|
304
|
+
resolved = path if path.is_absolute() else _REPO_ROOT / path
|
|
305
|
+
if not resolved.exists():
|
|
306
|
+
raise DocumentHarnessEvidenceError(f"document harness scenario not found: {path}")
|
|
307
|
+
loaded = yaml.safe_load(resolved.read_text(encoding="utf-8"))
|
|
308
|
+
try:
|
|
309
|
+
return parse_json_object(loaded)
|
|
310
|
+
except ValidationError as exc:
|
|
311
|
+
raise DocumentHarnessEvidenceError(
|
|
312
|
+
f"document harness scenario must be a JSON mapping: {path}"
|
|
313
|
+
) from exc
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Playwright-captured UX evidence for local document viewers."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import subprocess
|
|
10
|
+
import zlib
|
|
11
|
+
from collections.abc import Iterator, Mapping
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from datetime import UTC, datetime
|
|
15
|
+
from functools import partial
|
|
16
|
+
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from threading import Thread
|
|
19
|
+
from typing import Literal, Protocol, cast
|
|
20
|
+
from urllib.parse import quote
|
|
21
|
+
from uuid import uuid4
|
|
22
|
+
|
|
23
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DocumentViewerUxEvidenceError(ValueError):
|
|
27
|
+
"""Raised when document viewer UX evidence cannot be captured."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DocumentViewerViewportRect(BaseModel):
|
|
31
|
+
"""Viewport rectangle from a hidden document viewer manifest."""
|
|
32
|
+
|
|
33
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
34
|
+
|
|
35
|
+
x: float
|
|
36
|
+
y: float
|
|
37
|
+
width: float
|
|
38
|
+
height: float
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DocumentViewerManifest(BaseModel):
|
|
42
|
+
"""Subset of viewer-manifest.json needed for Evidence Fabric joins."""
|
|
43
|
+
|
|
44
|
+
model_config = ConfigDict(frozen=True, extra="allow")
|
|
45
|
+
|
|
46
|
+
viewer_artifact_id: str
|
|
47
|
+
mode: Literal["compact", "expand"]
|
|
48
|
+
page_index: int = 0
|
|
49
|
+
correlation_id: str | None = None
|
|
50
|
+
document_diff_id: str | None = None
|
|
51
|
+
source_render_artifact_id: str
|
|
52
|
+
baseline_render_artifact_id: str | None = None
|
|
53
|
+
viewport_rect: DocumentViewerViewportRect | None = None
|
|
54
|
+
change_ids: tuple[str, ...] = Field(default_factory=tuple)
|
|
55
|
+
local_only: bool = True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DocumentViewerUxArtifact(BaseModel):
|
|
59
|
+
"""Join-only UX artifact record for a Playwright document viewer screenshot."""
|
|
60
|
+
|
|
61
|
+
model_config = ConfigDict(frozen=True, extra="forbid", strict=True)
|
|
62
|
+
|
|
63
|
+
artifact_kind: Literal["document_viewer_png"] = "document_viewer_png"
|
|
64
|
+
artifact_id: str
|
|
65
|
+
source_ref: str
|
|
66
|
+
capture_tool: Literal["playwright"] = "playwright"
|
|
67
|
+
captured_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
|
68
|
+
viewer_artifact_id: str
|
|
69
|
+
mode: Literal["compact", "expand"]
|
|
70
|
+
correlation_id: str
|
|
71
|
+
document_diff_id: str | None = None
|
|
72
|
+
viewer_html_path: str
|
|
73
|
+
viewer_manifest_path: str
|
|
74
|
+
screenshot_path: str
|
|
75
|
+
screenshot_sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
76
|
+
frame_hash: str = Field(pattern=r"^sha256:[0-9a-f]{64}$")
|
|
77
|
+
page_index: int
|
|
78
|
+
source_render_artifact_id: str
|
|
79
|
+
baseline_render_artifact_id: str | None = None
|
|
80
|
+
viewport_rect: DocumentViewerViewportRect | None = None
|
|
81
|
+
change_ids: tuple[str, ...] = Field(default_factory=tuple)
|
|
82
|
+
local_only: bool
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class PlaywrightCapture(Protocol):
|
|
86
|
+
"""Callable boundary for Playwright screenshot capture."""
|
|
87
|
+
|
|
88
|
+
def __call__(self, *, viewer_url: str, output_path: Path) -> None:
|
|
89
|
+
"""Capture ``viewer_url`` into ``output_path`` as a PNG."""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class _PngImageData:
|
|
94
|
+
width: int
|
|
95
|
+
height: int
|
|
96
|
+
color_type: int
|
|
97
|
+
compressed_data: bytes
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def capture_document_viewer_ux_artifact(
|
|
101
|
+
*,
|
|
102
|
+
viewer_html_path: Path,
|
|
103
|
+
output_dir: Path,
|
|
104
|
+
source_ref: str,
|
|
105
|
+
correlation_id: str | None = None,
|
|
106
|
+
document_diff_id: str | None = None,
|
|
107
|
+
capture: PlaywrightCapture | None = None,
|
|
108
|
+
) -> DocumentViewerUxArtifact:
|
|
109
|
+
"""Capture a local document viewer with Playwright and return join metadata."""
|
|
110
|
+
|
|
111
|
+
viewer_html = viewer_html_path.resolve()
|
|
112
|
+
if not viewer_html.exists():
|
|
113
|
+
raise DocumentViewerUxEvidenceError(f"document viewer html not found: {viewer_html}")
|
|
114
|
+
manifest_path = viewer_html.parent / "viewer-manifest.json"
|
|
115
|
+
manifest = _load_manifest(manifest_path)
|
|
116
|
+
resolved_correlation_id = correlation_id or manifest.correlation_id
|
|
117
|
+
if resolved_correlation_id is None or resolved_correlation_id.strip() == "":
|
|
118
|
+
raise DocumentViewerUxEvidenceError(
|
|
119
|
+
"document viewer UX evidence requires a correlation_id in the "
|
|
120
|
+
"viewer manifest or CLI argument"
|
|
121
|
+
)
|
|
122
|
+
resolved_diff_id = document_diff_id or manifest.document_diff_id
|
|
123
|
+
|
|
124
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
screenshot_path = (
|
|
126
|
+
output_dir / f"{manifest.viewer_artifact_id}-{manifest.mode}-playwright.png"
|
|
127
|
+
).resolve()
|
|
128
|
+
capture_impl = capture or capture_with_playwright_cli
|
|
129
|
+
with _serve_viewer_directory(viewer_html) as viewer_url:
|
|
130
|
+
capture_impl(viewer_url=viewer_url, output_path=screenshot_path)
|
|
131
|
+
if not screenshot_path.exists():
|
|
132
|
+
raise DocumentViewerUxEvidenceError(
|
|
133
|
+
f"Playwright did not create screenshot: {screenshot_path}"
|
|
134
|
+
)
|
|
135
|
+
if not _png_has_visible_nonwhite_pixel(screenshot_path):
|
|
136
|
+
raise DocumentViewerUxEvidenceError(
|
|
137
|
+
f"Playwright document viewer capture is blank: {screenshot_path}"
|
|
138
|
+
)
|
|
139
|
+
screenshot_sha256 = _sha256_file(screenshot_path)
|
|
140
|
+
|
|
141
|
+
return DocumentViewerUxArtifact(
|
|
142
|
+
artifact_id=f"ux-{manifest.viewer_artifact_id}-{screenshot_sha256[:12]}",
|
|
143
|
+
source_ref=source_ref,
|
|
144
|
+
viewer_artifact_id=manifest.viewer_artifact_id,
|
|
145
|
+
mode=manifest.mode,
|
|
146
|
+
correlation_id=resolved_correlation_id,
|
|
147
|
+
document_diff_id=resolved_diff_id,
|
|
148
|
+
viewer_html_path=str(viewer_html),
|
|
149
|
+
viewer_manifest_path=str(manifest_path.resolve()),
|
|
150
|
+
screenshot_path=str(screenshot_path),
|
|
151
|
+
screenshot_sha256=screenshot_sha256,
|
|
152
|
+
frame_hash=f"sha256:{screenshot_sha256}",
|
|
153
|
+
page_index=manifest.page_index,
|
|
154
|
+
source_render_artifact_id=manifest.source_render_artifact_id,
|
|
155
|
+
baseline_render_artifact_id=manifest.baseline_render_artifact_id,
|
|
156
|
+
viewport_rect=manifest.viewport_rect,
|
|
157
|
+
change_ids=manifest.change_ids,
|
|
158
|
+
local_only=manifest.local_only,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def capture_with_playwright_cli(*, viewer_url: str, output_path: Path) -> None:
|
|
163
|
+
"""Capture a viewer URL through the bundled Playwright CLI wrapper."""
|
|
164
|
+
|
|
165
|
+
wrapper = Path(os.environ.get("CODEX_HOME", str(Path.home() / ".codex"))) / (
|
|
166
|
+
"skills/playwright/scripts/playwright_cli.sh"
|
|
167
|
+
)
|
|
168
|
+
if not wrapper.exists():
|
|
169
|
+
raise DocumentViewerUxEvidenceError(f"Playwright CLI wrapper not found: {wrapper}")
|
|
170
|
+
session = f"udv-{uuid4().hex[:12]}"
|
|
171
|
+
env = os.environ.copy()
|
|
172
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
173
|
+
commands = (
|
|
174
|
+
(str(wrapper), f"-s={session}", "open", viewer_url),
|
|
175
|
+
(str(wrapper), f"-s={session}", "resize", "1600", "1000"),
|
|
176
|
+
(
|
|
177
|
+
str(wrapper),
|
|
178
|
+
f"-s={session}",
|
|
179
|
+
"eval",
|
|
180
|
+
"() => document.documentElement.dataset.ready || document.readyState",
|
|
181
|
+
),
|
|
182
|
+
(
|
|
183
|
+
str(wrapper),
|
|
184
|
+
f"-s={session}",
|
|
185
|
+
"screenshot",
|
|
186
|
+
"--filename",
|
|
187
|
+
str(output_path),
|
|
188
|
+
"--full-page",
|
|
189
|
+
),
|
|
190
|
+
)
|
|
191
|
+
try:
|
|
192
|
+
for command in commands:
|
|
193
|
+
subprocess.run( # noqa: S603 - fixed local Playwright wrapper plus typed args.
|
|
194
|
+
command,
|
|
195
|
+
check=True,
|
|
196
|
+
capture_output=True,
|
|
197
|
+
text=True,
|
|
198
|
+
env=env,
|
|
199
|
+
)
|
|
200
|
+
except subprocess.CalledProcessError as exc:
|
|
201
|
+
detail = (exc.stderr or exc.stdout or str(exc)).strip()
|
|
202
|
+
raise DocumentViewerUxEvidenceError(
|
|
203
|
+
f"Playwright document viewer capture failed: {detail}"
|
|
204
|
+
) from exc
|
|
205
|
+
finally:
|
|
206
|
+
subprocess.run( # noqa: S603 - cleanup uses the same fixed local wrapper.
|
|
207
|
+
(str(wrapper), f"-s={session}", "close"),
|
|
208
|
+
check=False,
|
|
209
|
+
capture_output=True,
|
|
210
|
+
text=True,
|
|
211
|
+
env=env,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _load_manifest(path: Path) -> DocumentViewerManifest:
|
|
216
|
+
if not path.exists():
|
|
217
|
+
raise DocumentViewerUxEvidenceError(f"viewer manifest not found: {path}")
|
|
218
|
+
loaded = json.loads(path.read_text(encoding="utf-8"))
|
|
219
|
+
if not isinstance(loaded, Mapping):
|
|
220
|
+
raise DocumentViewerUxEvidenceError(f"viewer manifest must be a mapping: {path}")
|
|
221
|
+
try:
|
|
222
|
+
return DocumentViewerManifest.model_validate(cast(Mapping[str, object], loaded))
|
|
223
|
+
except ValidationError as exc:
|
|
224
|
+
raise DocumentViewerUxEvidenceError(str(exc)) from exc
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@contextmanager
|
|
228
|
+
def _serve_viewer_directory(viewer_html: Path) -> Iterator[str]:
|
|
229
|
+
handler = partial(
|
|
230
|
+
_QuietViewerRequestHandler,
|
|
231
|
+
directory=str(viewer_html.parent),
|
|
232
|
+
)
|
|
233
|
+
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
|
|
234
|
+
thread = Thread(target=server.serve_forever, daemon=True)
|
|
235
|
+
thread.start()
|
|
236
|
+
try:
|
|
237
|
+
port = int(server.server_address[1])
|
|
238
|
+
yield f"http://127.0.0.1:{port}/{quote(viewer_html.name)}"
|
|
239
|
+
finally:
|
|
240
|
+
server.shutdown()
|
|
241
|
+
server.server_close()
|
|
242
|
+
thread.join(timeout=2)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class _QuietViewerRequestHandler(SimpleHTTPRequestHandler):
|
|
246
|
+
def log_message(self, message_format: str, *args: object) -> None:
|
|
247
|
+
del message_format, args
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _png_has_visible_nonwhite_pixel(path: Path) -> bool:
|
|
251
|
+
image = _load_png_image_data(path)
|
|
252
|
+
channels = 4 if image.color_type == 6 else 3
|
|
253
|
+
stride = image.width * channels
|
|
254
|
+
raw = zlib.decompress(image.compressed_data)
|
|
255
|
+
previous = bytearray(stride)
|
|
256
|
+
offset = 0
|
|
257
|
+
for _row in range(image.height):
|
|
258
|
+
filter_type, scanline, offset = _read_png_scanline(
|
|
259
|
+
raw,
|
|
260
|
+
offset,
|
|
261
|
+
stride,
|
|
262
|
+
path,
|
|
263
|
+
)
|
|
264
|
+
_unfilter_png_scanline(scanline, previous, channels, filter_type)
|
|
265
|
+
if _scanline_has_visible_nonwhite_pixel(scanline, channels):
|
|
266
|
+
return True
|
|
267
|
+
previous = scanline
|
|
268
|
+
return False
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _load_png_image_data(path: Path) -> _PngImageData:
|
|
272
|
+
data = path.read_bytes()
|
|
273
|
+
if not data.startswith(b"\x89PNG\r\n\x1a\n"):
|
|
274
|
+
raise DocumentViewerUxEvidenceError(f"Playwright screenshot is not a PNG: {path}")
|
|
275
|
+
width = 0
|
|
276
|
+
height = 0
|
|
277
|
+
bit_depth = 0
|
|
278
|
+
color_type = 0
|
|
279
|
+
interlace = 0
|
|
280
|
+
idat_chunks: list[bytes] = []
|
|
281
|
+
for chunk_type, chunk_data in _iter_png_chunks(data, path):
|
|
282
|
+
if chunk_type == b"IHDR":
|
|
283
|
+
width = int.from_bytes(chunk_data[0:4], "big")
|
|
284
|
+
height = int.from_bytes(chunk_data[4:8], "big")
|
|
285
|
+
bit_depth = chunk_data[8]
|
|
286
|
+
color_type = chunk_data[9]
|
|
287
|
+
interlace = chunk_data[12]
|
|
288
|
+
elif chunk_type == b"IDAT":
|
|
289
|
+
idat_chunks.append(chunk_data)
|
|
290
|
+
elif chunk_type == b"IEND":
|
|
291
|
+
break
|
|
292
|
+
if width <= 0 or height <= 0 or not idat_chunks:
|
|
293
|
+
raise DocumentViewerUxEvidenceError(f"invalid PNG screenshot: {path}")
|
|
294
|
+
if bit_depth != 8 or color_type not in (2, 6) or interlace != 0:
|
|
295
|
+
raise DocumentViewerUxEvidenceError(
|
|
296
|
+
f"unsupported PNG screenshot format: bit_depth={bit_depth} "
|
|
297
|
+
f"color_type={color_type} interlace={interlace}"
|
|
298
|
+
)
|
|
299
|
+
return _PngImageData(
|
|
300
|
+
width=width,
|
|
301
|
+
height=height,
|
|
302
|
+
color_type=color_type,
|
|
303
|
+
compressed_data=b"".join(idat_chunks),
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _iter_png_chunks(data: bytes, path: Path) -> Iterator[tuple[bytes, bytes]]:
|
|
308
|
+
pos = 8
|
|
309
|
+
while pos + 8 <= len(data):
|
|
310
|
+
chunk_len = int.from_bytes(data[pos : pos + 4], "big")
|
|
311
|
+
chunk_type = data[pos + 4 : pos + 8]
|
|
312
|
+
chunk_start = pos + 8
|
|
313
|
+
chunk_end = chunk_start + chunk_len
|
|
314
|
+
if chunk_end + 4 > len(data):
|
|
315
|
+
raise DocumentViewerUxEvidenceError(f"invalid PNG chunk bounds: {path}")
|
|
316
|
+
yield chunk_type, data[chunk_start:chunk_end]
|
|
317
|
+
pos = chunk_end + 4
|
|
318
|
+
if chunk_type == b"IEND":
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _read_png_scanline(
|
|
323
|
+
raw: bytes,
|
|
324
|
+
offset: int,
|
|
325
|
+
stride: int,
|
|
326
|
+
path: Path,
|
|
327
|
+
) -> tuple[int, bytearray, int]:
|
|
328
|
+
if offset >= len(raw):
|
|
329
|
+
raise DocumentViewerUxEvidenceError(f"truncated PNG scanline data: {path}")
|
|
330
|
+
filter_type = raw[offset]
|
|
331
|
+
next_offset = offset + 1
|
|
332
|
+
scanline = bytearray(raw[next_offset : next_offset + stride])
|
|
333
|
+
if len(scanline) != stride:
|
|
334
|
+
raise DocumentViewerUxEvidenceError(f"truncated PNG scanline data: {path}")
|
|
335
|
+
return filter_type, scanline, next_offset + stride
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _scanline_has_visible_nonwhite_pixel(scanline: bytearray, channels: int) -> bool:
|
|
339
|
+
for pixel in range(0, len(scanline), channels):
|
|
340
|
+
red = scanline[pixel]
|
|
341
|
+
green = scanline[pixel + 1]
|
|
342
|
+
blue = scanline[pixel + 2]
|
|
343
|
+
alpha = scanline[pixel + 3] if channels == 4 else 255
|
|
344
|
+
if alpha > 0 and (red < 250 or green < 250 or blue < 250):
|
|
345
|
+
return True
|
|
346
|
+
return False
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _unfilter_png_scanline(
|
|
350
|
+
scanline: bytearray,
|
|
351
|
+
previous: bytearray,
|
|
352
|
+
bytes_per_pixel: int,
|
|
353
|
+
filter_type: int,
|
|
354
|
+
) -> None:
|
|
355
|
+
for index, value in enumerate(scanline):
|
|
356
|
+
left = scanline[index - bytes_per_pixel] if index >= bytes_per_pixel else 0
|
|
357
|
+
up = previous[index]
|
|
358
|
+
up_left = previous[index - bytes_per_pixel] if index >= bytes_per_pixel else 0
|
|
359
|
+
if filter_type == 0:
|
|
360
|
+
restored = value
|
|
361
|
+
elif filter_type == 1:
|
|
362
|
+
restored = value + left
|
|
363
|
+
elif filter_type == 2:
|
|
364
|
+
restored = value + up
|
|
365
|
+
elif filter_type == 3:
|
|
366
|
+
restored = value + ((left + up) // 2)
|
|
367
|
+
elif filter_type == 4:
|
|
368
|
+
restored = value + _paeth_predictor(left, up, up_left)
|
|
369
|
+
else:
|
|
370
|
+
raise DocumentViewerUxEvidenceError(f"unsupported PNG filter type: {filter_type}")
|
|
371
|
+
scanline[index] = restored & 0xFF
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _paeth_predictor(left: int, up: int, up_left: int) -> int:
|
|
375
|
+
estimate = left + up - up_left
|
|
376
|
+
distance_left = abs(estimate - left)
|
|
377
|
+
distance_up = abs(estimate - up)
|
|
378
|
+
distance_up_left = abs(estimate - up_left)
|
|
379
|
+
if distance_left <= distance_up and distance_left <= distance_up_left:
|
|
380
|
+
return left
|
|
381
|
+
if distance_up <= distance_up_left:
|
|
382
|
+
return up
|
|
383
|
+
return up_left
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _sha256_file(path: Path) -> str:
|
|
387
|
+
digest = hashlib.sha256()
|
|
388
|
+
with path.open("rb") as file:
|
|
389
|
+
for chunk in iter(lambda: file.read(1024 * 1024), b""):
|
|
390
|
+
digest.update(chunk)
|
|
391
|
+
return digest.hexdigest()
|