create-yss-spec 3.3.6 → 3.3.8

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 (222) hide show
  1. package/README.md +12 -13
  2. package/package.json +1 -1
  3. package/src/template/instance-runtime.js +8 -2
  4. package/src/validation/snapshot.js +7 -0
  5. package/template/.agents/skills/.strategic-design-skills-manifest.json +6 -4
  6. package/template/.agents/skills/archify/SKILL.md +1 -1
  7. package/template/.agents/skills/implementation-repo-onboarding/SKILL.md +2 -2
  8. package/template/.agents/skills/yss-application/SKILL.md +1 -1
  9. package/template/.agents/skills/yss-application/references/profiles/existing-domain-driven-maven.md +5 -0
  10. package/template/.agents/skills/yss-application/references/profiles/existing-layered-mvc-maven.md +5 -0
  11. package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +2 -2
  12. package/template/.agents/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +4 -0
  13. package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +6 -1
  14. package/template/.agents/skills/yss-prototype-stage/SKILL.md +10 -1
  15. package/template/.agents/skills/yss-stage-decision/SKILL.md +13 -4
  16. package/template/.agents/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  17. package/template/.agents/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  18. package/template/.agents/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  19. package/template/.agents/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  20. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  21. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  22. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  23. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  24. package/template/.agents/skills/yss-stage-decision/references/validation-rules.md +2 -2
  25. package/template/.agents/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  26. package/template/.agents/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  27. package/template/.agents/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  28. package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  29. package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  30. package/template/.agents/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  31. package/template/.agents/skills/yss-technical-design/SKILL.md +2 -0
  32. package/template/.agents/skills/yss-technical-design/references/engineering-design.schema.json +224 -0
  33. package/template/.agents/skills/yss-technical-design/references/technical-design-common.schema.json +6 -0
  34. package/template/.agents/skills/yss-technical-design/references/technical-design.schema.json +288 -10
  35. package/template/.agents/skills/yss-technical-design/scripts/engineering-design.mjs +30 -0
  36. package/template/.agents/skills/yss-technical-design/scripts/generate-schema.mjs +2 -1
  37. package/template/.agents/skills/yss-technical-design/scripts/validate-technical-design.mjs +20 -8
  38. package/template/.agents/skills/yss-technical-design/tests/engineering-scenarios.mjs +12 -0
  39. package/template/.agents/skills/yss-web-controller/SKILL.md +1 -1
  40. package/template/.agents/skills/yss-web-controller/references/profiles/existing-domain-driven-maven.md +5 -0
  41. package/template/.agents/skills/yss-web-controller/references/profiles/existing-layered-mvc-maven.md +5 -0
  42. package/template/.codex/skills/archify/SKILL.md +1 -1
  43. package/template/.codex/skills/implementation-repo-onboarding/SKILL.md +2 -2
  44. package/template/.codex/skills/yss-application/SKILL.md +1 -1
  45. package/template/.codex/skills/yss-application/references/profiles/existing-domain-driven-maven.md +5 -0
  46. package/template/.codex/skills/yss-application/references/profiles/existing-layered-mvc-maven.md +5 -0
  47. package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +2 -2
  48. package/template/.codex/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +4 -0
  49. package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +6 -1
  50. package/template/.codex/skills/yss-prototype-stage/SKILL.md +10 -1
  51. package/template/.codex/skills/yss-stage-decision/SKILL.md +13 -4
  52. package/template/.codex/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  53. package/template/.codex/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  54. package/template/.codex/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  55. package/template/.codex/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  56. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  57. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  58. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  59. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  60. package/template/.codex/skills/yss-stage-decision/references/validation-rules.md +2 -2
  61. package/template/.codex/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  62. package/template/.codex/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  63. package/template/.codex/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  64. package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  65. package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  66. package/template/.codex/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  67. package/template/.codex/skills/yss-technical-design/SKILL.md +2 -0
  68. package/template/.codex/skills/yss-technical-design/references/engineering-design.schema.json +224 -0
  69. package/template/.codex/skills/yss-technical-design/references/technical-design-common.schema.json +6 -0
  70. package/template/.codex/skills/yss-technical-design/references/technical-design.schema.json +288 -10
  71. package/template/.codex/skills/yss-technical-design/scripts/engineering-design.mjs +30 -0
  72. package/template/.codex/skills/yss-technical-design/scripts/generate-schema.mjs +2 -1
  73. package/template/.codex/skills/yss-technical-design/scripts/validate-technical-design.mjs +20 -8
  74. package/template/.codex/skills/yss-technical-design/tests/engineering-scenarios.mjs +12 -0
  75. package/template/.codex/skills/yss-web-controller/SKILL.md +1 -1
  76. package/template/.codex/skills/yss-web-controller/references/profiles/existing-domain-driven-maven.md +5 -0
  77. package/template/.codex/skills/yss-web-controller/references/profiles/existing-layered-mvc-maven.md +5 -0
  78. package/template/.cursor/skills/archify/SKILL.md +1 -1
  79. package/template/.cursor/skills/implementation-repo-onboarding/SKILL.md +2 -2
  80. package/template/.cursor/skills/yss-application/SKILL.md +1 -1
  81. package/template/.cursor/skills/yss-application/references/profiles/existing-domain-driven-maven.md +5 -0
  82. package/template/.cursor/skills/yss-application/references/profiles/existing-layered-mvc-maven.md +5 -0
  83. package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +2 -2
  84. package/template/.cursor/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +4 -0
  85. package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +6 -1
  86. package/template/.cursor/skills/yss-prototype-stage/SKILL.md +10 -1
  87. package/template/.cursor/skills/yss-stage-decision/SKILL.md +13 -4
  88. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  89. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  90. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  91. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  92. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  93. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  94. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  95. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  96. package/template/.cursor/skills/yss-stage-decision/references/validation-rules.md +2 -2
  97. package/template/.cursor/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  98. package/template/.cursor/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  99. package/template/.cursor/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  100. package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  101. package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  102. package/template/.cursor/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  103. package/template/.cursor/skills/yss-technical-design/SKILL.md +2 -0
  104. package/template/.cursor/skills/yss-technical-design/references/engineering-design.schema.json +224 -0
  105. package/template/.cursor/skills/yss-technical-design/references/technical-design-common.schema.json +6 -0
  106. package/template/.cursor/skills/yss-technical-design/references/technical-design.schema.json +288 -10
  107. package/template/.cursor/skills/yss-technical-design/scripts/engineering-design.mjs +30 -0
  108. package/template/.cursor/skills/yss-technical-design/scripts/generate-schema.mjs +2 -1
  109. package/template/.cursor/skills/yss-technical-design/scripts/validate-technical-design.mjs +20 -8
  110. package/template/.cursor/skills/yss-technical-design/tests/engineering-scenarios.mjs +12 -0
  111. package/template/.cursor/skills/yss-web-controller/SKILL.md +1 -1
  112. package/template/.cursor/skills/yss-web-controller/references/profiles/existing-domain-driven-maven.md +5 -0
  113. package/template/.cursor/skills/yss-web-controller/references/profiles/existing-layered-mvc-maven.md +5 -0
  114. package/template/.pi/skills/archify/SKILL.md +1 -1
  115. package/template/.pi/skills/implementation-repo-onboarding/SKILL.md +2 -2
  116. package/template/.pi/skills/yss-application/SKILL.md +1 -1
  117. package/template/.pi/skills/yss-application/references/profiles/existing-domain-driven-maven.md +5 -0
  118. package/template/.pi/skills/yss-application/references/profiles/existing-layered-mvc-maven.md +5 -0
  119. package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +2 -2
  120. package/template/.pi/skills/yss-implementation-contract-compiler/references/compiler-contract.yaml +4 -0
  121. package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +6 -1
  122. package/template/.pi/skills/yss-prototype-stage/SKILL.md +10 -1
  123. package/template/.pi/skills/yss-stage-decision/SKILL.md +13 -4
  124. package/template/.pi/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  125. package/template/.pi/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  126. package/template/.pi/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  127. package/template/.pi/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  128. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  129. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  130. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  131. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  132. package/template/.pi/skills/yss-stage-decision/references/validation-rules.md +2 -2
  133. package/template/.pi/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  134. package/template/.pi/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  135. package/template/.pi/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  136. package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  137. package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  138. package/template/.pi/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  139. package/template/.pi/skills/yss-technical-design/SKILL.md +2 -0
  140. package/template/.pi/skills/yss-technical-design/references/engineering-design.schema.json +224 -0
  141. package/template/.pi/skills/yss-technical-design/references/technical-design-common.schema.json +6 -0
  142. package/template/.pi/skills/yss-technical-design/references/technical-design.schema.json +288 -10
  143. package/template/.pi/skills/yss-technical-design/scripts/engineering-design.mjs +30 -0
  144. package/template/.pi/skills/yss-technical-design/scripts/generate-schema.mjs +2 -1
  145. package/template/.pi/skills/yss-technical-design/scripts/validate-technical-design.mjs +20 -8
  146. package/template/.pi/skills/yss-technical-design/tests/engineering-scenarios.mjs +12 -0
  147. package/template/.pi/skills/yss-web-controller/SKILL.md +1 -1
  148. package/template/.pi/skills/yss-web-controller/references/profiles/existing-domain-driven-maven.md +5 -0
  149. package/template/.pi/skills/yss-web-controller/references/profiles/existing-layered-mvc-maven.md +5 -0
  150. package/template/README.md +4 -6
  151. package/template/docs/agents/backend-architecture-profiles.md +4 -0
  152. package/template/docs/agents/yss-skill-registry.yaml +13 -0
  153. package/template/docs/design/templates/prototype-confirmation-template.md +2 -2
  154. package/template/docs/design/templates/prototype-review-checklist.md +2 -2
  155. package/template/docs/plan/templates/market-analysis-template.md +3 -3
  156. package/template/docs/plan/templates/user-pain-points-template.md +3 -3
  157. package/template/docs/process/delivery-preflight.md +72 -0
  158. package/template/docs/process/document-writing.md +5 -1
  159. package/template/docs/process/existing-backend-architecture.md +31 -0
  160. package/template/docs/process/existing-ui-baseline.md +40 -0
  161. package/template/docs/process/frontend-backend-delivery.md +21 -5
  162. package/template/docs/process/implementation-repo-integration.md +6 -0
  163. package/template/docs/process/schemas/backend-architecture-identity.schema.json +111 -14
  164. package/template/docs/process/schemas/backend-delivery.schema.json +4 -0
  165. package/template/docs/process/schemas/delivery-preflight-input.schema.json +158 -0
  166. package/template/docs/process/schemas/delivery-preflight-result.schema.json +159 -0
  167. package/template/docs/process/schemas/digital-human-task-package.schema.json +1 -1
  168. package/template/docs/process/schemas/existing-ui-baseline.schema.json +365 -0
  169. package/template/docs/process/schemas/frontend-delivery-acceptance-v1.schema.json +200 -0
  170. package/template/docs/process/schemas/frontend-delivery-acceptance-v3.schema.json +308 -0
  171. package/template/docs/process/schemas/frontend-delivery-acceptance.schema.json +37 -3
  172. package/template/docs/process/schemas/frontend-strategic-preflight-v2.schema.json +114 -0
  173. package/template/docs/process/schemas/frontend-strategic-preflight.schema.json +32 -0
  174. package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +2 -1
  175. package/template/docs/process/schemas/strategic-consumer-feedback.schema.json +40 -0
  176. package/template/docs/process/schemas/strategic-design-handoff-v3.schema.json +743 -0
  177. package/template/docs/process/schemas/strategic-design-handoff-v4.schema.json +712 -0
  178. package/template/docs/process/schemas/strategic-design-handoff-v5.schema.json +932 -0
  179. package/template/docs/process/schemas/strategic-feedback-adjudication.schema.json +42 -0
  180. package/template/docs/process/schemas/strategic-handoff-domain-strategy-v3.schema.json +216 -0
  181. package/template/docs/process/schemas/strategic-handoff-export-v2.schema.json +378 -0
  182. package/template/docs/process/schemas/strategic-handoff-import-receipt.schema.json +39 -0
  183. package/template/docs/process/schemas/strategic-handoff-stage-decision-package-v3.schema.json +148 -0
  184. package/template/docs/process/strategic-handoff-package.md +51 -10
  185. package/template/docs/process/templates/lifecycle-checkpoint-template.yaml +1 -0
  186. package/template/docs/process/templates/stage-checkpoint-template.md +1 -0
  187. package/template/docs/user-guide//346/212/200/346/234/257/350/256/276/350/256/241/347/224/250/346/210/267/346/214/207/345/215/227.md +43 -0
  188. package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +83 -67
  189. package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214/347/264/242/345/274/225.md +9 -10
  190. 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 +12 -9
  191. package/template/scripts/backend-delivery +20 -4
  192. package/template/scripts/lib/approval-record.mjs +2 -1
  193. package/template/scripts/lib/approved-execution-context.mjs +106 -0
  194. package/template/scripts/lib/backend-architecture.mjs +12 -0
  195. package/template/scripts/lib/backend-delivery.mjs +21 -5
  196. package/template/scripts/lib/delivery-preflight.mjs +238 -0
  197. package/template/scripts/lib/existing-backend-architecture.mjs +139 -0
  198. package/template/scripts/lib/existing-ui-baseline.mjs +84 -0
  199. package/template/scripts/lib/frontend-delivery-boundary.mjs +20 -4
  200. package/template/scripts/lib/frontend-delivery.mjs +76 -4
  201. package/template/scripts/lib/implementation-contract-compiler.mjs +19 -5
  202. package/template/scripts/lib/json-schema.mjs +19 -4
  203. package/template/scripts/lib/profile-skill-sync.mjs +26 -0
  204. package/template/scripts/lib/strategic-feedback.mjs +33 -0
  205. package/template/scripts/lib/strategic-handoff-consumption.mjs +11 -5
  206. package/template/scripts/lib/strategic-handoff-design-targets.mjs +2 -3
  207. package/template/scripts/lib/strategic-handoff-io.mjs +11 -0
  208. package/template/scripts/lib/strategic-handoff.mjs +143 -34
  209. package/template/scripts/lib/task-package.mjs +11 -3
  210. package/template/scripts/lib/technical-design-boundary.mjs +9 -1
  211. package/template/scripts/lib/ui-baseline.mjs +26 -0
  212. package/template/scripts/lib/user-decision-reuse.mjs +38 -0
  213. package/template/scripts/lib/user-decision.mjs +4 -1
  214. package/template/scripts/preflight-delivery +14 -0
  215. package/template/scripts/strategic-feedback +10 -0
  216. package/template/scripts/strategic-handoff +19 -4
  217. package/template/scripts/sync-profile-skills +4 -5
  218. package/template/scripts/verify-frontend-strategic-preflight +12 -0
  219. package/template/skills-lock.json +18 -18
  220. package/template.manifest.json +1 -0
  221. package/template.snapshot.json +6 -5
  222. 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 +0 -35
@@ -0,0 +1,139 @@
1
+ import {assertApprovedExecutionContext} from './approved-execution-context.mjs';
2
+ import {createHash} from 'node:crypto';
3
+ import {readFileSync,realpathSync,lstatSync,readdirSync} from 'node:fs';
4
+ import path from 'node:path';
5
+ import {execFileSync} from 'node:child_process';
6
+ import {parseDocument} from '../vendor/yaml.mjs';
7
+ import {validateApprovalRecord} from './approval-record.mjs';
8
+
9
+ const sha=bytes=>`sha256:${createHash('sha256').update(bytes).digest('hex')}`;
10
+ const stable=x=>Array.isArray(x)?x.map(stable):x&&typeof x==='object'?Object.fromEntries(Object.keys(x).sort().map(k=>[k,stable(x[k])])):x;
11
+ export const existingArchitectureDigest=x=>sha(JSON.stringify(stable(x)));
12
+ const text=x=>typeof x==='string'&&x.trim().length>0;
13
+ const digest=x=>typeof x==='string'&&/^sha256:[a-f0-9]{64}$/.test(x);
14
+ function requireThat(ok,code,message){if(!ok){const e=new TypeError(`${code}: ${message}`);e.code=code;throw e;}}
15
+ function relative(ref){requireThat(text(ref)&&!path.isAbsolute(ref)&&!/[\\\x00-\x1f]/.test(ref)&&ref.split('/').every(x=>x&&x!=='.'&&x!=='..'),'ARCH_PATH','证据路径必须为安全相对路径');return ref;}
16
+ function file(root,ref){relative(ref);let current=realpathSync(root);for(const part of ref.split('/')){current=path.join(current,part);requireThat(!lstatSync(current).isSymbolicLink(),'ARCH_PATH',`不允许 symlink: ${ref}`);}requireThat(lstatSync(current).isFile(),'ARCH_PATH',`不是文件: ${ref}`);return current;}
17
+ function parse(bytes){const doc=parseDocument(String(bytes),{uniqueKeys:true,maxAliasCount:0});requireThat(!doc.errors.length,'ARCH_EVIDENCE_FORMAT',doc.errors[0]?.message);return doc.toJS({maxAliasCount:0});}
18
+ function bound(root,binding){requireThat(binding&&text(binding.ref)&&digest(binding.digest),'ARCH_EVIDENCE_REF','缺少原始证据 ref/digest');const bytes=readFileSync(file(root,binding.ref));requireThat(sha(bytes)===binding.digest,'ARCH_EVIDENCE_STALE',`原始证据已改变: ${binding.ref}`);return parse(bytes);}
19
+ function same(a,b,code,label){requireThat(existingArchitectureDigest(a)===existingArchitectureDigest(b),code,label);}
20
+ const FIELDS=['schema_version','source_kind','architecture_family','architecture_profile','repository_id','project_id','source_digest','build_units_digest'];
21
+ export function validateExistingArchitecture(identity,registry){
22
+ requireThat(identity?.schema_version===2&&identity.source_kind==='existing-registration','ARCH_SOURCE_UNSUPPORTED','仅支持 existing-registration v2');
23
+ requireThat(FIELDS.every(k=>identity[k]!==undefined)&&Object.keys(identity).every(k=>FIELDS.includes(k)),'ARCH_IDENTITY_SHAPE','既有身份字段缺失或混入生成事实');
24
+ const p=registry.existing_project_profiles?.[identity.architecture_profile];
25
+ requireThat(p&&p.source_kind==='existing-registration'&&p.build_system==='maven'&&p.architecture_family===identity.architecture_family,'ARCH_PROFILE_UNSUPPORTED','未支持的既有工程架构/Profile');
26
+ requireThat(['domain-driven','layered-mvc'].includes(identity.architecture_family)&&text(identity.repository_id)&&text(identity.project_id)&&digest(identity.source_digest)&&digest(identity.build_units_digest),'ARCH_IDENTITY_SHAPE','既有身份未绑定项目或源码/构建摘要');
27
+ return p;
28
+ }
29
+ const beneath=(ref,scope)=>scope==='.'||ref===scope||ref.startsWith(`${scope}/`);
30
+ const excluded=ref=>ref.split('/').some(x=>['.git','target','node_modules'].includes(x));
31
+ function listFiles(root,prefix=''){return readdirSync(path.join(root,prefix),{withFileTypes:true}).flatMap(entry=>{const ref=prefix?`${prefix}/${entry.name}`:entry.name;if(excluded(ref))return[];requireThat(!entry.isSymbolicLink(),'ARCH_PATH',`源码中出现 symlink: ${ref}`);return entry.isDirectory()?listFiles(root,ref):[ref];});}
32
+ function inspectMaven(root,units){
33
+ // Only parses XML; never invokes Maven or executes project-supplied commands.
34
+ const script=`import json,sys,xml.etree.ElementTree as ET\nout=[]\nfor p in json.load(sys.stdin):\n r=ET.parse(p).getroot(); ns={'m':'http://maven.apache.org/POM/4.0.0'}; q=lambda s:r.find(s,ns)\n aid=q('m:artifactId'); aid=aid if aid is not None else q('artifactId')\n deps=[e.text.strip() for e in r.findall('m:dependencies/m:dependency/m:artifactId',ns)+r.findall('m:profiles/m:profile/m:dependencies/m:dependency/m:artifactId',ns)+r.findall('dependencies/dependency/artifactId') if e.text]\n modules=[e.text.strip() for e in r.findall('.//m:modules/m:module',ns)+r.findall('.//modules/module') if e.text]\n out.append({'artifact_id':aid.text.strip() if aid is not None else '', 'dependencies':deps, 'modules':modules})\nprint(json.dumps(out))`;
35
+ try{return JSON.parse(execFileSync('python3',['-c',script],{input:JSON.stringify(units.map(u=>file(root,u.pom_ref))),encoding:'utf8',timeout:15000,maxBuffer:2*1024*1024}));}catch(error){requireThat(false,'ARCH_BUILD_INVALID',`无法读取实际 Maven 构建单元: ${error.message}`);}
36
+ }
37
+ export function verifyExistingArchitecture(identity,bindings,{root,registry,execution}={}){
38
+ const profile=validateExistingArchitecture(identity,registry);
39
+ requireThat(text(root),'ARCH_EVIDENCE_REF','需要治理 root');
40
+ const baseline=bound(root,bindings?.engineering_baseline),registration=bound(root,bindings?.repository_registration),manifest=bound(root,bindings?.manifest);
41
+ for(const [name,record] of Object.entries({baseline,registration,manifest})){
42
+ same(identity,record.architecture_identity,'ARCH_IDENTITY_CONFLICT',`${name} 架构身份与合同不一致`);
43
+ requireThat(record.repository_id===identity.repository_id&&record.project_id===identity.project_id,'ARCH_REPOSITORY_CONFLICT',`${name} 仓库/项目不一致`);
44
+ }
45
+ requireThat(manifest.kind==='existing-project-observation'&&manifest.schema_version===1,'ARCH_MANIFEST_KIND','需要既有工程观测 manifest,不能冒充脚手架');
46
+ requireThat(baseline.kind==='existing-engineering-baseline'&&baseline.schema_version===1,'ARCH_BASELINE_KIND','需要既有工程基线');
47
+ requireThat(registration.status==='current'&&baseline.status==='current','ARCH_EVIDENCE_STALE','登记/工程基线不是 current');
48
+ requireThat(text(registration.local_worktree)&&text(registration.project_root)&&text(registration.owner)&&text(registration.repository_url),'ARCH_REGISTRATION_MISSING','登记缺根路径、仓库来源或责任人');
49
+ for(const key of ['engineering_baseline','manifest'])same(registration.architecture_evidence?.[key],bindings[key],'ARCH_EVIDENCE_CONFLICT',`登记未引用当前${key}`);
50
+ const repositoryRoot=realpathSync(registration.local_worktree);
51
+ const projectRoot=registration.project_root==='.'?repositoryRoot:path.join(repositoryRoot,relative(registration.project_root));
52
+ requireThat(realpathSync(projectRoot)===projectRoot,'ARCH_PATH','工程根存在符号链接');
53
+ const git=(args)=>execFileSync('git',['-C',repositoryRoot,...args],{encoding:'utf8',timeout:15000,maxBuffer:32*1024*1024}).trim();
54
+ requireThat(git(['rev-parse','--show-toplevel'])===repositoryRoot,'ARCH_REPOSITORY_CONFLICT','登记根不是实际 Git 根');
55
+ const source=manifest.source;
56
+ requireThat(source&&/^[a-f0-9]{40}$/.test(source.base_commit)&&Array.isArray(source.files)&&source.files.length&&Array.isArray(source.roots)&&source.roots.length,'ARCH_SOURCE_MISSING','源码基线缺固定提交、文件清单或根范围');
57
+ requireThat(git(['rev-parse',`${source.base_commit}^{commit}`])===source.base_commit,'ARCH_SOURCE_STALE','固定基础提交不可读');
58
+ requireThat(git(['remote','get-url','origin'])===registration.repository_url,'ARCH_REPOSITORY_CONFLICT','实际 origin 与登记来源不一致');
59
+ requireThat(git(['rev-parse','HEAD'])===source.base_commit,'ARCH_SOURCE_STALE','工作树 HEAD 与批准固定输入不一致');
60
+ same(source,baseline.source,'ARCH_SOURCE_CONFLICT','工程基线与观测的源码范围不一致');
61
+ requireThat(existingArchitectureDigest(source)===identity.source_digest,'ARCH_SOURCE_STALE','固定源码基线摘要不一致');
62
+ for(const ref of source.roots)if(ref!=='.')relative(ref);
63
+ const sourcePaths=source.files.map(x=>x.path);requireThat(new Set(sourcePaths).size===sourcePaths.length,'ARCH_SOURCE_INVALID','源码路径重复');
64
+ const prefix=registration.project_root==='.'?'':`${registration.project_root}/`;
65
+ const tree=git(['ls-tree','-r',source.base_commit]).split('\n').filter(Boolean).map(line=>{const [meta,ref]=line.split('\t');return{ref,blob:meta.split(' ')[2],mode:meta.split(' ')[0]};}).filter(x=>x.ref.startsWith(prefix)).map(x=>({...x,path:x.ref.slice(prefix.length)})).filter(x=>!excluded(x.path)&&source.roots.some(scope=>beneath(x.path,scope)));
66
+ requireThat(tree.every(x=>['100644','100755'].includes(x.mode)),'ARCH_SOURCE_INVALID','源码范围包含 gitlink 或 symlink,需单独登记');
67
+ same(sourcePaths.slice().sort(),tree.map(x=>x.path).sort(),'ARCH_SOURCE_INCOMPLETE','源码清单未覆盖声明范围的固定提交文件');
68
+ const treeMap=new Map(tree.map(x=>[x.path,x.blob]));
69
+ const changes=[];
70
+ for(const item of source.files){
71
+ relative(item.path);requireThat(digest(item.sha256),'ARCH_SOURCE_INVALID','源码摘要格式无效');
72
+ let bytes;
73
+ try { bytes=readFileSync(file(projectRoot,item.path)); } catch(error) { if(error.code==='ENOENT'){changes.push(item.path);continue;} throw error; }
74
+ const actual=sha(bytes);const baseBlob=treeMap.get(item.path);
75
+ requireThat(item.base_blob===baseBlob,'ARCH_SOURCE_STALE',`基础 Git blob 不一致: ${item.path}`);
76
+ const currentBlob=createHash('sha1').update(`blob ${bytes.length}\0`).update(bytes).digest('hex');
77
+ if(actual!==item.sha256){changes.push(item.path);continue;}
78
+ if(currentBlob!==baseBlob)requireThat(source.patches?.some(p=>p.path===item.path&&p.base_blob===baseBlob&&p.sha256===actual),'ARCH_SOURCE_UNDECLARED',`未登记基线补丁: ${item.path}`);
79
+ }
80
+ const observed=[...new Set(source.roots.flatMap(scope=>{ const entry=path.join(projectRoot,scope); try { const stat=lstatSync(entry); requireThat(!stat.isSymbolicLink(),'ARCH_PATH',`源码根出现 symlink: ${scope}`); return stat.isDirectory()?listFiles(projectRoot,scope==='.'?'':scope):[scope]; } catch(error) { if(error.code==='ENOENT')return [];throw error; } }))];
81
+ for(const ref of observed)if(!sourcePaths.includes(ref))changes.push(ref);
82
+ const units=manifest.build_units;
83
+ requireThat(Array.isArray(units)&&units.length&&units.every(u=>text(u.id)&&text(u.pom_ref)&&Array.isArray(u.roles)&&Array.isArray(u.depends_on)),'ARCH_BUILD_MISSING','缺少实际构建单元/角色/依赖');
84
+ same(units,baseline.build_units,'ARCH_BUILD_CONFLICT','三方构建单元映射不一致');
85
+ requireThat(existingArchitectureDigest(units)===identity.build_units_digest,'ARCH_BUILD_STALE','构建单元摘要不一致');
86
+ requireThat(new Set(units.map(u=>u.id)).size===units.length,'ARCH_BUILD_CONFLICT','构建单元重复');
87
+ const inspected=inspectMaven(projectRoot,units);const artifactMap=new Map(inspected.map((m,i)=>[m.artifact_id,units[i].id]));
88
+ for(const [i,u] of units.entries()){
89
+ requireThat(sourcePaths.includes(u.pom_ref),'ARCH_BUILD_CONFLICT',`POM未冻结: ${u.pom_ref}`);
90
+ requireThat(u.artifact_id===inspected[i].artifact_id,'ARCH_BUILD_CONFLICT',`artifactId不一致: ${u.id}`);
91
+ for(const module of inspected[i].modules){
92
+ requireThat(!module.includes('${'),'ARCH_BUILD_UNRESOLVED',`Maven module 路径尚未解析: ${u.id}`);
93
+ const child=path.posix.normalize(path.posix.join(path.posix.dirname(u.pom_ref),module,'pom.xml'));
94
+ relative(child); requireThat(units.some(unit=>unit.pom_ref===child),'ARCH_BUILD_INCOMPLETE',`实际 reactor 模块未登记: ${child}`);
95
+ }
96
+ const deps=[...new Set(inspected[i].dependencies.filter(a=>artifactMap.has(a)).map(a=>artifactMap.get(a)))].sort();
97
+ same([...u.depends_on].sort(),deps,'ARCH_BUILD_CONFLICT',`实际模块依赖不一致: ${u.id}`);
98
+ }
99
+ requireThat(Array.isArray(registration.allowed_write_paths)&&registration.allowed_write_paths.length,'ARCH_REGISTRATION_MISSING','登记缺少允许写范围');
100
+ for(const ref of registration.allowed_write_paths) if(ref!=='.')relative(ref);
101
+ requireThat(profile.required_roles.every(role=>units.some(u=>u.roles.includes(role))),'ARCH_BOUNDARY_MISSING','实际构建单元未覆盖架构责任');
102
+ requireThat(Array.isArray(baseline.boundary_scope)&&baseline.boundary_scope.length,'ARCH_BOUNDARY_MISSING','必须明确既有架构适用源码范围,不能推定整个混合仓同构');
103
+ baseline.boundary_scope.forEach(relative);
104
+ for(const unit of units)for(const role of unit.roles){
105
+ const refs=unit.role_paths?.[role];
106
+ requireThat(Array.isArray(refs)&&refs.length,'ARCH_BOUNDARY_MISSING',`缺少责任源码映射: ${unit.id}/${role}`);
107
+ for(const ref of refs){relative(ref);requireThat(sourcePaths.some(p=>beneath(p,ref))&&baseline.boundary_scope.some(scope=>beneath(ref,scope)),'ARCH_BOUNDARY_CONFLICT',`责任源码映射未在已冻结边界内: ${ref}`);}
108
+ }
109
+ requireThat(Array.isArray(baseline.verification_commands)&&baseline.verification_commands.length&&Array.isArray(registration.verification_commands),'ARCH_VERIFICATION_MISSING','缺少登记验证命令');
110
+ same(baseline.verification_commands,registration.verification_commands,'ARCH_VERIFICATION_CONFLICT','验证入口不一致');
111
+ const review=bound(root,baseline.boundary_review);
112
+ const rolesFile=path.join(root,'docs/agents/digital-human-roles.yaml');
113
+ const roles=parse(readFileSync(rolesFile));
114
+ validateApprovalRecord(review,{rolesDoc:roles,requireApproved:true,root});
115
+ requireThat(['check.architecture-reviewed','gate.technical-design-approved'].includes(review.gate_id)&&text(baseline.id)&&text(baseline.version),'ARCH_REVIEW_MISSING','需要架构边界审查');
116
+ const basis=existingArchitectureDigest(Object.fromEntries(Object.entries(baseline).filter(([k])=>k!=='boundary_review')));
117
+ requireThat(review.artifact_bindings?.some(x=>x.id===baseline.id&&x.version===baseline.version&&x.digest===basis),'ARCH_REVIEW_STALE','架构审查未绑定当前基线');
118
+ requireThat(text(review.principal_ref)&&review.principal_ref!==baseline.author,'ARCH_REVIEW_INVALID','起草者不能自审');
119
+ requireThat(text(baseline.author),'ARCH_REVIEW_INVALID','工程基线缺少起草者');
120
+ requireThat(Array.isArray(review.evidence_refs)&&review.evidence_refs.length,'ARCH_REVIEW_MISSING','缺少可读边界审查证据');
121
+ review.evidence_refs.forEach(ref=>file(root,ref));
122
+ requireThat(baseline.databases?.verification&&baseline.databases?.production,'ARCH_DATABASE_MISSING','需分别声明验证与生产数据库状态');
123
+ for(const [kind,record] of Object.entries(baseline.databases)){
124
+ requireThat(['declared','verified','unknown','not-applicable'].includes(record.status),'ARCH_DATABASE_INVALID',`${kind} 数据库状态无效`);
125
+ if(record.status==='verified'){
126
+ requireThat(text(record.engine)&&text(record.version)&&text(record.environment_id),'ARCH_DATABASE_INVALID','数据库验证未绑定环境和版本');
127
+ const evidence=bound(root,record.verification);requireThat(evidence.source_digest===identity.source_digest&&evidence.engine===record.engine&&evidence.version===record.version&&evidence.environment_id===record.environment_id,'ARCH_DATABASE_STALE','实库验证与声明/源码不一致');
128
+ requireThat(Array.isArray(evidence.results)&&evidence.results.length&&evidence.results.every(r=>text(r.command)&&r.exit_code===0&&Number.isFinite(Date.parse(r.executed_at))&&Array.isArray(r.evidence)&&r.evidence.length),'ARCH_DATABASE_UNVERIFIED','不能用数据库声明代替实际执行');
129
+ evidence.results.forEach(r=>r.evidence.forEach(b=>{requireThat(text(b?.ref)&&digest(b.digest),'ARCH_DATABASE_UNVERIFIED','缺少原始执行日志');requireThat(sha(readFileSync(file(root,b.ref)))===b.digest,'ARCH_DATABASE_STALE','实际执行日志摘要不一致');}));
130
+ }
131
+ }
132
+ if(changes.length){
133
+ requireThat(execution,'ARCH_SOURCE_STALE',`固定源码改变: ${changes.join(', ')}`);
134
+ const permission=assertApprovedExecutionContext(execution,{root,architectureIdentity:identity,architectureEvidence:bindings});
135
+ const allowed=permission.allowed_write_paths.map(x=>{if(path.isAbsolute(x)){const rel=path.relative(projectRoot,x).split(path.sep).join('/');return rel==='.'||rel===''?'.':relative(rel);}return x==='.'?'.':relative(x);});
136
+ requireThat(changes.every(ref=>allowed.some(scope=>beneath(ref,scope))&&registration.allowed_write_paths.some(scope=>beneath(ref,scope))),'ARCH_SOURCE_OUT_OF_SCOPE','输出增量超出批准写范围');
137
+ }
138
+ return {source_kind:'existing-registration',bindings:structuredClone(bindings),project_root:projectRoot,source_digest:identity.source_digest,build_units_digest:identity.build_units_digest,changed_files:[...new Set(changes)].sort(),profile_maturity:profile.maturity};
139
+ }
@@ -0,0 +1,84 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import { ensure, hash, digest, read, safe, files, schema, treeDigest } from './strategic-handoff-io.mjs';
3
+
4
+ // This is an observation bundle, not a prototype or an approval generator.
5
+ export function validateExistingUiBaseline(data, { bundleRoot } = {}) {
6
+ const errors=[];
7
+ try {
8
+ schema(data,'docs/process/schemas/existing-ui-baseline.schema.json');
9
+ ensure(bundleRoot,'existing-ui-baseline 需要原始 bundleRoot');
10
+ const consumed=new Set(['existing-ui-baseline.json']);
11
+ const allFiles=files(bundleRoot);
12
+ ensure(allFiles.length<=20000&&allFiles.reduce((total,ref)=>total+statSync(safe(bundleRoot,ref)).size,0)<=100*1024*1024,'existing-ui-baseline 文件数量或包大小超限');
13
+ const bound=binding=>{
14
+ consumed.add(binding.ref);
15
+ const bytes=readFileSync(safe(bundleRoot,binding.ref));
16
+ ensure(bytes.length>0&&hash(bytes)===binding.digest,`existing-ui-baseline 原始证据摘要不一致: ${binding.ref}`);
17
+ return bytes;
18
+ };
19
+ const document=binding=>{bound(binding);return read(safe(bundleRoot,binding.ref));};
20
+ const source=data.source, manifest=document(source.manifest);
21
+ ensure(source.digest===treeDigest(bundleRoot,source.root_ref),'existing-ui-baseline 固定源码摘要不一致');
22
+ bound(source.lock);
23
+ ensure(source.lock.ref.startsWith(`${source.root_ref}/`),'existing-ui-baseline 锁文件必须属于固定源码');
24
+ ensure(manifest.schema_version===1&&manifest.kind==='existing-ui-source-observation','existing-ui-baseline 源码观测 manifest 类型无效');
25
+ for(const key of ['repository_id','project_id','source_commit'])ensure(manifest[key]===source[key],`existing-ui-baseline 源码身份不一致: ${key}`);
26
+ ensure(manifest.source_digest===source.digest&&Array.isArray(manifest.files),'existing-ui-baseline 源码观测摘要不一致');
27
+ files(bundleRoot,source.root_ref).forEach(ref=>consumed.add(ref));
28
+ const actual=files(bundleRoot,source.root_ref).map(ref=>({path:ref.slice(source.root_ref.length+1),digest:hash(readFileSync(safe(bundleRoot,ref)))}));
29
+ ensure(digest([...manifest.files].sort((a,b)=>a.path.localeCompare(b.path)))===digest(actual.sort((a,b)=>a.path.localeCompare(b.path))),'existing-ui-baseline 观测文件集与实际源码不一致');
30
+ let routePrefix='';
31
+ if(data.api_route_mapping) {
32
+ const mapping=data.api_route_mapping;
33
+ for(const binding of [mapping.config,mapping.environment])ensure(binding.ref.startsWith(`${source.root_ref}/`)&&actual.some(file=>`${source.root_ref}/${file.path}`===binding.ref),'existing-ui-baseline 代理映射必须绑定固定源码');
34
+ const config=bound(mapping.config).toString('utf8'),environment=bound(mapping.environment).toString('utf8');
35
+ // A deliberately narrow existing Vite convention. Unrecognized rewrites need review;
36
+ // never execute a package's JavaScript to obtain a routing assertion.
37
+ ensure(/^\s*const apiBase = env\.VITE_API_BASE_URL \|\| ['"]\/api['"]\s*;?\s*$/m.test(config)
38
+ && /proxy:\s*\{\s*\[apiBase\]:\s*\{\s*target:\s*proxyTarget,\s*changeOrigin:\s*true,\s*rewrite:\s*\(path\)\s*=>\s*path\.replace\(new RegExp\(`\^\$\{apiBase\}`\),\s*''\)\s*\}/.test(config),'existing-ui-baseline 不支持或缺少固定 Vite prefix rewrite');
39
+ const values=environment.split(/\r?\n/).filter(line=>/^\s*VITE_API_BASE_URL\s*=/.test(line));
40
+ ensure(values.length===1&&values[0].split('=').slice(1).join('=').trim()===mapping.prefix,'existing-ui-baseline 代理 prefix 与原始环境配置不一致');
41
+ routePrefix=mapping.prefix;
42
+ }
43
+ const build=document(data.build),capture=document(data.capture);
44
+ const openapi=document(data.openapi);bound(data.replay);
45
+ ensure(/^3\.1\.[0-9]+$/.test(openapi.openapi)&&openapi.paths,'existing-ui-baseline 需要 OpenAPI 3.1 原始接口');
46
+ for(const [label,record] of [['build',build],['capture',capture]]) {
47
+ ensure(record.schema_version===1&&record.source_commit===source.source_commit&&record.source_digest===source.digest,`existing-ui-baseline ${label} 未绑定当前源码`);
48
+ ensure(record.exit_code===0&&typeof record.command==='string'&&record.command.trim()&&Number.isFinite(Date.parse(record.executed_at)),`existing-ui-baseline ${label} 缺少成功执行记录`);
49
+ ensure(Array.isArray(record.evidence)&&record.evidence.length,`existing-ui-baseline ${label} 缺少原始日志`);record.evidence.forEach(bound);
50
+ }
51
+ ensure(build.lock_digest===source.lock.digest,'existing-ui-baseline 构建锁文件不一致');
52
+ ensure(build.output?.ref&&build.output.digest===treeDigest(bundleRoot,build.output.ref),'existing-ui-baseline 构建输出不一致');
53
+ files(bundleRoot,build.output.ref).forEach(ref=>consumed.add(ref));
54
+ ensure(capture.build_digest===data.build.digest&&capture.openapi_digest===data.openapi.digest,'existing-ui-baseline 截图运行与构建/API 不一致');
55
+ ensure(capture.ui_change==='none'&&Array.isArray(capture.cases),'existing-ui-baseline 不支持 UI 改动');
56
+ const ids=new Set();
57
+ for(const item of data.cases) {
58
+ ensure(!ids.has(item.case_id),`existing-ui-baseline case_id 重复: ${item.case_id}`);ids.add(item.case_id);
59
+ for(const ref of item.source_refs)ensure(ref.startsWith(`${source.root_ref}/`)&&actual.some(file=>`${source.root_ref}/${file.path}`===ref),`existing-ui-baseline 用例源码悬空: ${ref}`);
60
+ bound(item.actions);const api=document(item.api),image=bound(item.image);
61
+ ensure(image.length<=5*1024*1024,'existing-ui-baseline 单张截图超过 5 MiB');
62
+ ensure(image.length>=24&&image.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10]))&&image.toString('ascii',12,16)==='IHDR','existing-ui-baseline 截图不是 PNG');
63
+ ensure(image.readUInt32BE(16)===item.viewport.width&&image.readUInt32BE(20)===item.viewport.height,'existing-ui-baseline 截图与 viewport 不一致');
64
+ ensure(api.schema_version===1&&api.case_id===item.case_id&&api.openapi_digest===data.openapi.digest&&api.source_digest===source.digest&&api.build_digest===data.build.digest,'existing-ui-baseline API 证据来源不一致');
65
+ ensure(Array.isArray(api.exchanges)&&api.exchanges.length,'existing-ui-baseline 缺少真实 API 请求/响应');
66
+ for(const exchange of api.exchanges) {
67
+ ensure(typeof exchange.operation_id==='string'&&exchange.operation_id&&typeof exchange.method==='string'&&/^https?:\/\//.test(exchange.url)&&Number.isFinite(Date.parse(exchange.executed_at))&&Number.isInteger(exchange.status)&&exchange.status>=100&&exchange.status<=599,'existing-ui-baseline API 交换记录无效');
68
+ const operation=Object.entries(openapi.paths).flatMap(([route,item])=>Object.entries(item).filter(([method,value])=>value?.operationId===exchange.operation_id&&method.toUpperCase()===exchange.method.toUpperCase()).map(()=>route));
69
+ ensure(operation.length===1,'existing-ui-baseline API operation 与冻结接口不一致');
70
+ const routePattern=new RegExp(`^${operation[0].split('/').map(part=>/^\{[^}]+\}$/.test(part)?'[^/]+':part.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join('/')}$`);
71
+ const observedPath=new URL(exchange.url).pathname;
72
+ ensure(!routePrefix||observedPath.startsWith(`${routePrefix}/`),'existing-ui-baseline API URL 与代理 prefix 不一致');
73
+ ensure(routePattern.test(routePrefix?observedPath.slice(routePrefix.length):observedPath),'existing-ui-baseline API URL 与冻结接口路径不一致');
74
+ bound(exchange.request);bound(exchange.response);
75
+ }
76
+ const observed=capture.cases.filter(entry=>entry.case_id===item.case_id);
77
+ ensure(observed.length===1,'existing-ui-baseline 缺少或重复 capture case');
78
+ for(const key of ['route','state','viewport','source_refs','actions','image','api'])ensure(digest(observed[0][key])===digest(item[key]),`existing-ui-baseline 截图动作绑定不一致: ${item.case_id}/${key}`);
79
+ }
80
+ ensure(capture.cases.length===ids.size,'existing-ui-baseline capture 含未登记用例');
81
+ ensure(allFiles.every(ref=>consumed.has(ref)),'existing-ui-baseline 包含未绑定原始文件');
82
+ } catch(error) { errors.push(error.message); }
83
+ return {errors};
84
+ }
@@ -7,14 +7,27 @@ import { ROOT, read, safe, ensure, digest } from './strategic-handoff-io.mjs';
7
7
  // Shared synchronous adapter for existing compiler, lifecycle and task-package seams.
8
8
  // Always re-executes the verifier; a caller-supplied "verified" flag is never evidence.
9
9
  export function enforceFrontendDelivery(state={}, {root=ROOT,phase='inputs'}={}) {
10
+ ensure(['preflight','design','contract','inputs','implementation','verification'].includes(phase),`未知前端交付阶段: ${phase}`);
10
11
  const profile=loadDeliveryProfile(root);
11
12
  let contract;
12
13
  const contractRef=state.slice_contract_ref||state.contract?.slice_contract_ref;
13
- const selected=profile?.profile_id==='harness.frontend-delivery'||profile?.frontend_delivery?.required===true;
14
+ const dedicatedProfile=profile?.profile_id==='harness.frontend-delivery'||profile?.profile_id==='yss-harness-frontend';
15
+ const selected=dedicatedProfile||profile?.frontend_delivery?.required===true||profile?.handoff?.consumer_capabilities?.includes('frontend-engineering-design');
14
16
  const identity=selected?read(safe(root,'yss-project.yaml')):null;
15
17
  if(selected)ensure(identity.schema_version===1&&['template-source','project-instance'].includes(identity.repository_mode),'前端项目仓库身份无效');
16
- const dedicated=selected&&identity.repository_mode==='project-instance';
18
+ const dedicated=dedicatedProfile&&identity.repository_mode==='project-instance';
17
19
  if(dedicated)ensure(state.contract?.kind!=='template-maintenance','前端产品项目不得用模板维护任务绕过输入条件');
20
+ if(['preflight','design'].includes(phase)) {
21
+ const preflight=state.frontend_preflight||state.frontend?.preflight||state.resolution?.frontend_preflight;
22
+ ensure(preflight?.preflight_ref,'frontend-preflight-required: 缺少 Frontend Strategic Preflight');
23
+ const args=[path.join(ROOT,'scripts/verify-frontend-strategic-preflight'),'--root',root];
24
+ if(preflight.digest)args.push('--expected-digest',preflight.digest);
25
+ args.push(preflight.preflight_ref);
26
+ const verified=spawnSync(process.execPath,args,{cwd:root,encoding:'utf8',timeout:60000,maxBuffer:2*1024*1024});
27
+ ensure(verified.status===0,`frontend-preflight-blocked: ${verified.error?.message||verified.stderr||verified.stdout}`);
28
+ const result=JSON.parse(verified.stdout);ensure(result.result==='preflight-verified'&&result.ready_for_agent===false,'前端战略预检返回非法状态');
29
+ return result;
30
+ }
18
31
  let binding=state.frontend_delivery||state.frontend?.delivery||state.resolution?.frontend_delivery;
19
32
  if(contractRef) {
20
33
  // Legacy callers may supply an absolute external contract. Discover whether
@@ -27,17 +40,20 @@ export function enforceFrontendDelivery(state={}, {root=ROOT,phase='inputs'}={})
27
40
  binding=direct||resolved||binding;
28
41
  }
29
42
  }
30
- if(!dedicated&&!binding)return {result:'not-applicable'};
43
+ const frontendStatus=contract?.frontend?.status||state.frontend?.status;
44
+ const contractRequiresFrontend=typeof frontendStatus==='string'&&!['not-applicable','disabled'].includes(frontendStatus);
45
+ if(!dedicated&&!binding&&!contractRequiresFrontend)return {result:'not-applicable'};
31
46
  if(contractRef)safe(root,contractRef);
32
47
  ensure(binding?.acceptance_ref,'frontend-delivery-required: 缺少战略与后端联合接收记录');
33
48
  const slice=contract?.slice_id||state.slice_id;
34
49
  ensure(!contract?.slice_id||!state.slice_id||contract.slice_id===state.slice_id,'前端任务与 Slice Contract 切片不一致');
35
50
  ensure(slice,'frontend-delivery-required: 缺少明确切片');
36
- if(phase==='implementation') {
51
+ if(['implementation','verification'].includes(phase)) {
37
52
  ensure(contract?.status==='approved','frontend-delivery-required: 必须读取已持久化的 approved Slice Contract');
38
53
  ensure(contract.frontend?.delivery||contract.resolution?.frontend_delivery,'frontend-delivery-required: Slice Contract 未绑定前端交付');
39
54
  ensure(binding.digest,'frontend-delivery-required: Slice Contract 未冻结接收记录摘要');
40
55
  }
56
+ if(phase==='verification')ensure(state.frontend_implementation_verification_ref||contract?.frontend?.implementation_verification_ref,'frontend-delivery-required: 验证阶段缺少 frontend_implementation_verification 引用');
41
57
  const args=[path.join(ROOT,'scripts/verify-frontend-delivery'),'--root',root,'--slice',slice];
42
58
  if(binding.digest)args.push('--expected-digest',binding.digest);
43
59
  args.push(binding.acceptance_ref);
@@ -1,9 +1,53 @@
1
+ import { uiBaselineRef, uiBaselineCaseIds, uiBaselineKind, hasConsumerRoutes } from './ui-baseline.mjs';
1
2
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
3
  import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
3
5
  import { openBackendDelivery } from './backend-delivery.mjs';
4
6
  import { openBundle } from './strategic-handoff.mjs';
5
7
  import { verifyConsumption } from './strategic-handoff-consumption.mjs';
6
- import { read, safe, ensure, hash, schema, project } from './strategic-handoff-io.mjs';
8
+ import { read, safe, ensure, hash, schema, project, ROOT } from './strategic-handoff-io.mjs';
9
+
10
+ const ACCEPTANCE_SCHEMAS=new Map([[1,'docs/process/schemas/frontend-delivery-acceptance-v1.schema.json'],[2,'docs/process/schemas/frontend-delivery-acceptance.schema.json'],[3,'docs/process/schemas/frontend-delivery-acceptance-v3.schema.json']]);
11
+
12
+ function acceptanceSchema(version) {
13
+ ensure(ACCEPTANCE_SCHEMAS.has(version),`Frontend Delivery Acceptance 未知 schema_version: ${String(version)};支持版本: 1, 2, 3;既有 UI 接收请使用 3`);
14
+ return ACCEPTANCE_SCHEMAS.get(version);
15
+ }
16
+
17
+ export async function verifyFrontendStrategicPreflight({root=process.cwd(),preflightRef,expectedDigest,readOnly=false}={}) {
18
+ project(root);
19
+ const file=safe(root,preflightRef),preflight=read(file);
20
+ ensure([1,2].includes(preflight.schema_version),'Frontend Strategic Preflight 未知 schema_version');
21
+ schema(preflight,preflight.schema_version===2?'docs/process/schemas/frontend-strategic-preflight-v2.schema.json':'docs/process/schemas/frontend-strategic-preflight.schema.json');
22
+ const preflightDigest=hash(readFileSync(file));
23
+ if(expectedDigest)ensure(expectedDigest===preflightDigest,'前端战略预检摘要已变化,需重编译合同');
24
+ ensure(preflight.status==='verified','前端战略预检尚未 verified');
25
+ const receipt=read(safe(root,preflight.import_receipt_ref));
26
+ ensure(receipt.schema_version===2,'Frontend Strategic Preflight 仅接受 Import Receipt v2');
27
+ const base=`docs/handoffs/${receipt.bundle_id}/${receipt.version}`;
28
+ ensure(preflight.import_receipt_ref===`${base}/import-receipt.json`&&receipt.package_ref===`${base}/package`,'战略预检收据路径与身份不匹配');
29
+ ensure(preflight.bundle_digest===receipt.bundle_digest,'战略预检与收据摘要不一致');
30
+ const routeReceipt=receipt.routes.find(item=>item.route_id===preflight.route_id&&item.capability==='frontend-engineering-design');
31
+ ensure(routeReceipt&&routeReceipt.activation!=='not-applicable','当前 Handoff 未启用前端工程设计路由');
32
+ const reconciliation=spawnSync(process.execPath,[path.join(ROOT,'scripts/verify-context-reconciliation'),'--root',root,safe(root,preflight.context_reconciliation_ref)],{encoding:'utf8'});
33
+ ensure(reconciliation.status===0,`前端战略预检 Context Reconciliation 未通过: ${reconciliation.error?.message||reconciliation.stderr}`);
34
+ return openBundle(safe(root,receipt.package_ref),bundle=>{
35
+ ensure(hasConsumerRoutes(bundle.handoff)&&bundle.manifest.bundle_digest===receipt.bundle_digest,'前端战略预检必须绑定支持消费者路由的当前交接包');
36
+ const frontendRoute=bundle.handoff.consumer_routes.find(item=>item.route_id===preflight.route_id&&item.capability==='frontend-engineering-design');
37
+ const backendRoute=bundle.handoff.consumer_routes.find(item=>item.capability==='backend-technical-design');
38
+ ensure(frontendRoute&&frontendRoute.activation!=='not-applicable','前端路由未启用');
39
+ const known=[...bundle.indexes.rules.map(item=>item.rule_id),...bundle.indexes.scenarios.filter(item=>item.critical).map(item=>item.scenario_id)].sort();
40
+ ensure(JSON.stringify([...preflight.source_rule_refs].sort())===JSON.stringify(known),'前端战略预检未完整绑定源规则/关键场景');
41
+ ensure((preflight.schema_version===2)===(bundle.handoff.schema_version===5),'前端预检与 Handoff 协议版本不匹配,禁止降级');
42
+ if(preflight.schema_version===2)ensure(preflight.ui_baseline_kind===uiBaselineKind(bundle.handoff),'前端预检基线类型不一致');
43
+ const expectedVisual=`${base}/package/payload/files/${uiBaselineRef(bundle.handoff).persisted_ref}/${uiBaselineRef(bundle.handoff).manifest_ref}`;
44
+ ensure((preflight.schema_version===2?preflight.ui_baseline_ref:preflight.visual_baseline_ref)===expectedVisual,'前端战略预检 Visual Baseline 引用不一致');
45
+ const expectedMode=backendRoute.activation==='not-applicable'?'not-applicable':'required';
46
+ ensure(preflight.backend_dependency.mode===expectedMode&&preflight.backend_dependency.route_id===backendRoute.route_id,'前端战略预检后端依赖与消费者路由不一致');
47
+ if(expectedMode==='not-applicable')ensure(preflight.backend_dependency.reason===backendRoute.reason&&JSON.stringify(preflight.backend_dependency.impact_refs)===JSON.stringify(backendRoute.impact_refs)&&JSON.stringify(preflight.backend_dependency.evidence_refs)===JSON.stringify(backendRoute.evidence_refs),'backend-not-applicable 依据与战略路由不一致');
48
+ return {result:'preflight-verified',ready_for_agent:false,preflight_ref:preflightRef,preflight_digest:preflightDigest,strategic_bundle_digest:receipt.bundle_digest,ui_baseline_kind:uiBaselineKind(bundle.handoff),preflight_schema_version:preflight.schema_version,backend_dependency:expectedMode,next_action:'prepare-frontend-engineering-design-draft'};
49
+ },{readOnly});
50
+ }
7
51
 
8
52
  function pointer(value, ref) {
9
53
  ensure(ref.startsWith('/')&&!ref.includes('//'),'服务版本 JSON Pointer 无效');
@@ -35,10 +79,36 @@ export async function probeBackend(delivery) {
35
79
  export async function verifyFrontendDelivery({root=process.cwd(),acceptanceRef,sliceRef,expectedDigest}) {
36
80
  project(root);
37
81
  const file=safe(root,acceptanceRef), acceptance=read(file);
38
- schema(acceptance,'docs/process/schemas/frontend-delivery-acceptance.schema.json');
82
+ schema(acceptance,acceptanceSchema(acceptance?.schema_version));
39
83
  const acceptanceDigest=hash(readFileSync(file));
40
84
  if(expectedDigest)ensure(expectedDigest===acceptanceDigest,'前端接收记录摘要已变化,需重编译合同');
41
85
  ensure(sliceRef&&acceptance.slice_id===sliceRef,'前端接收记录与执行切片不匹配');
86
+ if(acceptance.schema_version>=2) {
87
+ const preflight=await verifyFrontendStrategicPreflight({root,preflightRef:acceptance.strategic_preflight.ref,expectedDigest:acceptance.strategic_preflight.digest});
88
+ ensure((acceptance.schema_version===3)===(preflight.preflight_schema_version===2),'前端接收与预检协议版本不匹配,禁止降级');
89
+ if(acceptance.schema_version===3)ensure(acceptance.ui_baseline_kind===preflight.ui_baseline_kind,'前端接收基线类型不一致');
90
+ ensure(preflight.strategic_bundle_digest===acceptance.strategic_handoff.bundle_digest,'前端接收与战略预检版本不一致');
91
+ if(acceptance.backend_dependency.mode==='not-applicable') {
92
+ ensure(!acceptance.backend_delivery,'backend-not-applicable 不得绑定后端交付收据');
93
+ const strategic=await verifyConsumption(acceptance,{root,sliceRef,consumer:'frontend'});
94
+ ensure(strategic.result==='verified',`战略承接阻断: ${JSON.stringify(strategic.issues)}`);
95
+ const strategicReceipt=read(safe(root,acceptance.strategic_handoff.import_receipt_ref));
96
+ return openBundle(safe(root,strategicReceipt.package_ref),bundle=>{
97
+ ensure(acceptance.schema_version===3||bundle.handoff.schema_version!==5,'Handoff v5 必须使用前端接收 v3');
98
+ const visualCases=uiBaselineCaseIds(bundle.handoff);
99
+ const sourceIds=new Set([...bundle.indexes.rules.map(item=>item.rule_id),...bundle.indexes.scenarios.filter(item=>item.critical).map(item=>item.scenario_id)]);
100
+ const ids=acceptance.frontend_cases.map(item=>item.case_id);ensure(new Set(ids).size===ids.length,'前端验收用例 ID 重复');
101
+ for(const item of acceptance.frontend_cases){
102
+ ensure(item.operation_ids.length===0,'backend-not-applicable 前端用例不得绑定 operation_ids');
103
+ ensure(item.source_ids.every(id=>sourceIds.has(id)),'前端用例引用未知规则/场景');
104
+ ensure((acceptance.schema_version===3?item.baseline_case_ids:item.visual_case_ids).every(id=>visualCases.includes(id)),'前端用例视觉基线悬空');
105
+ const evidence=readFileSync(safe(root,item.evidence_ref));ensure(evidence.length>0&&hash(evidence)===item.evidence_digest,'前端承接用例说明为空或摘要漂移');
106
+ }
107
+ return {result:'inputs-verified',ready_for_agent:false,slice_id:sliceRef,acceptance_ref:acceptanceRef,acceptance_digest:acceptanceDigest,strategic_bundle_digest:strategic.consumed_bundle_digest,backend_dependency:'not-applicable',next_action:'prepare-or-revalidate-frontend-plan-and-slice-contract'};
108
+ });
109
+ }
110
+ ensure(acceptance.backend_dependency.mode==='required','frontend backend_dependency.mode 非法');
111
+ }
42
112
  const binding=acceptance.backend_delivery, receipt=read(safe(root,binding.import_receipt_ref));
43
113
  const base=`docs/backend-deliveries/${receipt.delivery_id}/${receipt.version}`;
44
114
  ensure(binding.import_receipt_ref===`${base}/import-receipt.json`&&receipt.package_ref===`${base}/package`,'后端收据路径与身份不匹配');
@@ -54,11 +124,13 @@ export async function verifyFrontendDelivery({root=process.cwd(),acceptanceRef,s
54
124
  const ids=acceptance.frontend_cases.map(item=>item.case_id);
55
125
  ensure(new Set(ids).size===ids.length,'前端验收用例 ID 重复');
56
126
  await openBundle(safe(backend.source,delivery.strategic_bundle_ref),bundle=>{
57
- const visualCases=bundle.handoff.source.visual_baseline_ref.case_ids;
127
+ ensure(acceptance.schema_version===3||bundle.handoff.schema_version!==5,'Handoff v5 必须使用前端接收 v3');
128
+ const visualCases=uiBaselineCaseIds(bundle.handoff);
58
129
  for(const item of acceptance.frontend_cases) {
130
+ if(acceptance.schema_version>=2)ensure(item.operation_ids.length>0,'有后端依赖时前端用例必须绑定已交付 operation_ids');
59
131
  ensure(item.operation_ids.every(id=>delivery.scope.operation_ids.includes(id)),'前端用例依赖未交付接口');
60
132
  ensure(item.source_ids.every(id=>delivery.scope.source_ids.includes(id)),'前端用例依赖未交付规则/场景');
61
- ensure(item.visual_case_ids.every(id=>visualCases.includes(id)),'前端用例视觉基线悬空');
133
+ ensure((acceptance.schema_version===3?item.baseline_case_ids:item.visual_case_ids).every(id=>visualCases.includes(id)),'前端用例视觉基线悬空');
62
134
  const evidence=readFileSync(safe(root,item.evidence_ref));
63
135
  ensure(evidence.length>0&&hash(evidence)===item.evidence_digest,'前端承接用例说明为空或摘要漂移');
64
136
  }
@@ -1,3 +1,4 @@
1
+ import { createApprovedExecutionContext, createApprovedRecompilationContext, assertApprovedExecutionContext } from './approved-execution-context.mjs';
1
2
  import { enforceTechnicalDesign } from './technical-design-boundary.mjs';
2
3
  import { enforceHarnessSkillScope } from "./harness-execution-scope.mjs";
3
4
  import { createHash } from "node:crypto";
@@ -6,7 +7,7 @@ import path from "node:path";
6
7
  import { parseDocument } from "../vendor/yaml.mjs";
7
8
  import { DEFAULT_REGISTRY, loadSkillRegistry } from "./skill-registry.mjs";
8
9
  import { ROOT } from "./skill-supply-chain.mjs";
9
- import { architectureDigest, assertArchitectureAgreement, validateArchitectureIdentity } from "./backend-architecture.mjs";
10
+ import { architectureDigest, assertArchitectureAgreement, validateArchitectureIdentity, verifyArchitectureEvidence } from "./backend-architecture.mjs";
10
11
  import { enforceFrontendDelivery } from "./frontend-delivery-boundary.mjs";
11
12
 
12
13
  export const DEFAULT_COMPILER_CONTRACT = path.join(
@@ -65,12 +66,14 @@ export function compileImplementationContract({
65
66
  compilerContractDigest,
66
67
  architecture_identity,
67
68
  architecture_evidence,
69
+ approved_slice,
68
70
  root = ROOT,
69
71
  slice_id,
70
72
  frontend_delivery,
71
73
  technical_design
72
74
  }) {
73
75
  assertV2(registry, compilerContract);
76
+ const recompilationExecution=approved_slice?createApprovedRecompilationContext(approved_slice,{root}):undefined;
74
77
  const deliveryInput = enforceFrontendDelivery({ slice_id, frontend_delivery }, { root });
75
78
  if (!Array.isArray(recipeIds) || !Array.isArray(requiredCapabilities) || !Array.isArray(conditions)) {
76
79
  fail("recipeIds、requiredCapabilities 与 conditions 必须是数组");
@@ -89,11 +92,11 @@ export function compileImplementationContract({
89
92
  if (architectural || architecture_identity) {
90
93
  architectureProfile = validateArchitectureIdentity(architecture_identity, registry);
91
94
  if (!architecture_evidence?.engineering_baseline || !architecture_evidence?.repository_registration || !architecture_evidence?.manifest) fail("缺少工程基线、仓库登记或 Manifest 架构证据");
92
- assertArchitectureAgreement(architecture_identity, architecture_evidence, registry);
95
+ verifyArchitectureEvidence(architecture_identity, architecture_evidence, { root, registry, execution:recompilationExecution });
93
96
  for (const recipe of orderedRecipes) if (recipe.architecture_family && recipe.architecture_family !== architecture_identity.architecture_family) fail(`Recipe ${recipe.id} 与架构族不匹配`);
94
97
  }
95
98
 
96
- enforceTechnicalDesign({ technical_design, conditions, architecture_identity, slice_id }, { root });
99
+ enforceTechnicalDesign({ technical_design, conditions, architecture_identity, slice_id }, { root, execution:recompilationExecution });
97
100
 
98
101
  const capabilitySources = new Map();
99
102
  const orderedCapabilities = [];
@@ -175,6 +178,7 @@ export function compileImplementationContract({
175
178
  ...(architecture_identity ? {
176
179
  architecture_identity: structuredClone(architecture_identity),
177
180
  architecture_identity_digest: architectureDigest(architecture_identity),
181
+ ...(architecture_identity.schema_version === 2 ? { architecture_evidence: structuredClone(architecture_evidence) } : {}),
178
182
  profile_maturity: architectureProfile.maturity,
179
183
  readiness_blockers: architectureProfile.maturity === "supported" ? [] : ["backend-profile-not-supported"],
180
184
  skill_profiles: Object.fromEntries(orderedSkills.map((skill) => [skill, architecture_identity.architecture_profile]))
@@ -202,12 +206,17 @@ export function compileDefaultImplementationContract(input = {}) {
202
206
  });
203
207
  }
204
208
 
205
- export function evaluateContractFreshness(contract, { registry, compilerContract, root = ROOT }) {
209
+ export function evaluateContractFreshness(contract, { registry, compilerContract, root = ROOT, approved_slice, readOnly = false }, execution) {
206
210
  assertV2(registry, compilerContract);
207
211
  if (contract?.schema_version !== 2) fail("Slice Implementation Contract schema v1 已停止支持;必须重新编译 v2 合同");
208
212
  const resolution = contract.resolution ?? contract;
209
213
  const reasons = [];
210
- try { enforceTechnicalDesign({ ...resolution, slice_id: contract.slice_id ?? resolution.slice_id }, { root }); }
214
+ let trustedExecution;
215
+ try {
216
+ trustedExecution=approved_slice?createApprovedExecutionContext(approved_slice,{root,contract}):execution;
217
+ if(trustedExecution)assertApprovedExecutionContext(trustedExecution,{root,contract});
218
+ } catch(error) { reasons.push(error.code||'EXECUTION_APPROVAL_INVALID');trustedExecution=undefined; }
219
+ try { enforceTechnicalDesign({ ...resolution, slice_id: contract.slice_id ?? resolution.slice_id }, { root,execution:trustedExecution,readOnly }); }
211
220
  catch { reasons.push("technical-design-stale-or-unavailable"); }
212
221
  try { enforceFrontendDelivery(contract, { root }); }
213
222
  catch { reasons.push("frontend-delivery-stale-or-unavailable"); }
@@ -216,6 +225,10 @@ export function evaluateContractFreshness(contract, { registry, compilerContract
216
225
  if (resolution.architecture_identity) {
217
226
  try { validateArchitectureIdentity(resolution.architecture_identity, registry); }
218
227
  catch { reasons.push("architecture-identity-invalid"); }
228
+ if (resolution.architecture_identity.schema_version === 2) {
229
+ try { verifyArchitectureEvidence(resolution.architecture_identity, resolution.architecture_evidence, { root, registry, execution:trustedExecution }); }
230
+ catch (error) { reasons.push(error.code ?? "architecture-evidence-stale-or-unavailable"); }
231
+ }
219
232
  if (resolution.architecture_identity_digest !== architectureDigest(resolution.architecture_identity)) reasons.push("architecture-identity-digest-changed");
220
233
  }
221
234
  return { freshness: reasons.length ? "stale" : "current", reasons };
@@ -225,6 +238,7 @@ export function validateExecutionResult(result, contract, current) {
225
238
  if (result?.schema_version !== 2) fail("YSS Skill Execution Result schema v1 已停止支持;请迁移到 v2");
226
239
  if (contract?.schema_version !== 2) fail("Slice Implementation Contract schema v1 已停止支持;必须重新编译 v2 合同");
227
240
  if (!EXECUTION_STATUSES.has(result.status)) fail(`未知 execution result status: ${result.status}`);
241
+ if(contract.resolution?.architecture_identity?.schema_version===2&&!current?.approved_slice)return {status:'stale',blockers:['EXECUTION_APPROVAL_REQUIRED']};
228
242
  const freshness = evaluateContractFreshness(contract, current);
229
243
  if (freshness.freshness === "stale") return { status: "stale", blockers: freshness.reasons };
230
244
  const consumed = result.consumed_contract ?? {};
@@ -9,7 +9,15 @@ import traceback
9
9
  from jsonschema import Draft202012Validator, FormatChecker
10
10
  validators = {}
11
11
  results = []
12
- for item in json.load(sys.stdin):
12
+ expected_bytes = int(sys.argv[1])
13
+ payload = sys.stdin.buffer.read(expected_bytes)
14
+ if len(payload) != expected_bytes:
15
+ raise ValueError(f"JSON_SCHEMA_PROTOCOL: truncated input frame: expected {expected_bytes} bytes, received {len(payload)}")
16
+ try:
17
+ jobs = json.loads(payload.decode('utf-8'))
18
+ except (UnicodeDecodeError, json.JSONDecodeError) as error:
19
+ raise ValueError(f"JSON_SCHEMA_PROTOCOL: invalid UTF-8 JSON input frame: {error}") from error
20
+ for item in jobs:
13
21
  try:
14
22
  key = (item['schemaPath'], item['formatChecker'])
15
23
  if key not in validators:
@@ -29,7 +37,8 @@ json.dump(results, sys.stdout)
29
37
  `;
30
38
 
31
39
  /** One immutable validation phase, never a cache of successful work units. */
32
- export function validateJsonSchemas(items, { cwd = process.cwd() } = {}) {
40
+ export function validateJsonSchemas(items, { cwd = process.cwd(), timeoutMs = 30000 } = {}) {
41
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new TypeError("schema timeoutMs 必须是正整数毫秒数");
33
42
  if (!Array.isArray(items)) throw new TypeError("schema batch 必须是数组");
34
43
  if (!items.length) return [];
35
44
  const jobs = items.map(({ value, schemaPath, formatChecker = true, errorStyle = "compact", label = "JSON Schema" }) => {
@@ -38,9 +47,15 @@ export function validateJsonSchemas(items, { cwd = process.cwd() } = {}) {
38
47
  if (!["compact", "verbose"].includes(errorStyle)) throw new TypeError(`未知 schema errorStyle: ${errorStyle}`);
39
48
  return { value, schemaPath: effectiveSchema, formatChecker, errorStyle };
40
49
  });
41
- const result = spawnSync("python3", ["-c", validator], { cwd, encoding: "utf8", input: JSON.stringify(jobs), maxBuffer: 16 * 1024 * 1024 });
50
+ // readall waits for EOF even after the complete JSON has arrived. A known UTF-8
51
+ // byte frame lets the validator finish when a transport keeps its write end open.
52
+ const input = Buffer.from(JSON.stringify(jobs), "utf8");
53
+ const result = spawnSync("python3", ["-c", validator, String(input.length)], { cwd, encoding: "utf8", input, timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 });
54
+ if (result.error?.code === "ETIMEDOUT") throw new TypeError(`JSON_SCHEMA_TIMEOUT: schema validation exceeded ${timeoutMs}ms`);
42
55
  if (result.error || result.status !== 0) throw new TypeError(result.error?.message || `${result.stdout}${result.stderr}`.trim());
43
- const results = JSON.parse(result.stdout);
56
+ let results;
57
+ try { results = JSON.parse(result.stdout); } catch { throw new TypeError("JSON_SCHEMA_PROTOCOL: validator returned invalid JSON"); }
58
+ if (!Array.isArray(results) || results.some(item => !item || typeof item.valid !== "boolean" || typeof item.error !== "string")) throw new TypeError("JSON_SCHEMA_PROTOCOL: validator returned invalid result records");
44
59
  if (results.length !== jobs.length) throw new TypeError("schema batch 返回数量不匹配");
45
60
  return results;
46
61
  }
@@ -334,3 +334,29 @@ export function reportProfileSkillSync(plan) {
334
334
  for (const item of [...plan.changes, ...plan.issues]) counts[item.status] = (counts[item.status] ?? 0) + 1;
335
335
  return { ...plan, changes: plan.changes.map(({ content, ...item }) => item), counts };
336
336
  }
337
+
338
+ export function formatProfileSkillSync(report, mode = "--dry-run") {
339
+ const affected = [...new Set([...report.changes, ...report.issues].map(({ profile }) => profile))];
340
+ const lines = [
341
+ `profile skills: ${report.profiles.join(", ")}`,
342
+ `模式:${mode === "--apply" ? "已执行同步" : mode === "--check" ? "只读检查" : "只读预览"}`,
343
+ `受影响 profile:${affected.join(", ") || "无"}`,
344
+ ];
345
+ for (const profile of affected) {
346
+ const skills = [...new Set(report.changes.filter((item) => item.profile === profile && item.skill).map(({ skill }) => skill))];
347
+ lines.push(`${profile} 变更技能:${skills.join(", ") || "无(请查看资产差异或问题)"}`);
348
+ }
349
+ lines.push(`已有问题:${report.issues.length} 项(含冲突、缺失依赖与未登记差异)`);
350
+ for (const [status, count] of Object.entries(report.counts)) lines.push(`${status}: ${count}`);
351
+ for (const item of report.changes) lines.push(`${item.profile}: ${item.status}: ${item.path}`);
352
+ if (!affected.length) lines.push("本次未发现同步差异或问题;不代表项目验证已通过。");
353
+ else {
354
+ // Profile names come from configuration; quote them as literal shell arguments.
355
+ const profiles = affected.map((profile) => `--profile '${profile.replaceAll("'", "'\\''")}'`).join(" ");
356
+ lines.push(`后续同步一致性检查(待执行;有差异或冲突时先处理):scripts/sync-profile-skills --check ${profiles}`);
357
+ lines.push("本仓 Skill 治理验证(待执行):scripts/verify-skill-governance");
358
+ }
359
+ lines.push("文件差异、分类与 SHA-256 继续使用现有 --json 报告;本摘要不判断下游项目兼容性。");
360
+ const issues = report.issues.map((item) => [item.profile, item.status, item.skill, item.path, item.message, item.marker === undefined ? undefined : `marker=${item.marker}`].filter((value) => value !== undefined).join(": "));
361
+ return { stdout: `${lines.join("\n")}\n`, stderr: issues.length ? `${issues.join("\n")}\n` : "" };
362
+ }
@@ -0,0 +1,33 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { openBundle } from './strategic-handoff.mjs';
3
+ import { read, safe, ensure, hash, schema, project } from './strategic-handoff-io.mjs';
4
+
5
+ export async function verifyConsumerFeedback({root=process.cwd(),feedbackRef}={}) {
6
+ project(root);const file=safe(root,feedbackRef),feedback=read(file);
7
+ schema(feedback,'docs/process/schemas/strategic-consumer-feedback.schema.json');
8
+ const receipt=read(safe(root,feedback.import_receipt_ref));
9
+ ensure(receipt.schema_version===2,'Consumer Feedback v1 只接受 Handoff v4 的 Import Receipt v2');
10
+ const base=`docs/handoffs/${receipt.bundle_id}/${receipt.version}`;
11
+ ensure(feedback.import_receipt_ref===`${base}/import-receipt.json`&&receipt.package_ref===`${base}/package`,'Consumer Feedback 导入收据路径无效');
12
+ ensure(feedback.bundle.id===receipt.bundle_id&&feedback.bundle.version===receipt.version&&feedback.bundle.digest===receipt.bundle_digest,'Consumer Feedback 绑定的 bundle 身份或摘要不一致');
13
+ const route=receipt.routes.find(item=>item.route_id===feedback.route_id);ensure(route&&route.activation!=='not-applicable','Consumer Feedback route_id 未被当前消费者激活');
14
+ for(const ref of feedback.evidence_refs)ensure(readFileSync(safe(root,ref)).length>0,`Consumer Feedback evidence 为空: ${ref}`);
15
+ return openBundle(safe(root,receipt.package_ref),bundle=>{
16
+ ensure(bundle.handoff.schema_version===4&&bundle.manifest.bundle_digest===receipt.bundle_digest,'Consumer Feedback 必须绑定当前 Handoff v4 包');
17
+ const source=[...bundle.indexes.rules.map(item=>({id:item.rule_id,digest:item.source_digest})),...bundle.indexes.scenarios.map(item=>({id:item.scenario_id,digest:item.source_digest}))].find(item=>item.id===feedback.source.id);
18
+ ensure(source&&source.digest===feedback.source.digest,'Consumer Feedback source ID 或摘要不是当前包内容');
19
+ return {result:'feedback-verified',feedback_ref:feedbackRef,feedback_digest:hash(readFileSync(file)),severity:feedback.severity,route_id:feedback.route_id,bundle_digest:receipt.bundle_digest};
20
+ });
21
+ }
22
+
23
+ export async function verifyFeedbackAdjudication({root=process.cwd(),adjudicationRef}={}) {
24
+ project(root);const file=safe(root,adjudicationRef),adjudication=read(file);
25
+ schema(adjudication,'docs/process/schemas/strategic-feedback-adjudication.schema.json');
26
+ const feedbackFile=safe(root,adjudication.feedback.ref);ensure(hash(readFileSync(feedbackFile))===adjudication.feedback.digest,'Feedback Adjudication 绑定的 feedback digest 已失效');
27
+ const verified=await verifyConsumerFeedback({root,feedbackRef:adjudication.feedback.ref});
28
+ for(const ref of adjudication.evidence_refs)ensure(readFileSync(safe(root,ref)).length>0,`Feedback Adjudication evidence 为空: ${ref}`);
29
+ if(adjudication.approval_ref)ensure(readFileSync(safe(root,adjudication.approval_ref)).length>0,'Feedback Adjudication approval_ref 为空');
30
+ if(adjudication.decision==='amend')return {result:'blocked',decision:'amend',requires_new_handoff:true,invalidated_bundle_digest:verified.bundle_digest,next_work_unit:adjudication.return_work_unit,target_version:adjudication.target_version};
31
+ if(adjudication.decision==='defer'&&verified.severity==='blocking')return {result:'blocked',decision:'defer',requires_new_handoff:false,next_work_unit:adjudication.return_work_unit,target_version:adjudication.target_version};
32
+ return {result:'adjudicated',decision:adjudication.decision,may_continue:true,bundle_digest:verified.bundle_digest,target_version:adjudication.target_version};
33
+ }