create-yss-spec 3.4.9 → 3.4.10
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/package.json +1 -1
- package/src/template/attach-planner.js +5 -0
- package/src/template/sync-planner.js +5 -0
- package/template/.agents/skills/.strategic-design-skills-manifest.json +1 -1
- package/template/.agents/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.agents/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.agents/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.agents/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration-contract.yaml +63 -1
- package/template/.agents/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.agents/skills/yss-prototype-stage/SKILL.md +2 -0
- package/template/.agents/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.agents/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.agents/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.agents/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.agents/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.agents/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/.codex/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.codex/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.codex/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.codex/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration-contract.yaml +63 -1
- package/template/.codex/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.codex/skills/yss-prototype-stage/SKILL.md +2 -0
- package/template/.codex/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.codex/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.codex/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.codex/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.codex/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.codex/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/.cursor/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.cursor/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.cursor/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.cursor/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration-contract.yaml +63 -1
- package/template/.cursor/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.cursor/skills/yss-prototype-stage/SKILL.md +2 -0
- package/template/.cursor/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.cursor/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.cursor/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.cursor/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.cursor/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.cursor/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/.pi/skills/yss-implementation-contract-compiler/SKILL.md +2 -0
- package/template/.pi/skills/yss-openapi-draft-review/SKILL.md +2 -0
- package/template/.pi/skills/yss-openapi-governance/SKILL.md +4 -0
- package/template/.pi/skills/yss-product-lifecycle/SKILL.md +11 -5
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration-contract.yaml +63 -1
- package/template/.pi/skills/yss-product-lifecycle/references/orchestration.md +2 -2
- package/template/.pi/skills/yss-prototype-stage/SKILL.md +2 -0
- package/template/.pi/skills/yss-prototype-stage/scripts/visual-baseline-contract.mjs +2 -2
- package/template/.pi/skills/yss-stage-decision/SKILL.md +2 -0
- package/template/.pi/skills/yss-tactical-design/SKILL.md +2 -0
- package/template/.pi/skills/yss-technical-design/SKILL.md +2 -0
- package/template/.pi/skills/yss-technical-design/scripts/engineering-design.mjs +1 -1
- package/template/.pi/skills/yss-technical-design/scripts/validate-technical-design.mjs +11 -8
- package/template/AGENTS.md +2 -0
- package/template/CONTEXT.md +2 -1
- package/template/README.md +1 -0
- package/template/docs/agents/issue-tracker.md +7 -0
- package/template/docs/process/contract-reading.md +46 -0
- package/template/docs/process/lifecycle-artifact-map.md +2 -2
- package/template/docs/process/lifecycle-registry-baseline.json +2 -1
- package/template/docs/process/lifecycle-registry.yaml +10 -4
- package/template/docs/process/schemas/api-contract-decision-v2.schema.json +198 -0
- package/template/docs/process/schemas/digital-human-task-package.schema.json +8 -0
- package/template/docs/process/schemas/lifecycle-checkpoint.schema.json +259 -0
- package/template/docs/process/schemas/stage-tracking.schema.json +259 -0
- package/template/docs/process/stage-tracking.md +62 -0
- package/template/docs/process/strategic-handoff-package.md +8 -0
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214.md +2 -0
- package/template/docs/user-guide//347/224/250/346/210/267/346/211/213/345/206/214/347/264/242/345/274/225.md +1 -0
- package/template/docs/user-guide//351/230/266/346/256/265/346/216/245/345/205/245/344/270/216/344/272/247/347/211/251/345/244/215/347/224/250/344/275/277/347/224/250/346/211/213/345/206/214.md +243 -0
- package/template/scripts/api-contract-decision +16 -0
- package/template/scripts/complete-backend-delivery +17 -0
- package/template/scripts/contract +13 -0
- package/template/scripts/dispatch-slice-task +13 -0
- package/template/scripts/lib/api-contract-decision.mjs +45 -4
- package/template/scripts/lib/approval-record.mjs +1 -1
- package/template/scripts/lib/approved-execution-context.mjs +35 -7
- package/template/scripts/lib/backend-architecture.mjs +2 -2
- package/template/scripts/lib/backend-delivery-terminal.mjs +51 -0
- package/template/scripts/lib/backend-platform-provenance.mjs +1 -1
- package/template/scripts/lib/backend-platform.mjs +1 -1
- package/template/scripts/lib/backend-scaffold-prerequisites.mjs +1 -1
- package/template/scripts/lib/context-contract.mjs +1 -1
- package/template/scripts/lib/context-reconciliation.mjs +1 -1
- package/template/scripts/lib/contract-source.mjs +22 -0
- package/template/scripts/lib/contract-views.mjs +109 -0
- package/template/scripts/lib/delivery-preflight.mjs +3 -2
- package/template/scripts/lib/digital-human-roles.mjs +1 -1
- package/template/scripts/lib/existing-backend-architecture.mjs +4 -3
- package/template/scripts/lib/existing-ui-baseline.mjs +4 -2
- package/template/scripts/lib/frontend-delivery-boundary.mjs +1 -1
- package/template/scripts/lib/frontend-delivery.mjs +1 -1
- package/template/scripts/lib/harness-execution-scope.mjs +15 -1
- package/template/scripts/lib/implementation-contract-compiler.mjs +4 -4
- package/template/scripts/lib/json-schema.mjs +19 -4
- package/template/scripts/lib/lifecycle-context-query.mjs +5 -1
- package/template/scripts/lib/lifecycle-controls.mjs +1 -1
- package/template/scripts/lib/lifecycle-execution-scope.mjs +92 -0
- package/template/scripts/lib/lifecycle-registry.mjs +2 -1
- package/template/scripts/lib/lifecycle-transition.mjs +21 -3
- package/template/scripts/lib/openapi-draft-validation.mjs +2 -1
- package/template/scripts/lib/skill-registry.mjs +1 -1
- package/template/scripts/lib/slice-contract-preparation.mjs +16 -14
- package/template/scripts/lib/slice-contract-views.mjs +1 -1
- package/template/scripts/lib/slice-contract.mjs +18 -3
- package/template/scripts/lib/slice-execution-preflight.mjs +7 -3
- package/template/scripts/lib/slice-repositories.mjs +1 -1
- package/template/scripts/lib/slice-task-package.mjs +3 -2
- package/template/scripts/lib/stage-tracking-migration.mjs +176 -0
- package/template/scripts/lib/stage-tracking.mjs +184 -0
- package/template/scripts/lib/strategic-handoff-consumption.mjs +5 -4
- package/template/scripts/lib/strategic-handoff-io.mjs +5 -2
- package/template/scripts/lib/strategic-handoff-routing.mjs +31 -0
- package/template/scripts/lib/strategic-handoff.mjs +4 -2
- package/template/scripts/lib/task-package.mjs +2 -0
- package/template/scripts/lib/technical-design-boundary.mjs +4 -2
- package/template/scripts/lib/ui-baseline.mjs +1 -1
- package/template/scripts/lib/user-decision.mjs +1 -1
- package/template/scripts/lib/validation-phase.mjs +72 -0
- package/template/scripts/stage-tracking +25 -0
- package/template/scripts/strategic-consumer-entry +47 -0
- package/template/scripts/verify-context-reconciliation +4 -3
- package/template/scripts/verify-lifecycle-checkpoint +10 -0
- package/template/skills-lock.json +13 -13
- package/template.snapshot.json +4 -4
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# 阶段接入与产物复用使用手册
|
|
2
|
+
|
|
3
|
+
已有需求、设计、冻结接口或交付包时,可以让 `yss-product-lifecycle` 核验这些产物,从最近可信阶段继续。有效的上游成果可以复用;缺失或过期的部分按依赖补齐,不需要默认从 Plan 重做。
|
|
4
|
+
|
|
5
|
+
本手册面向项目负责人、后端交付人员和前端开发人员,重点演示“后端已交付,前端接着开发”。本文是现行规则的操作说明,不新增阶段或批准入口;规则以[生命周期注册表](../process/lifecycle-registry.yaml)、[流程裁剪规则](../process/harness-process-tailoring.md)和[前后端交付合同](../process/frontend-backend-delivery.md)为准。
|
|
6
|
+
|
|
7
|
+
## 1. 先确认在哪个项目操作
|
|
8
|
+
|
|
9
|
+
在承载实际业务的 `project-instance` 中开始,读取本地 `yss-project.yaml`、`AGENTS.md`、根 `CONTEXT.md` 和适用 profile。当前主模板仓库是 `template-source`,用于维护流程和手册,不能在这里生成真实业务 Spec、切片或实现合同。
|
|
10
|
+
|
|
11
|
+
| 你所在的项目 | 可以接着做什么 |
|
|
12
|
+
|---|---|
|
|
13
|
+
| 综合项目实例 | 由 `yss-product-lifecycle` 核验上游,继续设计、工程契约、Ticket、实现或验证 |
|
|
14
|
+
| 专职前端项目实例 | 按本地主控入口联合接收战略和后端交付,继续前端工程设计、实现与验收 |
|
|
15
|
+
| 专职后端项目实例 | 消费批准的上游资产,完成后端技术设计、实现与交付 |
|
|
16
|
+
| 尚未初始化或接管的代码仓 | 先按[用户手册](用户手册.md)初始化或接管,并登记实现仓库 |
|
|
17
|
+
|
|
18
|
+
专职项目使用本地已安装的技能与职责规则;不要从主模板复制一个主控 Skill 来替换本地入口。治理仓与代码仓可以分离,代码写入已登记的实现仓及允许路径。
|
|
19
|
+
|
|
20
|
+
## 2. 最快开始:给 Agent 一段完整指令
|
|
21
|
+
|
|
22
|
+
把下面尖括号中的内容替换为真实信息。没有的资料写“缺失”,不知道的状态写“待核验”,不必为了开始咨询先填写整套合同。
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
使用 yss-product-lifecycle,从已有产物接入本功能。
|
|
26
|
+
|
|
27
|
+
目标:<本次希望完成的业务行为>
|
|
28
|
+
治理项目根:<project-instance 的绝对路径>
|
|
29
|
+
目标实现仓:<代码仓绝对路径或现有登记引用>
|
|
30
|
+
已有产物:<Spec、设计、交付包、合同等路径>
|
|
31
|
+
批准与验证证据:<可读取引用;没有则写缺失>
|
|
32
|
+
父 Ticket / 切片 / checkpoint:<现有引用;没有则说明>
|
|
33
|
+
希望开始的位置:<前端接收、工程设计、实现或验证>
|
|
34
|
+
本次范围:<功能、页面、切片及不包含的内容>
|
|
35
|
+
|
|
36
|
+
先只读判断最近可信阶段,给出可以复用的资产、阻塞项和下一工作单元。
|
|
37
|
+
核验上游版本、批准范围及当前证据,不把文件存在视为阶段通过。
|
|
38
|
+
暂不写文件或代码。
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
如果已决定推进,可以把最后一句替换为:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
完成核验后,在本次范围内继续可执行的工作单元,只补齐缺失项。
|
|
45
|
+
优先核验并延续已有授权;遇到实质变化、必要输入缺失或适用人工决定时,
|
|
46
|
+
展示当前资产、差异与下一动作。满足 ready-for-agent 后才实现代码。
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
自然语言即可表达入口意图,不要求记忆模式关键字。`route` 表示只读路由,`audit` 表示只读审计,`orchestrate` 表示有界推进,`resume` 表示重建状态后恢复。模式语义见[主控编排说明](../../.agents/skills/yss-product-lifecycle/references/orchestration.md)。指定“从实现开始”只是期望入口,最终仍由当前证据决定。
|
|
50
|
+
|
|
51
|
+
## 3. 根据手里的产物选择入口
|
|
52
|
+
|
|
53
|
+
| 已有材料 | 可以请求的下一动作 | 进入下一步前重点核验 |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| 已批准 Plan / 战略输入 | 继续 Spec | 当前范围、关键未决项、Plan → Spec 入口审阅及真实批准 |
|
|
56
|
+
| 已批准 Spec | 继续产品设计或适用工程分析 | 验收标准、影响面、上游新鲜度;有产品设计影响时完成对应设计 |
|
|
57
|
+
| 已批准页面方案或原型 | 继续工程契约与实现准备 | 页面、状态、权限体验与 Spec 一致;适用技术和接口契约完整 |
|
|
58
|
+
| 冻结接口及工程设计 | 继续 Ticket 与合同准备 | 工程批准、实现仓接入、切片范围及相关 UI 输入 |
|
|
59
|
+
| 完整后端交付包 | 从前端联合接收开始 | 战略与后端版本、目标词汇、UI 基线、场景映射和实际服务条件 |
|
|
60
|
+
| 当前、已批准的前端 Slice Contract | 恢复前端实现 | Ticket、实现计划、依赖、写入范围、适用检查与完整就绪计算 |
|
|
61
|
+
| 已实现代码及当前合同 | 继续审查与验证 | 实际改动与批准范围一致、当前验证证据;已有代码不能补作批准证明 |
|
|
62
|
+
|
|
63
|
+
这些是常见入口,不是凭材料名称自动流转的对照表。上游资产需要同时满足内容、审查、批准和新鲜度要求。某项确实未命中时,按现行规则记录有原因和证据的 `not-applicable`;某项适用但未完成时,保留阻塞。
|
|
64
|
+
|
|
65
|
+
## 4. 后端交付后,前端如何继续
|
|
66
|
+
|
|
67
|
+
### 4.1 后端准备什么
|
|
68
|
+
|
|
69
|
+
请后端提供由正式工具导出的 Backend Delivery 目录或 ZIP,以及目标环境和必要的访问条件。交付包应绑定当前窄业务切片、战略快照、规则和场景、冻结 OpenAPI、后端 Slice Contract、批准引用、构建部署身份与验证证据。包内不放运行时凭据。
|
|
70
|
+
|
|
71
|
+
后端可以使用以下指令:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
请为切片 <slice-id> 准备后端交付,供前端接收。
|
|
75
|
+
复用当前批准的战略基线、冻结接口和后端 Slice Contract。
|
|
76
|
+
核验构建、部署、测试数据和成功/失败场景证据,按现有协议准备交付描述。
|
|
77
|
+
从正式 backend-delivery 入口导出目录包及 ZIP,并报告导出路径、版本和摘要。
|
|
78
|
+
缺少源批准、部署身份或预检清单时列出缺口,不伪造记录。
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
只有 Swagger 链接、源码或接口截图时,先做缺口分析。它们不能单独证明业务范围、UI 输入和当前批准已齐备。
|
|
82
|
+
|
|
83
|
+
### 4.2 前端接收并复用上游
|
|
84
|
+
|
|
85
|
+
在前端治理项目中发送;综合项目明确调用 `yss-product-lifecycle`,专职项目按本地主控入口执行:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
请从前端联合接收入口继续。
|
|
89
|
+
后端交付包:<目录或 ZIP 的绝对路径>
|
|
90
|
+
前端治理根:<project-instance 绝对路径>
|
|
91
|
+
前端实现仓:<登记引用或绝对路径>
|
|
92
|
+
本次切片:<slice-id 与业务范围>
|
|
93
|
+
|
|
94
|
+
先验证交付包,再通过正式工具导入,复用包中的战略、Spec 和冻结接口。
|
|
95
|
+
核对目标根 CONTEXT.md、适用 UI 基线、规则和成功/失败场景映射,
|
|
96
|
+
完成战略预检及最终联合接收;有后端依赖时验证当前服务身份。
|
|
97
|
+
保留已有有效的前端设计,列出缺失项与责任方。
|
|
98
|
+
接收通过后继续前端实现计划、Ticket 和 Slice Contract 准备,
|
|
99
|
+
完成适用批准和 ready-for-agent 计算后再进入实现。
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
后端导入会把其绑定的战略快照通过现有工具导入 `docs/handoffs/`,并保存后端包于 `docs/backend-deliveries/<delivery-id>/<version>/`。不需要前端把后端设计重新写一遍,但需要完成目标仓词汇对账和自己的场景承接。
|
|
103
|
+
|
|
104
|
+
导入产生草案,不会自动批准前端资产。已有预检和设计草案应保留;同身份、同版本、不同内容的包会被拒绝。
|
|
105
|
+
|
|
106
|
+
### 4.3 判断当前能做到哪一步
|
|
107
|
+
|
|
108
|
+
| 当前结果 | 可以继续 | 尚不能据此认定 |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| 后端包 `verified` | 导入并准备目标仓接收 | 服务当前可用、前端可以编码 |
|
|
111
|
+
| 前端战略预检通过 | 起草前端工程设计与实现计划 | 前端实现已经获准 |
|
|
112
|
+
| 最终接收返回 `inputs-verified` | 准备、补齐前端实现资产 | `ready-for-agent`;工具此时仍返回 `ready_for_agent: false` |
|
|
113
|
+
| 当前前端合同获准且完整就绪计算通过 | 在批准切片及路径内实现 | 合并、推送或发布获准 |
|
|
114
|
+
| 前端实现及验证完成 | 按流程进行交付验收 | 整个业务切片已经验收或已经发布 |
|
|
115
|
+
|
|
116
|
+
前端自己的实现准备包括适用的工程与 UI 输入、已校验的 `frontend_implementation_plan`、实现仓接入、正式垂直切片 Ticket 和当前持久化 Slice Implementation Contract。后端 Slice Contract 仅证明后端对应范围,不能代替前端合同。规则见[实现接入](../process/implementation-repo-integration.md)和[前端实现证据模板](../process/templates/frontend-implementation-plan-template.yaml)。
|
|
117
|
+
|
|
118
|
+
### 4.4 后端还没交付时,前端能做什么
|
|
119
|
+
|
|
120
|
+
战略输入已齐备时,可以先完成 Frontend Strategic Preflight,并起草工程设计和实现计划。有 Backend/API/Data 依赖的最终接收仍等待对应后端交付;这条路线中的正式编码仍需当前合同获准。
|
|
121
|
+
|
|
122
|
+
真正的 UI-only 工作可以按影响面使用有证据的 `backend-not-applicable`。不能因为后端暂时没交付,就把实际后端依赖标为不适用。
|
|
123
|
+
|
|
124
|
+
## 5. 需要手动操作时的命令
|
|
125
|
+
|
|
126
|
+
以下命令供交付维护人员和 Agent 使用。普通用户可以直接使用第 2、4 节的指令,让 Agent 操作并报告结果。命令中的 `scripts/` 来自当前已安装相应工具的治理项目;不要在未核对 profile 和工具版本时混用其他项目的脚本。
|
|
127
|
+
|
|
128
|
+
尖括号表示需替换的参数,不要原样粘贴执行。`--source-root`、`--target-root`、`--root` 指向治理项目根;交付描述、预检和接收记录中的引用按各自协议相对该治理根解析。新建后端交付输出目录必须尚不存在。
|
|
129
|
+
|
|
130
|
+
### 后端导出
|
|
131
|
+
|
|
132
|
+
在后端治理项目准备好真实交付描述后运行:
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
scripts/backend-delivery export \
|
|
136
|
+
--source-root <后端治理根绝对路径> \
|
|
137
|
+
--delivery <相对后端治理根的交付描述路径> \
|
|
138
|
+
--output <新的交付输出目录绝对路径> \
|
|
139
|
+
--zip
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**既有后端工程身份为 v2 时,必须增加 `--preflight-input <预检清单绝对路径>`。**清单使用 `delivery_kind: backend-delivery`,绑定本次源交付描述和当前摘要;准备方法见[只读交付预检](../process/delivery-preflight.md)。预检失败时按返回的 `recovery` 补证,再从正常导出入口重试。
|
|
143
|
+
|
|
144
|
+
### 验包与导入
|
|
145
|
+
|
|
146
|
+
在前端治理项目运行:
|
|
147
|
+
|
|
148
|
+
```sh
|
|
149
|
+
scripts/backend-delivery verify --bundle <交付目录或ZIP绝对路径>
|
|
150
|
+
|
|
151
|
+
scripts/backend-delivery import \
|
|
152
|
+
--bundle <交付目录或ZIP绝对路径> \
|
|
153
|
+
--target-root <前端治理根绝对路径>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
验包成功输出 `result: verified`,并明确 `live_service_checked: false`。记下导入命令返回的真实收据和草案路径;不要自行推测文件名或把草案状态直接改成已批准。
|
|
157
|
+
|
|
158
|
+
如果当前只有战略包,可先独立验证和导入:
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
scripts/strategic-handoff verify --bundle <战略交付目录或包绝对路径>
|
|
162
|
+
scripts/strategic-handoff import \
|
|
163
|
+
--bundle <战略交付目录或包绝对路径> \
|
|
164
|
+
--target-root <前端治理根绝对路径>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
后端包已经带入同一战略版本时,无需重复做手工导入。版本兼容按[前后端交付合同](../process/frontend-backend-delivery.md)处理,不通过修改 schema 版本来绕过检查。
|
|
168
|
+
|
|
169
|
+
### 校验前端接收
|
|
170
|
+
|
|
171
|
+
先让主控根据真实资产补齐正式 Context Reconciliation、UI 基线和场景承接,完成预检草案:
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
scripts/verify-frontend-strategic-preflight \
|
|
175
|
+
--root <前端治理根绝对路径> \
|
|
176
|
+
<相对前端治理根的预检记录路径>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
最终接收记录完成核对后,按协议记录 `status: accepted`,再实际运行:
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
scripts/verify-frontend-delivery \
|
|
183
|
+
--root <前端治理根绝对路径> \
|
|
184
|
+
--slice <slice-id> \
|
|
185
|
+
<相对前端治理根的接收记录路径>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
最后一项检查会复验输入、场景映射和适用的真实服务身份。`accepted` 字段本身不等于通过。部署不可达、身份不符或证据过期时先处理阻塞;离线验包不能替代这一步。成功后仍需编译、批准前端合同并计算就绪,不能把返回值手改为 `true`。
|
|
189
|
+
|
|
190
|
+
### 只读查看主控规则
|
|
191
|
+
|
|
192
|
+
综合项目可以查询实现入口的确定性合同投影:
|
|
193
|
+
|
|
194
|
+
```sh
|
|
195
|
+
scripts/query-lifecycle-context \
|
|
196
|
+
--mode route \
|
|
197
|
+
--work-unit work-unit.slice-implementation \
|
|
198
|
+
--include modes
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
此命令只展示规则与路由,不读取业务资产来代替完整就绪判定,也不会批准或执行实现。可用阶段、工作单元和查询键以本地注册表及工具支持为准。
|
|
202
|
+
|
|
203
|
+
## 6. 已有合同或中断后如何恢复
|
|
204
|
+
|
|
205
|
+
已有完整前端合同,可以直接提出恢复请求:
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
使用 yss-product-lifecycle 恢复切片 <slice-id>。
|
|
209
|
+
checkpoint:<路径>
|
|
210
|
+
前端 Slice Contract:<路径>
|
|
211
|
+
接收记录:<路径>
|
|
212
|
+
目标实现仓:<登记引用>
|
|
213
|
+
|
|
214
|
+
重新核验当前合同批准、上游摘要、接收绑定、Ticket、实现计划和允许路径。
|
|
215
|
+
前提仍满足时,从第一个未完成且未阻塞的工作单元继续。
|
|
216
|
+
复用未变化的有效资产,不重做已完成的需求和设计;
|
|
217
|
+
发现过期或新影响时,只回到受影响的资产与工作单元处理。
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
新会话、新机器或新目录不会使旧结论自动有效。恢复必须能读取原始资产与证据;相关输入变化后,先更新接收和依赖合同,再恢复。不同切片使用各自交付身份,避免不相关工作相互阻塞。
|
|
221
|
+
|
|
222
|
+
## 7. 常见阻塞怎么处理
|
|
223
|
+
|
|
224
|
+
| 现象 | 应采取的动作 |
|
|
225
|
+
|---|---|
|
|
226
|
+
| 当前仓是 `template-source` | 切换到真实项目实例;不要手改身份字段把模板源伪装成产品仓 |
|
|
227
|
+
| 只有接口文档,没有战略或业务验收 | 请来源方补交业务规则、范围和批准依据,先完成缺口分析 |
|
|
228
|
+
| 交付包导入成功,但仍不能编码 | 查看接收检查、前端计划、Ticket、合同批准和就绪缺口 |
|
|
229
|
+
| 目标词汇与源包冲突 | 完成目标根 `CONTEXT.md` 对账;业务规则冲突回交来源方 |
|
|
230
|
+
| UI 基线缺失或不匹配 | 补适用产品设计;既有 UI 基线仅按其限定条件使用,截图不能自行成为批准 |
|
|
231
|
+
| `preflight-required` | 按只读交付预检合同准备当前源资产清单,并传入 `--preflight-input` |
|
|
232
|
+
| API、包摘要或部署身份不一致 | 核对交付版本,向后端补取同一基线的冻结接口、部署与证据 |
|
|
233
|
+
| 导入了新的同身份后端版本 | 重新接收当前版本,更新绑定并重编译受影响的合同 |
|
|
234
|
+
| 合同出现 `stale`、`drift` 或 `new_impacts` | 停止依赖旧合同的实现,回到受影响的设计、接收或合同编译环节 |
|
|
235
|
+
| 工具缺失、profile 或 schema 不支持 | 按[用户手册](用户手册.md)核对实例固定版本与同步路径,保留现有资料,不混拷脚本或静默降级 |
|
|
236
|
+
|
|
237
|
+
## 8. 如何判断这次接入已达到目标
|
|
238
|
+
|
|
239
|
+
要求主控用当前资产报告:复用了哪些上游内容、从哪个工作单元接入、哪些缺口已补齐、还剩哪些阻塞,以及下一动作。需要编码时,必须明确报告目标切片、当前前端合同和 `ready-for-agent` 结论;需要交付时,另提供实际测试、还原验证及独立审查结果。
|
|
240
|
+
|
|
241
|
+
前端验证使用登记的 `pnpm` 命令,并按适用范围记录截图或视觉回归、状态交互、console warning 和实际退出码。前端完成与整个业务切片完成分别判断;最终业务验收需绑定同一战略、API、后端部署和前端版本。Git 提交、推送、合并与发布仍各自遵守授权边界。
|
|
242
|
+
|
|
243
|
+
进一步阅读:[完整用户手册](用户手册.md)、[设备借用贯穿案例](设备借用贯穿案例.md)、[技术设计用户指南](技术设计用户指南.md)、[前后端交付合同](../process/frontend-backend-delivery.md)。
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {parseArgs} from 'node:util';
|
|
5
|
+
import {parseSliceYaml} from './lib/slice-contract.mjs';
|
|
6
|
+
import {hash,safe,write} from './lib/strategic-handoff-io.mjs';
|
|
7
|
+
import {prepareApiContractDecision,migrateApiContractDecision} from './lib/api-contract-decision.mjs';
|
|
8
|
+
import {stringify} from './vendor/yaml.mjs';
|
|
9
|
+
try{
|
|
10
|
+
const{values,positionals}=parseArgs({allowPositionals:true,options:{root:{type:'string',default:process.cwd()},output:{type:'string'},version:{type:'string'}}});
|
|
11
|
+
const[command,ref]=positionals;if(positionals.length!==2||!['prepare','migrate'].includes(command)||!values.output)throw Error('用法: api-contract-decision prepare|migrate <输入> --output <新路径> [--root <目录>] [--version v2]');
|
|
12
|
+
const root=path.resolve(values.root),bytes=fs.readFileSync(safe(root,ref)),input=parseSliceYaml(bytes);
|
|
13
|
+
const result=command==='prepare'?prepareApiContractDecision(input,{root}):migrateApiContractDecision({ref,version:input.decision_version,impact:input.impact,digest:hash(bytes)},{root,version:values.version});
|
|
14
|
+
write(root,values.output,stringify(result));
|
|
15
|
+
console.log(JSON.stringify({result:'draft',ref:values.output,digest:hash(fs.readFileSync(safe(root,values.output))),approval_inherited:false,next:'按现有差异与授权延续协议完成生命周期批准'}));
|
|
16
|
+
}catch(error){console.error(error.message);process.exitCode=1;}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { parse } from './lib/strategic-handoff-io.mjs';
|
|
6
|
+
import { completeBackendDelivery, verifyBackendDeliveryTerminal } from './lib/backend-delivery-terminal.mjs';
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const { values, positionals } = parseArgs({ allowPositionals: true, options: { root: { type: 'string' }, input: { type: 'string' } } });
|
|
10
|
+
if (positionals.length !== 1 || !values.root) throw new TypeError('用法: complete-backend-delivery complete --root <governance> --input <plan.json> | verify --root <governance>');
|
|
11
|
+
const root = path.resolve(values.root);
|
|
12
|
+
let result;
|
|
13
|
+
if (positionals[0] === 'verify' && !values.input) result = await verifyBackendDeliveryTerminal(root);
|
|
14
|
+
else if (positionals[0] === 'complete' && values.input) result = await completeBackendDelivery(root, parse(readFileSync(values.input)));
|
|
15
|
+
else throw new TypeError('需要 complete 或 verify 及匹配参数');
|
|
16
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
17
|
+
} catch (error) { process.stderr.write(`${JSON.stringify({ result: 'blocked', error: error.message })}\n`); process.exitCode = 1; }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {viewContract,diffContract} from './lib/contract-views.mjs';
|
|
4
|
+
try{
|
|
5
|
+
const args=process.argv.slice(2),mode=args.shift(),options={},positional=[];
|
|
6
|
+
for(let i=0;i<args.length;i++){const arg=args[i];if(arg==='--json')options.json=true;else if(arg.startsWith('--')){const key=arg.slice(2);if(!['kind','profile','unit','root','before-root'].includes(key)||!args[i+1]||args[i+1].startsWith('--'))throw Error(`未知或缺值参数 ${arg}`);options[key]=args[++i];}else positional.push(arg);}
|
|
7
|
+
const config={kind:options.kind,root:path.resolve(options.root||process.cwd()),profile:options.profile||'review',unit_id:options.unit,beforeRoot:options['before-root']?path.resolve(options['before-root']):undefined};
|
|
8
|
+
if(!options.kind)throw Error('必须用 --kind 明确资产类型');
|
|
9
|
+
if(!['view','diff'].includes(mode)||positional.length!==(mode==='view'?1:2))throw Error('用法: contract view <资产> --kind <类型> [--profile review|task|full] [--unit <ID>] [--json] | diff <旧资产> <新资产> --kind <类型>');
|
|
10
|
+
const result=mode==='view'?viewContract(positional[0],config):diffContract(positional[0],positional[1],config);
|
|
11
|
+
process.stdout.write(options.json||!result.markdown?JSON.stringify(result,null,2)+'\n':result.markdown);
|
|
12
|
+
if(result.blockers?.length)process.exitCode=1;
|
|
13
|
+
}catch(error){process.stderr.write(error.message+'\n');process.exitCode=1;}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { parseSliceYaml } from './lib/slice-contract.mjs';
|
|
6
|
+
import { compileSliceTaskPackage } from './lib/slice-task-package.mjs';
|
|
7
|
+
try {
|
|
8
|
+
const { values } = parseArgs({ options: { root: { type: 'string' }, input: { type: 'string' } } });
|
|
9
|
+
if (!values.root || !values.input) throw new TypeError('用法: dispatch-slice-task --root <project> --input <task-input.yaml>');
|
|
10
|
+
const input = parseSliceYaml(readFileSync(values.input));
|
|
11
|
+
const result = compileSliceTaskPackage(input.binding, { ...input, root: path.resolve(values.root) });
|
|
12
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
13
|
+
} catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {readContractSource} from './contract-source.mjs';
|
|
2
|
+
import { readFileSync, withValidationPhase } from "./validation-phase.mjs";
|
|
3
|
+
import { parseSliceYaml } from "./slice-contract.mjs";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
@@ -15,15 +17,18 @@ const readBound = (root, ref, label) => {
|
|
|
15
17
|
};
|
|
16
18
|
const requireDigest = (asset, expected) => { if (asset.digest !== expected) fail(`${asset.label}摘要漂移`); };
|
|
17
19
|
|
|
18
|
-
export function validateApiContractDecision(binding,
|
|
20
|
+
export function validateApiContractDecision(binding, options = {}) {return withValidationPhase({root:options.root||TEMPLATE_ROOT,purpose:'api-decision',slice_id:binding?.ref,readOnly:true},()=>validate(binding,options));}
|
|
21
|
+
function validate(binding, { root = TEMPLATE_ROOT } = {}) {
|
|
19
22
|
if (!binding || !binding.ref || !binding.version || !binding.digest || !["required", "not-applicable"].includes(binding.impact)) fail("缺少 API Contract Decision 当前版本绑定");
|
|
20
23
|
const projectRoot = path.resolve(root);
|
|
21
24
|
const decisionAsset = readBound(projectRoot, binding.ref, "API Contract Decision");
|
|
22
25
|
requireDigest(decisionAsset, binding.digest);
|
|
23
26
|
const document = parseDocument(decisionAsset.bytes.toString("utf8"), { uniqueKeys: true, maxAliasCount: 0 });
|
|
24
27
|
if (document.errors.length) fail(`API Contract Decision 无法解析: ${document.errors[0].message}`);
|
|
25
|
-
const
|
|
26
|
-
validateJsonSchema(
|
|
28
|
+
const original = parseSliceYaml(decisionAsset.bytes);
|
|
29
|
+
validateJsonSchema(original, original.schema_version===2?SCHEMA.replace(".schema", "-v2.schema"):SCHEMA, { cwd: TEMPLATE_ROOT, label: "API Contract Decision" });
|
|
30
|
+
if(original.status!=="approved")fail("API Contract Decision 必须经生命周期批准为 approved");
|
|
31
|
+
const decision={...structuredClone(original),current_version:true}; // Derived only after schema and current raw binding checks; never persisted.
|
|
27
32
|
if (decision.decision_version !== binding.version || decision.impact !== binding.impact) fail("API Contract Decision 版本或影响结论与合同不一致");
|
|
28
33
|
requireDigest(readBound(projectRoot, decision.assessment_ref, "API 影响评估"), decision.assessment_digest);
|
|
29
34
|
for (const ref of decision.evidence_refs) readBound(projectRoot, ref, "API Decision 证据");
|
|
@@ -40,6 +45,12 @@ export function validateApiContractDecision(binding, { root = TEMPLATE_ROOT } =
|
|
|
40
45
|
const reviewRecord = reviewDocument.toJS({ maxAliasCount: 0 });
|
|
41
46
|
if (reviewRecord?.result !== "approved" || !Array.isArray(reviewRecord.blocking_findings) || reviewRecord.blocking_findings.length > 0) fail("OpenAPI Draft Review 实际记录未批准或仍有阻断项");
|
|
42
47
|
if (reviewRecord.draft_ref !== decision.openapi.ref || reviewRecord.draft_digest !== decision.openapi.digest) fail("OpenAPI Draft Review 实际记录未绑定当前权威 YAML");
|
|
48
|
+
if (!Array.isArray(reviewRecord.evidence_refs) || !reviewRecord.evidence_refs.length) fail("OpenAPI Draft Review 缺少实际证据");
|
|
49
|
+
for (const ref of reviewRecord.evidence_refs) readBound(projectRoot, ref, "OpenAPI Draft Review 实际证据");
|
|
50
|
+
if(original.schema_version===2){
|
|
51
|
+
decision.draft_review={...decision.draft_review,result:reviewRecord.result,blocking_findings:reviewRecord.blocking_findings,draft_ref:reviewRecord.draft_ref,draft_digest:reviewRecord.draft_digest,evidence_refs:reviewRecord.evidence_refs};
|
|
52
|
+
decision.freeze={...decision.freeze,draft_ref:decision.openapi.ref,draft_digest:decision.openapi.digest};
|
|
53
|
+
}
|
|
43
54
|
for (const ref of decision.draft_review.evidence_refs) readBound(projectRoot, ref, "OpenAPI Draft Review 证据");
|
|
44
55
|
const validated = validateOpenApiDraftValidationRecord(validation.file, { root: projectRoot });
|
|
45
56
|
if (validated.record.draft.ref !== decision.openapi.ref || validated.draftDigest !== decision.openapi.digest) fail("OpenAPI Validation 未绑定当前权威 YAML");
|
|
@@ -47,3 +58,33 @@ export function validateApiContractDecision(binding, { root = TEMPLATE_ROOT } =
|
|
|
47
58
|
if (decision.freeze.version !== decision.openapi.version || decision.freeze.draft_ref !== decision.openapi.ref || decision.freeze.draft_digest !== decision.openapi.digest) fail("OpenAPI Freeze 未绑定当前权威 YAML 版本和摘要");
|
|
48
59
|
return { decision, openapi: decision.openapi, binding: { ref: binding.ref, version: binding.version, digest: binding.digest, impact: decision.impact } };
|
|
49
60
|
}
|
|
61
|
+
|
|
62
|
+
/** Source selectors contain business choices; digests are always derived from original bytes. */
|
|
63
|
+
export function prepareApiContractDecision(input,{root=TEMPLATE_ROOT}={}) {
|
|
64
|
+
return withValidationPhase({root,purpose:'api-prepare',slice_id:input.decision_id,readOnly:true},()=>{
|
|
65
|
+
if(input.schema_version!==undefined&&input.schema_version!==2)fail('旧 schema 必须通过显式 migrate 迁移');
|
|
66
|
+
if(input.kind!==undefined&&input.kind!=='api-contract-decision')fail('来源 kind 与 API 决定冲突');
|
|
67
|
+
const allowed=new Set(['schema_version','kind','decision_id','decision_version','status','impact','assessment_ref','assessment_digest','evidence_refs','reason','openapi','validation_record','draft_review','freeze']);
|
|
68
|
+
for(const key of Object.keys(input))if(!allowed.has(key))fail(`未分类约束需人工处理: ${key}`);
|
|
69
|
+
if(input.status&&input.status!=='draft')fail('准备器不能创建批准身份或更新已批准合同');
|
|
70
|
+
const result=structuredClone(input);Object.assign(result,{schema_version:2,kind:'api-contract-decision',status:'draft'});
|
|
71
|
+
const conflicts=[];
|
|
72
|
+
const bind=(value,label)=>{try{return {...value,...readContractSource(value,{root}).binding};}catch(error){conflicts.push({field:label,source_ref:value.ref,reason:error.message,conflicts:error.conflicts||[],recovery:'核对双方来源并补齐当前文件后重新准备'});return value;}};
|
|
73
|
+
result.assessment_digest=bind({ref:input.assessment_ref,...(input.assessment_digest?{digest:input.assessment_digest}:{})},'API 影响评估').digest;
|
|
74
|
+
for(const key of ['openapi','validation_record','draft_review'])if(result[key])result[key]=bind(result[key],key);
|
|
75
|
+
for(const ref of result.evidence_refs||[])try{readBound(root,ref,'API 决定证据');}catch(error){conflicts.push({field:'evidence_refs',source_ref:ref,reason:error.message,recovery:'补齐当前证据后重新准备'});}
|
|
76
|
+
if(conflicts.length)throw Object.assign(new TypeError(conflicts.map(item=>`${item.field}: ${item.reason}; 恢复: ${item.recovery}`).join('\n')),{code:'API_SOURCE_CONFLICT',conflicts});
|
|
77
|
+
validateJsonSchema(result,SCHEMA.replace('.schema','-v2.schema'),{label:'API Contract Decision v2 草案'});
|
|
78
|
+
return result;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function migrateApiContractDecision(binding,{root=TEMPLATE_ROOT,version}={}) {
|
|
82
|
+
return withValidationPhase({root,purpose:'api-migrate',slice_id:binding?.ref,readOnly:true},()=>{
|
|
83
|
+
const validated=validateApiContractDecision(binding,{root}),old=validated.decision;
|
|
84
|
+
if(old.schema_version!==1)fail('显式迁移只接受 v1');
|
|
85
|
+
if(!/^v[1-9][0-9]*$/.test(version)||BigInt(version.slice(1))<=BigInt(old.decision_version.slice(1)))fail('迁移必须选择新的递增决定版本');
|
|
86
|
+
const result=structuredClone(old);result.schema_version=2;result.decision_version=version;result.status='draft';delete result.current_version;
|
|
87
|
+
if(result.draft_review){result.evidence_refs=[...new Set([...result.evidence_refs,...result.draft_review.evidence_refs])];result.draft_review={ref:result.draft_review.ref,digest:result.draft_review.digest};result.freeze={version:result.freeze.version,frozen_at:result.freeze.frozen_at};}
|
|
88
|
+
return prepareApiContractDecision(result,{root});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assertApprovalUserDecision } from './user-decision-reuse.mjs';
|
|
2
|
-
import { existsSync, readFileSync } from
|
|
2
|
+
import { existsSync, readFileSync } from './validation-phase.mjs';
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
5
5
|
import {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import {validateJsonSchemas} from './json-schema.mjs';
|
|
2
|
+
import { assertScopeSlice, assertScopeWorkUnit } from './lifecycle-execution-scope.mjs';
|
|
1
3
|
import { selectSliceWorkUnit, normalizeSliceContract, sourceSliceContract, parseSliceYaml } from './slice-contract.mjs';
|
|
2
4
|
import path from 'node:path';
|
|
3
|
-
import { readFileSync } from '
|
|
5
|
+
import { readFileSync, inValidationPhase, validationMemo, validationPhaseToken } from './validation-phase.mjs';
|
|
4
6
|
import { ROOT, read, parse, safe, hash, digest, schema } from './strategic-handoff-io.mjs';
|
|
5
7
|
import { countersignRuleForGate } from './digital-human-roles.mjs';
|
|
6
8
|
import { validateApprovalRecord } from './approval-record.mjs';
|
|
7
9
|
import { assertImplementationDecision } from './user-decision.mjs';
|
|
8
10
|
import { loadSkillRegistry } from './skill-registry.mjs';
|
|
9
11
|
|
|
12
|
+
const primeCache=Symbol('approval-schema-prime');
|
|
10
13
|
const contexts=new WeakMap();
|
|
11
14
|
const gateId='gate.slice-contract-approved';
|
|
12
15
|
const check=(ok,code,message)=>{if(!ok){const error=new TypeError(`${code}: ${message}`);error.code=code;throw error;}};
|
|
@@ -68,27 +71,52 @@ function executionBasis(verified,{allowCompilerDrift=false}={}) {
|
|
|
68
71
|
return verified;
|
|
69
72
|
}
|
|
70
73
|
|
|
74
|
+
function primeApprovalSchemas(binding,root){
|
|
75
|
+
if(!inValidationPhase())return;
|
|
76
|
+
validationMemo(primeCache,JSON.stringify([root,binding]),()=>{
|
|
77
|
+
const jobs=[];
|
|
78
|
+
const add=(value,ref,formatChecker,errorStyle)=>jobs.push({value,schemaPath:path.join(ROOT,ref),formatChecker,errorStyle});
|
|
79
|
+
// Speculative shape checks never decide approval or change the original error order.
|
|
80
|
+
// Original consumers still validate every semantic and authorization rule below.
|
|
81
|
+
try{
|
|
82
|
+
const source=parseSliceYaml(readFileSync(safe(root,binding.ref))),raw=source.slice_contract||source;
|
|
83
|
+
if(raw.schema_version===3)add(raw,'docs/process/schemas/slice-implementation-contract-v3.schema.json',false,'verbose');
|
|
84
|
+
const approval=read(safe(root,binding.approval_ref));
|
|
85
|
+
if(approval.gates&&approval.human_review&&!approval.gate_id){
|
|
86
|
+
add(approval,'docs/process/schemas/lifecycle-checkpoint.schema.json',false,'verbose');
|
|
87
|
+
for(const requirement of approval.human_review.user_decisions||[])if(requirement.user_decision_ref&&!requirement.continuation_ref){const ref=path.isAbsolute(requirement.user_decision_ref)?path.relative(root,requirement.user_decision_ref):requirement.user_decision_ref;add(read(safe(root,ref)),'docs/process/schemas/user-decision.schema.json',true,'compact');}
|
|
88
|
+
}
|
|
89
|
+
}catch{/* The original consumer reports missing or invalid input with its existing code. */}
|
|
90
|
+
if(jobs.length)validateJsonSchemas(jobs);return true;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
71
93
|
export function createApprovedExecutionContext(binding,options={}) {
|
|
94
|
+
primeApprovalSchemas(binding,path.resolve(options.root||process.cwd()));
|
|
72
95
|
const verified=executionBasis(verifySliceContractApproval(binding,options));
|
|
73
|
-
selectSliceWorkUnit(verified.contract,options.work_unit_id);
|
|
96
|
+
assertScopeSlice(selectSliceWorkUnit(verified.contract,options.work_unit_id),{root:verified.root});
|
|
97
|
+
if(!options.readOnly)assertScopeWorkUnit('work-unit.slice-implementation',{root:verified.root});
|
|
74
98
|
const context=Object.freeze({kind:'approved-slice-execution-context'});
|
|
75
|
-
contexts.set(context,{binding:verified.binding,root:verified.root,work_unit_id:options.work_unit_id,allowCompilerDrift:false});
|
|
99
|
+
contexts.set(context,{phase:validationPhaseToken(),binding:verified.binding,root:verified.root,work_unit_id:options.work_unit_id,allowCompilerDrift:false,readOnly:options.readOnly===true});
|
|
76
100
|
return context;
|
|
77
101
|
}
|
|
78
102
|
|
|
79
103
|
/** A prior approved scope may authenticate bounded output while current compiler facts are recomputed. */
|
|
80
104
|
export function createApprovedRecompilationContext(binding,options={}) {
|
|
81
|
-
const verified=executionBasis(verifySliceContractApproval(binding,options),{allowCompilerDrift:true});
|
|
82
|
-
selectSliceWorkUnit(verified.contract,options.work_unit_id);
|
|
105
|
+
const verified=executionBasis(verifySliceContractApproval(binding,options),{allowCompilerDrift:true,readOnly:options.readOnly===true});
|
|
106
|
+
assertScopeSlice(selectSliceWorkUnit(verified.contract,options.work_unit_id),{root:verified.root});
|
|
107
|
+
if(!options.readOnly)assertScopeWorkUnit('work-unit.slice-implementation',{root:verified.root});
|
|
83
108
|
const context=Object.freeze({kind:'approved-slice-recompilation-context'});
|
|
84
|
-
contexts.set(context,{binding:verified.binding,root:verified.root,work_unit_id:options.work_unit_id,allowCompilerDrift:true});
|
|
109
|
+
contexts.set(context,{phase:validationPhaseToken(),binding:verified.binding,root:verified.root,work_unit_id:options.work_unit_id,allowCompilerDrift:true,readOnly:options.readOnly===true});
|
|
85
110
|
return context;
|
|
86
111
|
}
|
|
87
112
|
|
|
88
113
|
/** Revalidate original bytes on every boundary; a serialized/caller-invented context is rejected. */
|
|
89
|
-
export function assertApprovedExecutionContext(context,{root=process.cwd(),contract,architectureIdentity,architectureEvidence,technicalDesign,sliceId}={}) {
|
|
114
|
+
export function assertApprovedExecutionContext(context,{root=process.cwd(),contract,architectureIdentity,architectureEvidence,technicalDesign,sliceId,readOnly=false}={}) {
|
|
90
115
|
const saved=contexts.get(context);
|
|
91
116
|
check(saved&&saved.root===path.resolve(root),'EXECUTION_CONTEXT_UNTRUSTED','只允许从当前原始 Slice 与本地批准生成的执行上下文');
|
|
117
|
+
check(!saved.phase||saved.phase===validationPhaseToken(),'EXECUTION_CONTEXT_EXPIRED','不能跨验证阶段复用批准上下文');
|
|
118
|
+
check(!saved.readOnly||readOnly,'EXECUTION_CONTEXT_READ_ONLY','只读检查上下文不能执行实现');
|
|
119
|
+
if(!readOnly)assertScopeWorkUnit('work-unit.slice-implementation',{root});
|
|
92
120
|
const verified=executionBasis(verifySliceContractApproval(saved.binding,{root,contract}),{allowCompilerDrift:saved.allowCompilerDrift});
|
|
93
121
|
const current=selectSliceWorkUnit(verified.contract,saved.work_unit_id),resolution=current.resolution;
|
|
94
122
|
if(sliceId)check(current.slice_id===sliceId,'EXECUTION_SCOPE_CONFLICT','执行上下文属于另一切片');
|
|
@@ -37,9 +37,9 @@ export function validateArchitectureIdentity(identity, registry = loadSkillRegis
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// Existing projects must present original on-disk evidence, not three copies supplied by a caller.
|
|
40
|
-
export function verifyArchitectureEvidence(identity, evidence, { root, registry = loadSkillRegistry(), execution } = {}) {
|
|
40
|
+
export function verifyArchitectureEvidence(identity, evidence, { root, registry = loadSkillRegistry(), execution, readOnly=false } = {}) {
|
|
41
41
|
validateArchitectureIdentity(identity, registry);
|
|
42
|
-
if (identity.schema_version === 2) return verifyExistingArchitecture(identity, evidence, { root, registry, execution });
|
|
42
|
+
if (identity.schema_version === 2) return verifyExistingArchitecture(identity, evidence, { root, registry, execution, readOnly });
|
|
43
43
|
if (!evidence?.engineering_baseline || !evidence?.repository_registration || !evidence?.manifest) throw new TypeError("缺少工程基线、仓库登记或 Manifest 架构证据");
|
|
44
44
|
assertArchitectureAgreement(identity, evidence, registry);
|
|
45
45
|
return { source_kind: "scaffold", bindings: evidence };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { inspectBackendDelivery, openBackendDelivery } from './backend-delivery.mjs';
|
|
5
|
+
import { validateBackendReview } from './backend-review.mjs';
|
|
6
|
+
import { read, safe, hash, json, write, ensure, project } from './strategic-handoff-io.mjs';
|
|
7
|
+
import { loadExecutionScope, TERMINAL_REF } from './lifecycle-execution-scope.mjs';
|
|
8
|
+
|
|
9
|
+
async function inspect(root, record) {
|
|
10
|
+
project(root);
|
|
11
|
+
ensure(loadExecutionScope(root)?.scope_id === 'plan-to-backend', '需要 plan-to-backend 职责范围');
|
|
12
|
+
ensure(record.schema_version === 1 && record.kind === 'backend-delivery-terminal'
|
|
13
|
+
&& record.business_completed === false && record.release_authorized === false, '后端终点不是业务完成或发布批准');
|
|
14
|
+
for (const name of ['delivery', 'review_state']) {
|
|
15
|
+
ensure(hash(readFileSync(safe(root, record[name]?.ref))) === record[name].digest, `${name} 原始字节变化`);
|
|
16
|
+
}
|
|
17
|
+
const state = read(safe(root, record.review_state.ref));
|
|
18
|
+
const { delivery } = await inspectBackendDelivery(root, record.delivery.ref);
|
|
19
|
+
ensure(state.review_input?.scope_kind === 'change' && state.review_input.slice_contract_ref === delivery.slice_contract.ref, '独立审查未绑定交付的 Slice');
|
|
20
|
+
ensure(validateBackendReview(state, { root }).status === 'passed', '后端交付需要当前独立审查');
|
|
21
|
+
const commit = execFileSync('git', ['-C', path.resolve(root, state.review_input.project_root), 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
|
|
22
|
+
ensure(state.review_input.review_mode === 'committed' && delivery.build.source_commit === commit, '终点需要与当前已提交审查候选一致的真实构建源码提交');
|
|
23
|
+
ensure(record.downstream && ['owner', 'ticket_ref', 'verification_plan', 'target_version'].every(key => typeof record.downstream[key] === 'string' && record.downstream[key].trim()), '终点缺少下游接收责任和待办');
|
|
24
|
+
return openBackendDelivery(safe(root, record.bundle_ref), bundle => {
|
|
25
|
+
ensure(record.bundle_digest === bundle.manifest.bundle_digest && bundle.manifest.delivery_ref === record.delivery.ref, '终点绑定的交付包不一致');
|
|
26
|
+
// A different valid package must not be used to close the current source.
|
|
27
|
+
for (const file of bundle.manifest.files) if (file.original_ref) {
|
|
28
|
+
ensure(hash(readFileSync(safe(root, file.original_ref))) === file.sha256, `包与当前源不同: ${file.original_ref}`);
|
|
29
|
+
}
|
|
30
|
+
return { result: 'backend-delivered', delivery_id: delivery.delivery_id, version: delivery.version,
|
|
31
|
+
bundle_digest: bundle.manifest.bundle_digest, next_work_unit: null, business_completed: false,
|
|
32
|
+
release_authorized: false, live_service_checked: false, downstream: record.downstream };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function verifyBackendDeliveryTerminal(root) {
|
|
37
|
+
return inspect(root, read(safe(root, TERMINAL_REF)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** This closes only the backend responsibility after real package and review validation. */
|
|
41
|
+
export async function completeBackendDelivery(root, input) {
|
|
42
|
+
root = path.resolve(root);
|
|
43
|
+
const record = { ...input, schema_version: 1, kind: 'backend-delivery-terminal', business_completed: false, release_authorized: false };
|
|
44
|
+
ensure(!existsSync(path.join(root, TERMINAL_REF)), '终点已存在;只能复验,不覆盖旧终点');
|
|
45
|
+
const result = await inspect(root, record);
|
|
46
|
+
const bytes = json(record);
|
|
47
|
+
write(root, TERMINAL_REF, bytes);
|
|
48
|
+
try { await verifyBackendDeliveryTerminal(root); }
|
|
49
|
+
catch (error) { if (readFileSync(safe(root, TERMINAL_REF), 'utf8') === bytes) rmSync(safe(root, TERMINAL_REF)); throw error; }
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Version qualification binds executable sources and generated bytes, not Skill prose. */
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { readFileSync, readdirSync, lstatSync } from '
|
|
3
|
+
import { readFileSync, readdirSync, lstatSync } from './validation-phase.mjs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assertSourceFingerprint } from "./backend-platform-provenance.mjs";
|
|
2
2
|
/** Shared, exact backend platform selection. Catalog entries are maintenance-owned. */
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import { readFileSync } from
|
|
4
|
+
import { readFileSync } from './validation-phase.mjs';
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, readFileSync } from
|
|
2
|
+
import { existsSync, readFileSync } from './validation-phase.mjs';
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
5
5
|
import { loadApprovalRecord, validateApprovalRecord } from "./approval-record.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, lstatSync, readFileSync, readdirSync } from
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, readdirSync } from './validation-phase.mjs';
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
const CONTEXT_FILE = "CONTEXT.md";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from
|
|
1
|
+
import { existsSync, readFileSync } from './validation-phase.mjs';
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parseDocument } from "../vendor/yaml.mjs";
|
|
4
4
|
import { resolveContextTermRefs, verifyContextSnapshot } from "./context-contract.mjs";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {readFileSync} from './validation-phase.mjs';
|
|
2
|
+
import {safe,hash} from './strategic-handoff-io.mjs';
|
|
3
|
+
import {parseSliceYaml} from './slice-contract.mjs';
|
|
4
|
+
export function contractSourceMetadata(bytes){
|
|
5
|
+
const text=String(bytes),front=/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
|
|
6
|
+
if(front)return parseSliceYaml(front[1]);
|
|
7
|
+
return /^\s*[{[]/.test(text)||/^[A-Za-z_][\w-]*:\s/m.test(text)&&!/^#/m.test(text)?parseSliceYaml(text):{};
|
|
8
|
+
}
|
|
9
|
+
/** Reads original bytes; returns derivable facts only. It never selects or approves a source. */
|
|
10
|
+
export function readContractSource(selection,{root=process.cwd(),read=ref=>readFileSync(safe(root,ref))}={}){
|
|
11
|
+
const selected=typeof selection==='string'?{ref:selection}:selection;
|
|
12
|
+
if(!selected?.ref)throw new TypeError('来源选择缺少 ref');
|
|
13
|
+
const bytes=read(selected.ref),digest=hash(bytes),data=contractSourceMetadata(bytes);
|
|
14
|
+
const observed=data.requirement_version??data.version??data.contract_version??data.decision_version;
|
|
15
|
+
const conflicts=[];
|
|
16
|
+
const conflict=(field,before,after)=>conflicts.push({field,source_ref:selected.ref,before,after});
|
|
17
|
+
if(selected.digest&&selected.digest!==digest)conflict('digest',selected.digest,digest);
|
|
18
|
+
if(selected.version!==undefined&&observed!==undefined&&String(selected.version)!==String(observed))conflict('version',selected.version,observed);
|
|
19
|
+
if(conflicts.length){const error=new TypeError(conflicts.map(({field,before,after})=>`${field==='version'?'来源版本冲突':'来源冲突'} ${selected.ref}.${field}: supplied=${before}; observed=${after}; 恢复: 核对双方来源并显式选择当前版本`).join('\n'));Object.assign(error,{code:'SLICE_SOURCE_CONFLICT',...conflicts[0],conflicts});throw error;}
|
|
20
|
+
const version=selected.version??observed;
|
|
21
|
+
return {bytes,data,binding:{ref:selected.ref,digest,...(version!==undefined?{version:String(version)}:{}),...(selected.approval_ref?{approval_ref:selected.approval_ref}:{})}};
|
|
22
|
+
}
|