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.
Files changed (302) hide show
  1. package/README.md +119 -89
  2. package/bin/create-yss-spec.js +11 -3
  3. package/package.json +9 -2
  4. package/src/api/_sync-service.js +191 -0
  5. package/src/api/index.js +22 -0
  6. package/src/api/project-diff.js +9 -0
  7. package/src/api/project-doctor.js +10 -0
  8. package/src/api/template-apply.js +10 -0
  9. package/src/api/template-plan.js +9 -0
  10. package/src/cli/args.js +60 -0
  11. package/src/cli/error-output.js +52 -0
  12. package/src/cli/flags.js +9 -0
  13. package/src/cli/help.js +95 -0
  14. package/src/cli/index.js +82 -0
  15. package/src/cli/plan-output.js +61 -0
  16. package/src/cli/prompts.js +91 -0
  17. package/src/cli/router.js +41 -0
  18. package/src/cli.js +5 -2253
  19. package/src/commands/attach.js +280 -0
  20. package/src/commands/diff.js +23 -0
  21. package/src/commands/doctor.js +417 -0
  22. package/src/commands/init.js +193 -0
  23. package/src/commands/sync.js +146 -0
  24. package/src/commands/update.js +14 -0
  25. package/src/contracts/apply-result-v1.json +45 -0
  26. package/src/contracts/customization-policy-v1.json +28 -0
  27. package/src/contracts/doctor-report-v1.json +29 -0
  28. package/src/contracts/error-envelope-v1.json +24 -0
  29. package/src/contracts/generator-policy-v1.json +24 -0
  30. package/src/contracts/ownership-policy-v1.json +40 -0
  31. package/src/contracts/plan-schema-v1.json +115 -0
  32. package/src/family-identity.js +365 -0
  33. package/src/family-runtime.js +23 -0
  34. package/src/filesystem/apply-plan.js +44 -0
  35. package/src/filesystem/copy-path.js +28 -0
  36. package/src/filesystem/path-utils.js +67 -0
  37. package/src/filesystem/transaction-runner.js +41 -0
  38. package/src/filesystem/transaction.js +153 -0
  39. package/src/git/submodule.js +115 -0
  40. package/src/git/worktree.js +69 -0
  41. package/src/template/attach-planner-runtime.js +51 -0
  42. package/src/template/attach-planner.js +160 -0
  43. package/src/template/instance-runtime.js +463 -0
  44. package/src/template/lifecycle-metadata.js +99 -0
  45. package/src/template/lifecycle-policy.js +129 -0
  46. package/src/template/lifecycle-runtime.js +90 -0
  47. package/src/template/migration-planner.js +95 -0
  48. package/src/template/migration-runtime.js +247 -0
  49. package/src/template/ownership-metadata.js +79 -0
  50. package/src/template/ownership-policy.js +149 -0
  51. package/src/template/ownership-runtime.js +109 -0
  52. package/src/template/plan-schema.js +36 -0
  53. package/src/template/sync-planner-runtime.js +57 -0
  54. package/src/template/sync-planner.js +206 -0
  55. package/src/template/verification-runtime.js +154 -0
  56. package/src/validation/identity.js +41 -0
  57. package/src/validation/metadata.js +105 -0
  58. package/src/validation/security.js +67 -0
  59. package/src/validation/snapshot.js +47 -0
  60. package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
  61. package/template/.agents/skills/yss-domain/SKILL.md +3 -3
  62. package/template/.agents/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
  63. package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
  64. package/template/.agents/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  65. package/template/.agents/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
  66. package/template/.agents/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
  67. package/template/.agents/skills/yss-mvc-design/SKILL.md +16 -0
  68. package/template/.agents/skills/yss-mvc-design/agents/openai.yaml +4 -0
  69. package/template/.agents/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
  70. package/template/.agents/skills/yss-product-lifecycle/SKILL.md +4 -2
  71. package/template/.agents/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  72. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
  73. package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +1 -1
  74. package/template/.agents/skills/yss-product-lifecycle/references/state-model.md +1 -1
  75. package/template/.agents/skills/yss-tactical-design/SKILL.md +2 -2
  76. package/template/.agents/skills/yss-tactical-design/references/output-template.md +2 -0
  77. package/template/.agents/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
  78. package/template/.agents/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
  79. package/template/.agents/skills/yss-technical-design/SKILL.md +31 -0
  80. package/template/.agents/skills/yss-technical-design/agents/openai.yaml +4 -0
  81. package/template/.agents/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
  82. package/template/.agents/skills/yss-technical-design/references/technical-design-contract.md +31 -0
  83. package/template/.agents/skills/yss-technical-design/references/technical-design.schema.json +759 -0
  84. package/template/.agents/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
  85. package/template/.agents/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
  86. package/template/.agents/skills/yss-technical-design/tests/fixtures.mjs +32 -0
  87. package/template/.agents/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
  88. package/template/.agents/skills/yss-ui/SKILL.md +2 -0
  89. package/template/.claude/skills/yss-domain/SKILL.md +3 -3
  90. package/template/.claude/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
  91. package/template/.claude/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
  92. package/template/.claude/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  93. package/template/.claude/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
  94. package/template/.claude/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
  95. package/template/.claude/skills/yss-mvc-design/SKILL.md +16 -0
  96. package/template/.claude/skills/yss-mvc-design/agents/openai.yaml +4 -0
  97. package/template/.claude/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
  98. package/template/.claude/skills/yss-product-lifecycle/SKILL.md +4 -2
  99. package/template/.claude/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  100. package/template/.claude/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
  101. package/template/.claude/skills/yss-product-lifecycle/references/orchestration.md +1 -1
  102. package/template/.claude/skills/yss-product-lifecycle/references/state-model.md +1 -1
  103. package/template/.claude/skills/yss-tactical-design/SKILL.md +2 -2
  104. package/template/.claude/skills/yss-tactical-design/references/output-template.md +2 -0
  105. package/template/.claude/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
  106. package/template/.claude/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
  107. package/template/.claude/skills/yss-technical-design/SKILL.md +31 -0
  108. package/template/.claude/skills/yss-technical-design/agents/openai.yaml +4 -0
  109. package/template/.claude/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
  110. package/template/.claude/skills/yss-technical-design/references/technical-design-contract.md +31 -0
  111. package/template/.claude/skills/yss-technical-design/references/technical-design.schema.json +759 -0
  112. package/template/.claude/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
  113. package/template/.claude/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
  114. package/template/.claude/skills/yss-technical-design/tests/fixtures.mjs +32 -0
  115. package/template/.claude/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
  116. package/template/.claude/skills/yss-ui/SKILL.md +2 -0
  117. package/template/.codex/skills/yss-domain/SKILL.md +3 -3
  118. package/template/.codex/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
  119. package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
  120. package/template/.codex/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  121. package/template/.codex/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
  122. package/template/.codex/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
  123. package/template/.codex/skills/yss-mvc-design/SKILL.md +16 -0
  124. package/template/.codex/skills/yss-mvc-design/agents/openai.yaml +4 -0
  125. package/template/.codex/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
  126. package/template/.codex/skills/yss-product-lifecycle/SKILL.md +4 -2
  127. package/template/.codex/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  128. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
  129. package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +1 -1
  130. package/template/.codex/skills/yss-product-lifecycle/references/state-model.md +1 -1
  131. package/template/.codex/skills/yss-tactical-design/SKILL.md +2 -2
  132. package/template/.codex/skills/yss-tactical-design/references/output-template.md +2 -0
  133. package/template/.codex/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
  134. package/template/.codex/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
  135. package/template/.codex/skills/yss-technical-design/SKILL.md +31 -0
  136. package/template/.codex/skills/yss-technical-design/agents/openai.yaml +4 -0
  137. package/template/.codex/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
  138. package/template/.codex/skills/yss-technical-design/references/technical-design-contract.md +31 -0
  139. package/template/.codex/skills/yss-technical-design/references/technical-design.schema.json +759 -0
  140. package/template/.codex/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
  141. package/template/.codex/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
  142. package/template/.codex/skills/yss-technical-design/tests/fixtures.mjs +32 -0
  143. package/template/.codex/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
  144. package/template/.codex/skills/yss-ui/SKILL.md +2 -0
  145. package/template/.cursor/skills/yss-domain/SKILL.md +3 -3
  146. package/template/.cursor/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
  147. package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
  148. package/template/.cursor/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  149. package/template/.cursor/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
  150. package/template/.cursor/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
  151. package/template/.cursor/skills/yss-mvc-design/SKILL.md +16 -0
  152. package/template/.cursor/skills/yss-mvc-design/agents/openai.yaml +4 -0
  153. package/template/.cursor/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
  154. package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +4 -2
  155. package/template/.cursor/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  156. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
  157. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +1 -1
  158. package/template/.cursor/skills/yss-product-lifecycle/references/state-model.md +1 -1
  159. package/template/.cursor/skills/yss-tactical-design/SKILL.md +2 -2
  160. package/template/.cursor/skills/yss-tactical-design/references/output-template.md +2 -0
  161. package/template/.cursor/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
  162. package/template/.cursor/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
  163. package/template/.cursor/skills/yss-technical-design/SKILL.md +31 -0
  164. package/template/.cursor/skills/yss-technical-design/agents/openai.yaml +4 -0
  165. package/template/.cursor/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
  166. package/template/.cursor/skills/yss-technical-design/references/technical-design-contract.md +31 -0
  167. package/template/.cursor/skills/yss-technical-design/references/technical-design.schema.json +759 -0
  168. package/template/.cursor/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
  169. package/template/.cursor/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
  170. package/template/.cursor/skills/yss-technical-design/tests/fixtures.mjs +32 -0
  171. package/template/.cursor/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
  172. package/template/.cursor/skills/yss-ui/SKILL.md +2 -0
  173. package/template/.pi/skills/yss-domain/SKILL.md +3 -3
  174. package/template/.pi/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
  175. package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
  176. package/template/.pi/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  177. package/template/.pi/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
  178. package/template/.pi/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
  179. package/template/.pi/skills/yss-mvc-design/SKILL.md +16 -0
  180. package/template/.pi/skills/yss-mvc-design/agents/openai.yaml +4 -0
  181. package/template/.pi/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
  182. package/template/.pi/skills/yss-product-lifecycle/SKILL.md +4 -2
  183. package/template/.pi/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  184. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
  185. package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +1 -1
  186. package/template/.pi/skills/yss-product-lifecycle/references/state-model.md +1 -1
  187. package/template/.pi/skills/yss-tactical-design/SKILL.md +2 -2
  188. package/template/.pi/skills/yss-tactical-design/references/output-template.md +2 -0
  189. package/template/.pi/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
  190. package/template/.pi/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
  191. package/template/.pi/skills/yss-technical-design/SKILL.md +31 -0
  192. package/template/.pi/skills/yss-technical-design/agents/openai.yaml +4 -0
  193. package/template/.pi/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
  194. package/template/.pi/skills/yss-technical-design/references/technical-design-contract.md +31 -0
  195. package/template/.pi/skills/yss-technical-design/references/technical-design.schema.json +759 -0
  196. package/template/.pi/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
  197. package/template/.pi/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
  198. package/template/.pi/skills/yss-technical-design/tests/fixtures.mjs +32 -0
  199. package/template/.pi/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
  200. package/template/.pi/skills/yss-ui/SKILL.md +2 -0
  201. package/template/.qoder/skills/yss-domain/SKILL.md +3 -3
  202. package/template/.qoder/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
  203. package/template/.qoder/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
  204. package/template/.qoder/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  205. package/template/.qoder/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
  206. package/template/.qoder/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
  207. package/template/.qoder/skills/yss-mvc-design/SKILL.md +16 -0
  208. package/template/.qoder/skills/yss-mvc-design/agents/openai.yaml +4 -0
  209. package/template/.qoder/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
  210. package/template/.qoder/skills/yss-product-lifecycle/SKILL.md +4 -2
  211. package/template/.qoder/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  212. package/template/.qoder/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
  213. package/template/.qoder/skills/yss-product-lifecycle/references/orchestration.md +1 -1
  214. package/template/.qoder/skills/yss-product-lifecycle/references/state-model.md +1 -1
  215. package/template/.qoder/skills/yss-tactical-design/SKILL.md +2 -2
  216. package/template/.qoder/skills/yss-tactical-design/references/output-template.md +2 -0
  217. package/template/.qoder/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
  218. package/template/.qoder/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
  219. package/template/.qoder/skills/yss-technical-design/SKILL.md +31 -0
  220. package/template/.qoder/skills/yss-technical-design/agents/openai.yaml +4 -0
  221. package/template/.qoder/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
  222. package/template/.qoder/skills/yss-technical-design/references/technical-design-contract.md +31 -0
  223. package/template/.qoder/skills/yss-technical-design/references/technical-design.schema.json +759 -0
  224. package/template/.qoder/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
  225. package/template/.qoder/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
  226. package/template/.qoder/skills/yss-technical-design/tests/fixtures.mjs +32 -0
  227. package/template/.qoder/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
  228. package/template/.qoder/skills/yss-ui/SKILL.md +2 -0
  229. package/template/.trae/skills/yss-domain/SKILL.md +3 -3
  230. package/template/.trae/skills/yss-frontend-scaffold-generator/SKILL.md +2 -0
  231. package/template/.trae/skills/yss-implementation-contract-compiler/SKILL.md +4 -2
  232. package/template/.trae/skills/yss-implementation-contract-compiler/references/boundaries.md +5 -0
  233. package/template/.trae/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +16 -2
  234. package/template/.trae/skills/yss-implementation-contract-compiler/references/slice-implementation-contract.md +10 -1
  235. package/template/.trae/skills/yss-mvc-design/SKILL.md +16 -0
  236. package/template/.trae/skills/yss-mvc-design/agents/openai.yaml +4 -0
  237. package/template/.trae/skills/yss-mvc-design/references/mvc-design.schema.json +315 -0
  238. package/template/.trae/skills/yss-product-lifecycle/SKILL.md +4 -2
  239. package/template/.trae/skills/yss-product-lifecycle/references/artifact-dependencies.md +2 -2
  240. package/template/.trae/skills/yss-product-lifecycle/references/orchestration-contract.yaml +14 -9
  241. package/template/.trae/skills/yss-product-lifecycle/references/orchestration.md +1 -1
  242. package/template/.trae/skills/yss-product-lifecycle/references/state-model.md +1 -1
  243. package/template/.trae/skills/yss-tactical-design/SKILL.md +2 -2
  244. package/template/.trae/skills/yss-tactical-design/references/output-template.md +2 -0
  245. package/template/.trae/skills/yss-tactical-design/references/tactical-design-contract.md +3 -1
  246. package/template/.trae/skills/yss-tactical-design/scripts/validate-tactical-design.mjs +3 -2
  247. package/template/.trae/skills/yss-technical-design/SKILL.md +31 -0
  248. package/template/.trae/skills/yss-technical-design/agents/openai.yaml +4 -0
  249. package/template/.trae/skills/yss-technical-design/references/technical-design-common.schema.json +290 -0
  250. package/template/.trae/skills/yss-technical-design/references/technical-design-contract.md +31 -0
  251. package/template/.trae/skills/yss-technical-design/references/technical-design.schema.json +759 -0
  252. package/template/.trae/skills/yss-technical-design/scripts/generate-schema.mjs +20 -0
  253. package/template/.trae/skills/yss-technical-design/scripts/validate-technical-design.mjs +134 -0
  254. package/template/.trae/skills/yss-technical-design/tests/fixtures.mjs +32 -0
  255. package/template/.trae/skills/yss-technical-design/tests/run-scenarios.mjs +82 -0
  256. package/template/.trae/skills/yss-ui/SKILL.md +2 -0
  257. package/template/CONTEXT.md +4 -1
  258. package/template/README.md +4 -0
  259. package/template/docs/agents/digital-human-roles.yaml +3 -2
  260. package/template/docs/agents/yss-skill-registry.yaml +20 -1
  261. package/template/docs/architecture/templates/architecture-review-checklist.md +1 -1
  262. package/template/docs/architecture/templates/system-overview-design-template.md +15 -4
  263. package/template/docs/process/frontend-backend-delivery.md +72 -0
  264. package/template/docs/process/implementation-repo-integration.md +2 -0
  265. package/template/docs/process/lifecycle-artifact-map.md +3 -0
  266. package/template/docs/process/lifecycle-registry-baseline.json +4 -1
  267. package/template/docs/process/lifecycle-registry.yaml +14 -2
  268. package/template/docs/process/schemas/backend-delivery-verification.schema.json +113 -0
  269. package/template/docs/process/schemas/backend-delivery.schema.json +301 -0
  270. package/template/docs/process/schemas/digital-human-task-package.schema.json +8 -0
  271. package/template/docs/process/schemas/frontend-delivery-acceptance.schema.json +200 -0
  272. package/template/docs/process/schemas/frontend-implementation-evidence.schema.json +3 -0
  273. package/template/docs/process/schemas/strategic-design-handoff.schema.json +77 -4
  274. package/template/docs/process/strategic-handoff-package.md +4 -2
  275. package/template/docs/process/template-verification-profiles.yaml +20 -0
  276. 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
  277. package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +63 -963
  278. 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
  279. 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
  280. package/template/scripts/backend-delivery +14 -0
  281. package/template/scripts/fixtures/backend-delivery/revision-server.mjs +11 -0
  282. package/template/scripts/fixtures/strategic-handoff/fixture.mjs +3 -2
  283. package/template/scripts/instantiate-harness +69 -0
  284. package/template/scripts/lib/backend-delivery.mjs +164 -0
  285. package/template/scripts/lib/frontend-delivery-boundary.mjs +49 -0
  286. package/template/scripts/lib/frontend-delivery.mjs +73 -0
  287. package/template/scripts/lib/harness-execution-scope.mjs +52 -0
  288. package/template/scripts/lib/implementation-contract-compiler.mjs +19 -2
  289. package/template/scripts/lib/lifecycle-transition.mjs +7 -0
  290. package/template/scripts/lib/strategic-handoff-consumption.mjs +17 -10
  291. package/template/scripts/lib/strategic-handoff-design-targets.mjs +15 -0
  292. package/template/scripts/lib/strategic-handoff.mjs +1 -1
  293. package/template/scripts/lib/task-package.mjs +7 -0
  294. package/template/scripts/lib/technical-design-boundary.mjs +23 -0
  295. package/template/scripts/sync-strategic-handoff-tools +5 -2
  296. package/template/scripts/verify-frontend-delivery +9 -0
  297. package/template/scripts/verify-frontend-delivery-scenarios +131 -0
  298. package/template/scripts/verify-frontend-implementation-evidence +2 -0
  299. package/template/scripts/verify-strategic-handoff-package-scenarios +7 -1
  300. package/template/skills-lock.json +43 -13
  301. package/template.manifest.json +43 -1
  302. 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
- if (sliceRef) ensure(data.status === "approved", "切片只能消费 approved 战术合同");
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 catalogs=['aggregate_catalog','entity_catalog','value_object_catalog','behavior_catalog','invariant_catalog','state_transition_catalog','domain_event_catalog','gateway_catalog'];
45
- const ids=new Set(catalogs.flatMap(k=>(data[k]||[]).flatMap(x=>Object.entries(x).filter(([k])=>k.endsWith('_id')).map(([,v])=>v))));
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
- ensure(list(row.tactical_refs)&&row.tactical_refs.every(x=>ids.has(x)),`战术落点悬空: ${id}`);
54
- 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}`);
55
- 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}`);
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,tactical_digest:digest(data),scope:sliceRef||'whole-tactical-design',coverage_complete:[...known.keys()].every(id=>rows.has(id)),block_all:all,blocked_slice_refs:[...blocked].sort(),issues};
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
- function sync(source,dest){const bytes=readFileSync(source);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
+ // 专职执行范围由各模板持有;后端只消费 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');