@beyondwork/docx-react-component 1.0.0 → 1.0.2
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 +44 -104
- package/package.json +66 -15
- package/src/api/public-types.ts +1 -1
- package/src/compare/diff-engine.ts +530 -0
- package/src/compare/export-redlines.ts +162 -0
- package/src/compare/snapshot.ts +37 -0
- package/src/core/commands/index.ts +1 -1
- package/src/core/state/editor-state.ts +2 -2
- package/src/index.ts +45 -0
- package/src/legal/bookmarks.ts +196 -0
- package/src/legal/cross-references.ts +356 -0
- package/src/legal/defined-terms.ts +203 -0
- package/src/runtime/document-runtime.ts +3 -5
- package/src/runtime/table-commands.ts +4 -1
- package/src/runtime/table-schema.ts +17 -2
- package/src/runtime/virtualized-rendering.ts +258 -0
- package/src/ui/WordReviewEditor.tsx +256 -35
- package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +2 -2
- package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +16 -2
- package/.codex/config.toml +0 -5
- package/.corepack/v1/pnpm/10.30.3/.corepack +0 -1
- package/.corepack/v1/pnpm/10.30.3/LICENSE +0 -22
- package/.corepack/v1/pnpm/10.30.3/README.md +0 -240
- package/.corepack/v1/pnpm/10.30.3/dist/node-gyp-bin/node-gyp +0 -6
- package/.corepack/v1/pnpm/10.30.3/dist/node-gyp-bin/node-gyp.cmd +0 -5
- package/.corepack/v1/pnpm/10.30.3/dist/pnpm.cjs +0 -195400
- package/.corepack/v1/pnpm/10.30.3/dist/pnpmrc +0 -2
- package/.corepack/v1/pnpm/10.30.3/dist/reflink.darwin-arm64-2HJ4WGO6.node +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/reflink.darwin-x64-3G3H6IW4.node +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/reflink.win32-arm64-msvc-Q6BARPPB.node +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/reflink.win32-x64-msvc-J2TZHRQI.node +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/templates/completion.bash +0 -31
- package/.corepack/v1/pnpm/10.30.3/dist/templates/completion.fish +0 -22
- package/.corepack/v1/pnpm/10.30.3/dist/templates/completion.ps1 +0 -193
- package/.corepack/v1/pnpm/10.30.3/dist/templates/completion.zsh +0 -27
- package/.corepack/v1/pnpm/10.30.3/dist/vendor/fastlist-0.3.0-x64.exe +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/vendor/fastlist-0.3.0-x86.exe +0 -0
- package/.corepack/v1/pnpm/10.30.3/dist/worker.js +0 -10119
- package/.corepack/v1/pnpm/10.30.3/package.json +0 -192
- package/.cursor/mcp.json +0 -7
- package/.github/workflows/ci.yml +0 -35
- package/.mcp.json +0 -7
- package/.openclaw/workspace-state.json +0 -4
- package/.pnpmrc.json +0 -1
- package/.wave-launch.sh +0 -7
- package/.workspace-marker +0 -1
- package/AGENTS.md +0 -78
- package/CHANGELOG.md +0 -177
- package/DESIGN.md +0 -929
- package/HEARTBEAT.md +0 -7
- package/IDENTITY.md +0 -23
- package/SOUL.md +0 -36
- package/TOOLS.md +0 -40
- package/USER.md +0 -17
- package/docs/README.md +0 -107
- package/docs/agents/wave-cont-eval-role.md +0 -36
- package/docs/agents/wave-cont-qa-role.md +0 -52
- package/docs/agents/wave-deploy-verifier-role.md +0 -34
- package/docs/agents/wave-design-role.md +0 -47
- package/docs/agents/wave-documentation-role.md +0 -34
- package/docs/agents/wave-infra-role.md +0 -34
- package/docs/agents/wave-integration-role.md +0 -37
- package/docs/agents/wave-launcher-role.md +0 -41
- package/docs/agents/wave-orchestrator-role.md +0 -52
- package/docs/agents/wave-planner-role.md +0 -39
- package/docs/agents/wave-security-role.md +0 -40
- package/docs/architecture/docx/README.md +0 -10
- package/docs/architecture/future/README.md +0 -8
- package/docs/architecture/ooxml-upgrade-analysis.md +0 -134
- package/docs/architecture/platform/shared-openxml-editor-platform.md +0 -153
- package/docs/architecture/xlsx/canonical-workbook-model-and-commands.md +0 -187
- package/docs/architecture/xlsx/spreadsheet-editor-frontend-architecture.md +0 -150
- package/docs/comment-redline-overview.md +0 -350
- package/docs/concepts/context7-vs-skills.md +0 -118
- package/docs/concepts/operating-modes.md +0 -91
- package/docs/concepts/runtime-agnostic-orchestration.md +0 -111
- package/docs/concepts/what-is-a-wave.md +0 -217
- package/docs/context7/bundles.json +0 -222
- package/docs/context7/planner-agent/README.md +0 -28
- package/docs/context7/planner-agent/manifest.json +0 -83
- package/docs/context7/planner-agent/papers/cooperbench-why-coding-agents-cannot-be-your-teammates-yet.md +0 -3283
- package/docs/context7/planner-agent/papers/dova-deliberation-first-multi-agent-orchestration-for-autonomous-research-automation.md +0 -1699
- package/docs/context7/planner-agent/papers/dpbench-large-language-models-struggle-with-simultaneous-coordination.md +0 -2251
- package/docs/context7/planner-agent/papers/incremental-planning-to-control-a-blackboard-based-problem-solver.md +0 -1729
- package/docs/context7/planner-agent/papers/silo-bench-a-scalable-environment-for-evaluating-distributed-coordination-in-multi-agent-llm-systems.md +0 -3747
- package/docs/context7/planner-agent/papers/todoevolve-learning-to-architect-agent-planning-systems.md +0 -1675
- package/docs/context7/planner-agent/papers/verified-multi-agent-orchestration-a-plan-execute-verify-replan-framework-for-complex-query-resolution.md +0 -1173
- package/docs/context7/planner-agent/papers/why-do-multi-agent-llm-systems-fail.md +0 -5211
- package/docs/context7/planner-agent/topics/planning-and-orchestration.md +0 -24
- package/docs/evals/arm-templates/README.md +0 -13
- package/docs/evals/arm-templates/full-wave.json +0 -15
- package/docs/evals/arm-templates/single-agent.json +0 -15
- package/docs/evals/benchmark-catalog.json +0 -670
- package/docs/evals/cases/README.md +0 -47
- package/docs/evals/cases/wave-blackboard-inbox-targeting.json +0 -73
- package/docs/evals/cases/wave-contradiction-conflict.json +0 -104
- package/docs/evals/cases/wave-expert-routing-preservation.json +0 -69
- package/docs/evals/cases/wave-hidden-profile-private-evidence.json +0 -81
- package/docs/evals/cases/wave-premature-closure-guard.json +0 -71
- package/docs/evals/cases/wave-silo-cross-agent-state.json +0 -77
- package/docs/evals/cases/wave-simultaneous-lockstep.json +0 -92
- package/docs/evals/external-benchmarks.json +0 -85
- package/docs/evals/external-command-config.sample.json +0 -9
- package/docs/evals/external-command-config.swe-bench-pro.json +0 -8
- package/docs/evals/pilots/README.md +0 -47
- package/docs/evals/pilots/swe-bench-pro-public-full-wave-review-10.json +0 -64
- package/docs/evals/pilots/swe-bench-pro-public-pilot.json +0 -111
- package/docs/evals/wave-benchmark-program.md +0 -302
- package/docs/guides/planner.md +0 -220
- package/docs/guides/recommendations-0.8.9.md +0 -133
- package/docs/guides/signal-wrappers.md +0 -165
- package/docs/guides/terminal-surfaces.md +0 -96
- package/docs/image copy.png +0 -0
- package/docs/image.png +0 -0
- package/docs/images/image.png +0 -0
- package/docs/legal-feedback-architecture.md +0 -498
- package/docs/plans/component-cutover-matrix.json +0 -1072
- package/docs/plans/component-cutover-matrix.md +0 -307
- package/docs/plans/context7-wave-orchestrator.md +0 -155
- package/docs/plans/current-state.md +0 -198
- package/docs/plans/docx/README.md +0 -9
- package/docs/plans/examples/wave-benchmark-improvement.md +0 -108
- package/docs/plans/examples/wave-example-live-proof.md +0 -435
- package/docs/plans/master-plan.md +0 -224
- package/docs/plans/migration.md +0 -538
- package/docs/plans/operations/README.md +0 -7
- package/docs/plans/operations/wave-10-word-certification.md +0 -87
- package/docs/plans/operations/wave-8-railway-staging.md +0 -153
- package/docs/plans/operations/wave-9-manual-certification.md +0 -73
- package/docs/plans/platform/README.md +0 -9
- package/docs/plans/reference/legal-checklist-coverage.md +0 -258
- package/docs/plans/wave-orchestrator.md +0 -423
- package/docs/plans/waves/README.md +0 -75
- package/docs/plans/waves/completed/wave-0.md +0 -195
- package/docs/plans/waves/completed/wave-1.md +0 -379
- package/docs/plans/waves/completed/wave-10.md +0 -670
- package/docs/plans/waves/completed/wave-11.md +0 -335
- package/docs/plans/waves/completed/wave-12.md +0 -417
- package/docs/plans/waves/completed/wave-13.md +0 -316
- package/docs/plans/waves/completed/wave-14.md +0 -319
- package/docs/plans/waves/completed/wave-15.md +0 -321
- package/docs/plans/waves/completed/wave-16.md +0 -316
- package/docs/plans/waves/completed/wave-17.md +0 -331
- package/docs/plans/waves/completed/wave-18.md +0 -328
- package/docs/plans/waves/completed/wave-2.md +0 -438
- package/docs/plans/waves/completed/wave-3.md +0 -435
- package/docs/plans/waves/completed/wave-4.md +0 -430
- package/docs/plans/waves/completed/wave-5.md +0 -430
- package/docs/plans/waves/completed/wave-6.md +0 -430
- package/docs/plans/waves/completed/wave-7.md +0 -526
- package/docs/plans/waves/completed/wave-8.md +0 -596
- package/docs/plans/waves/completed/wave-9.md +0 -552
- package/docs/plans/waves/deferred/README.md +0 -14
- package/docs/plans/waves/deferred/encrypted-intake-contracts.md +0 -282
- package/docs/plans/waves/deferred/legal-feedback-wave-expansion.md +0 -308
- package/docs/plans/waves/deferred/wave-encrypted-intake.md +0 -451
- package/docs/plans/waves/design/README.md +0 -5
- package/docs/plans/waves/design/wave-1-a1.md +0 -309
- package/docs/plans/waves/reviews/README.md +0 -5
- package/docs/plans/waves/reviews/wave-0-cont-qa.md +0 -151
- package/docs/plans/waves/reviews/wave-1-cont-qa.md +0 -46
- package/docs/plans/waves/reviews/wave-10-accessibility-and-design.md +0 -51
- package/docs/plans/waves/reviews/wave-10-cont-qa.md +0 -24
- package/docs/plans/waves/reviews/wave-10-dashboard-proof.md +0 -46
- package/docs/plans/waves/reviews/wave-10-performance-signoff.md +0 -55
- package/docs/plans/waves/reviews/wave-10-regression-proof.md +0 -23
- package/docs/plans/waves/reviews/wave-10-release-audit.md +0 -31
- package/docs/plans/waves/reviews/wave-10-service-proof.md +0 -83
- package/docs/plans/waves/reviews/wave-10-word-certification.md +0 -31
- package/docs/plans/waves/reviews/wave-18-ai-contract-closure.md +0 -277
- package/docs/plans/waves/reviews/wave-18-cont-qa.md +0 -255
- package/docs/plans/waves/reviews/wave-18-parity-proof.md +0 -271
- package/docs/plans/waves/reviews/wave-19-cont-qa.md +0 -59
- package/docs/plans/waves/reviews/wave-2-cont-qa.md +0 -72
- package/docs/plans/waves/reviews/wave-20-cont-qa.md +0 -60
- package/docs/plans/waves/reviews/wave-25-cont-qa.md +0 -48
- package/docs/plans/waves/reviews/wave-28-cont-qa.md +0 -46
- package/docs/plans/waves/reviews/wave-29-cont-qa.md +0 -53
- package/docs/plans/waves/reviews/wave-3-cont-qa.md +0 -53
- package/docs/plans/waves/reviews/wave-3-core-proof.md +0 -77
- package/docs/plans/waves/reviews/wave-3-validator-proof.md +0 -73
- package/docs/plans/waves/reviews/wave-32-cont-qa.md +0 -43
- package/docs/plans/waves/reviews/wave-33-cont-qa.md +0 -526
- package/docs/plans/waves/reviews/wave-34-cont-qa.md +0 -100
- package/docs/plans/waves/reviews/wave-35-cont-qa.md +0 -145
- package/docs/plans/waves/reviews/wave-4-cont-qa.md +0 -47
- package/docs/plans/waves/reviews/wave-4-structure-proof.md +0 -69
- package/docs/plans/waves/reviews/wave-5-comment-proof.md +0 -158
- package/docs/plans/waves/reviews/wave-5-cont-qa.md +0 -68
- package/docs/plans/waves/reviews/wave-6-cont-qa.md +0 -416
- package/docs/plans/waves/reviews/wave-6-redline-proof.md +0 -130
- package/docs/plans/waves/reviews/wave-7-cont-qa.md +0 -82
- package/docs/plans/waves/reviews/wave-7-ooxml-compliance.md +0 -85
- package/docs/plans/waves/reviews/wave-7-preservation-proof.md +0 -119
- package/docs/plans/waves/reviews/wave-7-trust-ux.md +0 -87
- package/docs/plans/waves/reviews/wave-8-accessibility-and-design.md +0 -128
- package/docs/plans/waves/reviews/wave-8-cont-qa.md +0 -92
- package/docs/plans/waves/reviews/wave-8-live-proof.md +0 -140
- package/docs/plans/waves/reviews/wave-8-security.md +0 -47
- package/docs/plans/waves/reviews/wave-9-editor-embedding.md +0 -39
- package/docs/plans/waves/reviews/wave-9-fixture-runner.md +0 -56
- package/docs/plans/waves/reviews/wave-9-live-proof.md +0 -105
- package/docs/plans/waves/reviews/wave-9-usability-and-performance.md +0 -152
- package/docs/plans/waves/specs/README.md +0 -5
- package/docs/plans/waves/specs/wave-1-component-boundaries.md +0 -322
- package/docs/plans/waves/specs/wave-1-ooxml-contracts.md +0 -323
- package/docs/plans/waves/specs/wave-1-review-and-ui-contracts.md +0 -339
- package/docs/plans/waves/specs/wave-1-runtime-contracts.md +0 -509
- package/docs/plans/waves/wave-19.md +0 -341
- package/docs/plans/waves/wave-20.md +0 -308
- package/docs/plans/waves/wave-21.md +0 -289
- package/docs/plans/waves/wave-22.md +0 -221
- package/docs/plans/waves/wave-23.md +0 -295
- package/docs/plans/waves/wave-24.md +0 -286
- package/docs/plans/waves/wave-25.md +0 -313
- package/docs/plans/waves/wave-26.md +0 -300
- package/docs/plans/waves/wave-27.md +0 -299
- package/docs/plans/waves/wave-28.md +0 -368
- package/docs/plans/waves/wave-29.md +0 -303
- package/docs/plans/waves/wave-30.md +0 -307
- package/docs/plans/waves/wave-31.md +0 -231
- package/docs/plans/waves/wave-32.md +0 -152
- package/docs/plans/waves/wave-33.md +0 -147
- package/docs/plans/waves/wave-34.md +0 -148
- package/docs/plans/waves/wave-35.md +0 -141
- package/docs/plans/waves/wave-36.md +0 -146
- package/docs/plans/xlsx/README.md +0 -14
- package/docs/plans/xlsx/xlsx-fixture-corpus-and-certification-plan.md +0 -126
- package/docs/reference/cli-reference.md +0 -600
- package/docs/reference/coordination-and-closure.md +0 -487
- package/docs/reference/deep-research-report (15).md +0 -25
- package/docs/reference/docx/README.md +0 -10
- package/docs/reference/legal-checklist.md +0 -445
- package/docs/reference/live-proof-waves.md +0 -199
- package/docs/reference/ooxml-compliance.md +0 -129
- package/docs/reference/ooxml-feature-parity-matrix.md +0 -172
- package/docs/reference/platform/shared-ooxml-platform-guidance.md +0 -77
- package/docs/reference/prototype-agent-prompt-legal-fidelity.md +0 -155
- package/docs/reference/public-api.md +0 -456
- package/docs/reference/repository-guidance.md +0 -58
- package/docs/reference/runtime-config/README.md +0 -182
- package/docs/reference/runtime-config/claude.md +0 -110
- package/docs/reference/runtime-config/codex.md +0 -82
- package/docs/reference/runtime-config/opencode.md +0 -93
- package/docs/reference/sample-waves.md +0 -105
- package/docs/reference/skills.md +0 -237
- package/docs/reference/templates/AGENTS.md +0 -78
- package/docs/reference/templates/HEARTBEAT.md +0 -7
- package/docs/reference/templates/IDENTITY.md +0 -23
- package/docs/reference/templates/SOUL.md +0 -36
- package/docs/reference/templates/TOOLS.md +0 -40
- package/docs/reference/templates/USER.md +0 -17
- package/docs/reference/wave-control.md +0 -184
- package/docs/reference/wave-planning-lessons.md +0 -167
- package/docs/reference/word-review-editor-frontend-architecture.md +0 -479
- package/docs/reference/word-review-editor-ux-guide.md +0 -253
- package/docs/reference/xlsx/xlsx-ooxml-compliance.md +0 -137
- package/docs/research/agent-context-sources.md +0 -178
- package/docs/research/coordination-failure-review.md +0 -290
- package/docs/research/docx-react-component/Canonical Document Schema Specification for a React-based Word-compatible Editor.md +0 -2317
- package/docs/research/docx-react-component/Feature Compatibility Matrix for a React Word Compatible Legal Editor v1.md +0 -219
- package/docs/research/docx-react-component/React Component Architecture and Front-End Structure Specification for a Word-Compatible Legal Review Editor.md +0 -1112
- package/docs/research/docx-react-component/document_compatibility_and_testing_spec.md +0 -751
- package/docs/research/xlsx/raw/README.md +0 -13
- package/docs/roadmap.md +0 -174
- package/docs/superpowers/plans/2026-03-28-harness-control-bar.md +0 -677
- package/docs/superpowers/specs/2026-03-28-harness-control-bar-design.md +0 -274
- package/docs/xlsx-react/README.md +0 -38
- package/docs/xlsx-react/agent-llm-interaction-layer-docx-xlsx.md +0 -621
- package/docs/xlsx-react/canonical-workbook-model-and-commands.md +0 -948
- package/docs/xlsx-react/shared-openxml-editor-platform-docx-xlsx.md +0 -228
- package/docs/xlsx-react/spreadsheet-editor-component-architecture.md +0 -809
- package/docs/xlsx-react/spreadsheet-editor-frontend-architecture.md +0 -537
- package/docs/xlsx-react/spreadsheet-editor-ux-guide.md +0 -520
- package/docs/xlsx-react/xlsx-editor-research-pack.md +0 -871
- package/docs/xlsx-react/xlsx-fixture-corpus-and-certification-plan.md +0 -436
- package/docs/xlsx-react/xlsx-ooxml-compliance.md +0 -320
- package/examples/README.md +0 -16
- package/memory/MEMORY.md +0 -24
- package/pnpm-workspace.yaml +0 -4
- package/scripts/check-no-authored-js.sh +0 -13
- package/scripts/context7-api-check.sh +0 -65
- package/scripts/context7-export-env.sh +0 -42
- package/scripts/run-context7-mcp.sh +0 -8
- package/scripts/run-workspace-tests.sh +0 -15
- package/scripts/start-wave-10-local.sh +0 -189
- package/scripts/wave-agent-attach.sh +0 -47
- package/scripts/wave-auto-answer.sh +0 -118
- package/scripts/wave-dashboard-attach.sh +0 -13
- package/scripts/wave-launch.sh +0 -273
- package/scripts/wave-overnight-supervisor.sh +0 -145
- package/scripts/wave-status.sh +0 -379
- package/scripts/wave-watch.sh +0 -231
- package/services/README.md +0 -17
- package/services/openxml-validator/Dockerfile +0 -29
- package/services/openxml-validator/OpenXmlValidator.Api.csproj +0 -12
- package/services/openxml-validator/Program.cs +0 -436
- package/services/openxml-validator/README.md +0 -152
- package/services/openxml-validator/railway.json +0 -16
- package/services/react-word-editor/.tmp-a4/src/api/public-types.ts +0 -318
- package/services/react-word-editor/.tmp-a4/src/ui/WordReviewEditor.tsx +0 -1302
- package/services/react-word-editor/.tmp-a4/src/ui/editor-surface/editor-surface.tsx +0 -546
- package/services/react-word-editor/.tmp-a4/test/ui/word-review-editor.test.tsx +0 -146
- package/services/react-word-editor/.tmp-a4-build/src/api/public-types.js +0 -2
- package/services/react-word-editor/.tmp-a4-build/src/ui/WordReviewEditor.js +0 -818
- package/services/react-word-editor/.tmp-a4-build/src/ui/editor-surface/editor-surface.js +0 -229
- package/services/react-word-editor/.tmp-a4-build/test/ui/word-review-editor.test.js +0 -121
- package/services/react-word-editor/.tmp-wave-4-a3-tsconfig.json +0 -21
- package/services/react-word-editor/.tmp-wave-4-a3-tsconfig.tsbuildinfo +0 -1
- package/services/react-word-editor/Dockerfile +0 -26
- package/services/react-word-editor/README.md +0 -254
- package/services/react-word-editor/app/api/certification/route.ts +0 -79
- package/services/react-word-editor/app/api/demo-sessions/route.ts +0 -109
- package/services/react-word-editor/app/api/deploy-health/route.ts +0 -23
- package/services/react-word-editor/app/api/exports/[exportId]/route.ts +0 -34
- package/services/react-word-editor/app/api/exports/route.ts +0 -81
- package/services/react-word-editor/app/api/fixtures/[fixtureId]/run/route.ts +0 -100
- package/services/react-word-editor/app/api/health/route.ts +0 -70
- package/services/react-word-editor/app/api/runs/[runId]/route.ts +0 -36
- package/services/react-word-editor/app/api/scenarios/[scenarioId]/run/route.ts +0 -85
- package/services/react-word-editor/app/api/sessions/[sessionId]/route.ts +0 -199
- package/services/react-word-editor/app/api/sessions/[sessionId]/source/route.ts +0 -45
- package/services/react-word-editor/app/api/uploads/route.ts +0 -70
- package/services/react-word-editor/app/api/validate/route.ts +0 -310
- package/services/react-word-editor/app/certification/[runId]/page.tsx +0 -14
- package/services/react-word-editor/app/certification/page.tsx +0 -32
- package/services/react-word-editor/app/dashboard/page.tsx +0 -7
- package/services/react-word-editor/app/demo/page.tsx +0 -30
- package/services/react-word-editor/app/demo/prototype-client.tsx +0 -1080
- package/services/react-word-editor/app/editor/[sessionId]/page.tsx +0 -33
- package/services/react-word-editor/app/fixtures/page.tsx +0 -7
- package/services/react-word-editor/app/globals.css +0 -121
- package/services/react-word-editor/app/layout.tsx +0 -32
- package/services/react-word-editor/app/page.tsx +0 -30
- package/services/react-word-editor/app/runs/[runId]/page.tsx +0 -34
- package/services/react-word-editor/app/wave-10-word-review/page.tsx +0 -7
- package/services/react-word-editor/components/harness-control-bar.tsx +0 -289
- package/services/react-word-editor/components/harness-editor-session-client.tsx +0 -1214
- package/services/react-word-editor/components/harness-workspace-page.tsx +0 -715
- package/services/react-word-editor/components/reduced-motion-toggle.tsx +0 -79
- package/services/react-word-editor/components/workspace-certification-panel.tsx +0 -307
- package/services/react-word-editor/lib/certification-bundle.ts +0 -796
- package/services/react-word-editor/lib/certification-store.ts +0 -661
- package/services/react-word-editor/lib/demo-fixtures.test.mjs +0 -195
- package/services/react-word-editor/lib/demo-fixtures.ts +0 -1519
- package/services/react-word-editor/lib/editor-session-summary.test.mjs +0 -68
- package/services/react-word-editor/lib/editor-session-summary.ts +0 -14
- package/services/react-word-editor/lib/editor-session.ts +0 -228
- package/services/react-word-editor/lib/exports-route.test.mjs +0 -32
- package/services/react-word-editor/lib/harness-client.ts +0 -347
- package/services/react-word-editor/lib/harness-config.json +0 -30
- package/services/react-word-editor/lib/harness-config.test.mjs +0 -31
- package/services/react-word-editor/lib/harness-config.ts +0 -21
- package/services/react-word-editor/lib/harness-editor-datastore.test.mjs +0 -220
- package/services/react-word-editor/lib/harness-editor-datastore.ts +0 -161
- package/services/react-word-editor/lib/private-mode.test.mjs +0 -42
- package/services/react-word-editor/lib/private-mode.ts +0 -61
- package/services/react-word-editor/lib/regression-report.test.mjs +0 -352
- package/services/react-word-editor/lib/regression-report.ts +0 -896
- package/services/react-word-editor/lib/run-artifacts.ts +0 -934
- package/services/react-word-editor/lib/run-history.ts +0 -755
- package/services/react-word-editor/lib/scenario-artifacts.test.mjs +0 -41
- package/services/react-word-editor/lib/scenario-artifacts.ts +0 -44
- package/services/react-word-editor/lib/storage.ts +0 -953
- package/services/react-word-editor/lib/validator-client.test.mjs +0 -54
- package/services/react-word-editor/lib/validator-client.ts +0 -95
- package/services/react-word-editor/lib/workspace-navigation.ts +0 -79
- package/services/react-word-editor/middleware.ts +0 -35
- package/services/react-word-editor/next-env.d.ts +0 -6
- package/services/react-word-editor/next.config.mjs +0 -15
- package/services/react-word-editor/package.json +0 -38
- package/services/react-word-editor/postcss.config.mjs +0 -8
- package/services/react-word-editor/railway.json +0 -21
- package/services/react-word-editor/scripts/wave-10-certification.mjs +0 -101
- package/services/react-word-editor/scripts/wave-9-live-usability-pilot.mjs +0 -911
- package/services/react-word-editor/tsconfig.json +0 -39
- package/services/react-word-editor/tsconfig.tsbuildinfo +0 -1
- package/skills/README.md +0 -48
- package/skills/domain-docx-compatibility/SKILL.md +0 -44
- package/skills/domain-docx-compatibility/skill.json +0 -19
- package/skills/domain-editor-architecture/SKILL.md +0 -49
- package/skills/domain-editor-architecture/skill.json +0 -19
- package/skills/domain-legal-review/SKILL.md +0 -39
- package/skills/domain-legal-review/skill.json +0 -19
- package/skills/provider-aws/SKILL.md +0 -117
- package/skills/provider-aws/adapters/claude.md +0 -1
- package/skills/provider-aws/adapters/codex.md +0 -1
- package/skills/provider-aws/references/service-verification.md +0 -39
- package/skills/provider-aws/skill.json +0 -54
- package/skills/provider-custom-deploy/SKILL.md +0 -64
- package/skills/provider-custom-deploy/skill.json +0 -50
- package/skills/provider-docker-compose/SKILL.md +0 -96
- package/skills/provider-docker-compose/adapters/local.md +0 -1
- package/skills/provider-docker-compose/skill.json +0 -53
- package/skills/provider-github-release/SKILL.md +0 -121
- package/skills/provider-github-release/adapters/claude.md +0 -1
- package/skills/provider-github-release/adapters/codex.md +0 -1
- package/skills/provider-github-release/skill.json +0 -55
- package/skills/provider-kubernetes/SKILL.md +0 -143
- package/skills/provider-kubernetes/adapters/claude.md +0 -1
- package/skills/provider-kubernetes/adapters/codex.md +0 -1
- package/skills/provider-kubernetes/references/kubectl-patterns.md +0 -58
- package/skills/provider-kubernetes/skill.json +0 -52
- package/skills/provider-railway/SKILL.md +0 -123
- package/skills/provider-railway/adapters/claude.md +0 -1
- package/skills/provider-railway/adapters/codex.md +0 -1
- package/skills/provider-railway/adapters/local.md +0 -1
- package/skills/provider-railway/adapters/opencode.md +0 -1
- package/skills/provider-railway/references/verification-commands.md +0 -39
- package/skills/provider-railway/skill.json +0 -71
- package/skills/provider-ssh-manual/SKILL.md +0 -97
- package/skills/provider-ssh-manual/skill.json +0 -54
- package/skills/repo-coding-rules/SKILL.md +0 -55
- package/skills/repo-coding-rules/skill.json +0 -34
- package/skills/role-cont-eval/SKILL.md +0 -91
- package/skills/role-cont-eval/adapters/codex.md +0 -1
- package/skills/role-cont-eval/skill.json +0 -36
- package/skills/role-cont-qa/SKILL.md +0 -100
- package/skills/role-cont-qa/adapters/claude.md +0 -1
- package/skills/role-cont-qa/skill.json +0 -36
- package/skills/role-deploy/SKILL.md +0 -97
- package/skills/role-deploy/skill.json +0 -36
- package/skills/role-design/SKILL.md +0 -50
- package/skills/role-design/skill.json +0 -36
- package/skills/role-documentation/SKILL.md +0 -76
- package/skills/role-documentation/skill.json +0 -36
- package/skills/role-implementation/SKILL.md +0 -45
- package/skills/role-implementation/skill.json +0 -36
- package/skills/role-infra/SKILL.md +0 -81
- package/skills/role-infra/skill.json +0 -36
- package/skills/role-integration/SKILL.md +0 -91
- package/skills/role-integration/skill.json +0 -36
- package/skills/role-planner/SKILL.md +0 -39
- package/skills/role-planner/skill.json +0 -21
- package/skills/role-research/SKILL.md +0 -65
- package/skills/role-research/skill.json +0 -36
- package/skills/role-security/SKILL.md +0 -60
- package/skills/role-security/skill.json +0 -36
- package/skills/runtime-claude/SKILL.md +0 -66
- package/skills/runtime-claude/skill.json +0 -36
- package/skills/runtime-codex/SKILL.md +0 -58
- package/skills/runtime-codex/skill.json +0 -36
- package/skills/runtime-local/SKILL.md +0 -46
- package/skills/runtime-local/skill.json +0 -36
- package/skills/runtime-opencode/SKILL.md +0 -58
- package/skills/runtime-opencode/skill.json +0 -36
- package/skills/signal-hygiene/SKILL.md +0 -51
- package/skills/signal-hygiene/skill.json +0 -20
- package/skills/tui-design/SKILL.md +0 -77
- package/skills/tui-design/references/tui-design.md +0 -259
- package/skills/tui-design/skill.json +0 -36
- package/skills/wave-core/SKILL.md +0 -141
- package/skills/wave-core/references/marker-syntax.md +0 -70
- package/skills/wave-core/skill.json +0 -35
- package/test/README.md +0 -16
- package/test/core/formatting-commands.test.ts +0 -285
- package/test/core/image-commands.test.ts +0 -298
- package/test/core/mapping.test.ts +0 -186
- package/test/core/text-commands.test.ts +0 -176
- package/test/fixtures/docx/F01-basic-contract.docx +0 -0
- package/test/fixtures/docx/F01-basic-contract.md +0 -33
- package/test/fixtures/docx/F02-headings-styles.docx +0 -0
- package/test/fixtures/docx/F02-headings-styles.md +0 -33
- package/test/fixtures/docx/F03-legal-outline-numbering.docx +0 -0
- package/test/fixtures/docx/F03-legal-outline-numbering.md +0 -34
- package/test/fixtures/docx/F04-restart-numbering-schedules.docx +0 -0
- package/test/fixtures/docx/F04-restart-numbering-schedules.md +0 -33
- package/test/fixtures/docx/F05-table-heavy-agreement.docx +0 -0
- package/test/fixtures/docx/F05-table-heavy-agreement.md +0 -34
- package/test/fixtures/docx/F06-merged-cells-signature-table.docx +0 -0
- package/test/fixtures/docx/F06-merged-cells-signature-table.md +0 -34
- package/test/fixtures/docx/F07-inline-images-exhibit.docx +0 -0
- package/test/fixtures/docx/F07-inline-images-exhibit.md +0 -34
- package/test/fixtures/docx/F08-hyperlinks.docx +0 -0
- package/test/fixtures/docx/F08-hyperlinks.md +0 -33
- package/test/fixtures/docx/F09-comments-single-paragraph.docx +0 -0
- package/test/fixtures/docx/F09-comments-single-paragraph.md +0 -33
- package/test/fixtures/docx/F10-threaded-comments-resolve.docx +0 -0
- package/test/fixtures/docx/F10-threaded-comments-resolve.md +0 -33
- package/test/fixtures/docx/F11-redlines-basic.docx +0 -0
- package/test/fixtures/docx/F11-redlines-basic.md +0 -33
- package/test/fixtures/docx/F12-redlines-paragraph-joins-splits.docx +0 -0
- package/test/fixtures/docx/F12-redlines-paragraph-joins-splits.md +0 -33
- package/test/fixtures/docx/F13-comments-on-deleted-text.docx +0 -0
- package/test/fixtures/docx/F13-comments-on-deleted-text.md +0 -33
- package/test/fixtures/docx/F14-revisions-in-tables-and-lists.docx +0 -0
- package/test/fixtures/docx/F14-revisions-in-tables-and-lists.md +0 -33
- package/test/fixtures/docx/F15-sections-headers-footers.docx +0 -0
- package/test/fixtures/docx/F15-sections-headers-footers.md +0 -33
- package/test/fixtures/docx/F16-footnotes-endnotes.docx +0 -0
- package/test/fixtures/docx/F16-footnotes-endnotes.md +0 -33
- package/test/fixtures/docx/F17-fields-and-toc.docx +0 -0
- package/test/fixtures/docx/F17-fields-and-toc.md +0 -33
- package/test/fixtures/docx/F18-content-controls-template.docx +0 -0
- package/test/fixtures/docx/F18-content-controls-template.md +0 -33
- package/test/fixtures/docx/F19-custom-xml-doc-assembly.docx +0 -0
- package/test/fixtures/docx/F19-custom-xml-doc-assembly.md +0 -35
- package/test/fixtures/docx/F20-unknown-ooxml-and-alternatecontent.docx +0 -0
- package/test/fixtures/docx/F20-unknown-ooxml-and-alternatecontent.md +0 -33
- package/test/fixtures/docx/F21-malformed-broken-docx.docx +0 -0
- package/test/fixtures/docx/F21-malformed-broken-docx.md +0 -33
- package/test/fixtures/docx/README.md +0 -74
- package/test/fixtures/docx/certification-manifest.json +0 -104
- package/test/fixtures/docx/fixtures.manifest.json +0 -196
- package/test/fixtures/encrypted-docx/README.md +0 -27
- package/test/fixtures/encrypted-docx/certification-manifest.json +0 -9
- package/test/fixtures/encrypted-docx/fixtures.manifest.json +0 -47
- package/test/fixtures/scenarios/docx/README.md +0 -25
- package/test/fixtures/scenarios/docx/S01-sow-template.docx +0 -0
- package/test/fixtures/scenarios/docx/S01-sow-template.md +0 -30
- package/test/fixtures/scenarios/docx/S02-bw-partner-user-licence-agreement-redlines.docx +0 -0
- package/test/fixtures/scenarios/docx/S02-bw-partner-user-licence-agreement-redlines.md +0 -32
- package/test/fixtures/scenarios/docx/scenario-manifest.json +0 -53
- package/test/formats/xlsx/io/xlsx-import.test.ts +0 -766
- package/test/formats/xlsx/model/workbook.test.ts +0 -669
- package/test/helpers/dom-setup.ts +0 -124
- package/test/io/comment-roundtrip.test.ts +0 -272
- package/test/io/complex-content-roundtrip.test.ts +0 -632
- package/test/io/docx-compatibility-regression.test.ts +0 -199
- package/test/io/docx-session.test.ts +0 -1495
- package/test/io/footnotes-roundtrip.test.ts +0 -318
- package/test/io/headers-footers-roundtrip.test.ts +0 -547
- package/test/io/numbering-roundtrip.test.ts +0 -234
- package/test/io/package-reader.test.ts +0 -199
- package/test/io/paragraph-properties-roundtrip.test.ts +0 -129
- package/test/io/preserved-package-roundtrip.test.ts +0 -365
- package/test/io/property-completeness.test.ts +0 -292
- package/test/io/revision-roundtrip.test.ts +0 -347
- package/test/io/structural-blocks.test.ts +0 -202
- package/test/io/table-media-roundtrip.test.ts +0 -448
- package/test/io/table-properties-roundtrip.test.ts +0 -569
- package/test/io/table-roundtrip.test.ts +0 -302
- package/test/io/text-roundtrip.test.ts +0 -344
- package/test/model/canonical-document.test.ts +0 -285
- package/test/preservation/opaque-fragment-store.test.ts +0 -121
- package/test/preservation/package-preservation.test.ts +0 -395
- package/test/preservation/store.test.ts +0 -84
- package/test/review/comment-remapping.test.ts +0 -220
- package/test/review/comment-store.test.ts +0 -180
- package/test/review/move-revisions.test.ts +0 -143
- package/test/review/property-change-revisions.test.ts +0 -225
- package/test/review/revision-actions.test.ts +0 -330
- package/test/review/revision-store.test.ts +0 -193
- package/test/runtime/session-capabilities.test.ts +0 -260
- package/test/runtime/table-commands.test.ts +0 -356
- package/test/runtime/table-schema.test.ts +0 -221
- package/test/runtime/tracked-changes-toggle.test.ts +0 -107
- package/test/ui/comment-review-surface.test.tsx +0 -114
- package/test/ui/reduced-motion-toggle.test.tsx +0 -137
- package/test/ui/word-review-editor.imported-scenarios.test.tsx +0 -169
- package/test/ui/word-review-editor.interaction.test.tsx +0 -1198
- package/test/ui/word-review-editor.test.js +0 -188
- package/test/ui/word-review-editor.test.tsx +0 -280
- package/test/ui-tailwind/search-plugin.test.ts +0 -286
- package/test/validation/compatibility-engine.test.ts +0 -336
- package/test/validation/compatibility-report.test.ts +0 -189
- package/test/validation/low-priority-word-surfaces.test.ts +0 -282
- package/test/validation/malformed-doc.test.ts +0 -113
- package/test-results/.last-run.json +0 -4
- package/wave.config.json +0 -406
|
@@ -1,953 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
getScenarioArtifactPath,
|
|
8
|
-
loadScenarioArtifacts,
|
|
9
|
-
type ScenarioValidatorPolicy,
|
|
10
|
-
} from "./scenario-artifacts.ts";
|
|
11
|
-
|
|
12
|
-
export const DOCX_MIME_TYPE =
|
|
13
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
14
|
-
export const HARNESS_ALLOWED_DOCX_MIME_TYPES = [DOCX_MIME_TYPE, "application/octet-stream"] as const;
|
|
15
|
-
export const HARNESS_MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
16
|
-
export const HARNESS_UPLOAD_RETENTION_LIMIT = 100;
|
|
17
|
-
export const HARNESS_SESSION_RETENTION_LIMIT = 100;
|
|
18
|
-
export const HARNESS_EXPORT_RETENTION_LIMIT = 100;
|
|
19
|
-
export const HARNESS_VALIDATION_RETENTION_LIMIT = 200;
|
|
20
|
-
export const HARNESS_STORAGE_INDEX_VERSION = "react-word-editor-harness/1";
|
|
21
|
-
|
|
22
|
-
type HarnessStorageMode = "configured-volume-path" | "default-local-path";
|
|
23
|
-
|
|
24
|
-
type HarnessStorageIndex = {
|
|
25
|
-
version: typeof HARNESS_STORAGE_INDEX_VERSION;
|
|
26
|
-
uploads: HarnessUploadSourceRecord[];
|
|
27
|
-
sessions: HarnessSessionRecord[];
|
|
28
|
-
exports: HarnessExportRecord[];
|
|
29
|
-
validationRuns: HarnessValidationRunRecord[];
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
type FixtureManifest = {
|
|
33
|
-
manifestVersion: "docx-fixtures/1";
|
|
34
|
-
fixtures: FixtureManifestEntry[];
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
type FixtureManifestEntry = {
|
|
38
|
-
fixtureId: string;
|
|
39
|
-
name: string;
|
|
40
|
-
primaryFocus: string;
|
|
41
|
-
featureClassFocus: "supported-roundtrip" | "preserve-only" | "unsupported-fatal";
|
|
42
|
-
artifactStatus: string;
|
|
43
|
-
proofBearingWaves: number[];
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
type SourceLocator =
|
|
47
|
-
| {
|
|
48
|
-
kind: "repo-file";
|
|
49
|
-
absolutePath: string;
|
|
50
|
-
}
|
|
51
|
-
| {
|
|
52
|
-
kind: "storage-file";
|
|
53
|
-
relativePath: string;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
type HarnessSourceRecordBase = {
|
|
57
|
-
sourceId: string;
|
|
58
|
-
sourceKind: "fixture" | "scenario" | "upload";
|
|
59
|
-
name: string;
|
|
60
|
-
sourceLabel: string;
|
|
61
|
-
fileName: string;
|
|
62
|
-
mimeType: string;
|
|
63
|
-
byteLength?: number;
|
|
64
|
-
createdAt: string;
|
|
65
|
-
updatedAt: string;
|
|
66
|
-
available: boolean;
|
|
67
|
-
retentionClass: "repo-fixture" | "scenario-artifact" | "operator-upload";
|
|
68
|
-
locator?: SourceLocator;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export type HarnessSourceRecord = HarnessSourceRecordBase & {
|
|
72
|
-
fixtureId?: string;
|
|
73
|
-
scenarioId?: string;
|
|
74
|
-
noteFileName?: string;
|
|
75
|
-
primaryFocus?: string;
|
|
76
|
-
featureClassFocus?: string;
|
|
77
|
-
artifactStatus?: string;
|
|
78
|
-
proofBearingWaves?: number[];
|
|
79
|
-
releaseGate?: boolean;
|
|
80
|
-
waveTargets?: number[];
|
|
81
|
-
focusAreas?: string[];
|
|
82
|
-
validatorPolicy?: ScenarioValidatorPolicy;
|
|
83
|
-
wordExpectation?: string;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export type HarnessUploadSourceRecord = HarnessSourceRecordBase & {
|
|
87
|
-
sourceKind: "upload";
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
export type HarnessSessionRecord = {
|
|
91
|
-
sessionId: string;
|
|
92
|
-
documentId: string;
|
|
93
|
-
sourceId: string;
|
|
94
|
-
sourceKind: HarnessSourceRecord["sourceKind"];
|
|
95
|
-
sourceLabel: string;
|
|
96
|
-
createdAt: string;
|
|
97
|
-
updatedAt: string;
|
|
98
|
-
latestExportId?: string;
|
|
99
|
-
latestValidationRunId?: string;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export type HarnessExportRecord = {
|
|
103
|
-
exportId: string;
|
|
104
|
-
sessionId: string;
|
|
105
|
-
fileName: string;
|
|
106
|
-
mimeType: string;
|
|
107
|
-
byteLength: number;
|
|
108
|
-
storedRelativePath: string;
|
|
109
|
-
createdAt: string;
|
|
110
|
-
reason?: string;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export type HarnessValidationRunRecord = {
|
|
114
|
-
runId: string;
|
|
115
|
-
sessionId?: string;
|
|
116
|
-
sourceId?: string;
|
|
117
|
-
exportId?: string;
|
|
118
|
-
targetType: "source" | "export" | "direct-upload";
|
|
119
|
-
fileName: string;
|
|
120
|
-
byteLength: number;
|
|
121
|
-
storedRelativePath: string;
|
|
122
|
-
createdAt: string;
|
|
123
|
-
service: string;
|
|
124
|
-
summary: {
|
|
125
|
-
isValid: boolean;
|
|
126
|
-
packageFindingCount: number;
|
|
127
|
-
sdkFindingCount: number;
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
export type HarnessValidationReport = {
|
|
132
|
-
service?: string;
|
|
133
|
-
validatedAtUtc?: string;
|
|
134
|
-
document?: Record<string, unknown>;
|
|
135
|
-
summary?: {
|
|
136
|
-
isValid?: boolean;
|
|
137
|
-
packageFindingCount?: number;
|
|
138
|
-
sdkFindingCount?: number;
|
|
139
|
-
};
|
|
140
|
-
package?: {
|
|
141
|
-
findings?: unknown[];
|
|
142
|
-
};
|
|
143
|
-
sdk?: {
|
|
144
|
-
findings?: unknown[];
|
|
145
|
-
};
|
|
146
|
-
[key: string]: unknown;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
export type HarnessHealthSnapshot = {
|
|
150
|
-
storageDir: string;
|
|
151
|
-
storage: {
|
|
152
|
-
configuredStorageDir?: string;
|
|
153
|
-
mode: HarnessStorageMode;
|
|
154
|
-
persistence: "file-backed-manifest-index";
|
|
155
|
-
indexVersion: typeof HARNESS_STORAGE_INDEX_VERSION;
|
|
156
|
-
};
|
|
157
|
-
assumptions: HarnessStorageAssumptions;
|
|
158
|
-
retentionPolicy: {
|
|
159
|
-
maxUploadBytes: number;
|
|
160
|
-
uploadArtifactLimit: number;
|
|
161
|
-
sessionLimit: number;
|
|
162
|
-
exportArtifactLimit: number;
|
|
163
|
-
validationRunLimit: number;
|
|
164
|
-
mode: "volume-backed-manual-review";
|
|
165
|
-
};
|
|
166
|
-
counts: {
|
|
167
|
-
fixtures: number;
|
|
168
|
-
scenarios: number;
|
|
169
|
-
uploads: number;
|
|
170
|
-
sessions: number;
|
|
171
|
-
exports: number;
|
|
172
|
-
validationRuns: number;
|
|
173
|
-
};
|
|
174
|
-
bytes: {
|
|
175
|
-
uploads: number;
|
|
176
|
-
exports: number;
|
|
177
|
-
};
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
export type HarnessStorageAssumptions = {
|
|
181
|
-
acceptedExtensions: [".docx"];
|
|
182
|
-
acceptedMimeTypes: readonly string[];
|
|
183
|
-
maxUploadBytes: number;
|
|
184
|
-
uploadArtifactLimit: number;
|
|
185
|
-
sessionLimit: number;
|
|
186
|
-
exportArtifactLimit: number;
|
|
187
|
-
validationRunLimit: number;
|
|
188
|
-
retentionMode: "volume-backed-manual-review";
|
|
189
|
-
storageMode: HarnessStorageMode;
|
|
190
|
-
activeStorageDir: string;
|
|
191
|
-
configuredStorageDir?: string;
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
export class HarnessStorageError extends Error {
|
|
195
|
-
readonly code: string;
|
|
196
|
-
readonly status: number;
|
|
197
|
-
|
|
198
|
-
constructor(code: string, status: number, message: string) {
|
|
199
|
-
super(message);
|
|
200
|
-
this.name = "HarnessStorageError";
|
|
201
|
-
this.code = code;
|
|
202
|
-
this.status = status;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export function isHarnessStorageError(error: unknown): error is HarnessStorageError {
|
|
207
|
-
return error instanceof HarnessStorageError;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const moduleDirPath = path.dirname(fileURLToPath(import.meta.url));
|
|
211
|
-
const serviceRootDir = path.resolve(moduleDirPath, "..");
|
|
212
|
-
const repoRootDir = path.resolve(serviceRootDir, "../..");
|
|
213
|
-
const fixturesDirPath = path.join(repoRootDir, "test/fixtures/docx");
|
|
214
|
-
const fixturesManifestPath = path.join(fixturesDirPath, "fixtures.manifest.json");
|
|
215
|
-
const defaultStorageDir = path.join(serviceRootDir, ".tmp/react-word-editor-harness");
|
|
216
|
-
|
|
217
|
-
function getConfiguredStorageDir(): string | undefined {
|
|
218
|
-
const value = process.env.HARNESS_STORAGE_DIR?.trim();
|
|
219
|
-
return value ? value : undefined;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function getStorageDir(): string {
|
|
223
|
-
return getConfiguredStorageDir() || defaultStorageDir;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function getStorageMode(): HarnessStorageMode {
|
|
227
|
-
return getConfiguredStorageDir() ? "configured-volume-path" : "default-local-path";
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function getIndexPath(storageDir: string): string {
|
|
231
|
-
return path.join(storageDir, "index.json");
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function getUploadsDir(storageDir: string): string {
|
|
235
|
-
return path.join(storageDir, "uploads");
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function getExportsDir(storageDir: string): string {
|
|
239
|
-
return path.join(storageDir, "exports");
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function getValidationDir(storageDir: string): string {
|
|
243
|
-
return path.join(storageDir, "validation-runs");
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function createEmptyIndex(): HarnessStorageIndex {
|
|
247
|
-
return {
|
|
248
|
-
version: HARNESS_STORAGE_INDEX_VERSION,
|
|
249
|
-
uploads: [],
|
|
250
|
-
sessions: [],
|
|
251
|
-
exports: [],
|
|
252
|
-
validationRuns: [],
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function normalizeIndex(raw: unknown): HarnessStorageIndex {
|
|
257
|
-
if (!raw || typeof raw !== "object") {
|
|
258
|
-
return createEmptyIndex();
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const candidate = raw as Partial<HarnessStorageIndex>;
|
|
262
|
-
|
|
263
|
-
return {
|
|
264
|
-
version: HARNESS_STORAGE_INDEX_VERSION,
|
|
265
|
-
uploads: Array.isArray(candidate.uploads) ? candidate.uploads : [],
|
|
266
|
-
sessions: Array.isArray(candidate.sessions) ? candidate.sessions : [],
|
|
267
|
-
exports: Array.isArray(candidate.exports) ? candidate.exports : [],
|
|
268
|
-
validationRuns: Array.isArray(candidate.validationRuns) ? candidate.validationRuns : [],
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async function ensureStorageLayout(): Promise<string> {
|
|
273
|
-
const storageDir = getStorageDir();
|
|
274
|
-
await mkdir(storageDir, { recursive: true });
|
|
275
|
-
await mkdir(getUploadsDir(storageDir), { recursive: true });
|
|
276
|
-
await mkdir(getExportsDir(storageDir), { recursive: true });
|
|
277
|
-
await mkdir(getValidationDir(storageDir), { recursive: true });
|
|
278
|
-
|
|
279
|
-
const indexPath = getIndexPath(storageDir);
|
|
280
|
-
|
|
281
|
-
try {
|
|
282
|
-
await stat(indexPath);
|
|
283
|
-
} catch {
|
|
284
|
-
await writeFile(indexPath, JSON.stringify(createEmptyIndex(), null, 2));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return storageDir;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async function readIndex(): Promise<HarnessStorageIndex> {
|
|
291
|
-
const storageDir = await ensureStorageLayout();
|
|
292
|
-
const raw = await readFile(getIndexPath(storageDir), "utf8");
|
|
293
|
-
const normalized = normalizeIndex(JSON.parse(raw));
|
|
294
|
-
const serialized = JSON.stringify(normalized, null, 2);
|
|
295
|
-
|
|
296
|
-
if (raw !== serialized) {
|
|
297
|
-
await writeIndex(normalized);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return normalized;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
async function writeIndex(index: HarnessStorageIndex): Promise<void> {
|
|
304
|
-
const storageDir = await ensureStorageLayout();
|
|
305
|
-
const normalized = normalizeIndex(index);
|
|
306
|
-
const indexPath = getIndexPath(storageDir);
|
|
307
|
-
const tempPath = `${indexPath}.${process.pid}.${Date.now()}.tmp`;
|
|
308
|
-
await writeFile(tempPath, JSON.stringify(normalized, null, 2));
|
|
309
|
-
await rename(tempPath, indexPath);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
async function loadFixtureManifest(): Promise<FixtureManifest> {
|
|
313
|
-
const raw = await readFile(fixturesManifestPath, "utf8");
|
|
314
|
-
return JSON.parse(raw) as FixtureManifest;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
async function findFixtureFileNameMap(): Promise<Map<string, string>> {
|
|
318
|
-
const entries = await readdir(fixturesDirPath);
|
|
319
|
-
const map = new Map<string, string>();
|
|
320
|
-
|
|
321
|
-
for (const entry of entries) {
|
|
322
|
-
const match = /^(F\d{2})-.*\.docx$/i.exec(entry);
|
|
323
|
-
|
|
324
|
-
if (match) {
|
|
325
|
-
map.set(match[1].toUpperCase(), entry);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return map;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
async function listFixtureSources(): Promise<HarnessSourceRecord[]> {
|
|
333
|
-
const [manifest, fileNameMap] = await Promise.all([
|
|
334
|
-
loadFixtureManifest(),
|
|
335
|
-
findFixtureFileNameMap(),
|
|
336
|
-
]);
|
|
337
|
-
|
|
338
|
-
return Promise.all(
|
|
339
|
-
manifest.fixtures.map(async (fixture) => {
|
|
340
|
-
const resolvedFileName = fileNameMap.get(fixture.fixtureId.toUpperCase());
|
|
341
|
-
const absolutePath = resolvedFileName ? path.join(fixturesDirPath, resolvedFileName) : undefined;
|
|
342
|
-
const fileStat = absolutePath ? await stat(absolutePath) : undefined;
|
|
343
|
-
|
|
344
|
-
return {
|
|
345
|
-
sourceId: `fixture:${fixture.fixtureId}`,
|
|
346
|
-
sourceKind: "fixture",
|
|
347
|
-
fixtureId: fixture.fixtureId,
|
|
348
|
-
name: fixture.name,
|
|
349
|
-
sourceLabel: `${fixture.fixtureId} ${fixture.name}`,
|
|
350
|
-
fileName: resolvedFileName ?? `${fixture.fixtureId}.docx`,
|
|
351
|
-
mimeType: DOCX_MIME_TYPE,
|
|
352
|
-
byteLength: fileStat?.size,
|
|
353
|
-
createdAt: "manifest-frozen",
|
|
354
|
-
updatedAt: "manifest-frozen",
|
|
355
|
-
available: Boolean(absolutePath),
|
|
356
|
-
retentionClass: "repo-fixture",
|
|
357
|
-
locator: absolutePath
|
|
358
|
-
? {
|
|
359
|
-
kind: "repo-file",
|
|
360
|
-
absolutePath,
|
|
361
|
-
}
|
|
362
|
-
: undefined,
|
|
363
|
-
primaryFocus: fixture.primaryFocus,
|
|
364
|
-
featureClassFocus: fixture.featureClassFocus,
|
|
365
|
-
artifactStatus: fixture.artifactStatus,
|
|
366
|
-
proofBearingWaves: fixture.proofBearingWaves,
|
|
367
|
-
} satisfies HarnessSourceRecord;
|
|
368
|
-
}),
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
async function listScenarioSources(): Promise<HarnessSourceRecord[]> {
|
|
373
|
-
const scenarios = await loadScenarioArtifacts();
|
|
374
|
-
|
|
375
|
-
return Promise.all(
|
|
376
|
-
scenarios.map(async (scenario) => {
|
|
377
|
-
const absolutePath = getScenarioArtifactPath(scenario.fileName);
|
|
378
|
-
const fileStat = await stat(absolutePath);
|
|
379
|
-
|
|
380
|
-
return {
|
|
381
|
-
sourceId: `scenario:${scenario.scenarioId}`,
|
|
382
|
-
sourceKind: "scenario",
|
|
383
|
-
scenarioId: scenario.scenarioId,
|
|
384
|
-
name: scenario.name,
|
|
385
|
-
sourceLabel: `${scenario.scenarioId} ${scenario.name}`,
|
|
386
|
-
fileName: scenario.fileName,
|
|
387
|
-
mimeType: DOCX_MIME_TYPE,
|
|
388
|
-
byteLength: fileStat.size,
|
|
389
|
-
createdAt: "scenario-frozen",
|
|
390
|
-
updatedAt: "scenario-frozen",
|
|
391
|
-
available: true,
|
|
392
|
-
retentionClass: "scenario-artifact",
|
|
393
|
-
locator: {
|
|
394
|
-
kind: "repo-file",
|
|
395
|
-
absolutePath,
|
|
396
|
-
},
|
|
397
|
-
noteFileName: scenario.noteFileName,
|
|
398
|
-
releaseGate: scenario.releaseGate,
|
|
399
|
-
waveTargets: scenario.waveTargets,
|
|
400
|
-
focusAreas: scenario.focusAreas,
|
|
401
|
-
validatorPolicy: scenario.validatorPolicy,
|
|
402
|
-
wordExpectation: scenario.wordExpectation,
|
|
403
|
-
} satisfies HarnessSourceRecord;
|
|
404
|
-
}),
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function sortSources(a: HarnessSourceRecord, b: HarnessSourceRecord): number {
|
|
409
|
-
const sourceOrder = {
|
|
410
|
-
fixture: 0,
|
|
411
|
-
scenario: 1,
|
|
412
|
-
upload: 2,
|
|
413
|
-
} as const;
|
|
414
|
-
|
|
415
|
-
if (sourceOrder[a.sourceKind] !== sourceOrder[b.sourceKind]) {
|
|
416
|
-
return sourceOrder[a.sourceKind] - sourceOrder[b.sourceKind];
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (a.sourceKind === "upload" && b.sourceKind === "upload") {
|
|
420
|
-
return b.createdAt.localeCompare(a.createdAt);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return a.sourceLabel.localeCompare(b.sourceLabel);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function sanitizeFileName(fileName: string): string {
|
|
427
|
-
const sanitized = fileName.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
|
|
428
|
-
return sanitized.length > 0 ? sanitized : "document.docx";
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
export function getHarnessStorageAssumptions(): HarnessStorageAssumptions {
|
|
432
|
-
return {
|
|
433
|
-
acceptedExtensions: [".docx"],
|
|
434
|
-
acceptedMimeTypes: HARNESS_ALLOWED_DOCX_MIME_TYPES,
|
|
435
|
-
maxUploadBytes: HARNESS_MAX_UPLOAD_BYTES,
|
|
436
|
-
uploadArtifactLimit: HARNESS_UPLOAD_RETENTION_LIMIT,
|
|
437
|
-
sessionLimit: HARNESS_SESSION_RETENTION_LIMIT,
|
|
438
|
-
exportArtifactLimit: HARNESS_EXPORT_RETENTION_LIMIT,
|
|
439
|
-
validationRunLimit: HARNESS_VALIDATION_RETENTION_LIMIT,
|
|
440
|
-
retentionMode: "volume-backed-manual-review",
|
|
441
|
-
storageMode: getStorageMode(),
|
|
442
|
-
activeStorageDir: getStorageDir(),
|
|
443
|
-
configuredStorageDir: getConfiguredStorageDir(),
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
export function assertAcceptedDocxPayload(
|
|
448
|
-
fileName: string,
|
|
449
|
-
byteLength: number,
|
|
450
|
-
mimeType?: string,
|
|
451
|
-
): void {
|
|
452
|
-
if (!fileName.toLowerCase().endsWith(".docx")) {
|
|
453
|
-
throw new HarnessStorageError(
|
|
454
|
-
"invalid_extension",
|
|
455
|
-
415,
|
|
456
|
-
"Harness uploads and exports must be `.docx` packages.",
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const normalizedMimeType = mimeType?.trim().toLowerCase();
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
normalizedMimeType &&
|
|
464
|
-
!HARNESS_ALLOWED_DOCX_MIME_TYPES.includes(
|
|
465
|
-
normalizedMimeType as (typeof HARNESS_ALLOWED_DOCX_MIME_TYPES)[number],
|
|
466
|
-
)
|
|
467
|
-
) {
|
|
468
|
-
throw new HarnessStorageError(
|
|
469
|
-
"unsupported_mime_type",
|
|
470
|
-
415,
|
|
471
|
-
`Harness accepts DOCX uploads with ${HARNESS_ALLOWED_DOCX_MIME_TYPES.join(" or ")} MIME types.`,
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (byteLength <= 0) {
|
|
476
|
-
throw new HarnessStorageError("empty_file", 400, "Harness uploads cannot be empty.");
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (byteLength > HARNESS_MAX_UPLOAD_BYTES) {
|
|
480
|
-
throw new HarnessStorageError(
|
|
481
|
-
"file_too_large",
|
|
482
|
-
413,
|
|
483
|
-
`Harness uploads are capped at ${Math.round(HARNESS_MAX_UPLOAD_BYTES / (1024 * 1024))} MiB.`,
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function createDocumentId(prefix: string): string {
|
|
489
|
-
return `${prefix}-${randomUUID().slice(0, 12)}`;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
function summarizeValidationReport(report: HarnessValidationReport): HarnessValidationRunRecord["summary"] {
|
|
493
|
-
const packageFindingCount =
|
|
494
|
-
typeof report.summary?.packageFindingCount === "number"
|
|
495
|
-
? report.summary.packageFindingCount
|
|
496
|
-
: Array.isArray(report.package?.findings)
|
|
497
|
-
? report.package.findings.length
|
|
498
|
-
: 0;
|
|
499
|
-
const sdkFindingCount =
|
|
500
|
-
typeof report.summary?.sdkFindingCount === "number"
|
|
501
|
-
? report.summary.sdkFindingCount
|
|
502
|
-
: Array.isArray(report.sdk?.findings)
|
|
503
|
-
? report.sdk.findings.length
|
|
504
|
-
: 0;
|
|
505
|
-
|
|
506
|
-
return {
|
|
507
|
-
isValid:
|
|
508
|
-
typeof report.summary?.isValid === "boolean"
|
|
509
|
-
? report.summary.isValid
|
|
510
|
-
: packageFindingCount === 0 && sdkFindingCount === 0,
|
|
511
|
-
packageFindingCount,
|
|
512
|
-
sdkFindingCount,
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
async function pruneArtifactsIfNeeded(index: HarnessStorageIndex): Promise<HarnessStorageIndex> {
|
|
517
|
-
const storageDir = await ensureStorageLayout();
|
|
518
|
-
let nextIndex = index;
|
|
519
|
-
|
|
520
|
-
if (nextIndex.uploads.length > HARNESS_UPLOAD_RETENTION_LIMIT) {
|
|
521
|
-
const protectedSourceIds = new Set(nextIndex.sessions.map((session) => session.sourceId));
|
|
522
|
-
const retained: HarnessUploadSourceRecord[] = [];
|
|
523
|
-
const trimmed: HarnessUploadSourceRecord[] = [];
|
|
524
|
-
|
|
525
|
-
for (const record of nextIndex.uploads) {
|
|
526
|
-
if (
|
|
527
|
-
retained.length < HARNESS_UPLOAD_RETENTION_LIMIT ||
|
|
528
|
-
protectedSourceIds.has(record.sourceId)
|
|
529
|
-
) {
|
|
530
|
-
retained.push(record);
|
|
531
|
-
} else {
|
|
532
|
-
trimmed.push(record);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
for (const record of trimmed) {
|
|
537
|
-
if (record.locator?.kind !== "storage-file") {
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
try {
|
|
541
|
-
await unlink(path.join(storageDir, record.locator.relativePath));
|
|
542
|
-
} catch {
|
|
543
|
-
// Best-effort cleanup for old upload artifacts.
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
nextIndex = {
|
|
548
|
-
...nextIndex,
|
|
549
|
-
uploads: retained,
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (nextIndex.sessions.length > HARNESS_SESSION_RETENTION_LIMIT) {
|
|
554
|
-
const protectedSessionIds = new Set(
|
|
555
|
-
[
|
|
556
|
-
...nextIndex.exports.map((record) => record.sessionId),
|
|
557
|
-
...nextIndex.validationRuns
|
|
558
|
-
.map((record) => record.sessionId)
|
|
559
|
-
.filter((value): value is string => typeof value === "string"),
|
|
560
|
-
],
|
|
561
|
-
);
|
|
562
|
-
const retained: HarnessSessionRecord[] = [];
|
|
563
|
-
|
|
564
|
-
for (const record of nextIndex.sessions) {
|
|
565
|
-
if (
|
|
566
|
-
retained.length < HARNESS_SESSION_RETENTION_LIMIT ||
|
|
567
|
-
protectedSessionIds.has(record.sessionId)
|
|
568
|
-
) {
|
|
569
|
-
retained.push(record);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
nextIndex = {
|
|
574
|
-
...nextIndex,
|
|
575
|
-
sessions: retained,
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (nextIndex.validationRuns.length > HARNESS_VALIDATION_RETENTION_LIMIT) {
|
|
580
|
-
const retained = nextIndex.validationRuns.slice(0, HARNESS_VALIDATION_RETENTION_LIMIT);
|
|
581
|
-
const trimmed = nextIndex.validationRuns.slice(HARNESS_VALIDATION_RETENTION_LIMIT);
|
|
582
|
-
|
|
583
|
-
for (const record of trimmed) {
|
|
584
|
-
try {
|
|
585
|
-
await unlink(path.join(storageDir, record.storedRelativePath));
|
|
586
|
-
} catch {
|
|
587
|
-
// Best-effort cleanup for old validation artifacts.
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
nextIndex = {
|
|
592
|
-
...nextIndex,
|
|
593
|
-
validationRuns: retained,
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
if (nextIndex.exports.length > HARNESS_EXPORT_RETENTION_LIMIT) {
|
|
598
|
-
const protectedExportIds = new Set(
|
|
599
|
-
nextIndex.validationRuns.map((record) => record.exportId).filter(Boolean) as string[],
|
|
600
|
-
);
|
|
601
|
-
const retained: HarnessExportRecord[] = [];
|
|
602
|
-
const trimmed: HarnessExportRecord[] = [];
|
|
603
|
-
|
|
604
|
-
for (const record of nextIndex.exports) {
|
|
605
|
-
if (
|
|
606
|
-
retained.length < HARNESS_EXPORT_RETENTION_LIMIT ||
|
|
607
|
-
protectedExportIds.has(record.exportId) ||
|
|
608
|
-
nextIndex.sessions.some((session) => session.latestExportId === record.exportId)
|
|
609
|
-
) {
|
|
610
|
-
retained.push(record);
|
|
611
|
-
} else {
|
|
612
|
-
trimmed.push(record);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
for (const record of trimmed) {
|
|
617
|
-
try {
|
|
618
|
-
await unlink(path.join(storageDir, record.storedRelativePath));
|
|
619
|
-
} catch {
|
|
620
|
-
// Best-effort cleanup for old export artifacts.
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
nextIndex = {
|
|
625
|
-
...nextIndex,
|
|
626
|
-
exports: retained,
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
return nextIndex;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
export async function listHarnessSources(): Promise<HarnessSourceRecord[]> {
|
|
634
|
-
const [index, fixtures, scenarios] = await Promise.all([
|
|
635
|
-
readIndex(),
|
|
636
|
-
listFixtureSources(),
|
|
637
|
-
listScenarioSources(),
|
|
638
|
-
]);
|
|
639
|
-
|
|
640
|
-
return [...fixtures, ...scenarios, ...index.uploads].sort(sortSources);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
export async function getHarnessSource(sourceId: string): Promise<HarnessSourceRecord | undefined> {
|
|
644
|
-
const sources = await listHarnessSources();
|
|
645
|
-
return sources.find((source) => source.sourceId === sourceId);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
export async function readHarnessSourceBytes(sourceId: string): Promise<Uint8Array> {
|
|
649
|
-
const source = await getHarnessSource(sourceId);
|
|
650
|
-
|
|
651
|
-
if (!source?.available || !source.locator) {
|
|
652
|
-
throw new Error(`Harness source ${sourceId} is not available for session launch.`);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
const storageDir = await ensureStorageLayout();
|
|
656
|
-
const absolutePath =
|
|
657
|
-
source.locator.kind === "repo-file"
|
|
658
|
-
? source.locator.absolutePath
|
|
659
|
-
: path.join(storageDir, source.locator.relativePath);
|
|
660
|
-
|
|
661
|
-
return new Uint8Array(await readFile(absolutePath));
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
export async function createHarnessUploadSource(input: {
|
|
665
|
-
fileName: string;
|
|
666
|
-
mimeType?: string;
|
|
667
|
-
bytes: Uint8Array;
|
|
668
|
-
}): Promise<HarnessUploadSourceRecord> {
|
|
669
|
-
assertAcceptedDocxPayload(input.fileName, input.bytes.byteLength, input.mimeType);
|
|
670
|
-
|
|
671
|
-
const now = new Date().toISOString();
|
|
672
|
-
const sourceId = `upload:${randomUUID()}`;
|
|
673
|
-
const storageDir = await ensureStorageLayout();
|
|
674
|
-
const safeFileName = sanitizeFileName(input.fileName);
|
|
675
|
-
const storedRelativePath = path.join("uploads", `${sourceId.replace(":", "-")}-${safeFileName}`);
|
|
676
|
-
const storedAbsolutePath = path.join(storageDir, storedRelativePath);
|
|
677
|
-
|
|
678
|
-
await mkdir(path.dirname(storedAbsolutePath), { recursive: true });
|
|
679
|
-
await writeFile(storedAbsolutePath, Buffer.from(input.bytes));
|
|
680
|
-
|
|
681
|
-
const sourceRecord: HarnessUploadSourceRecord = {
|
|
682
|
-
sourceId,
|
|
683
|
-
sourceKind: "upload",
|
|
684
|
-
name: safeFileName,
|
|
685
|
-
sourceLabel: safeFileName,
|
|
686
|
-
fileName: safeFileName,
|
|
687
|
-
mimeType: input.mimeType || DOCX_MIME_TYPE,
|
|
688
|
-
byteLength: input.bytes.byteLength,
|
|
689
|
-
createdAt: now,
|
|
690
|
-
updatedAt: now,
|
|
691
|
-
available: true,
|
|
692
|
-
retentionClass: "operator-upload",
|
|
693
|
-
locator: {
|
|
694
|
-
kind: "storage-file",
|
|
695
|
-
relativePath: storedRelativePath,
|
|
696
|
-
},
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
const index = await readIndex();
|
|
700
|
-
index.uploads.unshift(sourceRecord);
|
|
701
|
-
const prunedIndex = await pruneArtifactsIfNeeded(index);
|
|
702
|
-
await writeIndex(prunedIndex);
|
|
703
|
-
|
|
704
|
-
return sourceRecord;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
export async function createHarnessSession(sourceId: string): Promise<HarnessSessionRecord> {
|
|
708
|
-
const source = await getHarnessSource(sourceId);
|
|
709
|
-
|
|
710
|
-
if (!source?.available) {
|
|
711
|
-
throw new HarnessStorageError(
|
|
712
|
-
"unknown_source",
|
|
713
|
-
404,
|
|
714
|
-
`Harness source ${sourceId} cannot be launched because its artifact is missing.`,
|
|
715
|
-
);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
const now = new Date().toISOString();
|
|
719
|
-
const sessionRecord: HarnessSessionRecord = {
|
|
720
|
-
sessionId: randomUUID(),
|
|
721
|
-
documentId: createDocumentId("harness-doc"),
|
|
722
|
-
sourceId: source.sourceId,
|
|
723
|
-
sourceKind: source.sourceKind,
|
|
724
|
-
sourceLabel: source.sourceLabel,
|
|
725
|
-
createdAt: now,
|
|
726
|
-
updatedAt: now,
|
|
727
|
-
};
|
|
728
|
-
|
|
729
|
-
const index = await readIndex();
|
|
730
|
-
index.sessions.unshift(sessionRecord);
|
|
731
|
-
const prunedIndex = await pruneArtifactsIfNeeded(index);
|
|
732
|
-
await writeIndex(prunedIndex);
|
|
733
|
-
|
|
734
|
-
return sessionRecord;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
export async function getHarnessSession(
|
|
738
|
-
sessionId: string,
|
|
739
|
-
): Promise<HarnessSessionRecord | undefined> {
|
|
740
|
-
const index = await readIndex();
|
|
741
|
-
return index.sessions.find((session) => session.sessionId === sessionId);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
export async function listHarnessSessions(): Promise<HarnessSessionRecord[]> {
|
|
745
|
-
const index = await readIndex();
|
|
746
|
-
return [...index.sessions].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
export async function saveHarnessExport(input: {
|
|
750
|
-
sessionId: string;
|
|
751
|
-
fileName: string;
|
|
752
|
-
mimeType: string;
|
|
753
|
-
bytes: Uint8Array;
|
|
754
|
-
reason?: string;
|
|
755
|
-
}): Promise<HarnessExportRecord> {
|
|
756
|
-
assertAcceptedDocxPayload(input.fileName, input.bytes.byteLength, input.mimeType);
|
|
757
|
-
|
|
758
|
-
const index = await readIndex();
|
|
759
|
-
const session = index.sessions.find((entry) => entry.sessionId === input.sessionId);
|
|
760
|
-
|
|
761
|
-
if (!session) {
|
|
762
|
-
throw new HarnessStorageError(
|
|
763
|
-
"unknown_session",
|
|
764
|
-
404,
|
|
765
|
-
`Harness session ${input.sessionId} does not exist.`,
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const now = new Date().toISOString();
|
|
770
|
-
const exportId = randomUUID();
|
|
771
|
-
const storageDir = await ensureStorageLayout();
|
|
772
|
-
const storedRelativePath = path.join(
|
|
773
|
-
"exports",
|
|
774
|
-
`${exportId}-${sanitizeFileName(input.fileName)}`,
|
|
775
|
-
);
|
|
776
|
-
const storedAbsolutePath = path.join(storageDir, storedRelativePath);
|
|
777
|
-
|
|
778
|
-
await mkdir(path.dirname(storedAbsolutePath), { recursive: true });
|
|
779
|
-
await writeFile(storedAbsolutePath, Buffer.from(input.bytes));
|
|
780
|
-
|
|
781
|
-
const record: HarnessExportRecord = {
|
|
782
|
-
exportId,
|
|
783
|
-
sessionId: input.sessionId,
|
|
784
|
-
fileName: sanitizeFileName(input.fileName),
|
|
785
|
-
mimeType: input.mimeType || DOCX_MIME_TYPE,
|
|
786
|
-
byteLength: input.bytes.byteLength,
|
|
787
|
-
storedRelativePath,
|
|
788
|
-
createdAt: now,
|
|
789
|
-
reason: input.reason,
|
|
790
|
-
};
|
|
791
|
-
|
|
792
|
-
index.exports.unshift(record);
|
|
793
|
-
session.updatedAt = now;
|
|
794
|
-
session.latestExportId = exportId;
|
|
795
|
-
|
|
796
|
-
const prunedIndex = await pruneArtifactsIfNeeded(index);
|
|
797
|
-
await writeIndex(prunedIndex);
|
|
798
|
-
|
|
799
|
-
return record;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
export async function listHarnessExportsForSession(
|
|
803
|
-
sessionId: string,
|
|
804
|
-
): Promise<HarnessExportRecord[]> {
|
|
805
|
-
const index = await readIndex();
|
|
806
|
-
return index.exports
|
|
807
|
-
.filter((record) => record.sessionId === sessionId)
|
|
808
|
-
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
export async function getHarnessExport(
|
|
812
|
-
exportId: string,
|
|
813
|
-
): Promise<HarnessExportRecord | undefined> {
|
|
814
|
-
const index = await readIndex();
|
|
815
|
-
return index.exports.find((record) => record.exportId === exportId);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
export async function readHarnessExportBytes(exportId: string): Promise<Uint8Array> {
|
|
819
|
-
const record = await getHarnessExport(exportId);
|
|
820
|
-
|
|
821
|
-
if (!record) {
|
|
822
|
-
throw new Error(`Harness export ${exportId} does not exist.`);
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
const storageDir = await ensureStorageLayout();
|
|
826
|
-
return new Uint8Array(await readFile(path.join(storageDir, record.storedRelativePath)));
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
export async function saveHarnessValidationRun(input: {
|
|
830
|
-
report: HarnessValidationReport;
|
|
831
|
-
fileName: string;
|
|
832
|
-
byteLength: number;
|
|
833
|
-
sessionId?: string;
|
|
834
|
-
sourceId?: string;
|
|
835
|
-
exportId?: string;
|
|
836
|
-
targetType: "source" | "export" | "direct-upload";
|
|
837
|
-
}): Promise<HarnessValidationRunRecord> {
|
|
838
|
-
const now = new Date().toISOString();
|
|
839
|
-
const runId = randomUUID();
|
|
840
|
-
const storageDir = await ensureStorageLayout();
|
|
841
|
-
const storedRelativePath = path.join("validation-runs", `${runId}.json`);
|
|
842
|
-
const storedAbsolutePath = path.join(storageDir, storedRelativePath);
|
|
843
|
-
|
|
844
|
-
await mkdir(path.dirname(storedAbsolutePath), { recursive: true });
|
|
845
|
-
await writeFile(storedAbsolutePath, JSON.stringify(input.report, null, 2));
|
|
846
|
-
|
|
847
|
-
const record: HarnessValidationRunRecord = {
|
|
848
|
-
runId,
|
|
849
|
-
sessionId: input.sessionId,
|
|
850
|
-
sourceId: input.sourceId,
|
|
851
|
-
exportId: input.exportId,
|
|
852
|
-
targetType: input.targetType,
|
|
853
|
-
fileName: sanitizeFileName(input.fileName),
|
|
854
|
-
byteLength: input.byteLength,
|
|
855
|
-
storedRelativePath,
|
|
856
|
-
createdAt: now,
|
|
857
|
-
service: typeof input.report.service === "string" ? input.report.service : "openxml-validator",
|
|
858
|
-
summary: summarizeValidationReport(input.report),
|
|
859
|
-
};
|
|
860
|
-
|
|
861
|
-
const index = await readIndex();
|
|
862
|
-
index.validationRuns.unshift(record);
|
|
863
|
-
|
|
864
|
-
if (input.sessionId) {
|
|
865
|
-
const session = index.sessions.find((entry) => entry.sessionId === input.sessionId);
|
|
866
|
-
|
|
867
|
-
if (session) {
|
|
868
|
-
session.updatedAt = now;
|
|
869
|
-
session.latestValidationRunId = runId;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
const prunedIndex = await pruneArtifactsIfNeeded(index);
|
|
874
|
-
await writeIndex(prunedIndex);
|
|
875
|
-
|
|
876
|
-
return record;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
export async function getHarnessValidationRun(
|
|
880
|
-
runId: string,
|
|
881
|
-
): Promise<HarnessValidationRunRecord | undefined> {
|
|
882
|
-
const index = await readIndex();
|
|
883
|
-
return index.validationRuns.find((record) => record.runId === runId);
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
export async function listHarnessValidationRuns(
|
|
887
|
-
options: { sessionId?: string; limit?: number } = {},
|
|
888
|
-
): Promise<HarnessValidationRunRecord[]> {
|
|
889
|
-
const index = await readIndex();
|
|
890
|
-
const filtered = options.sessionId
|
|
891
|
-
? index.validationRuns.filter((record) => record.sessionId === options.sessionId)
|
|
892
|
-
: index.validationRuns;
|
|
893
|
-
|
|
894
|
-
return filtered
|
|
895
|
-
.slice()
|
|
896
|
-
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
897
|
-
.slice(0, options.limit ?? filtered.length);
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
export async function readHarnessValidationReport(
|
|
901
|
-
runId: string,
|
|
902
|
-
): Promise<HarnessValidationReport> {
|
|
903
|
-
const record = await getHarnessValidationRun(runId);
|
|
904
|
-
|
|
905
|
-
if (!record) {
|
|
906
|
-
throw new Error(`Harness validation run ${runId} does not exist.`);
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
const storageDir = await ensureStorageLayout();
|
|
910
|
-
const raw = await readFile(path.join(storageDir, record.storedRelativePath), "utf8");
|
|
911
|
-
return JSON.parse(raw) as HarnessValidationReport;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
export async function getHarnessHealthSnapshot(): Promise<HarnessHealthSnapshot> {
|
|
915
|
-
const [index, fixtures, scenarios] = await Promise.all([
|
|
916
|
-
readIndex(),
|
|
917
|
-
listFixtureSources(),
|
|
918
|
-
listScenarioSources(),
|
|
919
|
-
]);
|
|
920
|
-
|
|
921
|
-
const assumptions = getHarnessStorageAssumptions();
|
|
922
|
-
|
|
923
|
-
return {
|
|
924
|
-
storageDir: assumptions.activeStorageDir,
|
|
925
|
-
storage: {
|
|
926
|
-
configuredStorageDir: assumptions.configuredStorageDir,
|
|
927
|
-
mode: assumptions.storageMode,
|
|
928
|
-
persistence: "file-backed-manifest-index",
|
|
929
|
-
indexVersion: HARNESS_STORAGE_INDEX_VERSION,
|
|
930
|
-
},
|
|
931
|
-
assumptions,
|
|
932
|
-
retentionPolicy: {
|
|
933
|
-
maxUploadBytes: HARNESS_MAX_UPLOAD_BYTES,
|
|
934
|
-
uploadArtifactLimit: HARNESS_UPLOAD_RETENTION_LIMIT,
|
|
935
|
-
sessionLimit: HARNESS_SESSION_RETENTION_LIMIT,
|
|
936
|
-
exportArtifactLimit: HARNESS_EXPORT_RETENTION_LIMIT,
|
|
937
|
-
validationRunLimit: HARNESS_VALIDATION_RETENTION_LIMIT,
|
|
938
|
-
mode: "volume-backed-manual-review",
|
|
939
|
-
},
|
|
940
|
-
counts: {
|
|
941
|
-
fixtures: fixtures.length,
|
|
942
|
-
scenarios: scenarios.length,
|
|
943
|
-
uploads: index.uploads.length,
|
|
944
|
-
sessions: index.sessions.length,
|
|
945
|
-
exports: index.exports.length,
|
|
946
|
-
validationRuns: index.validationRuns.length,
|
|
947
|
-
},
|
|
948
|
-
bytes: {
|
|
949
|
-
uploads: index.uploads.reduce((sum, record) => sum + (record.byteLength ?? 0), 0),
|
|
950
|
-
exports: index.exports.reduce((sum, record) => sum + record.byteLength, 0),
|
|
951
|
-
},
|
|
952
|
-
};
|
|
953
|
-
}
|