create-yss-spec 3.5.8 → 3.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli-core.lock.json +1 -1
- package/package.json +1 -1
- package/src/commands/doctor.js +40 -0
- package/src/commands/skills.js +7 -2
- package/src/template/asset-runtime.js +120 -21
- package/template/.agents/skills/.strategic-design-skills-manifest.json +3 -3
- package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +20 -16
- package/template/.agents/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -1
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +43 -0
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +3 -1
- package/template/.agents/skills/yss-product-lifecycle/references/plan-requirements.md +7 -6
- package/template/.agents/skills/yss-prototype-stage/scripts/verify-prototype-design.mjs +1 -0
- package/template/.agents/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +8 -5
- package/template/.agents/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +8 -5
- package/template/.agents/skills/yss-stage-decision/tests/run-scenarios.mjs +83 -13
- package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +20 -16
- package/template/.codex/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -1
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +43 -0
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +3 -1
- package/template/.codex/skills/yss-product-lifecycle/references/plan-requirements.md +7 -6
- package/template/.codex/skills/yss-prototype-stage/scripts/verify-prototype-design.mjs +1 -0
- package/template/.codex/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +8 -5
- package/template/.codex/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +8 -5
- package/template/.codex/skills/yss-stage-decision/tests/run-scenarios.mjs +83 -13
- package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +20 -16
- package/template/.cursor/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -1
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +43 -0
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +3 -1
- package/template/.cursor/skills/yss-product-lifecycle/references/plan-requirements.md +7 -6
- package/template/.cursor/skills/yss-prototype-stage/scripts/verify-prototype-design.mjs +1 -0
- package/template/.cursor/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +8 -5
- package/template/.cursor/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +8 -5
- package/template/.cursor/skills/yss-stage-decision/tests/run-scenarios.mjs +83 -13
- package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +20 -16
- package/template/.pi/skills/yss-product-lifecycle/references/matt-yss-adapter.md +3 -1
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +43 -0
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +3 -1
- package/template/.pi/skills/yss-product-lifecycle/references/plan-requirements.md +7 -6
- package/template/.pi/skills/yss-prototype-stage/scripts/verify-prototype-design.mjs +1 -0
- package/template/.pi/skills/yss-stage-decision/scripts/validate-domain-strategy.mjs +8 -5
- package/template/.pi/skills/yss-stage-decision/scripts/validate-stage-decision-package.mjs +8 -5
- package/template/.pi/skills/yss-stage-decision/tests/run-scenarios.mjs +83 -13
- package/template/.template-spec/plan/entry-review.md +2 -1
- package/template/.template-spec/plan/templates/plan-template.md +11 -5
- package/template/.template-spec/process/script-execution.md +37 -0
- package/template/.template-spec/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +2 -0
- package/template/skills-lock.json +11 -11
- package/template.snapshot.json +4 -4
|
@@ -35,6 +35,40 @@ function parseYaml(source) {
|
|
|
35
35
|
return document.toJS({ maxAliasCount: 0 });
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
const hashBytes = source => createHash("sha256").update(source).digest("hex");
|
|
39
|
+
// This independent, test-only consumer context is never reconstructed from a record.
|
|
40
|
+
function bindSource(source, file, approvalRef, contextDigest) {
|
|
41
|
+
const document = parseDocument(source, { maxAliasCount: 0, uniqueKeys: true });
|
|
42
|
+
if (document.errors.length) throw new Error(document.errors[0].message);
|
|
43
|
+
document.setIn(["approval", "approval_ref"], approvalRef);
|
|
44
|
+
document.setIn(["approval", "persisted_ref"], file);
|
|
45
|
+
document.set("approval_context", {
|
|
46
|
+
subject_ref: file,
|
|
47
|
+
approval_scope: ["feature.supplier-admission"],
|
|
48
|
+
basis: [{ ref: "CONTEXT.md", digest: contextDigest }],
|
|
49
|
+
drafter_principal_ref: "instance:role.requirements-manager"
|
|
50
|
+
});
|
|
51
|
+
return document.toString();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fixtureApproval(gateId, file, source, contextDigest) {
|
|
55
|
+
return {
|
|
56
|
+
schema_version: 1, gate_id: gateId, decision: "approved", actor_kind: "digital-human",
|
|
57
|
+
role_id: "role.product-manager", runtime_id: "runtime.generic", principal_ref: "instance:role.product-manager",
|
|
58
|
+
drafter_role_id: "role.requirements-manager", drafter_principal_ref: "instance:role.requirements-manager",
|
|
59
|
+
subject_ref: file, subject_digest: hashBytes(source), approval_scope: ["feature.supplier-admission"],
|
|
60
|
+
basis: [{ ref: "CONTEXT.md", digest: contextDigest }], evidence_refs: [file, "CONTEXT.md"]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function writeApprovedFixture(source, file, gateId, contextDigest) {
|
|
65
|
+
const approvalRef = `${file}.approval.json`;
|
|
66
|
+
const bound = bindSource(source, file, approvalRef, contextDigest);
|
|
67
|
+
await writeFile(file, bound);
|
|
68
|
+
await writeFile(approvalRef, JSON.stringify(fixtureApproval(gateId, file, bound, contextDigest), null, 2));
|
|
69
|
+
return bound;
|
|
70
|
+
}
|
|
71
|
+
|
|
38
72
|
function toV2Domain(source) {
|
|
39
73
|
const value = structuredClone(parseYaml(source));
|
|
40
74
|
value.schema_version = 2;
|
|
@@ -115,18 +149,18 @@ try {
|
|
|
115
149
|
if (contextResult.status !== 0) throw new Error(`context fixture should pass: ${contextResult.stderr}`);
|
|
116
150
|
const context = JSON.parse(contextResult.stdout);
|
|
117
151
|
|
|
118
|
-
const
|
|
152
|
+
const domainTemplateSource = (await readFile(validTemplate, "utf8"))
|
|
119
153
|
.replace("<document-digest>", context.document_digest)
|
|
120
154
|
.replace("<referenced-terms-digest>", context.referenced_terms_digest);
|
|
121
155
|
const domainFile = join(temporaryRoot, "domain-strategy.yaml");
|
|
122
|
-
|
|
156
|
+
const contextDigest = hashBytes(contextSource);
|
|
157
|
+
const domainSource = await writeApprovedFixture(domainTemplateSource, domainFile, "check.domain-strategy-approved", contextDigest);
|
|
123
158
|
const pass = run(validator, domainFile, temporaryRoot);
|
|
124
159
|
if (pass.status !== 0) throw new Error(`valid v3 fixture should pass: ${pass.stderr}`);
|
|
125
160
|
|
|
126
161
|
const v2Domain = toV2Domain(domainSource);
|
|
127
|
-
const v2DomainSource = JSON.stringify(v2Domain, null, 2);
|
|
128
162
|
const v2DomainFile = join(temporaryRoot, "domain-strategy-v2.yaml");
|
|
129
|
-
await
|
|
163
|
+
const v2DomainSource = await writeApprovedFixture(JSON.stringify(v2Domain, null, 2), v2DomainFile, "check.domain-strategy-approved", contextDigest);
|
|
130
164
|
const v2Pass = run(validator, v2DomainFile, temporaryRoot);
|
|
131
165
|
if (v2Pass.status !== 0) throw new Error(`read-only v2 fixture should pass: ${v2Pass.stderr}`);
|
|
132
166
|
|
|
@@ -192,12 +226,12 @@ try {
|
|
|
192
226
|
await writeFile(unknownContext, domainSource.replace("to_context: ComplianceReview", "to_context: UnknownContext"));
|
|
193
227
|
expectBlocked(run(validator, unknownContext, temporaryRoot), /未引用已声明上下文|未在领域战略中登记/, "unknown context");
|
|
194
228
|
|
|
195
|
-
const
|
|
229
|
+
const packageTemplateSource = (await readFile(validPackageTemplate, "utf8"))
|
|
196
230
|
.replace("<document-digest>", context.document_digest)
|
|
197
231
|
.replace("<referenced-terms-digest>", context.referenced_terms_digest)
|
|
198
232
|
.replace("<domain-strategy-digest>", digestYaml(domainSource));
|
|
199
233
|
const packageFile = join(temporaryRoot, "stage-decision.yaml");
|
|
200
|
-
await
|
|
234
|
+
const packageSource = await writeApprovedFixture(packageTemplateSource, packageFile, "check.stage-decision-package-approved", contextDigest);
|
|
201
235
|
const packagePass = run(packageValidator, packageFile, temporaryRoot);
|
|
202
236
|
if (packagePass.status !== 0) throw new Error(`valid v3 stage decision package should pass: ${packagePass.stderr}`);
|
|
203
237
|
for (const format of ['yaml', 'json']) {
|
|
@@ -215,16 +249,14 @@ try {
|
|
|
215
249
|
|
|
216
250
|
const bundleFile = join(temporaryRoot, "plan-review-bundle.json");
|
|
217
251
|
const bundledDomainValue = parseYaml(domainSource);
|
|
218
|
-
bundledDomainValue.approval.approval_ref = bundleFile;
|
|
219
|
-
const bundledDomainSource = JSON.stringify(bundledDomainValue, null, 2);
|
|
220
252
|
const bundledDomainFile = join(temporaryRoot, "domain-strategy-bundled.yaml");
|
|
253
|
+
const bundledDomainSource = bindSource(JSON.stringify(bundledDomainValue, null, 2), bundledDomainFile, bundleFile, contextDigest);
|
|
221
254
|
await writeFile(bundledDomainFile, bundledDomainSource);
|
|
222
255
|
const bundledPackageValue = parseYaml(packageSource);
|
|
223
256
|
bundledPackageValue.domain_strategy_ref.persisted_ref = "domain-strategy-bundled.yaml";
|
|
224
257
|
bundledPackageValue.domain_strategy_ref.digest = digestYaml(bundledDomainSource);
|
|
225
|
-
bundledPackageValue.approval.approval_ref = bundleFile;
|
|
226
|
-
const bundledPackageSource = JSON.stringify(bundledPackageValue, null, 2);
|
|
227
258
|
const bundledPackageFile = join(temporaryRoot, "stage-decision-bundled.yaml");
|
|
259
|
+
const bundledPackageSource = bindSource(JSON.stringify(bundledPackageValue, null, 2), bundledPackageFile, bundleFile, contextDigest);
|
|
228
260
|
await writeFile(bundledPackageFile, bundledPackageSource);
|
|
229
261
|
await writeFile(bundleFile, JSON.stringify({
|
|
230
262
|
schema_version: 1,
|
|
@@ -237,8 +269,8 @@ try {
|
|
|
237
269
|
runtime_id: "runtime.generic",
|
|
238
270
|
principal_ref: "instance:role.product-manager",
|
|
239
271
|
reviews: [
|
|
240
|
-
|
|
241
|
-
|
|
272
|
+
fixtureApproval("check.domain-strategy-approved", bundledDomainFile, bundledDomainSource, contextDigest),
|
|
273
|
+
fixtureApproval("check.stage-decision-package-approved", bundledPackageFile, bundledPackageSource, contextDigest)
|
|
242
274
|
]
|
|
243
275
|
}, null, 2));
|
|
244
276
|
const bundledDomainPass = run(validator, bundledDomainFile, temporaryRoot);
|
|
@@ -246,9 +278,47 @@ try {
|
|
|
246
278
|
const bundledPackagePass = run(packageValidator, bundledPackageFile, temporaryRoot);
|
|
247
279
|
if (bundledPackagePass.status !== 0) throw new Error(`stage decision should select its conclusion from Plan review-bundle: ${bundledPackagePass.stderr}`);
|
|
248
280
|
|
|
281
|
+
// Both consumers reject coherent record edits unless independently current inputs match.
|
|
282
|
+
for (const [kind, command, source, gateId] of [
|
|
283
|
+
["domain", validator, domainSource, "check.domain-strategy-approved"],
|
|
284
|
+
["stage", packageValidator, packageSource, "check.stage-decision-package-approved"]
|
|
285
|
+
]) {
|
|
286
|
+
for (const [label, mutateAsset, mutateRecord, pattern] of [
|
|
287
|
+
["missing-context", value => { delete value.approval_context; }, null, /APPROVAL_CONTEXT_REQUIRED/],
|
|
288
|
+
["context-missing-basis", value => { value.approval_context.basis = []; }, null, /APPROVAL_CONTEXT_REQUIRED/],
|
|
289
|
+
["context-stale-basis", value => { value.approval_context.basis[0].digest = "0".repeat(64); }, null, /当前消费上下文.*证据过期/],
|
|
290
|
+
["context-missing-drafter", value => { delete value.approval_context.drafter_principal_ref; }, null, /独立起草者来源/],
|
|
291
|
+
["context-drafter", value => { value.approval_context.drafter_principal_ref = "instance:other-drafter"; }, null, /起草者身份.*不匹配/],
|
|
292
|
+
["context-scope", value => { value.approval_context.approval_scope.push("feature.other"); }, null, /审查范围不匹配/],
|
|
293
|
+
["record-subject", null, record => { record.subject_ref = domainFile; }, /审查对象不匹配/],
|
|
294
|
+
["record-scope", null, record => { record.approval_scope.push("feature.other"); }, /审查范围不匹配/],
|
|
295
|
+
["record-drafter", null, record => { record.drafter_principal_ref = "instance:other-drafter"; }, /起草者身份.*不匹配/],
|
|
296
|
+
["record-self-sign", null, record => { record.principal_ref = record.drafter_principal_ref; }, /起草者自签/],
|
|
297
|
+
["record-missing-basis", null, record => { record.basis = []; }, /批准记录.*缺少当前证据摘要/],
|
|
298
|
+
["record-stale-basis", null, record => { record.basis[0].digest = "0".repeat(64); }, /批准记录.*证据过期/],
|
|
299
|
+
["record-semantic-digest", null, record => { record.subject_digest = digestYaml(record.fixtureSource).slice(7); }, /审查资产摘要不匹配/]
|
|
300
|
+
]) {
|
|
301
|
+
const file = join(temporaryRoot, `${kind}-${label}.yaml`);
|
|
302
|
+
let bound = await writeApprovedFixture(source, file, gateId, contextDigest);
|
|
303
|
+
if (mutateAsset) {
|
|
304
|
+
const value = parseYaml(bound); mutateAsset(value);
|
|
305
|
+
bound = JSON.stringify(value, null, 2);
|
|
306
|
+
await writeFile(file, bound);
|
|
307
|
+
}
|
|
308
|
+
const record = fixtureApproval(gateId, file, bound, contextDigest);
|
|
309
|
+
// Keep the raw bytes current even for consumer-context changes: an unrelated stale
|
|
310
|
+
// subject must not be what makes the current-context negative pass.
|
|
311
|
+
if (mutateRecord) {
|
|
312
|
+
record.fixtureSource = bound; mutateRecord(record); delete record.fixtureSource;
|
|
313
|
+
}
|
|
314
|
+
await writeFile(`${file}.approval.json`, JSON.stringify(record, null, 2));
|
|
315
|
+
expectBlocked(run(command, file, temporaryRoot), pattern, `${kind} ${label}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
249
319
|
const v2Stage = toV2Stage(packageSource, digestYaml(v2DomainSource));
|
|
250
320
|
const v2StageFile = join(temporaryRoot, "stage-decision-v2.yaml");
|
|
251
|
-
await
|
|
321
|
+
await writeApprovedFixture(JSON.stringify(v2Stage, null, 2), v2StageFile, "check.stage-decision-package-approved", contextDigest);
|
|
252
322
|
const v2StagePass = run(packageValidator, v2StageFile, temporaryRoot);
|
|
253
323
|
if (v2StagePass.status !== 0) throw new Error(`read-only v2 stage decision package should pass: ${v2StagePass.stderr}`);
|
|
254
324
|
|
|
@@ -30,6 +30,8 @@ Slice v3 的当前执行结果须按 `references/yss-skill-execution-result.md`
|
|
|
30
30
|
|
|
31
31
|
完整草案使用 `scripts/slice-contract prepare` 或 `prepareSliceImplementationContract` 从已有 Ticket / checkpoint 和必要细化组装;执行前读取唯一 YAML,视图和检查报告不授予权限。
|
|
32
32
|
|
|
33
|
+
来源摘要、闭包、阅读视图和校验直接复用 `slice-contract` / `contract` 入口;不再用临时脚本手工重算同一合同。工具或参数缺失按消费项目 `.template-spec/process/script-execution.md` 诊断,禁止用自写校验替代当前合同门禁。
|
|
34
|
+
|
|
33
35
|
合同结构见 [slice-implementation-contract.md](references/slice-implementation-contract.md),专项返回协议见 [yss-skill-execution-result.md](references/yss-skill-execution-result.md)。前端、后端和测试子任务必须由生命周期主控从批准的 Slice Contract 编译任务包;任务包 schema 为 `.template-spec/process/schemas/subagent-task-package.schema.json`,技能列表必须来自 `taskPackageDefaults`,不能由编译器或执行 Agent 另行手写。
|
|
34
36
|
|
|
35
37
|
## 硬规则
|
|
@@ -9,7 +9,7 @@ description: 编排 YSS 研发全生命周期;当阶段、产物、门禁或 S
|
|
|
9
9
|
|
|
10
10
|
五类机器资产默认 JSON;读写、迁移及恢复须遵循 `.template-spec/process/structured-assets.md`。
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
文档/进度按 `document_writing` 调用 `i-have-adhd`,遵循 `.template-spec/process/document-writing.md`。
|
|
13
13
|
|
|
14
14
|
## 事实源
|
|
15
15
|
|
|
@@ -29,18 +29,20 @@ scripts/query-lifecycle-context --work-unit work-unit.plan-requirements --check-
|
|
|
29
29
|
|
|
30
30
|
`--include` 限合同顶层键。调用前按 [来源与补装](references/matt-yss-adapter.md) 预检,在既有授权内补装并重验。
|
|
31
31
|
|
|
32
|
-
按 `execution_efficiency`
|
|
32
|
+
按 `execution_efficiency` 复用入口、合并查询和未变资料,核验资产与门禁;见项目 `.template-spec/process/script-execution.md`。
|
|
33
33
|
|
|
34
34
|
## 入口与模式
|
|
35
35
|
|
|
36
36
|
1. 严格解析 `yss-project.yaml`,不得按目录、Git remote 或占位符猜仓库身份。
|
|
37
|
-
2.
|
|
38
|
-
3.
|
|
37
|
+
2. 判定影响面及最近可信阶段;完成须有内容、审查、新鲜上游和可读证据,文件存在不代表通过。
|
|
38
|
+
3. 按 `request_triage` 选 `route`、`orchestrate`、`resume`、`audit`;行动请求无需模式关键字,意图不明只读 `route`。模式、门禁及授权边界仍适用。
|
|
39
39
|
4. `project-instance` 按生命周期注册表推进;`template-source` 只走模板维护流程。
|
|
40
40
|
|
|
41
|
-
Plan 入口读 `.template-spec/plan/README.md`、`.template-spec/process/plan-migration.md
|
|
41
|
+
Plan 入口读 `.template-spec/plan/README.md`、`.template-spec/process/plan-migration.md`,核验战略输入与退出条件。关键未决项阻断 Spec;其余记责任人、时点和接收方。仅认 Plan,不解析旧阶段或自动沿用历史批准。
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
必须确认的未决项按 `planning.clarification_policy` 主动调用 `grilling`,见 [澄清](references/plan-requirements.md)。
|
|
44
|
+
|
|
45
|
+
分诊见 [协议](references/request-triage.md) 与 `--include request_triage`;先查证据,再问必要缺口。
|
|
44
46
|
|
|
45
47
|
## 不可越过的边界
|
|
46
48
|
|
|
@@ -58,18 +60,18 @@ Plan 入口读 `.template-spec/plan/README.md`、`.template-spec/process/plan-mi
|
|
|
58
60
|
|
|
59
61
|
### 用户决定
|
|
60
62
|
|
|
61
|
-
按角色表 `user_decision_policy`
|
|
63
|
+
按角色表 `user_decision_policy` 与 `references/user-decisions.md` 展示资产、范围、风险、后续动作,再取得提问者或指定生物人的原始回复。数字人、超时、默认项不得代答。先验 `continuation_ref`;未知先调查,实质变化或缺强制审批时重新决定。缺陷/缺证据阻断,建议记待办;合同、验证、外部授权仍适用。
|
|
62
64
|
|
|
63
65
|
### 外部副作用与 Git
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
生命周期批准、实现授权、泛泛意向不构成 Git 授权。按 `references/user-decisions.md` 整理明确动作与范围的真实回复,无需重填字段。commit、push、其他外部动作分别核验;来源不可读、撤回、越界则不执行。`git-submodule` 逐仓授权、非 detached HEAD、先子仓后父仓 gitlink。
|
|
66
68
|
|
|
67
69
|
## 有界编排循环
|
|
68
70
|
|
|
69
71
|
1. 从真实资产重建状态,查询 mode、stage、work-unit 及必要合同子树。
|
|
70
72
|
2. 核验影响面、上游新鲜度、门禁和阻塞,选择首个未阻塞单元;`not-applicable` 不作豁免。
|
|
71
73
|
3. 主控持有原生正式资产;专项按结构化任务包派发,只调用合同允许的 model-invoked skill。
|
|
72
|
-
4. `to-spec`、`to-tickets`、`implement`
|
|
74
|
+
4. 兼容入口 `to-spec`、`to-tickets`、`implement` 仅由用户显式调用。主控预检输入、路径、门禁;入口按 `matt_invocation_boundary` 写产物并回交验收,不批准或改变 Ticket 就绪状态。原生资产归主控。
|
|
73
75
|
5. 先逐项验收用户目标、遗漏、错误假设和未决问题,再验收 `Workflow Execution Result`:工作单元、合同、允许写路径、`context_reconciliation`、证据、实际验证、延期 seam、漂移和下一路由必须可核验。实现派发额外绑定当前 Slice Implementation Contract v3;其他阶段不得伪造该合同。
|
|
74
76
|
6. 按 `blocking_disposition` 自主处理阻塞;缺真实决定或新授权才展示资产后询问。独立工作继续,完成结论不新增暂停。
|
|
75
77
|
|
|
@@ -81,22 +83,24 @@ Plan 入口读 `.template-spec/plan/README.md`、`.template-spec/process/plan-mi
|
|
|
81
83
|
|
|
82
84
|
Plan → Spec 写入前按 `.template-spec/plan/entry-review.md` 持久化审阅包,执行 `node scripts/verify-plan-spec-entry <state.yaml>`;缺项、过期或无真实回复阻断,独立调研继续。
|
|
83
85
|
|
|
86
|
+
`checks.grill_exit=passed` 仅准备就绪;共同理解与当前 Plan 合并真实确认后,入口判定完整退出。
|
|
87
|
+
|
|
84
88
|
## 结果与暂停
|
|
85
89
|
|
|
86
|
-
|
|
90
|
+
每轮返回/暂停按 `user_progress_report` 说明阶段及依据、结果、下一阶段/单元及条件、问题/阻塞、责任方、解除与复验、主控动作、用户决定。未知写“待核验”,负责人缺失写“未登记”;目标不代表批准,已授权修复继续。
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
按 `workflow_execution_result` 与 `references/state-model.md` 记录状态、证据新鲜度、阻塞、动作、路由、Ticket / Git checkpoint。
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
发送前核对状态、证据新鲜度、阻塞与结构化结果;见 [结果提示](references/orchestration.md#结果与友好提示)。会签保留门禁、角色/运行时、文件、推荐答案、恢复动作。恢复先验证;完成/合并/发布须同一候选通过适用审查和 fresh verification;发布须生物人决定。
|
|
91
95
|
|
|
92
|
-
Plan
|
|
96
|
+
Plan/Spec/Design 追踪见 `.template-spec/process/stage-tracking.md`。
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
澄清读 [对账](references/plan-requirements.md),外部输入读 [问卷](references/external-input-questionnaire.md)。
|
|
95
99
|
|
|
96
100
|
Spec 起草业务票,Design 校准后正式化;无设计影响直接正式化,研发再细化 Slice。按 `.template-spec/process/business-tickets.md` 检查覆盖和批准;业务票不授予实现资格。
|
|
97
101
|
|
|
98
102
|
## 功能资产整理
|
|
99
103
|
|
|
100
|
-
|
|
104
|
+
资产整理及旧票迁移按 `.template-spec/process/feature-assets.md` 和显式计划保留状态、批准依据、原文,不推进阶段;被引用草稿按正式资产保留。
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
既有实例模板升级、旧布局迁移和升级恢复用 `yss-harness-upgrade`,遵循 `.template-spec/process/harness-upgrade.md`;不推进产品阶段或重写历史批准。
|
|
@@ -35,7 +35,7 @@ Matt skills 决定如何工作;YSS 生命周期决定是否允许推进;YSS
|
|
|
35
35
|
| 情形 | Matt flow | 生命周期验收 |
|
|
36
36
|
|---|---|---|
|
|
37
37
|
| 首次启用或配置缺失 | `setup-matt-pocock-skills`(用户显式) | `needs-human`,说明缺失项;用户完成 setup 后重新计算 readiness |
|
|
38
|
-
| 需求澄清 | 原生 `work-unit.plan-requirements
|
|
38
|
+
| 需求澄清 | 原生 `work-unit.plan-requirements`,按 `planning.clarification_policy` 主动调用 `grilling` 处理必须确认的未决决定,术语使用 `domain-modeling` | 逐项验收未决项和证据回流;完整退出联合当前 Plan 用户决定 |
|
|
39
39
|
| 信息在其他人手中 | 生命周期外部输入问卷 | 使用 `external-input-required` 暂停;答案回流后记录 response、重新分类影响面,再进入 `work-unit.plan-requirements` 或 `to-spec` |
|
|
40
40
|
| 大型模糊工作 | `wayfinder`(可选) | 仅在跨会话 / 跨 Agent 或 frontier 不清晰时启用;map 真正完成后 `handoff → to-spec` |
|
|
41
41
|
| 技术或战略事实 | `yss-research`(`research` 为 deprecated alias) | `technical-evidence` 核验一手技术资料;`strategy-evidence` 为领域战略和阶段决策提供可审计证据;研究包不得自行修改或批准下游资产 |
|
|
@@ -92,6 +92,8 @@ blocking_signals: []
|
|
|
92
92
|
|
|
93
93
|
`grill_exit` 不是“已经聊过”的自然语言声明。它必须同时证明 frontier 为空、事实已解决或分别路由到 `yss-research` / prototype / external input、用户决策已确认、双方共同理解已确认,并且没有未回流的 runnable blocker。
|
|
94
94
|
|
|
95
|
+
按 `planning.clarification_policy` 主动开始必要澄清,调查、实验和专业审查分别取得证据,分轮仅询问前提已明确的必要决定。审阅包中的 `checks.grill_exit=passed` 表示澄清准备就绪;共同理解总述与完整当前 Plan 审阅包合并展示,由同一真实回复确认。入口联合准备检查与当前 `gate.plan-approved` 决定后才判断完整退出;回复独立保存,不回写审阅包或依据,不增加批准阶段。
|
|
96
|
+
|
|
95
97
|
## Review 候选与 Git 授权
|
|
96
98
|
|
|
97
99
|
YSS 调用 `code-review` 前必须形成 review input,至少包含 `review_mode`、`review_base_ref`、`implementation_candidate_ref`、`candidate_snapshot_ref`、`candidate_digest`、`spec_ref`、`ticket_ref`、`slice_contract_ref`、`build_architecture_checklist_ref` 和 `yss_execution_result_refs`,并满足 `orchestration-contract.yaml.review_input.manifest_required_by_mode`。Standards 还必须编译 `review_standards_route`:合同 `required_skills`、影响面专项检查输入、报告模板与 `finding_disposition`;机器检查按 `run_if_present` 执行。质量标准只从 `engineering-baseline` 引用,禁止在 review input 或切片中另起一份。命中高风险影响时,review input 还要引用 Doubt-Driven 主张 / 反证记录;缺少反证或残余风险未处理时返回 `blocked`。`committed` 审查 merge-base 到不可变 `HEAD`;`worktree` 一次捕获 merge-base 到 working tree 的 committed、staged、unstaged 和 untracked 内容,使用 `yss-worktree-candidate-v1` 规定的 raw path、uint64 big-endian 长度、tracked/untracked record 和不支持条目阻断规则计算 SHA-256,让所有参与审查者(人数按 `orchestration-contract.yaml.gate_consolidation`)消费同一不可变快照,并在返回和完成 checkpoint 复核摘要未变化。缺少输入、候选为空、专项覆盖缺失、未关闭 mandatory `violation`、适用行空白或摘要变化时返回 `blocked`,不能漏掉首轮适用项、另起通用审查 skill、由审查者改实现,或合并未重绑定的不同候选结论。`violation` 类 finding 交实现者在原合同路径修复,按差异、受影响结论 / 行为及依赖定向复审并重绑定;摘要变化、UI 或 `new_impacts` 不触发全轴默认或兜底,未知先调查。`drift` / `new_impacts` 使合同 `stale` 并回 实现合同编译器更新受影响合同;未受影响项须有可读比较依据才能复用。
|
|
@@ -164,6 +164,14 @@ request_triage:
|
|
|
164
164
|
structural_checks_prove_model_accuracy: false
|
|
165
165
|
|
|
166
166
|
execution_efficiency:
|
|
167
|
+
script_execution:
|
|
168
|
+
standard_operations: reuse-installed-public-entrypoints
|
|
169
|
+
entrypoint_invocation: declared-interpreter-or-executable-shebang
|
|
170
|
+
missing_capability: inspect-help-and-profile-before-adding-adapter
|
|
171
|
+
temporary_analysis: bounded-scope-outside-repository-no-new-authority
|
|
172
|
+
diagnostic_generation: synthetic-fixtures-no-real-approval-bodies
|
|
173
|
+
dependency_explanation: derive-from-current-source-and-distribution-closure
|
|
174
|
+
guidance_ref: .template-spec/process/script-execution.md
|
|
167
175
|
context_query:
|
|
168
176
|
strategy: combine-mode-stage-work-unit-and-required-includes
|
|
169
177
|
read_scope: current-work-unit-and-triggered-impacts
|
|
@@ -787,6 +795,38 @@ planning:
|
|
|
787
795
|
implementation_authorized: false
|
|
788
796
|
frontier_scope: current-plan-decisions
|
|
789
797
|
deferred_details: excluded-from-frontier-only-after-user-confirms-deferral
|
|
798
|
+
clarification_policy:
|
|
799
|
+
trigger: required-plan-user-decision-unresolved
|
|
800
|
+
skill: grilling
|
|
801
|
+
invocation_mode: model-invoked
|
|
802
|
+
explicit_user_invocation_required: false
|
|
803
|
+
inputs: [ current_scope, discovered_facts, confirmed_decisions, open_items, decision_dependencies ]
|
|
804
|
+
dispositions:
|
|
805
|
+
required_user_decision: grilling-with-real-user-response
|
|
806
|
+
discoverable_fact: investigate-or-yss-research
|
|
807
|
+
runnable_blocker: prototype-or-actual-verification-with-evidence
|
|
808
|
+
professional_wait: autonomously-dispatch-or-inspect-existing-task-then-wait
|
|
809
|
+
verification_failure: repair-supply-evidence-or-reroute
|
|
810
|
+
noncritical_detail: existing-deferral-policy-with-user-confirmation
|
|
811
|
+
rounds:
|
|
812
|
+
frontier: decisions-with-settled-prerequisites
|
|
813
|
+
questions: all-independent-actionable-frontier-questions-with-recommendations
|
|
814
|
+
advance: after-real-user-response
|
|
815
|
+
unsettled_facts: wait-only-for-dependent-questions
|
|
816
|
+
reuse: unchanged-confirmed-decisions
|
|
817
|
+
correction: reopen-only-affected-decisions-and-dependencies
|
|
818
|
+
independent_authorized_work: continue
|
|
819
|
+
convergence:
|
|
820
|
+
summary: [ user_problem, goals, mvp, non_goals, key_rules, acceptance_examples, open_item_resolutions, remaining_uncertainty ]
|
|
821
|
+
required_open_items: resolved-with-current-evidence
|
|
822
|
+
facts_and_verification: readable-evidence-required
|
|
823
|
+
preparation_check: grill_exit
|
|
824
|
+
preparation_passed_means: clarification-materials-and-prerequisites-ready
|
|
825
|
+
final_confirmation_boundary: gate.plan-approved
|
|
826
|
+
final_confirmation: shared-understanding-and-current-plan-in-one-real-response
|
|
827
|
+
final_confirmation_proof: plan_user_decision_ref-or-valid-plan_continuation_ref
|
|
828
|
+
confirmation_writeback: external-proof-without-review-or-basis-rewrite
|
|
829
|
+
completion: passed-preparation-and-valid-current-user-decision
|
|
790
830
|
|
|
791
831
|
grill_exit:
|
|
792
832
|
required: [ frontier_empty, facts_resolved_or_routed, decisions_confirmed, shared_understanding_confirmed, no_unresolved_runnable_blocker ]
|
|
@@ -795,6 +835,9 @@ grill_exit:
|
|
|
795
835
|
runnable_question: handoff-prototype-handoff
|
|
796
836
|
external_decision: external-input-required
|
|
797
837
|
user_confirmation_required: true
|
|
838
|
+
preparation_check: grill_exit
|
|
839
|
+
final_confirmation_boundary: gate.plan-approved
|
|
840
|
+
completion: passed-preparation-and-valid-current-user-decision
|
|
798
841
|
output: plan-evidence
|
|
799
842
|
|
|
800
843
|
ticket_role_transition:
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
|
|
56
56
|
脚本命令、实际退出码和 duration_ms 记入已有 checkpoint 的验证证据;Agent 编排和人工等待仅在有计时来源时记录,否则标为 unknown。无需为耗时再建一套阶段产物。
|
|
57
57
|
|
|
58
|
+
常见查询、合同准备/阅读/验证、状态恢复和技能同步直接消费已安装入口,调用约定见消费项目 `.template-spec/process/script-execution.md`。工具缺失先按 Profile 和固定版本核对安装;只有实际参数或输出缺口才补适配。一次性分析保留明确输入、允许写范围与实际日志,不承载阶段裁决或批准;诊断使用固定适配器与合成 fixture。
|
|
59
|
+
|
|
58
60
|
## 阶段边界
|
|
59
61
|
|
|
60
62
|
Matt phase boundary 是工作阶段之间的上下文决策,不是新的生命周期状态。按以下顺序判断,第一项适用即停止判断:
|
|
@@ -101,7 +103,7 @@ tracker 选择和冲突按 `.template-spec/agents/issue-tracker.md` 裁决:已
|
|
|
101
103
|
|
|
102
104
|
- `work-unit.technical-analysis` 由 `yss-technical-design` 组织后端技术设计;其 `domain-driven` 分支调用 `yss-tactical-design`,`layered-mvc` 分支由自身持有。状态、规则、一致性、持久化影响本身不意味着选择 DDD。无后端技术设计影响记录带原因的 `not-applicable`,其他 API / 前端技术分析继续各自路由。新合同使用 `artifact.technical-design`,通过 `evidence.technical-design-review` 回交现有架构审查;旧 DDD 稳定 ID 只读兼容。批准仍由生命周期维护,编译器消费批准且当前的设计起草实现合同;`stale`、`drift` 或 `new_impacts` 时不得继续 Ticket 正式化。
|
|
103
105
|
|
|
104
|
-
- `work-unit.plan-requirements`
|
|
106
|
+
- `work-unit.plan-requirements` 按 `planning.clarification_policy` 在必须确认的需求、规则、范围或取舍未决时主动调用 `grilling`,术语工作使用 `domain-modeling`。事实、实验、专业审查分别回流证据,依赖分轮并复用未变确认。`checks.grill_exit=passed` 表示准备就绪;固定审阅包后将共同理解与 Plan 批准范围一并展示,以单独保存的真实回复联合完成完整退出。`work-unit.plan-opportunity` 按事实类型路由 `competitive-intelligence` 或 `yss-research`。`yss-research:quick` 只用于探索;外部证据进入领域战略、阶段决策或其他生命周期批准输入前必须升级为 `evidence-audited`。生命周期原生工作单元默认负责 Spec、Ticket 和实现资产;`to-spec`、`to-tickets`、`implement` 仅保留为显式兼容入口,结果必须回交生命周期验收。
|
|
105
107
|
- 原生 `work-unit.ticket-decomposition` 只能在 OpenAPI Freeze 或无 API 影响记录后创建垂直切片,初始 Ticket 状态统一为 `ready-for-human`;生命周期复算完整公式后才能提升 `ready-for-agent`。该工作单元必须返回 `ticket_decomposition_result_ref` 和垂直切片引用,并作为实现的必经前置证据。
|
|
106
108
|
- 原生 `work-unit.slice-implementation` 必须在生命周期批准并持久化 Slice Implementation Contract 和 Build Architecture Checklist 后执行;用户显式 `implement` 仍走兼容入口,不得绕过生命周期。
|
|
107
109
|
- `Workflow Execution Result.next_route` 必须通过生命周期转换校验;Spec、原型和技术分析不得直接跳转到 Ticket 正式化或实现,必须先完成 `work-unit.implementation-repository-preparation`,再进入 `work-unit.ticket-decomposition`。
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# 需求澄清与 Context 对账
|
|
2
2
|
|
|
3
|
-
`work-unit.plan-requirements` 由 `yss-product-lifecycle` 或当前 profile
|
|
3
|
+
`work-unit.plan-requirements` 由 `yss-product-lifecycle` 或当前 profile 编排器执行。发现必须确认的需求、业务规则、范围或取舍未决项时,按 `planning.clarification_policy` 主动调用 `grilling`;术语工作使用 `domain-modeling`。不另建批准入口。
|
|
4
4
|
|
|
5
5
|
1. 读取 yss-project.yaml 与唯一根 CONTEXT.md,执行 `scripts/verify-context-contract --root . --json`。缺失、大小写错误、嵌套合同、跨仓路径、伪锚点或不支持的 schema 都阻断;格式消费 `domain-modeling/CONTEXT-FORMAT.md`。
|
|
6
|
-
2.
|
|
7
|
-
3.
|
|
8
|
-
4.
|
|
9
|
-
5.
|
|
10
|
-
6.
|
|
6
|
+
2. 自行查证可发现事实,技术事实路由 `yss-research`,可执行阻塞安排原型或实际验证,专业审查由主控派发,验证失败修复或补证。真正用户决定才组成当前决策前沿;调查未完成只等待依赖该事实的问题,其他必要问题和独立已授权工作继续。未确认的术语留在 Plan,不能写成稳定事实。
|
|
7
|
+
3. 依问题依赖分轮询问全部当前可回答的必要问题,每题给出建议,保留真实原始回复;回复后再推进依赖问题。复用未变化的确认,用户纠正理解时只重开受影响问题及依赖。不能用口头同意代替事实、实验或审查证据。
|
|
8
|
+
4. 确认后由 domain-modeling 维护五列业务词汇表,以 `<ContextId>/<EnglishIdentifier>` 标识;非 Global 的 ContextId 来自已确认业务责任区。只有难以逆转、非显然且有真实取舍的决定才形成 ADR。研究和实际验证保留可读证据。
|
|
9
|
+
5. `project-instance` 批准或返回下一工作单元前生成 `context_reconciliation`,保存可读 ref、document_digest、referenced_terms_digest 与 term_refs,并执行对账验证。候选、别名、范围冲突或摘要漂移均阻断;对账不另增门禁。`template-source` 仅校验模板合同并记录有原因的 `not-applicable`,不虚构业务术语。
|
|
10
|
+
6. 收敛时回述用户问题、目标、MVP、非目标、关键规则、验收例子、未决项结论及剩余不确定性,核对解决证据和非关键延期交接。`checks.grill_exit=passed` 表示准备就绪,仍待最终真实决定。固定完整 Plan 审阅包后,将共同理解与当前 Plan 批准范围合并展示、取得同一真实回复;回复存入外部 `plan_user_decision_ref`,不回写审阅包或其 `basis`。入口通过准备检查和当前决定后才判定完整 `grill_exit`;既有批准仅按用户决定协议核验延续。
|
|
11
|
+
7. 结束时列出已确认决定、未决假设、变更文档、对账状态与引用、下一授权动作。未回流 blocker 不得进入 Spec。共同理解与变更范围未确认前不改实现合同或代码;澄清结果不能批准资产、设置 ready-for-agent 或自行推进阶段,交回当前编排器验收。
|
|
11
12
|
|
|
12
13
|
起草 Plan 使用 `.template-spec/plan/templates/plan-template.md`,Spec 使用 `.template-spec/templates/spec-template.md`;显式 `to-spec` 使用同一模板。可选只读诊断及未评估项见 `.template-spec/process/plan-spec-quality.md`,不增加阶段门禁。
|
|
@@ -26,6 +26,7 @@ export async function verifyPrototypeDesign({scope='all',toolchain=process.env.Y
|
|
|
26
26
|
if((await readdir(output)).length)throw Error('验证输出非空,拒绝覆盖历史证据');
|
|
27
27
|
const report={schema_version:1,scope,status:'running',started_at:new Date().toISOString(),output,source:null,tools:{node:process.version},checks:[],counts:{passed:0,failed:0,'not-executed':0,'not-applicable':0},limitations:['自动化结果不构成人工视觉批准、读屏结论或真实用户效果。']};
|
|
28
28
|
const env={...process.env,TMPDIR:output,YSS_PROTOTYPE_EVIDENCE_DIR:output};
|
|
29
|
+
delete env.NODE_TEST_CONTEXT; // 子进程独立输出 TAP,不继承父级 node --test 的内部传输协议。
|
|
29
30
|
const add=(id,status,reason,extra={})=>report.checks.push({id,status,...(reason?{reason}:{}),...extra});
|
|
30
31
|
async function run(id,args){
|
|
31
32
|
const log=path.join(output,id+'.log'),command=[process.execPath,...args];
|
|
@@ -6,6 +6,7 @@ import process from "node:process";
|
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import { parseDocument } from "../../../../scripts/vendor/yaml.mjs";
|
|
8
8
|
import { loadApprovalRecord, resolveApprovalRef, validateApprovalRecord } from "../../../../scripts/lib/approval-record.mjs";
|
|
9
|
+
import { approvalExpectationForBoundAsset } from "../../../../scripts/lib/approval-consumption.mjs";
|
|
9
10
|
import { verifyContextSnapshot } from "../../../../scripts/lib/context-contract.mjs";
|
|
10
11
|
import { parseAsset, validateAssetStructure } from '../../../../scripts/lib/structured-assets.mjs';
|
|
11
12
|
|
|
@@ -51,7 +52,7 @@ function requireArray(object, field, path, errors, min = 0, itemKind = "string")
|
|
|
51
52
|
if (field in object && list(object[field]) && itemKind === "string") object[field].forEach((item, index) => { if (!nonEmpty(item)) errors.push(`${path}.${field}[${index}] 必须是非空字符串`); });
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
function validate(data, contextRoot) {
|
|
55
|
+
function validate(data, contextRoot, currentRef) {
|
|
55
56
|
validateAssetStructure(data, 'domain-strategy');
|
|
56
57
|
const errors = [];
|
|
57
58
|
if (data?.traceability_version !== undefined) { try { extractTraceability(data); } catch(error) { errors.push(error.message); } }
|
|
@@ -199,9 +200,11 @@ function validate(data, contextRoot) {
|
|
|
199
200
|
if (contexts.some((context) => context.status !== "confirmed")) errors.push("approved 合同要求所有上下文 status=confirmed");
|
|
200
201
|
if (approval.current_version !== data.domain_version) errors.push("approval.current_version 必须等于 domain_version");
|
|
201
202
|
try {
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
203
|
+
const options = { root: contextRoot, review_package: false };
|
|
204
|
+
const approvalPath = resolveApprovalRef(approval.approval_ref, currentRef, options);
|
|
205
|
+
const expected = approvalExpectationForBoundAsset("check.domain-strategy-approved", { ref: currentRef }, options);
|
|
206
|
+
const record = loadApprovalRecord(approvalPath, "check.domain-strategy-approved", options);
|
|
207
|
+
validateApprovalRecord(record, { ...options, requireApproved: true, expected });
|
|
205
208
|
if (record.gate_id !== "check.domain-strategy-approved") errors.push("approval.approval_ref 的 gate_id 必须为 check.domain-strategy-approved");
|
|
206
209
|
if (record.role_id !== approval.approver) errors.push("approval.approver 必须与会签记录 role_id 一致");
|
|
207
210
|
} catch (error) { errors.push(`approval.approval_ref 会签记录无效: ${error.message}`); }
|
|
@@ -218,7 +221,7 @@ else {
|
|
|
218
221
|
assertCurrentAssetReference(values.root, path.relative(path.resolve(values.root), path.resolve(file)));
|
|
219
222
|
const source = await readFile(file, "utf8");
|
|
220
223
|
{
|
|
221
|
-
const errors = validate(parseAsset(source, file), values.root);
|
|
224
|
+
const errors = validate(parseAsset(source, file), values.root, path.resolve(file));
|
|
222
225
|
if (errors.length) fail(errors);
|
|
223
226
|
else process.stdout.write(JSON.stringify({ result: "completed", contract: file }, null, 2) + "\n");
|
|
224
227
|
}
|
|
@@ -8,6 +8,7 @@ import process from "node:process";
|
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
9
|
import { parseDocument } from "../../../../scripts/vendor/yaml.mjs";
|
|
10
10
|
import { loadApprovalRecord, resolveApprovalRef, validateApprovalRecord } from "../../../../scripts/lib/approval-record.mjs";
|
|
11
|
+
import { approvalExpectationForBoundAsset } from "../../../../scripts/lib/approval-consumption.mjs";
|
|
11
12
|
import { verifyContextSnapshot } from "../../../../scripts/lib/context-contract.mjs";
|
|
12
13
|
import { parseAsset, validateAssetStructure } from '../../../../scripts/lib/structured-assets.mjs';
|
|
13
14
|
|
|
@@ -27,7 +28,7 @@ function unique(items, label, errors) { const seen = new Set(); for (const item
|
|
|
27
28
|
function canonical(value) { if (Array.isArray(value)) return value.map(canonical); if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])])); return value; }
|
|
28
29
|
function digest(value) { return `sha256:${createHash("sha256").update(JSON.stringify(canonical(value))).digest("hex")}`; }
|
|
29
30
|
|
|
30
|
-
async function validate(data, contextRoot) {
|
|
31
|
+
async function validate(data, contextRoot, currentRef) {
|
|
31
32
|
validateAssetStructure(data, 'stage-decision-package');
|
|
32
33
|
const errors = [];
|
|
33
34
|
if (!data || typeof data !== "object" || Array.isArray(data)) return ["合同必须是对象"];
|
|
@@ -132,9 +133,11 @@ async function validate(data, contextRoot) {
|
|
|
132
133
|
if (unresolved.some((item) => item?.type === "blocker") && data.status === "approved") errors.push("存在 blocker 未决项时不得 approved");
|
|
133
134
|
if (data.status === "approved" && nonEmpty(approval.approval_ref)) {
|
|
134
135
|
try {
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
136
|
+
const options = { root: contextRoot, review_package: false };
|
|
137
|
+
const approvalPath = resolveApprovalRef(approval.approval_ref, currentRef, options);
|
|
138
|
+
const expected = approvalExpectationForBoundAsset("check.stage-decision-package-approved", { ref: currentRef }, options);
|
|
139
|
+
const record = loadApprovalRecord(approvalPath, "check.stage-decision-package-approved", options);
|
|
140
|
+
validateApprovalRecord(record, { ...options, requireApproved: true, expected });
|
|
138
141
|
if (record.gate_id !== "check.stage-decision-package-approved") errors.push("approval_ref 的 gate_id 必须为 check.stage-decision-package-approved");
|
|
139
142
|
if (record.role_id !== approval.approver) errors.push("approval.approver 必须与会签记录 role_id 一致");
|
|
140
143
|
} catch (error) { errors.push(`approval_ref 会签记录无效: ${error.message}`); }
|
|
@@ -151,7 +154,7 @@ else {
|
|
|
151
154
|
assertCurrentAssetReference(values.root, path.relative(path.resolve(values.root), path.resolve(file)));
|
|
152
155
|
const source = await readFile(file, "utf8");
|
|
153
156
|
{
|
|
154
|
-
const errors = await validate(parseAsset(source, file), values.root);
|
|
157
|
+
const errors = await validate(parseAsset(source, file), values.root, path.resolve(file));
|
|
155
158
|
if (errors.length) fail(errors);
|
|
156
159
|
else process.stdout.write(JSON.stringify({ result: "completed", contract: file }, null, 2) + "\n");
|
|
157
160
|
}
|