create-yss-spec 3.3.6 → 3.3.7

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 (103) hide show
  1. package/README.md +2 -2
  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 +5 -3
  6. package/template/.agents/skills/archify/SKILL.md +1 -1
  7. package/template/.agents/skills/yss-stage-decision/SKILL.md +13 -4
  8. package/template/.agents/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  9. package/template/.agents/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  10. package/template/.agents/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  11. package/template/.agents/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  12. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  13. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  14. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  15. package/template/.agents/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  16. package/template/.agents/skills/yss-stage-decision/references/validation-rules.md +2 -2
  17. package/template/.agents/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  18. package/template/.agents/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  19. package/template/.agents/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  20. package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  21. package/template/.agents/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  22. package/template/.agents/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  23. package/template/.codex/skills/archify/SKILL.md +1 -1
  24. package/template/.codex/skills/yss-stage-decision/SKILL.md +13 -4
  25. package/template/.codex/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  26. package/template/.codex/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  27. package/template/.codex/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  28. package/template/.codex/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  29. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  30. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  31. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  32. package/template/.codex/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  33. package/template/.codex/skills/yss-stage-decision/references/validation-rules.md +2 -2
  34. package/template/.codex/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  35. package/template/.codex/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  36. package/template/.codex/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  37. package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  38. package/template/.codex/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  39. package/template/.codex/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  40. package/template/.cursor/skills/archify/SKILL.md +1 -1
  41. package/template/.cursor/skills/yss-stage-decision/SKILL.md +13 -4
  42. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  43. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  44. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  45. package/template/.cursor/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  46. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  47. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  48. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  49. package/template/.cursor/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  50. package/template/.cursor/skills/yss-stage-decision/references/validation-rules.md +2 -2
  51. package/template/.cursor/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  52. package/template/.cursor/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  53. package/template/.cursor/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  54. package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  55. package/template/.cursor/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  56. package/template/.cursor/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  57. package/template/.pi/skills/archify/SKILL.md +1 -1
  58. package/template/.pi/skills/yss-stage-decision/SKILL.md +13 -4
  59. package/template/.pi/skills/yss-stage-decision/references/domain-strategy-contract.md +4 -3
  60. package/template/.pi/skills/yss-stage-decision/references/domain-strategy-v2.schema.json +8 -0
  61. package/template/.pi/skills/yss-stage-decision/references/domain-strategy-v3.schema.json +8 -0
  62. package/template/.pi/skills/yss-stage-decision/references/domain-strategy.schema.json +75 -5
  63. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-contract.md +4 -4
  64. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-v2.schema.json +8 -0
  65. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package-v3.schema.json +8 -0
  66. package/template/.pi/skills/yss-stage-decision/references/stage-decision-package.schema.json +59 -9
  67. package/template/.pi/skills/yss-stage-decision/references/validation-rules.md +2 -2
  68. package/template/.pi/skills/yss-stage-decision/scripts/migrate-contract-v3.mjs +143 -0
  69. package/template/.pi/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +46 -4
  70. package/template/.pi/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +48 -4
  71. package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-stage-decision-package.yaml +42 -13
  72. package/template/.pi/skills/yss-stage-decision/tests/fixtures/valid-supplier-domain.yaml +37 -5
  73. package/template/.pi/skills/yss-stage-decision/tests/run-scenarios.mjs +116 -23
  74. package/template/docs/process/frontend-backend-delivery.md +7 -4
  75. package/template/docs/process/schemas/backend-delivery.schema.json +4 -0
  76. package/template/docs/process/schemas/frontend-delivery-acceptance-v1.schema.json +200 -0
  77. package/template/docs/process/schemas/frontend-delivery-acceptance.schema.json +37 -3
  78. package/template/docs/process/schemas/frontend-strategic-preflight.schema.json +32 -0
  79. package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +2 -1
  80. package/template/docs/process/schemas/strategic-consumer-feedback.schema.json +40 -0
  81. package/template/docs/process/schemas/strategic-design-handoff-v3.schema.json +743 -0
  82. package/template/docs/process/schemas/strategic-design-handoff-v4.schema.json +712 -0
  83. package/template/docs/process/schemas/strategic-feedback-adjudication.schema.json +42 -0
  84. package/template/docs/process/schemas/strategic-handoff-domain-strategy-v3.schema.json +216 -0
  85. package/template/docs/process/schemas/strategic-handoff-import-receipt.schema.json +39 -0
  86. package/template/docs/process/schemas/strategic-handoff-stage-decision-package-v3.schema.json +148 -0
  87. package/template/docs/process/strategic-handoff-package.md +37 -4
  88. package/template/docs/process/templates/lifecycle-checkpoint-template.yaml +1 -0
  89. package/template/docs/process/templates/stage-checkpoint-template.md +1 -0
  90. package/template/scripts/lib/approval-record.mjs +2 -1
  91. package/template/scripts/lib/backend-delivery.mjs +15 -3
  92. package/template/scripts/lib/frontend-delivery-boundary.mjs +20 -4
  93. package/template/scripts/lib/frontend-delivery.mjs +66 -2
  94. package/template/scripts/lib/strategic-feedback.mjs +33 -0
  95. package/template/scripts/lib/strategic-handoff-consumption.mjs +5 -0
  96. package/template/scripts/lib/strategic-handoff-io.mjs +1 -0
  97. package/template/scripts/lib/strategic-handoff.mjs +101 -10
  98. package/template/scripts/lib/user-decision-reuse.mjs +38 -0
  99. package/template/scripts/strategic-feedback +10 -0
  100. package/template/scripts/verify-frontend-strategic-preflight +12 -0
  101. package/template/skills-lock.json +10 -10
  102. package/template.manifest.json +1 -0
  103. package/template.snapshot.json +6 -5
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { buildDecisionFixture } from "../../../../scripts/fixtures/user-decision/build-fixture.mjs";
3
2
  import { createHash } from "node:crypto";
4
3
  import { spawnSync } from "node:child_process";
5
4
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
@@ -14,6 +13,7 @@ const validator = join(testsRoot, "..", "scripts", "validate-domain-strategy.mjs
14
13
  const packageValidator = join(testsRoot, "..", "scripts", "validate-stage-decision-package.mjs");
15
14
  const contextValidator = join(projectRoot, "scripts", "verify-context-contract");
16
15
  const migrationTool = join(testsRoot, "..", "scripts", "migrate-context-references.mjs");
16
+ const v3MigrationTool = join(testsRoot, "..", "scripts", "migrate-contract-v3.mjs");
17
17
  const validTemplate = join(testsRoot, "fixtures", "valid-supplier-domain.yaml");
18
18
  const validPackageTemplate = join(testsRoot, "fixtures", "valid-stage-decision-package.yaml");
19
19
 
@@ -29,6 +29,58 @@ function digestYaml(source) {
29
29
  return `sha256:${createHash("sha256").update(JSON.stringify(canonical(document.toJS({ maxAliasCount: 0 })))).digest("hex")}`;
30
30
  }
31
31
 
32
+ function parseYaml(source) {
33
+ const document = parseDocument(source, { maxAliasCount: 0, uniqueKeys: true });
34
+ if (document.errors.length) throw new Error(document.errors[0].message);
35
+ return document.toJS({ maxAliasCount: 0 });
36
+ }
37
+
38
+ function toV2Domain(source) {
39
+ const value = structuredClone(parseYaml(source));
40
+ value.schema_version = 2;
41
+ value.domain_version = "v2";
42
+ delete value.traceability_version;
43
+ delete value.rule_catalog;
44
+ for (const scenario of value.scenarios) {
45
+ delete scenario.rule_refs;
46
+ delete scenario.critical;
47
+ delete scenario.success_results;
48
+ delete scenario.evidence_refs;
49
+ }
50
+ for (const invariant of value.invariants) {
51
+ delete invariant.rule_ref;
52
+ delete invariant.evidence_refs;
53
+ }
54
+ value.downstream_mapping = [{
55
+ domain_change: "admission-decision-validity",
56
+ impacts: ["spec", "openapi", "procurement-acceptance-tests"],
57
+ propagation: "stale",
58
+ reapproval_condition: "ComplianceReview 决定和有效期重新批准"
59
+ }];
60
+ value.approval.current_version = "v2";
61
+ return value;
62
+ }
63
+
64
+ function toV2Stage(source, domainDigest) {
65
+ const value = structuredClone(parseYaml(source));
66
+ value.schema_version = 2;
67
+ value.package_version = "v2";
68
+ for (const field of ["success_criteria", "test_seams", "confirmed_decisions", "assumptions", "constraints"]) {
69
+ value[field] = value[field].map((item) => item.statement);
70
+ }
71
+ value.domain_strategy_ref.domain_version = "v2";
72
+ value.domain_strategy_ref.digest = domainDigest;
73
+ value.domain_strategy_ref.persisted_ref = "domain-strategy-v2.yaml";
74
+ value.downstream_mapping = [{
75
+ domain_change: "admission-decision-validity",
76
+ consumer: "spec-synthesis",
77
+ propagation: "direct",
78
+ reapproval_condition: "领域策略版本或有效性规则变化"
79
+ }];
80
+ value.approval.current_version = "v2";
81
+ return value;
82
+ }
83
+
32
84
  function run(command, file, contextRoot) {
33
85
  return spawnSync(process.execPath, [command, file, "--root", contextRoot], { cwd: projectRoot, encoding: "utf8" });
34
86
  }
@@ -56,34 +108,59 @@ context_schema_version: 1
56
108
  | 准入决定 | 合规审查形成的准入结论。 | AdmissionDecision | ComplianceReview | 避免:\`审批结果\` |
57
109
  `;
58
110
 
59
- const temporaryRoot = await mkdtemp(join(tmpdir(), "yss-stage-decision-v2-"));
111
+ const temporaryRoot = await mkdtemp(join(tmpdir(), "yss-stage-decision-v3-"));
60
112
  try {
61
113
  await writeFile(join(temporaryRoot, "CONTEXT.md"), contextSource);
62
114
  const contextResult = spawnSync(process.execPath, [contextValidator, "--root", temporaryRoot, "--allowed-context", "SupplierManagement", "--allowed-context", "ComplianceReview", "--allowed-context", "ProcurementExecution", "--term-ref", "Global/Supplier", "--term-ref", "ComplianceReview/AdmissionDecision", "--json"], { cwd: projectRoot, encoding: "utf8" });
63
115
  if (contextResult.status !== 0) throw new Error(`context fixture should pass: ${contextResult.stderr}`);
64
116
  const context = JSON.parse(contextResult.stdout);
65
117
 
66
- const domainApprovalRef = join(temporaryRoot, "domain-approval.json");
67
118
  const domainSource = (await readFile(validTemplate, "utf8"))
68
- .replace(".agents/skills/yss-stage-decision/tests/fixtures/domain-strategy-approval.yaml", domainApprovalRef)
69
119
  .replace("<document-digest>", context.document_digest)
70
120
  .replace("<referenced-terms-digest>", context.referenced_terms_digest);
71
121
  const domainFile = join(temporaryRoot, "domain-strategy.yaml");
72
122
  await writeFile(domainFile, domainSource);
73
- const domainDecision = buildDecisionFixture(join(temporaryRoot, "domain-decision"), { boundary: "check.domain-strategy-approved", subjectRef: domainFile });
74
- const domainApproval = parseDocument(await readFile(join(testsRoot, "fixtures/domain-strategy-approval.yaml"), "utf8")).toJS();
75
- await writeFile(domainApprovalRef, JSON.stringify({ ...domainApproval, subject_ref: domainFile, approval_scope: domainDecision.requirement.scope, user_decision_ref: domainDecision.ref }));
76
123
  const pass = run(validator, domainFile, temporaryRoot);
77
- if (pass.status !== 0) throw new Error(`valid v2 fixture should pass: ${pass.stderr}`);
124
+ if (pass.status !== 0) throw new Error(`valid v3 fixture should pass: ${pass.stderr}`);
125
+
126
+ const v2Domain = toV2Domain(domainSource);
127
+ const v2DomainSource = JSON.stringify(v2Domain, null, 2);
128
+ const v2DomainFile = join(temporaryRoot, "domain-strategy-v2.yaml");
129
+ await writeFile(v2DomainFile, v2DomainSource);
130
+ const v2Pass = run(validator, v2DomainFile, temporaryRoot);
131
+ if (v2Pass.status !== 0) throw new Error(`read-only v2 fixture should pass: ${v2Pass.stderr}`);
132
+
133
+ const migratedDomainV3 = spawnSync(process.execPath, [v3MigrationTool, v2DomainFile, "--kind", "domain-strategy", "--root", temporaryRoot], { cwd: projectRoot, encoding: "utf8" });
134
+ if (migratedDomainV3.status !== 0) throw new Error(`v2 domain strategy should migrate: ${migratedDomainV3.stderr}`);
135
+ const migratedDomainV3Value = JSON.parse(migratedDomainV3.stdout);
136
+ if (migratedDomainV3Value.schema_version !== 3 || migratedDomainV3Value.status !== "draft" || migratedDomainV3Value.approval.approval_ref !== "migration-required") throw new Error("domain v2 -> v3 migration must reset approval and status");
137
+
138
+ const duplicateRule = join(temporaryRoot, "duplicate-rule.yaml");
139
+ const duplicateRuleValue = parseYaml(domainSource);
140
+ duplicateRuleValue.rule_catalog.push(structuredClone(duplicateRuleValue.rule_catalog[0]));
141
+ await writeFile(duplicateRule, JSON.stringify(duplicateRuleValue, null, 2));
142
+ expectBlocked(run(validator, duplicateRule, temporaryRoot), /rule_id.*重复|重复.*rule_id/, "duplicate v3 rule id");
143
+
144
+ const danglingRule = join(temporaryRoot, "dangling-rule.yaml");
145
+ const danglingRuleValue = parseYaml(domainSource);
146
+ danglingRuleValue.scenarios[0].rule_refs = ["rule.missing"];
147
+ await writeFile(danglingRule, JSON.stringify(danglingRuleValue, null, 2));
148
+ expectBlocked(run(validator, danglingRule, temporaryRoot), /rule_refs.*未声明|悬空/, "dangling v3 rule ref");
149
+
150
+ const missingEvidence = join(temporaryRoot, "missing-rule-evidence.yaml");
151
+ const missingEvidenceValue = parseYaml(domainSource);
152
+ missingEvidenceValue.rule_catalog[0].evidence_refs = [];
153
+ await writeFile(missingEvidence, JSON.stringify(missingEvidenceValue, null, 2));
154
+ expectBlocked(run(validator, missingEvidence, temporaryRoot), /evidence_refs/, "missing v3 evidence");
78
155
 
79
156
  const legacyFile = join(temporaryRoot, "legacy-domain.yaml");
80
- await writeFile(legacyFile, domainSource.replace("schema_version: 2", "schema_version: 1"));
157
+ await writeFile(legacyFile, JSON.stringify({ ...v2Domain, schema_version: 1 }, null, 2));
81
158
  expectBlocked(run(validator, legacyFile, temporaryRoot), /migration-required/, "legacy domain contract");
82
159
 
83
160
  const migratableLegacy = join(temporaryRoot, "migratable-legacy-domain.yaml");
84
- const migratableLegacySource = domainSource
85
- .replace("schema_version: 2", "schema_version: 1")
86
- .replace(/context_snapshot:\n[\s\S]*?\ndownstream_mapping:/, "terminology_refs: [CONTEXT.md#Supplier, contexts/ComplianceReview/CONTEXT.md#AdmissionDecision]\ndownstream_mapping:");
161
+ const migratableLegacyValue = { ...structuredClone(v2Domain), schema_version: 1, terminology_refs: ["CONTEXT.md#Supplier", "contexts/ComplianceReview/CONTEXT.md#AdmissionDecision"] };
162
+ delete migratableLegacyValue.context_snapshot;
163
+ const migratableLegacySource = JSON.stringify(migratableLegacyValue, null, 2);
87
164
  await writeFile(migratableLegacy, migratableLegacySource);
88
165
  const migrated = spawnSync(process.execPath, [migrationTool, migratableLegacy, "--root", temporaryRoot], { cwd: projectRoot, encoding: "utf8" });
89
166
  if (migrated.status !== 0) throw new Error(`migratable v1 contract should migrate: ${migrated.stderr}`);
@@ -91,7 +168,7 @@ try {
91
168
  if (migratedValue.schema_version !== 2 || migratedValue.terminology_refs !== undefined || migratedValue.context_snapshot.context_ref !== "CONTEXT.md" || migratedValue.context_snapshot.term_refs.join(",") !== "ComplianceReview/AdmissionDecision,Global/Supplier") throw new Error("migrated context snapshot is incomplete");
92
169
 
93
170
  const ambiguousLegacy = join(temporaryRoot, "ambiguous-legacy-domain.yaml");
94
- await writeFile(ambiguousLegacy, migratableLegacySource.replace("CONTEXT.md#Supplier, contexts/ComplianceReview/CONTEXT.md#AdmissionDecision", "contexts/ComplianceReview/CONTEXT.md"));
171
+ await writeFile(ambiguousLegacy, JSON.stringify({ ...migratableLegacyValue, terminology_refs: ["contexts/ComplianceReview/CONTEXT.md"] }, null, 2));
95
172
  const ambiguousMigration = spawnSync(process.execPath, [migrationTool, ambiguousLegacy, "--root", temporaryRoot], { cwd: projectRoot, encoding: "utf8" });
96
173
  expectBlocked(ambiguousMigration, /migration-required.*无法唯一定位|无法唯一定位.*migration-required/, "ambiguous legacy reference");
97
174
 
@@ -115,32 +192,48 @@ try {
115
192
  await writeFile(unknownContext, domainSource.replace("to_context: ComplianceReview", "to_context: UnknownContext"));
116
193
  expectBlocked(run(validator, unknownContext, temporaryRoot), /未引用已声明上下文|未在领域战略中登记/, "unknown context");
117
194
 
118
- const packageApprovalRef = join(temporaryRoot, "package-approval.json");
119
195
  const packageSource = (await readFile(validPackageTemplate, "utf8"))
120
- .replace(".agents/skills/yss-stage-decision/tests/fixtures/stage-decision-approval.yaml", packageApprovalRef)
121
196
  .replace("<document-digest>", context.document_digest)
122
197
  .replace("<referenced-terms-digest>", context.referenced_terms_digest)
123
198
  .replace("<domain-strategy-digest>", digestYaml(domainSource));
124
199
  const packageFile = join(temporaryRoot, "stage-decision.yaml");
125
200
  await writeFile(packageFile, packageSource);
126
- const packageDecision = buildDecisionFixture(join(temporaryRoot, "package-decision"), { boundary: "check.stage-decision-package-approved", subjectRef: packageFile });
127
- const packageApproval = parseDocument(await readFile(join(testsRoot, "fixtures/stage-decision-approval.yaml"), "utf8")).toJS();
128
- await writeFile(packageApprovalRef, JSON.stringify({ ...packageApproval, subject_ref: packageFile, approval_scope: packageDecision.requirement.scope, user_decision_ref: packageDecision.ref }));
129
201
  const packagePass = run(packageValidator, packageFile, temporaryRoot);
130
- if (packagePass.status !== 0) throw new Error(`valid v2 stage decision package should pass: ${packagePass.stderr}`);
202
+ if (packagePass.status !== 0) throw new Error(`valid v3 stage decision package should pass: ${packagePass.stderr}`);
203
+
204
+ const v2Stage = toV2Stage(packageSource, digestYaml(v2DomainSource));
205
+ const v2StageFile = join(temporaryRoot, "stage-decision-v2.yaml");
206
+ await writeFile(v2StageFile, JSON.stringify(v2Stage, null, 2));
207
+ const v2StagePass = run(packageValidator, v2StageFile, temporaryRoot);
208
+ if (v2StagePass.status !== 0) throw new Error(`read-only v2 stage decision package should pass: ${v2StagePass.stderr}`);
209
+
210
+ const migratedV3 = spawnSync(process.execPath, [v3MigrationTool, v2StageFile, "--kind", "stage-decision", "--root", temporaryRoot], { cwd: projectRoot, encoding: "utf8" });
211
+ if (migratedV3.status !== 0) throw new Error(`v2 stage decision should migrate: ${migratedV3.stderr}`);
212
+ const migratedV3Value = JSON.parse(migratedV3.stdout);
213
+ if (migratedV3Value.schema_version !== 3 || migratedV3Value.status !== "draft" || migratedV3Value.approval.approval_ref !== "migration-required") throw new Error("v2 -> v3 migration must reset approval and status");
214
+
215
+ const wrongSourceRef = join(temporaryRoot, "stage-decision-wrong-source-ref.yaml");
216
+ const wrongSourceRefValue = parseYaml(packageSource);
217
+ wrongSourceRefValue.success_criteria[0].source_refs = ["rule.missing"];
218
+ await writeFile(wrongSourceRef, JSON.stringify(wrongSourceRefValue, null, 2));
219
+ expectBlocked(run(packageValidator, wrongSourceRef, temporaryRoot), /source_refs.*未在领域战略中声明|悬空/, "stage decision dangling source ref");
131
220
 
132
221
  const packageLegacy = join(temporaryRoot, "legacy-stage-decision.yaml");
133
- await writeFile(packageLegacy, packageSource.replace("schema_version: 2", "schema_version: 1"));
222
+ await writeFile(packageLegacy, JSON.stringify({ ...v2Stage, schema_version: 1 }, null, 2));
134
223
  expectBlocked(run(packageValidator, packageLegacy, temporaryRoot), /migration-required/, "legacy stage decision contract");
135
224
 
136
225
  const packageBlocker = join(temporaryRoot, "stage-decision-blocker.yaml");
137
- await writeFile(packageBlocker, packageSource.replace("type: deferred", "type: blocker"));
226
+ const blockerValue = parseYaml(packageSource);
227
+ blockerValue.unresolved_items[0].type = "blocker";
228
+ await writeFile(packageBlocker, JSON.stringify(blockerValue, null, 2));
138
229
  expectBlocked(run(packageValidator, packageBlocker, temporaryRoot), /blocker/, "blocker package");
139
230
 
140
231
  const packageWrongArray = join(temporaryRoot, "stage-decision-wrong-array.yaml");
141
- await writeFile(packageWrongArray, packageSource.replace("target_users: [采购专员、合规专员]", "target_users: [42]"));
232
+ const wrongArrayValue = parseYaml(packageSource);
233
+ wrongArrayValue.target_users = [42];
234
+ await writeFile(packageWrongArray, JSON.stringify(wrongArrayValue, null, 2));
142
235
  expectBlocked(run(packageValidator, packageWrongArray, temporaryRoot), /target_users\[0\]/, "package non-string array item");
143
236
  } finally {
144
237
  await rm(temporaryRoot, { recursive: true, force: true });
145
238
  }
146
- process.stdout.write("业务边界与规则设计 v2 场景验证通过\n");
239
+ process.stdout.write("业务边界与规则设计 v2/v3 场景验证通过\n");
@@ -6,7 +6,7 @@
6
6
 
7
7
  战略方维护业务规则、Spec、页面流程和视觉基线;后端维护 API 及后端交付;前端维护工程设计、页面和验收证据;统一管理方维护基线引用、跨仓切片与业务验收汇总。
8
8
 
9
- 前端消费需求可在上游设计时提前反馈。前端项目在战略和后端交付尚未齐备时,只能验包、诊断和回交缺口。两类输入通过接收核验后,可以准备前端工程设计、实现计划和 Slice Contract;合同获准前不得写代码。
9
+ 前端消费需求可在上游设计时提前反馈。Handoff v4 导入后的 Frontend Strategic Preflight 可在后端交付前完成战略输入、Context、视觉基线和源规则追踪核验,并允许起草前端工程设计与实现计划;它固定返回 `ready_for_agent: false`。仅当 Backend/API/Data 影响命中时,最终接收才等待后端交付;UI-only 路径使用有依据的 `backend-not-applicable`。Slice Contract 获准前均不得写代码。
10
10
 
11
11
  ## 后端导出与前端导入
12
12
 
@@ -28,11 +28,14 @@ scripts/backend-delivery import --bundle <directory-or-zip> --target-root <front
28
28
 
29
29
  ## 接收与启动
30
30
 
31
- 导入只产生 `frontend-acceptance-draft.json`,不会批准资产。接收方完成正式词汇对账并按 `schemas/frontend-delivery-acceptance.schema.json` 准备接收记录:
31
+ Handoff v4 先生成 `frontend-strategic-preflight-draft.json`;补齐正式 Context Reconciliation 后执行 `scripts/verify-frontend-strategic-preflight`。通过仅允许进入前端工程设计草案。
32
32
 
33
- - `backend_delivery` 绑定后端导入收据和包摘要。
33
+ 后端交付导入只新增匹配的 backend binding 并产生 `frontend-acceptance-draft.json`,不会覆盖已填写的前端预检/设计草案,也不会批准资产。最终接收方按当前 `schemas/frontend-delivery-acceptance.schema.json`(v2)准备记录;v1 仅服务历史 Handoff v3,继续要求真实后端交付:
34
+
35
+ - `strategic_preflight` 绑定当前预检文件及字节摘要。
36
+ - `backend_dependency.mode: required` 时,`backend_delivery` 绑定真实后端导入收据和包摘要;`not-applicable` 时必须与 Handoff v4 backend 路由的影响引用、原因和证据一致,且不得绑定后端收据。
34
37
  - `strategic_handoff` 绑定战略导入收据、包摘要、正式 `context_reconciliation_ref` 和全部源规则/关键场景的承接 rows。
35
- - `frontend_cases` 绑定业务规则/场景、成功或失败结果、已交付接口、Visual Baseline `case_id` 和可读取的用例说明。
38
+ - `frontend_cases` 绑定业务规则/场景、成功或失败结果、Visual Baseline `case_id` 和可读取的用例说明;有后端依赖时绑定已交付接口,`backend-not-applicable` 时 `operation_ids` 必须为空。
36
39
  - 已规划的承接使用 `mapped`,表示已映射用例,不声称代码已实现。当前交付范围必须 mapped 到当前切片;其他范围的 pending、conflict、deferred 或 not-applicable 沿用战略逐条承接的理由、证据和依赖阻断规则。
37
40
 
38
41
  完成接收核对后记录 `status: accepted` 并执行:
@@ -25,6 +25,10 @@
25
25
  "type": "string",
26
26
  "pattern": "^sha256:[0-9a-f]{64}$"
27
27
  },
28
+ "strategic_route_id": {
29
+ "type": "string",
30
+ "pattern": "^route\\.[a-z0-9][a-z0-9-]*$"
31
+ },
28
32
  "scope": {
29
33
  "type": "object",
30
34
  "additionalProperties": false,
@@ -0,0 +1,200 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "properties": {
6
+ "schema_version": {
7
+ "const": 1
8
+ },
9
+ "status": {
10
+ "const": "accepted"
11
+ },
12
+ "slice_id": {
13
+ "type": "string",
14
+ "minLength": 1
15
+ },
16
+ "backend_delivery": {
17
+ "type": "object",
18
+ "additionalProperties": false,
19
+ "properties": {
20
+ "import_receipt_ref": {
21
+ "type": "string",
22
+ "minLength": 1
23
+ },
24
+ "bundle_digest": {
25
+ "type": "string",
26
+ "pattern": "^sha256:[0-9a-f]{64}$"
27
+ }
28
+ },
29
+ "required": [
30
+ "import_receipt_ref",
31
+ "bundle_digest"
32
+ ]
33
+ },
34
+ "strategic_handoff": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "properties": {
38
+ "import_receipt_ref": {
39
+ "type": "string",
40
+ "minLength": 1
41
+ },
42
+ "bundle_digest": {
43
+ "type": "string",
44
+ "pattern": "^sha256:[0-9a-f]{64}$"
45
+ },
46
+ "context_reconciliation_ref": {
47
+ "type": "string",
48
+ "minLength": 1
49
+ },
50
+ "rows": {
51
+ "type": "array",
52
+ "items": {
53
+ "type": "object",
54
+ "required": [
55
+ "source_id",
56
+ "source_digest",
57
+ "disposition",
58
+ "dependency_status",
59
+ "dependent_slice_refs",
60
+ "evidence_refs"
61
+ ],
62
+ "properties": {
63
+ "source_id": {
64
+ "type": "string",
65
+ "minLength": 1
66
+ },
67
+ "source_digest": {
68
+ "type": "string",
69
+ "pattern": "^sha256:[0-9a-f]{64}$"
70
+ },
71
+ "disposition": {
72
+ "enum": [
73
+ "mapped",
74
+ "pending",
75
+ "conflict",
76
+ "deferred",
77
+ "not-applicable"
78
+ ]
79
+ },
80
+ "dependency_status": {
81
+ "enum": [
82
+ "known",
83
+ "unknown"
84
+ ]
85
+ },
86
+ "dependent_slice_refs": {
87
+ "type": "array",
88
+ "items": {
89
+ "type": "string",
90
+ "minLength": 1
91
+ },
92
+ "minItems": 0,
93
+ "uniqueItems": true
94
+ },
95
+ "evidence_refs": {
96
+ "type": "array",
97
+ "items": {
98
+ "type": "string",
99
+ "minLength": 1
100
+ },
101
+ "minItems": 0,
102
+ "uniqueItems": true
103
+ },
104
+ "frontend_case_refs": {
105
+ "type": "array",
106
+ "items": {
107
+ "type": "string",
108
+ "minLength": 1
109
+ },
110
+ "minItems": 0,
111
+ "uniqueItems": true
112
+ }
113
+ }
114
+ },
115
+ "minItems": 1,
116
+ "uniqueItems": true
117
+ }
118
+ },
119
+ "required": [
120
+ "import_receipt_ref",
121
+ "bundle_digest",
122
+ "context_reconciliation_ref",
123
+ "rows"
124
+ ]
125
+ },
126
+ "frontend_cases": {
127
+ "type": "array",
128
+ "items": {
129
+ "type": "object",
130
+ "additionalProperties": false,
131
+ "properties": {
132
+ "case_id": {
133
+ "type": "string",
134
+ "minLength": 1
135
+ },
136
+ "source_ids": {
137
+ "type": "array",
138
+ "items": {
139
+ "type": "string",
140
+ "minLength": 1
141
+ },
142
+ "minItems": 1,
143
+ "uniqueItems": true
144
+ },
145
+ "outcome": {
146
+ "enum": [
147
+ "success",
148
+ "failure"
149
+ ]
150
+ },
151
+ "operation_ids": {
152
+ "type": "array",
153
+ "items": {
154
+ "type": "string",
155
+ "minLength": 1
156
+ },
157
+ "minItems": 1,
158
+ "uniqueItems": true
159
+ },
160
+ "visual_case_ids": {
161
+ "type": "array",
162
+ "items": {
163
+ "type": "string",
164
+ "minLength": 1
165
+ },
166
+ "minItems": 1,
167
+ "uniqueItems": true
168
+ },
169
+ "evidence_ref": {
170
+ "type": "string",
171
+ "minLength": 1
172
+ },
173
+ "evidence_digest": {
174
+ "type": "string",
175
+ "pattern": "^sha256:[a-f0-9]{64}$"
176
+ }
177
+ },
178
+ "required": [
179
+ "case_id",
180
+ "source_ids",
181
+ "outcome",
182
+ "operation_ids",
183
+ "visual_case_ids",
184
+ "evidence_ref",
185
+ "evidence_digest"
186
+ ]
187
+ },
188
+ "minItems": 1,
189
+ "uniqueItems": true
190
+ }
191
+ },
192
+ "required": [
193
+ "schema_version",
194
+ "status",
195
+ "slice_id",
196
+ "backend_delivery",
197
+ "strategic_handoff",
198
+ "frontend_cases"
199
+ ]
200
+ }
@@ -4,7 +4,7 @@
4
4
  "additionalProperties": false,
5
5
  "properties": {
6
6
  "schema_version": {
7
- "const": 1
7
+ "const": 2
8
8
  },
9
9
  "status": {
10
10
  "const": "accepted"
@@ -31,6 +31,27 @@
31
31
  "bundle_digest"
32
32
  ]
33
33
  },
34
+ "strategic_preflight": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "required": ["ref", "digest"],
38
+ "properties": {
39
+ "ref": {"type": "string", "minLength": 1},
40
+ "digest": {"type": "string", "pattern": "^sha256:[0-9a-f]{64}$"}
41
+ }
42
+ },
43
+ "backend_dependency": {
44
+ "type": "object",
45
+ "additionalProperties": false,
46
+ "required": ["mode", "route_id"],
47
+ "properties": {
48
+ "mode": {"enum": ["required", "not-applicable"]},
49
+ "route_id": {"const": "route.backend"},
50
+ "impact_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
51
+ "reason": {"type": "string", "minLength": 1},
52
+ "evidence_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}}
53
+ }
54
+ },
34
55
  "strategic_handoff": {
35
56
  "type": "object",
36
57
  "additionalProperties": false,
@@ -43,6 +64,10 @@
43
64
  "type": "string",
44
65
  "pattern": "^sha256:[0-9a-f]{64}$"
45
66
  },
67
+ "route_id": {
68
+ "type": "string",
69
+ "pattern": "^route\\.[a-z0-9][a-z0-9-]*$"
70
+ },
46
71
  "context_reconciliation_ref": {
47
72
  "type": "string",
48
73
  "minLength": 1
@@ -119,6 +144,7 @@
119
144
  "required": [
120
145
  "import_receipt_ref",
121
146
  "bundle_digest",
147
+ "route_id",
122
148
  "context_reconciliation_ref",
123
149
  "rows"
124
150
  ]
@@ -154,7 +180,7 @@
154
180
  "type": "string",
155
181
  "minLength": 1
156
182
  },
157
- "minItems": 1,
183
+ "minItems": 0,
158
184
  "uniqueItems": true
159
185
  },
160
186
  "visual_case_ids": {
@@ -193,8 +219,16 @@
193
219
  "schema_version",
194
220
  "status",
195
221
  "slice_id",
196
- "backend_delivery",
222
+ "strategic_preflight",
223
+ "backend_dependency",
197
224
  "strategic_handoff",
198
225
  "frontend_cases"
226
+ ],
227
+ "allOf": [
228
+ {
229
+ "if": {"properties": {"backend_dependency": {"properties": {"mode": {"const": "required"}}}}, "required": ["backend_dependency"]},
230
+ "then": {"required": ["backend_delivery"]},
231
+ "else": {"properties": {"backend_dependency": {"required": ["reason", "impact_refs", "evidence_refs"]}}}
232
+ }
199
233
  ]
200
234
  }
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://yss.local/schemas/frontend-strategic-preflight-v1.json",
4
+ "title": "YSS Frontend Strategic Preflight v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "status", "route_id", "capability", "import_receipt_ref", "bundle_digest", "context_reconciliation_ref", "visual_baseline_ref", "source_rule_refs", "backend_dependency", "ready_for_agent"],
8
+ "properties": {
9
+ "schema_version": {"const": 1},
10
+ "status": {"enum": ["draft", "verified", "blocked", "stale"]},
11
+ "route_id": {"type": "string", "pattern": "^route\\.[a-z0-9][a-z0-9-]*$"},
12
+ "capability": {"const": "frontend-engineering-design"},
13
+ "import_receipt_ref": {"type": "string", "minLength": 1},
14
+ "bundle_digest": {"type": "string", "pattern": "^sha256:[a-f0-9]{64}$"},
15
+ "context_reconciliation_ref": {"type": "string"},
16
+ "visual_baseline_ref": {"type": "string", "minLength": 1},
17
+ "source_rule_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "pattern": "^(rule|scenario|invariant)\\.[a-z0-9][a-z0-9-]*$"}},
18
+ "backend_dependency": {
19
+ "type": "object",
20
+ "additionalProperties": false,
21
+ "required": ["mode", "route_id"],
22
+ "properties": {
23
+ "mode": {"enum": ["required", "not-applicable"]},
24
+ "route_id": {"const": "route.backend"},
25
+ "reason": {"type": "string", "minLength": 1},
26
+ "impact_refs": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
27
+ "evidence_refs": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}
28
+ }
29
+ },
30
+ "ready_for_agent": {"const": false}
31
+ }
32
+ }
@@ -221,7 +221,8 @@
221
221
  "type": "object"
222
222
  },
223
223
  "ticket_sync": {
224
- "type": "object"
224
+ "type": "object",
225
+ "description": "Ticket 仅保存追踪同步状态;refs 关联消费者 Import Receipt、Consumer Feedback、Feedback Adjudication 与 Delivery Acceptance 等权威资产,不复制其机器状态。"
225
226
  },
226
227
  "verification": {
227
228
  "type": "object"
@@ -0,0 +1,40 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://yss.local/schemas/strategic-consumer-feedback-v1.json",
4
+ "title": "YSS Strategic Consumer Feedback v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "feedback_id", "status", "import_receipt_ref", "bundle", "route_id", "source", "issue_type", "severity", "suggestion", "raised_by", "evidence_refs", "created_at"],
8
+ "properties": {
9
+ "schema_version": {"const": 1},
10
+ "feedback_id": {"type": "string", "pattern": "^consumer-feedback\\.[a-z0-9][a-z0-9-]*$"},
11
+ "status": {"const": "submitted"},
12
+ "import_receipt_ref": {"type": "string", "minLength": 1},
13
+ "bundle": {
14
+ "type": "object",
15
+ "additionalProperties": false,
16
+ "required": ["id", "version", "digest"],
17
+ "properties": {
18
+ "id": {"type": "string", "pattern": "^strategic-design-handoff\\.[a-z0-9][a-z0-9-]*$"},
19
+ "version": {"type": "string", "pattern": "^v[1-9][0-9]*$"},
20
+ "digest": {"type": "string", "pattern": "^sha256:[a-f0-9]{64}$"}
21
+ }
22
+ },
23
+ "route_id": {"type": "string", "pattern": "^route\\.[a-z0-9][a-z0-9-]*$"},
24
+ "source": {
25
+ "type": "object",
26
+ "additionalProperties": false,
27
+ "required": ["id", "digest"],
28
+ "properties": {
29
+ "id": {"type": "string", "pattern": "^(rule|scenario|invariant)\\.[a-z0-9][a-z0-9-]*$"},
30
+ "digest": {"type": "string", "pattern": "^sha256:[a-f0-9]{64}$"}
31
+ }
32
+ },
33
+ "issue_type": {"enum": ["rule-conflict", "scenario-gap", "impact-mismatch", "context-conflict", "design-baseline", "other"]},
34
+ "severity": {"enum": ["blocking", "non-blocking"]},
35
+ "suggestion": {"type": "string", "minLength": 1},
36
+ "raised_by": {"type": "string", "minLength": 1},
37
+ "evidence_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
38
+ "created_at": {"type": "string", "format": "date-time"}
39
+ }
40
+ }