ai-delivery-workflow 0.2.2 → 0.4.0
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 +45 -14
- package/bin/ai-delivery.mjs +181 -6
- package/bin/benchmark-autonomy.mjs +217 -0
- package/bin/release-check.mjs +382 -0
- package/docs/CLI-PARAMETER-REFERENCE.zh-CN.md +346 -0
- package/docs/CODEGRAPH-INTEGRATION.zh-CN.md +89 -0
- package/docs/DUAL-REPOSITORY-WORKSPACE.zh-CN.md +2 -2
- package/docs/EVOLUTION-MAINTENANCE.zh-CN.md +7 -7
- package/docs/EVOLUTION-USER-GUIDE.zh-CN.md +2 -2
- package/docs/FILE-REFERENCE.zh-CN.md +120 -39
- package/docs/MAINTENANCE-VERIFICATION.zh-CN.md +51 -0
- package/docs/MOCK-SYSTEM.zh-CN.md +141 -0
- package/docs/NPM-RELEASE-MANAGEMENT.zh-CN.md +106 -0
- package/docs/PROJECT-MANUAL.zh-CN.md +154 -95
- package/docs/PROTOTYPE-MIGRATION.zh-CN.md +164 -0
- package/docs/STATE-CLI-MAINTENANCE.zh-CN.md +35 -17
- package/docs/STATE-CLI-USER-GUIDE.zh-CN.md +115 -39
- package/docs/VIEWER-MAINTENANCE.zh-CN.md +101 -324
- package/docs/VIEWER-USER-GUIDE.zh-CN.md +68 -190
- package/docs/adr/0001-prototype-first-ui-governance.md +3 -0
- package/docs/adr/0002-microcks-as-default-mock-system.md +3 -0
- package/docs/adr/0003-workflow-manager-governed-control-surface.md +46 -0
- package/docs/adr/0004-operational-carbon-visual-standard.md +9 -0
- package/docs/adr/0005-prototype-management-as-routed-pages.md +7 -0
- package/docs/agents/domain.md +12 -0
- package/docs/agents/issue-tracker.md +27 -0
- package/docs/agents/triage-labels.md +11 -0
- package/docs/agents/workflow-manager-acceptance-matrix.md +93 -0
- package/docs/agents/workflow-manager-agent-interface.md +145 -0
- package/docs/agents/workflow-manager-current-state.md +205 -0
- package/lib/artifact-files.mjs +47 -0
- package/lib/codegraph.mjs +405 -0
- package/lib/delivery-state.mjs +3079 -198
- package/lib/frontend-guard.mjs +288 -0
- package/lib/fs-utils.mjs +53 -0
- package/lib/history-anchor.mjs +98 -0
- package/lib/mock-scenario-pack.mjs +687 -0
- package/lib/mock-system.mjs +897 -0
- package/lib/npm-package-result.mjs +11 -0
- package/lib/png-evidence.mjs +149 -0
- package/lib/project-bootstrap.mjs +96 -55
- package/lib/project-installer.mjs +1484 -164
- package/lib/project-migration.mjs +1193 -0
- package/lib/prototype-migration.mjs +713 -0
- package/lib/prototype-page-review.mjs +384 -0
- package/lib/prototype-review-target.mjs +334 -0
- package/lib/prototype-revision.mjs +812 -0
- package/lib/stable-value.mjs +15 -0
- package/lib/toml-hooks.mjs +69 -0
- package/lib/ui-acceptance.mjs +565 -0
- package/lib/visual-standard.mjs +232 -0
- package/lib/workflow-manager-contracts.mjs +367 -0
- package/lib/workflow-manager-facts.mjs +778 -0
- package/lib/workflow-manager-governance.mjs +514 -0
- package/lib/workflow-manager-mock.mjs +183 -0
- package/lib/workflow-manager-prototypes.mjs +1847 -0
- package/lib/workflow-manager-public/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/lib/workflow-manager-public/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/lib/workflow-manager-public/assets/index-Chlg6r8f.css +1 -0
- package/lib/workflow-manager-public/assets/index-E-hGB5ZD.js +13 -0
- package/lib/workflow-manager-public/fonts/OFL-1.1.txt +90 -0
- package/lib/workflow-manager-public/index.html +14 -0
- package/lib/workflow-manager-systems.mjs +519 -0
- package/lib/workflow-manager-transaction.mjs +397 -0
- package/lib/workflow-manager.mjs +745 -0
- package/package.json +8 -2
- package/skills/ai-delivery-assemble-release/agents/openai.yaml +3 -3
- package/skills/ai-delivery-assemble-release/assets/release-material-index.csv +1 -1
- package/skills/ai-delivery-assemble-release/assets/version-inclusion.csv +1 -1
- package/skills/ai-delivery-bootstrap/SKILL.md +4 -4
- package/skills/ai-delivery-bootstrap/agents/openai.yaml +3 -3
- package/skills/ai-delivery-bootstrap/references/bootstrap-contract.md +4 -2
- package/skills/ai-delivery-checkpoint-task/SKILL.md +17 -3
- package/skills/ai-delivery-checkpoint-task/agents/openai.yaml +3 -3
- package/skills/ai-delivery-checkpoint-task/assets/runtime-template/task.json +7 -1
- package/skills/ai-delivery-checkpoint-task/references/checkpoint-contract.md +22 -8
- package/skills/ai-delivery-checkpoint-task/scripts/hook-event.mjs +91 -0
- package/skills/ai-delivery-checkpoint-task/scripts/task-state.mjs +1086 -51
- package/skills/ai-delivery-close-version/SKILL.md +9 -8
- package/skills/ai-delivery-close-version/agents/openai.yaml +3 -3
- package/skills/ai-delivery-close-version/assets/version-lineage.yaml +1 -1
- package/skills/ai-delivery-close-version/references/version-closeout-contract.md +2 -2
- package/skills/ai-delivery-define-product/SKILL.md +8 -4
- package/skills/ai-delivery-define-product/agents/openai.yaml +4 -4
- package/skills/ai-delivery-define-product/references/product-contract.md +2 -2
- package/skills/ai-delivery-deploy-production/agents/openai.yaml +3 -3
- package/skills/ai-delivery-design-architecture/SKILL.md +9 -5
- package/skills/ai-delivery-design-architecture/assets/architecture-template/prototype-architecture-validation.yaml +16 -16
- package/skills/ai-delivery-design-architecture/references/architecture-contract.md +1 -1
- package/skills/ai-delivery-design-experience/SKILL.md +36 -30
- package/skills/ai-delivery-design-experience/agents/openai.yaml +4 -4
- package/skills/ai-delivery-design-experience/assets/experience-template/LEGACY-COMPATIBILITY.md +16 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/design-reference-selection.yaml +32 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/experience-baseline.yaml +2 -2
- package/skills/ai-delivery-design-experience/assets/experience-template/formal-ui-confirmation.yaml +16 -3
- package/skills/ai-delivery-design-experience/assets/experience-template/frontend-change-scope.yaml +30 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/interaction-contract.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/mock-admin-export.yaml +8 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/mock-scenario-mappings.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/mock-scenario-pack-request.yaml +24 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/product-prototype-reconciliation.yaml +2 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-adjustment-log.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-component-bindings.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-component-candidates.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-components.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-confirmation.yaml +18 -18
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-data-contracts.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-impact-assessment.yaml +5 -5
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-implementation-bindings.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-manifest.yaml +2 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-pages.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-product-coverage.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-comments.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-decision.yaml +13 -12
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-scenes.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-sessions.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-unit-pages.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-units.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-revision.yaml +88 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-routes.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-set.yaml +2 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-source-inspection-snapshots.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-terminals.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-candidates.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-selection.yaml +2 -3
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-traceability.csv +1 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-trigger-conditions.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype.yaml +6 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/ui-framework-selection.yaml +17 -1
- package/skills/ai-delivery-design-experience/assets/experience-template/ui-handoff.yaml +6 -5
- package/skills/ai-delivery-design-experience/assets/experience-template/ui-mapping.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/visual-direction.yaml +4 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/visual-reference-manifest.yaml +23 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/visual-reference.html +163 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/visual-system-confirmation.yaml +11 -0
- package/skills/ai-delivery-design-experience/references/experience-contract.md +26 -27
- package/skills/ai-delivery-design-experience/references/prototype-management-contract.md +42 -21
- package/skills/ai-delivery-design-tests/SKILL.md +13 -7
- package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-scope.csv +1 -1
- package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-validation.yaml +17 -16
- package/skills/ai-delivery-design-tests/references/test-design-contract.md +7 -5
- package/skills/ai-delivery-develop-iteration/SKILL.md +18 -11
- package/skills/ai-delivery-develop-iteration/agents/openai.yaml +3 -3
- package/skills/ai-delivery-develop-iteration/assets/development-template/candidate-manifest.yaml +36 -0
- package/skills/ai-delivery-develop-iteration/assets/development-template/task-prototype-traceability.csv +1 -1
- package/skills/ai-delivery-develop-iteration/references/development-contract.md +5 -3
- package/skills/ai-delivery-evolve-workflow/agents/openai.yaml +3 -3
- package/skills/ai-delivery-execute-test/SKILL.md +6 -2
- package/skills/ai-delivery-execute-test/agents/openai.yaml +4 -4
- package/skills/ai-delivery-execute-work-package/SKILL.md +10 -3
- package/skills/ai-delivery-execute-work-package/agents/openai.yaml +4 -4
- package/skills/ai-delivery-execute-work-package/references/work-package-contract.md +1 -1
- package/skills/ai-delivery-manage-git/agents/openai.yaml +3 -3
- package/skills/ai-delivery-manage-standards/agents/openai.yaml +4 -4
- package/skills/ai-delivery-orchestrate/SKILL.md +47 -29
- package/skills/ai-delivery-orchestrate/agents/openai.yaml +3 -3
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/control/migrations/.gitkeep +0 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/control/repairs/.gitkeep +0 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/project.yaml +1 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/workflow-state.yaml +2 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/frontend-guard/frontend-guard.mjs +50 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/hooks/hook-event.mjs +91 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/manager.mjs +64 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/start.cmd +4 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/manager/start.ps1 +2 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/{viewer → manager}/start.sh +1 -1
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/mock/mock.mjs +50 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/ui-acceptance/ui-acceptance.mjs +37 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md +32 -6
- package/skills/ai-delivery-orchestrate/assets/project-template/gitignore.template +1 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/CONTRIBUTING.md +25 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/LICENSE +21 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/README.md +250 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/UPSTREAM.yaml +19 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airbnb/DESIGN.md +545 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airbnb/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airtable/DESIGN.md +554 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/airtable/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/apple/DESIGN.md +562 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/apple/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/binance/DESIGN.md +634 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/binance/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw/DESIGN.md +544 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw-m/DESIGN.md +503 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bmw-m/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bugatti/DESIGN.md +454 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/bugatti/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cal/DESIGN.md +542 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cal/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/claude/DESIGN.md +589 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/claude/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clay/DESIGN.md +541 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clay/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clickhouse/DESIGN.md +544 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/clickhouse/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cohere/DESIGN.md +451 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cohere/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/coinbase/DESIGN.md +570 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/coinbase/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/composio/DESIGN.md +506 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/composio/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cursor/DESIGN.md +537 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/cursor/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/dell-1996/DESIGN.md +632 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/dell-1996/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/elevenlabs/DESIGN.md +504 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/elevenlabs/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/expo/DESIGN.md +526 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/expo/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ferrari/DESIGN.md +531 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ferrari/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/figma/DESIGN.md +578 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/figma/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/framer/DESIGN.md +544 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/framer/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hashicorp/DESIGN.md +575 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hashicorp/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hp/DESIGN.md +670 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/hp/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ibm/DESIGN.md +550 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ibm/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/intercom/DESIGN.md +546 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/intercom/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/kraken/DESIGN.md +125 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/kraken/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lamborghini/DESIGN.md +288 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lamborghini/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/linear.app/DESIGN.md +548 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/linear.app/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lovable/DESIGN.md +298 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/lovable/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mastercard/DESIGN.md +365 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mastercard/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/meta/DESIGN.md +683 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/meta/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/minimax/DESIGN.md +746 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/minimax/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mintlify/DESIGN.md +852 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mintlify/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/miro/DESIGN.md +825 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/miro/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mistral.ai/DESIGN.md +773 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mistral.ai/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mongodb/DESIGN.md +767 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/mongodb/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nike/DESIGN.md +575 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nike/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nintendo-2001/DESIGN.md +649 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nintendo-2001/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/notion/DESIGN.md +821 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/notion/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nvidia/DESIGN.md +640 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/nvidia/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ollama/DESIGN.md +539 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/ollama/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/opencode.ai/DESIGN.md +521 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/opencode.ai/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/pinterest/DESIGN.md +597 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/pinterest/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/playstation/DESIGN.md +661 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/playstation/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/posthog/DESIGN.md +690 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/posthog/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/raycast/DESIGN.md +669 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/raycast/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/renault/DESIGN.md +589 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/renault/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/replicate/DESIGN.md +616 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/replicate/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/resend/DESIGN.md +585 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/resend/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/revolut/DESIGN.md +636 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/revolut/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/runwayml/DESIGN.md +244 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/runwayml/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sanity/DESIGN.md +357 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sanity/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sentry/DESIGN.md +551 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/sentry/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/shopify/DESIGN.md +516 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/shopify/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/slack/DESIGN.md +482 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spacex/DESIGN.md +363 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spacex/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spotify/DESIGN.md +246 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/spotify/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/starbucks/DESIGN.md +580 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/starbucks/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/stripe/DESIGN.md +487 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/stripe/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/supabase/DESIGN.md +462 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/supabase/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/superhuman/DESIGN.md +448 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/superhuman/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/tesla/DESIGN.md +286 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/tesla/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/theverge/DESIGN.md +339 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/theverge/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/together.ai/DESIGN.md +633 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/together.ai/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/uber/DESIGN.md +636 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/uber/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vercel/DESIGN.md +736 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vercel/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vodafone/DESIGN.md +538 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/vodafone/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/voltagent/DESIGN.md +521 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/voltagent/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/warp/DESIGN.md +526 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/warp/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/webflow/DESIGN.md +588 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/webflow/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wired/DESIGN.md +497 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wired/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wise/DESIGN.md +544 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/wise/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/x.ai/DESIGN.md +465 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/x.ai/README.md +5 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/zapier/DESIGN.md +537 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/ref/ui/design-md/zapier/README.md +5 -0
- package/skills/ai-delivery-orchestrate/references/formal-state-contract.md +65 -20
- package/skills/ai-delivery-orchestrate/references/workflow-model.md +7 -5
- package/skills/ai-delivery-orchestrate-release/SKILL.md +2 -2
- package/skills/ai-delivery-orchestrate-release/agents/openai.yaml +4 -4
- package/skills/ai-delivery-orchestrate-release/references/release-workflow-contract.md +1 -1
- package/skills/ai-delivery-plan-iteration/SKILL.md +11 -4
- package/skills/ai-delivery-plan-iteration/agents/openai.yaml +4 -4
- package/skills/ai-delivery-plan-iteration/assets/planning-template/scope-manifest.yaml +17 -0
- package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-prototype-traceability.csv +1 -1
- package/skills/ai-delivery-plan-iteration/references/planning-contract.md +8 -2
- package/skills/ai-delivery-prepare-platform/SKILL.md +5 -1
- package/skills/ai-delivery-prepare-platform/agents/openai.yaml +4 -4
- package/skills/ai-delivery-prepare-release/agents/openai.yaml +4 -4
- package/skills/ai-delivery-review-change/SKILL.md +14 -4
- package/skills/ai-delivery-review-change/agents/openai.yaml +4 -4
- package/skills/ai-delivery-review-change/references/review-contract.md +6 -0
- package/skills/ai-delivery-triage-failure/SKILL.md +5 -1
- package/skills/ai-delivery-triage-failure/agents/openai.yaml +4 -4
- package/skills/ai-delivery-validate-artifacts/SKILL.md +6 -3
- package/skills/ai-delivery-validate-artifacts/agents/openai.yaml +4 -4
- package/skills/ai-delivery-validate-artifacts/references/artifact-contract.md +13 -13
- package/skills/ai-delivery-verify-candidate/SKILL.md +13 -6
- package/skills/ai-delivery-verify-candidate/agents/openai.yaml +3 -3
- package/skills/ai-delivery-verify-candidate/assets/verification-template/ui-acceptance-request.yaml +19 -0
- package/skills/ai-delivery-verify-candidate/assets/verification-template/ui-acceptance-results.yaml +53 -0
- package/skills/ai-delivery-verify-candidate/references/candidate-verification-contract.md +10 -2
- package/skills/ai-delivery-verify-production/agents/openai.yaml +3 -3
- package/skills/ai-delivery-design-experience/assets/experience-template/low-fidelity-confirmation.yaml +0 -32
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/app.js +0 -546
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/index.html +0 -199
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/styles.css +0 -529
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/server.mjs +0 -780
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.cmd +0 -4
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.ps1 +0 -2
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
readYamlMapping as readYaml,
|
|
6
|
+
requireSha256 as requireChecksum,
|
|
7
|
+
resolveContainedPath as containedPath,
|
|
8
|
+
sha256File,
|
|
9
|
+
} from "./artifact-files.mjs";
|
|
10
|
+
|
|
11
|
+
export const PROTOTYPE_REVISION_CATALOG_SCHEMA_VERSION = "prototype-revision-catalog.v2";
|
|
12
|
+
|
|
13
|
+
export const COMPONENT_CANDIDATE_LIFECYCLE_STATES = Object.freeze([
|
|
14
|
+
"candidate",
|
|
15
|
+
"under-review",
|
|
16
|
+
"registered",
|
|
17
|
+
"modified",
|
|
18
|
+
"deprecated",
|
|
19
|
+
"replaced",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export const COMPONENT_CANDIDATE_LIFECYCLE_TRANSITIONS = Object.freeze({
|
|
23
|
+
candidate: Object.freeze(["under-review"]),
|
|
24
|
+
"under-review": Object.freeze(["registered"]),
|
|
25
|
+
registered: Object.freeze(["modified", "deprecated", "replaced"]),
|
|
26
|
+
modified: Object.freeze([]),
|
|
27
|
+
deprecated: Object.freeze([]),
|
|
28
|
+
replaced: Object.freeze([]),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const COMPONENT_CANDIDATE_BINDING_STATUS = Object.freeze({
|
|
32
|
+
candidate: "pending-review",
|
|
33
|
+
"under-review": "pending-review",
|
|
34
|
+
registered: "active",
|
|
35
|
+
modified: "pending-review",
|
|
36
|
+
deprecated: "stale",
|
|
37
|
+
replaced: "stale",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const prototypeRevisionCatalogContracts = Object.freeze({
|
|
41
|
+
terminals: "terminal_id,terminal_name,source_entrypoint,build_command,review_base_path,production_base_path,compatibility_group,lifecycle_status",
|
|
42
|
+
pages: "page_id,terminal_id,page_name,display_label,parent_page_id,lifecycle_status",
|
|
43
|
+
review_scenes: "review_scene_id,page_id,terminal_id,scene_name,scene_type,trigger_condition_id,lifecycle_status",
|
|
44
|
+
trigger_conditions: "trigger_condition_id,trigger_kind,condition,fixture_ref,notes",
|
|
45
|
+
routes: "route_id,terminal_id,route_kind,path,page_id,review_scene_id,lifecycle_status",
|
|
46
|
+
ui_mapping: "ui_mapping_id,target_kind,target_id,terminal_id,page_id,review_scene_id,prototype_route_id,production_route_id,frontend_source_ref,acceptance_id,lifecycle_status",
|
|
47
|
+
review_units: "review_unit_id,unit_kind,target_id,source_ref,checksum,lifecycle_status",
|
|
48
|
+
review_unit_pages: "review_unit_id,page_id",
|
|
49
|
+
components: "component_id,name,layer,base_component,variants,states,owner,lifecycle_status",
|
|
50
|
+
data_contracts: "data_contract_id,entity,source,stable_id,fields,context,permission,sort,default_value,url_param,states,version,supersedes,compatibility,lifecycle_status",
|
|
51
|
+
component_candidates: "component_id,name,layer,base_component,variants,states,owner,lifecycle,source_page_id,source_scene_ids,proposed_implementation_entry,data_contract_id,compatibility,migration_note,review_comment",
|
|
52
|
+
component_bindings: "page_id,scene_ids,terminal_id,component_id,variant,data_contract_id,context,status",
|
|
53
|
+
implementation_bindings: "component_id,implementation_entry,data_contract_id",
|
|
54
|
+
source_inspection_snapshots: "source_snapshot_id,asset_kind,page_id,review_scene_id,terminal_id,source_ref,checksum,language,scene_branch,lifecycle_status",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
function listFiles(directory) {
|
|
58
|
+
const files = [];
|
|
59
|
+
const visit = (current) => {
|
|
60
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
61
|
+
const absolute = path.join(current, entry.name);
|
|
62
|
+
if (entry.isDirectory()) visit(absolute);
|
|
63
|
+
else if (entry.isFile()) files.push(absolute);
|
|
64
|
+
else if (entry.isSymbolicLink()) {
|
|
65
|
+
throw new Error(`Prototype revision trees must not contain symbolic links: ${absolute}.`);
|
|
66
|
+
} else throw new Error(`Prototype revision trees contain an unsupported filesystem entry: ${absolute}.`);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
visit(directory);
|
|
70
|
+
return files.sort((left, right) => left.localeCompare(right));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function prototypeRevisionTreeDigest(directory) {
|
|
74
|
+
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
|
|
75
|
+
throw new Error(`Checksummed directory is missing: ${directory}`);
|
|
76
|
+
}
|
|
77
|
+
const digest = crypto.createHash("sha256");
|
|
78
|
+
for (const file of listFiles(directory)) {
|
|
79
|
+
digest.update(path.relative(directory, file).replaceAll("\\", "/"));
|
|
80
|
+
digest.update("\0");
|
|
81
|
+
digest.update(fs.readFileSync(file));
|
|
82
|
+
digest.update("\0");
|
|
83
|
+
}
|
|
84
|
+
return digest.digest("hex");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function parsePrototypeCatalogCsv(text, label) {
|
|
88
|
+
const rows = [];
|
|
89
|
+
let row = [];
|
|
90
|
+
let field = "";
|
|
91
|
+
let state = "unquoted";
|
|
92
|
+
const pushField = () => {
|
|
93
|
+
row.push(field);
|
|
94
|
+
field = "";
|
|
95
|
+
state = "unquoted";
|
|
96
|
+
};
|
|
97
|
+
const pushRow = () => {
|
|
98
|
+
pushField();
|
|
99
|
+
rows.push(row);
|
|
100
|
+
row = [];
|
|
101
|
+
};
|
|
102
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
103
|
+
const character = text[index];
|
|
104
|
+
if (state === "quoted") {
|
|
105
|
+
if (character === '"' && text[index + 1] === '"') {
|
|
106
|
+
field += '"';
|
|
107
|
+
index += 1;
|
|
108
|
+
} else if (character === '"') state = "after-quote";
|
|
109
|
+
else field += character;
|
|
110
|
+
} else if (state === "after-quote") {
|
|
111
|
+
if (character === ",") pushField();
|
|
112
|
+
else if (character === "\n") pushRow();
|
|
113
|
+
else if (character === "\r" && text[index + 1] === "\n") {
|
|
114
|
+
index += 1;
|
|
115
|
+
pushRow();
|
|
116
|
+
} else throw new Error(`${label} contains an invalid character after closing quote.`);
|
|
117
|
+
} else if (character === '"') {
|
|
118
|
+
if (field.length !== 0) throw new Error(`${label} contains a quote inside an unquoted field.`);
|
|
119
|
+
state = "quoted";
|
|
120
|
+
}
|
|
121
|
+
else if (character === ",") {
|
|
122
|
+
pushField();
|
|
123
|
+
} else if (character === "\n") pushRow();
|
|
124
|
+
else if (character === "\r" && text[index + 1] === "\n") {
|
|
125
|
+
index += 1;
|
|
126
|
+
pushRow();
|
|
127
|
+
} else field += character;
|
|
128
|
+
}
|
|
129
|
+
if (state === "quoted") throw new Error(`${label} contains an unterminated quoted field.`);
|
|
130
|
+
if (field.length || row.length) {
|
|
131
|
+
pushRow();
|
|
132
|
+
}
|
|
133
|
+
if (!rows.length) throw new Error(`${label} is empty.`);
|
|
134
|
+
const headers = rows[0];
|
|
135
|
+
if (headers.some((header) => !header) || new Set(headers).size !== headers.length) {
|
|
136
|
+
throw new Error(`${label} has empty or duplicate column names.`);
|
|
137
|
+
}
|
|
138
|
+
const values = [];
|
|
139
|
+
for (let index = 1; index < rows.length; index += 1) {
|
|
140
|
+
if (rows[index].length === 1 && rows[index][0] === "") continue;
|
|
141
|
+
if (rows[index].length !== headers.length) {
|
|
142
|
+
throw new Error(`${label} row ${index + 1} has ${rows[index].length} fields; expected ${headers.length}.`);
|
|
143
|
+
}
|
|
144
|
+
values.push(Object.fromEntries(headers.map((header, column) => [header, rows[index][column]])));
|
|
145
|
+
}
|
|
146
|
+
return { header: headers.join(","), rows: values };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function serializePrototypeCatalogCsv(headers, rows) {
|
|
150
|
+
if (!Array.isArray(headers) || !headers.length
|
|
151
|
+
|| headers.some((header) => typeof header !== "string" || !header)) {
|
|
152
|
+
throw new Error("Prototype catalog CSV headers must be a non-empty string array.");
|
|
153
|
+
}
|
|
154
|
+
if (!Array.isArray(rows)) throw new Error("Prototype catalog CSV rows must be an array.");
|
|
155
|
+
const escape = (value) => {
|
|
156
|
+
const text = value === undefined || value === null ? "" : String(value);
|
|
157
|
+
return /[",\r\n]/.test(text) ? `"${text.replaceAll('"', '""')}"` : text;
|
|
158
|
+
};
|
|
159
|
+
return `${[
|
|
160
|
+
headers.map(escape).join(","),
|
|
161
|
+
...rows.map((row) => headers.map((header) => escape(row?.[header])).join(",")),
|
|
162
|
+
].join("\n")}\n`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function indexUnique(rows, field, label) {
|
|
166
|
+
const result = new Map();
|
|
167
|
+
for (const row of rows) {
|
|
168
|
+
const identity = String(row[field] || "").trim();
|
|
169
|
+
if (!identity) throw new Error(`${label} has an empty ${field}.`);
|
|
170
|
+
if (result.has(identity)) throw new Error(`Duplicate ${field} in ${label}: ${identity}.`);
|
|
171
|
+
result.set(identity, row);
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function requireCatalogFields(rows, identityField, label, fields) {
|
|
177
|
+
for (const row of rows) {
|
|
178
|
+
const identity = String(row[identityField] || "").trim() || "<unknown>";
|
|
179
|
+
for (const field of fields) {
|
|
180
|
+
if (!String(row[field] || "").trim()) {
|
|
181
|
+
throw new Error(`${label} ${identity} is missing ${field}.`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function active(rows) {
|
|
188
|
+
return rows.filter((row) => row.lifecycle_status === "active");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function validateLifecycle(collection, identityField, label) {
|
|
192
|
+
for (const row of collection.values()) {
|
|
193
|
+
if (!new Set(["active", "retired"]).has(row.lifecycle_status)) {
|
|
194
|
+
throw new Error(`${label} ${row[identityField]} has invalid lifecycle_status ${row.lifecycle_status || "<empty>"}.`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function requireReference(collection, identity, label) {
|
|
200
|
+
if (!collection.has(identity)) throw new Error(`${label} references unknown identity: ${identity}.`);
|
|
201
|
+
return collection.get(identity);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function requireActiveReference(collection, identity, label, type) {
|
|
205
|
+
const referenced = requireReference(collection, identity, label);
|
|
206
|
+
if (referenced.lifecycle_status !== "active") {
|
|
207
|
+
throw new Error(`${label} is active but references retired ${type}: ${identity}.`);
|
|
208
|
+
}
|
|
209
|
+
return referenced;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function requireTypedReference(collection, identity, label, type) {
|
|
213
|
+
const referenced = collection.get(identity);
|
|
214
|
+
if (!referenced) throw new Error(`${label} references unknown ${type}: ${identity || "<empty>"}.`);
|
|
215
|
+
return referenced;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function pipeReferences(value, label) {
|
|
219
|
+
const source = String(value || "").trim();
|
|
220
|
+
if (!source) return [];
|
|
221
|
+
const identities = source.split("|").map((identity) => identity.trim());
|
|
222
|
+
if (identities.some((identity) => !identity)) throw new Error(`${label} has an empty pipe-delimited reference.`);
|
|
223
|
+
if (new Set(identities).size !== identities.length) throw new Error(`${label} has duplicate pipe-delimited references.`);
|
|
224
|
+
return identities;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function validateCatalogs(catalogRows, sourceRoot) {
|
|
228
|
+
const terminals = indexUnique(catalogRows.terminals, "terminal_id", "terminal catalog");
|
|
229
|
+
const pages = indexUnique(catalogRows.pages, "page_id", "page catalog");
|
|
230
|
+
const scenes = indexUnique(catalogRows.review_scenes, "review_scene_id", "Review Scene catalog");
|
|
231
|
+
const triggers = indexUnique(catalogRows.trigger_conditions, "trigger_condition_id", "trigger-condition catalog");
|
|
232
|
+
const routes = indexUnique(catalogRows.routes, "route_id", "route catalog");
|
|
233
|
+
const mappings = indexUnique(catalogRows.ui_mapping, "ui_mapping_id", "UI Mapping catalog");
|
|
234
|
+
const reviewUnits = indexUnique(catalogRows.review_units, "review_unit_id", "Review Unit catalog");
|
|
235
|
+
const components = indexUnique(
|
|
236
|
+
catalogRows.components,
|
|
237
|
+
"component_id",
|
|
238
|
+
"Component Registration catalog",
|
|
239
|
+
);
|
|
240
|
+
const dataContracts = indexUnique(
|
|
241
|
+
catalogRows.data_contracts,
|
|
242
|
+
"data_contract_id",
|
|
243
|
+
"Data Contract catalog",
|
|
244
|
+
);
|
|
245
|
+
const implementationBindings = indexUnique(
|
|
246
|
+
catalogRows.implementation_bindings,
|
|
247
|
+
"component_id",
|
|
248
|
+
"Implementation Binding catalog",
|
|
249
|
+
);
|
|
250
|
+
const componentCandidates = indexUnique(
|
|
251
|
+
catalogRows.component_candidates,
|
|
252
|
+
"component_id",
|
|
253
|
+
"Component Candidate catalog",
|
|
254
|
+
);
|
|
255
|
+
const sourceInspectionSnapshots = indexUnique(
|
|
256
|
+
catalogRows.source_inspection_snapshots,
|
|
257
|
+
"source_snapshot_id",
|
|
258
|
+
"Source Inspection Snapshot catalog",
|
|
259
|
+
);
|
|
260
|
+
requireCatalogFields(catalogRows.components, "component_id", "Component Registration", [
|
|
261
|
+
"component_id", "name", "layer", "base_component", "variants", "states", "owner", "lifecycle_status",
|
|
262
|
+
]);
|
|
263
|
+
requireCatalogFields(catalogRows.data_contracts, "data_contract_id", "Data Contract", [
|
|
264
|
+
"data_contract_id", "entity", "source", "stable_id", "fields", "context", "permission", "sort",
|
|
265
|
+
"default_value", "url_param", "states", "version", "compatibility", "lifecycle_status",
|
|
266
|
+
]);
|
|
267
|
+
requireCatalogFields(catalogRows.component_candidates, "component_id", "Component Candidate", [
|
|
268
|
+
"component_id", "name", "layer", "base_component", "variants", "states", "owner", "lifecycle",
|
|
269
|
+
"source_page_id", "source_scene_ids", "proposed_implementation_entry", "data_contract_id", "compatibility",
|
|
270
|
+
"migration_note", "review_comment",
|
|
271
|
+
]);
|
|
272
|
+
requireCatalogFields(catalogRows.component_bindings, "page_id", "Component Binding", [
|
|
273
|
+
"page_id", "scene_ids", "terminal_id", "component_id", "variant", "data_contract_id", "context", "status",
|
|
274
|
+
]);
|
|
275
|
+
requireCatalogFields(catalogRows.implementation_bindings, "component_id", "Implementation Binding", [
|
|
276
|
+
"component_id", "implementation_entry", "data_contract_id",
|
|
277
|
+
]);
|
|
278
|
+
requireCatalogFields(catalogRows.source_inspection_snapshots, "source_snapshot_id", "Source Inspection Snapshot", [
|
|
279
|
+
"source_snapshot_id", "asset_kind", "page_id", "terminal_id", "source_ref", "checksum", "language",
|
|
280
|
+
"scene_branch", "lifecycle_status",
|
|
281
|
+
]);
|
|
282
|
+
const reviewUnitPages = new Map();
|
|
283
|
+
const reviewUnitPagePairs = new Set();
|
|
284
|
+
for (const relation of catalogRows.review_unit_pages) {
|
|
285
|
+
const unit = requireReference(
|
|
286
|
+
reviewUnits,
|
|
287
|
+
relation.review_unit_id,
|
|
288
|
+
"Review Unit Page relation",
|
|
289
|
+
);
|
|
290
|
+
const page = requireReference(
|
|
291
|
+
pages,
|
|
292
|
+
relation.page_id,
|
|
293
|
+
`Review Unit ${relation.review_unit_id}`,
|
|
294
|
+
);
|
|
295
|
+
const pair = `${unit.review_unit_id}\0${page.page_id}`;
|
|
296
|
+
if (reviewUnitPagePairs.has(pair)) {
|
|
297
|
+
throw new Error(`Duplicate Review Unit Page relation: ${unit.review_unit_id}/${page.page_id}.`);
|
|
298
|
+
}
|
|
299
|
+
reviewUnitPagePairs.add(pair);
|
|
300
|
+
const related = reviewUnitPages.get(unit.review_unit_id) || [];
|
|
301
|
+
related.push(page.page_id);
|
|
302
|
+
reviewUnitPages.set(unit.review_unit_id, related);
|
|
303
|
+
}
|
|
304
|
+
validateLifecycle(terminals, "terminal_id", "Terminal");
|
|
305
|
+
validateLifecycle(pages, "page_id", "Page");
|
|
306
|
+
validateLifecycle(scenes, "review_scene_id", "Review Scene");
|
|
307
|
+
validateLifecycle(routes, "route_id", "Route");
|
|
308
|
+
validateLifecycle(mappings, "ui_mapping_id", "UI Mapping");
|
|
309
|
+
validateLifecycle(reviewUnits, "review_unit_id", "Review Unit");
|
|
310
|
+
validateLifecycle(components, "component_id", "Component Registration");
|
|
311
|
+
validateLifecycle(dataContracts, "data_contract_id", "Data Contract");
|
|
312
|
+
validateLifecycle(sourceInspectionSnapshots, "source_snapshot_id", "Source Inspection Snapshot");
|
|
313
|
+
|
|
314
|
+
for (const terminal of terminals.values()) {
|
|
315
|
+
for (const field of ["source_entrypoint", "build_command", "review_base_path", "production_base_path"]) {
|
|
316
|
+
if (!String(terminal[field] || "").trim()) throw new Error(`Terminal ${terminal.terminal_id} is missing ${field}.`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
for (const page of pages.values()) {
|
|
320
|
+
if (!/^P[0-9]{2,}(?:-[0-9]{2,})?$/.test(page.page_id)) {
|
|
321
|
+
throw new Error(`Page ID is invalid: ${page.page_id}.`);
|
|
322
|
+
}
|
|
323
|
+
const terminal = requireReference(terminals, page.terminal_id, `Page ${page.page_id}`);
|
|
324
|
+
if (page.lifecycle_status === "active" && terminal.lifecycle_status !== "active") {
|
|
325
|
+
throw new Error(`Page ${page.page_id} is active but references retired Terminal: ${page.terminal_id}.`);
|
|
326
|
+
}
|
|
327
|
+
if (page.display_label !== `${page.page_id}-${page.page_name}`) {
|
|
328
|
+
throw new Error(`Page ${page.page_id} display_label must be derived from its identity and name.`);
|
|
329
|
+
}
|
|
330
|
+
if (page.page_id.includes("-")) {
|
|
331
|
+
const parent = requireReference(pages, page.parent_page_id, `Page ${page.page_id}`);
|
|
332
|
+
if (parent.page_id.includes("-") || parent.terminal_id !== page.terminal_id) {
|
|
333
|
+
throw new Error(`Page ${page.page_id} has an invalid parent page.`);
|
|
334
|
+
}
|
|
335
|
+
if (page.lifecycle_status === "active" && parent.lifecycle_status !== "active") {
|
|
336
|
+
throw new Error(`Page ${page.page_id} is active but references retired parent Page: ${parent.page_id}.`);
|
|
337
|
+
}
|
|
338
|
+
} else if (page.parent_page_id) throw new Error(`Main Page ${page.page_id} must not have a parent.`);
|
|
339
|
+
}
|
|
340
|
+
const allowedSceneTypes = new Set([
|
|
341
|
+
"default", "loading", "empty", "success", "partial", "error", "permission", "disabled", "recovery",
|
|
342
|
+
]);
|
|
343
|
+
for (const scene of scenes.values()) {
|
|
344
|
+
if (!/^RS[0-9]{4,}$/.test(scene.review_scene_id)) {
|
|
345
|
+
throw new Error(`Review Scene ID is invalid: ${scene.review_scene_id}.`);
|
|
346
|
+
}
|
|
347
|
+
const page = requireReference(pages, scene.page_id, `Review Scene ${scene.review_scene_id}`);
|
|
348
|
+
if (page.terminal_id !== scene.terminal_id) {
|
|
349
|
+
throw new Error(`Review Scene ${scene.review_scene_id} terminal does not match its Page.`);
|
|
350
|
+
}
|
|
351
|
+
if (scene.lifecycle_status === "active" && page.lifecycle_status !== "active") {
|
|
352
|
+
throw new Error(`Review Scene ${scene.review_scene_id} is active but references retired Page: ${page.page_id}.`);
|
|
353
|
+
}
|
|
354
|
+
if (!allowedSceneTypes.has(scene.scene_type)) {
|
|
355
|
+
throw new Error(`Review Scene ${scene.review_scene_id} has invalid scene_type ${scene.scene_type}.`);
|
|
356
|
+
}
|
|
357
|
+
requireReference(triggers, scene.trigger_condition_id, `Review Scene ${scene.review_scene_id}`);
|
|
358
|
+
}
|
|
359
|
+
const routePaths = new Set();
|
|
360
|
+
for (const route of routes.values()) {
|
|
361
|
+
if (!new Set(["prototype", "production"]).has(route.route_kind)) {
|
|
362
|
+
throw new Error(`Route ${route.route_id} has invalid route_kind ${route.route_kind}.`);
|
|
363
|
+
}
|
|
364
|
+
const terminal = requireReference(terminals, route.terminal_id, `Route ${route.route_id}`);
|
|
365
|
+
const page = requireReference(pages, route.page_id, `Route ${route.route_id}`);
|
|
366
|
+
if (page.terminal_id !== route.terminal_id) throw new Error(`Route ${route.route_id} terminal does not match its Page.`);
|
|
367
|
+
if (!route.path.startsWith("/")) throw new Error(`Route ${route.route_id} path must start with '/'.`);
|
|
368
|
+
const pathIdentity = `${route.terminal_id}:${route.route_kind}:${route.path}`;
|
|
369
|
+
if (routePaths.has(pathIdentity)) throw new Error(`Duplicate route path: ${pathIdentity}.`);
|
|
370
|
+
routePaths.add(pathIdentity);
|
|
371
|
+
if (route.route_kind === "production" && route.review_scene_id) {
|
|
372
|
+
throw new Error(`Production route ${route.route_id} must not target a Review Scene.`);
|
|
373
|
+
}
|
|
374
|
+
if (route.review_scene_id) {
|
|
375
|
+
const scene = requireReference(scenes, route.review_scene_id, `Route ${route.route_id}`);
|
|
376
|
+
if (scene.page_id !== route.page_id) throw new Error(`Route ${route.route_id} Review Scene does not belong to its Page.`);
|
|
377
|
+
if (route.lifecycle_status === "active" && scene.lifecycle_status !== "active") {
|
|
378
|
+
throw new Error(`Route ${route.route_id} is active but references retired Review Scene: ${scene.review_scene_id}.`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (route.lifecycle_status === "active"
|
|
382
|
+
&& (terminal.lifecycle_status !== "active" || page.lifecycle_status !== "active")) {
|
|
383
|
+
throw new Error(`Route ${route.route_id} is active but references a retired Terminal or Page.`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const mappedPages = new Set();
|
|
388
|
+
const mappedScenes = new Set();
|
|
389
|
+
for (const mapping of active(catalogRows.ui_mapping)) {
|
|
390
|
+
const page = requireActiveReference(
|
|
391
|
+
pages, mapping.page_id, `Active UI Mapping ${mapping.ui_mapping_id}`, "Page",
|
|
392
|
+
);
|
|
393
|
+
if (page.terminal_id !== mapping.terminal_id) throw new Error(`UI Mapping ${mapping.ui_mapping_id} terminal does not match its Page.`);
|
|
394
|
+
const prototypeRoute = requireActiveReference(
|
|
395
|
+
routes, mapping.prototype_route_id, `Active UI Mapping ${mapping.ui_mapping_id}`, "route",
|
|
396
|
+
);
|
|
397
|
+
const productionRoute = requireActiveReference(
|
|
398
|
+
routes, mapping.production_route_id, `Active UI Mapping ${mapping.ui_mapping_id}`, "route",
|
|
399
|
+
);
|
|
400
|
+
if (prototypeRoute.route_kind !== "prototype" || productionRoute.route_kind !== "production") {
|
|
401
|
+
throw new Error(`UI Mapping ${mapping.ui_mapping_id} must bind prototype and production route kinds.`);
|
|
402
|
+
}
|
|
403
|
+
if (prototypeRoute.page_id !== page.page_id || productionRoute.page_id !== page.page_id) {
|
|
404
|
+
throw new Error(`UI Mapping ${mapping.ui_mapping_id} routes must target the mapped Page.`);
|
|
405
|
+
}
|
|
406
|
+
if (!mapping.frontend_source_ref || !mapping.acceptance_id) {
|
|
407
|
+
throw new Error(`UI Mapping ${mapping.ui_mapping_id} must bind frontend source and acceptance evidence.`);
|
|
408
|
+
}
|
|
409
|
+
if (mapping.target_kind === "page") {
|
|
410
|
+
if (mapping.target_id !== page.page_id || mapping.review_scene_id || prototypeRoute.review_scene_id) {
|
|
411
|
+
throw new Error(`Page UI Mapping ${mapping.ui_mapping_id} must target a Page-level prototype route.`);
|
|
412
|
+
}
|
|
413
|
+
if (mappedPages.has(page.page_id)) throw new Error(`Page ${page.page_id} has duplicate active UI Mapping rows.`);
|
|
414
|
+
mappedPages.add(page.page_id);
|
|
415
|
+
} else if (mapping.target_kind === "review-scene") {
|
|
416
|
+
const scene = requireActiveReference(
|
|
417
|
+
scenes, mapping.review_scene_id, `Active UI Mapping ${mapping.ui_mapping_id}`, "Review Scene",
|
|
418
|
+
);
|
|
419
|
+
if (mapping.target_id !== scene.review_scene_id || scene.page_id !== page.page_id
|
|
420
|
+
|| prototypeRoute.review_scene_id !== scene.review_scene_id) {
|
|
421
|
+
throw new Error(`Review Scene UI Mapping ${mapping.ui_mapping_id} does not match its direct prototype route.`);
|
|
422
|
+
}
|
|
423
|
+
if (mappedScenes.has(scene.review_scene_id)) {
|
|
424
|
+
throw new Error(`Review Scene ${scene.review_scene_id} has duplicate active UI Mapping rows.`);
|
|
425
|
+
}
|
|
426
|
+
mappedScenes.add(scene.review_scene_id);
|
|
427
|
+
} else throw new Error(`UI Mapping ${mapping.ui_mapping_id} has invalid target_kind ${mapping.target_kind}.`);
|
|
428
|
+
}
|
|
429
|
+
for (const page of active([...pages.values()])) {
|
|
430
|
+
if (!mappedPages.has(page.page_id)) throw new Error(`Page ${page.page_id} has no active UI Mapping.`);
|
|
431
|
+
}
|
|
432
|
+
for (const scene of active([...scenes.values()])) {
|
|
433
|
+
if (!mappedScenes.has(scene.review_scene_id)) {
|
|
434
|
+
throw new Error(`Review Scene ${scene.review_scene_id} has no directly accessible prototype route.`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const coveredPages = new Set();
|
|
438
|
+
const coveredScenes = new Set();
|
|
439
|
+
for (const unit of reviewUnits.values()) {
|
|
440
|
+
if (!new Set(["page", "review-scene", "shared-component"]).has(unit.unit_kind)) {
|
|
441
|
+
throw new Error(`Review Unit ${unit.review_unit_id} has invalid unit_kind ${unit.unit_kind || "<empty>"}.`);
|
|
442
|
+
}
|
|
443
|
+
const sourceFile = containedPath(sourceRoot, unit.source_ref, `Review Unit ${unit.review_unit_id} source`);
|
|
444
|
+
if (!fs.existsSync(sourceFile) || !fs.statSync(sourceFile).isFile()) {
|
|
445
|
+
throw new Error(`Review Unit ${unit.review_unit_id} source is missing.`);
|
|
446
|
+
}
|
|
447
|
+
if (sha256File(sourceFile) !== requireChecksum(unit.checksum, `Review Unit ${unit.review_unit_id}`)) {
|
|
448
|
+
throw new Error(`Review Unit ${unit.review_unit_id} checksum does not match its source.`);
|
|
449
|
+
}
|
|
450
|
+
const pageIds = reviewUnitPages.get(unit.review_unit_id) || [];
|
|
451
|
+
if (!pageIds.length) throw new Error(`Review Unit ${unit.review_unit_id} has no affected Page relation.`);
|
|
452
|
+
for (const pageId of pageIds) {
|
|
453
|
+
const page = requireReference(pages, pageId, `Review Unit ${unit.review_unit_id}`);
|
|
454
|
+
if (unit.lifecycle_status === "active" && page.lifecycle_status !== "active") {
|
|
455
|
+
throw new Error(`Active Review Unit ${unit.review_unit_id} references retired Page: ${pageId}.`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (unit.unit_kind === "page") {
|
|
459
|
+
const page = requireReference(pages, unit.target_id, `Review Unit ${unit.review_unit_id}`);
|
|
460
|
+
if (pageIds.length !== 1 || pageIds[0] !== page.page_id) {
|
|
461
|
+
throw new Error(`Page Review Unit ${unit.review_unit_id} must affect exactly its target Page.`);
|
|
462
|
+
}
|
|
463
|
+
if (unit.lifecycle_status === "active") coveredPages.add(page.page_id);
|
|
464
|
+
} else if (unit.unit_kind === "review-scene") {
|
|
465
|
+
const scene = requireReference(scenes, unit.target_id, `Review Unit ${unit.review_unit_id}`);
|
|
466
|
+
if (pageIds.length !== 1 || pageIds[0] !== scene.page_id) {
|
|
467
|
+
throw new Error(`Review Scene Unit ${unit.review_unit_id} must affect exactly its owning Page.`);
|
|
468
|
+
}
|
|
469
|
+
if (unit.lifecycle_status === "active") coveredScenes.add(scene.review_scene_id);
|
|
470
|
+
} else if (unit.target_id) {
|
|
471
|
+
throw new Error(`Shared-component Review Unit ${unit.review_unit_id} must not declare target_id.`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
for (const page of active([...pages.values()])) {
|
|
475
|
+
if (!coveredPages.has(page.page_id)) throw new Error(`Page ${page.page_id} has no active Page Review Unit.`);
|
|
476
|
+
}
|
|
477
|
+
for (const scene of active([...scenes.values()])) {
|
|
478
|
+
if (!coveredScenes.has(scene.review_scene_id)) {
|
|
479
|
+
throw new Error(`Review Scene ${scene.review_scene_id} has no active Review Unit.`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const implementationEntries = new Map();
|
|
483
|
+
for (const binding of implementationBindings.values()) {
|
|
484
|
+
const label = `Implementation Binding ${binding.component_id}`;
|
|
485
|
+
const candidate = componentCandidates.get(binding.component_id);
|
|
486
|
+
if (!components.has(binding.component_id) && !candidate) {
|
|
487
|
+
throw new Error(`${label} references unknown Component Registration or Component Candidate: ${binding.component_id || "<empty>"}.`);
|
|
488
|
+
}
|
|
489
|
+
if (candidate && candidate.lifecycle !== "registered") {
|
|
490
|
+
throw new Error(`${label} requires Component Candidate ${binding.component_id} to be registered.`);
|
|
491
|
+
}
|
|
492
|
+
requireTypedReference(dataContracts, binding.data_contract_id, label, "Data Contract");
|
|
493
|
+
if (candidate && candidate.data_contract_id !== binding.data_contract_id) {
|
|
494
|
+
throw new Error(
|
|
495
|
+
`${label} Data Contract ${binding.data_contract_id} conflicts with Component Candidate ${binding.component_id} Data Contract ${candidate.data_contract_id}.`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
const implementationEntry = String(binding.implementation_entry || "").trim();
|
|
499
|
+
if (implementationEntries.has(implementationEntry)) {
|
|
500
|
+
throw new Error(
|
|
501
|
+
`${label} reuses implementation entry ${implementationEntry} from Component Registration ${implementationEntries.get(implementationEntry)}.`,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
implementationEntries.set(implementationEntry, binding.component_id);
|
|
505
|
+
}
|
|
506
|
+
for (const candidate of componentCandidates.values()) {
|
|
507
|
+
const label = `Component Candidate ${candidate.component_id}`;
|
|
508
|
+
if (!COMPONENT_CANDIDATE_LIFECYCLE_STATES.includes(candidate.lifecycle)) {
|
|
509
|
+
throw new Error(`${label} has invalid lifecycle ${candidate.lifecycle || "<empty>"}.`);
|
|
510
|
+
}
|
|
511
|
+
const page = requireTypedReference(pages, candidate.source_page_id, label, "Page");
|
|
512
|
+
requireTypedReference(dataContracts, candidate.data_contract_id, label, "Data Contract");
|
|
513
|
+
for (const sceneId of pipeReferences(candidate.source_scene_ids, label)) {
|
|
514
|
+
const scene = requireTypedReference(scenes, sceneId, label, "Review Scene");
|
|
515
|
+
if (scene.page_id !== page.page_id) {
|
|
516
|
+
throw new Error(`${label} Review Scene ${sceneId} is outside its source Page.`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (components.has(candidate.component_id) && candidate.lifecycle !== "registered") {
|
|
520
|
+
throw new Error(`${label} is not registered but duplicates a Component Registration.`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
for (const binding of catalogRows.component_bindings) {
|
|
524
|
+
const label = `Component Binding ${binding.page_id || "<unknown>"}`;
|
|
525
|
+
const page = requireTypedReference(pages, binding.page_id, label, "Page");
|
|
526
|
+
const terminal = requireTypedReference(terminals, binding.terminal_id, label, "Logical Terminal");
|
|
527
|
+
if (page.terminal_id !== terminal.terminal_id) {
|
|
528
|
+
throw new Error(`${label} terminal does not match its Page.`);
|
|
529
|
+
}
|
|
530
|
+
const candidate = componentCandidates.get(binding.component_id);
|
|
531
|
+
const component = components.get(binding.component_id) || candidate;
|
|
532
|
+
if (!component) {
|
|
533
|
+
throw new Error(
|
|
534
|
+
`Component Binding ${binding.page_id || "<unknown>"} references unknown Component Registration or Component Candidate: ${binding.component_id || "<empty>"}.`,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
requireTypedReference(dataContracts, binding.data_contract_id, label, "Data Contract");
|
|
538
|
+
const variants = pipeReferences(component.variants, `Component ${binding.component_id}`);
|
|
539
|
+
if (!variants.includes(binding.variant)) {
|
|
540
|
+
throw new Error(
|
|
541
|
+
`${label} variant ${binding.variant || "<empty>"} is not registered by Component ${binding.component_id}.`,
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
if (!new Set(["active", "pending-review", "stale"]).has(binding.status)) {
|
|
545
|
+
throw new Error(`${label} has invalid status ${binding.status || "<empty>"}.`);
|
|
546
|
+
}
|
|
547
|
+
const expectedCandidateBindingStatus = candidate
|
|
548
|
+
&& COMPONENT_CANDIDATE_BINDING_STATUS[candidate.lifecycle];
|
|
549
|
+
if (expectedCandidateBindingStatus && binding.status !== expectedCandidateBindingStatus) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
`Component Candidate ${candidate.component_id} lifecycle ${candidate.lifecycle} requires ${expectedCandidateBindingStatus} bindings.`,
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
const implementation = implementationBindings.get(binding.component_id);
|
|
555
|
+
if (implementation && implementation.data_contract_id !== binding.data_contract_id) {
|
|
556
|
+
throw new Error(
|
|
557
|
+
`${label} Data Contract ${binding.data_contract_id} conflicts with Implementation Binding ${binding.component_id} Data Contract ${implementation.data_contract_id}.`,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
for (const sceneId of pipeReferences(binding.scene_ids, label)) {
|
|
561
|
+
const scene = requireTypedReference(scenes, sceneId, label, "Review Scene");
|
|
562
|
+
if (scene.page_id !== page.page_id || scene.terminal_id !== terminal.terminal_id) {
|
|
563
|
+
throw new Error(`${label} Review Scene ${sceneId} is outside its Page or Logical Terminal.`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
for (const snapshot of sourceInspectionSnapshots.values()) {
|
|
568
|
+
const label = `Source Inspection Snapshot ${snapshot.source_snapshot_id}`;
|
|
569
|
+
if (!new Set(["implementation", "acceptance"]).has(snapshot.asset_kind)) {
|
|
570
|
+
throw new Error(`${label} has invalid asset_kind ${snapshot.asset_kind || "<empty>"}.`);
|
|
571
|
+
}
|
|
572
|
+
for (const field of ["page_id", "terminal_id", "source_ref", "language", "scene_branch"]) {
|
|
573
|
+
if (!String(snapshot[field] || "").trim()) {
|
|
574
|
+
throw new Error(`${label} is missing ${field}.`);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (snapshot.source_ref.includes("\\")
|
|
578
|
+
|| path.posix.isAbsolute(snapshot.source_ref)
|
|
579
|
+
|| path.posix.normalize(snapshot.source_ref) !== snapshot.source_ref) {
|
|
580
|
+
throw new Error(`${label} source_ref must be a normalized relative POSIX path.`);
|
|
581
|
+
}
|
|
582
|
+
const terminal = requireTypedReference(terminals, snapshot.terminal_id, label, "Logical Terminal");
|
|
583
|
+
const page = requireTypedReference(pages, snapshot.page_id, label, "Page");
|
|
584
|
+
if (page.terminal_id !== terminal.terminal_id) {
|
|
585
|
+
throw new Error(`${label} Logical Terminal does not match its Page.`);
|
|
586
|
+
}
|
|
587
|
+
if (snapshot.review_scene_id) {
|
|
588
|
+
const scene = requireTypedReference(scenes, snapshot.review_scene_id, label, "Review Scene");
|
|
589
|
+
if (scene.page_id !== page.page_id || scene.terminal_id !== terminal.terminal_id) {
|
|
590
|
+
throw new Error(`${label} Review Scene is outside its Page or Logical Terminal.`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const sourceFile = containedPath(sourceRoot, snapshot.source_ref, `${label} source`);
|
|
594
|
+
if (!fs.existsSync(sourceFile) || !fs.statSync(sourceFile).isFile()) {
|
|
595
|
+
throw new Error(`${label} source is missing.`);
|
|
596
|
+
}
|
|
597
|
+
if (sha256File(sourceFile) !== requireChecksum(snapshot.checksum, label)) {
|
|
598
|
+
throw new Error(`${label} checksum does not match its source.`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return {
|
|
602
|
+
terminalCount: terminals.size,
|
|
603
|
+
pageCount: pages.size,
|
|
604
|
+
reviewSceneCount: scenes.size,
|
|
605
|
+
reviewUnitCount: reviewUnits.size,
|
|
606
|
+
reviewUnitPageCount: reviewUnitPagePairs.size,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function containedFrom(base, relative, allowedRoot, label) {
|
|
611
|
+
if (!relative || path.isAbsolute(relative)) throw new Error(`${label} must be a relative path.`);
|
|
612
|
+
const absolute = path.resolve(base, relative);
|
|
613
|
+
const fromAllowedRoot = path.relative(path.resolve(allowedRoot), absolute);
|
|
614
|
+
return containedPath(allowedRoot, fromAllowedRoot, label);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function readRevisionCatalog(revision, revisionFile, experienceRoot, name) {
|
|
618
|
+
const bundle = revision.review_bundle;
|
|
619
|
+
const revisionRoot = path.dirname(revisionFile);
|
|
620
|
+
const bundleRoot = containedFrom(revisionRoot, bundle?.ref, experienceRoot, "Previous review bundle");
|
|
621
|
+
if (bundle?.algorithm !== "sha256-tree-v1"
|
|
622
|
+
|| prototypeRevisionTreeDigest(bundleRoot) !== requireChecksum(bundle?.checksum, "Previous review bundle")) {
|
|
623
|
+
throw new Error("Previous Prototype review bundle checksum mismatch.");
|
|
624
|
+
}
|
|
625
|
+
const entry = revision.catalogs?.[name];
|
|
626
|
+
if (entry?.algorithm !== "sha256") throw new Error(`Previous Prototype ${name} catalog must use sha256.`);
|
|
627
|
+
const file = containedFrom(bundleRoot, entry?.ref, bundleRoot, `Previous Prototype ${name} catalog`);
|
|
628
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
|
629
|
+
throw new Error(`Previous Prototype ${name} catalog is missing.`);
|
|
630
|
+
}
|
|
631
|
+
if (sha256File(file) !== requireChecksum(entry?.checksum, `Previous Prototype ${name} catalog`)) {
|
|
632
|
+
throw new Error(`Previous Prototype ${name} catalog checksum mismatch.`);
|
|
633
|
+
}
|
|
634
|
+
const parsed = parsePrototypeCatalogCsv(
|
|
635
|
+
fs.readFileSync(file, "utf8"), `Previous Prototype ${name} catalog`,
|
|
636
|
+
);
|
|
637
|
+
if (parsed.header !== prototypeRevisionCatalogContracts[name]) {
|
|
638
|
+
throw new Error(`Previous Prototype ${name} catalog header is invalid.`);
|
|
639
|
+
}
|
|
640
|
+
return parsed.rows;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function readPreviousRevisionLink(revision, revisionFile, experienceRoot) {
|
|
644
|
+
const previousEntry = revision.previous_revision;
|
|
645
|
+
if (revision.version === 1) {
|
|
646
|
+
if (previousEntry !== null && previousEntry !== undefined) {
|
|
647
|
+
throw new Error("Initial Prototype revision must not declare a previous revision.");
|
|
648
|
+
}
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
if (!previousEntry || previousEntry.algorithm !== "sha256") {
|
|
652
|
+
throw new Error("Prototype revision version greater than one must pin the previous revision and checksum.");
|
|
653
|
+
}
|
|
654
|
+
const previousFile = containedFrom(
|
|
655
|
+
path.dirname(revisionFile), previousEntry.ref, experienceRoot, "Previous Prototype revision",
|
|
656
|
+
);
|
|
657
|
+
if (!fs.existsSync(previousFile) || !fs.statSync(previousFile).isFile()) {
|
|
658
|
+
throw new Error("Previous Prototype revision is missing.");
|
|
659
|
+
}
|
|
660
|
+
const previousChecksum = requireChecksum(previousEntry.checksum, "Previous Prototype revision");
|
|
661
|
+
if (sha256File(previousFile) !== previousChecksum) {
|
|
662
|
+
throw new Error("Previous Prototype revision checksum mismatch.");
|
|
663
|
+
}
|
|
664
|
+
const previous = readYaml(previousFile, "Previous Prototype revision");
|
|
665
|
+
if (previous.artifact_type !== "prototype-revision" || previous.status !== "frozen"
|
|
666
|
+
|| !previous.artifact_id || previous.prototype_id !== revision.prototype_id
|
|
667
|
+
|| previous.version !== revision.version - 1) {
|
|
668
|
+
throw new Error("Previous Prototype revision identity, status, or version is invalid.");
|
|
669
|
+
}
|
|
670
|
+
return { revision: previous, file: previousFile, checksum: previousChecksum };
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function readPrototypeRevisionAncestors(revision, revisionFile, experienceRoot) {
|
|
674
|
+
const ancestors = [];
|
|
675
|
+
const seenIds = new Set([revision.artifact_id]);
|
|
676
|
+
let current = revision;
|
|
677
|
+
let currentFile = revisionFile;
|
|
678
|
+
while (true) {
|
|
679
|
+
const previous = readPreviousRevisionLink(current, currentFile, experienceRoot);
|
|
680
|
+
if (!previous) return ancestors;
|
|
681
|
+
if (seenIds.has(previous.revision.artifact_id)) {
|
|
682
|
+
throw new Error("Prototype revision ancestry contains a repeated identity.");
|
|
683
|
+
}
|
|
684
|
+
ancestors.push(previous);
|
|
685
|
+
seenIds.add(previous.revision.artifact_id);
|
|
686
|
+
current = previous.revision;
|
|
687
|
+
currentFile = previous.file;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function validateStableIdentityHistory(previousLink, experienceRoot, catalogRows) {
|
|
692
|
+
if (!previousLink) return;
|
|
693
|
+
const previous = previousLink.revision;
|
|
694
|
+
const previousFile = previousLink.file;
|
|
695
|
+
for (const [name, identityField, label, immutableFields] of [
|
|
696
|
+
["pages", "page_id", "Page", ["terminal_id", "parent_page_id"]],
|
|
697
|
+
["review_scenes", "review_scene_id", "Review Scene", ["terminal_id", "page_id"]],
|
|
698
|
+
]) {
|
|
699
|
+
const previousRows = readRevisionCatalog(previous, previousFile, experienceRoot, name);
|
|
700
|
+
const prior = indexUnique(previousRows, identityField, `previous ${label} catalog`);
|
|
701
|
+
const current = indexUnique(catalogRows[name], identityField, `current ${label} catalog`);
|
|
702
|
+
validateLifecycle(prior, identityField, `Previous ${label}`);
|
|
703
|
+
for (const [identity, previousRow] of prior) {
|
|
704
|
+
const currentRow = current.get(identity);
|
|
705
|
+
if (!currentRow) throw new Error(`Stable ${label} identity ${identity} was removed instead of retired.`);
|
|
706
|
+
if (previousRow.lifecycle_status === "retired" && currentRow.lifecycle_status !== "retired") {
|
|
707
|
+
throw new Error(`Retired ${label} identity ${identity} was reactivated or reused.`);
|
|
708
|
+
}
|
|
709
|
+
for (const field of immutableFields) {
|
|
710
|
+
if (previousRow[field] !== currentRow[field]) {
|
|
711
|
+
throw new Error(`Stable ${label} identity ${identity} changed immutable ${field}.`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export function validatePrototypeRevision(projectRoot, options = {}) {
|
|
719
|
+
const experienceRoot = containedPath(projectRoot, options.experience, "Experience directory");
|
|
720
|
+
const revisionFile = containedPath(experienceRoot, options.revision, "Prototype revision");
|
|
721
|
+
const revision = readYaml(revisionFile, "Prototype revision");
|
|
722
|
+
const current = readYaml(path.join(experienceRoot, "prototype.yaml"), "Current Prototype descriptor");
|
|
723
|
+
if (current.artifact_type !== "prototype-current" || revision.artifact_type !== "prototype-revision") {
|
|
724
|
+
throw new Error("Prototype artifacts do not declare the current and revision artifact types.");
|
|
725
|
+
}
|
|
726
|
+
if (!current.prototype_id || current.prototype_id !== revision.prototype_id) {
|
|
727
|
+
throw new Error("Prototype revision must belong to the single current Prototype identity.");
|
|
728
|
+
}
|
|
729
|
+
if (revision.status !== "frozen" || !Number.isInteger(revision.version) || revision.version < 1) {
|
|
730
|
+
throw new Error("Prototype revision must be a frozen positive semantic version.");
|
|
731
|
+
}
|
|
732
|
+
if (!revision.artifact_id || !revision.iteration_id || !Number.isFinite(Date.parse(revision.frozen_at || ""))) {
|
|
733
|
+
throw new Error("Prototype revision must declare artifact, iteration, and frozen-at identities.");
|
|
734
|
+
}
|
|
735
|
+
if (!revision.source || revision.source.ref !== current.current_source_ref) {
|
|
736
|
+
throw new Error("Prototype revision source must bind the single current Prototype source path.");
|
|
737
|
+
}
|
|
738
|
+
if (!String(current.current_review_shell_ref || "").trim()) {
|
|
739
|
+
throw new Error("Current Prototype must declare one current Review Shell path.");
|
|
740
|
+
}
|
|
741
|
+
if (!String(revision.source.revision || "").trim() || revision.source.algorithm !== "sha256-tree-v1") {
|
|
742
|
+
throw new Error("Prototype source must pin an immutable revision and sha256-tree-v1 digest.");
|
|
743
|
+
}
|
|
744
|
+
const revisionRelative = path.relative(experienceRoot, revisionFile).replaceAll("\\", "/");
|
|
745
|
+
const sourceRoot = containedPath(experienceRoot, revision.source.ref, "Current Prototype source");
|
|
746
|
+
if (prototypeRevisionTreeDigest(sourceRoot) !== requireChecksum(revision.source.checksum, "Prototype source")) {
|
|
747
|
+
throw new Error("Current Prototype source checksum does not match its selected revision.");
|
|
748
|
+
}
|
|
749
|
+
const currentShellRoot = containedPath(
|
|
750
|
+
experienceRoot, current.current_review_shell_ref, "Current Review Shell",
|
|
751
|
+
);
|
|
752
|
+
if (!fs.existsSync(currentShellRoot) || !fs.statSync(currentShellRoot).isDirectory()
|
|
753
|
+
|| listFiles(currentShellRoot).length === 0) {
|
|
754
|
+
throw new Error("Current Prototype must expose one non-empty current Review Shell directory.");
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const revisionRoot = path.dirname(revisionFile);
|
|
758
|
+
const bundleRoot = containedPath(revisionRoot, revision.review_bundle?.ref, "Prototype review bundle");
|
|
759
|
+
if (revision.review_bundle?.algorithm !== "sha256-tree-v1"
|
|
760
|
+
|| prototypeRevisionTreeDigest(bundleRoot) !== requireChecksum(revision.review_bundle?.checksum, "Prototype review bundle")) {
|
|
761
|
+
throw new Error("Prototype review bundle checksum mismatch.");
|
|
762
|
+
}
|
|
763
|
+
const verifyBundleFile = (entry, label) => {
|
|
764
|
+
const file = containedPath(bundleRoot, entry?.ref, label);
|
|
765
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) throw new Error(`${label} is missing.`);
|
|
766
|
+
if (sha256File(file) !== requireChecksum(entry?.checksum, label)) throw new Error(`${label} checksum mismatch.`);
|
|
767
|
+
return file;
|
|
768
|
+
};
|
|
769
|
+
const shellFile = containedPath(bundleRoot, revision.review_shell?.entrypoint_in_bundle, "Review Shell entrypoint");
|
|
770
|
+
if (!fs.existsSync(shellFile) || !fs.statSync(shellFile).isFile()) throw new Error("Review Shell entrypoint is missing from the bundle.");
|
|
771
|
+
if (sha256File(shellFile) !== requireChecksum(revision.review_shell?.checksum, "Review Shell")) {
|
|
772
|
+
throw new Error("Review Shell checksum mismatch.");
|
|
773
|
+
}
|
|
774
|
+
verifyBundleFile(revision.visual_standard, "Visual Standard evidence");
|
|
775
|
+
verifyBundleFile(revision.review_evidence, "Prototype review evidence");
|
|
776
|
+
|
|
777
|
+
const catalogs = revision.catalogs;
|
|
778
|
+
if (revision.catalog_schema_version !== PROTOTYPE_REVISION_CATALOG_SCHEMA_VERSION) {
|
|
779
|
+
throw new Error(`Prototype revision must use ${PROTOTYPE_REVISION_CATALOG_SCHEMA_VERSION}.`);
|
|
780
|
+
}
|
|
781
|
+
if (!catalogs || typeof catalogs !== "object" || Array.isArray(catalogs)) {
|
|
782
|
+
throw new Error("Prototype revision catalogs are missing.");
|
|
783
|
+
}
|
|
784
|
+
const catalogRows = {};
|
|
785
|
+
for (const [name, expectedHeader] of Object.entries(prototypeRevisionCatalogContracts)) {
|
|
786
|
+
const entry = catalogs[name];
|
|
787
|
+
if (entry?.algorithm !== "sha256") throw new Error(`Prototype ${name} catalog must use sha256.`);
|
|
788
|
+
const file = verifyBundleFile(entry, `Prototype ${name} catalog`);
|
|
789
|
+
const parsed = parsePrototypeCatalogCsv(fs.readFileSync(file, "utf8"), `Prototype ${name} catalog`);
|
|
790
|
+
if (parsed.header !== expectedHeader) throw new Error(`Prototype ${name} catalog header is invalid.`);
|
|
791
|
+
catalogRows[name] = parsed.rows;
|
|
792
|
+
}
|
|
793
|
+
const counts = validateCatalogs(catalogRows, sourceRoot);
|
|
794
|
+
const ancestorLinks = readPrototypeRevisionAncestors(revision, revisionFile, experienceRoot);
|
|
795
|
+
validateStableIdentityHistory(ancestorLinks[0] || null, experienceRoot, catalogRows);
|
|
796
|
+
return {
|
|
797
|
+
valid: true,
|
|
798
|
+
prototype_id: revision.prototype_id,
|
|
799
|
+
revision_id: revision.artifact_id,
|
|
800
|
+
catalog_schema_version: revision.catalog_schema_version,
|
|
801
|
+
terminal_count: counts.terminalCount,
|
|
802
|
+
page_count: counts.pageCount,
|
|
803
|
+
review_scene_count: counts.reviewSceneCount,
|
|
804
|
+
review_unit_count: counts.reviewUnitCount,
|
|
805
|
+
review_unit_page_count: counts.reviewUnitPageCount,
|
|
806
|
+
ancestor_revisions: ancestorLinks.map((ancestor) => ({
|
|
807
|
+
prototype_revision_id: ancestor.revision.artifact_id,
|
|
808
|
+
prototype_revision_checksum: ancestor.checksum,
|
|
809
|
+
})),
|
|
810
|
+
revision: revisionRelative,
|
|
811
|
+
};
|
|
812
|
+
}
|