Merge authoritative main into the task branch
Why: include the current integration baseline before verification. Evidence: fetched origin/main=415f9ac58ece9d9b63bd9b3fea39978bb847fcce. Impact: integrate upstream changes without rewriting task history. Refs: task-sync
This commit is contained in:
+16
-2
@@ -1,6 +1,6 @@
|
||||
# feature/rdm-increment-settings-duties 任务卡
|
||||
|
||||
任务主记录:PR #513 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/513(进行中;前一批 #502 已合入 3e5416a8——并行会话曾按 #502 误闭合本卡并归档,本提交恢复:任务授权终点为 P8 组合验收,尚未达成);状态:进行中;owner:weichen
|
||||
任务主记录:PR #513 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/513(进行中;前一批 #502 已合入 3e5416a8——并行会话曾按 #502 误闭合本卡并归档,本提交恢复:任务授权终点为 P8 组合验收,尚未达成);状态:已完成(回填);owner:weichen
|
||||
|
||||
## 基线与授权
|
||||
|
||||
@@ -124,7 +124,7 @@ tag 后 Agent 连续动作:RDM pins 四仓升 v0.4.0 + rdm.account.list query/
|
||||
- **基座发版 tag**(双钥匙)→ RDM 升 pins → rdm.account.list query/Name 透传接线(前端适配层已透传 query,发版后全量生效)。
|
||||
- **任务清理回收**(阶段 10 人工节点)。
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
## 结果与验证
|
||||
|
||||
- 结果:
|
||||
- 验证:<结论摘要 PASS/FAIL/SKIP/NOT_RUN 逐项;逐条证据由 make evidence 写入上方证据表>
|
||||
@@ -723,3 +723,17 @@ tag 后 Agent 连续动作:RDM pins 四仓升 v0.4.0 + rdm.account.list query/
|
||||
"receipt_sha256": "382851fef602c06924eb80e19c3bf6fd793ee23eef6e3ce830e6c781ad2d126c"
|
||||
}
|
||||
```
|
||||
|
||||
## 闭合记录(card-autoclose 回填)
|
||||
|
||||
- 依据:merge 69c5c84d8a2f1a7966ebfb809cbb26d54e8e8abb(PR #529)已包含于 origin/main,分支成果已落盘;本段为合入后回填。
|
||||
- 合并主题:Merge pull request 'Complete scenario-2 name-filter assertion and refresh the harness for v0.4.0' (#529) from feature/rdm-increment-settings-duties into main
|
||||
- 归档:随闭合移入 .task/archive/;任务执行期的过程证据见原任务记录与 PR。
|
||||
|
||||
|
||||
## 验收记录(阶段 7,2026-10-13 回填)
|
||||
|
||||
- 正式验收审阅结论:**通过**。来源:用户于 2026-10-13 会话确认(回复「可以」)。
|
||||
- 佐证:自动化十场景于 v0.4.0 组合实例 25/25 PASS(SELF 证据行见本卡与 Dev #529);
|
||||
浏览器多角色目视复验以用户确认为准,本记录不代行、不补写执行细节。
|
||||
- 至此方案(提案 r1)P1–P8、发版接续、清理回收与验收记录全部闭合。
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,82 @@
|
||||
# IAM I1 最终 Dev 组合:独立元数据与规范 Review
|
||||
|
||||
日期:2026-10-13 UTC。审阅者:`/root/i1_stage8_docs_review`,native Codex 独立审阅智能体;未参与本次实现或任务卡修订。只读取各仓,报告与收据仅写 /tmp。没有 Git mutation、平台账号批准、pins 修改或 RDM 源码 Review。
|
||||
|
||||
结论:**固定组合的文档/规范符合性通过;Critical 0、Important 0、Minor 0(原任务卡 Minor 已独立复审闭合)。** 当前没有发现范围扩大、提供者回退或文档契约失配。此结论不放行尚未完成的 Dev 当前 HEAD CI,亦不声称 I1 后续全模型、部署或清理完成。
|
||||
|
||||
## 固定对象与 diff
|
||||
|
||||
- Dev:`/home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries`。
|
||||
- base:`1e5c6b9c10ba77ed34663fd670138621a17342bd`。
|
||||
- 初审 head:`a8c42d9a59fcd355a9c9d7ee3e0ed5b6742380fd`。
|
||||
- 最终复审 head:`19b02b3cad9ff9ff19f6f7dbd04bfb0b11293f3f`。
|
||||
- 初审对象:`1e5c6b9c10ba77ed34663fd670138621a17342bd..a8c42d9a59fcd355a9c9d7ee3e0ed5b6742380fd`。
|
||||
- 最终结论对象:`1e5c6b9c10ba77ed34663fd670138621a17342bd..19b02b3cad9ff9ff19f6f7dbd04bfb0b11293f3f`;增量复审对象:`a8c42d9a59fcd355a9c9d7ee3e0ed5b6742380fd..19b02b3cad9ff9ff19f6f7dbd04bfb0b11293f3f`。
|
||||
- 初审差异仅 PROJECT 的 4 个新增行(3 个文档链接)、Documents gitlink 与本任务卡/14 个自有记录文件,共 17 个文件;没有业务源码、UI、提供者或治理实现修改。
|
||||
- Documents pin:`a7ff466d9ae91fc67a53d2bf4991f47fc63a2a4c`;与归档 PR99 actual-merge 记录一致,并以本地祖先检查确认包含已独立审阅的 `ba9d76426ded94b0ab760b25201c3a389541b4ee`、`1f3bd54cfba7518bf5d52cbef3d52a03743513bd`。
|
||||
- 提供者/组件与 base 完全相同:Backend `5d5fc9891c0eb8994545ccb8b740535d9b99fb0e`、Core `5773e74086791731059730bd3726dc8c9cd1381f`、Frontend `eb2d6d32f9323094d568bc9d80b4e5ed4f0af25b`、UI `2c4322ef56df64ad72f65907d25b048a74bb5ffc`、Guidelines `2f5b19872be2a4597838a4131ad75d1be089ee5e`。没有以旧本地 pins 覆盖已纳入主干的能力。
|
||||
|
||||
初审读取 a8c42d9 的固定任务卡内容;初审时工作树另有该任务卡未提交修改,独立 receipt 的 status/diff 摘要完整保留。未将活动工作树内容计入固定提交结论。初审时交付者计划追加 RDM 的真实最终结果及当前 Dev 输出,新增 metadata 提交须独立核对差异/源码等价,并对新实际 HEAD 取得 CI。
|
||||
|
||||
## 先重放、后作者材料
|
||||
|
||||
先执行以下最小集,再读取作者任务卡和归档记录:
|
||||
|
||||
```text
|
||||
RECEIPT=/tmp/iam-i1-dev-final-independent-doccheck.json CMD='git diff --check 1e5c6b9c10ba77ed34663fd670138621a17342bd..a8c42d9a59fcd355a9c9d7ee3e0ed5b6742380fd && python3 /tmp/iam-i1-u1-doccheck.py' make evidence
|
||||
```
|
||||
|
||||
完整 SELF 收据:`/tmp/iam-i1-dev-final-independent-doccheck.json`;exit=0;receipt_sha256=`85971f4697137937a1f4a3b96a12694d5615d748e6fcc00f249a4aa7e04691dc`。before/after head 均为上述 Dev head,Documents 子对象另记为 a7ff466。固定父 SHA 与组件 SHA 未混用。
|
||||
|
||||
检查通过 diff 空白、原统一方案摘要 `39b8e9a6e078dc509bd80118c1807ce6ed23e4466e5902b51f4bfa255d55c0a1`、87 个当前本地链接目标、R1–R20 唯一追溯、17 稳定 action/scope、扩展申请四要素、legacy 与最终多职边界、冻结 RDM 规范修订入口及固定初始 08 核心规则。
|
||||
|
||||
补充静态 metadata 检查通过:3 个 PROJECT 新链接目标、Documents 实际 pin 与归档记录及两个被审提交祖先关系、所有提供者和 Guidelines pin 对 base 不变、批准投影来源原文与归档 authenticated API 摘要一致。完整 SELF 收据:`/tmp/iam-i1-dev-final-independent-metadata-check.json`;exit=0;receipt_sha256=`c28d53679d0cad41ee666a747c591e92748d4fb08addf6d8f918c2ddbe69cad1`。其精确单行命令由收据 command 字段保存;它仅进行本地 Git/文件/记录检查,没有重新请求远端。第一次辅助命令因 CMD 含实际换行被 evidence 在执行前拒绝,随后改为单行编码命令才实际运行;该拒绝不计业务结果。
|
||||
|
||||
所有本地检查来源为 SELF,独立身份不将工具来源升级为 GATE 或 REPLAY。
|
||||
|
||||
## 规范、授权和范围
|
||||
|
||||
- PROJECT 第 15–17 行准确限定已批准统一方案的首批 I1 U1–U2,导航到合同和结果记录;新增链接均指向实际固定 Documents 内容,不把列入规划当成全目标已完成。
|
||||
- 任务卡第 10–15、49–50、889–894 行分别保留原实施授权、阶段 6 “批准”、阶段 7 “确认”的原对象,并记后续投影 r1 明确批准。原批准方案摘要保持;投影原文摘要 `3eee5714cce974ce13229b3e184bc3aa763a5e0819fdf7dcf088dafe68b1ed2a` 与 RDM 4ee4eb99 原 blob 一致。
|
||||
- I1 当前 legacy 单绑定、管理员无隐含业务访问、cap/停用/换岗收口、私人资源边界及单目标投影与已审合同一致。多职、资料/目录恢复、矩阵 UI、完整资源职责、epoch、迁移、全产品浏览器/模型、部署和工作区清理没有据此关闭或启动。
|
||||
- Guidelines 和 merge-queue policy 属本次 base 已包含的主干内容。policy/merge-queue.md 页首明确 approved/pending-activation、执行模式未切换;本任务没有把它当作已启用的自动队列或跳过既有同步、当前 HEAD CI、独立 Review 和平台准入条件。
|
||||
- Documents 归档记录有 PR98/99 的各自 head、实际 merge_sha、时间/执行账号及准入观察;父仓实际 pin 匹配 PR99 合入记录。Docs 无 workflow/required context 的 N/A 不替代 Dev 的 CI。
|
||||
|
||||
## Finding:Minor M1(初审发现,已闭合)
|
||||
|
||||
位置:`.task/feature-iam-i1-permission-boundaries.md:12,14,73,75,89,91`(固定 a8c42d9 对象)。当前交付模式仍写“仅 I1 实施与本地自检;阶段 6 出口保留人工评审”;未标快照的旧结果/合入节还写阶段 6 尚未完成、正式阶段 8 NOT_RUN。这与页首阶段 7 已通过、进入 8–9,以及本次用户明确批准 r1 修复并继续交付的范围不完全一致,恢复者可能重复请求旧阶段批准。
|
||||
|
||||
最小修订:将原 delivery mode 明确标为历史授权,并记录当前 r1 修复的连续实施/测试/Review/CI/合入来源与边界;旧“结果与验证”“合入与清理”标题标明原阶段 6 快照,当前以末尾集成记录为准。不修改旧批准对象、数值或收据。交付者已被通知,可随真实 RDM 结果和最终 Dev metadata 一起收口;属于文字/状态整改,不要求权限或组件变化。
|
||||
|
||||
## 历史结果与最终准入
|
||||
|
||||
固定卡第 892 行写 RDM 当前 CI2997 仍运行、未代签合入,是该提交时的候选快照。任务来信提供了后续 RDM PR360 实际合入 `9eb8c616ef5f4431fb2dd83a3f5267bbaa725af6` 及 Actions2997/job4517 成功的结果;这些后续真实记录应由交付者原样保存到最终 metadata,而不能改写历史收据或用旧 PASS 冒充新候选。本人没有重跑 RDM 源码/真实 HTTP,RDM 两路 Review 与 13 组 HTTP 的有效对象继续由其独立记录负责。
|
||||
|
||||
`projection-source-link-verified.json` 明确 verified_access 为 authenticated repository owner API,200 与固定原文摘要相符;卡第 893 行也明确 RDM 为私仓。本人只重核记录结构与本地 blob,没有独立远端 API 重验,不声明该资源未认证公开可见。服务器返回的旧 html_url 不提升为权威访问入口,不重试旧域名;实际合同链接仍为当前 git-home。
|
||||
|
||||
初审时 Dev 最终 make test 正在独立运行并输出到源码外 `/tmp/iam-i1-dev-final-composition-test.json`;初审未读取或代签其最终结果;现已导入的实际结果及元数据复审见下节。无论该本地 SELF 是否成功,交付者仍须核验最终实际 HEAD 的 test、test-pg、svc-realcombo 等全部必需 CI、运行身份/SHA/链接及平台 stale guard;缺检查、运行中或失败不能合入。
|
||||
|
||||
初审 readiness 为文档/组件范围与规范一致、元数据 Minor 待闭合,最终验证与 CI 分别待核验。以下最终复审覆盖元数据整改及已追加的真实结果,不修改初审历史依据。
|
||||
|
||||
|
||||
## 最终元数据增量复审与问题闭合
|
||||
|
||||
最终审阅 head:`19b02b3cad9ff9ff19f6f7dbd04bfb0b11293f3f`。相对 a8c42d9 的 9 个变更文件仅为本任务卡和自有 artifacts;固定业务源码、PROJECT、Documents/提供者/Guidelines pins、policy、治理和 workflow 完全等价。只对这一增量独立重放,不重复未变化的业务测试。
|
||||
|
||||
精确命令:
|
||||
|
||||
```text
|
||||
RECEIPT=/tmp/iam-i1-dev-final-independent-metadata-v2.json CMD='git diff --check a8c42d9a59fcd355a9c9d7ee3e0ed5b6742380fd..19b02b3cad9ff9ff19f6f7dbd04bfb0b11293f3f && git diff --exit-code a8c42d9a59fcd355a9c9d7ee3e0ed5b6742380fd..19b02b3cad9ff9ff19f6f7dbd04bfb0b11293f3f -- . ":(exclude).task"' make evidence
|
||||
```
|
||||
|
||||
完整 SELF 收据:`/tmp/iam-i1-dev-final-independent-metadata-v2.json`;exit=0;receipt_sha256=`0c92b8806e6bc9b4d446d42ff459339fdfe88eca35fe94df6a626883d1c90127`。before/after head 均为 19b02b3,检查期间工作树 clean。
|
||||
|
||||
M1 闭合:实施者将当前任务卡第 12 行标为原交付模式快照,在第 13、15 行明确当前投影 r1 修复的连续实施/验证/Review/当前 HEAD CI/合入及停止边界。第 74、90 行的旧结果/合入/清理节已标为原阶段 6 快照,并指向末尾当前记录。旧对象和原始结果没有被改写。本人检查固定新对象,确认这就是初审建议的语义,未参与修订。
|
||||
|
||||
第 936–940 行及新增 artifacts 保存了 RDM PR360 fast-forward-only 实际合入结果与精确 head/merge_sha `9eb8c616ef5f4431fb2dd83a3f5267bbaa725af6`;归档 GATE 记录中的 run2997 与 job4517 build-and-test 均 completed/success,head 相符。记录保留平台执行身份/workflow/来源,同时本地 evidence 仍为 SELF,不把包装摘要提升为 GATE。本人重核这些归档字段的一致性,没有新远端 API 运行或 RDM 源码 Review。旧“运行中”状态明确为当时历史,不与当前结果混用。
|
||||
|
||||
新增 Dev make test 原始收据为 `.task/artifacts/iam-i1-stage8/dev-final-composition-test.json`,command=make test、exit=0、source=SELF、receipt_sha256=`a08bc3d477d4d9bd33a37412685cc9f5372a39acaf58e02f1796c51903fa900e`,before/after head 都是 a8c42d9。执行中任务卡存在元数据修改,原始 dirty 状态及摘要保留;当前记录也明示此点,没有冒称整棵工作树完全固定。收据与最终 metadata 的业务/组件等价关系由本次 diff 重放辅助核验;它仍不替代 19b02b3 或后续最终实际 HEAD 的全部必需 CI。
|
||||
|
||||
RDM 闭合 PR362 为独立 metadata 对象,当前卡写仍待其 Review/CI;没有提前宣称已合入。Dev 合入与 RDM 主 PR/闭合 PR 分开记录。部署、生产迁移、U3–U9 和工作区清理仍没有完成或新增授权声明。
|
||||
|
||||
**最终结论:上述固定 Dev diff 的元数据、Documents 组合及规范符合性 Review 通过,Critical 0、Important 0、Minor 0。** 原 Minor 的发现与闭合保留。当前 Review scope 为本次 metadata/文档/组件组合,不承担 RDM 源码审查或替代当前 HEAD CI。交付者仍须核验最新 base/head、最终实际 HEAD 的 test/test-pg/svc-realcombo、执行身份与运行链接、平台 stale guard 及 PR 准入,之后才能声明 Dev 已真实合入。继续追加 metadata 会形成新 HEAD,须按影响核对等价并取得该 HEAD 的必需 CI。
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,89 @@
|
||||
# IAM I1 阶段 8 独立文档 Review
|
||||
|
||||
日期:2026-10-13 UTC。审阅者:`/root/i1_stage8_docs_review`,native Codex 独立审阅智能体。未参与本次实现或修订;未修改任何仓库;整改由实施者完成,本人复审。此次为指定 Documents diff 的独立文档审阅,覆盖需求符合性、一致性、范围、公开契约、证据与维护性;不是完整业务源码 Review、受信 CI 或平台非作者账号批准。
|
||||
|
||||
结论:**Documents 文档 Review 通过;当前 Critical 0、Important 0、Minor 0,全部已记录问题闭合。** 此结论只适用于下列固定 diff,不声明其他仓当前源码、PR、CI、合入或部署已完成。
|
||||
|
||||
## 固定对象
|
||||
|
||||
- Documents 工作区:`/home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries/modules/documents`。
|
||||
- base:`129cc3579d9c9e3edfb46c99270d65f3e1dad0a0`。
|
||||
- 初审 head:`b4a2573742d1377d432704f0114832d0af5a3c7b`。
|
||||
- 首轮整改复审 head:`30bceb748fdb01f56ef622c6bc490b2115f62725`。
|
||||
- 最终复审/本结论 head:`ba9d76426ded94b0ab760b25201c3a389541b4ee`。
|
||||
- 正式审阅 diff:`129cc3579d9c9e3edfb46c99270d65f3e1dad0a0...ba9d76426ded94b0ab760b25201c3a389541b4ee`,5 个文件:批准方案、I1 合同、阶段 6 实施记录、阶段 7 验收记录、INDEX。
|
||||
- 整改 diff:`b4a2573742d1377d432704f0114832d0af5a3c7b...30bceb748fdb01f56ef622c6bc490b2115f62725`,仅修改合同页首授权状态与 INDEX 的 IAM 状态文字。没有改动权限语义或批准方案。
|
||||
- 最后一项整改 diff:`30bceb748fdb01f56ef622c6bc490b2115f62725...ba9d76426ded94b0ab760b25201c3a389541b4ee`,仅合同第 108–109 行两句,明确 Backend 原快照与后续组合须重新固定核验。
|
||||
- 批准原文文件 SHA256:`39b8e9a6e078dc509bd80118c1807ce6ed23e4466e5902b51f4bfa255d55c0a1`;独立重放三次均核验保持此摘要。原方案的草案状态是保存的历史批准对象,不要求改写它而破坏摘要。
|
||||
|
||||
## 先重放、后阅读作者材料
|
||||
|
||||
在读取作者验收/自检材料及任务卡收据之前,已独立运行最小重放集。命令和完整脱敏输出由 Dev 的现代 evidence.py 保存,来源均为 **SELF**;独立审阅身份不把工具来源提升为 GATE 或 REPLAY。
|
||||
|
||||
初审命令:
|
||||
|
||||
```text
|
||||
RECEIPT=/tmp/iam-i1-stage8-independent-docs-review.json CMD='git -C /home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries/modules/documents diff --check 129cc3579d9c9e3edfb46c99270d65f3e1dad0a0...b4a2573742d1377d432704f0114832d0af5a3c7b && python3 /tmp/iam-i1-u1-doccheck.py' python3 /home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries/scripts/evidence.py
|
||||
```
|
||||
|
||||
初审收据:`/tmp/iam-i1-stage8-independent-docs-review.json`;exit=0;receipt_sha256=`3604658204d53c94e905b7f5281c7f1ebb0507c6b5ffc6fe12923f6ff54ba8e6`。
|
||||
|
||||
复审命令:
|
||||
|
||||
```text
|
||||
RECEIPT=/tmp/iam-i1-stage8-independent-docs-review-v2.json CMD='git -C /home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries/modules/documents diff --check 129cc3579d9c9e3edfb46c99270d65f3e1dad0a0...30bceb748fdb01f56ef622c6bc490b2115f62725 && python3 /tmp/iam-i1-u1-doccheck.py' python3 /home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries/scripts/evidence.py
|
||||
```
|
||||
|
||||
复审收据:`/tmp/iam-i1-stage8-independent-docs-review-v2.json`;exit=0;receipt_sha256=`1a77cf93f374019a86a81a62bde91b0c21c362a555f27e577de476a8e113e290`。
|
||||
|
||||
最终复审命令:
|
||||
|
||||
```text
|
||||
RECEIPT=/tmp/iam-i1-stage8-independent-docs-review-v3.json CMD='git -C /home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries/modules/documents diff --check 129cc3579d9c9e3edfb46c99270d65f3e1dad0a0...ba9d76426ded94b0ab760b25201c3a389541b4ee && python3 /tmp/iam-i1-u1-doccheck.py' python3 /home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries/scripts/evidence.py
|
||||
```
|
||||
|
||||
最终复审收据:`/tmp/iam-i1-stage8-independent-docs-review-v3.json`;exit=0;receipt_sha256=`6a39ff76afb42b5c9a4f91b38616db713ff6195f95a9f53f56c4801a7c36681a`。再次通过 diff 空白、批准原文摘要、87 个链接目标、R1–R20 与 17 个稳定动作及其余 doccheck 判据。
|
||||
|
||||
evidence 的执行 cwd 是 Dev;三次收据记录 Dev HEAD `4292fe3c6d9e7b2e874b137f72b8433756ecde1f` 及组合状态。被审 Documents 的完整 SHA 则明确固定在命令 diff 参数中,并在读取前后以 git rev-parse 和 clean status 核对。不能将 Dev HEAD 当成 Documents HEAD。
|
||||
|
||||
最小重放覆盖差异空白、批准原文摘要、当前本地链接目标、R1–R20 各一次追溯、17 个稳定 action/scope、公开申请四要素、I1 legacy 与后续多职边界、RDM 三份规范修订入口及固定 RDM R0 的 08 核心 R1–R5 保持。脚本读取 RDM 的冻结规范文档,不修改该仓;它不验证 Markdown anchor、平台链接身份或业务行为。
|
||||
|
||||
## 需求与范围审阅
|
||||
|
||||
- 批准方案 R2/R10、KTD4 与 U4/U5 明确一职多人、一人多职、项目明确选用 binding,并要求完整路径判定,不拼接不同角色的 grant 与资源范围。I1 合同第 15、21、24–27、77–92、174 行保持这一设计,将当前单绑定兼容与后续最终模型分开,没有以 legacy 实现取代用户的多职裁定。
|
||||
- 原方案 R7、KTD3/KTD4 与 I1 合同第 14、28–29、60–62、96–105、135 行一致:系统管理员管理系统主数据,管理员身份和 Host.IsAdmin 不隐含业务项目、正文、原件、草稿或审计访问。独立 audit.read 与当前完整 doc.read 的聚合兼容限制有明示。
|
||||
- 17 个 I1 稳定动作保持系统/项目 scope,旧入口有映射,未知 ID 不隐式变成别名。系统动作不可由 project.settings 或项目覆盖获得;创建者仍先验平台与角色 cap。
|
||||
- IAM-P/D/R/C 及补充 IAM-H/K 均标为公开申请或集成缺口,规定提供者/消费者版本、失败拒绝与兼容方式,没有把保存合同当成导出实现。完整资料/目录/恢复、多绑定、项目选用、资源职责、UI、epoch 与迁移继续归属 U3–U9。
|
||||
- RDM adoption 逐项维护 R1–R20 到既有 UR/SR 和后续单元的追溯;原 ID 与 08 核心规则保留。本 Review 不把映射完整等同于 R1–R20 行为关闭。
|
||||
|
||||
## 问题及闭合
|
||||
|
||||
### Important D1 — 授权终点的当前表述与阶段 7 已通过冲突(已闭合)
|
||||
|
||||
初审位置:`docs/proposals/2026-10-13-iam-i1-permission-contract.md:6`,在原本地实施终点的基础上裸写“未授权推送、PR、合入”,但 `2026-10-13-iam-i1-acceptance.md:3–5` 已记录用户阶段 7 确认并按 policy 进入 8–9。继续原表述会使后续执行者从两个当前入口读到相冲突的交付边界。
|
||||
|
||||
实施者在 `30bceb748fdb01f56ef622c6bc490b2115f62725` 将原终点明确为历史,并关联阶段 6/7 的通过和当前 Review/PR/CI/合入责任,同时保留迁移、部署、清理和后续增量未授权。本人检查整改 diff 与复审收据,确认闭合;无需改写批准方案原文。
|
||||
|
||||
### Minor D2 — INDEX 仍显示阶段 7 待验收(已闭合)
|
||||
|
||||
初审位置:`docs/proposals/INDEX.md:12–13`。该导航把已通过的阶段 7 显示为待用户验收,容易导致重复请求批准和错误恢复阶段。
|
||||
|
||||
实施者在同一复审提交更新为实施评审及阶段 7 已通过、当前进入 8–9;本人复审确认与 acceptance 第 3–5 行一致。闭合。
|
||||
|
||||
### Minor D3 — 公开能力表应注明其历史固定提供者(已闭合)
|
||||
|
||||
位置:`docs/proposals/2026-10-13-iam-i1-permission-contract.md:108–115,121–123`。2.11.0、owner+valid 等现有能力叙述对应 I1 原固定 Backend `0f192a481388184df39f81c28b0587a7e16ec3ca`;它与阶段 7 固定组合相符。当前同步中的 Dev 提供者已前进到 2.12,目录已有额外 Name/Search,因此“当前”一词容易被未来消费者读成所有组合的即时事实。
|
||||
|
||||
建议后续组合状态收口时在此表标明“原 I1 合同的 Backend 0f192a48 快照”,并把新的固定组合能力放在当前任务记录,继续分别核验提供者与消费者。该差异不改变 I1 规则、不要求扩大 IAM-D 或启动 U3,也不构成当前文档 Review 阻断。本人未对正在变化的 RDM 工作树作固定业务结论。
|
||||
|
||||
实施者在 `ba9d76426ded94b0ab760b25201c3a389541b4ee` 按此建议修订合同第 108–109 行,明确原 Backend 完整 SHA、2.11 为原快照且不代表后续主干能力,同步组合仍须实际固定核验。本人独立检查相对首轮复审对象的 diff,确认仅此两句;最终完整最小重放通过,原文摘要不变,D3 闭合。未参与修订。
|
||||
|
||||
## 证据和诚实边界
|
||||
|
||||
重放之后核对了阶段 7 引用的 4 个完整作者/前期审阅收据:Dev `ea7b7758…`、RDM `6e684a90…`、独立 focused `6bbb0f74…`、独立 HTTP `c7edde36…`,其命令、exit=0、before/after head 与报告列出的对应历史对象一致,均保留 SELF。报告承认编译/夹具/确认流失败和零用例不能计为业务通过。
|
||||
|
||||
阶段 6 页第 73 行把旧测试和缺项表明示为当时快照,阶段 7 页明示其固定对象、用户决定、全产品浏览器与真实模型对照缺项,以及 PostgreSQL、多实例、epoch、恢复、多职和完整三权的后续归属;没有声称部署、生产数据验收或完整 R1–R20 已关闭。
|
||||
|
||||
此审阅只重新执行文档最小检查;没有重新运行业务 make test、HTTP、浏览器或真实模型,因此不以作者历史收据替代这些路径的当前重放。另一个指定源码审阅及最终组合验证负责其对应结论。平台所需非作者账号批准、当前 HEAD 的可信 CI、最新 base/head、冲突和 PR 准入仍须由交付者独立核验;本 Review 不能替代这些条件。
|
||||
|
||||
就上述固定 Documents diff 而言,全部 Important/Minor 已闭合,权限设计/需求追溯和证据边界一致,可进入下一交付门禁。base/head 或规范语义变化时按影响复审。
|
||||
@@ -0,0 +1 @@
|
||||
{"pr": 98, "url": "https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Documents/pulls/98", "head": "ba9d76426ded94b0ab760b25201c3a389541b4ee", "merge_sha": "3cf263962bd106529ee0531c8415cb16eecedfe3", "base": "main", "merged_by": "weichen", "merged_at": "2026-10-13T15:39:54Z"}
|
||||
@@ -0,0 +1 @@
|
||||
{"pr": 98, "state": "open", "merged": false, "base": "129cc3579d9c9e3edfb46c99270d65f3e1dad0a0", "head": "ba9d76426ded94b0ab760b25201c3a389541b4ee", "mergeable": true, "draft": false, "status": {"http_status": 200, "data": {"state": "pending", "sha": "ba9d76426ded94b0ab760b25201c3a389541b4ee", "total_count": 0, "statuses": null, "repository": {"id": 19, "owner": {"id": 1, "login": "weichen", "login_name": "", "source_id": 0, "full_name": "", "email": "weichen@home.local", "avatar_url": "https://git-home.u1680832.nyat.app:30319/avatars/51e6cd4a83883a2910632d26ebc6d665", "html_url": "https://git-home.u1680832.nyat.app:30319/weichen", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2026-09-06T14:39:07Z", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 0, "username": "weichen"}, "name": "Agent-System-Documents", "full_name": "weichen/Agent-System-Documents", "description": "系统文档与过程记录仓:架构决策、方案与验证记录、插件开发指南;不含产品代码。", "empty": false, "private": false, "fork": false, "template": false, "mirror": false, "size": 1447, "language": "", "languages_url": "https://git-panel.u1680832.nyat.app:30319/api/v1/repos/weichen/Agent-System-Documents/languages", "html_url": "https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Documents", "url": "https://git-panel.u1680832.nyat.app:30319/api/v1/repos/weichen/Agent-System-Documents", "link": "", "ssh_url": "ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Documents.git", "clone_url": "https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Documents.git", "original_url": "", "website": "", "stars_count": 0, "forks_count": 0, "watchers_count": 1, "branch_count": 76, "open_issues_count": 0, "open_pr_counter": 3, "release_counter": 0, "default_branch": "main", "archived": false, "created_at": "2026-09-28T07:07:06Z", "updated_at": "2026-10-13T15:39:22Z", "archived_at": "1970-01-01T00:00:00Z", "permissions": {"admin": true, "push": true, "pull": true}, "has_code": true, "has_issues": true, "internal_tracker": {"enable_time_tracker": true, "allow_only_contributors_to_track_time": true, "enable_issue_dependencies": true}, "has_wiki": true, "has_pull_requests": true, "has_projects": true, "projects_mode": "all", "has_releases": true, "has_packages": true, "has_actions": true, "ignore_whitespace_conflicts": false, "allow_merge_commits": true, "allow_rebase": true, "allow_rebase_explicit": true, "allow_squash_merge": true, "allow_fast_forward_only_merge": true, "allow_merge_update": true, "allow_rebase_update": true, "allow_manual_merge": false, "autodetect_manual_merge": false, "default_delete_branch_after_merge": false, "default_merge_style": "merge", "default_update_style": "merge", "default_allow_maintainer_edit": true, "avatar_url": "", "internal": false, "mirror_interval": "", "object_format_name": "sha1", "mirror_updated": "0001-01-01T00:00:00Z", "mirror_last_sync_at": "0001-01-01T00:00:00Z", "topics": [], "licenses": []}, "commit_url": "https://git-panel.u1680832.nyat.app:30319/api/v1/repos/weichen/Agent-System-Documents/commits/ba9d76426ded94b0ab760b25201c3a389541b4ee", "url": "https://git-panel.u1680832.nyat.app:30319/api/v1/repos/weichen/Agent-System-Documents/commits/ba9d76426ded94b0ab760b25201c3a389541b4ee/status"}}, "protection": {"http_status": 200, "data": {"branch_name": "main", "rule_name": "main", "priority": 1, "enable_push": false, "enable_push_whitelist": false, "push_whitelist_usernames": [], "push_whitelist_teams": [], "push_whitelist_deploy_keys": false, "enable_force_push": false, "enable_force_push_allowlist": false, "force_push_allowlist_usernames": [], "force_push_allowlist_teams": [], "force_push_allowlist_deploy_keys": false, "enable_merge_whitelist": false, "merge_whitelist_usernames": [], "merge_whitelist_teams": [], "enable_bypass_allowlist": false, "bypass_allowlist_usernames": [], "bypass_allowlist_teams": [], "enable_status_check": false, "status_check_contexts": null, "required_approvals": 0, "enable_approvals_whitelist": false, "approvals_whitelist_username": [], "approvals_whitelist_teams": [], "block_on_rejected_reviews": false, "block_on_official_review_requests": false, "block_on_outdated_branch": true, "dismiss_stale_approvals": false, "ignore_stale_approvals": false, "require_signed_commits": false, "protected_file_patterns": "", "unprotected_file_patterns": "", "block_admin_merge_override": false, "created_at": "2026-09-30T03:46:04Z", "updated_at": "2026-09-30T03:46:04Z"}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"http_status": 200, "data": null}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,65 @@
|
||||
# IAM I1 宿主改名投影合同补充:独立文档 Review
|
||||
|
||||
日期:2026-10-13 UTC。审阅者:`/root/i1_stage8_docs_review`,native Codex 独立审阅智能体。未参与 §8 编制或 RDM 实现,未修改仓库,没有 Git mutation、平台批准或 pins 操作。此任务只审 Documents 的指定补充 diff。
|
||||
|
||||
**结论:文档语义 Review 通过,Critical 0、Important 0、Minor 0。** §8 与批准的 r1 修订对象一致,没有把保存合同等同于实现、测试或运行通过。文档合入仍等待来源链接可访问性验证,以及交付者对最新 base/head、当前 HEAD CI 和平台准入的核验。
|
||||
|
||||
## 固定对象
|
||||
|
||||
- Documents 工作区:`/home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries/modules/documents`。
|
||||
- branch:`feature/iam-i1-projection-contract`。
|
||||
- base:`3cf263962bd106529ee0531c8415cb16eecedfe3`。
|
||||
- head:`1f3bd54cfba7518bf5d52cbef3d52a03743513bd`。
|
||||
- 审阅 diff:`3cf263962bd106529ee0531c8415cb16eecedfe3...1f3bd54cfba7518bf5d52cbef3d52a03743513bd`;仅 `docs/proposals/2026-10-13-iam-i1-permission-contract.md` 第 183–193 行新增 §8,其余文本与 pins 不变。
|
||||
- 批准来源对象:RDM `4ee4eb99a95460d6660f05a67181357b67e2e956:docs/proposals/2026-10-13-iam-i1-host-projection-amendment.md`。
|
||||
- 来源原文 SHA256:`3eee5714cce974ce13229b3e184bc3aa763a5e0819fdf7dcf088dafe68b1ed2a`,以本地 Git 该完整提交的 blob 独立计算,与 §8 完全一致。
|
||||
- 原 IAM 统一方案文件摘要仍为 `39b8e9a6e078dc509bd80118c1807ce6ed23e4466e5902b51f4bfa255d55c0a1`。
|
||||
|
||||
批准来源提交页首仍为“待用户审阅”,属于当时完整原文快照;当前 RDM 主记录页首已另记本会话的明确批准、原文摘要和“实施与验证正在进行”。§8 使用完整原批准对象及摘要,没有由旧页首错误推出修订尚未批准,也没有静默改写该批准快照。用户批准依据来自本任务授权及当前批准记录;本 Review 不代替用户或平台账号签名。
|
||||
|
||||
## 独立最小重放(先于作者材料)
|
||||
|
||||
先以固定 Documents 完整 SHA 执行 diff 空白检查与 doccheck,再读取批准提案正文及作者材料。现代 evidence.py 的执行 cwd 在 Dev,完整脱敏命令/输出与对象信息位于下列收据;来源均为 **SELF**,不会提升成 GATE 或 REPLAY。
|
||||
|
||||
```text
|
||||
RECEIPT=/tmp/iam-i1-projection-docs-review.json CMD='git -C /home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries/modules/documents diff --check 3cf263962bd106529ee0531c8415cb16eecedfe3...1f3bd54cfba7518bf5d52cbef3d52a03743513bd && python3 /tmp/iam-i1-u1-doccheck.py' python3 /home/weichen/projects/Agent-System-Dev-wt-feature-iam-i1-permission-boundaries/scripts/evidence.py
|
||||
```
|
||||
|
||||
检查收据:`/tmp/iam-i1-projection-docs-review.json`,exit=0,receipt_sha256=`840e780179f411a0061f55d87cd85d930111bda47576e3dc37eea9a6fc7c2c7c`。
|
||||
|
||||
收据父 Dev before/after 均为 `eca92e99207ca054f96e502f5f5969b02c26da51`;submodules 中 Documents before/after 均明确为 `1f3bd54cfba7518bf5d52cbef3d52a03743513bd`。父 SHA 不是 Documents SHA,审阅 diff 对象已另行固定。Documents 检查前后 HEAD 相同且工作树 clean。
|
||||
|
||||
最小集覆盖 diff 空白、原统一方案摘要、87 个当前本地链接目标、R1–R20 一次追溯、17 个稳定 action/scope、扩展申请四要素、legacy 与多职边界、冻结 RDM 三份规范入口及固定初始 RDM 08 核心规则。全部通过;脚本不验证新增 HTTPS 链接的远端可访问性,也不执行业务路径。
|
||||
|
||||
另以本地 Git 完整来源提交计算批准修订原文摘要,并通过 evidence 保存。实际命令:
|
||||
|
||||
```text
|
||||
python3 -c 'import hashlib,subprocess; p="docs/proposals/2026-10-13-iam-i1-host-projection-amendment.md"; d=hashlib.sha256(subprocess.check_output(["git","-C","/home/weichen/projects/Agent-System-RDM-wt-feature-iam-i1-permission-boundaries","show","4ee4eb99a95460d6660f05a67181357b67e2e956:"+p])).hexdigest(); print("Approved projection snapshot SHA256:",d); assert d=="3eee5714cce974ce13229b3e184bc3aa763a5e0819fdf7dcf088dafe68b1ed2a"'
|
||||
```
|
||||
|
||||
摘要收据:`/tmp/iam-i1-projection-approved-snapshot-review.json`,exit=0,receipt_sha256=`bbf6c21fefaa5018942192570e5692bf3b8afbe3abbda1f3f2bef6e93d2815ab`。原文 blob 与期望摘要一致;此记录只证明本地原文身份,不证明远端已经推送。
|
||||
|
||||
## 需求与语义核对
|
||||
|
||||
| 合同位置 | 批准来源位置 | 结论 |
|
||||
|---|---|---|
|
||||
| §8 第 185 行,唯一来源及批准摘要 | r1 第 1–4、75–78 行与当前批准状态记录 | 原文来源固定,当前批准与历史快照分开;正文维护在 RDM 一处 |
|
||||
| 第 187 行,实际 actor 授权与单目标物理投影 | r1 第 23–33、35–37 行 | 实际认证 actor 保持请求身份,当前 project.settings/cap/关系/状态为权威;创建者 scoped Host 仅执行已核实 hostID 的改名,不重新授权或向其他动作传递 |
|
||||
| 第 187 行,无 doc.read 的合法管理与 Native work 边界 | r1 第 35–37、64、70 行 | 管理设置资格可独立授予;投影元数据查找不扩大正文、原件、私聊、私稿或 Native work 的访问 |
|
||||
| 第 189 行,同主体持久意图与受限恢复 | r1 第 28、33、39–44、69 行 | 同事务持久意图固定 actor、域项目、host、创建者、目标与版本;恢复重验当前实际 actor,只恢复已证实同目标;未知 failed/create 和归属变化拒绝,非 ready 的读/work不开放 |
|
||||
| 第 191 行,两入口 DTO、脱敏与审计身份 | r1 第 30、32、56、64、70 行 | 原请求 Host 负责审计实际操作者;命令与 HTTP 保持各自 DTO,禁止 creator raw settings 或投影句柄外泄;插件来源 Actor 与业务 Owner/evidence.actor 明确区分 |
|
||||
| 第 193 行,公开边界与验证出口 | r1 第 23、44、51、56–71 行 | 不新增 Backend 公开符号,不放宽 owner-only 规则,不改 pins;最终验收仍要求合法、越权、撤权、归属、恢复、两入口及真实 HTTP,用文档保存不能关闭 I1 |
|
||||
|
||||
第 191 行的审计字段解释还核对了固定 Backend `5d5fc9891c0eb8994545ccb8b740535d9b99fb0e` 的 `internal/app/hostcaps.go:273–279`:Audit 写入 Owner=h.owner、Actor=plugin:pluginID,并保留 evidence。该源码证据解释字段角色;它不是当前 RDM 接线已经正确或审计运行通过的声明。
|
||||
|
||||
§8 为摘要采纳,并明确引用完整批准正文;当前资格包括具体成员关系、角色有效态、动作定义、平台/角色 cap、覆盖与生命周期,最终写入前重验等完整条件仍以该批准正文和原合同共同约束。不要求在摘要重复全部实现步骤,也没有缩减这些条件。
|
||||
|
||||
## Findings 和合入 readiness
|
||||
|
||||
Critical:无。Important:无。Minor:无。无需改写原批准方案或扩大公开接口范围。
|
||||
|
||||
**文档 Review readiness:PASS(上述固定 diff;本地检查来源 SELF)。**
|
||||
|
||||
**跨仓来源 URL 可访问性:NOT_VERIFIED。** 第 185 行的固定 RDM 来源提交目前没有本轮远端访问验证收据。本地 blob 核验不替代 HTTPS/平台可访问性。交付者应在包含该提交的 RDM 最终分支正式推送后验证实际 URL,再决定 Documents 合入;不能仅因链接格式合法计为链接通过。
|
||||
|
||||
**RDM 实现、当前产品运行验收、HTTP、业务测试、受信 CI、PR 与合入:本 Review 未重放或核验。** RDM 正在实施;此文档 Review 不声称修复或产品行为已经完成。最终源码 Review、组合验证、平台账号批准和 CI 仍各自承担原门禁。后续 base/head 或 §8 语义变化须按影响复审。
|
||||
@@ -0,0 +1 @@
|
||||
{"pr": 99, "url": "https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Documents/pulls/99", "head": "1f3bd54cfba7518bf5d52cbef3d52a03743513bd", "merge_sha": "a7ff466d9ae91fc67a53d2bf4991f47fc63a2a4c", "merged_by": "weichen", "merged_at": "2026-10-13T16:24:57Z"}
|
||||
@@ -0,0 +1 @@
|
||||
{"pr": 99, "base": "3cf263962bd106529ee0531c8415cb16eecedfe3", "head": "1f3bd54cfba7518bf5d52cbef3d52a03743513bd", "mergeable": true, "required_approvals": 0, "stale_guard": true, "ci": "N/A: no workflow or required context", "source_verified": {"api_path": "/contents/docs/proposals/2026-10-13-iam-i1-host-projection-amendment.md?ref=4ee4eb99a95460d6660f05a67181357b67e2e956", "http_status": 200, "html_url": "https://git-panel.u1680832.nyat.app:30319/weichen/Agent-System-RDM/src/commit/4ee4eb99a95460d6660f05a67181357b67e2e956/docs/proposals/2026-10-13-iam-i1-host-projection-amendment.md", "sha256": "3eee5714cce974ce13229b3e184bc3aa763a5e0819fdf7dcf088dafe68b1ed2a", "verified_access": "authenticated repository owner API"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"api_path": "/contents/docs/proposals/2026-10-13-iam-i1-host-projection-amendment.md?ref=4ee4eb99a95460d6660f05a67181357b67e2e956", "http_status": 200, "html_url": "https://git-panel.u1680832.nyat.app:30319/weichen/Agent-System-RDM/src/commit/4ee4eb99a95460d6660f05a67181357b67e2e956/docs/proposals/2026-10-13-iam-i1-host-projection-amendment.md", "sha256": "3eee5714cce974ce13229b3e184bc3aa763a5e0819fdf7dcf088dafe68b1ed2a", "verified_access": "authenticated repository owner API"}
|
||||
@@ -0,0 +1 @@
|
||||
{"pr": 360, "url": "https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-RDM/pulls/360", "head": "9eb8c616ef5f4431fb2dd83a3f5267bbaa725af6", "merge_sha": "9eb8c616ef5f4431fb2dd83a3f5267bbaa725af6", "method": "fast-forward-only", "merged_by": "weichen", "merged_at": "2026-10-13T16:26:51Z"}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"http_status": 200, "data": null}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -25,8 +25,9 @@
|
||||
完整脱敏收据(不要求已有任务卡);按输出完成必读。`SHARED_WORKSPACE_BUSY / SYNC_REQUIRED /
|
||||
REMOTE_UNVERIFIED` 均不得进入写入流程;失败后排除原因再启动,不把缓存远端当最新事实。
|
||||
- 新任务运行 `make task-start NAME=feature/<slug> PRECHECK_RECEIPT=/tmp/<本次唯一名称>.json`,
|
||||
建立独立 worktree 与任务卡并导入首次收据。恢复已有任务在原任务工作区导入收据:
|
||||
`IMPORT_RECEIPT=/tmp/<本次唯一名称>.json make evidence`。任务中途不重复启动检查。
|
||||
建立独立 worktree 与任务卡并导入首次收据。恢复已有登记任务在原 worktree 用
|
||||
`make task-resume PRECHECK_RECEIPT=/tmp/<本次唯一名称>.json` 验证并原样导入收据。
|
||||
默认 delivery 要求严格 READY;局部恢复接口、启用条件与旧收据处理见 [开发门禁](policy/governance.md#启动与恢复)。任务中途不重复启动检查。
|
||||
- **分支层级**:任务分支一律从权威 `origin/main` 切(`task-start` 已强制);同批互不依赖的
|
||||
子任务用同一 R0 的兄弟分支。确有顺序依赖需从父 feature 分支切子分支时(collaboration.md
|
||||
允许的堆叠例外):启动命令增加 `PARENT=feature/<父slug> PARENT_PR=<父PR链接>`,
|
||||
@@ -35,6 +36,9 @@
|
||||
base 改回 main,方可声明可合入(分支保护要求 head 含 origin/main,未同步必被拦)。
|
||||
- **验收、请求审核、合入前**各运行一次 `make task-sync`;完成声明的 precheck、task-sync、
|
||||
测试三段输出由 `make evidence` 写入任务卡(见「证据与验证门禁」),不手抄。
|
||||
- **多个分支交付 / CI 调度**:先读 [合入调度](policy/merge-queue.md),核对采纳状态、协调者、
|
||||
记录及当前窗口。新模式尚未启用时保留既有同步节点;启用后最终同步至合入核验共用窗口,
|
||||
同基线 `task-sync` 只核验,固定后检查用源码外 RECEIPT 保存(见 [证据流程](policy/governance.md#候选固定前后))。
|
||||
- 合入后由工作区 owner 确认所有写者退出,在**已登记任务 worktree 根目录**运行
|
||||
`TASK_WRITERS_STOPPED=1 make task-finish`。父子仓改动、未跟踪/ignored 唯一文件先保存或显式处置;删除被拒时
|
||||
保留现场并按原因处理,禁止强删兜底。放弃未合入工作用
|
||||
|
||||
@@ -13,13 +13,15 @@ PLUGINS := plugins
|
||||
COMBO := core=$(shell git -C $(CORE) rev-parse --short HEAD 2>/dev/null)+backend=$(shell git -C $(BACKEND) rev-parse --short HEAD 2>/dev/null)+frontend=$(shell git -C $(FRONTEND) rev-parse --short HEAD 2>/dev/null)+ui=$(shell git -C $(UI) rev-parse --short HEAD 2>/dev/null)+agentcmd=$(shell git -C deps/agent-command rev-parse --short HEAD 2>/dev/null)+documents=$(shell git -C modules/documents rev-parse --short HEAD 2>/dev/null)+native=$(shell git -C deps/agent-native rev-parse --short HEAD 2>/dev/null)+squad=$(shell git -C deps/agent-squad rev-parse --short HEAD 2>/dev/null)+intent=$(shell git -C deps/agent-intent rev-parse --short HEAD 2>/dev/null)
|
||||
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null)+$(COMBO)
|
||||
|
||||
.PHONY: help sync status build frontend svc svc-list svc-info svc-commits svc-history svc-update svc-recover svc-watchdog test-backend test-backend-pg test-multi-instance test-lifecycle test-frontend test-ui test-core test-agentcmd test-pins test-libs dev pin clean setup precheck task-start task-sync task-close task-finish facts-verify evidence version
|
||||
.PHONY: help sync status build frontend svc svc-list svc-info svc-commits svc-history svc-update svc-recover svc-watchdog test-backend test-backend-pg test-multi-instance test-lifecycle test-frontend test-ui test-core test-agentcmd test-pins test-libs dev pin clean setup precheck task-start task-resume task-sync task-close task-finish facts-verify evidence version
|
||||
|
||||
version:
|
||||
@echo "agent-system $(VERSION)"
|
||||
|
||||
TASK := scripts/task.sh
|
||||
export PRECHECK_RECEIPT RECEIPT IMPORT_RECEIPT TASK_WRITERS_STOPPED
|
||||
TASK_WORK_MODE ?= delivery
|
||||
export TASK_WORK_MODE
|
||||
TASK_PARENT_BRANCH ?= $(PARENT)
|
||||
TASK_PARENT_PR ?= $(PARENT_PR)
|
||||
export TASK_PARENT_BRANCH TASK_PARENT_PR
|
||||
@@ -36,6 +38,9 @@ task-start:
|
||||
@if [ -z "$(NAME)" ]; then echo "usage: make task-start NAME=feature/<slug>"; exit 2; fi
|
||||
@bash $(TASK) start "$(NAME)"
|
||||
|
||||
task-resume:
|
||||
@bash $(TASK) resume
|
||||
|
||||
task-sync:
|
||||
@bash $(TASK) sync
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
目录中的既有文档不因被收录而自动成为已批准规划。记录、任务关联及收尾建议按
|
||||
[交付规范的长期方向与任务衔接](policy/delivery.md#长期方向与任务衔接) 执行。
|
||||
|
||||
- [用户、职位角色与分层权限统一方案 r1](modules/documents/docs/proposals/2026-10-13-identity-role-permission-plan.md):用户已批准,首批仅 I1(U1–U2);
|
||||
[权限合同](modules/documents/docs/proposals/2026-10-13-iam-i1-permission-contract.md) 冻结边界,
|
||||
[I1 实施候选与评审记录](modules/documents/docs/proposals/2026-10-13-iam-i1-review.md) 维护实际结果与剩余限制;后续单元须另行授权。
|
||||
|
||||
- [快慢通道展示与动态工作摘要方案 r1.1](modules/documents/docs/proposals/2026-10-12-fast-slow-channel-display.md):
|
||||
对话即时答复、后台动态发现与来源、四层阅读、真实状态、并发/恢复及 U1–U4;
|
||||
2026-10-12 方案设计已批准(阶段 1–4),首批仅 U1;
|
||||
@@ -51,6 +55,8 @@ U2 起实施与部署分别评审。
|
||||
|
||||
## 验证
|
||||
|
||||
规范修订:[最终验证与合入调度 r1](policy/merge-queue.md) 及[U1–U6 实施计划](modules/documents/docs/proposals/2026-10-13-merge-queue-implementation-plan.md)已批准。通用规范与计划已分别经 PR 合入,本候选组合固定实际合入 SHA,见[采纳记录](policy/adoption.md)。同步幂等化、显式局部恢复和源码外证据基础已实施;Dev 最新组合验收及平台调度模式启用仍按各自门禁办理。
|
||||
|
||||
`make test` 先核对子模块工作树与固定指针,再运行治理脚本、公开边界、跨仓指针、库及各模块
|
||||
测试。实际目标以根 Makefile 为准。`make test-governance` 可独立检查开发门禁,使用临时
|
||||
真实仓库,不访问生产数据;操作与证据要求见 [开发门禁](policy/governance.md)。
|
||||
|
||||
Vendored
+1
-1
Submodule deps/development-guidelines updated: af199eeaa2...2f5b19872b
+5
-2
@@ -6,12 +6,14 @@
|
||||
- 过程文档位置:modules/documents(docs/records/)
|
||||
- 版本权威:父仓 gitlink
|
||||
- 批准/生效依据:Dev PR#1(19/19 正负例 + make test);guidelines PR#1
|
||||
- 本次升级候选:`0.1.0-draft.11`,实际合入提交 `2f5b19872be2a4597838a4131ad75d1be089ee5e`(Guidelines PR #5);本候选父仓固定该源码版本。基础门禁继续执行,合入调度流程单独记 approved/pending-activation;Dev 组合验收、平台正负例和全部写入者覆盖完成前不切换交付模式。
|
||||
- 过程计划固定:Documents PR #96 实际合入 `129cc3579d9c9e3edfb46c99270d65f3e1dad0a0`,包含既有主干文档及获批 U1–U6 计划;当前进展以任务/PR 记录为准。
|
||||
|
||||
| 规范/工作流 | 适用条件与范围 | 执行方式/证据 |
|
||||
|---|---|---|
|
||||
| core/baseline.md | 日常 | manual |
|
||||
| workflows/delivery.md(含启动自检 §1) | 交付 | 本地命令检查;首次收据与 BUSY/SYNC/REMOTE_UNVERIFIED 语义见 policy/governance.md |
|
||||
| standards/collaboration.md | 并行/同步节点 | **enforced(本地)**:pre-commit 拒绝集成分支直提与坏分支名(负例:hook-main/badbranch 拒绝输出);节点同步由 task-sync 落卡 |
|
||||
| standards/collaboration.md | 并行/同步节点 | **enforced(本地命令)**:分支、现场、组件检查;task-sync 实际合并时记录,同基线只核验。最终平台与阶段授权另验 |
|
||||
| 提交格式(主题行/四段 body) | 全部提交 | 主题行 enforced(local),body manual;任务工具生成四段 body,历史 PR 仅作旧版本证据 |
|
||||
| standards/verification.md | 验收 | 本地 make test-governance + 组合测试;当前证据与历史 scratch 记录分开 |
|
||||
| standards/multi-repo.md | 跨仓/gitlink | manual;受控历史迁移通道已于 2026-09-30 行使一次(documents/records/2026-09-30-history-migration.md) |
|
||||
@@ -20,6 +22,7 @@
|
||||
| standards/tool-adaptation.md(draft.8) | 为工具创建规则/钩子实例 | 本组合 CLI/git 级实例已登记:policy/tool-adaptation-matrix.md(A1–A7 逐行定级+正负例证据) |
|
||||
| 同上(zcode 实例) | zcode 客户端写路径 | policy/tool-adaptation-zcode.md:PreToolUse 写守卫;脚本负例与客户端接线分开,客户端 pending-live |
|
||||
| core/baseline.md「规范层级」+ documentation.md「记忆分层/机制化」(draft.9) | 层级效力、记忆权威、经验机制化 | manual:本仓 policy/ 仅收紧不放宽;记忆四层按条文执行(任务卡=任务记忆,documents/records=项目记忆,Dev 仓 AGENTS.md 指针已就位) |
|
||||
| draft.11 合入调度候选 | approved/pending-activation;本候选组合固定实际合入 SHA,Dev 验收待完成 | CLI/文档已有 SELF、独立 REPLAY 和历史固定对象 GATE;当前新组合须另验。Issue #518 已建且协调者已指定,平台正负例、所有写入者覆盖及真实试行未完成;现行准入条件保留 |
|
||||
|
||||
## 项目差异
|
||||
|
||||
@@ -29,4 +32,4 @@
|
||||
| delivery.md §2、design-verification.md 的轻量流程默认做法 | 按 [policy/delivery.md](delivery.md) 保留需求、方案、实施与验收的人工节点;代码 Review 与 PR 合入交由智能体,冲突解决且当前 HEAD 的 CI 通过后合入;小任务简化篇幅,阶段内可迭代 | 2026-10-12 用户明确要求智能体 Review 后合入,合入前解决冲突且 CI 通过;其他人工决策与高后果双钥匙边界保留 | weichen;Review、CI 或平台准入条件变化时复查 |
|
||||
|
||||
- 历史记录(2026-09-30)报告 11 个组合仓 main 禁直推、拒绝陈旧候选;当前平台能力须独立重验,本地脚本测试不替代远端证据。
|
||||
- 当前规范内容以 gitlink 及其 VERSION 为准。治理可靠性修复的命令与证据约定见 [开发门禁](governance.md),本地与客户端/平台能力分别登记;本轮不改变分支保护、发布授权或通用库版本。
|
||||
- 规范源码以 gitlink 及其 VERSION 为准,流程启用以本采纳状态及项目条件为准;固定源码版本不代替实际能力证明。治理可靠性修复的命令与证据约定见 [开发门禁](governance.md),本地与客户端/平台能力分别登记;本轮不改变分支保护或发布授权。
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
正式测试和最终 Review 的顺序不限制设计阶段试验、实施期测试或提前审阅。
|
||||
文本合并无冲突不证明组合兼容;合入后仍核验真实内容与被测候选是否一致。
|
||||
|
||||
多个就绪任务的最终验证与合入按 [合入调度](merge-queue.md) 的采纳状态执行。
|
||||
新模式生效后,阶段 7–9 在同一目标交付窗口内完成;对象未变时各节点重复核验,
|
||||
不因记录检查再创建提交或重跑有效 CI。窗口领取不代替人工结论、独立 Review 或平台准入;
|
||||
未生效时继续既有同步规则。固定后的证据按 [源码外收据流程](governance.md#候选固定前后) 保存。
|
||||
|
||||
## 长期方向与任务衔接
|
||||
|
||||
用户提出大方向、长期目标或跨任务规划时,必须在项目中留存可接续的主记录,不能仅保留
|
||||
@@ -169,6 +174,8 @@
|
||||
|
||||
本流程由智能体执行,任务卡模板和本地提示不构成平台强制门禁。平台能力按
|
||||
[能力矩阵](tool-adaptation-matrix.md) 核验;分支保护变更仍由用户按双钥匙边界执行。
|
||||
交付窗口覆盖普通 PR、闭合 PR、机器人及紧急通道;coordination 仍为 manual 时如实登记,
|
||||
当前目标在窗口内被外部推进则建立新尝试,不继续使用旧候选的绿色结果。
|
||||
|
||||
### 清理与恢复
|
||||
|
||||
|
||||
+34
-2
@@ -20,10 +20,23 @@ make task-start NAME=feature/my-task PRECHECK_RECEIPT=/tmp/my-task-precheck.json
|
||||
同一收据不可覆盖。首次检查失败时处理原因,使用新路径重新启动。主干在两步间前进时,
|
||||
重新确认新的启动基线,不能把旧收据移花接木到新对象。
|
||||
|
||||
恢复已有任务仍先捕获本次启动检查,再在原任务工作区用
|
||||
`IMPORT_RECEIPT=/tmp/my-task-resume.json make evidence` 导入;不重新建分支。
|
||||
恢复已有登记任务先捕获本次启动检查,再在原任务 worktree 用
|
||||
`make task-resume PRECHECK_RECEIPT=/tmp/my-task-resume.json` 验证并原样导入;不重新建分支。
|
||||
默认 `TASK_WORK_MODE=delivery`,继续要求严格 READY 和当前目标一致。
|
||||
工作区忙或落后时先由 owner 保存现场并同步;远端不可达时只继续不依赖新鲜基线的阅读和分析。
|
||||
|
||||
局部恢复接口已具备,流程启用仍以 [合入调度](merge-queue.md#采纳顺序与停止条件) 的采纳条件为准。
|
||||
启用后,既有任务可以显式选择 local,保持原 R0,标记落后,继续已授权局部工作:
|
||||
|
||||
```bash
|
||||
TASK_WORK_MODE=local RECEIPT=/tmp/my-local-resume.json CMD="make precheck" make evidence
|
||||
TASK_WORK_MODE=local make task-resume PRECHECK_RECEIPT=/tmp/my-local-resume.json
|
||||
```
|
||||
|
||||
LOCAL_READY 只用于局部恢复;新任务入口始终校验严格 READY,正式验收与合入另行同步。
|
||||
恢复核验实际 worktree/卡片/分支、R0 祖先关系及源码外原始收据;脏现场、Git 操作、组件漂移和未知远端仍拒绝。
|
||||
旧收据允许作为历史证据原样导入,不自动成为当前恢复或交付准入;格式继续为 schema 1。
|
||||
|
||||
堆叠任务使用 `PARENT=feature/parent PARENT_PR=<父PR链接>`。父分支须包含本批主干 R0;
|
||||
卡片同时记录父分支、父 SHA 和父 PR。父先合入,子同步最新 main 并改回 main 为 PR base。
|
||||
|
||||
@@ -37,6 +50,9 @@ make task-start NAME=feature/my-task PRECHECK_RECEIPT=/tmp/my-task-precheck.json
|
||||
它是组合测试和构建的前置检查。父仓未提交改动可以局部测试,但证据必须记录实际对象状态;
|
||||
正式验收使用已提交的固定对象,不能仅写一个与工作树不同的 HEAD。
|
||||
|
||||
`task-sync` 对同一基线重复调用只核验,保留 HEAD、索引和卡片;实际合并新目标才追加一次记录。
|
||||
输出完整 sync_base、记录完成后的 tested_head、checked_at 及 changed;changed 表示是否实际纳入新目标,组件仍须核对。
|
||||
|
||||
## 本地记录与独立证据
|
||||
|
||||
本地 `make evidence` 一律生成 SELF。`SRC=GATE/REPLAY` 不可把本地执行升级为权威来源。
|
||||
@@ -51,6 +67,22 @@ PR 合入由智能体执行:先完成有效 Review,再同步并解决冲突
|
||||
命令含敏感值时展示脱敏形式,并记录原命令摘要。执行前后对象分别记录;命令改变 HEAD
|
||||
时不把执行前 SHA 冒充执行后对象。不能读取执行后状态时明确记录缺项。
|
||||
|
||||
### 候选固定前后
|
||||
|
||||
固定前,完成同步、必要检查、任务卡及本地收据提交,核对干净的组件组合,再读取和推送最终 HEAD。
|
||||
将这个 head、base、完整组件及相关配置引用到调度尝试;任务卡表内“记录前对象”不当作最终候选。
|
||||
|
||||
固定后,本地命令使用源码外唯一 RECEIPT 保存完整输出,平台 CI/Review/合入结果写调度记录:
|
||||
|
||||
```bash
|
||||
RECEIPT=/tmp/my-attempt-worktree-check.json CMD="make test-worktree" make evidence
|
||||
```
|
||||
|
||||
执行者选择位于当前 checkout 和 Git 元数据之外的未用绝对路径,工具保留 before/after、原命令退出码和 SELF 来源。
|
||||
这是 manual 流程,工具不提供工作树冻结或路径范围的 OS 强制隔离。命令改变源码、组件或相关配置时,
|
||||
保留实际记录,重新建立尝试及受影响验证;新增提交必须核验新 HEAD 的必需 CI。
|
||||
后续实际合入/清理后按授权闭合任务,再原样导入已保存收据;不向仍冻结的候选追加记录提交或补写未来结果。
|
||||
|
||||
凭据从环境或受保护文件引用,避免出现在命令字面量。脱敏是补充防护,不是允许输出秘密。
|
||||
执行脚本不通过 shell tracing 暴露环境;管道任何一段失败应传回非零退出码。
|
||||
工具写入失败本身也必须报错,不能只保留一个看似成功的退出状态。
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
# 最终验证与合入调度(候选条文 r1)
|
||||
|
||||
状态:approved(r1 条文作为实施计划基线);执行模式:未切换;日期:2026-10-13(Asia/Shanghai);规范采纳负责人:weichen。
|
||||
r1 条文来源提交为 913b214f2826a50b3dab65f79d9ebf83e84f2519;Documents 实施与测试计划 r1@ba4044d57e38d0887dd425d052abf26b20a28bf4 已于 2026-10-13 获用户「批准」。阶段结论与执行进展按 [迭代交付](delivery.md) 记录在任务主记录,候选固定后的结论保存在关联 PR 与源码外收据中;规范及计划批准不等于模式已启用。
|
||||
现行规则仍以根 AGENTS.md、[迭代交付](delivery.md)、[开发门禁](governance.md) 和已采纳 gitlink 为准。
|
||||
本分支已实施 CLI 同步/恢复及源码外证据基础,自检依据见任务主记录及固定后记录;通用条文和实施计划已经各自 PR 合入,本候选组合固定实际合入版本。线上队列、CI 分流与平台保护尚未切换,不承诺自动调度能力已就位。
|
||||
|
||||
## 通用规范与项目职责
|
||||
|
||||
通用正文的唯一候选来源是 Development-Guidelines 的 `standards/merge-queue.md`,
|
||||
配套模板为该仓 `templates/merge-queue.md`,候选内容版本 `0.1.0-draft.11`。
|
||||
本仓仅细化目标、入口、阶段、预算和采纳条件,不复制通用调度正文。
|
||||
通用候选已经 [Guidelines PR #5](https://git-home.u1680832.nyat.app:30319/weichen/Development-Guidelines/pulls/5) 合入为 `2f5b19872be2a4597838a4131ad75d1be089ee5e`;本候选 `deps/development-guidelines` 已物化该固定版本及上述文件。Dev 组合验收、平台正负例、全部正常写入者覆盖及模式切换条件满足前,本文件保持 approved/pending-activation,不标 active。
|
||||
|
||||
Dev 目标身份使用实际托管实例、仓库 ID 与 `refs/heads/main`;其他组件仓各有自己的目标队列。
|
||||
跨仓组合按任务中的依赖关系先交付组件,再固定父仓组合;保留其他任务已经合入的 gitlink 能力。
|
||||
队列仅安排工作,受保护分支最终准入继续由 Gitea 或等价受信入口负责。
|
||||
|
||||
## 项目调度记录
|
||||
|
||||
采纳后的首版使用每个目标一份 Gitea 调度 Issue,由一个明确协调者维护,标为 manual。
|
||||
任务卡引用 Issue 条目及尝试标识,原任务仍是授权、需求和阶段的状态权威。
|
||||
共享记录已建立:[Dev main 调度记录 #518](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/issues/518),包含实际托管实例、仓库 ID 和目标引用。用户已指定当前交付智能体负责后续人工协调,weichen 保留阶段评审和平台保护操作;具体任期与接管约束以该记录为准。
|
||||
记录仍为准备中,不开放领取,不自动向其他任务分配工作;角色分工不代替启用条件。
|
||||
|
||||
必须记录目标、条目、执行者、源 HEAD、依赖、顺序、尝试、最终 base/head、组件清单、
|
||||
检查定义及相关配置、实际 CI 链接/身份、Review 对象、真实合入结果和暂停/恢复原因。
|
||||
平台结果先保存于调度记录;本地命令仍用 `make evidence` 保存完整 SELF 收据。
|
||||
候选固定后新增命令收据使用源码之外的唯一绝对 `RECEIPT` 路径,不立即提交进被测分支。
|
||||
收据之后按已授权闭合流程原样导入;不能手抄、补写或自行提升来源。
|
||||
|
||||
候选固定前完成任务卡、必要收据和其他需提交内容,再推送最终 HEAD。
|
||||
固定后如果必须提交新内容,即使是任务卡,也建立新尝试并核验新 HEAD 的必需 CI;不复用旧提交状态放行。
|
||||
合入 SHA 和清理结果在实际发生后回填,不能提前将未来合入或清理标为已完成。
|
||||
闭合 PR 进入同一队列;可在授权范围内批量闭合已合入任务,保留每个真实主 PR 和结果。
|
||||
|
||||
## 拟替换的启动与同步要求
|
||||
|
||||
正式采纳时须一起修订根 AGENTS.md、`policy/governance.md` 与 `scripts/task.sh` 的对应入口;
|
||||
不能仅新增本文就宣布以下能力有效。
|
||||
|
||||
| 场景 | 拟采用动作 | 当前实现边界 |
|
||||
|---|---|---|
|
||||
| 新任务启动 | 保留唯一 precheck 收据与 `task-start`,从最新权威 main 建独立工作区 | 现有命令保留 |
|
||||
| 恢复落后任务的局部工作 | 核对现场、授权和落后状态,记录待最终同步;允许已授权局部编辑 | 已实现显式 local/LOCAL_READY 及 task-resume;默认仍严格,流程待采纳启用 |
|
||||
| 其他任务合入、排队等待 | 更新落后/依赖提示,不自动同步各分支或触发完整 CI | 无自动调度能力,不能宣称已接线 |
|
||||
| 领取最终交付窗口 | 成功 fetch,运行 `make task-sync`,实际合并最新 main、处置冲突、物化固定组件 | 使用真实同步及祖先检查,保留脏状态/远端失败拒绝 |
|
||||
| 窗口内再次到达验收、审核、合入节点 | 再查最新目标和候选;未变只核验,不创建元数据提交 | 已实现同基线不提交,保留组件核对和完整最终 SHA |
|
||||
| base/head、组件或相关配置变化 | 保留旧结果对象范围,重新建立尝试并按影响补测/复审 | 当前 HEAD 的全部必需 CI 仍必须实际通过 |
|
||||
|
||||
SYNC_REQUIRED 不整体降级为 READY;local 与 delivery 用独立结果和验证用途区分。
|
||||
已同步启动收据验证、恢复入口及回归,写守卫保留原分支检查,不宣称已校验阶段批准。
|
||||
共享工作区忙、未知远端和活动 Git 操作继续拒绝相应写入。
|
||||
|
||||
幂等化的判据是:成功 fetch、目标已包含、递归组件检查通过时,重复核验不改变 HEAD、索引和提交内容;
|
||||
仍产生可复核的实际输出。任务真正需要纳入新目标时保留 merge 与对应对象记录。
|
||||
所有候选身份使用必要记录提交完成后的最终 HEAD,不能把记录提交前的 SHA 当作最终被测对象。
|
||||
|
||||
## 拟调整的交付阶段
|
||||
|
||||
人工评审和独立 Review 要求不变;提前审阅可以并行,最终有效性核验和受影响重放在窗口内完成。
|
||||
入队与领取不构成人工批准,也不授权跳过缺失阶段。
|
||||
|
||||
| 原阶段 | 采纳后的调度衔接 |
|
||||
|---|---|
|
||||
| 1–6 需求、设计、计划、实施 | 各任务并行推进,在既有授权内局部检查;内容就绪时登记交付条目 |
|
||||
| 7 正式测试与验收 | 领取窗口后实际同步最新 main,固定候选,完成正式验证并取得必要人工结论 |
|
||||
| 8 代码 Review | 对同一固定候选核验有效 Review、独立重放和问题闭合;同步或整改后按影响复审 |
|
||||
| 9 合入 | 保持窗口,核验最新 base/head、全部必需 CI 和平台准入,经 PR 合入并查询真实结果 |
|
||||
| 10 核验与收口 | 核对合入内容和组件后释放窗口;部署、清理另按原授权,闭合 PR 单独排队 |
|
||||
|
||||
等待人工结论超过预算时进入 blocked 并安全释放窗口;再次领取时判断原结论及证据是否仍适用。
|
||||
窗口不得长期持有等待新的需求或方案批准;必要前置安排应在入队时说明。
|
||||
普通失败和需要决策只阻塞当前条目及依赖项;未知合入结果则保持该目标在 reconciling,先对账。
|
||||
|
||||
## CI 资源与检查
|
||||
|
||||
本草案只定义后续分配要求;`.gitea/workflows/ci.yaml` 当前行为保持原样。
|
||||
后续将局部反馈与最终准入的触发和资源安排区分,不通过跳过必需作业返回绿色来节省成本。
|
||||
当前 workflow 中的 `test`、`test-pg`、`svc-realcombo` 等作业须按最终已批准集合核验;
|
||||
平台状态上下文的完整名称、运行身份和被测 SHA 从当次实际运行核对,不从历史文档直接照抄。
|
||||
|
||||
建议首轮人工试行预算,待本文件及试行计划获批后使用:
|
||||
|
||||
- 每个目标一个最终候选;其独立作业可在实际 runner 能力内并行。
|
||||
- 最大无进展时间 20 分钟,单次窗口预算 90 分钟;有效进展、延长及原因记入尝试。
|
||||
- 已知基础设施失败最多额外重试一次;代码失败直接退出整改,不自动重复跑完整 CI。
|
||||
- 等待队列每 30 分钟复查一次;最终准入优先,但不无限延后其他目标、main 验证或发布检查。
|
||||
- 全局 runner 并发预算在试行前读取实际容量后登记;未核验容量时按现有资源安排,不凭建议数值调整平台。
|
||||
|
||||
预算是试行参数,长期值由真实耗时和等待情况复盘决定。
|
||||
容量不足时先等待 runner,再领取目标窗口;不得持有另一个仓库窗口去等待组件窗口。
|
||||
取消仅针对失效且归属明确的当前尝试,其他任务有效的局部检查继续运行。
|
||||
|
||||
## 平台与接管边界
|
||||
|
||||
现有 [平台准入所有者操作](platform-admission.md) 是历史准备材料,执行前仍需当场重验。
|
||||
最新只读配置观察、命令和对象由本任务 evidence 收据保存;配置读取属于 SELF,不证明拒绝路径已经有效。
|
||||
本草案不修改分支保护、批准数或白名单。
|
||||
|
||||
人工协调期间,全部正常合入者按窗口约定行动。没有执行端覆盖全部写入者及原子准入的正负例时,能力上限为 manual。
|
||||
发现外部目标推进,立即判定本轮候选失效,保留结果并建立新尝试;不能以目标锁的本地存在绕过平台拒绝。
|
||||
自动模式未来必须具备原子持久领取和合入入口的旧执行者拒绝;租约到期不直接授权接管。
|
||||
合入请求超时先核对 PR 和目标真实状态,旧执行者退出后才能安全重试或释放。
|
||||
|
||||
需要调整平台保护时,由 Agent 准备具体配置、回退和隔离验证命令,用户按根 AGENTS.md 双钥匙要求确认并亲手执行。
|
||||
至少验证合法候选可合入,以及缺检查、失败检查、陈旧候选和无权限推进被拒。
|
||||
人工首版验证旧执行者确认退出后交接及未知结果对账;自动接管启用前另须验证实际合入入口拒绝旧代次。
|
||||
当前不运行这些平台试验,也不影响其他进行中任务。
|
||||
|
||||
## 采纳顺序与停止条件
|
||||
|
||||
1. 人工审阅通用候选、Dev 条文及记录模板;确认同步时机、阶段衔接、预算和恢复边界。
|
||||
2. 在通用仓完成候选修订 Review 与其文档验证,按授权经 PR 合入;宿主采纳完整固定 SHA。
|
||||
3. 另行提交实施计划,覆盖 task-sync 幂等化、恢复/写守卫兼容、源码外收据、CI 分配和能力矩阵。
|
||||
4. 在隔离场景完成治理脚本正负例;涉及脚本必须运行 `make test-governance`。
|
||||
5. 跨仓 gitlink 正式推进前运行 Dev `make test`;最终对象的 CI/构建及平台准入遵循现行规则。
|
||||
6. 完成所有者平台操作和对应验证,登记真实协调者与调度 Issue,才切换新交付模式。
|
||||
7. 用三个就绪任务试行,记录同步轮数、CI 次数/耗时、废弃运行、排队和恢复,复盘后决定扩大。
|
||||
|
||||
第 2 步规范文本合入或第 5 步指针推进均不自动将新模式标 active/enforced。
|
||||
迁移期保留现行准入规则,直到新条文、命令和平台条件一致;未满足时暂停模式切换,已授权局部开发可继续。
|
||||
回退先停止新领取并对账活动合入,再恢复旧同步流程,保留尝试和证据历史。
|
||||
|
||||
| 验收 ID | 需验证的行为 | 本轮状态 |
|
||||
|---|---|---|
|
||||
| MQ1 | A/B/C 正常顺序交付;各任务轮到后最终同步一次,无外部推进时不因其他正常合入反复跑最终 CI | NOT_RUN(后续隔离试行) |
|
||||
| MQ2 | 同目标重复领取被拒,不同目标可在资源预算内并行 | NOT_RUN(后续机制验证) |
|
||||
| MQ3 | 同步后目标未变,重复核验不改 HEAD;固定后记录不改变候选 | 本地 CLI/证据回归已执行(SELF,原始输出见任务卡);平台试行 NOT_RUN |
|
||||
| MQ4 | 新 HEAD、base 或组合不能使用旧 CI/Review 直接放行 | NOT_RUN(后续正负例) |
|
||||
| MQ5 | 冲突、代码失败、等待人工决定超时释放窗口,不阻塞无依赖任务 | NOT_RUN(后续试行) |
|
||||
| MQ6 | 紧急插队有依据,受影响候选失效;跨仓依赖和组件能力不回退 | NOT_RUN(后续试行) |
|
||||
| MQ7 | 请求超时、协调者退出先对账;不能仅因租约过期让旧新执行者同时合入 | NOT_RUN(后续恢复验证) |
|
||||
| MQ8 | 平台拒绝未满足准入者,合法固定候选可合入;manual 能力如实登记,自动接管另验证旧代次拒绝 | NOT_RUN(所有者操作后验证) |
|
||||
| MQ9 | 文档链接、入口、版本、单一来源、状态与模板字段一致 | 本轮执行;原始输出见任务卡 SELF 收据 |
|
||||
|
||||
这些验收 ID 用于实施与验收追溯;本分支已完成可独立实施的 CLI/证据基础、规则与模板准备及临时真实 Git 场景自检。固定规范采纳、实际模式切换、平台正负例和真实 CI/三任务试行仍待各自门禁,未执行项保持 NOT_RUN;当前进展与原始证据统一见任务卡。
|
||||
@@ -5,6 +5,22 @@
|
||||
|
||||
## 已核实的差距
|
||||
|
||||
本轮读取结果见 `.task/feature-merge-queue-policy-draft.md` 的原始 SELF 收据(2026-10-13;
|
||||
命令 `python3 /tmp/mq-read-platform-20261013.py`)。观察对象 main 为
|
||||
`ff3fb4a7ebe432ab21c6b9668abc9823aca2fdd1`:禁直推/强推、陈旧分支阻断开启,
|
||||
必需状态检查关闭,管理员强制合入覆盖未阻断。此为配置观察,不是拒绝路径通过证明。
|
||||
PR #513 的固定 head `9a3c205b0b93185172cb6b31f6793b3a4d71ea58` 状态列表实际包含以下三个上下文;
|
||||
该次记录未核验它们的最终通过结果,不能据此合入该 PR。
|
||||
|
||||
```text
|
||||
ci / test (pull_request)
|
||||
ci / test-pg (pull_request)
|
||||
ci / svc-realcombo (pull_request)
|
||||
```
|
||||
|
||||
仓级 runner 查询为空,不代表实例/组织共享 runner 不存在;总体容量与预算仍待核验。
|
||||
执行以下操作前重查 main、保护配置和当次 PR/运行,不把本次读配置当长期授权或能力保证。
|
||||
|
||||
2026-10-05 读取 Dev 的 main 分支保护 API,观察到禁直推与陈旧分支拦截开启,
|
||||
必需状态检查关闭、批准数为 0。读取历史 PR head 的状态 API,实际检查名为
|
||||
`ci / test (pull_request)`。完整命令与输出由 evidence 工具记录在
|
||||
@@ -15,8 +31,9 @@
|
||||
|
||||
1. 在 Gitea 的 Agent-System-Dev 仓库设置中打开 main 的分支保护,先保留当前配置截图或导出。
|
||||
2. 确认本次修复 PR 的固定 head 已实际运行 CI,并在提交状态中核对完整检查名。
|
||||
3. 启用必需状态检查,选择实际的 `ci / test (pull_request)`;保持禁直推及陈旧分支拦截。
|
||||
3. 启用必需状态检查,逐项选择当次核验的 test、test-pg、svc-realcombo 三个完整 PR 上下文;保持禁直推、禁强推及陈旧分支拦截。
|
||||
不用仅在合入后运行的 `ci / test (push)` 替代 PR 检查。
|
||||
建议同时启用“管理员遵守分支保护”,关闭管理员强制合入覆盖;该配置也由所有者亲手执行。
|
||||
4. 批准数暂不随此修复自动调整。若另行启用非作者批准,先确认有可用且符合权限要求的
|
||||
独立审核账号;不能把切换 Agent 角色当独立账号,也不能盲设阈值使单账号仓库无法交付。
|
||||
5. 在隔离的测试 PR 上验证:无检查、检查失败和陈旧候选均不可合入;合法且检查通过的
|
||||
@@ -26,6 +43,38 @@
|
||||
任一判据未满足时暂停合入并保留测试对象。配置调整失败由所有者对照保存的配置恢复;
|
||||
不把“配置 API 返回成功”写成“门禁已有效”。其他组合仓分别核验,不外推 Dev 的结果。
|
||||
|
||||
## 可评审配置与恢复
|
||||
|
||||
以下是待所有者执行的最小字段变更,不是已经执行的 API 请求;批准数、白名单和其他策略保持原值。
|
||||
在 Gitea 仓库设置 → Branches → main 对照这些字段保存即可。
|
||||
API 使用 PATCH `/api/v1/repos/weichen/Agent-System-Dev/branch_protections/main`,只改变指定字段,
|
||||
语义依据 [官方 API](https://docs.gitea.com/api/operations/repo-edit-branch-protection/) 和
|
||||
[分支保护说明](https://docs.gitea.com/1.27/usage/access-control/protected-branches/)。
|
||||
|
||||
```json
|
||||
{
|
||||
"enable_push": false,
|
||||
"enable_force_push": false,
|
||||
"block_on_outdated_branch": true,
|
||||
"enable_status_check": true,
|
||||
"status_check_contexts": [
|
||||
"ci / test (pull_request)",
|
||||
"ci / test-pg (pull_request)",
|
||||
"ci / svc-realcombo (pull_request)"
|
||||
],
|
||||
"block_admin_merge_override": true
|
||||
}
|
||||
```
|
||||
|
||||
所有者先通过 GET 同一资源保存完整旧配置到仓外受保护文件;恢复只写回本次涉及的这六类字段,
|
||||
使用保存时的实际值,避免覆盖同时发生的其他配置调整。根 AGENTS.md 的双钥匙边界对恢复同样适用,Agent 不自行恢复保护。
|
||||
凭据只从环境或受保护文件引用,不放请求 URL、日志或提交;不生成带 token 字面量的 curl 示例。
|
||||
|
||||
验证对象由所有者和协调者在动作前指定:优先隔离保护规则/测试 PR,记录实际 head/base 和预期副作用。
|
||||
需要作用于 Dev 时单独确认该测试对象及合入/撤回安排;不为构造测试直推 main、发布或改写历史。
|
||||
临时仓的 fixture CI 只证明保护机制,Dev workflow 的仓库 allowlist 和完整业务 CI 仍需在实际 Dev 候选核验。
|
||||
缺检查/失败/陈旧/无权推进的拒绝与合法路径全部有 GATE/REPLAY 后,再更新能力矩阵并判断是否可切换调度模式。
|
||||
|
||||
## CI 信任前提
|
||||
|
||||
本次 workflow 修复减少 checkout token 对候选代码的暴露;仍须由平台维护者核验
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
适配对象:通过 shell/git 操作本仓的执行者。要求来自固定的 Development-Guidelines
|
||||
`standards/tool-adaptation.md` 与根 AGENTS.md;本表登记机制范围,不复制另一套规则。
|
||||
当前修订为治理可靠性修复候选;实际验证记录见 `.task/feature-governance-reliability.md`
|
||||
及其关联 PR。历史 19/19 临时测试不代替本次回归。
|
||||
历史治理可靠性记录见 `.task/feature-governance-reliability.md`;本轮调度与恢复候选的
|
||||
实际 SELF 命令记录见 `.task/feature-merge-queue-policy-draft.md`。历史测试不代替当前对象回归。
|
||||
|
||||
| 要求 | 执行能力及范围 | 机制 | 复验入口 |
|
||||
|---|---|---|---|
|
||||
| A1 启动 | 本地命令阻断;执行者首次调用仍属 manual | precheck 区分忙/落后/远端未核实;start 验证首次收据 | `tests/test_task.py`、`tests/test_evidence.py` |
|
||||
| A2 同步 | 本地命令阻断;最终准入由平台承担 | 合并父仓、保护组件分支、物化固定指针、递归核对 | `tests/test_task.py`、`make test-worktree` |
|
||||
| A1 启动/恢复 | 本地命令阻断;首次调用及阶段授权仍属 manual | 默认严格 READY;显式 local 验证既有任务/R0及 LOCAL_READY;start 固定严格用途,resume 验证后导入 | `tests/test_task.py`、`tests/test_task_recovery.py`、`tests/test_evidence.py` |
|
||||
| A2 同步 | 本地命令阻断;最终准入由平台承担 | 合并父仓、保护组件分支、物化固定指针、同基线幂等、完整最终 SHA | `tests/test_task.py`、`make test-worktree` |
|
||||
| A3 集成分支 | 本地提交闸;平台状态须独立核验 | pre-commit 拒绝 main/master 与坏分支名 | 本地 hook 负例;远端缺 CI/陈旧候选/直推等负例另验 |
|
||||
| A4 提交格式 | 主题行 enforced(local),body manual | commit-msg 校验宽度/句点;任务工具生成四段 body | `tests/test_task.py`;人工核对 PR/卡片同源 |
|
||||
| A5 回收 | 本地命令检查;写者退出由 owner 确认 | 登记根目录、锁/操作状态、父子仓与唯一文件、合入核验 | `tests/test_task.py` |
|
||||
@@ -16,6 +16,8 @@
|
||||
| A7 证据 | 本地 SELF 收据;外部来源独立核验 | 首次捕获、完整脱敏输出/摘要、对象记录、原样导入 | `tests/test_evidence.py`;CI/审阅链接另核 |
|
||||
| A8 阶段评审与迭代 | manual(人工/Agent 执行);前序人工决策,智能体 Review/合入;模板和提示不构成批准身份校验或全流程阻断 | policy/delivery.md;任务卡分别记录阶段结论、Review、冲突、当前 HEAD 的 CI 和合入;post-merge 默认仅提示 | `tests/test_task.py` 核对模板及钩子不自动写入;智能体核对 Review、冲突及匹配 HEAD 的 CI/平台准入;人工批准范围仍须核验 |
|
||||
| A9 长期方向与后续任务 | manual;模板不校验规划有效性或自动调度任务 | policy/delivery.md;PROJECT.md 提供规划入口,任务卡关联规划/里程碑并记录下一步建议 | `tests/test_task.py` 核对生成字段与未授权默认值;人工核对当前规划、进展、重复任务和建议依据 |
|
||||
| A10 交付窗口 | manual(待启用);无自动互斥或接管 | policy/merge-queue.md;Issue #518 与协调者已登记,本候选固定实际合入规范版本;Dev 组合验收、平台正负例及全部写入者覆盖待完成 | tests/test_merge_queue_trial.py 临时 Git 场景 SELF;平台/真实 CI 三任务试行 NOT_RUN |
|
||||
| A11 固定后证据 | manual 选择源码外位置;工具捕获绝对未用路径/原始输出;无 OS 冻结 | policy/governance.md;RECEIPT 捕获,后续授权闭合再原样导入 | `tests/test_evidence.py` 固定对象、权限、覆盖拒绝、原样导入与幂等 |
|
||||
|
||||
## 失败处理
|
||||
|
||||
|
||||
+74
-6
@@ -197,19 +197,80 @@ def read_receipt(filename):
|
||||
return value
|
||||
|
||||
|
||||
def validate_precheck(receipt):
|
||||
def task_context(current=None):
|
||||
"""Verify an existing task checkout; this is not an approval identity."""
|
||||
current = snapshot() if current is None else current
|
||||
root = Path(git_text("rev-parse", "--show-toplevel")).resolve()
|
||||
branch = current["branch"]
|
||||
if not re.fullmatch(r"(?:feature|fix|hotfix|release)/[A-Za-z0-9._-]+", branch):
|
||||
raise EvidenceError("Local recovery requires a task branch")
|
||||
records = [row.split(b"\0") for row in git("worktree", "list", "--porcelain", "-z").split(b"\0\0") if row]
|
||||
matches = [row for row in records if row[0].startswith(b"worktree ")
|
||||
and Path(os.fsdecode(row[0][9:])).resolve() == root]
|
||||
if len(matches) != 1 or not records or matches[0] == records[0]:
|
||||
raise EvidenceError("Recovery requires a registered linked task worktree")
|
||||
entry = matches[0]
|
||||
if any(field.startswith((b"bare", b"detached", b"prunable")) for field in entry) or (
|
||||
b"branch refs/heads/" + branch.encode() not in entry):
|
||||
raise EvidenceError("Task worktree registration is not valid")
|
||||
if (root / ".task").is_symlink():
|
||||
raise EvidenceError("Task registration must belong to the current checkout")
|
||||
card = find_card()
|
||||
if card.is_symlink() or card.resolve().parent != root / ".task":
|
||||
raise EvidenceError("Task registration must belong to the current checkout")
|
||||
content = card.read_text()
|
||||
locations = re.findall(r"^- 工作区:(.+?)\s*[((]worktree[))];分支:([^\s]+)\s*$", content, re.M)
|
||||
bases = re.findall(r"^- 权威仓:.*;目标分支:([^;]+);R0(initial_base_sha)= ([0-9a-f]+)\s*$", content, re.M)
|
||||
if len(locations) != 1 or Path(locations[0][0]).resolve() != root or locations[0][1] != branch:
|
||||
raise EvidenceError("Task card belongs to a different worktree or branch")
|
||||
if len(bases) != 1 or bases[0][0] != current["target_branch"] or not GIT_SHA.fullmatch(bases[0][1]):
|
||||
raise EvidenceError("Task card has no valid target and initial baseline")
|
||||
r0 = bases[0][1]
|
||||
if not current["target_head"]:
|
||||
raise EvidenceError("Task target is unverified; fetch it first")
|
||||
for tip in (current["head"], current["target_head"]):
|
||||
git("merge-base", "--is-ancestor", r0, tip)
|
||||
check = subprocess.run([sys.executable, str(Path(__file__).with_name("check-worktree.py")), "before-sync"],
|
||||
capture_output=True, text=True)
|
||||
if check.returncode:
|
||||
raise EvidenceError("Task workspace is not ready: " + (check.stdout + check.stderr).strip())
|
||||
return {"root": root, "r0": r0}
|
||||
|
||||
|
||||
def validate_precheck(receipt, purpose="start"):
|
||||
if purpose not in ("start", "resume-delivery", "resume-local"):
|
||||
raise EvidenceError("Unknown precheck validation purpose")
|
||||
after = receipt["after"]
|
||||
current = snapshot()
|
||||
if receipt["command"] not in ("make precheck", "bash scripts/task.sh precheck"):
|
||||
raise EvidenceError("Task start requires a precheck receipt")
|
||||
if receipt["exit_code"] != 0 or not re.search(r"^\[verdict\] READY(?:\s|$)", receipt["output"], re.M):
|
||||
raise EvidenceError("Precheck receipt is not READY")
|
||||
expected = "LOCAL_READY" if purpose == "resume-local" else "READY"
|
||||
verdicts = re.findall(r"^\[verdict\] ([A-Z_]+)(?:\s|$)", receipt["output"], re.M)
|
||||
if receipt["exit_code"] != 0 or verdicts != [expected]:
|
||||
raise EvidenceError("Precheck receipt is not " + expected)
|
||||
if after is None or receipt["after_error"] is not None:
|
||||
raise EvidenceError("Precheck object capture is incomplete")
|
||||
if receipt["before"]["head"] != current["head"] or after["head"] != current["head"]:
|
||||
raise EvidenceError("Precheck source HEAD has changed")
|
||||
if after["target_branch"] != current["target_branch"] or not after["target_head"] or after["target_head"] != current["target_head"]:
|
||||
if after["target_branch"] != current["target_branch"] or not after["target_head"]:
|
||||
raise EvidenceError("Precheck authoritative target has changed")
|
||||
if purpose != "resume-local" and after["target_head"] != current["target_head"]:
|
||||
raise EvidenceError("Precheck authoritative target has changed")
|
||||
if purpose.startswith("resume-"):
|
||||
context = task_context(current)
|
||||
try:
|
||||
Path(receipt["cwd"]).resolve().relative_to(context["root"])
|
||||
except ValueError:
|
||||
raise EvidenceError("Precheck belongs to a different task worktree")
|
||||
if any(obj["branch"] != current["branch"] or obj["target_branch"] != current["target_branch"]
|
||||
for obj in (receipt["before"], after)):
|
||||
raise EvidenceError("Precheck belongs to a different task branch")
|
||||
git("merge-base", "--is-ancestor", context["r0"], after["target_head"])
|
||||
if purpose == "resume-local":
|
||||
try:
|
||||
git("merge-base", "--is-ancestor", after["target_head"], current["target_head"])
|
||||
except EvidenceError:
|
||||
raise EvidenceError("Precheck target history has rewound or diverged") from None
|
||||
|
||||
|
||||
def find_card():
|
||||
@@ -261,6 +322,12 @@ def main():
|
||||
try:
|
||||
if os.environ.get("SRC", "SELF") not in ("", "SELF"):
|
||||
raise EvidenceError("Local evidence is SELF; GATE/REPLAY require external execution identity")
|
||||
if sys.argv[1:] == ["--local-context"]:
|
||||
context = task_context()
|
||||
print("[task-state] registered=" + str(context["root"]) + " initial_base=" + context["r0"])
|
||||
return 0
|
||||
if sys.argv[1:]:
|
||||
raise EvidenceError("Unknown evidence arguments")
|
||||
capture, imported, validated = (os.environ.get(key) for key in
|
||||
("RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT"))
|
||||
command = os.environ.get("CMD", "")
|
||||
@@ -269,7 +336,7 @@ def main():
|
||||
if imported or validated:
|
||||
receipt = read_receipt(imported or validated)
|
||||
if validated:
|
||||
validate_precheck(receipt)
|
||||
validate_precheck(receipt, os.environ.get("VALIDATE_RECEIPT_PURPOSE", "start"))
|
||||
report(receipt, "precheck validated")
|
||||
else:
|
||||
card = find_card()
|
||||
@@ -286,7 +353,8 @@ def main():
|
||||
source_cwd, source_common = str(Path.cwd().resolve()), common_dir()
|
||||
timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
|
||||
child_env = os.environ.copy()
|
||||
for key in ("CMD", "SRC", "RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT", "TAIL"):
|
||||
for key in ("CMD", "SRC", "RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT", "TAIL",
|
||||
"VALIDATE_RECEIPT_PURPOSE"):
|
||||
child_env.pop(key, None)
|
||||
result = subprocess.run(["bash", "-o", "pipefail", "-c", command],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=child_env)
|
||||
|
||||
+70
-20
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# 任务启动门禁:precheck / start / sync / close / finish(配套 Makefile 同名目标)。
|
||||
# 任务启动门禁:precheck / start / resume / sync / close / finish(配套 Makefile 同名目标)。
|
||||
# 规范依据:collaboration.md 共同基线与同步节点、治理清理;delivery.md §1/§5。
|
||||
set -euo pipefail
|
||||
|
||||
@@ -20,9 +20,17 @@ fetch_target() {
|
||||
}
|
||||
check_worktree() { python3 "${SCRIPT_DIR}/check-worktree.py" "$1"; }
|
||||
main_worktree_root() { git worktree list --porcelain -z | python3 -c 'import os,sys; print(os.fsdecode(sys.stdin.buffer.read().split(bytes([0]),1)[0][9:]))'; }
|
||||
work_mode() {
|
||||
case "${TASK_WORK_MODE:-delivery}" in
|
||||
delivery|local) printf '%s\n' "${TASK_WORK_MODE:-delivery}" ;;
|
||||
*) die "TASK_WORK_MODE must be delivery or local" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_precheck() {
|
||||
need_repo
|
||||
local mode
|
||||
mode=$(work_mode)
|
||||
fetch_target
|
||||
local branch head rmain dirty sub_drift sub_uninit
|
||||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
@@ -100,11 +108,20 @@ cmd_precheck() {
|
||||
echo "[facts] WARN policy/facts.md 不存在——外部事实登记表缺失(AGENTS.md 证据与验证门禁)"
|
||||
fi
|
||||
echo "[required-reading] core/baseline.md, workflows/delivery.md §1$([ -f policy/delivery.md ] && echo ", policy/delivery.md")$([ "$dirty" -gt 0 ] && echo ", standards/collaboration.md(树脏=他人或未收尾现场,不得写入)")"
|
||||
echo "[R0] $(echo "$rmain" | cut -c1-10)(完整:$rmain)"
|
||||
if [ "$mode" = local ]; then
|
||||
echo "[target] ${rmain}(observed; task initial baseline is reported separately)"
|
||||
else
|
||||
echo "[R0] $(echo "$rmain" | cut -c1-10)(完整:$rmain)"
|
||||
fi
|
||||
if [ "$dirty" -gt 0 ] || [ "${sub_drift:-0}" -gt 0 ]; then
|
||||
echo "[verdict] SHARED_WORKSPACE_BUSY — 工作树不干净,不得写入;先收尾或经 owner 确认"
|
||||
exit 3
|
||||
fi
|
||||
if [ "$mode" = local ]; then
|
||||
python3 "${SCRIPT_DIR}/evidence.py" --local-context || return 2
|
||||
echo "[verdict] LOCAL_READY — existing task may continue authorized local work; final delivery still requires latest-target sync and checks"
|
||||
return 0
|
||||
fi
|
||||
if ! check_worktree before-sync; then
|
||||
echo "[verdict] SHARED_WORKSPACE_BUSY — component or active operation check failed; preserve the workspace"
|
||||
exit 3
|
||||
@@ -123,7 +140,7 @@ cmd_start() {
|
||||
fetch_target
|
||||
[ -n "${PRECHECK_RECEIPT:-}" ] || die "PRECHECK_RECEIPT required: capture RECEIPT=/tmp/task-precheck.json CMD='make precheck' make evidence first"
|
||||
PRECHECK_RECEIPT=$(realpath -- "$PRECHECK_RECEIPT")
|
||||
VALIDATE_RECEIPT="$PRECHECK_RECEIPT" bash "${SCRIPT_DIR}/evidence.sh"
|
||||
VALIDATE_RECEIPT="$PRECHECK_RECEIPT" VALIDATE_RECEIPT_PURPOSE=start bash "${SCRIPT_DIR}/evidence.sh"
|
||||
check_worktree before-sync
|
||||
# 已合入的同名分支不得重建(2026-10-05 实测:合入后再 task-start 同名会从新 main 长出
|
||||
# 第二段历史,空卡覆盖已合入任务卡,两轮证据差点丢失)。复核/追加沿用原分支或换新 slug。
|
||||
@@ -239,6 +256,14 @@ ${parent_note}
|
||||
| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
## 交付调度
|
||||
|
||||
- 调度状态:未入队;调度记录:无
|
||||
- entry_id:无;attempt_id:无;窗口执行者:无
|
||||
- 固定候选:无(base/head、组件与相关配置引用实际尝试)
|
||||
- 调度依据:policy/merge-queue.md;新模式启用前按既有交付门禁
|
||||
- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合
|
||||
|
||||
## 结果与验证(完成后填写)
|
||||
|
||||
- 结果:
|
||||
@@ -274,22 +299,43 @@ EOF
|
||||
echo "[started] branch=${name} worktree=${wt} R0=$(echo "$rmain" | cut -c1-10)"
|
||||
}
|
||||
|
||||
cmd_resume() {
|
||||
need_repo
|
||||
local mode
|
||||
mode=$(work_mode)
|
||||
fetch_target
|
||||
[ -n "${PRECHECK_RECEIPT:-}" ] || die "PRECHECK_RECEIPT required: capture the current task precheck before resume"
|
||||
local receipt
|
||||
receipt=$(realpath -- "$PRECHECK_RECEIPT")
|
||||
VALIDATE_RECEIPT="$receipt" VALIDATE_RECEIPT_PURPOSE="resume-${mode}" bash "${SCRIPT_DIR}/evidence.sh"
|
||||
IMPORT_RECEIPT="$receipt" bash "${SCRIPT_DIR}/evidence.sh"
|
||||
echo "[resumed] work_mode=${mode} source_head=$(git rev-parse HEAD) observed_target=$(git rev-parse "origin/${TARGET_BRANCH}")"
|
||||
}
|
||||
|
||||
cmd_sync() {
|
||||
need_repo
|
||||
fetch_target
|
||||
check_worktree before-sync
|
||||
git merge --no-edit "origin/${TARGET_BRANCH}" \
|
||||
-m "Merge authoritative ${TARGET_BRANCH} into the task branch" \
|
||||
-m "Why: include the current integration baseline before verification." \
|
||||
-m "Evidence: fetched origin/${TARGET_BRANCH}=$(git rev-parse "origin/${TARGET_BRANCH}")." \
|
||||
-m "Impact: integrate upstream changes without rewriting task history." \
|
||||
-m "Refs: task-sync"
|
||||
git merge-base --is-ancestor "origin/${TARGET_BRANCH}" HEAD || die "sync incomplete"
|
||||
local base head card branch slug pc changed=0 ancestry_status
|
||||
base=$(git rev-parse "origin/${TARGET_BRANCH}")
|
||||
if git merge-base --is-ancestor "$base" HEAD; then
|
||||
:
|
||||
else
|
||||
ancestry_status=$?
|
||||
[ "$ancestry_status" -eq 1 ] || die "cannot verify target ancestry"
|
||||
git merge --no-edit "$base" \
|
||||
-m "Merge authoritative ${TARGET_BRANCH} into the task branch" \
|
||||
-m "Why: include the current integration baseline before verification." \
|
||||
-m "Evidence: fetched origin/${TARGET_BRANCH}=${base}." \
|
||||
-m "Impact: integrate upstream changes without rewriting task history." \
|
||||
-m "Refs: task-sync"
|
||||
changed=1
|
||||
fi
|
||||
git merge-base --is-ancestor "$base" HEAD || die "sync incomplete"
|
||||
check_worktree before-update
|
||||
check_worktree materialize
|
||||
check_worktree after-sync
|
||||
local base head card branch slug pc
|
||||
base=$(git rev-parse "origin/${TARGET_BRANCH}"); head=$(git rev-parse HEAD)
|
||||
head=$(git rev-parse HEAD)
|
||||
branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
slug=$(echo "$branch" | tr '/' '-')
|
||||
card=".task/${slug}.md"
|
||||
@@ -304,18 +350,21 @@ cmd_sync() {
|
||||
done
|
||||
fi
|
||||
if [ -n "$card" ] && [ -f "$card" ]; then
|
||||
printf '| 同步前验收 | %s | %s | %s |\n' "$base" "$head" "$(date -u +%FT%TZ)" >> "$card"
|
||||
git add "$card"
|
||||
git commit -q -m "Record the sync checkpoint" \
|
||||
-m "Why: bind the task validation checkpoint to the latest integration baseline." \
|
||||
-m "Evidence: SELF recursive component checks; sync_base=${base}; candidate=${head}." \
|
||||
-m "Impact: task metadata only." -m "Refs: ${card}"
|
||||
if [ "$changed" -eq 1 ]; then
|
||||
printf '| 同步(记录前对象) | %s | %s | %s |\n' "$base" "$head" "$(date -u +%FT%TZ)" >> "$card"
|
||||
git add "$card"
|
||||
git commit -q -m "Record the sync checkpoint" \
|
||||
-m "Why: bind the task validation checkpoint to the integration baseline." \
|
||||
-m "Evidence: SELF recursive component checks; sync_base=${base}; before_record=${head}." \
|
||||
-m "Impact: task metadata only." -m "Refs: ${card}" -- "$card"
|
||||
fi
|
||||
# JSON receipts carry before/after state; older table rows remain readable.
|
||||
python3 "${SCRIPT_DIR}/check-evidence.py" "$card"
|
||||
else
|
||||
echo "[warn] 找不到分支 ${branch} 的任务卡,同步记录未写入" >&2
|
||||
fi
|
||||
echo "[synced] sync_base=$(echo "$base" | cut -c1-10) tested_head=$(echo "$head" | cut -c1-10) checked_at=$(date -u +%FT%TZ)"
|
||||
head=$(git rev-parse HEAD)
|
||||
echo "[synced] sync_base=${base} tested_head=${head} checked_at=$(date -u +%FT%TZ) changed=${changed}"
|
||||
echo "[next] 跑正式验收(make test 等),证据写入任务卡"
|
||||
}
|
||||
|
||||
@@ -531,8 +580,9 @@ cmd_finish() {
|
||||
case "${1:-}" in
|
||||
precheck) shift; cmd_precheck "$@" ;;
|
||||
start) shift; cmd_start "$@" ;;
|
||||
resume) shift; cmd_resume "$@" ;;
|
||||
sync) shift; cmd_sync "$@" ;;
|
||||
close) shift; cmd_close "$@" ;;
|
||||
finish) shift; cmd_finish "$@" ;;
|
||||
*) echo "usage: task.sh <precheck|start|sync|close|finish> [args]"; exit 2 ;;
|
||||
*) echo "usage: task.sh <precheck|start|resume|sync|close|finish> [args]"; exit 2 ;;
|
||||
esac
|
||||
|
||||
+23
-1
@@ -68,7 +68,8 @@ class EvidenceTests(unittest.TestCase):
|
||||
|
||||
def run_evidence(self, command=None, cwd=None, **values):
|
||||
env = os.environ.copy()
|
||||
for key in ("CMD", "SRC", "RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT"):
|
||||
for key in ("CMD", "SRC", "RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT",
|
||||
"VALIDATE_RECEIPT_PURPOSE", "TASK_WORK_MODE"):
|
||||
env.pop(key, None)
|
||||
if command is not None:
|
||||
env["CMD"] = command
|
||||
@@ -103,6 +104,27 @@ class EvidenceTests(unittest.TestCase):
|
||||
self.assertEqual(result.returncode, 7)
|
||||
self.assertIn('"exit_code": 7', self.card.read_text())
|
||||
|
||||
def test_validation_purpose_is_not_injected_into_measured_command(self):
|
||||
data = self.capture("printf '%s' \"${VALIDATE_RECEIPT_PURPOSE-unset}\"",
|
||||
VALIDATE_RECEIPT_PURPOSE="resume-local")
|
||||
self.assertEqual(data["output"], "unset")
|
||||
|
||||
def test_fixed_candidate_capture_preserves_checkout(self):
|
||||
self.git("add", ".task/main.md")
|
||||
self.git("commit", "-qm", "Save fixture card before validation")
|
||||
head = self.git("rev-parse", "HEAD")
|
||||
card = self.card.read_bytes()
|
||||
index = self.git("ls-files", "--stage")
|
||||
data = self.capture("printf 'fixed candidate check\\n'")
|
||||
self.assertEqual(data["before"]["head"], head)
|
||||
self.assertEqual(data["after"]["head"], head)
|
||||
self.assertEqual(data["output"], "fixed candidate check\n")
|
||||
self.assertEqual(self.git("rev-parse", "HEAD"), head)
|
||||
self.assertEqual(self.git("ls-files", "--stage"), index)
|
||||
self.assertEqual(self.card.read_bytes(), card)
|
||||
self.assertEqual(self.git("status", "--porcelain"), "")
|
||||
self.assertEqual(self.receipt.stat().st_mode & 0o777, 0o600)
|
||||
|
||||
def test_capture_without_card_and_import_without_rerun(self):
|
||||
self.card.unlink()
|
||||
data = self.capture("printf 'once\\n' >> invocation; cat invocation")
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Real disposable Git scenarios; these do not claim CI or automatic queue locking."""
|
||||
import unittest
|
||||
|
||||
import test_task as lifecycle
|
||||
|
||||
|
||||
class QueueTrialTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.fixture = lifecycle.TaskLifecycleTests(methodName="runTest")
|
||||
self.fixture.setUp()
|
||||
self.addCleanup(self.fixture.doCleanups)
|
||||
|
||||
def create_task(self, branch):
|
||||
f = self.fixture
|
||||
wt = f.root / branch.replace("/", "-")
|
||||
f.git(f.repo, "worktree", "add", "-b", branch, str(wt), "origin/main")
|
||||
f.record_task_card(wt)
|
||||
name = branch.split("/")[1]
|
||||
(wt / name).write_text(name + "\n")
|
||||
f.git(wt, "add", name)
|
||||
f.git(wt, "commit", "-m", "Add independent fixture change")
|
||||
return wt
|
||||
|
||||
def test_sequential_delivery_does_not_sync_waiting_branches(self):
|
||||
f = self.fixture
|
||||
branches = ["feature/a", "feature/b", "feature/c"]
|
||||
worktrees = {branch: self.create_task(branch) for branch in branches}
|
||||
waiting = {branch: f.git(wt, "rev-parse", "HEAD").stdout for branch, wt in worktrees.items()}
|
||||
f.advance_remote()
|
||||
for position, branch in enumerate(branches):
|
||||
wt = worktrees[branch]
|
||||
f.task(wt, "sync")
|
||||
candidate = f.git(wt, "rev-parse", "HEAD").stdout
|
||||
second = f.task(wt, "sync")
|
||||
self.assertIn("changed=0", second.stdout)
|
||||
self.assertEqual(f.git(wt, "rev-parse", "HEAD").stdout, candidate)
|
||||
self.assertEqual(f.git(wt, "status", "--porcelain").stdout, "")
|
||||
f.git(f.repo, "merge", "--no-ff", branch, "-m", "Merge fixture candidate")
|
||||
f.git(f.repo, "push", "origin", "main")
|
||||
for pending in branches[position + 1:]:
|
||||
self.assertEqual(f.git(worktrees[pending], "rev-parse", "HEAD").stdout, waiting[pending])
|
||||
for name in ("a", "b", "c"):
|
||||
self.assertEqual((f.repo / name).read_text(), name + "\n")
|
||||
|
||||
def test_conflicted_task_does_not_block_an_independent_healthy_task(self):
|
||||
f = self.fixture
|
||||
conflict = self.create_task("feature/conflict")
|
||||
healthy = self.create_task("feature/healthy")
|
||||
(conflict / "tracked").write_text("task choice\n")
|
||||
f.git(conflict, "commit", "-am", "Conflicting task edit")
|
||||
(f.repo / "tracked").write_text("target choice\n")
|
||||
f.git(f.repo, "commit", "-am", "Target edit")
|
||||
f.git(f.repo, "push", "origin", "main")
|
||||
failed = f.task(conflict, "sync", check=False)
|
||||
self.assertNotEqual(failed.returncode, 0)
|
||||
self.assertNotIn("[synced]", failed.stdout)
|
||||
self.assertIn("UU tracked", f.git(conflict, "status", "--porcelain").stdout)
|
||||
succeeded = f.task(healthy, "sync")
|
||||
self.assertIn("[synced]", succeeded.stdout)
|
||||
self.assertEqual((healthy / "tracked").read_text(), "target choice\n")
|
||||
self.assertEqual((healthy / "healthy").read_text(), "healthy\n")
|
||||
|
||||
def test_external_target_advance_requires_a_new_candidate(self):
|
||||
f = self.fixture
|
||||
task = self.create_task("feature/a")
|
||||
f.task(task, "sync")
|
||||
original = f.git(task, "rev-parse", "HEAD").stdout
|
||||
f.advance_remote()
|
||||
target = f.git(f.repo, "rev-parse", "HEAD").stdout.strip()
|
||||
result = f.task(task, "sync")
|
||||
current = f.git(task, "rev-parse", "HEAD").stdout
|
||||
self.assertNotEqual(current, original)
|
||||
self.assertIn("changed=1", result.stdout)
|
||||
f.git(task, "merge-base", "--is-ancestor", target, "HEAD")
|
||||
self.assertEqual((task / "a").read_text(), "a\n")
|
||||
self.assertEqual((task / "new-main").read_text(), "new main\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+92
-1
@@ -26,7 +26,8 @@ class TaskLifecycleTests(unittest.TestCase):
|
||||
"GIT_AUTHOR_NAME": "Lifecycle Test", "GIT_AUTHOR_EMAIL": "test@example.invalid",
|
||||
"GIT_COMMITTER_NAME": "Lifecycle Test", "GIT_COMMITTER_EMAIL": "test@example.invalid"})
|
||||
for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GITEA_TOKEN", "PRECHECK_RECEIPT",
|
||||
"TASK_PARENT_BRANCH", "TASK_PARENT_PR", "FORCE_ABANDON", "TASK_TARGET_BRANCH"):
|
||||
"TASK_PARENT_BRANCH", "TASK_PARENT_PR", "FORCE_ABANDON", "TASK_TARGET_BRANCH",
|
||||
"TASK_WORK_MODE", "VALIDATE_RECEIPT_PURPOSE"):
|
||||
self.env.pop(key, None)
|
||||
self.git(self.root, "init", "--bare", "--initial-branch=main", str(self.remote))
|
||||
# receive-pack 默认在后台跑 gc --auto,与 TemporaryDirectory 清理竞态
|
||||
@@ -69,6 +70,20 @@ class TaskLifecycleTests(unittest.TestCase):
|
||||
self.git(self.repo, "commit", "-m", "Advance main")
|
||||
self.git(self.repo, "push", "origin", "main")
|
||||
|
||||
def record_task_card(self, wt):
|
||||
branch = self.git(wt, "branch", "--show-current").stdout.strip()
|
||||
r0 = self.git(wt, "rev-parse", "origin/main").stdout.strip()
|
||||
card = wt / ".task" / (branch.replace("/", "-") + ".md")
|
||||
card.parent.mkdir(exist_ok=True)
|
||||
card.write_text(
|
||||
f"# {branch}\n任务主记录:fixture;状态:进行中;owner:Lifecycle Test\n"
|
||||
f"- 权威仓:fixture;目标分支:main;R0(initial_base_sha)= {r0}\n"
|
||||
f"- 工作区:{wt}(worktree);分支:{branch}\n"
|
||||
)
|
||||
self.git(wt, "add", str(card.relative_to(wt)))
|
||||
self.git(wt, "commit", "-m", "Record fixture task")
|
||||
return card
|
||||
|
||||
def add_submodule(self):
|
||||
child = self.root / "component"
|
||||
self.git(self.root, "init", "--initial-branch=main", str(child))
|
||||
@@ -105,6 +120,76 @@ class TaskLifecycleTests(unittest.TestCase):
|
||||
reading = next(line for line in result.stdout.splitlines() if line.startswith("[required-reading]"))
|
||||
self.assertIn("policy/delivery.md", reading)
|
||||
|
||||
def test_sync_same_base_preserves_head_and_card(self):
|
||||
wt = self.worktree()
|
||||
card = self.record_task_card(wt)
|
||||
head = self.git(wt, "rev-parse", "HEAD").stdout.strip()
|
||||
content = card.read_bytes()
|
||||
index = self.git(wt, "ls-files", "--stage").stdout
|
||||
for _ in range(2):
|
||||
result = self.task(wt, "sync")
|
||||
self.assertEqual(self.git(wt, "rev-parse", "HEAD").stdout.strip(), head)
|
||||
self.assertEqual(card.read_bytes(), content)
|
||||
self.assertEqual(self.git(wt, "ls-files", "--stage").stdout, index)
|
||||
self.assertEqual(self.git(wt, "status", "--porcelain").stdout, "")
|
||||
self.assertIn("changed=0", result.stdout)
|
||||
self.assertIn(f"tested_head={head}", result.stdout)
|
||||
|
||||
def test_sync_new_base_reports_actual_final_head(self):
|
||||
wt = self.worktree()
|
||||
card = self.record_task_card(wt)
|
||||
self.advance_remote()
|
||||
base = self.git(self.repo, "rev-parse", "HEAD").stdout.strip()
|
||||
result = self.task(wt, "sync")
|
||||
head = self.git(wt, "rev-parse", "HEAD").stdout.strip()
|
||||
self.git(wt, "merge-base", "--is-ancestor", base, head)
|
||||
self.assertIn(f"sync_base={base}", result.stdout)
|
||||
self.assertIn(f"tested_head={head}", result.stdout)
|
||||
self.assertIn("changed=1", result.stdout)
|
||||
content = card.read_bytes()
|
||||
self.task(wt, "sync")
|
||||
self.assertEqual(self.git(wt, "rev-parse", "HEAD").stdout.strip(), head)
|
||||
self.assertEqual(card.read_bytes(), content)
|
||||
|
||||
def test_sync_preserves_dirty_parent_and_staged_changes(self):
|
||||
for staged in (False, True):
|
||||
with self.subTest(staged=staged):
|
||||
wt = self.worktree(name="feature/staged" if staged else "feature/dirty")
|
||||
head = self.git(wt, "rev-parse", "HEAD").stdout.strip()
|
||||
(wt / "tracked").write_text("keep owner work\n")
|
||||
if staged:
|
||||
self.git(wt, "add", "tracked")
|
||||
index = self.git(wt, "ls-files", "--stage").stdout
|
||||
result = self.task(wt, "sync", check=False)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertEqual(self.git(wt, "rev-parse", "HEAD").stdout.strip(), head)
|
||||
self.assertEqual(self.git(wt, "ls-files", "--stage").stdout, index)
|
||||
self.assertEqual((wt / "tracked").read_text(), "keep owner work\n")
|
||||
self.git(self.repo, "worktree", "remove", "--force", str(wt))
|
||||
|
||||
def test_sync_rejects_unreachable_target_without_changing_head(self):
|
||||
wt = self.worktree()
|
||||
self.record_task_card(wt)
|
||||
head = self.git(wt, "rev-parse", "HEAD").stdout.strip()
|
||||
self.git(wt, "remote", "set-url", "origin", str(self.root / "missing.git"))
|
||||
result = self.task(wt, "sync", check=False)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("REMOTE_UNVERIFIED", result.stdout)
|
||||
self.assertEqual(self.git(wt, "rev-parse", "HEAD").stdout.strip(), head)
|
||||
|
||||
def test_sync_conflict_is_not_reported_as_synced(self):
|
||||
wt = self.worktree()
|
||||
self.record_task_card(wt)
|
||||
(wt / "tracked").write_text("task choice\n")
|
||||
self.git(wt, "commit", "-am", "Task edit")
|
||||
(self.repo / "tracked").write_text("target choice\n")
|
||||
self.git(self.repo, "commit", "-am", "Target edit")
|
||||
self.git(self.repo, "push", "origin", "main")
|
||||
result = self.task(wt, "sync", check=False)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertNotIn("[synced]", result.stdout)
|
||||
self.assertIn("UU tracked", self.git(wt, "status", "--porcelain").stdout)
|
||||
|
||||
def test_sync_materializes_updated_component(self):
|
||||
child = self.add_submodule()
|
||||
wt = self.worktree()
|
||||
@@ -396,6 +481,7 @@ class TaskLifecycleTests(unittest.TestCase):
|
||||
(wt / ".task/feature-test.md").write_text("# Task\n")
|
||||
self.git(wt, "add", ".task")
|
||||
self.git(wt, "commit", "-m", "Create task card")
|
||||
self.advance_remote()
|
||||
hook = self.repo / ".git/hooks/pre-commit"
|
||||
hook.write_text("#!/bin/sh\nexit 1\n")
|
||||
hook.chmod(0o755)
|
||||
@@ -611,6 +697,11 @@ class TaskLifecycleTests(unittest.TestCase):
|
||||
self.assertIn(f"{field}:无", card)
|
||||
for field in ("当前阶段", "当前增量", "方案引用", "实施计划引用", "测试计划引用"):
|
||||
self.assertIn(field + ":", card)
|
||||
self.assertIn("## 交付调度\n", card)
|
||||
queue = card.split("## 交付调度\n", 1)[1].split("\n## ", 1)[0]
|
||||
self.assertIn("调度状态:未入队", queue)
|
||||
for field in ("调度记录", "entry_id", "attempt_id", "窗口执行者", "固定候选"):
|
||||
self.assertIn(field + ":无", queue)
|
||||
self.assertIn("## 关联方向与规划\n", card)
|
||||
planning = card.split("## 关联方向与规划\n", 1)[1].split("\n## ", 1)[0]
|
||||
for field in ("适用状态", "主记录路径/版本", "对应里程碑"):
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
"""Local recovery is task-scoped and cannot grant delivery readiness."""
|
||||
import json
|
||||
import unittest
|
||||
|
||||
import test_task as lifecycle
|
||||
|
||||
|
||||
class TaskRecoveryTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.fixture = lifecycle.TaskLifecycleTests(methodName="runTest")
|
||||
self.fixture.setUp()
|
||||
self.addCleanup(self.fixture.doCleanups)
|
||||
self.wt = self.fixture.worktree()
|
||||
self.card = self.fixture.record_task_card(self.wt)
|
||||
self.receipt_number = 0
|
||||
|
||||
def precheck(self, mode="local", cwd=None):
|
||||
f = self.fixture
|
||||
return f.task(cwd or self.wt, "precheck", check=False,
|
||||
env=f.env | {"TASK_WORK_MODE": mode})
|
||||
|
||||
def capture(self, mode="local"):
|
||||
f = self.fixture
|
||||
self.receipt_number += 1
|
||||
path = f.root / f"recovery-{self.receipt_number}.json"
|
||||
result = f.run_cmd(self.wt, "bash", "scripts/evidence.sh", check=False,
|
||||
env=f.env | {"TASK_WORK_MODE": mode, "RECEIPT": str(path),
|
||||
"CMD": "make precheck"})
|
||||
self.assertEqual(result.returncode, 0, result.stdout)
|
||||
return path
|
||||
|
||||
def resume(self, receipt, mode="local", cwd=None):
|
||||
f = self.fixture
|
||||
return f.task(cwd or self.wt, "resume", check=False,
|
||||
env=f.env | {"TASK_WORK_MODE": mode, "PRECHECK_RECEIPT": str(receipt)})
|
||||
|
||||
def save_card_change(self):
|
||||
f = self.fixture
|
||||
f.git(self.wt, "add", str(self.card.relative_to(self.wt)))
|
||||
f.git(self.wt, "commit", "-m", "Update fixture registration")
|
||||
|
||||
def test_local_precheck_allows_known_lag_without_changing_head(self):
|
||||
f = self.fixture
|
||||
head = f.git(self.wt, "rev-parse", "HEAD").stdout
|
||||
f.advance_remote()
|
||||
strict = self.precheck(mode="delivery")
|
||||
self.assertNotEqual(strict.returncode, 0)
|
||||
self.assertIn("SYNC_REQUIRED", strict.stdout)
|
||||
local = self.precheck()
|
||||
self.assertEqual(local.returncode, 0, local.stdout)
|
||||
self.assertIn("[verdict] LOCAL_READY", local.stdout)
|
||||
self.assertNotIn("[verdict] READY", local.stdout)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head)
|
||||
|
||||
def test_local_precheck_never_advertises_delivery_even_when_current(self):
|
||||
result = self.precheck()
|
||||
self.assertEqual(result.returncode, 0, result.stdout)
|
||||
self.assertIn("[verdict] LOCAL_READY", result.stdout)
|
||||
self.assertNotIn("[verdict] READY", result.stdout)
|
||||
|
||||
def test_local_precheck_rejects_main_and_missing_registration(self):
|
||||
result = self.precheck(cwd=self.fixture.repo)
|
||||
self.assertNotEqual(result.returncode, 0, result.stdout)
|
||||
self.card.unlink()
|
||||
self.save_card_change()
|
||||
result = self.precheck()
|
||||
self.assertNotEqual(result.returncode, 0, result.stdout)
|
||||
|
||||
def test_local_precheck_rejects_foreign_registration(self):
|
||||
self.card.write_text(self.card.read_text().replace(str(self.wt), str(self.fixture.repo)))
|
||||
self.save_card_change()
|
||||
result = self.precheck()
|
||||
self.assertNotEqual(result.returncode, 0, result.stdout)
|
||||
|
||||
def test_local_precheck_rejects_wrong_branch_registration(self):
|
||||
self.card.write_text(self.card.read_text().replace("分支:feature/test", "分支:feature/other"))
|
||||
self.save_card_change()
|
||||
self.assertNotEqual(self.precheck().returncode, 0)
|
||||
|
||||
def test_local_precheck_rejects_invalid_baseline(self):
|
||||
f = self.fixture
|
||||
r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
|
||||
self.card.write_text(self.card.read_text().replace(r0, "0" * 40))
|
||||
self.save_card_change()
|
||||
self.assertNotEqual(self.precheck().returncode, 0)
|
||||
|
||||
def test_local_precheck_rejects_dirty_state_and_unknown_remote(self):
|
||||
f = self.fixture
|
||||
(self.wt / "tracked").write_text("preserve current edit\n")
|
||||
result = self.precheck()
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("SHARED_WORKSPACE_BUSY", result.stdout)
|
||||
f.git(self.wt, "commit", "-am", "Save owner edit")
|
||||
f.git(self.wt, "remote", "set-url", "origin", str(f.root / "missing.git"))
|
||||
result = self.precheck()
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("REMOTE_UNVERIFIED", result.stdout)
|
||||
|
||||
def test_unknown_work_mode_is_rejected(self):
|
||||
result = self.precheck(mode="anything")
|
||||
self.assertNotEqual(result.returncode, 0, result.stdout)
|
||||
|
||||
def test_local_resume_imports_original_receipt_without_sync(self):
|
||||
f = self.fixture
|
||||
receipt = self.capture()
|
||||
original = receipt.read_bytes()
|
||||
head = f.git(self.wt, "rev-parse", "HEAD").stdout
|
||||
f.advance_remote()
|
||||
result = self.resume(receipt)
|
||||
self.assertEqual(result.returncode, 0, result.stdout)
|
||||
self.assertIn("[resumed]", result.stdout)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head)
|
||||
self.assertEqual(receipt.read_bytes(), original)
|
||||
self.assertIn(json.loads(original)["receipt_sha256"], self.card.read_text())
|
||||
current = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
|
||||
self.assertNotEqual(json.loads(original)["after"]["target_head"], current)
|
||||
|
||||
def assert_history_change_rejected(self, receipt):
|
||||
f = self.fixture
|
||||
before = self.card.read_bytes()
|
||||
original = receipt.read_bytes()
|
||||
head = f.git(self.wt, "rev-parse", "HEAD").stdout
|
||||
result = self.resume(receipt)
|
||||
self.assertNotEqual(result.returncode, 0, result.stdout)
|
||||
self.assertIn("Precheck target history has rewound or diverged", result.stdout)
|
||||
self.assertNotIn("[resumed]", result.stdout)
|
||||
self.assertEqual(self.card.read_bytes(), before)
|
||||
self.assertEqual(receipt.read_bytes(), original)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head)
|
||||
|
||||
def test_local_resume_rejects_target_rewind_before_import(self):
|
||||
f = self.fixture
|
||||
r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
|
||||
f.advance_remote()
|
||||
receipt = self.capture()
|
||||
observed = json.loads(receipt.read_text())["after"]["target_head"]
|
||||
self.assertNotEqual(observed, r0)
|
||||
# Only this test's disposable bare remote is rewritten.
|
||||
f.git(f.remote, "update-ref", "refs/heads/main", r0, observed)
|
||||
# Reproduce recovery after another client operation refreshed the target.
|
||||
f.git(self.wt, "fetch", "origin", "+refs/heads/main:refs/remotes/origin/main")
|
||||
self.assert_history_change_rejected(receipt)
|
||||
|
||||
def test_local_resume_rejects_divergent_target_before_import(self):
|
||||
f = self.fixture
|
||||
r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
|
||||
f.advance_remote()
|
||||
receipt = self.capture()
|
||||
observed = json.loads(receipt.read_text())["after"]["target_head"]
|
||||
f.git(f.repo, "switch", "--detach", r0)
|
||||
(f.repo / "divergent-main").write_text("different target history\n")
|
||||
f.git(f.repo, "add", "divergent-main")
|
||||
f.git(f.repo, "commit", "-m", "Create divergent fixture history")
|
||||
divergent = f.git(f.repo, "rev-parse", "HEAD").stdout.strip()
|
||||
f.git(f.repo, "push", "origin", "HEAD:refs/heads/divergent")
|
||||
self.assertEqual(f.git(f.repo, "merge-base", "--is-ancestor", observed,
|
||||
divergent, check=False).returncode, 1)
|
||||
# Both targets retain R0; only normal ancestry may permit recovery.
|
||||
f.git(f.remote, "update-ref", "refs/heads/main", divergent, observed)
|
||||
f.git(self.wt, "fetch", "origin", "+refs/heads/main:refs/remotes/origin/main")
|
||||
self.assert_history_change_rejected(receipt)
|
||||
|
||||
def test_make_resume_exports_the_selected_mode_and_receipt(self):
|
||||
f = self.fixture
|
||||
receipt = self.capture()
|
||||
result = f.run_cmd(self.wt, "make", "-f", str(lifecycle.SOURCE / "Makefile"),
|
||||
"task-resume", check=False,
|
||||
env=f.env | {"TASK_WORK_MODE": "local", "PRECHECK_RECEIPT": str(receipt)})
|
||||
self.assertEqual(result.returncode, 0, result.stdout)
|
||||
self.assertIn("[resumed]", result.stdout)
|
||||
|
||||
def test_local_receipt_cannot_start_a_task_or_resume_for_delivery(self):
|
||||
f = self.fixture
|
||||
receipt = self.capture()
|
||||
before = self.card.read_bytes()
|
||||
delivery = self.resume(receipt, mode="delivery")
|
||||
self.assertNotEqual(delivery.returncode, 0, delivery.stdout)
|
||||
self.assertEqual(self.card.read_bytes(), before)
|
||||
start = f.task(self.wt, "start", "feature/bypass", check=False,
|
||||
env=f.env | {"PRECHECK_RECEIPT": str(receipt), "TASK_WORK_MODE": "local",
|
||||
"VALIDATE_RECEIPT_PURPOSE": "resume-local"})
|
||||
self.assertNotEqual(start.returncode, 0, start.stdout)
|
||||
self.assertFalse((f.root / "repo-wt-feature-bypass").exists())
|
||||
|
||||
def test_strict_resume_rejects_target_advance(self):
|
||||
f = self.fixture
|
||||
receipt = self.capture(mode="delivery")
|
||||
head = f.git(self.wt, "rev-parse", "HEAD").stdout
|
||||
f.advance_remote()
|
||||
rejected = self.resume(receipt, mode="delivery")
|
||||
self.assertNotEqual(rejected.returncode, 0)
|
||||
self.assertNotIn("[resumed]", rejected.stdout)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head)
|
||||
|
||||
def test_strict_resume_imports_current_ready_receipt(self):
|
||||
receipt = self.capture(mode="delivery")
|
||||
result = self.resume(receipt, mode="delivery")
|
||||
self.assertEqual(result.returncode, 0, result.stdout)
|
||||
|
||||
def test_resume_rejects_changed_source_head(self):
|
||||
receipt = self.capture()
|
||||
f = self.fixture
|
||||
(self.wt / "tracked").write_text("new task revision\n")
|
||||
f.git(self.wt, "commit", "-am", "Change source after precheck")
|
||||
before = self.card.read_bytes()
|
||||
result = self.resume(receipt)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertEqual(self.card.read_bytes(), before)
|
||||
|
||||
def test_resume_rejects_another_worktree_with_same_head(self):
|
||||
f = self.fixture
|
||||
other = f.root / "other-worktree"
|
||||
r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
|
||||
other_card = self.wt / ".task/feature-other.md"
|
||||
other_card.write_text(
|
||||
"# Other task\n任务主记录:fixture;状态:进行中;owner:Lifecycle Test\n"
|
||||
f"- 权威仓:fixture;目标分支:main;R0(initial_base_sha)= {r0}\n"
|
||||
f"- 工作区:{other}(worktree);分支:feature/other\n")
|
||||
f.git(self.wt, "add", ".task/feature-other.md")
|
||||
f.git(self.wt, "commit", "-m", "Register second fixture task")
|
||||
f.git(f.repo, "worktree", "add", "-b", "feature/other", str(other), "HEAD")
|
||||
# The main checkout has an older HEAD; align this owned fixture branch to the task HEAD.
|
||||
f.git(other, "merge", "--ff-only", "feature/test")
|
||||
receipt = self.capture(mode="delivery")
|
||||
self.assertEqual(f.git(other, "rev-parse", "HEAD").stdout,
|
||||
f.git(self.wt, "rev-parse", "HEAD").stdout)
|
||||
result = self.resume(receipt, mode="delivery", cwd=other)
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
|
||||
def test_unknown_validation_purpose_is_rejected(self):
|
||||
receipt = self.capture(mode="delivery")
|
||||
f = self.fixture
|
||||
result = f.run_cmd(self.wt, "bash", "scripts/evidence.sh", check=False,
|
||||
env=f.env | {"VALIDATE_RECEIPT": str(receipt),
|
||||
"VALIDATE_RECEIPT_PURPOSE": "ignore"})
|
||||
self.assertNotEqual(result.returncode, 0, result.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user