create-yss-spec 3.1.0 → 3.2.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 +119 -89
- package/bin/create-yss-spec.js +11 -3
- package/package.json +9 -2
- package/src/api/_sync-service.js +191 -0
- package/src/api/index.js +22 -0
- package/src/api/project-diff.js +9 -0
- package/src/api/project-doctor.js +10 -0
- package/src/api/template-apply.js +10 -0
- package/src/api/template-plan.js +9 -0
- package/src/cli/args.js +60 -0
- package/src/cli/error-output.js +52 -0
- package/src/cli/flags.js +9 -0
- package/src/cli/help.js +95 -0
- package/src/cli/index.js +82 -0
- package/src/cli/plan-output.js +61 -0
- package/src/cli/prompts.js +91 -0
- package/src/cli/router.js +41 -0
- package/src/cli.js +5 -2253
- package/src/commands/attach.js +280 -0
- package/src/commands/diff.js +23 -0
- package/src/commands/doctor.js +417 -0
- package/src/commands/init.js +193 -0
- package/src/commands/sync.js +146 -0
- package/src/commands/update.js +14 -0
- package/src/contracts/apply-result-v1.json +45 -0
- package/src/contracts/customization-policy-v1.json +28 -0
- package/src/contracts/doctor-report-v1.json +29 -0
- package/src/contracts/error-envelope-v1.json +24 -0
- package/src/contracts/generator-policy-v1.json +24 -0
- package/src/contracts/ownership-policy-v1.json +40 -0
- package/src/contracts/plan-schema-v1.json +115 -0
- package/src/family-identity.js +365 -0
- package/src/family-runtime.js +23 -0
- package/src/filesystem/apply-plan.js +44 -0
- package/src/filesystem/copy-path.js +28 -0
- package/src/filesystem/path-utils.js +67 -0
- package/src/filesystem/transaction-runner.js +41 -0
- package/src/filesystem/transaction.js +153 -0
- package/src/git/submodule.js +115 -0
- package/src/git/worktree.js +69 -0
- package/src/template/attach-planner-runtime.js +51 -0
- package/src/template/attach-planner.js +160 -0
- package/src/template/instance-runtime.js +463 -0
- package/src/template/lifecycle-metadata.js +99 -0
- package/src/template/lifecycle-policy.js +129 -0
- package/src/template/lifecycle-runtime.js +90 -0
- package/src/template/migration-planner.js +95 -0
- package/src/template/migration-runtime.js +247 -0
- package/src/template/ownership-metadata.js +79 -0
- package/src/template/ownership-policy.js +149 -0
- package/src/template/ownership-runtime.js +109 -0
- package/src/template/plan-schema.js +36 -0
- package/src/template/sync-planner-runtime.js +57 -0
- package/src/template/sync-planner.js +206 -0
- package/src/template/verification-runtime.js +154 -0
- package/src/validation/identity.js +41 -0
- package/src/validation/metadata.js +105 -0
- package/src/validation/security.js +67 -0
- package/src/validation/snapshot.js +47 -0
- package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
- package/template/.agents/skills/yss-domain/SKILL.md +3 -3
- package/template/.agents/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
- package/template/.agents/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
- package/template/.agents/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
- package/template/.agents/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
- package/template/.agents/skills/yss-mvc-design/SKILL.md +16 -0
- package/template/.agents/skills/yss-mvc-design/agents/openai.yaml +4 -0
- package/template/.agents/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +4 -2
- package/template/.agents/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +1 -1
- package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.agents/skills/yss-tactical-design/SKILL.md +2 -2
- package/template/.agents/skills/yss-tactical-design/references/output-template.md +2 -0
- package/template/.agents/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
- package/template/.agents/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
- package/template/.agents/skills/yss-technical-design/SKILL.md +31 -0
- package/template/.agents/skills/yss-technical-design/agents/openai.yaml +4 -0
- package/template/.agents/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
- package/template/.agents/skills/yss-technical-design/references/technical-design-contract.md +31 -0
- package/template/.agents/skills/yss-technical-design/references/technical-design.schema.json +759 -0
- package/template/.agents/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
- package/template/.agents/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
- package/template/.agents/skills/yss-technical-design/tests/fixtures.mjs +32 -0
- package/template/.agents/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
- package/template/.agents/skills/yss-ui/SKILL.md +2 -0
- package/template/.claude/skills/yss-domain/SKILL.md +3 -3
- package/template/.claude/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.claude/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
- package/template/.claude/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
- package/template/.claude/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
- package/template/.claude/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
- package/template/.claude/skills/yss-mvc-design/SKILL.md +16 -0
- package/template/.claude/skills/yss-mvc-design/agents/openai.yaml +4 -0
- package/template/.claude/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
- package/template/.claude/skills/yss-product-lifecycle/SKILL.md +4 -2
- package/template/.claude/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
- package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +1 -1
- package/template/.claude/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.claude/skills/yss-tactical-design/SKILL.md +2 -2
- package/template/.claude/skills/yss-tactical-design/references/output-template.md +2 -0
- package/template/.claude/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
- package/template/.claude/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
- package/template/.claude/skills/yss-technical-design/SKILL.md +31 -0
- package/template/.claude/skills/yss-technical-design/agents/openai.yaml +4 -0
- package/template/.claude/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
- package/template/.claude/skills/yss-technical-design/references/technical-design-contract.md +31 -0
- package/template/.claude/skills/yss-technical-design/references/technical-design.schema.json +759 -0
- package/template/.claude/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
- package/template/.claude/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
- package/template/.claude/skills/yss-technical-design/tests/fixtures.mjs +32 -0
- package/template/.claude/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
- package/template/.claude/skills/yss-ui/SKILL.md +2 -0
- package/template/.codex/skills/yss-domain/SKILL.md +3 -3
- package/template/.codex/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
- package/template/.codex/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
- package/template/.codex/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
- package/template/.codex/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
- package/template/.codex/skills/yss-mvc-design/SKILL.md +16 -0
- package/template/.codex/skills/yss-mvc-design/agents/openai.yaml +4 -0
- package/template/.codex/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +4 -2
- package/template/.codex/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +1 -1
- package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.codex/skills/yss-tactical-design/SKILL.md +2 -2
- package/template/.codex/skills/yss-tactical-design/references/output-template.md +2 -0
- package/template/.codex/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
- package/template/.codex/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
- package/template/.codex/skills/yss-technical-design/SKILL.md +31 -0
- package/template/.codex/skills/yss-technical-design/agents/openai.yaml +4 -0
- package/template/.codex/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
- package/template/.codex/skills/yss-technical-design/references/technical-design-contract.md +31 -0
- package/template/.codex/skills/yss-technical-design/references/technical-design.schema.json +759 -0
- package/template/.codex/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
- package/template/.codex/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
- package/template/.codex/skills/yss-technical-design/tests/fixtures.mjs +32 -0
- package/template/.codex/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
- package/template/.codex/skills/yss-ui/SKILL.md +2 -0
- package/template/.cursor/skills/yss-domain/SKILL.md +3 -3
- package/template/.cursor/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
- package/template/.cursor/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
- package/template/.cursor/skills/yss-mvc-design/SKILL.md +16 -0
- package/template/.cursor/skills/yss-mvc-design/agents/openai.yaml +4 -0
- package/template/.cursor/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +4 -2
- package/template/.cursor/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +1 -1
- package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.cursor/skills/yss-tactical-design/SKILL.md +2 -2
- package/template/.cursor/skills/yss-tactical-design/references/output-template.md +2 -0
- package/template/.cursor/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
- package/template/.cursor/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
- package/template/.cursor/skills/yss-technical-design/SKILL.md +31 -0
- package/template/.cursor/skills/yss-technical-design/agents/openai.yaml +4 -0
- package/template/.cursor/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
- package/template/.cursor/skills/yss-technical-design/references/technical-design-contract.md +31 -0
- package/template/.cursor/skills/yss-technical-design/references/technical-design.schema.json +759 -0
- package/template/.cursor/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
- package/template/.cursor/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
- package/template/.cursor/skills/yss-technical-design/tests/fixtures.mjs +32 -0
- package/template/.cursor/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
- package/template/.cursor/skills/yss-ui/SKILL.md +2 -0
- package/template/.pi/skills/yss-domain/SKILL.md +3 -3
- package/template/.pi/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
- package/template/.pi/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
- package/template/.pi/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
- package/template/.pi/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
- package/template/.pi/skills/yss-mvc-design/SKILL.md +16 -0
- package/template/.pi/skills/yss-mvc-design/agents/openai.yaml +4 -0
- package/template/.pi/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +4 -2
- package/template/.pi/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +1 -1
- package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.pi/skills/yss-tactical-design/SKILL.md +2 -2
- package/template/.pi/skills/yss-tactical-design/references/output-template.md +2 -0
- package/template/.pi/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
- package/template/.pi/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
- package/template/.pi/skills/yss-technical-design/SKILL.md +31 -0
- package/template/.pi/skills/yss-technical-design/agents/openai.yaml +4 -0
- package/template/.pi/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
- package/template/.pi/skills/yss-technical-design/references/technical-design-contract.md +31 -0
- package/template/.pi/skills/yss-technical-design/references/technical-design.schema.json +759 -0
- package/template/.pi/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
- package/template/.pi/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
- package/template/.pi/skills/yss-technical-design/tests/fixtures.mjs +32 -0
- package/template/.pi/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
- package/template/.pi/skills/yss-ui/SKILL.md +2 -0
- package/template/.qoder/skills/yss-domain/SKILL.md +3 -3
- package/template/.qoder/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.qoder/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
- package/template/.qoder/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
- package/template/.qoder/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
- package/template/.qoder/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
- package/template/.qoder/skills/yss-mvc-design/SKILL.md +16 -0
- package/template/.qoder/skills/yss-mvc-design/agents/openai.yaml +4 -0
- package/template/.qoder/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
- package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +4 -2
- package/template/.qoder/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
- package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +1 -1
- package/template/.qoder/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.qoder/skills/yss-tactical-design/SKILL.md +2 -2
- package/template/.qoder/skills/yss-tactical-design/references/output-template.md +2 -0
- package/template/.qoder/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
- package/template/.qoder/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
- package/template/.qoder/skills/yss-technical-design/SKILL.md +31 -0
- package/template/.qoder/skills/yss-technical-design/agents/openai.yaml +4 -0
- package/template/.qoder/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
- package/template/.qoder/skills/yss-technical-design/references/technical-design-contract.md +31 -0
- package/template/.qoder/skills/yss-technical-design/references/technical-design.schema.json +759 -0
- package/template/.qoder/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
- package/template/.qoder/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
- package/template/.qoder/skills/yss-technical-design/tests/fixtures.mjs +32 -0
- package/template/.qoder/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
- package/template/.qoder/skills/yss-ui/SKILL.md +2 -0
- package/template/.trae/skills/yss-domain/SKILL.md +3 -3
- package/template/.trae/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
- package/template/.trae/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
- package/template/.trae/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
- package/template/.trae/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
- package/template/.trae/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
- package/template/.trae/skills/yss-mvc-design/SKILL.md +16 -0
- package/template/.trae/skills/yss-mvc-design/agents/openai.yaml +4 -0
- package/template/.trae/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
- package/template/.trae/skills/yss-product-lifecycle/SKILL.md +4 -2
- package/template/.trae/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
- package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +1 -1
- package/template/.trae/skills/yss-product-lifecycle/references/state-model.md +1 -1
- package/template/.trae/skills/yss-tactical-design/SKILL.md +2 -2
- package/template/.trae/skills/yss-tactical-design/references/output-template.md +2 -0
- package/template/.trae/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
- package/template/.trae/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
- package/template/.trae/skills/yss-technical-design/SKILL.md +31 -0
- package/template/.trae/skills/yss-technical-design/agents/openai.yaml +4 -0
- package/template/.trae/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
- package/template/.trae/skills/yss-technical-design/references/technical-design-contract.md +31 -0
- package/template/.trae/skills/yss-technical-design/references/technical-design.schema.json +759 -0
- package/template/.trae/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
- package/template/.trae/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
- package/template/.trae/skills/yss-technical-design/tests/fixtures.mjs +32 -0
- package/template/.trae/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
- package/template/.trae/skills/yss-ui/SKILL.md +2 -0
- package/template/CONTEXT.md +4 -1
- package/template/README.md +4 -0
- package/template/docs/agents/digital-human-roles.yaml +3 -2
- package/template/docs/agents/yss-skill-registry.yaml +20 -1
- package/template/docs/architecture/templates/architecture-review-checklist.md +1 -1
- package/template/docs/architecture/templates/system-overview-design-template.md +15 -4
- package/template/docs/process/frontend-backend-delivery.md +72 -0
- package/template/docs/process/implementation-repo-integration.md +2 -0
- package/template/docs/process/lifecycle-artifact-map.md +3 -0
- package/template/docs/process/lifecycle-registry-baseline.json +4 -1
- package/template/docs/process/lifecycle-registry.yaml +14 -2
- package/template/docs/process/schemas/backend-delivery-verification.schema.json +113 -0
- package/template/docs/process/schemas/backend-delivery.schema.json +301 -0
- package/template/docs/process/schemas/digital-human-task-package.schema.json +8 -0
- package/template/docs/process/schemas/frontend-delivery-acceptance.schema.json +200 -0
- package/template/docs/process/schemas/frontend-implementation-evidence.schema.json +3 -0
- package/template/docs/process/schemas/strategic-design-handoff.schema.json +77 -4
- package/template/docs/process/strategic-handoff-package.md +4 -2
- package/template/docs/process/template-verification-profiles.yaml +20 -0
- package/template/docs/user-guide//346/210/230/346/234/257/350/256/276/350/256/241/345/255/220/351/241/271/347/233/256/347/224/250/346/210/267/346/211/213/345/206/214.md +21 -206
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +63 -963
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214/347/264/242/345/274/225.md +11 -14
- package/template/docs/user-guide//350/256/276/345/244/207/345/200/237/347/224/250/350/264/257/347/251/277/346/241/210/344/276/213.md +127 -0
- package/template/scripts/backend-delivery +14 -0
- package/template/scripts/fixtures/backend-delivery/revision-server.mjs +11 -0
- package/template/scripts/fixtures/strategic-handoff/fixture.mjs +3 -2
- package/template/scripts/instantiate-harness +69 -0
- package/template/scripts/lib/backend-delivery.mjs +164 -0
- package/template/scripts/lib/frontend-delivery-boundary.mjs +49 -0
- package/template/scripts/lib/frontend-delivery.mjs +73 -0
- package/template/scripts/lib/harness-execution-scope.mjs +52 -0
- package/template/scripts/lib/implementation-contract-compiler.mjs +19 -2
- package/template/scripts/lib/lifecycle-transition.mjs +7 -0
- package/template/scripts/lib/strategic-handoff-consumption.mjs +17 -10
- package/template/scripts/lib/strategic-handoff-design-targets.mjs +15 -0
- package/template/scripts/lib/strategic-handoff.mjs +1 -1
- package/template/scripts/lib/task-package.mjs +7 -0
- package/template/scripts/lib/technical-design-boundary.mjs +23 -0
- package/template/scripts/sync-strategic-handoff-tools +5 -2
- package/template/scripts/verify-frontend-delivery +9 -0
- package/template/scripts/verify-frontend-delivery-scenarios +131 -0
- package/template/scripts/verify-frontend-implementation-evidence +2 -0
- package/template/scripts/verify-strategic-handoff-package-scenarios +7 -1
- package/template/skills-lock.json +43 -13
- package/template.manifest.json +43 -1
- package/template.snapshot.json +5 -5
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ROOT, read, safe, ensure } from './strategic-handoff-io.mjs';
|
|
4
|
+
|
|
5
|
+
export function loadDeliveryProfile(root=ROOT) {
|
|
6
|
+
const metadata=['backend','frontend'].filter(side=>existsSync(path.join(root,`.yss-harness-${side}.json`)));
|
|
7
|
+
ensure(metadata.length<=1,'专职 Harness metadata 身份冲突');
|
|
8
|
+
const profilePath=path.join(root,'docs/process/harness-profile.yaml');
|
|
9
|
+
ensure(!metadata.length||existsSync(profilePath),'专职 Harness 缺少 profile,不得绕过执行门禁');
|
|
10
|
+
const profile=existsSync(profilePath)?read(safe(root,'docs/process/harness-profile.yaml')):null;
|
|
11
|
+
if(metadata.length) {
|
|
12
|
+
const expected=`harness.${metadata[0]}-delivery`;
|
|
13
|
+
const record=read(safe(root,`.yss-harness-${metadata[0]}.json`));
|
|
14
|
+
// Runtime scope checks preserve old projects; the new CLI still refuses their adoption.
|
|
15
|
+
const newIdentity=record.metadataSchemaVersion===2 && !('schema_version' in record) && !('profile_id' in record)
|
|
16
|
+
&& record.profileId===expected && record.templateSource===`github:iloveZzz/yss-harness-${metadata[0]}-agent`
|
|
17
|
+
&& profile?.instantiation?.cli_package===`create-yss-harness-${metadata[0]}`;
|
|
18
|
+
const oldIdentity=record.schema_version===1 && !('metadataSchemaVersion' in record) && record.profile_id===expected;
|
|
19
|
+
ensure((newIdentity||oldIdentity)&&profile?.profile_id===expected,'专职 Harness metadata 与 profile 不一致');
|
|
20
|
+
}
|
|
21
|
+
return profile;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function activeProfile(root) {
|
|
25
|
+
const profile=loadDeliveryProfile(root);
|
|
26
|
+
if (!['harness.backend-delivery','harness.frontend-delivery'].includes(profile?.profile_id)) return null;
|
|
27
|
+
const identity=read(safe(root,'yss-project.yaml'));
|
|
28
|
+
ensure(identity.schema_version===1&&['template-source','project-instance'].includes(identity.repository_mode),'专职 Harness 仓库身份无效');
|
|
29
|
+
return identity.repository_mode==='project-instance'?profile:null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function enforceHarnessTaskScope(task,{root=ROOT}={}) {
|
|
33
|
+
const profile=activeProfile(root);
|
|
34
|
+
if (!profile) return;
|
|
35
|
+
ensure(task.contract?.kind!=='template-maintenance','专职产品项目不得派发模板维护任务');
|
|
36
|
+
const local=[...profile.audience.target_user_roles,...profile.audience.control_plane_roles];
|
|
37
|
+
const crossReview=['Explorer','Reviewer'].includes(task.execution_state)&&task.allowed_write_paths.length===0;
|
|
38
|
+
ensure(local.includes(task.role_id)||crossReview,'专职 Harness 不允许派发另一端的实现/起草任务');
|
|
39
|
+
const opposite=profile.profile_id==='harness.frontend-delivery'?'backend':'frontend';
|
|
40
|
+
ensure(task.allowed_write_paths.every(ref=>!new RegExp(`^apps/${opposite}(?:/|$)`).test(ref)),'专职 Harness 不允许写入另一端工程');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function enforceHarnessSkillScope(skills,registry,{root=ROOT}={}) {
|
|
44
|
+
const profile=activeProfile(root);
|
|
45
|
+
if (!profile) return;
|
|
46
|
+
const side=profile.profile_id==='harness.frontend-delivery'?'frontend':'backend';
|
|
47
|
+
const opposite=side==='frontend'?'backend':'frontend';
|
|
48
|
+
for (const id of skills) {
|
|
49
|
+
const impacts=registry.skills.find(skill=>skill.id===id)?.impacts||[];
|
|
50
|
+
ensure(!impacts.includes(opposite)||impacts.includes(side)||impacts.includes('lifecycle'),`专职 Harness 不允许编译另一端技能: ${id}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { enforceTechnicalDesign } from './technical-design-boundary.mjs';
|
|
2
|
+
import { enforceHarnessSkillScope } from "./harness-execution-scope.mjs";
|
|
1
3
|
import { createHash } from "node:crypto";
|
|
2
4
|
import { readFileSync } from "node:fs";
|
|
3
5
|
import path from "node:path";
|
|
@@ -5,6 +7,7 @@ import { parseDocument } from "../vendor/yaml.mjs";
|
|
|
5
7
|
import { DEFAULT_REGISTRY, loadSkillRegistry } from "./skill-registry.mjs";
|
|
6
8
|
import { ROOT } from "./skill-supply-chain.mjs";
|
|
7
9
|
import { architectureDigest, assertArchitectureAgreement, validateArchitectureIdentity } from "./backend-architecture.mjs";
|
|
10
|
+
import { enforceFrontendDelivery } from "./frontend-delivery-boundary.mjs";
|
|
8
11
|
|
|
9
12
|
export const DEFAULT_COMPILER_CONTRACT = path.join(
|
|
10
13
|
ROOT,
|
|
@@ -61,9 +64,14 @@ export function compileImplementationContract({
|
|
|
61
64
|
registryDigest,
|
|
62
65
|
compilerContractDigest,
|
|
63
66
|
architecture_identity,
|
|
64
|
-
architecture_evidence
|
|
67
|
+
architecture_evidence,
|
|
68
|
+
root = ROOT,
|
|
69
|
+
slice_id,
|
|
70
|
+
frontend_delivery,
|
|
71
|
+
technical_design
|
|
65
72
|
}) {
|
|
66
73
|
assertV2(registry, compilerContract);
|
|
74
|
+
const deliveryInput = enforceFrontendDelivery({ slice_id, frontend_delivery }, { root });
|
|
67
75
|
if (!Array.isArray(recipeIds) || !Array.isArray(requiredCapabilities) || !Array.isArray(conditions)) {
|
|
68
76
|
fail("recipeIds、requiredCapabilities 与 conditions 必须是数组");
|
|
69
77
|
}
|
|
@@ -85,6 +93,8 @@ export function compileImplementationContract({
|
|
|
85
93
|
for (const recipe of orderedRecipes) if (recipe.architecture_family && recipe.architecture_family !== architecture_identity.architecture_family) fail(`Recipe ${recipe.id} 与架构族不匹配`);
|
|
86
94
|
}
|
|
87
95
|
|
|
96
|
+
enforceTechnicalDesign({ technical_design, conditions, architecture_identity, slice_id }, { root });
|
|
97
|
+
|
|
88
98
|
const capabilitySources = new Map();
|
|
89
99
|
const orderedCapabilities = [];
|
|
90
100
|
const addCapability = (capabilityId, source) => {
|
|
@@ -156,10 +166,12 @@ export function compileImplementationContract({
|
|
|
156
166
|
orderedSkills.push(skill);
|
|
157
167
|
};
|
|
158
168
|
for (const root of roots) visit(root);
|
|
169
|
+
enforceHarnessSkillScope(orderedSkills, registry, { root });
|
|
159
170
|
|
|
160
171
|
return {
|
|
161
172
|
schema_version: 2,
|
|
162
173
|
status: "draft",
|
|
174
|
+
...(deliveryInput.result === "inputs-verified" ? { slice_id: deliveryInput.slice_id, frontend_delivery: { acceptance_ref: deliveryInput.acceptance_ref, digest: deliveryInput.acceptance_digest } } : {}),
|
|
163
175
|
...(architecture_identity ? {
|
|
164
176
|
architecture_identity: structuredClone(architecture_identity),
|
|
165
177
|
architecture_identity_digest: architectureDigest(architecture_identity),
|
|
@@ -167,6 +179,7 @@ export function compileImplementationContract({
|
|
|
167
179
|
readiness_blockers: architectureProfile.maturity === "supported" ? [] : ["backend-profile-not-supported"],
|
|
168
180
|
skill_profiles: Object.fromEntries(orderedSkills.map((skill) => [skill, architecture_identity.architecture_profile]))
|
|
169
181
|
} : {}),
|
|
182
|
+
...(technical_design ? { technical_design, slice_id } : {}),
|
|
170
183
|
recipe_ids: orderedRecipes.map((recipe) => recipe.id),
|
|
171
184
|
conditions: [...conditionSet].sort(),
|
|
172
185
|
required_capabilities: orderedCapabilities,
|
|
@@ -189,11 +202,15 @@ export function compileDefaultImplementationContract(input = {}) {
|
|
|
189
202
|
});
|
|
190
203
|
}
|
|
191
204
|
|
|
192
|
-
export function evaluateContractFreshness(contract, { registry, compilerContract }) {
|
|
205
|
+
export function evaluateContractFreshness(contract, { registry, compilerContract, root = ROOT }) {
|
|
193
206
|
assertV2(registry, compilerContract);
|
|
194
207
|
if (contract?.schema_version !== 2) fail("Slice Implementation Contract schema v1 已停止支持;必须重新编译 v2 合同");
|
|
195
208
|
const resolution = contract.resolution ?? contract;
|
|
196
209
|
const reasons = [];
|
|
210
|
+
try { enforceTechnicalDesign({ ...resolution, slice_id: contract.slice_id ?? resolution.slice_id }, { root }); }
|
|
211
|
+
catch { reasons.push("technical-design-stale-or-unavailable"); }
|
|
212
|
+
try { enforceFrontendDelivery(contract, { root }); }
|
|
213
|
+
catch { reasons.push("frontend-delivery-stale-or-unavailable"); }
|
|
197
214
|
if (resolution.registry_digest !== digestDocument(registry)) reasons.push("registry-digest-changed");
|
|
198
215
|
if (resolution.compiler_contract_digest !== digestDocument(compilerContract)) reasons.push("compiler-contract-digest-changed");
|
|
199
216
|
if (resolution.architecture_identity) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
3
3
|
import { assertWorkUnitUserDecision, assertImplementationDecision } from "./user-decision.mjs";
|
|
4
|
+
import { enforceFrontendDelivery } from "./frontend-delivery-boundary.mjs";
|
|
4
5
|
|
|
5
6
|
const IMPLEMENTATION_WORK_UNIT = "work-unit.slice-implementation";
|
|
6
7
|
const TICKET_DECOMPOSITION_WORK_UNIT = "work-unit.ticket-decomposition";
|
|
@@ -112,6 +113,10 @@ function validateTicketReference(ref, trackerKind) {
|
|
|
112
113
|
* by `validateWorkflowExecutionResult` before this function is called.
|
|
113
114
|
*/
|
|
114
115
|
export function validateNextRoute(currentWorkUnit, nextRoute, decisionState, options = {}) {
|
|
116
|
+
if (["work-unit.technical-analysis", TICKET_DECOMPOSITION_WORK_UNIT, IMPLEMENTATION_WORK_UNIT, "work-unit.frontend-implementation-verification"].includes(nextRoute)) {
|
|
117
|
+
try { enforceFrontendDelivery(decisionState, { root: options.root, phase: nextRoute === IMPLEMENTATION_WORK_UNIT ? "implementation" : "inputs" }); }
|
|
118
|
+
catch (error) { return blockedResult(["frontend-delivery-blocked"], [error.message]); }
|
|
119
|
+
}
|
|
115
120
|
const routes = NEXT_ROUTES[currentWorkUnit];
|
|
116
121
|
if (!routes) return blockedResult([BLOCKING_SIGNALS.invalidRoute], ["known_current_work_unit"]);
|
|
117
122
|
if (nextRoute === null && routes.length === 0) {
|
|
@@ -138,6 +143,8 @@ function validateDecisionBoundary(workUnit, state, options) {
|
|
|
138
143
|
* `exists` is injectable so external adapters can resolve their own tracker refs.
|
|
139
144
|
*/
|
|
140
145
|
export function validateTicketFormalization(state, { exists = existsSync, read = (ref) => readFileSync(ref, "utf8"), ...decisionOptions } = {}) {
|
|
146
|
+
try { enforceFrontendDelivery(state, { root: decisionOptions.root, phase: "implementation" }); }
|
|
147
|
+
catch (error) { return blockedResult(["frontend-delivery-blocked"], [error.message]); }
|
|
141
148
|
const decomposition = state?.ticket_decomposition_result;
|
|
142
149
|
const ticket = state?.vertical_slice_ticket;
|
|
143
150
|
const contract = state?.slice_contract;
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { openBundle } from './strategic-handoff.mjs';
|
|
5
5
|
import { read, safe, ensure, ROOT, digest, project } from './strategic-handoff-io.mjs';
|
|
6
|
+
import { designTargets } from './strategic-handoff-design-targets.mjs';
|
|
6
7
|
import { parseContextContract } from './context-contract.mjs';
|
|
7
8
|
const present=x=>typeof x==='string'&&x.trim();
|
|
8
9
|
const list=x=>Array.isArray(x)&&x.length&&x.every(present);
|
|
@@ -11,9 +12,11 @@ const strategyBasis=value=>({
|
|
|
11
12
|
...without(value,['domain_version','approval','rule_catalog','scenarios','invariants']),
|
|
12
13
|
invariants:(value.invariants||[]).map(item=>without(item,['statement'])),
|
|
13
14
|
});
|
|
14
|
-
export async function verifyConsumption(data,{root=process.cwd(),sliceRef}={}) {
|
|
15
|
+
export async function verifyConsumption(data,{root=process.cwd(),sliceRef,consumer='tactical'}={}) {
|
|
15
16
|
project(root);
|
|
16
|
-
|
|
17
|
+
ensure(['tactical','frontend'].includes(consumer),'未知战略消费者');
|
|
18
|
+
if (data.schema_version === 2 && consumer === 'tactical') ensure(['domain-driven','layered-mvc'].includes(data.architecture?.family), '未知技术设计架构');
|
|
19
|
+
if (sliceRef) ensure(data.status === (consumer==='frontend'?'accepted':'approved'), consumer==='frontend'?'前端接收记录尚未 accepted':'切片只能消费 approved 战术合同');
|
|
17
20
|
const binding=data.strategic_handoff;
|
|
18
21
|
ensure(binding && present(binding.import_receipt_ref) && present(binding.context_reconciliation_ref),'缺少战略交接导入收据与目标对账引用');
|
|
19
22
|
const receipt=read(safe(root,binding.import_receipt_ref));
|
|
@@ -41,18 +44,22 @@ export async function verifyConsumption(data,{root=process.cwd(),sliceRef}={}) {
|
|
|
41
44
|
const rows=new Map();for(const row of binding.rows){ensure(!rows.has(row.source_id),`重复承接: ${row.source_id}`);rows.set(row.source_id,row);}
|
|
42
45
|
const issues=[], blocked=new Set();let all=false;
|
|
43
46
|
const mark=(id,reason,row)=>{issues.push({source_id:id,reason});if(row?.dependency_status!=='known'||!list(row.dependent_slice_refs))all=true;else row.dependent_slice_refs.forEach(x=>blocked.add(x));};
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const seams=new Map((data.test_seams||[]).map(x=>[x.seam_id,x]));
|
|
47
|
+
const { ids, seams } = designTargets(data);
|
|
48
|
+
const cases=new Map((data.frontend_cases||[]).map(x=>[x.case_id,x]));
|
|
47
49
|
for(const[id,source]of known){
|
|
48
50
|
const row=rows.get(id);
|
|
49
51
|
if(!row){mark(id,'missing-mapping');continue;}
|
|
50
52
|
ensure(['known','unknown'].includes(row.dependency_status) && Array.isArray(row.dependent_slice_refs),'缺少切片依赖判定');
|
|
51
53
|
if(row.source_digest!==source.source_digest){mark(id,'stale-source',row);continue;}
|
|
52
|
-
if(row.disposition==='implemented'){
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
if(row.disposition===(consumer==='frontend'?'mapped':'implemented')){
|
|
55
|
+
if(consumer==='frontend'){
|
|
56
|
+
ensure(list(row.frontend_case_refs)&&row.frontend_case_refs.every(ref=>cases.has(ref)&&cases.get(ref).source_ids.includes(id)),`前端用例未承接源规则/场景: ${id}`);
|
|
57
|
+
if(id.startsWith('scenario.'))ensure(['success','failure'].every(outcome=>row.frontend_case_refs.some(ref=>cases.get(ref).outcome===outcome)),`前端关键场景缺少成功/失败用例: ${id}`);
|
|
58
|
+
}else{
|
|
59
|
+
ensure(list(row.tactical_refs)&&row.tactical_refs.every(x=>ids.has(x)),`战术落点悬空: ${id}`);
|
|
60
|
+
ensure(list(row.test_seam_refs)&&row.test_seam_refs.every(x=>seams.has(x)&&row.tactical_refs.includes(seams.get(x).subject_ref)),`测试 seam 未关联战术落点: ${id}`);
|
|
61
|
+
if(id.startsWith('scenario.'))ensure(['success','failure'].every(outcome=>(row.scenario_tests||[]).some(x=>x.outcome===outcome&&row.test_seam_refs.includes(x.seam_ref))),`关键场景缺少成功/失败测试: ${id}`);
|
|
62
|
+
}
|
|
56
63
|
ensure(list(row.evidence_refs),`缺少落实证据: ${id}`);row.evidence_refs.forEach(ref=>safe(root,ref));
|
|
57
64
|
}else if(row.disposition==='deferred'){
|
|
58
65
|
for(const key of ['reason','risk','owner','followup_ticket_ref','verification_plan','target_version'])ensure(present(row[key]),`延期缺少 ${key}: ${id}`);
|
|
@@ -70,7 +77,7 @@ export async function verifyConsumption(data,{root=process.cwd(),sliceRef}={}) {
|
|
|
70
77
|
all=true;issues.push({source_id:'strategic-design-basis',reason:'upstream-design-stale'});
|
|
71
78
|
}
|
|
72
79
|
const blockedHere=all||(sliceRef?blocked.has(sliceRef):issues.length>0);
|
|
73
|
-
return {result:blockedHere?'blocked':'verified',bundle_digest:current.manifest.bundle_digest,consumed_bundle_digest:receipt.bundle_digest
|
|
80
|
+
return {result:blockedHere?'blocked':'verified',bundle_digest:current.manifest.bundle_digest,consumed_bundle_digest:receipt.bundle_digest,...(consumer==='frontend'?{frontend_acceptance_digest:digest(data)}:{tactical_digest:digest(data), ...(data.schema_version === 2 ? {technical_design_digest:digest(data)} : {})}),scope:sliceRef||`whole-${consumer}-design`,coverage_complete:[...known.keys()].every(id=>rows.has(id)),block_all:all,blocked_slice_refs:[...blocked].sort(),issues};
|
|
74
81
|
});
|
|
75
82
|
});
|
|
76
83
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ensure } from './strategic-handoff-io.mjs';
|
|
2
|
+
|
|
3
|
+
export function designTargets(data) {
|
|
4
|
+
const design = data.schema_version === 2 ? data.design : data;
|
|
5
|
+
const catalogs = data.schema_version === 2 && data.architecture?.family === 'layered-mvc'
|
|
6
|
+
? ['module_catalog', 'use_case_catalog', 'rule_catalog', 'state_transition_catalog', 'persistence_mapping', 'integration_catalog']
|
|
7
|
+
: ['aggregate_catalog', 'entity_catalog', 'value_object_catalog', 'behavior_catalog', 'invariant_catalog', 'state_transition_catalog', 'domain_event_catalog', 'gateway_catalog'];
|
|
8
|
+
const primaryKeys = { module_catalog: 'module_id', use_case_catalog: 'use_case_id', rule_catalog: 'rule_id', state_transition_catalog: 'transition_id', persistence_mapping: 'mapping_id', integration_catalog: 'integration_id', aggregate_catalog: 'aggregate_id', entity_catalog: 'entity_id', value_object_catalog: 'value_object_id', behavior_catalog: 'behavior_id', invariant_catalog: 'invariant_id', domain_event_catalog: 'event_id', gateway_catalog: 'gateway_id' };
|
|
9
|
+
const ids = catalogs.flatMap(key => (design?.[key] || []).map(item => item[primaryKeys[key]]).filter(Boolean));
|
|
10
|
+
ensure(new Set(ids).size === ids.length, '设计对象 ID 重复');
|
|
11
|
+
const seams = design?.test_seams || [];
|
|
12
|
+
ensure(new Set(seams.map(item => item.seam_id)).size === seams.length, '测试 seam ID 重复');
|
|
13
|
+
return { ids: new Set(ids), seams: new Map(seams.map(item => [item.seam_id, item])) };
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -13,7 +13,7 @@ const own = (a,b) => JSON.stringify(canonical(a)) === JSON.stringify(canonical(b
|
|
|
13
13
|
const nonempty = x => typeof x === 'string' && x.trim();
|
|
14
14
|
const bytesDigest = (bytes, kind) => kind === 'canonical-json' ? digest(parse(bytes)) : kind === 'sha256-bytes' ? hash(bytes) : (() => { throw new TypeError(`未知摘要算法: ${kind}`); })();
|
|
15
15
|
|
|
16
|
-
async function sourceApproval(record, roles, root) {
|
|
16
|
+
export async function sourceApproval(record, roles, root) {
|
|
17
17
|
const options={rolesDoc:roles,requireApproved:true,root,read:ref=>readFileSync(safe(root,path.relative(root,ref).split(path.sep).join('/')))};
|
|
18
18
|
if (roles.user_decision_policy.gates.includes(record.gate_id)) {
|
|
19
19
|
ensure(existsSync(path.join(ROOT,'scripts/lib/user-decision.mjs')), '接收工具不支持源用户决定策略,请升级工具后验包');
|
|
@@ -6,6 +6,8 @@ import { loadRegistry, ROOT } from "./lifecycle-registry.mjs";
|
|
|
6
6
|
import { loadMaintenanceCheckpoint, validateMaintenanceCheckpoint } from "./maintenance-intensity.mjs";
|
|
7
7
|
import { assertImplementationDecision } from "./user-decision.mjs";
|
|
8
8
|
import { validateNextRoute } from "./lifecycle-transition.mjs";
|
|
9
|
+
import { enforceHarnessTaskScope } from "./harness-execution-scope.mjs";
|
|
10
|
+
import { enforceFrontendDelivery } from "./frontend-delivery-boundary.mjs";
|
|
9
11
|
import { LEGACY_TASK_PACKAGE_SCHEMA, TASK_PACKAGE_SCHEMA, validateTaskPackageSchema } from "./task-package-schema.mjs";
|
|
10
12
|
|
|
11
13
|
export { LEGACY_TASK_PACKAGE_SCHEMA, TASK_PACKAGE_SCHEMA, validateTaskPackageSchema };
|
|
@@ -181,6 +183,11 @@ function validateContract(value, registry, lifecycle) {
|
|
|
181
183
|
|
|
182
184
|
export function validateTaskPackage(value, { rolesDoc, lifecycleDoc } = {}) {
|
|
183
185
|
validateTaskPackageSchema(value);
|
|
186
|
+
enforceHarnessTaskScope(value);
|
|
187
|
+
const intake = value.execution_state === "Explorer" && value.allowed_write_paths.length === 0 && ["work-unit.entry-triage", "work-unit.harness-entry"].includes(value.work_unit_id);
|
|
188
|
+
if (!intake) {
|
|
189
|
+
enforceFrontendDelivery(value, { phase: value.contract.kind === "slice-implementation" ? "implementation" : "inputs" });
|
|
190
|
+
}
|
|
184
191
|
const registry = rolesDoc || loadDigitalHumanRoles();
|
|
185
192
|
const lifecycle = lifecycleDoc || loadRegistry();
|
|
186
193
|
validateCommon(value, registry, lifecycle);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { ROOT, read, safe, ensure, hash } from './strategic-handoff-io.mjs';
|
|
5
|
+
|
|
6
|
+
// The compiler's synchronous boundary reruns the design verifier against current files.
|
|
7
|
+
export function enforceTechnicalDesign(state = {}, { root = ROOT } = {}) {
|
|
8
|
+
const binding = state.technical_design;
|
|
9
|
+
const affected = (state.conditions || []).some(value => ['backend-technical-design-impact','tactical-domain','tactical-domain-impact','aggregate-impact','state-machine-impact','invariant-impact','consistency-impact','domain-event-impact','gateway-boundary-impact','persistence-mapping-impact'].includes(value));
|
|
10
|
+
if (!binding && !affected) return;
|
|
11
|
+
ensure(binding?.ref && binding?.digest && binding?.version, '缺少已批准技术设计绑定');
|
|
12
|
+
ensure(state.slice_id, '技术设计消费必须绑定切片 ID');
|
|
13
|
+
const file = safe(root, binding.ref);
|
|
14
|
+
ensure(hash(readFileSync(file)) === binding.digest, '技术设计绑定摘要漂移');
|
|
15
|
+
const data = read(file);
|
|
16
|
+
ensure(data.status === 'approved' && data.version === binding.version, '技术设计必须为批准且绑定版本');
|
|
17
|
+
const family = data.schema_version === 1 && binding.legacy_ddd === true ? 'domain-driven' : data.architecture?.family;
|
|
18
|
+
ensure(family && family === state.architecture_identity?.architecture_family, '技术设计与实现架构不匹配');
|
|
19
|
+
const args = [path.join(ROOT, '.agents/skills/yss-technical-design/scripts/validate-technical-design.mjs'), file, '--root', root, '--slice', state.slice_id];
|
|
20
|
+
if (binding.legacy_ddd === true) args.push('--legacy-ddd');
|
|
21
|
+
const verified = spawnSync(process.execPath, args, { cwd: root, encoding: 'utf8', timeout: 60000, maxBuffer: 2 * 1024 * 1024 });
|
|
22
|
+
ensure(verified.status === 0, `技术设计不可消费: ${verified.error?.message || verified.stderr}`);
|
|
23
|
+
}
|
|
@@ -5,14 +5,17 @@ import path from 'node:path';
|
|
|
5
5
|
import { inspectWorkingTreeScope } from './lib/repository-scope-policy.mjs';
|
|
6
6
|
const root=path.resolve(import.meta.dirname,'..');
|
|
7
7
|
const check=process.argv.includes('--check');
|
|
8
|
-
const targets=['submodules/yss-harness-design-agent','submodules/yss-harness-dev-agent'];
|
|
8
|
+
const targets=['submodules/yss-harness-design-agent','submodules/yss-harness-dev-agent','submodules/yss-harness-backend-agent','submodules/yss-harness-frontend-agent'];
|
|
9
9
|
const shared=['scripts/strategic-handoff','scripts/verify-strategic-handoff-consumption','scripts/verify-strategic-handoff-package-scenarios','scripts/verify-strategic-context-import','scripts/verify-strategic-context-import-scenarios','scripts/lib/context-contract.mjs','docs/process/strategic-handoff-package.md','docs/process/schemas/strategic-design-handoff.schema.json',...readdirSync(path.join(root,'scripts/lib')).filter(x=>x.startsWith('strategic-handoff-')||x==='strategic-handoff.mjs').map(x=>`scripts/lib/${x}`),...readdirSync(path.join(root,'docs/process/schemas')).filter(x=>x.startsWith('strategic-handoff-')).map(x=>`docs/process/schemas/${x}`),...readdirSync(path.join(root,'scripts/fixtures/strategic-handoff')).map(x=>`scripts/fixtures/strategic-handoff/${x}`)];
|
|
10
|
+
shared.push('scripts/instantiate-harness','scripts/backend-delivery','scripts/verify-frontend-delivery','scripts/lib/backend-delivery.mjs','scripts/lib/frontend-delivery.mjs','scripts/lib/frontend-delivery-boundary.mjs','docs/process/frontend-backend-delivery.md','docs/process/schemas/backend-delivery.schema.json','docs/process/schemas/backend-delivery-verification.schema.json','docs/process/schemas/frontend-delivery-acceptance.schema.json');
|
|
10
11
|
let differences=[];
|
|
11
|
-
|
|
12
|
+
// 专职执行范围由各模板持有;后端只消费 wire 校验器,不恢复原型构建技能。
|
|
13
|
+
function sync(source,dest){let bytes=readFileSync(source);if(dest.includes('/yss-harness-backend-agent/')){let content=bytes.toString();if(dest.endsWith('/scripts/lib/strategic-handoff.mjs'))content=content.replace('../../.agents/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs','./visual-baseline-contract.mjs');if(dest.endsWith('/scripts/fixtures/strategic-handoff/fixture.mjs'))content=content.replace('../../../.agents/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs','../../lib/visual-baseline-contract.mjs');bytes=Buffer.from(content);}if(!existsSync(dest)||!bytes.equals(readFileSync(dest))){differences.push(path.relative(root,dest));if(!check){mkdirSync(path.dirname(dest),{recursive:true});writeFileSync(dest,bytes);}}if(!check&&path.basename(path.dirname(dest))==='scripts'&&!path.extname(dest))chmodSync(dest,0o755);}
|
|
12
14
|
for(const kind of ['domain-strategy','stage-decision-package'])sync(path.join(root,`.agents/skills/yss-stage-decision/references/${kind}.schema.json`),path.join(root,`docs/process/schemas/strategic-handoff-${kind}.schema.json`));
|
|
13
15
|
for(const target of targets){
|
|
14
16
|
if(!existsSync(path.join(root,target,'.git')))continue;
|
|
15
17
|
if(!check){const state=inspectWorkingTreeScope(root,{project_root:target,repository_scope:'git-submodule',checkout_state:'attached-branch'});if(!state.writable)throw new Error(`${target}: ${state.violation}`);}
|
|
18
|
+
if(!target.endsWith('design-agent'))for(const ref of ['scripts/verify-frontend-delivery-scenarios','scripts/fixtures/backend-delivery/revision-server.mjs'])sync(path.join(root,ref),path.join(root,target,ref));
|
|
16
19
|
for(const ref of new Set(shared))sync(path.join(root,ref),path.join(root,target,ref));
|
|
17
20
|
}
|
|
18
21
|
if(check&&differences.length){process.stderr.write(`交接工具同步漂移: ${differences.join(', ')}\n`);process.exitCode=1;}else process.stdout.write(`战略交接共享工具 ${check?'同步检查通过':`同步 ${differences.length} 文件`}\n`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { verifyFrontendDelivery } from './lib/frontend-delivery.mjs';
|
|
4
|
+
try {
|
|
5
|
+
const {values,positionals}=parseArgs({allowPositionals:true,options:{root:{type:'string',default:process.cwd()},slice:{type:'string'},'expected-digest':{type:'string'}}});
|
|
6
|
+
if(positionals.length!==1||!values.slice)throw new TypeError('用法: verify-frontend-delivery --root <repo> --slice <slice-id> [--expected-digest <sha256>] <acceptance-ref>');
|
|
7
|
+
const result=await verifyFrontendDelivery({root:values.root,sliceRef:values.slice,expectedDigest:values['expected-digest'],acceptanceRef:positionals[0]});
|
|
8
|
+
process.stdout.write(`${JSON.stringify(result,null,2)}\n`);
|
|
9
|
+
} catch(error) { process.stderr.write(`${JSON.stringify({result:'blocked',error:error.message})}\n`);process.exitCode=1; }
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { fork } from 'node:child_process';
|
|
5
|
+
import { once } from 'node:events';
|
|
6
|
+
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, cpSync, existsSync } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { fixture, context } from './fixtures/strategic-handoff/fixture.mjs';
|
|
11
|
+
import { exportBundle } from './lib/strategic-handoff.mjs';
|
|
12
|
+
const backendTools=process.env.YSS_DELIVERY_TEST_BACKEND_ROOT;
|
|
13
|
+
const frontendTools=process.env.YSS_DELIVERY_TEST_FRONTEND_ROOT;
|
|
14
|
+
const { backendDeliveryBasis, exportBackendDelivery, openBackendDelivery, inspectBackendDelivery } = await import(backendTools?pathToFileURL(path.join(backendTools,'scripts/lib/backend-delivery.mjs')):'./lib/backend-delivery.mjs');
|
|
15
|
+
const {importBackendDelivery}=await import(frontendTools?pathToFileURL(path.join(frontendTools,'scripts/lib/backend-delivery.mjs')):'./lib/backend-delivery.mjs');
|
|
16
|
+
const { verifyFrontendDelivery } = await import(frontendTools?pathToFileURL(path.join(frontendTools,'scripts/lib/frontend-delivery.mjs')):'./lib/frontend-delivery.mjs');
|
|
17
|
+
const { enforceFrontendDelivery } = await import(frontendTools?pathToFileURL(path.join(frontendTools,'scripts/lib/frontend-delivery-boundary.mjs')):'./lib/frontend-delivery-boundary.mjs');
|
|
18
|
+
const { compileDefaultImplementationContract } = await import(frontendTools?pathToFileURL(path.join(frontendTools,'scripts/lib/implementation-contract-compiler.mjs')):'./lib/implementation-contract-compiler.mjs');
|
|
19
|
+
const { validateNextRoute, validateImplementationEntry } = await import(frontendTools?pathToFileURL(path.join(frontendTools,'scripts/lib/lifecycle-transition.mjs')):'./lib/lifecycle-transition.mjs');
|
|
20
|
+
import { read, hash, json, ROOT } from './lib/strategic-handoff-io.mjs';
|
|
21
|
+
const runtimeRegistry=read(path.join(frontendTools||ROOT,'docs/process/lifecycle-registry.yaml'));
|
|
22
|
+
const frontendStage=runtimeRegistry.stages.find(x=>x.id==='stage.frontend-engineering-design')?.id||runtimeRegistry.stages.find(x=>x.id==='stage.tactical-design')?.id||'stage.system-data-engineering';
|
|
23
|
+
const frontendWorkUnit=frontendStage==='stage.frontend-engineering-design'?'work-unit.frontend-engineering-design':frontendStage==='stage.tactical-design'?'work-unit.tactical-design':'work-unit.technical-analysis';
|
|
24
|
+
|
|
25
|
+
test('战略与后端交付联合接收:便携、真实服务、拒绝不完整/过期输入及执行入口绕过',async t=>{
|
|
26
|
+
const base=mkdtempSync(path.join(tmpdir(),'frontend-delivery-test-'));
|
|
27
|
+
const put=(root,ref,value)=>{mkdirSync(path.dirname(path.join(root,ref)),{recursive:true});writeFileSync(path.join(root,ref),typeof value==='string'?value:json(value));};
|
|
28
|
+
const source=path.join(base,'strategy'), backend=path.join(base,'backend'), frontend=path.join(base,'frontend');
|
|
29
|
+
[source,backend,frontend].forEach(root=>mkdirSync(root));
|
|
30
|
+
if(backendTools)cpSync(backendTools,backend,{recursive:true});
|
|
31
|
+
if(frontendTools)cpSync(frontendTools,frontend,{recursive:true});
|
|
32
|
+
const f=await fixture(source);
|
|
33
|
+
for(const root of [backend,frontend]) {put(root,'yss-project.yaml','schema_version: 1\nrepository_mode: project-instance\n');put(root,'CONTEXT.md',context);}
|
|
34
|
+
const strategic=await exportBundle({sourceRoot:source,handoffRef:'handoff.yaml',output:path.join(backend,'strategy-package')});
|
|
35
|
+
const roles=read(path.join(source,'docs/agents/digital-human-roles.yaml'));
|
|
36
|
+
for(const gate of ['gate.openapi-frozen','gate.slice-contract-approved']) {
|
|
37
|
+
roles.gate_policy.biological_human=(roles.gate_policy.biological_human||[]).filter(x=>x!==gate);
|
|
38
|
+
roles.gate_policy.digital_human_review=(roles.gate_policy.digital_human_review||[]).filter(x=>x.gate!==gate);
|
|
39
|
+
roles.gate_policy.digital_human_review.push({gate,countersigners:['role.product-manager']});
|
|
40
|
+
}
|
|
41
|
+
put(backend,'docs/agents/digital-human-roles.yaml',roles);
|
|
42
|
+
put(backend,'api.yaml',{openapi:'3.1.0',info:{title:'Synthetic supplier API',version:'1'},paths:{'/suppliers':{post:{operationId:'submitSupplier',responses:{'200':{description:'OK'}}}}}});
|
|
43
|
+
put(backend,'slice.json',{schema_version:2,contract_id:'contract.backend',contract_version:'v1',slice_id:'slice.submit',status:'approved'});
|
|
44
|
+
put(backend,'data.md','Synthetic data setup for maintenance tests, not product verification.');
|
|
45
|
+
put(backend,'verification.log','Synthetic test fixture: successful/failed submissions exercised.');
|
|
46
|
+
const file=ref=>({ref,digest:hash(readFileSync(path.join(backend,ref)))});
|
|
47
|
+
const approved=(ref,id,gate)=>{
|
|
48
|
+
const binding={...file(ref),id,version:'v1',approval_ref:`approvals/${id}.json`};
|
|
49
|
+
put(backend,binding.approval_ref,{schema_version:1,gate_id:gate,decision:'approved',actor_kind:'digital-human',role_id:'role.product-manager',runtime_id:'runtime.generic',principal_ref:'synthetic-maintenance-fixture',artifact_bindings:[{id,version:'v1',digest:binding.digest}]});return binding;
|
|
50
|
+
};
|
|
51
|
+
const serverState=path.join(base,'server.json');put(base,'server.json',{body:{}});
|
|
52
|
+
const server=fork(path.join(ROOT,'scripts/fixtures/backend-delivery/revision-server.mjs'),[serverState],{stdio:['ignore','ignore','inherit','ipc']});
|
|
53
|
+
t.after(()=>{server.kill();rmSync(base,{recursive:true,force:true});});
|
|
54
|
+
const [{port}]=await once(server,'message');
|
|
55
|
+
const delivery={schema_version:1,delivery_id:'backend-delivery.supplier',version:'v1',status:'verified',strategic_bundle_ref:'strategy-package',strategic_bundle_digest:strategic.bundle_digest,scope:{slice_id:'slice.submit',source_ids:['rule.complete','scenario.submit'],operation_ids:['submitSupplier']},openapi:approved('api.yaml','api.supplier','gate.openapi-frozen'),slice_contract:approved('slice.json','contract.backend','gate.slice-contract-approved'),build:{source_commit:'a'.repeat(40),artifact_digest:`sha256:${'b'.repeat(64)}`},environment:{id:'fixture-local',base_url:`http://127.0.0.1:${port}`,deployment_id:'fixture-v1',revision_path:'/version',revision_pointers:{deployment_id:'/deployment_id',source_commit:'/source_commit',openapi_digest:'/openapi_digest',artifact_digest:'/artifact_digest',test_data_digest:'/test_data_digest'},test_data:file('data.md')},verification:{},supporting_files:[]};
|
|
56
|
+
const record=kind=>({schema_version:1,kind,subject_digest:backendDeliveryBasis(delivery),results:[{command:'synthetic-maintenance-test',executed_at:new Date().toISOString(),exit_code:0,evidence:[file('verification.log')]}],operation_ids:['submitSupplier'],coverage:[{source_id:'scenario.submit',outcome:'success'},{source_id:'scenario.submit',outcome:'failure'}]});
|
|
57
|
+
put(backend,'contract-test.json',record('backend-contract'));put(backend,'deployment-test.json',record('backend-deployment'));
|
|
58
|
+
delivery.verification={contract:file('contract-test.json'),deployment:file('deployment-test.json')};put(backend,'delivery.json',delivery);
|
|
59
|
+
const body={deployment_id:'fixture-v1',source_commit:delivery.build.source_commit,openapi_digest:delivery.openapi.digest,artifact_digest:delivery.build.artifact_digest,test_data_digest:delivery.environment.test_data.digest};
|
|
60
|
+
put(base,'server.json',{body});
|
|
61
|
+
await t.test('导出拒绝缺失场景、篡改接口及错误批准',async()=>{
|
|
62
|
+
const bytes=readFileSync(path.join(backend,'api.yaml'));put(backend,'api.yaml','changed');
|
|
63
|
+
await assert.rejects(()=>inspectBackendDelivery(backend,'delivery.json'),/摘要不一致/);writeFileSync(path.join(backend,'api.yaml'),bytes);
|
|
64
|
+
const approval=read(path.join(backend,delivery.openapi.approval_ref));put(backend,delivery.openapi.approval_ref,{...approval,artifact_bindings:[]});
|
|
65
|
+
await assert.rejects(()=>inspectBackendDelivery(backend,'delivery.json'),/批准未绑定/);put(backend,delivery.openapi.approval_ref,approval);
|
|
66
|
+
const tests=record('backend-contract');tests.coverage.pop();put(backend,'contract-test.json',tests);
|
|
67
|
+
const broken=structuredClone(delivery);broken.verification.contract=file('contract-test.json');put(backend,'delivery.json',broken);
|
|
68
|
+
await assert.rejects(()=>inspectBackendDelivery(backend,'delivery.json'),/成功\/失败/);
|
|
69
|
+
put(backend,'contract-test.json',record('backend-contract'));delivery.verification.contract=file('contract-test.json');put(backend,'delivery.json',delivery);
|
|
70
|
+
});
|
|
71
|
+
const output=path.join(base,'package');await exportBackendDelivery({sourceRoot:backend,deliveryRef:'delivery.json',output,zip:true});
|
|
72
|
+
const imported=await importBackendDelivery({bundle:`${output}.zip`,targetRoot:frontend});
|
|
73
|
+
assert.equal((await importBackendDelivery({bundle:output,targetRoot:frontend})).result,'already-imported');
|
|
74
|
+
const acceptance=read(path.join(frontend,imported.acceptance_ref));
|
|
75
|
+
await t.test('草案/缺少两类产物不能进入前端任务',async()=>{
|
|
76
|
+
await assert.rejects(()=>verifyFrontendDelivery({root:frontend,acceptanceRef:imported.acceptance_ref,sliceRef:'slice.submit'}),/schema/);
|
|
77
|
+
const profileRef=path.join(frontend,'docs/process/harness-profile.yaml');
|
|
78
|
+
const profile=existsSync(profileRef)?read(profileRef):{schema_version:1,profile_id:'harness.frontend-delivery'};
|
|
79
|
+
put(frontend,'docs/process/harness-profile.yaml',{...profile,frontend_delivery:{required:true}});
|
|
80
|
+
assert.throws(()=>enforceFrontendDelivery({}, {root:frontend}),/frontend-delivery-required/);
|
|
81
|
+
assert.throws(()=>compileDefaultImplementationContract({root:frontend}),/frontend-delivery-required/);
|
|
82
|
+
assert.throws(()=>enforceFrontendDelivery({contract:{kind:'template-maintenance'}},{root:frontend}),/不得用模板维护/);
|
|
83
|
+
assert.equal(validateNextRoute(frontendWorkUnit==='work-unit.technical-analysis'?'work-unit.spec-synthesis':'work-unit.harness-entry',frontendWorkUnit,undefined,{root:frontend}).result,'blocked');
|
|
84
|
+
assert.equal(validateImplementationEntry({}, {root:frontend}).blocking_signals[0],'frontend-delivery-blocked');
|
|
85
|
+
});
|
|
86
|
+
put(frontend,'reconciliation.json',{schema_version:1,repository_mode:'project-instance',stage:frontendStage,work_unit:frontendWorkUnit,status:'reconciled',context_snapshot:f.snapshot,changes:{added:['Global/Supplier'],updated:[],deprecated:[]},unresolved_terms:[],evidence_refs:['CONTEXT.md']});
|
|
87
|
+
put(frontend,'cases.md','Synthetic planned UI cases: complete submission succeeds; incomplete submission fails.');
|
|
88
|
+
acceptance.status='accepted';acceptance.strategic_handoff.context_reconciliation_ref='reconciliation.json';
|
|
89
|
+
acceptance.frontend_cases=['success','failure'].map(outcome=>({case_id:`submit-${outcome}`,source_ids:['rule.complete','scenario.submit'],outcome,operation_ids:['submitSupplier'],visual_case_ids:['primary-desktop','primary-narrow'],evidence_ref:'cases.md',evidence_digest:hash(readFileSync(path.join(frontend,'cases.md')))}));
|
|
90
|
+
for(const row of acceptance.strategic_handoff.rows)Object.assign(row,{disposition:'mapped',frontend_case_refs:acceptance.frontend_cases.map(x=>x.case_id),dependency_status:'known',dependent_slice_refs:['slice.submit'],evidence_refs:['cases.md']});
|
|
91
|
+
put(frontend,imported.acceptance_ref,acceptance);
|
|
92
|
+
const check=()=>verifyFrontendDelivery({root:frontend,acceptanceRef:imported.acceptance_ref,sliceRef:'slice.submit'});
|
|
93
|
+
await t.test('源仓消失后可验 ZIP 并独立恢复;输入就绪不授予代码实施',async()=>{
|
|
94
|
+
rmSync(source,{recursive:true});rmSync(backend,{recursive:true});
|
|
95
|
+
assert.equal(await openBackendDelivery(`${output}.zip`,b=>b.delivery.scope.slice_id),'slice.submit');
|
|
96
|
+
const verified=await check();assert.equal(verified.result,'inputs-verified');assert.equal(verified.ready_for_agent,false);
|
|
97
|
+
assert.equal(verified.actual_verification.observed.deployment_id,'fixture-v1');
|
|
98
|
+
const draft=compileDefaultImplementationContract({root:frontend,slice_id:'slice.submit',frontend_delivery:{acceptance_ref:imported.acceptance_ref},requiredCapabilities:['governance.implementation-contract']});
|
|
99
|
+
assert.equal(draft.status,'draft');assert.equal(draft.slice_id,'slice.submit');assert.equal(draft.frontend_delivery.digest,verified.acceptance_digest);
|
|
100
|
+
assert.equal(enforceFrontendDelivery({slice_id:'slice.submit',frontend_delivery:{acceptance_ref:imported.acceptance_ref}}, {root:frontend}).result,'inputs-verified');
|
|
101
|
+
assert.throws(()=>enforceFrontendDelivery({slice_id:'slice.submit',frontend_delivery:{acceptance_ref:imported.acceptance_ref}}, {root:frontend,phase:'implementation'}),/approved Slice Contract/);
|
|
102
|
+
});
|
|
103
|
+
await t.test('实现边界读取持久化合同并冻结接收记录及用例字节',async()=>{
|
|
104
|
+
const binding={acceptance_ref:imported.acceptance_ref,digest:hash(readFileSync(path.join(frontend,imported.acceptance_ref)))};
|
|
105
|
+
put(frontend,'frontend-contract.json',{status:'approved',slice_id:'slice.submit',frontend:{delivery:binding}});
|
|
106
|
+
assert.equal(enforceFrontendDelivery({slice_contract_ref:'frontend-contract.json'},{root:frontend,phase:'implementation'}).result,'inputs-verified');
|
|
107
|
+
put(frontend,'cases.md','changed case');await assert.rejects(check,/摘要漂移/);
|
|
108
|
+
put(frontend,'cases.md','Synthetic planned UI cases: complete submission succeeds; incomplete submission fails.');
|
|
109
|
+
put(frontend,'frontend-contract.json',{status:'draft',slice_id:'slice.submit',frontend:{delivery:binding}});
|
|
110
|
+
assert.throws(()=>enforceFrontendDelivery({slice_contract_ref:'frontend-contract.json'},{root:frontend,phase:'implementation'}),/approved Slice Contract/);
|
|
111
|
+
});
|
|
112
|
+
await t.test('部署版本/数据失配与服务失败使旧接收记录失效',async()=>{
|
|
113
|
+
for(const field of ['deployment_id','source_commit','openapi_digest','artifact_digest','test_data_digest']) {
|
|
114
|
+
put(base,'server.json',{body:{...body,[field]:'wrong'}});await assert.rejects(check,/真实服务版本不匹配/);
|
|
115
|
+
}
|
|
116
|
+
put(base,'server.json',{status:503,body});await assert.rejects(check,/真实接口不可用/);put(base,'server.json',{body});
|
|
117
|
+
});
|
|
118
|
+
await t.test('范围、摘要和逐条承接缺口均拒绝',async()=>{
|
|
119
|
+
await assert.rejects(()=>verifyFrontendDelivery({root:frontend,acceptanceRef:imported.acceptance_ref,sliceRef:'slice.other'}),/切片不匹配/);
|
|
120
|
+
await assert.rejects(()=>verifyFrontendDelivery({root:frontend,acceptanceRef:imported.acceptance_ref,sliceRef:'slice.submit',expectedDigest:`sha256:${'0'.repeat(64)}`}),/摘要已变化/);
|
|
121
|
+
for(const mutate of [a=>a.frontend_cases.pop(),a=>a.strategic_handoff.rows[0].disposition='pending',a=>a.frontend_cases[0].operation_ids=['unknown'],a=>a.strategic_handoff.bundle_digest=`sha256:${'0'.repeat(64)}`]) {
|
|
122
|
+
const changed=structuredClone(acceptance);mutate(changed);put(frontend,imported.acceptance_ref,changed);await assert.rejects(check);
|
|
123
|
+
}
|
|
124
|
+
put(frontend,imported.acceptance_ref,acceptance);
|
|
125
|
+
});
|
|
126
|
+
await t.test('已导入更新版本阻断旧切片;包篡改拒绝',async()=>{
|
|
127
|
+
const receipt=read(path.join(frontend,imported.receipt_ref));const newer=path.posix.dirname(path.posix.dirname(imported.receipt_ref))+'/v2';
|
|
128
|
+
put(frontend,`${newer}/import-receipt.json`,{...receipt,version:'v2'});await assert.rejects(check,/旧前端接收记录 stale/);rmSync(path.join(frontend,newer),{recursive:true});
|
|
129
|
+
const changed=path.join(base,'tampered');cpSync(output,changed,{recursive:true});put(changed,'extra.txt','extra');await assert.rejects(()=>openBackendDelivery(changed,()=>null),/未登记文件/);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -6,6 +6,7 @@ import { parseArgs } from "node:util";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { parseDocument } from "./vendor/yaml.mjs";
|
|
8
8
|
import { validateVisualBaseline } from "../.agents/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs";
|
|
9
|
+
import { enforceFrontendDelivery } from "./lib/frontend-delivery-boundary.mjs";
|
|
9
10
|
|
|
10
11
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
12
|
const schemaPath = path.join(repoRoot, "docs/process/schemas/frontend-implementation-evidence.schema.json");
|
|
@@ -29,6 +30,7 @@ try {
|
|
|
29
30
|
const document = parseDocument(readFileSync(path.resolve(positionals[0]), "utf8"), { maxAliasCount: 0, uniqueKeys: true });
|
|
30
31
|
if (document.errors.length) throw new TypeError(document.errors[0].message);
|
|
31
32
|
const value = document.toJS({ maxAliasCount: 0 });
|
|
33
|
+
if (!values["allow-template"]) enforceFrontendDelivery(value, { root, phase: value.kind === "verification" ? "implementation" : "inputs" });
|
|
32
34
|
if (value.schema_version === 1 && !values["allow-template"]) throw new TypeError("migration-required: frontend implementation evidence v1 是只读历史资产;在途 UI 工作必须升级 v2 并绑定 Visual Baseline case_id");
|
|
33
35
|
const result = spawnSync("python3", ["-c", validator, schemaPath], { cwd: repoRoot, encoding: "utf8", input: JSON.stringify(value) });
|
|
34
36
|
if (result.status !== 0) throw new TypeError(`${result.stdout}${result.stderr}`.trim());
|
|
@@ -9,7 +9,7 @@ import { fixture, context } from './fixtures/strategic-handoff/fixture.mjs';
|
|
|
9
9
|
import { exportBundle, openBundle, importBundle } from './lib/strategic-handoff.mjs';
|
|
10
10
|
import { read, json, hash, digest } from './lib/strategic-handoff-io.mjs';
|
|
11
11
|
import { parseContextContract } from './lib/context-contract.mjs';
|
|
12
|
-
const setup=async t=>{const base=mkdtempSync(path.join(tmpdir(),'handoff-test-'));t.after(()=>rmSync(base,{recursive:true,force:true}));const source=path.join(base,'source');mkdirSync(source);return{base,source,...await fixture(source)};};
|
|
12
|
+
const setup=async t=>{const base=mkdtempSync(path.join(tmpdir(),'handoff-test-'));t.after(()=>rmSync(base,{recursive:true,force:true}));const source=path.join(base,'source');mkdirSync(source);return{base,source,...await fixture(source,{technicalDesign:true})};};
|
|
13
13
|
function advanceStrategy(f,version='v2') {
|
|
14
14
|
f.handoff.handoff_version=version;
|
|
15
15
|
f.strategy.domain_version=version;
|
|
@@ -79,6 +79,12 @@ test('受控延期只阻断依赖切片;未知依赖扩大阻断;目标未
|
|
|
79
79
|
writeFileSync(path.join(target,'reconciliation.yaml'),json(record));
|
|
80
80
|
for(const row of tactical.strategic_handoff.rows)Object.assign(row,{disposition:'implemented',tactical_refs:['behavior.submit'],test_seam_refs:['test-seam.submit'],scenario_tests:[{outcome:'success',seam_ref:'test-seam.submit'},{outcome:'failure',seam_ref:'test-seam.submit'}],evidence_refs:['CONTEXT.md'],dependency_status:'known',dependent_slice_refs:['slice.submit']});
|
|
81
81
|
assert.equal((await verifyConsumption(tactical,{root:target})).result,'verified');
|
|
82
|
+
const mvc={schema_version:2,status:'approved',architecture:{family:'layered-mvc'},strategic_handoff:structuredClone(tactical.strategic_handoff),design:{use_case_catalog:[{use_case_id:'use-case.submit'}],test_seams:[{seam_id:'test-seam.submit',subject_ref:'use-case.submit'}]}};
|
|
83
|
+
for(const row of mvc.strategic_handoff.rows)row.tactical_refs=['use-case.submit'];
|
|
84
|
+
assert.equal((await verifyConsumption(mvc,{root:target})).result,'verified');
|
|
85
|
+
mvc.strategic_handoff.rows[0].tactical_refs=['aggregate.fake'];
|
|
86
|
+
await assert.rejects(()=>verifyConsumption(mvc,{root:target}),/落点悬空/);
|
|
87
|
+
|
|
82
88
|
const row=tactical.strategic_handoff.rows[0];Object.assign(row,{disposition:'deferred',reason:'分批实现',risk:'不能提交',owner:'需求负责人',followup_ticket_ref:'ticket.followup',verification_plan:'提交测试',target_version:'v2'});
|
|
83
89
|
assert.equal((await verifyConsumption(tactical,{root:target,sliceRef:'slice.submit'})).result,'blocked');
|
|
84
90
|
assert.equal((await verifyConsumption(tactical,{root:target,sliceRef:'slice.other'})).result,'verified');
|