diff --git a/.task/archive/feature-norm-remediation-u2.md b/.task/archive/feature-norm-remediation-u2.md new file mode 100644 index 00000000..d49aa673 --- /dev/null +++ b/.task/archive/feature-norm-remediation-u2.md @@ -0,0 +1,302 @@ +# feature/norm-remediation-u2 任务卡 + +任务主记录:PR #618 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/618;状态:已完成(合入前闭合);owner:weichen + +- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= b5e23daed2f0ea46261979de1d62e4de0cfa88f9 +- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u2(worktree);分支:feature/norm-remediation-u2 +- 分支类型:child;initiative_id:feature-norm-remediation;root_target:main;immediate_target:feature/norm-remediation;accepting_requirements:false;parent_branch:feature/norm-remediation;parent_head_sha:01d9f50ee8aba1c40cb9b1769598698c91c30c4b;parent_pr:无;parent_source:origin +- 需求分流:免(父 initiative 子任务登记已列出 feature/norm-remediation-u2;parent_head_sha=01d9f50ee8aba1c40cb9b1769598698c91c30c4b) + +- 授权与交付终点:用户于 2026-10-18 授权按规范整改计划实施 U2。仅允许安全既有任务的本地续作,并保留新任务启动及正式交付门禁。未授权扩展:改平台保护、host fallback、worker 权限、凭据轮换或其他 U-ID。 +- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进) +- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md) +- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入) +- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位) + +## 目标与非目标 + +目标:当远端暂时不可用或落后时,已有且仍获授权的任务只有在身份、记录基线、本地工作区和请求范围均可核验时,才能进入 `LOCAL_READY` 继续有界本地工作;本地收据不得被解释为远端新鲜或正式交付证据。 + +范围:`scripts/task.sh`、`scripts/evidence.py`、`policy/governance.md`、`tests/test_task.py`、`tests/test_task_recovery.py`、`tests/test_evidence.py`。测试可按现有代码模式作必要的最小补充,但不得改动 `task-pr-check`、`task-sync`、CI 或平台准入语义。 + +硬停止:关闭/放弃任务、授权或身份缺失/冲突、请求超范围、R0 本地对象缺失、dirty/共享工作区状态不安全、活动 Git 操作、未知外部副作用或未对账状态均拒绝续作。 + +验收:有效既有任务可在远端失败时继续本地工作;新任务仍需新鲜远端 triage;`LOCAL_READY` 不能成为 `READY`、`REPLAY`、`GATE` 或 merge check 的依据;远端恢复后 Review/merge 仍需同步权威当前目标。 + +## Agent 实施收据 + +路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。 + +| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 | +|---|---|---|---|---|---|---|---|---|---|---| +| U2 original | local recovery behavior | caefcb72 | implementation | cursor, agy, pi | 1 | cursor-agent-stable / auto | blocked | none | 0 | /tmp/norm-remediation-u2-receipt.json | +| U2 test-only | offline local precheck regression | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | 30a582372b4c | 0 | /tmp/norm-remediation-u2-offline-test-receipt.json | +| U2 task precheck | local precheck fallback | caefcb72 | implementation | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | d6df427e39e2 | 0 | /tmp/norm-remediation-u2-precheck-impl-receipt.json | +| U2 local receipt | resume-local receipt validation | caefcb72 | implementation | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 210bb031d4ac | 0 | /tmp/norm-remediation-u2-evidence-receipt-2.json | +| U2 missing-ref test | resume-local with missing tracking ref | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | dcbd36656bb3 | 0 | /tmp/norm-remediation-u2-no-tracking-test-receipt.json | +| U2 missing-ref fix | local receipt with missing tracking ref | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | a43aa171ea7f | 0 | /tmp/norm-remediation-u2-no-tracking-fix-receipt.json | +| U2 offline-capture test | capture LOCAL_READY without tracking ref | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | eb6be71cfc34 | 0 | /tmp/norm-remediation-u2-no-tracking-capture-test-receipt.json | +| U2 offline-capture fix | capture and resume without tracking ref | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | d2986f1f5fee | 0 | /tmp/norm-remediation-u2-offline-capture-fix-receipt.json | +| U2 offline-recovery docs | clarify missing-ref local recovery | caefcb72 | implementation | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 74e4b06e5e8b | 0 | /tmp/norm-remediation-u2-offline-doc-receipt.json | +| U2 review regression tests | closed task, reachable rewind, reappearing target | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | d5b8e74c4306 | 0 | /tmp/norm-remediation-u2-review-fix-tests-receipt.json | +| U2 review fixes | reject closed task and known target rewind/divergence | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 05a9a3bc89b6 | 0 | /tmp/norm-remediation-u2-review-fixes-receipt.json | +| U2 final edge tests | target divergence and receipt target branch mismatch | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | 2e0da0dadb73 | 0 | /tmp/norm-remediation-u2-final-edge-tests-receipt.json | +| U2 uncomparable-target test | reachable advertised SHA missing locally | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | 95d17188585c | 0 | /tmp/norm-remediation-u2-uncomparable-target-test-receipt.json | +| U2 target-history fix | block known but locally uncomparable history | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 9c0e76570832 | 0 | /tmp/norm-remediation-u2-uncomparable-target-fix-receipt.json | +| U2 no-cache target tests | target reachable/missing or forward with no tracking ref | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | fbb2aacb71ba | 0 | /tmp/norm-remediation-u2-no-cache-history-tests-receipt.json | +| U2 offline resume tests | standalone/child local resume during origin outage | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | 8d8585afec66 | 0 | /tmp/norm-remediation-u2-offline-resume-tests-receipt.json | +| U2 offline resume fix | local-only receipt resume with target history and parent R0 checks | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | f81326bd0836 | 0 | /tmp/norm-remediation-u2-offline-resume-fix-receipt.json | +| U2 no-cache R0 fix | compare reachable targets to registered R0 without cached ref | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 1ff6444d3352 | 0 | /tmp/norm-remediation-u2-precheck-r0-fix-receipt.json | +| U2 reachable-missing-parent test | reject known-absent remote child target | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | 5b5537aa0fc4 | 0 | /tmp/norm-remediation-u2-child-missing-parent-test-receipt-2.json | +| U2 reachable-missing-parent fix | local child resume only falls back on remote outage | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 105901595805 | 0 | /tmp/norm-remediation-u2-child-missing-parent-fix-receipt.json | +| U2 offline resume tests | root/child receipt import during origin outage | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | 8d8585afec66 | 0 | /tmp/norm-remediation-u2-offline-resume-tests-receipt.json | +| U2 offline resume fix | local resume on R0/local registered parent | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | f81326bd0836 | 0 | /tmp/norm-remediation-u2-offline-resume-fix-receipt.json | +| U2 review-proof tests | no-ref paths, auth failure, and parent identity | caefcb72 | test | pi, agy, cursor | 1 | pi / MiniMax-M3 | success | bc304111a47f | 0 | /tmp/norm-remediation-u2-final-review-tests-receipt.json | +| U2 final offline guards | missing cache, auth failures, and parent SHA checks | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 471948c59cfa | 0 | /tmp/norm-remediation-u2-final-guards-receipt.json | +| U2 precheck R0 implementation | compare remote advertisement without cached tracking ref | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 1ff6444d3352 | 0 | /tmp/norm-remediation-u2-precheck-r0-fix-receipt.json | +| U2 cached-target outage R0 regression/fix | reject cached target known to diverge from registered R0 during origin outage | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | a06209ff0a2b | 0 | /tmp/norm-u2-cached-target-outage-fix-2.json | +| U2 cached-target independent review | independent review of exact implementation diff | caefcb72 | review | pi, antigravity, cursor, codex | 1 | pi / MiniMax-M3 | failed-unknown | none | 0 | /tmp/norm-u2-cached-target-outage-review.json | + +## 同步记录 + +| 节点 | sync_base_sha | tested_head_sha | checked_at | +|---|---|---|---| +| 启动 | b5e23daed2f0ea46261979de1d62e4de0cfa88f9 | 01d9f50ee8aba1c40cb9b1769598698c91c30c4b | 2026-10-18T09:51:35Z | + +## 证据(Evidence) + + +| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) | +|---|---|---|---|---|---| +| 2026-10-18T11:24Z | `python3 -m unittest tests.test_evidence tests.test_task_recovery` | 210bb031d4acc5b9bde6cde66e7fa0f735bcf9f8 | 0 | SELF | d196e73da7f33421 | +| 2026-10-18T12:07Z | `python3 -m unittest tests.test_evidence tests.test_task_recovery` | d2986f1f5fee0e0ad151fd813b9306873131c805 | 0 | SELF | 85197f351f8458a8 | +| 2026-10-18T12:03Z | `python3 -m unittest tests.test_task tests.test_task_recovery tests.test_evidence` | d2986f1f5fee0e0ad151fd813b9306873131c805 | 1 | SELF | 9d19d33489e15d4d | +| 2026-10-18T12:42Z | `python3 -m unittest tests.test_evidence tests.test_task_recovery` | 05a9a3bc89b65eebd92cac6f82c3e4f28e13ab71 | 0 | SELF | 2d19085b0725fc46 | +| 2026-10-18T12:43Z | `make test-governance` | 05a9a3bc89b65eebd92cac6f82c3e4f28e13ab71 | 0 | SELF | 018194c0a804d1b9 | +| 2026-10-18T13:01Z | `make test-governance` | 2e0da0dadb73db1b6a70f8545dbfd5df3a8e4d00 | 0 | SELF | a44db826c4be80fb | +| 2026-10-18T13:40Z | `bash -n scripts/task.sh && make test-governance` | 9c0e7657083259759f5bd1bed6921471411dba2c | 0 | SELF | 872bbc3437ae83e3 | +| 2026-10-18T14:41Z | `python3 -m unittest tests.test_evidence tests.test_task_recovery` | 1ff6444d3352c7e13c2b75cefe7d65c631181454 | 0 | SELF | bf12f030894e1530 | +| 2026-10-18T14:40Z | `bash -n scripts/task.sh && make test-governance` | 1ff6444d3352c7e13c2b75cefe7d65c631181454 | 0 | SELF | 8e81c5353849fbd5 | +| 2026-10-18T15:47Z | `python3 -m unittest tests.test_evidence tests.test_task_recovery` | 471948c59cfabff36c018f219998139fea3757fe | 0 | SELF | 7620bd82fed319e7 | +| 2026-10-18T15:48Z | `bash -n scripts/task.sh && make test-governance` | 471948c59cfabff36c018f219998139fea3757fe | 0 | SELF | 5a3346fd73b80243 | + +## 结果与验证 + +- 结果:U2 允许既有安全任务在真正远端不可用或目标跟踪 ref 缺失时继续有界本地工作;闭合任务和已证实的远端 target 回退/分叉均拒绝 `LOCAL_READY`。新任务和正式交付仍要求严格 READY/权威同步。 +- 验证:host `python3 -m unittest tests.test_evidence tests.test_task_recovery` 79/79 通过(SELF,HEAD `1ff6444d3352c7e13c2b75cefe7d65c631181454`),覆盖 root/child 离线 task-resume 与严格 delivery 拒绝;host `bash -n scripts/task.sh && make test-governance` 490 项通过、18 项 skip(SELF,同 HEAD)。不含后续主脚本改动的 transport。local precheck no-cache 的 2 个红灯回归已由紧随其后的 `1ff6444d` 修复,三条 no-cache 状态单测 3/3 通过;最终候选完整测试仍待重跑。 +- Review 发现与闭合:正确性 reviewer 发现闭合任务仍可在 precheck 收到 LOCAL_READY、以及可达但不可比较的 target 仍被当作离线;可靠性 reviewer 发现缓存 target 回退/分叉问题。已分别加入 test-first 回归并修复。测试 reviewer 建议的目标 ref 重现、receipt target branch 错配、远端分叉和不可比较 target 场景均已覆盖;项目规范/可维护性审阅无 finding。旧 CI run 3884 曾因变更外 Go 测试 `TestToolCallDeltaBeforeExecution` 失败,后续 `b492bea...` CI 通过;当前最终 head 的 GATE 与新候选完整 Review 待执行。 +- 执行偏差:原 U2 Cursor attempt 对 `tests/test_task_recovery.py` 的两个 edit 调用重叠约 449 ms,并被 fail-stop;新的 evidence 单元明确要求串行编辑,但 worker 仍在同一批次向 `scripts/evidence.py` 发出两次编辑调用(未重叠,最终 diff 已审阅且验证通过)。不能据此声称提示已强制串行;后续规范计划须区分提示、可观测证据与工具级并发防护。 +- 新增执行偏差:修复闭合任务与目标回退时,worker 在同一批次向 `scripts/evidence.py` 和 `scripts/task.sh` 发出两次互不重叠编辑,违反了任务包的串行编辑要求;最终 diff 已逐项审阅,定向与完整治理测试均通过,但该结果不能证明提示具备工具级强制力。 +- Review 路由记录:第一次 PR 对抗性 cross-model job 因 orchestrator 使用了不匹配的约束文件名,在 provider egress 前跳过,未把代码发送给外部模型;job 目录已清理。该审阅不计入 Review 覆盖,最终 head 使用本地 adversarial reviewer。 +- 后续 Review 补充(覆盖前述状态):关闭任务 precheck、reachable-but-uncomparable/缺失 target、无 tracking ref 时需查询 advertised target、以及离线 `cmd_resume` 的 fetch-first 缺口,均已补 test-first 回归与修复。root 离线 resume 使用登记 R0;child resume 仅使用已登记、包含 `parent_head_sha` 的本地 parent 作为局部参考,delivery 仍 fetch-first。最终候选完整 Review 与 exact-head CI 待完成。 +- 最新 child 边界修复:origin 可达但已登记 parent ref 缺失时,local resume 不再回退本地 parent;origin 确实不可查询时仍可使用本地 parent,并验证 `parent_head_sha`。 +- 最新代码验证:host `tests.test_evidence tests.test_task_recovery` 80/80 通过,`bash -n scripts/task.sh && make test-governance` 491 项通过、18 项 skip,测试对象为 `105901595805cc5fa5ce19f1a6d48317b950d6af`。此前 CI green head 尚早于这些离线恢复修复,须以最终 PR head 重跑 GATE。 +- 最新验证覆盖 80 个 evidence/recovery 测试与 491 项治理测试(18 项 skip),以上命令都成功;测试对象为当前实现代码 `105901595805cc5fa5ce19f1a6d48317b950d6af`。若后续只改任务卡,代码测试可复用但 CI head 会变,须在最终提交 head 重新等待 GATE。 +- child 父目标补充:若 origin 可达但已登记 parent branch 不存在,local resume 返回 REMOTE_UNVERIFIED,不再使用本地 parent;只有远端确实不可查询时才回退本地 parent,并仍核验 `parent_head_sha`。delivery 仍保持 fetch-first。 +- 最终边界修复及验证:`target_history` 仅将已识别的 DNS/连接类传输错误视为 outage;认证/权限/未知错误不再降级。无 tracking ref 时 precheck 以 active task R0 比较 advertised target;reachable target/parent 缺失或不可比均拒绝。child local resume 要求有效且本地可用的 `parent_head_sha`。去掉了无 tracking-ref 时会触发 strict-shell 退出的 merged-branch 查询。 +- 最终安全复审阻塞修复:当 origin 传输不可达但本地缓存 `origin/` 已知分叉于登记 R0 时,`target_history` 现在返回 `rewound`;缓存存在但 ancestry 无法比较时返回 `uncomparable`,仅兼容缓存或无缓存才允许继续判断。新增回归覆盖 local precheck 拒绝 `LOCAL_READY`、resume 不导入 receipt,且 HEAD、卡片和 receipt 保持不变。修复经 agent-exec transport `a06209ff0a2b23fb750528a4bec4e7e64b3f2d6c` 集成。 +- 修复验证(SELF,HEAD `a06209ff0a2b23fb750528a4bec4e7e64b3f2d6c`):`python3 -m unittest tests.test_task_recovery tests.test_evidence` 84/84 通过;`bash -n scripts/task.sh && make test-governance` 495 项通过、18 项 skip。对应完整脱敏收据由 `IMPORT_RECEIPT` 附于本卡。 +- Review:PI 独立 Review attempt `a44c5c3e758a08f3ebe5d8a59bd241fd` 以 `failed-unknown` 结束,未形成可核验结论且未产生改动;当前 host 对 `398d12141c84453fb6db3dba2eaf9a208342e29f..a06209ff0a2b23fb750528a4bec4e7e64b3f2d6c` 的实际 diff 进行独立代码审查,未发现阻塞问题。旧 head `398d12141c84453fb6db3dba2eaf9a208342e29f` 的 CI run 3959 `ci / test` 失败;原因尚未从旧 run 日志确认,修复 head 的 exact CI 尚待执行。 +- 最终已测生产代码 head `471948c59cfabff36c018f219998139fea3757fe`:host evidence+recovery 83/83 通过;`bash -n scripts/task.sh && make test-governance` 494 项通过、18 项 skip。随后 `105901595805cc5fa5ce19f1a6d48317b950d6af` 又通过 targeted unreachable-vs-reachable missing-parent 对照测试;其后未再改生产逻辑。exact final-head CI 与当前候选 Review 尚待重跑。 +- 离线 resume 已端到端验证:root 与 child 任务在捕获有效 LOCAL_READY 收据后、origin 不可达时可用 `TASK_WORK_MODE=local make task-resume` 导入;child 仅使用本地已登记 parent 并核对 `parent_head_sha`。同一环境下 delivery resume 仍以 REMOTE_UNVERIFIED 拒绝。 +- 验收补充:对照计划 U2 的“remote-tracking ref 不存在但 R0 对象本地可用”场景,host Review 发现首个修复只覆盖 receipt 捕获后 ref 消失,未覆盖离线 precheck 捕获时 ref 已缺失;随后新增 test-first 回归覆盖两种时序,并修复 `--local-context` 与 `resume-local` 的有界验证。`start` 与 `resume-delivery` 继续要求当前权威目标。 +- 影响与剩余项:原失败 receipt `/tmp/norm-remediation-u2-receipt.json`,run_id `af20482c3e24cc32f3873d1451043481`;其 worktree 保留为诊断证据。该 run `blocked`、`transport_ref=null`、`host_fallback_authorizations=0`。Development-Manager PR #26 已合入 `b178dec`;Dev gitlink adoption 仍待 initiative 集成,不在本 U2 单元内。 + +## 合入与清理 + +- Review 记录:host Review 完成(base `398d12141c84453fb6db3dba2eaf9a208342e29f`,head `a06209ff0a2b23fb750528a4bec4e7e64b3f2d6c`;结论:无阻塞 finding;独立 PI attempt failed-unknown,不计为通过) +- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测) +- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果) +- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验) +- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置) + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T09:51:00.301997+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev", + "before_head": "b5e23daed2f0ea46261979de1d62e4de0cfa88f9", + "after_head": "b5e23daed2f0ea46261979de1d62e4de0cfa88f9", + "branch": "main", + "target_branch": "main", + "target_head": "b5e23daed2f0ea46261979de1d62e4de0cfa88f9", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'", + "[repo] branch=main head=b5e23daed2", + "[remote] origin/main=b5e23daed2 ahead=0 behind=0", + "[tree] clean submodules_drift=0 uninit=3", + "[warn] 3 个子模块未初始化——构建/测试前需物化(不阻断写入)", + "[worktrees] linked=11", + "[recycle] merged un-recycled branches: feature/layered-python-env", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] b5e23daed2(完整:b5e23daed2f0ea46261979de1d62e4de0cfa88f9)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'" + ], + "output_sha256": "d33d43216a047c642360e2afa99b4d72781f81698ca933e8687cc8127afde332", + "receipt_sha256": "57db39b3f66caf60040427f50f45423bb2be546f4930be3696b8218f1c29f148", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/57db39b3f66caf60040427f50f45423bb2be546f4930be3696b8218f1c29f148.json" +} +``` +| 同步(记录前对象) | 74bd05a876136ed8506332c76d535a6eb6356433 | f65d0169f61addfc0b2a21ef3813f38b55deb687 | 2026-10-18T10:58:22Z | + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T11:14:41.060001+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u2", + "before_head": "d6df427e39e2dd8f57d51d04318dedffff96523a", + "after_head": "d6df427e39e2dd8f57d51d04318dedffff96523a", + "branch": "feature/norm-remediation-u2", + "target_branch": "main", + "target_head": "ba81d638a6c845d0acde6896f4001bed096ba26c", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u2'", + "[repo] branch=feature/norm-remediation-u2 head=d6df427e39", + "[remote] origin/main=ba81d638a6 ahead=10 behind=12", + "[tree] clean submodules_drift=0 uninit=0", + "[worktrees] linked=11", + "[recycle] merged un-recycled branches: feature/layered-python-env", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[target] ba81d638a6c845d0acde6896f4001bed096ba26c(observed; task initial baseline is reported separately)", + "[task-state] registered=/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u2 initial_base=b5e23daed2f0ea46261979de1d62e4de0cfa88f9", + "[verdict] LOCAL_READY — existing task may continue authorized local work; final delivery still requires latest-target sync and checks", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u2'" + ], + "output_sha256": "9875968d327058ec22a07453a7c748f5c7b3557fdc8d05355b907b8458d8fa6d", + "receipt_sha256": "00e543451c4435f6669c74d51a6a678db2cd40a0498a56ae377447044adce7f8", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/00e543451c4435f6669c74d51a6a678db2cd40a0498a56ae377447044adce7f8.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T11:39:55.756192+00:00", + "command": "python3 -m unittest tests.test_evidence tests.test_task_recovery", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u2", + "before_head": "1e26b1cccdcfe8ac83a004915cad591d27eed91c", + "after_head": "1e26b1cccdcfe8ac83a004915cad591d27eed91c", + "branch": "feature/norm-remediation-u2", + "target_branch": "main", + "target_head": "ba81d638a6c845d0acde6896f4001bed096ba26c", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "..................................................................", + "----------------------------------------------------------------------", + "Ran 66 tests in 18.749s", + "", + "OK" + ], + "output_sha256": "ed93f5670caa3576929bab841735c1ad873975985fcf1a6575ce88bad5510aa7", + "receipt_sha256": "d08508edc20bdd35b340355c5b1a9295ddce5f866d1706f34afc8f76dcf1d939", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/d08508edc20bdd35b340355c5b1a9295ddce5f866d1706f34afc8f76dcf1d939.json" +} +``` + +## 闭合记录(合入前折叠) + +- 本卡在 PR #618(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/618)合入前闭合并归档,随该 PR 一同合入。 +- 该 PR 的合并提交即本任务的落地记录。 +- 合入后无需回填,也无需单独的闭合 PR。 + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T16:13:57.369094+00:00", + "command": "python3 -m unittest tests.test_task_recovery tests.test_evidence", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u2", + "before_head": "a06209ff0a2b23fb750528a4bec4e7e64b3f2d6c", + "after_head": "a06209ff0a2b23fb750528a4bec4e7e64b3f2d6c", + "branch": "feature/norm-remediation-u2", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "....................................................................................", + "----------------------------------------------------------------------", + "Ran 84 tests in 31.295s", + "", + "OK" + ], + "output_sha256": "7cf18f380130a11f0494ce26e3e331cb9c6532d790a869414b68b3cb5a8161f1", + "receipt_sha256": "4054bd2917a76495eec89a929dea0929ed7ab7af633ca99424950baf300a56b4", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/4054bd2917a76495eec89a929dea0929ed7ab7af633ca99424950baf300a56b4.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T16:14:30.451262+00:00", + "command": "bash -n scripts/task.sh && make test-governance", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u2", + "before_head": "a06209ff0a2b23fb750528a4bec4e7e64b3f2d6c", + "after_head": "a06209ff0a2b23fb750528a4bec4e7e64b3f2d6c", + "branch": "feature/norm-remediation-u2", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "test_feature_missing_parent_is_allowed (test_write_guard.WriteGuardTests.test_feature_missing_parent_is_allowed) ... ok", + "test_invalid_json_is_denied (test_write_guard.WriteGuardTests.test_invalid_json_is_denied) ... ok", + "test_missing_parent_on_main_is_denied (test_write_guard.WriteGuardTests.test_missing_parent_on_main_is_denied) ... ok", + "test_missing_unknown_and_malformed_write_input_are_denied (test_write_guard.WriteGuardTests.test_missing_unknown_and_malformed_write_input_are_denied) ... ok", + "test_non_git_destination_is_explicitly_outside_scope (test_write_guard.WriteGuardTests.test_non_git_destination_is_explicitly_outside_scope) ... ok", + "test_nonwrite_tools_are_outside_scope (test_write_guard.WriteGuardTests.test_nonwrite_tools_are_outside_scope) ... ok", + "test_relative_path_uses_hook_cwd_not_process_cwd (test_write_guard.WriteGuardTests.test_relative_path_uses_hook_cwd_not_process_cwd) ... ok", + "test_symlink_into_main_with_missing_parents_is_denied (test_write_guard.WriteGuardTests.test_symlink_into_main_with_missing_parents_is_denied) ... ok", + "test_valid_multi_file_patch_on_feature_is_allowed (test_write_guard.WriteGuardTests.test_valid_multi_file_patch_on_feature_is_allowed) ... ok", + "", + "----------------------------------------------------------------------", + "Ran 495 tests in 364.208s", + "", + "OK (skipped=18)", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u2'" + ], + "output_sha256": "3edcb54df736b840127f6b466cac1f4046632e81f38fa02fe96cdb95cee0b87f", + "receipt_sha256": "ae37da5e99243a166bafa46ff6bf95cb704de194338a669d929f409cb540768f", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/ae37da5e99243a166bafa46ff6bf95cb704de194338a669d929f409cb540768f.json" +} +``` diff --git a/.task/archive/feature-norm-remediation-u3.md b/.task/archive/feature-norm-remediation-u3.md new file mode 100644 index 00000000..b2e96f77 --- /dev/null +++ b/.task/archive/feature-norm-remediation-u3.md @@ -0,0 +1,256 @@ +# feature/norm-remediation-u3 任务卡 + +任务主记录:PR #620 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/620;状态:已完成(合入前闭合);owner:weichen + +- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:feature/norm-remediation;R0(initial_base_sha)= fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b +- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3(worktree);分支:feature/norm-remediation-u3 +- 分支类型:child;initiative_id:feature-norm-remediation;root_target:main;immediate_target:feature/norm-remediation;accepting_requirements:false;parent_branch:feature/norm-remediation;parent_head_sha:fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b;parent_pr:https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/616;parent_source:origin +- 需求分流:免(父 initiative 子任务登记已列出 feature/norm-remediation-u3;parent_head_sha=fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b) + +- 授权与交付终点:用户于 2026-10-18 授权按规范整改计划实施 U3,并按本仓门禁将子任务合入 `feature/norm-remediation`。仅允许调整 Dev `policy/delivery.md` 的任务通道分类与拓扑选择规则;未授权修改脚本、分支命令/流程、CI/平台设置、其他 U-ID 或 Guidelines。 +- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进) +- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md) +- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入) +- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位) + +## 目标与非目标 + +目标:让修复/完善/扩展/新增类别决定优先级,而 standalone、child、initiative 拓扑由集成、依赖与验收需要决定。独立可验收工作(含小完善/新能力)可 standalone;多依赖单元、跨单元集成或共同最终验收需要 initiative;修复若属于 L1 范围则可作为 child,同时保留 fix/hotfix 优先级。JOIN 必须与 initiative 已记录目标/验收一致;远端发现不可核验仍不得判断 NEW 或建分支。 + +范围:`policy/delivery.md`「层级、任务通道与分级门禁」。非目标:不改脚本、任务字段、命令、分支模型、triage 事实来源、门禁/排队规则或平台行为。 + +计划:[U1-U7 规范整改计划](),U3。 + +## Agent 实施收据 + +路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。 + +| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 | +|---|---|---|---|---|---|---|---|---|---|---| +| norm-u3-task-channel-by-integration-need | `policy/delivery.md` task channel/topology policy | caefcb72 | implementation | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 98447e269cea | 0 | /tmp/norm-u3-task-channel-policy-receipt.json | +| norm-u3-independent-policy-review | fresh-session review of policy/delivery.md scenarios | caefcb72 | review | pi, antigravity, cursor, codex | 1 | pi / MiniMax-M3 | success | cb582d170cc4 | 0 | /tmp/norm-u3-independent-review-receipt.json | + +## 同步记录 + +| 节点 | sync_base_sha | tested_head_sha | checked_at | +|---|---|---|---| +| 启动 | fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b | fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b | 2026-10-18T16:45:52Z | + +## 证据(Evidence) + + +| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) | +|---|---|---|---|---|---| + +## 结果与验证 + +- 结果:U3 将类别与集成拓扑分开:修复类别保留优先级及 hotfix 插队;独立可验收的修复、完善和新能力可走 standalone;多依赖单元、跨单元集成或共同最终验收才建立 initiative;符合在途 L1 目标与验收的工作可 JOIN/child,远端不可核验仍拒绝 NEW。 +- 验证:`git diff --check && make test-affected` 通过;受影响目标为 `test-worktree` 与 `test-governance`,其中治理测试 495 项通过、18 项 skip(SELF,HEAD `98447e269ceaf1982405580d4ce5549d47b165c4`)。worker 对四个验收场景逐项核对;host 复核实际 diff 与原有分级表/triage 规则,无矛盾。SELF 收据已写入本卡。 +- 影响与剩余项: + +## 合入与清理 + +- Review 记录:host Review 完成(base `d3223f5ec13bb5456ccb96dd136374f6cce94253`,head `98447e269ceaf1982405580d4ce5549d47b165c4`;目标优先级与集成拓扑分离、修复可 standalone 或 child、JOIN/NEW/REMOTE_UNVERIFIED 条件和现有门禁均符合 U3;无阻塞 finding) + - 独立 Review(fresh session,单元 `norm-u3-independent-policy-review`,仅检视 `policy/delivery.md` 在 `d3223f5ec13bb5456ccb96dd136374f6cce94253..98447e269ceaf1982405580d4ce5549d47b165c4` 的 diff,未接触其它文件、未做 `git add`/`commit`/`push`/PR/合入/索引/配置改动):`policy/delivery.md` 净改动 +9/-8,集中于 standalone 定位(line 163)、任务通道两步判定(line 193–200)与分级门禁表(line 210)。 + - 场景 1(小完善/新能力仍可 standalone):通过。line 163 明确 standalone 涵盖「修复、完善与新能力」;line 196 再次声明「小完善与新能力同样适用」并把「完善、扩展、新增不设额外优先级」写死。 + - 场景 2(多依赖单元 + 共同最终验收 → L1,即便含修复):通过。line 197 L1 触发条件为「多个相互依赖的单元、跨单元集成或一个共同最终验收需要阶段集成与组合验证」,与「类别只决定优先级」正交;line 198 显式允许「工作(含修复)」作为在途 L1 的 child。 + - 场景 3(属于在途 L1 集成契约的修复保留优先级且不被强推 main):通过。line 193 保留 `fix/`/`hotfix/` 队列优先级与 #518 插队权;line 198 允许该类修复走 child;分级表把原「L1 或修复→main」改为「L1 或 standalone→main」(line 210),移除对修复的 main 强制通路。修复合入主干的快慢继续由类别决定,而非由拓扑决定。 + - 场景 4(远端不可核验时拒绝建分支):通过。line 168「不得得出 `NEW`」、line 170「不一致或远端不可核验时拒绝,不建分支」保持原样;line 198 任务通道节内再次写入「远端发现不可核验时不得得出 `NEW`、不建分支」,与既有分流契约一致且重复强化。 + - 跨节一致性:合并调度 line 363「standalone/修复 PR」、line 366「`fix/`、`hotfix/` 优先」与本 diff 共同表述一致;修复 PR 仍以 standalone 形式占用 #518 窗口并享有优先级,子任务合入(child → L1)按 line 207 表「L2→L1」走轻量门禁且不排窗口,符合「类别只决定优先级,拓扑只决定目标」的原则。 + - 残余非阻塞注意:line 363 措辞「standalone/修复 PR」在拓扑语境下存在轻微冗余(standalone 修复已由「standalone」覆盖),但当前未引发歧义、不阻塞 U3 验收,可作为后续治理 L1 增量整理。 + - 结论:**无阻塞 finding**;U3 验收四条场景均在 `policy/delivery.md` 文本中得到明确、可独立验证的支撑。未声明 CI 通过(按约束)。 +- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测) +- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果) +- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验) +- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置) + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T16:45:08.496457+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation", + "before_head": "fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b", + "after_head": "fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b", + "branch": "feature/norm-remediation", + "target_branch": "feature/norm-remediation", + "target_head": "fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'", + "[repo] branch=feature/norm-remediation head=fe0f1cd464", + "[remote] origin/feature/norm-remediation=fe0f1cd464 ahead=0 behind=0", + "[tree] clean submodules_drift=0 uninit=0", + "[worktrees] linked=7", + "[recycle] merged un-recycled branches: feature/layered-python-env feature/norm-remediation-u2", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] fe0f1cd464(完整:fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'" + ], + "output_sha256": "ee1240abc7866d6b272cf3c307a650b907dc7b36ae662335d8573d8c3dd7240c", + "receipt_sha256": "92b34a0bd5532f33b1598e25686504a91dab621b36f372e7e0d31c18addb4c3a", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/92b34a0bd5532f33b1598e25686504a91dab621b36f372e7e0d31c18addb4c3a.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T16:47:54.838263+00:00", + "command": "git diff --check && make test-affected", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3", + "before_head": "98447e269ceaf1982405580d4ce5549d47b165c4", + "after_head": "98447e269ceaf1982405580d4ce5549d47b165c4", + "branch": "feature/norm-remediation-u3", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "feature/norm-remediation", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "test_invalid_json_is_denied (test_write_guard.WriteGuardTests.test_invalid_json_is_denied) ... ok", + "test_missing_parent_on_main_is_denied (test_write_guard.WriteGuardTests.test_missing_parent_on_main_is_denied) ... ok", + "test_missing_unknown_and_malformed_write_input_are_denied (test_write_guard.WriteGuardTests.test_missing_unknown_and_malformed_write_input_are_denied) ... ok", + "test_non_git_destination_is_explicitly_outside_scope (test_write_guard.WriteGuardTests.test_non_git_destination_is_explicitly_outside_scope) ... ok", + "test_nonwrite_tools_are_outside_scope (test_write_guard.WriteGuardTests.test_nonwrite_tools_are_outside_scope) ... ok", + "test_relative_path_uses_hook_cwd_not_process_cwd (test_write_guard.WriteGuardTests.test_relative_path_uses_hook_cwd_not_process_cwd) ... ok", + "test_symlink_into_main_with_missing_parents_is_denied (test_write_guard.WriteGuardTests.test_symlink_into_main_with_missing_parents_is_denied) ... ok", + "test_valid_multi_file_patch_on_feature_is_allowed (test_write_guard.WriteGuardTests.test_valid_multi_file_patch_on_feature_is_allowed) ... ok", + "", + "----------------------------------------------------------------------", + "Ran 495 tests in 357.921s", + "", + "OK (skipped=18)", + "make[2]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3'", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3'" + ], + "output_sha256": "56d1d5d11197d500505b98bd63f64e1c4a3375eee0bbf51b5cb7f948d3de1e24", + "receipt_sha256": "f214edd4293ec312c8ef1e50b1761db12563fe8c7f19c73dc82a95003b06e28a", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/f214edd4293ec312c8ef1e50b1761db12563fe8c7f19c73dc82a95003b06e28a.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T16:55:49.532309+00:00", + "command": "make task-sync", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3", + "before_head": "f4a95e5698b6d31752f206f26094d31980a00f7e", + "after_head": "f4a95e5698b6d31752f206f26094d31980a00f7e", + "branch": "feature/norm-remediation-u3", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "feature/norm-remediation", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3'", + "[worktree] before-sync: recursive component checks passed", + "[worktree] before-update: recursive component checks passed", + "[worktree] materialize: recursive component checks passed", + "[worktree] after-sync: recursive component checks passed", + "[evidence] WARN JSON receipt line 59: recorded HEAD predates non-card changes ——关键检查须重跑并以 make evidence 重新写入", + "[synced] sync_base=fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b tested_head=f4a95e5698b6d31752f206f26094d31980a00f7e checked_at=2026-10-18T16:56:14Z changed=0 sync_target=feature/norm-remediation", + "[next] 跑正式验收(make test 等),证据写入任务卡", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3'" + ], + "output_sha256": "c2b5f39f8fc6cc8a30af9012b2eb46ced09d2ddbcab9ad490762a4bfc85a613d", + "receipt_sha256": "b42f1564d10e97928cdfd2f39fd6eda97e969f22bfbcff4668543a7c3be93dce", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/b42f1564d10e97928cdfd2f39fd6eda97e969f22bfbcff4668543a7c3be93dce.json" +} +``` + +## 闭合记录(合入前折叠) + +- 本卡在 PR #620(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/620)合入前闭合并归档,随该 PR 一同合入。 +- 该 PR 的合并提交即本任务的落地记录。 +- 合入后无需回填,也无需单独的闭合 PR。 + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:00:44.968802+00:00", + "command": "make task-close SELF=1", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3", + "before_head": "56d16ac458de980511da70f9dbeec4f0fbdab6e9", + "after_head": "1f34c35e446a3a93959fda4daf7bbdcab0819f81", + "branch": "feature/norm-remediation-u3", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "feature/norm-remediation", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3'", + "admit: ok", + "remote: ", + "remote: Create a new pull request for 'feature/norm-remediation-u3': ", + "remote: https://git-panel.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/new/feature/norm-remediation-u3 ", + "remote: ", + "[closed] .task/feature-norm-remediation-u3.md 已在 PR #620 合入前闭合并归档;合入后无需 task-close", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3'" + ], + "output_sha256": "656fc7c46263e0b49916551bc77638a287adaf06f42197540607c9ae0fb564a4", + "receipt_sha256": "183eb304dfa804b340e56d129108f413f5616aa653a3225d5f5ee58e505bc013", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/183eb304dfa804b340e56d129108f413f5616aa653a3225d5f5ee58e505bc013.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:03:30.573232+00:00", + "command": "make task-sync", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3", + "before_head": "532c7f3b6098d8071925afa48633b840b1148b69", + "after_head": "532c7f3b6098d8071925afa48633b840b1148b69", + "branch": "feature/norm-remediation-u3", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3'", + "[worktree] before-sync: recursive component checks passed", + "[worktree] before-update: recursive component checks passed", + "[worktree] materialize: recursive component checks passed", + "[worktree] after-sync: recursive component checks passed", + "[evidence] WARN JSON receipt line 68: recorded HEAD predates non-card changes ——关键检查须重跑并以 make evidence 重新写入", + "[synced] sync_base=fe0f1cd4649f11b0d855ec39b882b8ca37fffb6b tested_head=532c7f3b6098d8071925afa48633b840b1148b69 checked_at=2026-10-18T17:03:56Z changed=0 sync_target=feature/norm-remediation", + "[next] 跑正式验收(make test 等),证据写入任务卡", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u3'" + ], + "output_sha256": "13d0ce871c7520312e4e6b199dac9274b1fc6f65d26ab150e84d9b35a3305fd2", + "receipt_sha256": "ad5add14e897beee093e25645f0c30b280175676b0b7826ea6cb634b07b772df", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/ad5add14e897beee093e25645f0c30b280175676b0b7826ea6cb634b07b772df.json" +} +``` diff --git a/.task/archive/feature-norm-remediation-u4.md b/.task/archive/feature-norm-remediation-u4.md new file mode 100644 index 00000000..ffb61683 --- /dev/null +++ b/.task/archive/feature-norm-remediation-u4.md @@ -0,0 +1,261 @@ +# feature/norm-remediation-u4 任务卡 + +任务主记录:PR #621 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/621;状态:已完成(合入前闭合);owner:weichen + +- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= eef7e5e778cabcb6216f74b74213a3d7511f383b +- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u4(worktree);分支:feature/norm-remediation-u4 +- 分支类型:child;initiative_id:feature-norm-remediation;root_target:main;immediate_target:feature/norm-remediation;accepting_requirements:false;parent_branch:feature/norm-remediation;parent_head_sha:6cee4c5728f1c5e723e599986f1f0e4372ec3032;parent_pr:无;parent_source:origin +- 需求分流:免(父 initiative 子任务登记已列出 feature/norm-remediation-u4;parent_head_sha=6cee4c5728f1c5e723e599986f1f0e4372ec3032) + +- 授权与交付终点:按用户批准的 U1-U7 规范整改计划实施 U4,并完成 Review、测试、PR/CI 后合入 initiative。范围限 `policy/facts.md`、`scripts/facts-verify.sh`、`tests/test_facts_verify.py`;不接触真实凭据、不跑现场事实刷新、不修改平台或其他 U-ID。 +- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进) +- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md) +- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入) +- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位) + +## 目标与非目标 + +目标:F2 的验证路径不让展开后的 token 进入子进程 argv、输出或证据;F4 验证只刷新它能证明的事实,DNS/超时/泛化网络错误不能刷新 TLS 特定原因。复用已有 token-safe 请求模式,回归使用合成 HOME/IMC 凭据、桩客户端和临时表,验证正常/异常及 --update 日期语义。 + +计划:U1-U7 规范整改实施计划 U4(唯一正文位于 `Agent-System-Dev-wt-feature-norm-remediation-plan/docs/superpowers/plans/2026-10-18-0849-fix-development-norm-remediation-plan.md`)。实际 child 代码基线为 parent_head_sha `6cee4c5`;启动收据的 R0 为当时最新 root main 快照,二者不混同。 + +## Agent 实施收据 + +路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。 + +| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 | +|---|---|---|---|---|---|---|---|---|---|---| +| norm-u4-safe-credential-truthful-facts | facts policy/probes/offline tests | caefcb72 | implementation | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 7e0b0e4ff8c5 | 0 | /tmp/norm-u4-facts-receipt.json | +| norm-u4-credential-destination-repair | exact destination and redirect negative cases | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 39834ead7d5c | 0 | /tmp/norm-u4-credential-boundary-repair-receipt.json | +| norm-u4-security-correctness-review | independent fixed-diff review and offline replay | caefcb72 | review | pi, agy, cursor, codex | 1 | pi / minimax-cn/MiniMax-M3 | success | 25cdc6adc848 | 0 | /tmp/norm-u4-independent-review-receipt.json | + +## 同步记录 + +| 节点 | sync_base_sha | tested_head_sha | checked_at | +|---|---|---|---| +| 启动 | eef7e5e778cabcb6216f74b74213a3d7511f383b | 6cee4c5728f1c5e723e599986f1f0e4372ec3032 | 2026-10-18T17:17:00Z | + +## 证据(Evidence) + + +| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) | +|---|---|---|---|---|---| + +## 结果与验证 + +- 结果:F2 token 仅在进程内读取/发送,不进入 child argv/输出/收据;端点在读取凭据前逐字核对,不跟随 301/302/303/307/308 重定向。F4 将退役禁令与现场 TLS 现象分开,DNS/超时/连接错误/握手成功均不刷新 TLS 特定事实;历史日期明确不证明新判据。 +- 验证:host 在代码候选 `39834ead7d5cae48477621b5e68f737ae0082e65` 重放 8 项离线事实测试、shell syntax/diff 检查通过;`make test-governance` 执行 503 项(18 项 skip),无失败(SELF,完整收据经 evidence 导入)。独立 PI Review 同 head 重放 8 项并判无阻塞项。测试先红后绿证据在各 worker receipt;所有用例只使用合成 HOME/IMC 和客户端桩,不读真实凭据、不访问 Gitea。 +- 影响与剩余项:没有刷新任何真实 last_verified 日期;无部署、凭据轮换、平台或其他范围修改。F4 若需重新证明当前 TLS 现象应另行现场重验,不能沿用旧宽泛失败。 + +## 合入与清理 + +- Review 记录(独立 fresh CLI session,2026-10-18):PI / minimax-cn / MiniMax-M3;host 按 run `03b4578a94b710425f11bd74df523a20`、attempt `d0bfc2e77832` 核验新进程及独立 Review,执行器本地记录为 SELF,不冒充 CI/GATE 身份; + base/head=6cee4c5728f1c5e723e599986f1f0e4372ec3032..39834ead7d5cae48477621b5e68f737ae0082e65, + tested_head=39834ead7d5cae48477621b5e68f737ae0082e65(`git rev-parse HEAD` 已对账); + 离线 focused test `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_facts_verify -v` + → 8 tests in 4.608s OK(test_f2_pass_sends_home_token_in_process_and_leaks_nowhere / test_f2_missing_or_malformed_credentials_fail_without_echo / test_f2_rejected_or_failing_request_does_not_refresh_or_leak / test_f2_rejects_unregistered_destination_before_reading_credentials / test_f2_refuses_redirect_without_forwarding_authorization / test_f4_only_tls_handshake_failure_passes_and_refreshes / test_f4_helper_reports_each_condition_distinctly / test_registry_separates_retired_endpoint_policy_from_tls_observation); + bash -n scripts/facts-verify.sh 无语法错误。 + 验收逐项: + · HOME token 仅进程内经 Authorization 头发送,child argv/stderr/stdout/收据/登记表中不存在 HOME/IMC 真实凭据值:test_f2_pass 断言 http 拦截仅 1 次且 url=https://git-home.u1680832.nyat.app:30319/api/v1/user,authorization=token ;assert_no_secrets 覆盖子进程 argv、PATH 桩 curl/wget argv、stdout/stderr、收据 JSON、登记表; + · 凭据缺失/格式错/文件缺失三条 subTest 均 FAIL,http 拦截=[] 即未发请求,stderr 含 "ACCESS_TOKEN" 但不含 token 字面值,且 `--update` 不刷新 F2 日期(保持 OLD_DATE):test_f2_missing_or_malformed_credentials_fail_without_echo; + · URL 校验 11 个变体(含 http://、不同端口、userinfo、userinfo@、fragment、查询串、路径尾缀 /../、路径变体、尾随空格)全部 exit 6 且无 http 拦截,且 ACCESS_TOKEN 不在 stderr(端点先于凭据校验):test_f2_rejects_unregistered_destination_before_reading_credentials;脚本侧 `if sys.argv[2] != ENDPOINT: ... sys.exit(6)` 是逐字字符串等价,避开任何 URL 归一化; + · 301/302/303/307/308 五种全不转发 Authorization:`NoRedirect(HTTPRedirectHandler).redirect_request` 固定返回 None,stub 拦截 `[r["url"] for r in requests]==["https://git-home.u1680832.nyat.app:30319/api/v1/user"]` 无后续请求、收不到 redirect-target 主机名,且 `--update` 模式下 F2 仍是 FAIL 且未刷新日期; + · F4 仅 TLS 握手错误判 PASS,dns/timeout/refused/reset/tls-timeout/ok 全 FAIL 且不刷新日期:test_f4_only_tls_handshake_failure_passes_and_refreshes;probe `tls-handshake-fails` 出口 0/3/4/5/6 区分明确,F4 现有 2026-10-14 last_verified 在 `policy/facts.md` 被显式标注为「历史日期…不证明当前 TLS 层握手失败判据;经该判据独立重验前不得当作已核实」,与 F4 行禁用字符串「不得使用」(政策禁令,相对独立)分离(test_registry_separates_retired_endpoint_policy_from_tls_observation); + · 测试全离线:`socket.socket.connect/_ex = _deny` 拒绝未桩接的真实连接,`socket.create_connection`、`ssl.SSLContext.wrap_socket`、`urllib.request.HTTPSHandler/https_open / http_open` 全部经 sitecustomize 桩化;HOME 指向 tmpdir 不读真实 ~/.secrets;PATH 桩化 curl/wget 仅写 argv 日志;全程未触达 Gitea 或旧端点;本评审运行无任何 `--update` 写回真实 `policy/facts.md`。 + · reviewer profile/model 以独立运行事件为依据;Shell=bash,Bash 语法 `bash -n` 通过;Python=3.x,`task_triage.TOKEN_RE`、`task_triage.secrets_file_token()`、`task_triage.http_get_json()`、`task_triage.Stop` 已直接阅读验证其 token 仅入 Authorization 头、异常消息仅含 `type(exc).__name__` 不含凭据。 + 无阻塞项;不修正实现;不在本评审做合入/CI/PR 操作(合规范围内仅写一条 review 记录)。 +- 冲突记录:最新 initiative `c6fe5ad` 已经 task-sync 合入;仅父任务元数据变化,无冲突;实现/测试/事实文件相对已审已测代码候选 `39834ead` 的 diff 为空,既有直接验证仍适用。 +- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果) +- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验) +- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置) + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:16:04.326553+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev", + "before_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "after_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "branch": "main", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'", + "[repo] branch=main head=eef7e5e778", + "[remote] origin/main=eef7e5e778 ahead=0 behind=0", + "[tree] clean submodules_drift=0 uninit=12", + "[warn] 12 个子模块未初始化——构建/测试前需物化(不阻断写入)", + "[worktrees] linked=8", + "[recycle] merged un-recycled branches: feature/layered-python-env", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] eef7e5e778(完整:eef7e5e778cabcb6216f74b74213a3d7511f383b)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'" + ], + "output_sha256": "b1b224c850fc8ccfd988d17caaf1f716e2c2e07ab50d9c5387de9ed292aa9ad0", + "receipt_sha256": "818aca5afe7aca05cc3436a020562d87b532a92ebdee7a828b83a39b417e003c", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/818aca5afe7aca05cc3436a020562d87b532a92ebdee7a828b83a39b417e003c.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:22:01.884792+00:00", + "command": "python3 -m unittest tests.test_facts_verify && bash -n scripts/facts-verify.sh && git diff --check", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u4", + "before_head": "7e0b0e4ff8c54e9095d86be133965ab1828a8e3d", + "after_head": "7e0b0e4ff8c54e9095d86be133965ab1828a8e3d", + "branch": "feature/norm-remediation-u4", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "......", + "----------------------------------------------------------------------", + "Ran 6 tests in 3.532s", + "", + "OK" + ], + "output_sha256": "0fff5c17193d077cf601d6c73735a79a0074e48107773120f33b8f14d8e71409", + "receipt_sha256": "7101fa8f0bf09c8e8ca06be96351cc1f81f4a13e69363b427a61b49b37ad9941", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/7101fa8f0bf09c8e8ca06be96351cc1f81f4a13e69363b427a61b49b37ad9941.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:29:35.268634+00:00", + "command": "python3 -m unittest tests.test_facts_verify && bash -n scripts/facts-verify.sh && git diff --check", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u4", + "before_head": "39834ead7d5cae48477621b5e68f737ae0082e65", + "after_head": "39834ead7d5cae48477621b5e68f737ae0082e65", + "branch": "feature/norm-remediation-u4", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "........", + "----------------------------------------------------------------------", + "Ran 8 tests in 4.333s", + "", + "OK" + ], + "output_sha256": "c5b777791a060b3d9c4ef8c01df28662e588ac3c05ccd2de96068357ba5438d3", + "receipt_sha256": "0f6b6ec11bdbe651b8f6f73c82501755916e9f0d495af8cd6c8ddc0dc6e408e1", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/0f6b6ec11bdbe651b8f6f73c82501755916e9f0d495af8cd6c8ddc0dc6e408e1.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:30:04.489780+00:00", + "command": "make test-governance", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u4", + "before_head": "39834ead7d5cae48477621b5e68f737ae0082e65", + "after_head": "39834ead7d5cae48477621b5e68f737ae0082e65", + "branch": "feature/norm-remediation-u4", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "test_feature_missing_parent_is_allowed (test_write_guard.WriteGuardTests.test_feature_missing_parent_is_allowed) ... ok", + "test_invalid_json_is_denied (test_write_guard.WriteGuardTests.test_invalid_json_is_denied) ... ok", + "test_missing_parent_on_main_is_denied (test_write_guard.WriteGuardTests.test_missing_parent_on_main_is_denied) ... ok", + "test_missing_unknown_and_malformed_write_input_are_denied (test_write_guard.WriteGuardTests.test_missing_unknown_and_malformed_write_input_are_denied) ... ok", + "test_non_git_destination_is_explicitly_outside_scope (test_write_guard.WriteGuardTests.test_non_git_destination_is_explicitly_outside_scope) ... ok", + "test_nonwrite_tools_are_outside_scope (test_write_guard.WriteGuardTests.test_nonwrite_tools_are_outside_scope) ... ok", + "test_relative_path_uses_hook_cwd_not_process_cwd (test_write_guard.WriteGuardTests.test_relative_path_uses_hook_cwd_not_process_cwd) ... ok", + "test_symlink_into_main_with_missing_parents_is_denied (test_write_guard.WriteGuardTests.test_symlink_into_main_with_missing_parents_is_denied) ... ok", + "test_valid_multi_file_patch_on_feature_is_allowed (test_write_guard.WriteGuardTests.test_valid_multi_file_patch_on_feature_is_allowed) ... ok", + "", + "----------------------------------------------------------------------", + "Ran 503 tests in 368.596s", + "", + "OK (skipped=18)", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u4'" + ], + "output_sha256": "54c9dc06bb99075654b296b8d494da68390cc635159c9a364bcc4cf9925c84a0", + "receipt_sha256": "3621e9d21fb28c174997b1c2fdbc100eda2ad653ea81d96f6206a36b15bf8ba3", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/3621e9d21fb28c174997b1c2fdbc100eda2ad653ea81d96f6206a36b15bf8ba3.json" +} +``` +| 同步(记录前对象) | c6fe5adba34d7266e7bcfbb9a2c64a0ae61e9b8e | ddad7838535ae06070bfef973106e582c75c6380 | 2026-10-18T17:37:17Z | + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:36:52.906926+00:00", + "command": "make task-sync", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u4", + "before_head": "53897dc218b492c1498637cca9a736143793e3df", + "after_head": "e2da2fef9a205584ba5d82a9e70cf43ad294c9db", + "branch": "feature/norm-remediation-u4", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u4'", + "[worktree] before-sync: recursive component checks passed", + "Merge made by the 'ort' strategy.", + " .task/feature-norm-remediation.md | 16 ++++++++++++++--", + " 1 file changed, 14 insertions(+), 2 deletions(-)", + "[worktree] before-update: recursive component checks passed", + "[worktree] materialize: recursive component checks passed", + "[worktree] after-sync: recursive component checks passed", + "admit: ok", + "[evidence] WARN JSON receipt line 73: unverified: recorded component state was not at its pinned SHA ——关键检查须重跑并以 make evidence 重新写入", + "[evidence] WARN JSON receipt line 114: recorded HEAD predates non-card changes ——关键检查须重跑并以 make evidence 重新写入", + "[synced] sync_base=c6fe5adba34d7266e7bcfbb9a2c64a0ae61e9b8e tested_head=e2da2fef9a205584ba5d82a9e70cf43ad294c9db checked_at=2026-10-18T17:37:17Z changed=1 sync_target=feature/norm-remediation", + "[next] 跑正式验收(make test 等),证据写入任务卡", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u4'" + ], + "output_sha256": "f0e3a1083a6d4caf71bdb0213510488f7496f8bf8e73ae3bcb8eee8bd940d977", + "receipt_sha256": "3f384a0ba3dba53089f1d7b26c15265f8bcc7f9007d001e7666e74245388d94d", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/3f384a0ba3dba53089f1d7b26c15265f8bcc7f9007d001e7666e74245388d94d.json" +} +``` + +## 闭合记录(合入前折叠) + +- 本卡在 PR #621(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/621)合入前闭合并归档,随该 PR 一同合入。 +- 该 PR 的合并提交即本任务的落地记录。 +- 合入后无需回填,也无需单独的闭合 PR。 diff --git a/.task/archive/feature-norm-remediation-u5.md b/.task/archive/feature-norm-remediation-u5.md new file mode 100644 index 00000000..69a7b959 --- /dev/null +++ b/.task/archive/feature-norm-remediation-u5.md @@ -0,0 +1,221 @@ +# feature/norm-remediation-u5 任务卡 + +任务主记录:PR #623 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/623;状态:已完成(合入前闭合);owner:weichen + +- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= eef7e5e778cabcb6216f74b74213a3d7511f383b +- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u5(worktree);分支:feature/norm-remediation-u5 +- 分支类型:child;initiative_id:feature-norm-remediation;root_target:main;immediate_target:feature/norm-remediation;accepting_requirements:false;parent_branch:feature/norm-remediation;parent_head_sha:825e4fc98f30da4950a48d15d59f5338206726bf;parent_pr:无;parent_source:origin +- 需求分流:免(父 initiative 子任务登记已列出 feature/norm-remediation-u5;parent_head_sha=825e4fc98f30da4950a48d15d59f5338206726bf) + +- 授权与交付终点:按批准的规范整改计划实施 U5,范围仅 `AGENTS.md` 与 `PROJECT.md`,完成必要检查、独立 Review、PR/CI 后合入 initiative。U1-U4 前置已完成(U4 #621 / `825e4fc`);不修改规范正文、命令、执行器、平台、组件或业务。 +- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进) +- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md) +- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入) +- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位) + +## 目标与非目标 + +目标:根入口只保留强制原则与触发式必读指针,详细规则分别归 delivery/governance/AGENT-EXECUTION/facts 等权威文档;删除重复且不安全的 token 提取步骤。PROJECT 保留稳定事实、验证入口和全部既有主题索引,将动态批准/进度/验收状态归还主记录,避免多份状态漂移。保持既有授权、安全、独立 Review、证据与最终准入边界。 + +## Agent 实施收据 + +路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。 + +| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 | +|---|---|---|---|---|---|---|---|---|---|---| +| norm-u5-concise-project-entrypoints | AGENTS.md + PROJECT.md | caefcb72 | implementation | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 832bee205380 | 0 | /tmp/norm-u5-entrypoints-receipt.json | +| norm-u5-readiness-evidence-summary-consistency | conditional local readiness / layered evidence summaries | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 36103dc62d59 | 0 | /tmp/norm-u5-summary-consistency-repair-receipt.json | +| norm-u5-ready-routing-without-circular-gate | sync/recovery is not gated by prior READY | caefcb72 | repair | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | 1b04fd4a0408 | 0 | /tmp/norm-u5-readiness-routing-final-receipt.json | +| norm-u5-independent-entrypoint-review | independent fixed-diff/rule/link review | caefcb72 | review | pi, agy, cursor, codex | 1 | pi / minimax-cn/MiniMax-M3 | success | d0ae825547b4 | 0 | /tmp/norm-u5-independent-review-receipt.json | + +## 同步记录 + +| 节点 | sync_base_sha | tested_head_sha | checked_at | +|---|---|---|---| +| 启动 | eef7e5e778cabcb6216f74b74213a3d7511f383b | 825e4fc98f30da4950a48d15d59f5338206726bf | 2026-10-18T17:48:21Z | + +## 证据(Evidence) + + +| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) | +|---|---|---|---|---|---| + +## 结果与验证 + +- 结果:AGENTS 改为触发式权威路由与必要边界,删除重复的 token/端点说明;明确本地续作与正式准入、同步恢复和分级证据,避免无条件摘要重新阻断 U2/U3。PROJECT 保留稳定组合/验证事实与全部既有主题链接,将动态批准/进度/验收归还主记录。 +- 验证:host 在实现候选 `1b04fd4a0408931bb84c44c2fe692205ff25079f` 检查 diff 与物化后的核心权威目标文件,均通过(SELF receipt 已导入);独立 fresh PI Review 对规则保留、恢复无循环门禁、证据分级及链接/锚点审查无阻塞项。纯说明/路由变更不新增镜像文本测试、不重复完整套件;最终组合验证仍由 U6 执行。 +- 影响与剩余项:只改两个入口文件。review 私有目录未物化子模块,报告中相关目标存在性以其末尾限制说明为准;host 已在当前物化 checkout 验证核心权威文件存在,既有 proposal 链接未改变。不读取凭据、不运行在线事实刷新。 + +## 合入与清理 + +- Review 记录(独立 fresh CLI session,2026-10-18):PI / minimax-cn / MiniMax-M3;本评审仅写一条 task 卡记录,不触碰源代码/索引/引用/config,不做合入/CI/PR/部署/同侪派遣。 + base/head = `825e4fc98f30da4950a48d15d59f5338206726bf..1b04fd4a0408931bb84c44c2fe692205ff25079f`(已 `git rev-parse` 对账); + diff 范围仅 `.task/feature-norm-remediation-u5.md` + `AGENTS.md` + `PROJECT.md`(与 U5 任务卡「范围仅 AGENTS.md 与 PROJECT.md」一致,未越界)。 + 结论:**无阻塞项**。所有强制边界在压缩后保留,原始规则未丢失,仅路由至权威文档;`AGENTS.md` 不再展开凭据提取命令、SSH 指纹、API/SSH 主机名、`~/.secrets/gitea` / `~/.ssh/home_gitea` 路径字面值与 `awk` 切片片段,与 U4「凭据字面值不入仓、不回显、不进命令行/日志/证据/产出物」的硬边界一致;事实查询路由到 `policy/facts.md`,与其登记的探针命令一致。 + 逐项校验(按约束清单): + · **goal-first / 严格范围 / 最小充分 / 测试与编排成比例** 完整保留为「最高优先级:目标兑现与最小充分交付(强制)」节:优先级四层链、「先交付用户明确要求的产物」「最小充分实现」「绝对禁止私自扩大范围」「测试与编排成比例」与「编排成本不得超过任务本身」原文落地,细则链到 `policy/delivery.md` 三段标题锚点。 + · **agent-per-unit / no-host-fallback / transport / admission / fresh independent Review** 保留为「Agent 实施与独立 Review(强制)」节:`agent-per-unit(配置:AGENT-EXECUTION.md)`、`fallback_to_host` 为 `never`、`scripts/agent-exec apply` 或 pre-commit `admit` 精确核对、host 豁免仅 `.task/**`、`docs/superpowers/specs/**`、`docs/superpowers/plans/**`、`不同阶段、不同单元各用全新会话`、`禁止实施者经 continue/resume/session 等机制续作评审`、`无法证明会话独立时不得标为独立 Review` 均原义落地;细则链 `policy/delivery.md#agent-实施单元与主模型职责`、`policy/delivery.md#阶段会话隔离与独立-review`、`deps/development-guidelines/core/baseline.md`(unobservable,见限制)。 + · **任务生命周期 → governance** 保留为「任务生命周期(强制)」节,启动/恢复、PR 检查、任务卡闭合、回收、放弃未合入工作用 `make task-finish ABANDON=…`、首次克隆 `make setup`、治理脚本改动 `make test-governance` 全部原文落地,命令与细则链 `policy/governance.md`(启动与恢复 / 回收 / 持续回归)和 `policy/delivery.md`(两级分支与分流 / 分级门禁 / 合入调度 / Review 与合入);与 `policy/governance.md` 同名标题一一对应。 + · **新建任务严格 fresh READY;既有任务 LOCAL_READY 仅本地** 原文落地:`SHARED_WORKSPACE_BUSY / SYNC_REQUIRED / REMOTE_UNVERIFIED` 均不得建任务;`LOCAL_READY 只是 SELF 局部工作记录,不能替代建任务、正式交付、CI、GATE 或 REPLAY 的准入或证据;脏现场、未对账或副作用未知时仍须停止`,与 `policy/governance.md` 启动与恢复节一致。 + · **恢复 / task-sync 允许绕开 READY 前置** 原文落地:`已登记任务的恢复与局部继续按 [启动与恢复](policy/governance.md#启动与恢复),为消除落后所需的同步按 [被测对象](policy/governance.md#被测对象) 执行,READY 不是执行该同步的前提`,明确解环。 + · **L2 / main 证据按分层表** 原文落地:`各层级要求的证据与 CI 以 [分级门禁表](policy/delivery.md#层级任务通道与分级门禁) 为准:L2→L1 不要求三段证据;推进 main 的交付仍须三段输出(precheck / task-sync / 测试)由 evidence 写入任务卡,PR 描述引用不另手抄。测试通过的权威源是 PR HEAD 上 .gitea/workflows/ci.yaml 的 CI`,与 `policy/governance.md` 本地记录与独立证据、`policy/delivery.md` 分级门禁一致;细则链回 governance 与 guidelines `standards/verification.md`。 + · **凭据字面值 / 提取片段已清除**:`awk '/^HOME:/{h=1;next} /^IMC:/{h=0} h&&sub(/^- ACCESS_TOKEN=/,""){print;exit}' ~/.secrets/gitea` 命令、SSH 公钥指纹 `SHA256:GYXaOXIMeloQ1fKSfdJ/ndgPQzs/Ix7Hk62ekfkfQ20`、API/SSH 主机名 `git-home.u1680832.nyat.app:30319` / `git-ssh.u1680832.nyat.app:31411`、`~/.secrets/gitea` HOME/IMC 段、`~/.ssh/home_gitea` 与 `~/.ssh/config` 主机条目、`GITEA_TOKEN` 环境变量注入方式、token `Authorization: token <值>` 头拼接示例均已从 `AGENTS.md` 删除;现仅以「端点、SSH、API 与 token 可用性只以 [policy/facts.md](policy/facts.md) 为准;引用前先跑其验证命令;凭据类核验只用该表登记的探针」+「凭据值不入仓、不回显,不进命令行字面量、日志、证据或产出物(baseline 硬边界 2)」两短句承接,路由到 `policy/facts.md`(含 F1–F6 登记命令)与 `policy/governance.md#ci-凭据边界`;未发现事实表以外的任何凭据字面值残留。 + · **高后果操作双钥匙 owner 执行** 保留为独立节:`改写历史、解除/变更分支保护、删除归档引用、发布版本:Agent 只准备方案与命令,由用户确认并亲手执行`,未削弱。 + · **UI 强制 DESIGN / tokens / motion / Collapsible / 异步边界** 保留为「UI 设计门禁(强制)」节:`DESIGN.md` 四原则、`@ui/tokens.css` 颜色/圆角/字号、`--fs-ui-*` 字号标尺、`@ui/motion.css` 的 `mk-*` 工具类、`@ui` Collapsible 折叠原语、`
` 禁用、`design-guard.test.ts`(UI 与 Frontend 各一份)棘轮拦截裸色值/私有 `@keyframes`/行内字号、风格调整的 UI 令牌 PR + Dev 组合 PR 流程、跨仓不要求同一 PR、异步状态唯一所有者、事件按 seq 过期丢弃均原义落地,与 `DESIGN.md` 第 9 节组件范式一致。 + · **提交与 PR** 保留:`Why: / Evidence: / Impact: / Refs:` 四段 body、英文祈使句主题行 ≤100 列、跨仓 gitlink 组合验证 + 受保护分支 PR 合入均原文落地。 + · **必读路由表(强制)** 新增 `## 必读路由(强制)` 表把触发映射到权威文档,覆盖需求/设计/实施/交付 → `policy/delivery.md`;任务分流/启动/恢复/同步/PR 检查/证据/任务卡闭合/回收 → `policy/governance.md`;实施/修复/测试/Review 候选与模型与尝试上限与 fail-stop 与会话路由 → `AGENT-EXECUTION.md` + `deps/development-guidelines/standards/agent-execution.md`;外部 API/Git/SSH 端点或凭据 → `policy/facts.md`;界面 → `DESIGN.md`;项目组成/构建/验证/方向规划入口 → `PROJECT.md`,与各权威文档作用域一一对应。 + · **`PROJECT.md` 动态批准/进度回收**:移除每条主记录后的「r1/r2/r3/r3.1、批准日期、未授权/部署/真实账号验收、U1-U5/U6 状态、PR #xxx #yyy 落地、dev 8788 续查形成候选草案」等动态状态段;保留主题链接与一句话主题描述;新增显式声明 `下列条目只是主题索引:批准、进展、验收与部署状态一律以所链主记录、任务卡与 PR 为准,本入口不复制;收录不构成批准`,符合「动态批准/进度/验收状态归还主记录,避免多份状态漂移」;同时核对保留的链接集合等于或收敛于基线(2026-10-10..18 共 14 条主记录 + 续审记录 + INDEX + 采纳记录 + 合入调度 + 开发门禁),未删除任何既往主题索引;`docs/svc-deploy/2026-10-12-account-durability-plan-review.md`、`policy/adoption.md`、`policy/governance.md`、`policy/delivery.md`、所有 `modules/documents/docs/proposals/*.md` 链接路径字符串相对基线未变(链接路径差异 = 仅删除 r1/r2/r3/r3.1 后缀与状态描述)。 + 链接 / 锚点核验:`git diff --check 825e4fc..1b04fd4` 无空白冲突;仓内非子模块相对链接(`policy/delivery.md`、`policy/governance.md`、`policy/facts.md`、`policy/adoption.md`、`AGENT-EXECUTION.md`、`DESIGN.md`、`PROJECT.md`、`docs/svc-deploy/2026-10-12-account-durability-plan-review.md`、`modules/documents/docs/proposals/INDEX.md`、`modules/documents/docs/proposals/`)目标文件在本仓库工作区全部存在;新引用的 16 个标题锚点(policy/delivery.md ×9 + policy/governance.md ×7)与 `policy/delivery.md` / `policy/governance.md` 的标题层级对应(去标点 lowercase slug,与基线 `[policy/delivery.md](policy/delivery.md#agent-实施单元与主模型职责)` 等使用的同一生成规则一致,渲染器侧由 Gitea 自行生成,未在 PR 头验证);`policy/facts.md` 锚点 `## ` 段无,故未引用,路由回整文件。 + 限制(按约束声明): + · 子模块链接 `deps/development-guidelines/core/baseline.md`、`deps/development-guidelines/standards/agent-execution.md` 与全部 `modules/documents/docs/proposals/*.md` 在本私有 checkout 未物化(`modules/documents` 与 `deps/development-guidelines` 子模块目录为空),未初始化它们;按约束仅做链接集合对比,未对子模块做 `git submodule init/update`、未断言渲染或文件存在性,host 须在物化后独立核验目标文件与锚点;此为 unobservable,不影响本评审「无阻塞项」结论。 + · 本评审不跑 `make precheck` / `make test` / `make test-governance`(范围仅 prose + 任务卡路由,无脚本/治理改动);不刷新 `policy/facts.md` `last_verified`;不申请 CI(CI 是 PR HEAD 的 `make test` + `make build` 由 Gitea Actions 执行,本评审不冒充 GATE)。 + · 不修正实现;不在本评审做合入 / CI / PR 操作(合规范围内仅写一条 review 记录到 `.task/feature-norm-remediation-u5.md`)。 + · 基线 825e4fc 之前未合入的 prose 提案内容不在 U5 范围,不重复审查。 +- 冲突记录:已同步包含 U7 的 initiative `2e5d981`,无冲突;两个入口相对已审实现 `1b04fd4` 未再改变。合成候选 `45bcba0` 的执行配置、diff 和核心权威链接检查通过(SELF receipt 已导入),不重复完整套件。 +- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果) +- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验) +- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置) + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:46:31.953994+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev", + "before_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "after_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "branch": "main", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'", + "[repo] branch=main head=eef7e5e778", + "[remote] origin/main=eef7e5e778 ahead=0 behind=0", + "[tree] clean submodules_drift=0 uninit=12", + "[warn] 12 个子模块未初始化——构建/测试前需物化(不阻断写入)", + "[worktrees] linked=8", + "[recycle] merged un-recycled branches: feature/layered-python-env", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] eef7e5e778(完整:eef7e5e778cabcb6216f74b74213a3d7511f383b)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'" + ], + "output_sha256": "b1b224c850fc8ccfd988d17caaf1f716e2c2e07ab50d9c5387de9ed292aa9ad0", + "receipt_sha256": "b2bafbab20fc394a5013d8714d3ccdb15afdcfcc090992cf81cbc97ce930c283", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/b2bafbab20fc394a5013d8714d3ccdb15afdcfcc090992cf81cbc97ce930c283.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:01:25.152723+00:00", + "command": "git diff --check 825e4fc..HEAD && for task_target in policy/delivery.md policy/governance.md AGENT-EXECUTION.md policy/facts.md DESIGN.md PROJECT.md deps/development-guidelines/core/baseline.md deps/development-guidelines/standards/agent-execution.md; do test -f \"$task_target\" || exit 1; done && echo \"PASS: entrypoint authority targets exist\"", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u5", + "before_head": "1b04fd4a0408931bb84c44c2fe692205ff25079f", + "after_head": "1b04fd4a0408931bb84c44c2fe692205ff25079f", + "branch": "feature/norm-remediation-u5", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "PASS: entrypoint authority targets exist" + ], + "output_sha256": "6143d7beacc4096c4d0313c752e882c73f41557171c148f3ce4da07fc2ca7517", + "receipt_sha256": "1708ac2a687dfc99f17ef1eea02ab183aacd7d72e83ee8fc8a12d4dc6fb8d7a8", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/1708ac2a687dfc99f17ef1eea02ab183aacd7d72e83ee8fc8a12d4dc6fb8d7a8.json" +} +``` +| 同步(记录前对象) | 2e5d98118fe436794d1267c5d2ab5ce5013d25ca | 405d00ba05cdf3db643d2bc4d35a8aaf92ed0370 | 2026-10-18T18:03:01Z | + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:02:28.140768+00:00", + "command": "make task-sync", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u5", + "before_head": "92c74cee99bf49e7b93ed089d24b7d7529e9ecc8", + "after_head": "45bcba0829dc08fa932c3a52cb99e1e10a099547", + "branch": "feature/norm-remediation-u5", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + " .task/archive/feature-norm-remediation-u7.md | 216 +++++++++++++++++++++++++++", + " .task/feature-norm-remediation.md | 22 ++-", + " AGENT-EXECUTION.md | 41 ++++-", + " policy/delivery.md | 24 +++", + " 4 files changed, 295 insertions(+), 8 deletions(-)", + " create mode 100644 .task/archive/feature-norm-remediation-u7.md", + "[worktree] before-update: recursive component checks passed", + "[worktree] materialize: recursive component checks passed", + "[worktree] after-sync: recursive component checks passed", + "admit: ok", + "[evidence] WARN JSON receipt line 80: unverified: recorded component state was not at its pinned SHA ——关键检查须重跑并以 make evidence 重新写入", + "[evidence] WARN JSON receipt line 121: recorded HEAD predates non-card changes ——关键检查须重跑并以 make evidence 重新写入", + "[synced] sync_base=2e5d98118fe436794d1267c5d2ab5ce5013d25ca tested_head=45bcba0829dc08fa932c3a52cb99e1e10a099547 checked_at=2026-10-18T18:03:01Z changed=1 sync_target=feature/norm-remediation", + "[next] 跑正式验收(make test 等),证据写入任务卡", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u5'" + ], + "output_sha256": "23685fbda4ccf9211ab3c9ffee2f8ebd0e239149d7a687aac214881a3192aa6b", + "receipt_sha256": "639cd7b09ba93fea06daec4dc1c750e05e7a83293ddd4da5e43fddaaf6b54388", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/639cd7b09ba93fea06daec4dc1c750e05e7a83293ddd4da5e43fddaaf6b54388.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:04:48.522344+00:00", + "command": "scripts/agent-exec validate --repo . && git diff --check 2e5d981..HEAD && for task_target in policy/delivery.md policy/governance.md AGENT-EXECUTION.md policy/facts.md DESIGN.md PROJECT.md deps/development-guidelines/core/baseline.md deps/development-guidelines/standards/agent-execution.md; do test -f \"$task_target\" || exit 1; done && echo \"PASS: entrypoint authority targets and merged execution config\"", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u5", + "before_head": "45bcba0829dc08fa932c3a52cb99e1e10a099547", + "after_head": "45bcba0829dc08fa932c3a52cb99e1e10a099547", + "branch": "feature/norm-remediation-u5", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "61ad55e3861841014dd9c58c94f17bc251511c7f83e7a70dbf2ab98e5cff5240", + "after_tracked_diff_sha256": "61ad55e3861841014dd9c58c94f17bc251511c7f83e7a70dbf2ab98e5cff5240", + "output_tail": [ + "validate: ok", + "PASS: entrypoint authority targets and merged execution config" + ], + "output_sha256": "5315267aaca5e016412b6f406903e907d4e7eead2f2f02bbef689581a2127221", + "receipt_sha256": "638362d67ec6d009a4dea4047844154f5e33bd81e724dd3815243b5cde60c595", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/638362d67ec6d009a4dea4047844154f5e33bd81e724dd3815243b5cde60c595.json" +} +``` + +## 闭合记录(合入前折叠) + +- 本卡在 PR #623(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/623)合入前闭合并归档,随该 PR 一同合入。 +- 该 PR 的合并提交即本任务的落地记录。 +- 合入后无需回填,也无需单独的闭合 PR。 diff --git a/.task/archive/feature-norm-remediation-u6.md b/.task/archive/feature-norm-remediation-u6.md new file mode 100644 index 00000000..ddd573c5 --- /dev/null +++ b/.task/archive/feature-norm-remediation-u6.md @@ -0,0 +1,221 @@ +# feature/norm-remediation-u6 任务卡 + +任务主记录:PR #624 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/624;状态:已完成(合入前闭合);owner:weichen + +- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= eef7e5e778cabcb6216f74b74213a3d7511f383b +- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6(worktree);分支:feature/norm-remediation-u6 +- 分支类型:child;initiative_id:feature-norm-remediation;root_target:main;immediate_target:feature/norm-remediation;accepting_requirements:false;parent_branch:feature/norm-remediation;parent_head_sha:69174fcb6955f03d009dd83909da5a18373b1459;parent_pr:无;parent_source:origin +- 需求分流:免(父 initiative 子任务登记已列出 feature/norm-remediation-u6;parent_head_sha=69174fcb6955f03d009dd83909da5a18373b1459) + +- 授权与交付终点:完成已批准 U6 的固定规范/执行器版本采纳、证据对账及最终组合验证,经 child PR 合入 initiative,随后 L1 最终交付 main。范围限两个治理 gitlink、policy/adoption.md、policy/guidelines-adoption.md,及最终同步时保留最新 main 的 PROJECT 主题/运行入口;不改平台保护、发布 tag、轮换凭据、部署或更新其他消费者。 +- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进) +- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md) +- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入) +- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位) + +## 目标与非目标 + +目标:将 Guidelines 固定到已审并合入 draft.14 `a15db5085585f199c3c399824f6cf24c3144bbd5`;DM 固定到修复原始单写者提示及 gitlink 准备缺口的已合入 `6c019595fa02aa2d0a1a9b8f98fc224e69d2a901`(#26/#27),纠正过时采纳说明,保留既有成功/四类负例来源,未执行的 Schema/平台/真实模型质量基准继续 NOT_RUN。执行器引导采用该已审/已测/已合入固定源码构建的既有 agent-exec,生成受控 transport;仍由现有 apply/admit 核验来源,不开放 host 代写或新路由。 + +DM #27 的 exact-head `2bf86c9` 双 CI build-test 通过;安全/正确性独立审阅及最终 `go test ./...`、controller race/vet 通过;最终 merge `6c019595`。上游规范 #11 的 Review/CI 与 merge 见 U7 卡。 + +## Agent 实施收据 + +路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。 + +| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 | +|---|---|---|---|---|---|---|---|---|---|---| +| norm-u6-fixed-guidelines-executor-pins | two governance gitlinks | current config digest in receipt | implementation | cursor, agy, pi | 1 | cursor / auto | success | 97745ca3875f | 0 | /tmp/norm-u6-pins-receipt.json (bootstrap executor 6c019595) | +| norm-u6-reconcile-adoption-and-project-index | adoption records + main PROJECT compatibility | receipt | implementation | cursor, agy, pi | 1 | cursor / auto | success | 30f1ba7596ae | 0 | /tmp/norm-u6-adoption-docs-receipt.json | +| norm-u6-project-component-index-authority | single PROJECT component authority pointer | receipt | repair | cursor, agy, pi | 1 | cursor / auto | success | 847fb002225b | 0 | /tmp/norm-u6-project-components-authority-receipt.json | + +## 同步记录 + +| 节点 | sync_base_sha | tested_head_sha | checked_at | +|---|---|---|---| +| 启动 | eef7e5e778cabcb6216f74b74213a3d7511f383b | 69174fcb6955f03d009dd83909da5a18373b1459 | 2026-10-18T18:20:28Z | + +## 证据(Evidence) + + +| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) | +|---|---|---|---|---|---| + +## 结果与验证 + +- 结果:Guidelines pin=`a15db508`(draft.14),DM pin=`6c019595`;采纳文档区分源码/规则 active、manual/advisory 能力、历史成功/四负例及本次未执行项;过时 #602 引用改为已合入归档。PROJECT 同时保留 U5 简明索引和 main eef7e5e 的法规/SQLite 稳定入口,完整组件清单引用 .gitmodules。 +- 验证:host 在 `847fb002225b2d15f3e49f26f39b537f6501ecb9` 物化固定嵌套依赖后运行配置校验、治理检查、test-pins、14 项入口/事实测试和 diff-check,全部通过(SELF receipt 已导入)。首次检查因新 Guidelines 嵌套 DM 未物化而停止的 exit=2 收据也保留,不计 PASS;非本次消费者旧规范 pin 仅产生既有警告,不升级其版本。独立 fresh Reviewer `u6_adoption_review` 批准上述精确对象,无阻塞项。 +- 影响与剩余项:完整 `make test-governance`(包含在 make test)、`make test` 与 build 在 L1 同步最新 main 后的最终组合候选执行一次;本次 child 不重复旧业务组件组合的全套本地测试。新 PROJECT 的 main-only 目标需最终同步后验实;未宣称当前 child 中已存在。供应方容量/压缩和模型质量基准、额外 Schema/平台负例按采纳文档保持 NOT_RUN。 + +## 合入与清理 + +- Review 记录:独立 fresh review `u6_adoption_review`,session ID not-observable;base `69174fcb6955f03d009dd83909da5a18373b1459` / head `847fb002225b2d15f3e49f26f39b537f6501ecb9`;核对准确 pin/VERSION、上游合入对象、历史正负例 SELF 与新 NOT_RUN、能力等级和 PROJECT main 入口,无阻塞,APPROVE;独立重放 config/governance/diff 检查通过(只读本地结果,不冒充 GATE)。 +- 冲突记录:NOT_RUN(最新目标同步、冲突处置、组合兼容核验及复审/补测) +- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果) +- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验) +- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置) + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:19:20.152696+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev", + "before_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "after_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "branch": "main", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'", + "[repo] branch=main head=eef7e5e778", + "[remote] origin/main=eef7e5e778 ahead=0 behind=0", + "[tree] clean submodules_drift=0 uninit=12", + "[warn] 12 个子模块未初始化——构建/测试前需物化(不阻断写入)", + "[worktrees] linked=7", + "[recycle] merged un-recycled branches: feature/layered-python-env", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] eef7e5e778(完整:eef7e5e778cabcb6216f74b74213a3d7511f383b)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'" + ], + "output_sha256": "8ebd14a4e8639e7dffb04336ad69df1bb8f80b36d3f9cf1c0875966b3503b3f4", + "receipt_sha256": "ff4d0e2ee43dc1cdc2dfbd9ae539ca52bf50fc650785f956d2682675838ad09a", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/ff4d0e2ee43dc1cdc2dfbd9ae539ca52bf50fc650785f956d2682675838ad09a.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:31:46.193016+00:00", + "command": "scripts/agent-exec validate --repo . && scripts/dm governance check --repo . && make test-pins && python3 -m unittest tests.test_agent_exec_entry tests.test_facts_verify && git diff --check 69174fc..HEAD", + "exit_code": 2, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6", + "before_head": "847fb002225b2d15f3e49f26f39b537f6501ecb9", + "after_head": "847fb002225b2d15f3e49f26f39b537f6501ecb9", + "branch": "feature/norm-remediation-u6", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "ok file AGENTS.md", + "ok file PROJECT.md", + "ok file policy/guidelines-adoption.md", + "ok file .task/templates/task-card.md", + "ok executable scripts/agent-exec", + "ok executable scripts/hooks/pre-commit", + "ok executable scripts/dm", + "ok executable scripts/task", + "ok adoption status", + "ok fixtures", + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6'", + "python3 scripts/check-worktree.py verify", + "ERROR: uninitialized submodule: /home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6/deps/development-guidelines/deps/development-manager; initialize and inspect it first", + "make[1]: *** [Makefile:218: test-worktree] Error 1", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6'" + ], + "output_sha256": "86b91eefa0aaf1c11220b17fe27b7680052f09126eaae5d0189b09df55bbfb5d", + "receipt_sha256": "cfba7dee4b098b7c50441e488e99504bf19e7a1abdd595234c3cb465fc29ca27", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/cfba7dee4b098b7c50441e488e99504bf19e7a1abdd595234c3cb465fc29ca27.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:32:47.051092+00:00", + "command": "scripts/agent-exec validate --repo . && scripts/dm governance check --repo . && make test-pins && python3 -m unittest tests.test_agent_exec_entry tests.test_facts_verify && git diff --check 69174fc..HEAD", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6", + "before_head": "847fb002225b2d15f3e49f26f39b537f6501ecb9", + "after_head": "847fb002225b2d15f3e49f26f39b537f6501ecb9", + "branch": "feature/norm-remediation-u6", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "ok deps/agent-squad agent-intent", + "ok deps/agent-squad agent-native", + "warn: deps/agent-squad deps/development-guidelines is 26b562d2e34f, Dev is a15db5085585", + "ok deps/agent-squad error-code-system", + "ok deps/agent-squad logging-system", + "warn: deps/agent-intent deps/development-guidelines is 26b562d2e34f, Dev is a15db5085585", + "ok deps/agent-intent error-code-system", + "ok deps/agent-intent logging-system", + "pins ok", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6'", + "..............", + "----------------------------------------------------------------------", + "Ran 14 tests in 4.625s", + "", + "OK" + ], + "output_sha256": "47156d314ddc13ab2b36beea736fdb08e4996834a5690fa64bfb0e21db838ba2", + "receipt_sha256": "079c7647661d7879b0f62d85d83af380ed49a06e1e9ff9896a81b62c2024fb03", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/079c7647661d7879b0f62d85d83af380ed49a06e1e9ff9896a81b62c2024fb03.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:34:28.257843+00:00", + "command": "make task-sync", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6", + "before_head": "20c8fd00f6cfbb01c98c8d2ca298033b5fd26da3", + "after_head": "20c8fd00f6cfbb01c98c8d2ca298033b5fd26da3", + "branch": "feature/norm-remediation-u6", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6'", + "[worktree] before-sync: recursive component checks passed", + "[worktree] before-update: recursive component checks passed", + "[worktree] materialize: recursive component checks passed", + "[worktree] after-sync: recursive component checks passed", + "[evidence] WARN JSON receipt line 59: unverified: recorded component state was not at its pinned SHA ——关键检查须重跑并以 make evidence 重新写入", + "[evidence] WARN JSON receipt line 100: unverified: recorded component state was not at its pinned SHA ——关键检查须重跑并以 make evidence 重新写入", + "[synced] sync_base=38c5b93b79a8f383dbb7d7883b26d8432355a52c tested_head=20c8fd00f6cfbb01c98c8d2ca298033b5fd26da3 checked_at=2026-10-18T18:34:54Z changed=0 sync_target=feature/norm-remediation", + "[next] 跑正式验收(make test 等),证据写入任务卡", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6'" + ], + "output_sha256": "993b6800959e6e1d6363c0e34341063609b849b4640b5be621f21fa353caeaf8", + "receipt_sha256": "197c71283d9201ea0d4870626e729a6a1fa441e25fa02272c99ac280fc6ff30d", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/197c71283d9201ea0d4870626e729a6a1fa441e25fa02272c99ac280fc6ff30d.json" +} +``` + +## 闭合记录(合入前折叠) + +- 本卡在 PR #624(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/624)合入前闭合并归档,随该 PR 一同合入。 +- 该 PR 的合并提交即本任务的落地记录。 +- 合入后无需回填,也无需单独的闭合 PR。 diff --git a/.task/archive/feature-norm-remediation-u7.md b/.task/archive/feature-norm-remediation-u7.md new file mode 100644 index 00000000..838d38c8 --- /dev/null +++ b/.task/archive/feature-norm-remediation-u7.md @@ -0,0 +1,216 @@ +# feature/norm-remediation-u7 任务卡 + +任务主记录:PR #622 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/622;状态:已完成(合入前闭合);owner:weichen + +- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= eef7e5e778cabcb6216f74b74213a3d7511f383b +- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u7(worktree);分支:feature/norm-remediation-u7 +- 分支类型:child;initiative_id:feature-norm-remediation;root_target:main;immediate_target:feature/norm-remediation;accepting_requirements:false;parent_branch:feature/norm-remediation;parent_head_sha:c6fe5adba34d7266e7bcfbb9a2c64a0ae61e9b8e;parent_pr:无;parent_source:origin +- 需求分流:免(父 initiative 子任务登记已列出 feature/norm-remediation-u7;parent_head_sha=c6fe5adba34d7266e7bcfbb9a2c64a0ae61e9b8e) + +- 授权与交付终点:用户追加并确认 U7:完善 subagent/CLI 的有界任务、容量规划、模型分工、阶段交接与测试要求;本子任务经 Review/CI 合入 initiative。范围限 `AGENT-EXECUTION.md`、`policy/delivery.md`;通用规则已由 Guidelines PR #11 合入,最终 pin/采纳归 U6。本任务不部署、不改平台或执行器架构。 +- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进) +- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md) +- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入) +- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位) + +## 目标与非目标 + +目标:工作包适合较小/未知容量模型,以一个可独立验证的结果、最小背景和明确验收派发;阶段完成自动持久交接,下一 CLI 会话全新,原生压缩仅在支持时可选;具体规划/执行模型偏好归项目本地配置,requested/effective/不可观测状态如实区分。未知容量使用 100K 显式规划假设,给实现/迭代/测试保留空间;不新增控制器、Schema 或失败门禁。 + +共享基线:Guidelines PR #11,merge `a15db5085585f199c3c399824f6cf24c3144bbd5`(draft.14);独立 Review、schema/config 校验与 CI run 3978 均通过。正文/模板和 Review 修复 transport 分别见 `/tmp/guidelines-u7-receipt.json`、`/tmp/guidelines-u7-review-repair-receipt.json`;worker 私有索引 intent-to-add 曾被撤销,为执行偏差,不证明提示具备工具强制力。 + +## Agent 实施收据 + +路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。 + +| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 | +|---|---|---|---|---|---|---|---|---|---|---| +| norm-u7-dev-bounded-cli-routing | AGENT-EXECUTION.md + policy/delivery.md | caefcb72 (prior config) | implementation | cursor, agy, pi | 1 | cursor-agent-stable / auto | success | e16a75c5111c | 0 | /tmp/norm-u7-dev-receipt.json | +| norm-u7-dev-independent-review | independent fixed-diff/config review | run receipt | review | pi, agy, cursor, codex | 1 | pi / minimax-cn/MiniMax-M3 (actual event) | success | 892cf59c5075 | 0 | /tmp/norm-u7-dev-review-receipt.json | + +## 同步记录 + +| 节点 | sync_base_sha | tested_head_sha | checked_at | +|---|---|---|---| +| 启动 | eef7e5e778cabcb6216f74b74213a3d7511f383b | c6fe5adba34d7266e7bcfbb9a2c64a0ae61e9b8e | 2026-10-18T17:35:52Z | + +## 证据(Evidence) + + +| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) | +|---|---|---|---|---|---| + +## 结果与验证 + +- 结果:共享有界工作包与交接标准已合入 Guidelines PR #11(draft.14);本仓补齐工作包最小信息、100K 保守假设、阶段交接和 requested/effective 区分。PI 请求已明确为 `minimax-cn/MiniMax-M3`,独立 Review 实际事件也报告同 provider/model;Cursor auto,agy Flash 偏好保持 NOT_VERIFIED。普通上下文压力不触发扩围停止;无自动压缩或未知容量不阻断其他授权工作。 +- 验证:host 在 `e16a75c5111c861afea11458194ac308f81ca99a` 运行配置校验、6 项 agent-exec 入口测试与 diff-check 均通过(SELF);独立 fresh PI Review 重放固定 diff/配置,无阻塞 finding。两个 CLI run 均为新进程和独立隔离工作区,未使用 resume/continue;session ID 未单独提取时记 not-observable,独立性依据 run/attempt 及新进程,不伪造 ID。 +- 影响与剩余项:U6 才推进 Guidelines pin/采纳记录;当前旧 pin 不宣称已经采用 draft.14。未知/已知容量例子是规范一致性检查,不证明实际模型最大容量或质量提升;同任务真实模型前后质量基准 NOT_RUN(本次交付是规范与配置,未建设评测平台,也未宣称质量提升的实测数值)。端点/登录/压缩与并发强制能力均不作无证据断言。 + +## 合入与清理 + +- Review 记录(stage=review,fresh_session=true,model=minimax-cn/MiniMax-M3 as configured for PI,session ID=not-observable):独立 Review session;base=c16665365bbbf3b733b034628f102db90d985637;head=e16a75c5111c861afea11458194ac308f81ca99a;范围限 diff AGENT-EXECUTION.md + policy/delivery.md。 + - 命令与对象:`git diff c16665365bbbf3b733b034628f102db90d985637..e16a75c5111c861afea11458194ac308f81ca99a -- AGENT-EXECUTION.md policy/delivery.md` → 98 行差异(AGENT-EXECUTION.md +41/-3,policy/delivery.md +24/-0);`scripts/agent-exec validate --repo .`(deps/development-manager 未初始化,回退到精确钉住 dm 二进制 `$HOME/.cache/agent-system-dev/agent-exec/dm-server-67784f43eb0430bf596c2cda685cbb4946d2e032` 与 index 钉住 `67784f43eb0430bf596c2cda685cbb4946d2e032` 完全一致;未写规范仓;binary version=0.1.0-dev)→ `validate: ok`,exit=0。 + - 结论:无 blocker。机器块(`execution.fallback_to_host=never`、`max_attempts_per_profile=3`、`emergency_profile=codex`、`roles.implementation/repair/test/review` 候选顺序、超时/输出字节)逐项与 base 一致;PI `model` 由 `auto` 改为显式 `minimax-cn/MiniMax-M3`,Cursor 保持 `auto`,agy 保持 `auto` 并在 prose 明示 Gemini 3.8 Flash 为 `NOT_VERIFIED`(未观测模型 ID,未声称生效);新增 prose 段只描述本仓做法并显式声明「本节不新增门禁,不放宽凭据、范围、未知副作用与 CI 边界」与「Role lists, attempt limits, timeouts, output limits, the single Codex emergency attempt, stage isolation and fallback_to_host: never are unchanged」。 + - 观测性:`requested` 模型是提示/配置意图,`effective` 以运行事件信封为准,CLI 不报告时记 `not-observable`;未知容量、agy 缺登录、缺自动压缩均登记事实,不作为全局 fail-close(仅真实扩围走既有 `scope-expansion` fail-stop)。现有 scope/credential/unknown-side-effect fail-stops 未删未弱。 + - 单元包/容量:bounded packet JSON 的 `goal/constraints/verification` 已含一个结果与验收、允许路径、最小必读、前置 SHA、直接验证命令、未知容量按保守 100K 假设并为执行留余量;100K/200K/300K/500K 仅为规划参考,未声明任何 provider 能力;不新增 schema/字段/控制器。 + - 阶段交接:里程碑持久交接写入既有任务记录,下一 CLI 阶段或单元以全新会话启动,只读交接与最小必读,不为交接等人工。 + - 上下文压力:仅在 provider 支持且阶段完成后做上下文压缩;不支持不阻塞;普通压力走交接 + host 重新打包。 + - 非阻塞观察(不阻塞交付):`AGENT-EXECUTION.md`/`policy/delivery.md` 中新增的相对链接指向 `deps/development-guidelines/standards/agent-execution.md` 与 `deps/development-guidelines/workflows/handoff.md`;当前 `deps/development-guidelines` gitlink 仍为 `2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb`(未在 U7 范围 bump),文件存在与否须 U6 pin 时再核,prose 未声称当前 pin 已采纳 Guidelines PR #11(merge `a15db5085585f199c3c399824f6cf24c3144bbd5`),仅引导后续采纳。`policy/delivery.md` 锚 `#有界单元包上下文与阶段交接` 与新章节 `### 有界单元包、上下文与阶段交接` 在 GFM 风格锚归一化(移除中文标点)下等价。`agy` 段仅将「Gemini 3.8 Flash」记为请求,未声明能力,符合「不发明模型能力」要求。 + - 不主张 CI 通过;未运行任何 live API/模型探测;未改 source/config/index/refs;未开 PR/部署/派遣同伴。审阅者身份:本独立 session(PI / minimax-cn/MiniMax-M3,session ID not-observable,stage=review)。重放依据:`git rev-parse HEAD` 与 `git ls-files -s deps/development-manager` 即可复现。 +- 冲突记录:已同步包含 U4 的 initiative `825e4fc`,无冲突;U7 代码/配置相对已审实现 `e16a75c` 未再改变。host 在合成 head `3da20d4` 校验配置并重放入口+事实测试 14 项,均通过(SELF,receipt 已导入),未重跑与此无关的完整套件。 +- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果) +- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验) +- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置) + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:34:44.780514+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev", + "before_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "after_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "branch": "main", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'", + "[repo] branch=main head=eef7e5e778", + "[remote] origin/main=eef7e5e778 ahead=0 behind=0", + "[tree] clean submodules_drift=0 uninit=12", + "[warn] 12 个子模块未初始化——构建/测试前需物化(不阻断写入)", + "[worktrees] linked=7", + "[recycle] merged un-recycled branches: feature/layered-python-env", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] eef7e5e778(完整:eef7e5e778cabcb6216f74b74213a3d7511f383b)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'" + ], + "output_sha256": "8ebd14a4e8639e7dffb04336ad69df1bb8f80b36d3f9cf1c0875966b3503b3f4", + "receipt_sha256": "595376851618682c9df8e7c637c465e9950237249d07bf36ea0726026961fe06", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/595376851618682c9df8e7c637c465e9950237249d07bf36ea0726026961fe06.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:39:09.639625+00:00", + "command": "scripts/agent-exec validate --repo . && python3 -m unittest tests.test_agent_exec_entry && git diff --check", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u7", + "before_head": "e16a75c5111c861afea11458194ac308f81ca99a", + "after_head": "e16a75c5111c861afea11458194ac308f81ca99a", + "branch": "feature/norm-remediation-u7", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "validate: ok", + "......", + "----------------------------------------------------------------------", + "Ran 6 tests in 0.389s", + "", + "OK" + ], + "output_sha256": "8fde6c3d2592daa001b2e9856870a9725c40bcd8c52e7e56c58852005c56eab7", + "receipt_sha256": "1478aa40c6d51ff8198bf6517ba5fa4a7fd51f33a7112d52057e771f346cf248", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/1478aa40c6d51ff8198bf6517ba5fa4a7fd51f33a7112d52057e771f346cf248.json" +} +``` +| 同步(记录前对象) | 825e4fc98f30da4950a48d15d59f5338206726bf | 66696bfc436a4adadf70fe7ecfc6ab4cda631d65 | 2026-10-18T17:48:16Z | + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:47:48.776787+00:00", + "command": "make task-sync", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u7", + "before_head": "2de3d398ccc1db9d235238016a12755b4ea989f2", + "after_head": "3da20d47b5a4a0732effad4992daf17acd2af944", + "branch": "feature/norm-remediation-u7", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + " policy/facts.md | 9 +-", + " scripts/facts-verify.sh | 79 ++++++", + " tests/test_facts_verify.py | 399 +++++++++++++++++++++++++++", + " 4 files changed, 746 insertions(+), 2 deletions(-)", + " create mode 100644 .task/archive/feature-norm-remediation-u4.md", + " create mode 100644 tests/test_facts_verify.py", + "[worktree] before-update: recursive component checks passed", + "[worktree] materialize: recursive component checks passed", + "[worktree] after-sync: recursive component checks passed", + "admit: ok", + "[evidence] WARN JSON receipt line 66: unverified: recorded component state was not at its pinned SHA ——关键检查须重跑并以 make evidence 重新写入", + "[evidence] WARN JSON receipt line 107: recorded HEAD predates non-card changes ——关键检查须重跑并以 make evidence 重新写入", + "[synced] sync_base=825e4fc98f30da4950a48d15d59f5338206726bf tested_head=3da20d47b5a4a0732effad4992daf17acd2af944 checked_at=2026-10-18T17:48:16Z changed=1 sync_target=feature/norm-remediation", + "[next] 跑正式验收(make test 等),证据写入任务卡", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u7'" + ], + "output_sha256": "0397ae866aa211d57b600b60355563e5dc4ebb1d09569ef0cc607dfbe5bd1f27", + "receipt_sha256": "bcb38dab3f750867a93de90576aa0d39f7fd0501fb0557c085a6d26cf1a29779", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/bcb38dab3f750867a93de90576aa0d39f7fd0501fb0557c085a6d26cf1a29779.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T17:49:37.631093+00:00", + "command": "scripts/agent-exec validate --repo . && python3 -m unittest tests.test_agent_exec_entry tests.test_facts_verify && git diff --check", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u7", + "before_head": "3da20d47b5a4a0732effad4992daf17acd2af944", + "after_head": "3da20d47b5a4a0732effad4992daf17acd2af944", + "branch": "feature/norm-remediation-u7", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "feature/norm-remediation", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "validate: ok", + "..............", + "----------------------------------------------------------------------", + "Ran 14 tests in 4.466s", + "", + "OK" + ], + "output_sha256": "0abf86ce43ea39bd4b51a9be1a3ca8bc47531f3e75e13af28f7f1f4d6452bc19", + "receipt_sha256": "3fb74abf04253157034696850c14561fdee89c6a47e3da22aaea6a5ef419ef70", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/3fb74abf04253157034696850c14561fdee89c6a47e3da22aaea6a5ef419ef70.json" +} +``` + +## 闭合记录(合入前折叠) + +- 本卡在 PR #622(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/622)合入前闭合并归档,随该 PR 一同合入。 +- 该 PR 的合并提交即本任务的落地记录。 +- 合入后无需回填,也无需单独的闭合 PR。 diff --git a/.task/archive/feature-norm-remediation.md b/.task/archive/feature-norm-remediation.md new file mode 100644 index 00000000..3f27194a --- /dev/null +++ b/.task/archive/feature-norm-remediation.md @@ -0,0 +1,507 @@ +# feature/norm-remediation 任务卡 + +任务主记录:PR #616 https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/616;状态:已完成(合入前闭合);owner:weichen + +## 基线与授权 + +- 权威仓:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Agent-System-Dev.git;目标分支:main;R0(initial_base_sha)= b5e23daed2f0ea46261979de1d62e4de0cfa88f9 +- 工作区:/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation(worktree);分支:feature/norm-remediation +- 分支类型:initiative;initiative_id:feature-norm-remediation;root_target:main;immediate_target:main;accepting_requirements:false +- 需求分流:verdict=NEW;selected_initiative=无;requirement_sha256=2342cdada7570c731d5c3216b74fbb55d85946b63d72974f3de29b45e5052a90;triage_receipt=/tmp/norm-remediation-implementation-triage-20261018.json;receipt_sha256=bb4542a99bda1327e47c56c451f36cade62ffada4ab84b878dcf3865ce1a2401 + +- 授权与交付终点:2026-10-18 用户选择开始实施已审查计划,并追加确认 U7,授权按 U1-U7 完成规范整改及必要缺陷修复、PR/CI/合入/收口。未授权:修改平台分支保护、发布不可移动版本 tag、轮换凭据、V3 指标复测 M、扩展到其他消费者仓库。 +- 交付模式:goal-first(目标、范围和交付终点一次确认;范围内连续推进) +- 实施执行:agent-per-unit(配置:AGENT-EXECUTION.md) +- 合入执行:agent(已授权交付范围内,Review 通过、冲突解决且当前 HEAD 的 CI 通过后合入) +- 人工决策点:仅限目标/范围/验收变更、重大取舍、高后果操作或无法自动判定的业务验收 +- 当前阶段:U1-U7 全部完成;已同步 main `eef7e5e778cabcb6216f74b74213a3d7511f383b`,固定代码候选 `f52df0e2e6bd52d1b60989ac079826787fef3307` 已通过独立 Review、本地分段组合检查与构建。当前只整理任务元数据、合入前闭合并冻结最终 PR HEAD,随后核验该 HEAD 的三项必需 CI、合入与安全收口。 +- 流程依据:仓库根目录 policy/delivery.md(归档后仍按仓库根目录定位) + +## 目标与非目标 + +目标:完成计划 U1-U6,使 Development-Guidelines 只定义可复用的交付原则,Dev 保留自己的分支/命令/证据细节,并允许已授权、工作区安全的既有任务在远端暂不可用时有限本地续作,同时保持新任务启动和正式交付的严格远端门禁。 + +非目标:重做已合入 V3 P1-P3;执行两周指标复测 M;改变分支保护、自动合入或凭据管理;更新其他规范消费者。 + +方案/计划:仓库根目录 `docs/superpowers/plans/2026-10-18-0849-fix-development-norm-remediation-plan.md`(与源计划工作区正文逐字一致的已批准快照;原始工作区文件未改动,交付后以仓内版本为准)。U1 已在 Development-Guidelines PR #10 合入 `5b72099`;U7 共享规则由 #11 合入 `a15db508`;DM #26/#27 已合入 `6c019595`。U2-U7 均通过子 PR 集成;本 L1 已完成 main 同步及本地组合覆盖,最终 HEAD CI 与合入仍待核验。 + +分级验证:U1 文档改动核对链接、职责归属、版本/迁移记录与规则强度;U2-U5 运行受影响治理/事实验证的聚焦测试;U6 运行 `make test-governance` 与 `make test`,最终 PR 仍须当前 HEAD 独立 Review 和完整 CI。 + +验收:R1-R10 全部满足;LOCAL_READY 不得被提升为 READY/REPLAY/GATE;未知副作用、未授权、扩围、不安全共享工作区和未对账 Git 状态仍停;Guidelines 已合入,Dev pin 与采纳记录匹配其 SHA,组合验证及当前 HEAD CI 通过。 + +生命周期:接纳新需求关闭(`accepting_requirements=false`);只执行下列已登记子任务。所有必需子任务完成或显式延期后,再同步 main、冻结候选并执行整体 Review/CI/合入。 + +## 子任务登记 + +| 子任务分支 | 必需 | 状态 | PR | +|---|---|---|---| +| feature/norm-remediation-u2 | 是 | 已合入 | #618 | +| feature/norm-remediation-u3 | 是 | 已合入 | #620 | +| feature/norm-remediation-u4 | 是 | 已合入 | #621 | +| feature/norm-remediation-u5 | 是 | 已合入 | #623 | +| feature/norm-remediation-u6 | 是 | 已合入 | #624 | +| feature/norm-remediation-u7 | 是 | 已合入 | #622 | + +## Agent 实施收据 + +路由与 fail-stop 规则见根目录 AGENT-EXECUTION.md 与 policy/delivery.md;每次尝试一行,原始日志留在源码外收据路径。 + +| 单元 ID | 预期范围 | 配置摘要 | 角色 | 候选顺序 | attempt | CLI/模型 | 终态 | transport | fallback | 源码外收据路径 | +|---|---|---|---|---|---|---|---|---|---|---| +| U1-U7 | 各 child 卡及共享仓 PR 中的有界单元 | 各归档卡逐次记录 | implementation/repair | 依运行配置 | 见子卡 | 见子卡 requested/effective | completed | 见子卡 | 见子卡 | `.task/archive/feature-norm-remediation-u2.md` 至 `u7.md`;共享仓 #10/#11、DM #26/#27 | +| final-governance-pin-check | scripts/check-pins.sh、tests/test_check_pins.py | 原始收据记录配置 digest | repair | cursor, antigravity, pi | 以原始收据为准 | 以原始事件为准 | completed | f52df0e2e6bd52d1b60989ac079826787fef3307 | false | /tmp/norm-final-governance-pin-check-receipt.json | + +## 关联方向与规划 + +- 适用状态:已批准实施 +- 主记录路径/版本:docs/superpowers/plans/2026-10-18-0849-fix-development-norm-remediation-plan.md +- 对应里程碑:U1-U6;Guidelines U1 PR #10 first, followed by Dev U2-U6 integration + +## 需求分析 + +| ID | 描述 | 项目符合度及依据 | 优先级 | 验收条件 | 依赖 | 结论 | +|---|---|---|---|---|---|---| +| R1-R10 | 见关联计划的 Product Contract | Dev 实施 U2-U6;U1 在 Development-Guidelines | P1/P2(按计划) | 满足计划 R1-R10、U1-U6 与 Definition of Done | U1 precedes U2-U6; U5/U6 have declared dependencies | 用户已确认范围 | + +## 方案与计划 + +- 方案引用:规范审查报告与用户确认的风险分层方案;U1 preserves remote startup and formal delivery gates. +- 实施计划引用:docs/superpowers/plans/2026-10-18-0849-fix-development-norm-remediation-plan.md (U1-U6) +- 测试计划引用:计划 Verification Contract;U1 文档检查、U2-U5 focused governance tests、U6 governance + combination tests + +## 阶段记录 + +| 阶段 | 对象版本 | 状态 | 决策/执行者 | 时间 | 依据 | +|---|---|---|---|---|---| +| 需求拆解 | 用户批准计划 | 完成 | host | 2026-10-18 | 规范整改计划 | +| 需求评估 | Dev `b5e23da` / Guidelines `6c8e6cd` | 完成 | host | 2026-10-18 | 源码与启动状态核验 | +| 需求表确认 | R1-R10 | 完成 | 用户 | 2026-10-18 | 用户选择 Start ce-work | +| 方案设计 | KTD1-KTD8 | 完成 | host | 2026-10-18 | 见计划 | +| 实施计划 | U1-U6 | 完成 | host | 2026-10-18 | U1 Guidelines PR #10 先合入 | +| 实施与自检 | U1-U7 / f52df0e | 完成 | host + configured workers | 2026-10-18 | 全部 child 已合入;共享规范及执行器已固定采纳 | +| 正式测试与验收 | f52df0e | 本地分段覆盖完成;最终 GATE 待核验 | host / CI | 2026-10-18 | 原始失败及成功续跑收据见 Evidence;不声明单次全量成功 | +| 代码 Review | eef7e5e..6b58060;6b58060..f52df0e | 无阻塞 | final_norm_correctness_review / final_norm_security_review | 2026-10-18 | 全新独立审阅;增量正确性重放 5 项通过 | +| 合入 | 待定 | 待执行 | — | — | — | +| 合入核验与收口 | 待定 | 待执行 | — | — | — | + +## 探索试验与迭代 + +| 增量/试验 | 假设 | 范围 | 判断和停止条件 | 授权/决策依据 | 结果 | 变更影响 | 需重审补测 | +|---|---|---|---|---|---|---|---| +| 待定义 | — | — | — | 待核对 | NOT_RUN | 待评估 | 待评估 | + +## 同步记录 + +| 节点 | sync_base_sha | tested_head_sha | checked_at | +|---|---|---|---| +| 启动 | b5e23daed2f0ea46261979de1d62e4de0cfa88f9 | b5e23daed2f0ea46261979de1d62e4de0cfa88f9 | 2026-10-18T09:24:52Z | + +## 证据(Evidence) + + +| 时间(UTC) | 命令 | 被测HEAD | exit | 来源 | sha256(16) | +|---|---|---|---|---|---| + +## 交付调度 + +- 调度状态:最终窗口进行中;调度记录:Issue #518 评论 7605(前窗口 7602 已由 7604 释放) +- entry_id:norm-remediation;attempt_id:mq-dev-20261018-norm-remediation-final-02;窗口执行者:当前主协调智能体 / weichen +- 固定候选:base `eef7e5e778cabcb6216f74b74213a3d7511f383b`;代码 `f52df0e2e6bd52d1b60989ac079826787fef3307`;后续只改任务元数据,最终 HEAD 以 PR/CI 为准。手动窗口不代表原子锁或工具级单写者保证。 +- 调度依据:policy/delivery.md#合入调度 +- 固定后证据:源码外 RECEIPT;实际结果后按授权原样导入和闭合 + +## 结果与验证 + +- 结果:U1-U7 全部集成。共享规范只保留通用原则,Dev 保留具体流程/命令/模型偏好;LOCAL_READY 支持安全的既有授权任务局部续作,不替代正式远端门禁。上下文不足、缺自动压缩或模型身份不可观测不成为全局 fail-close;未知副作用、秘密、授权、未对账 Git 等安全边界保留。 +- 验证:首次组合尝试在治理 DM pin 误分类处 FAIL;最小修复及 5 个真实 Git 正负例 PASS。修复后的组合尝试完成 513 项治理检查(18 skip)、架构、pin、库及 Core 检查,在 Backend 权限负例因当前 umask 0077 不满足其 0644 前提而 FAIL。源码未变,显式 umask 022 后该用例、完整 Backend/Frontend/UI/agent-command 及 build PASS;仅复用同一代码对象上此前已通过的五个 make 目标,不虚报整条失败命令成功。权威结论仍等待最终 PR HEAD 的完整 CI。 +- 影响与剩余项:没有部署、发布 tag 或修改分支保护。agy 精确模型 ID/有效模型、平台自动压缩能力与真实模型质量前后基准为 NOT_VERIFIED/NOT_RUN;不声称已自动压缩或实测质量提升。原始脏计划工作区和安全拒绝删除的跨仓子模块 worktree 保留。 + +## Post-Deploy Monitoring & Validation + +No additional operational monitoring required:本次仅规范、治理脚本与固定工具采纳,不改变业务运行时,也不部署;后续治理启动/恢复与 CI 继续使用现有检查入口观察结果。 + +## 规划进展与下一步 + +- 本次推进/剩余:U1-U7、main 同步、独立 Review 及本地组合覆盖完成。余下仅任务卡闭合、最终精确 HEAD CI、PR #616 合入/核验及安全回收。 +- 首选下一任务:完成本任务既定交付,不启动新的完善增量。 + +### 阶段交接(2026-10-18;U2/U3 完成,U4/U7 在途) + +以下为该阶段历史快照;最新状态以本卡「当前阶段」「子任务登记」及后面的收口交接为准,不重启已完成的运行。 + +- 目标/授权:继续 U1-U7;保持未知副作用、权限、范围、秘密及正式交付边界;不部署、不改保护或发布 tag。 +- 唯一计划正文:`/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-plan/docs/superpowers/plans/2026-10-18-0849-fix-development-norm-remediation-plan.md`(仍为该工作区中的既有未提交文件,不覆盖或丢弃)。父 PR #616 的目标为 main。 +- U4 工作区:`/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u4`,分支同名后缀;首轮 transport `7e0b0e4`,6 项离线事实测试、syntax/diff 检查通过,SELF 已入卡。当前 head `9b29e0f` 是元数据提交。 +- U4 在途 repair:`/tmp/norm-u4-credential-boundary-repair.json` → `/tmp/norm-u4-credential-boundary-repair-receipt.json`;仅封闭新 F2 探针的目标与重定向边界、补负例,及说明 F4 原日期未被新探针重验。首轮 receipt `/tmp/norm-u4-facts-receipt.json`;外部 worker 与 transport 独立,host 不直接实施。 +- U7 共享工作区:`/home/weichen/projects/Development-Guidelines-wt-feature-bounded-agent-work`,分支 `feature/bounded-agent-work`,基线为最新 Guidelines main `5b72099`。远端 triage NEW 收据 `/tmp/guidelines-u7-triage.json`;共享 task-start 因规范源无消费者模板而未写入,使用已核验基线建立普通 worktree。过程记录归本卡,不进 Guidelines。 +- U7 在途 worker:packet `/tmp/guidelines-u7-packet.json`,receipt `/tmp/guidelines-u7-receipt.json`;仅 Agent 标准、交接流程、工作包模板、VERSION/CHANGELOG,供应商无关,不新增控制器或 Schema。 +- 模型观察:PI 已有运行事件报告 `minimax-cn / MiniMax-M3`;agy 模型列表因未登录不可核验。Dev U7 须分开 requested/effective/unobservable,不声称 agy Gemini 偏好已可用;保留已有候选和无 host fallback 边界。 +- 下一步:收取两个外部运行终态→审阅实际 diff→受控 apply→聚焦检查;U4 安全/目标负例及必要治理测试、fresh 独立 Review、child PR exact-head CI 后合入。U7 共享标准经 Review/CI 合入后,Dev 采纳其固定 SHA 和项目偏好。不要覆盖未结束执行器运行,不在 failed-unknown 后自动重放。 +- 证据/外部状态:本节为协调摘要(SELF),不替代执行器收据、evidence 或 GATE;没有部署或共享业务写入。任务/测试/Review/CI 各自状态分别记录。 + +### 收口交接(2026-10-18;U4/U7 已合入) + +本节为当时快照;U5、DM #27 与 U6 的最新进展见下方最终候选交接,不恢复已结束 run。 + +- 已合入:U4 #621(head `4b718344`,merge `825e4fc`),8 项离线事实用例及 503 项治理测试(18 skip)、独立 PI Review、exact-head CI;U7 #622(head `f61c53e7`,merge `2ce6bc9`),配置校验、6 项入口检查及同步 U4 后 14 项组合聚焦检查、独立 PI Review、exact-head CI。完整命令证据在各归档卡/仓外 receipt,不以本摘要代替。 +- 共享 U7:Guidelines #11 已合入 `a15db5085585f199c3c399824f6cf24c3144bbd5`(draft.14),独立 Review + CI run 3978;上下文压力与 scope-expansion 混用已修复,不新增 fail-close。Dev 最终 pin/采纳仍归 U6。 +- U5:`Agent-System-Dev-wt-feature-norm-remediation-u5`,实现候选 `1b04fd4a0408931bb84c44c2fe692205ff25079f`;只改 AGENTS/PROJECT,修复 LOCAL_READY 与分级证据摘要冲突;`/tmp/norm-u5-independent-review-receipt.json` 是在途独立 Review 收据,不在运行期修改该规范检出。 +- U6 直接阻塞:旧 cloneAttempt 不物化子模块,worker 又禁止 init/update/改父仓索引,无法按既有契约更新 gitlink。仅修共享执行器准备步骤,不新建架构/Schema/路由。DM 工作区 `/home/weichen/projects/Development-Manager-wt-fix-agent-exec-gitlink-preparation`,分支 `fix/agent-exec-gitlink-preparation`,基线 `b178dec`;首次 transport `b5098bc` 的真实临时仓库测试覆盖 transport/未声明模块/宿主不变/拒绝场景。 +- DM 在途修复:`/tmp/dm-gitlink-worktree-compatibility-repair.json` → `/tmp/dm-gitlink-worktree-compatibility-receipt.json`,补实际 linked-worktree metadata 位置及活动 index.lock 回归。首轮 receipt `/tmp/dm-gitlink-preparation-receipt.json`;完整 agent-exec suite 需在物化固定 Guidelines 的 host 工作区、测试子进程常规 umask 下重放,worker 隔离样例缺失/umask 负例的失败不可算 PASS。 +- 下一步:完成 U5 独立 Review/检查/child PR CI/合入;完成 DM 最小修复、独立安全/正确性 Review、当前 HEAD CI/合入;U6 用已审核固定执行器受控生成 pin 升级 transport(记录 requested/effective 执行器 SHA),经现有 apply/admit 核验,再固定 Guidelines/DM SHA、纠正采纳记录并进行一次最终完整组合验证。最终 initiative main 窗口前同步最新 main、Review、完整必需 CI、PR #616 合入及安全收口。 +- 无部署、无保护变更、无 tag 发布;未知副作用/未对账状态仍停止,其他授权工作继续。本节为协调摘要 SELF。 + +### 最终候选交接(2026-10-18) + +- U5 #623 已合入 `69174fcb6955f03d009dd83909da5a18373b1459`,最终 head `68233fd5` CI 通过;独立 PI Review 无阻塞,两个入口实际差异与主记录/链接检查已保存。U4/U5/U7 的已合入任务 worktree/本地远端分支均经 task-finish 安全回收,仅删除已确认 Python 字节码缓存。 +- DM 修复 #27 已合入 `6c019595fa02aa2d0a1a9b8f98fc224e69d2a901`,最终候选 `2bf86c9` 的 push/PR build-test CI success;代码 `910425e` 的全 Go 测试、controller race/vet 通过,安全与正确性两位独立 reviewer 关闭所有阻塞。原始及后续小修 packet/receipt `/tmp/dm-gitlink-*` 保留,不重放已完成运行。 +- U6 工作区 `/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation-u6`,parent head `69174fc`;两项 pin 经新固定执行器 `6c019595` 引导运行、旧 apply 校验后由 transport `97745ca` 成功集成(receipt `/tmp/norm-u6-pins-receipt.json`)。Guidelines=`a15db508` draft.14;DM=`6c019595`;host 物化后配置/治理检查全部 ok。 +- U6 采纳文档 transport `30f1ba7`(receipt `/tmp/norm-u6-adoption-docs-receipt.json`)已集成:两份采纳文档纠正版本和 #602 历史证据,PROJECT 预先保留 main eef7e5e 的法规组合/SQLite 入口且不复制动态 pin。当前在途 `/tmp/norm-u6-project-components-authority-receipt.json` 仅将 PROJECT 开头硬编码组件清单改为指向 .gitmodules(main 新增 renderer);不要在 worker 运行期间改该规范检出。 +- 最终 main 当前已核验 `eef7e5e778cabcb6216f74b74213a3d7511f383b`;只读 merge-tree 预检原来仅 PROJECT.md 文档冲突。U6 PROJECT 已由 worker 准备同时保留 main 新入口与 U5 简明索引的语义结果;L1 最终窗口再次核验冲突集,仅在仍只有该文档冲突时可用已审预制结果集成,其余路径正常合并。绝不丢弃 main 的业务/组件变化,最终物化 main 新组件后一次完整组合验证。 +- 下一步:U6 剩余小修终态→受控 apply→固定 pin/采纳/PROJECT 独立 Review 与聚焦校验→child PR/CI/合入;父卡子任务全部标已合入、领取 #518 main 窗口→同步最新 main并核对语义→最终 Review/完整 make test+build、全部必需 CI→PR #616 合入/核验/回收。真实供应方容量/压缩与模型质量前后基准仍 NOT_RUN,不能宣称已自动执行或已实测提升。 +- 推荐依据:待填写 +- 前置条件/待确认:待核对 +- 预期产物/验收条件:待定义 +- 重复任务核对:待核对(已有/在途任务、PR 与规划记录) +- 启动授权:未授权(建议不自动启动新任务,仍须明确授权) + +## 合入与清理 + +- Review 记录:全新独立 final_norm_correctness_review 与 final_norm_security_review 审阅 `eef7e5e..6b58060`,无阻塞;前者独立重放恢复/evidence 84 项,后者事实探针 8 项。二者随后对 `6b58060..f52df0e` 两文件增量 APPROVE,前者重放新增 5 项通过。源码外会话身份 not-observable;不是 CI GATE。后续仅任务元数据变化时核对 non-task diff 为空。 +- 冲突记录:同步 main `eef7e5e` 唯一冲突 PROJECT.md,使用已由 U6 worker 预制并独立审阅的语义合并结果,保留 main 法规/SQLite/renderer 入口及 U5 精简索引。实际合并和只读 merge-tree 的 PROJECT 内容一致;业务 modules、Makefile、workflow、.gitmodules 与 main 一致。同步及物化已完成,后续组合覆盖见 Evidence。 +- CI 记录:NOT_RUN(当前 HEAD 完整 SHA、全部必需检查、运行链接、执行身份及 GATE 结果) +- 合入记录:NOT_RUN(交付授权范围、PR、被核验 HEAD、合入 SHA、目标分支及内容核验) +- 清理记录:NOT_RUN(授权依据、写者退出确认、worktree/分支/任务卡处置) + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T09:24:17.082155+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev", + "before_head": "b5e23daed2f0ea46261979de1d62e4de0cfa88f9", + "after_head": "b5e23daed2f0ea46261979de1d62e4de0cfa88f9", + "branch": "main", + "target_branch": "main", + "target_head": "b5e23daed2f0ea46261979de1d62e4de0cfa88f9", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev'", + "[repo] branch=main head=b5e23daed2", + "[remote] origin/main=b5e23daed2 ahead=0 behind=0", + "[tree] clean submodules_drift=0 uninit=3", + "[warn] 3 个子模块未初始化——构建/测试前需物化(不阻断写入)", + "[worktrees] linked=10", + "[recycle] merged un-recycled branches: feature/layered-python-env", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] b5e23daed2(完整:b5e23daed2f0ea46261979de1d62e4de0cfa88f9)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev'" + ], + "output_sha256": "6e552710cd7e401d0977d3d0b58a5e1f85dfb9ea08513c912be0778b1beb3733", + "receipt_sha256": "287ec384589843c0b2ee411ffc97383e668ac7c9121fda7eb391125cd1dd19b9", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/287ec384589843c0b2ee411ffc97383e668ac7c9121fda7eb391125cd1dd19b9.json" +} +``` +| 同步(记录前对象) | eef7e5e778cabcb6216f74b74213a3d7511f383b | fd00a06096a27750c76eb202ec5515d2ac9379b6 | 2026-10-18T18:47:17Z | + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T19:08:02.437026+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation", + "before_head": "f52df0e2e6bd52d1b60989ac079826787fef3307", + "after_head": "f52df0e2e6bd52d1b60989ac079826787fef3307", + "branch": "feature/norm-remediation", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'", + "[repo] branch=feature/norm-remediation head=f52df0e2e6", + "[remote] origin/main=eef7e5e778 ahead=107 behind=0", + "[tree] clean submodules_drift=0 uninit=0", + "[worktrees] linked=7", + "[recycle] merged un-recycled branches: feature/layered-python-env", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] eef7e5e778(完整:eef7e5e778cabcb6216f74b74213a3d7511f383b)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'" + ], + "output_sha256": "d364561ba0a811ef8826b0db35d156ff5a561b7e899550048512eb71f5e45b66", + "receipt_sha256": "f2e57a2a883a1693f1009af263a335b2df431dc7d433ad7ef0c868eb3b197a74", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/f2e57a2a883a1693f1009af263a335b2df431dc7d433ad7ef0c868eb3b197a74.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:47:45.105555+00:00", + "command": "make precheck", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation", + "before_head": "6b5806078f5119fd016465eba71f8fca490479fe", + "after_head": "6b5806078f5119fd016465eba71f8fca490479fe", + "branch": "feature/norm-remediation", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "make[1]: Entering directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'", + "[repo] branch=feature/norm-remediation head=6b5806078f", + "[remote] origin/main=eef7e5e778 ahead=106 behind=0", + "[tree] clean submodules_drift=0 uninit=0", + "[worktrees] linked=7", + "[recycle] merged un-recycled branches: feature/layered-python-env", + "[initiatives] local cards (not authoritative): none", + "[hooks] core.hooksPath=scripts/hooks(pre-commit/post-merge 实际接线)", + "[facts] policy/facts.md 6 条事实均在有效期内", + "[required-reading] core/baseline.md, workflows/delivery.md §1, policy/delivery.md", + "[R0] eef7e5e778(完整:eef7e5e778cabcb6216f74b74213a3d7511f383b)", + "[worktree] before-sync: recursive component checks passed", + "[verdict] READY — 保留本次 precheck receipt 后建任务工作区:PRECHECK_RECEIPT=/tmp/task-precheck.json make task-start NAME=feature/your-branch", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'" + ], + "output_sha256": "d6c359174171f0e7759d550c5236c6909b41ee78ad1b4fb9d7b2c0975fc312d3", + "receipt_sha256": "5bbd5a7dbd32f936a2c3c0b21b1675d0f54747a882ff72e15bbbb5a8a4209a92", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/5bbd5a7dbd32f936a2c3c0b21b1675d0f54747a882ff72e15bbbb5a8a4209a92.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:46:23.709451+00:00", + "command": "GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=branch.feature/norm-remediation.mergeOptions GIT_CONFIG_VALUE_0=-Xours make task-sync", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation", + "before_head": "3812031e6b481b77c5106dd40dbfed243e396504", + "after_head": "6b5806078f5119fd016465eba71f8fca490479fe", + "branch": "feature/norm-remediation", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "Cloning into '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation/modules/ui/deps/development-guidelines'...", + "Submodule path 'deps/development-guidelines': checked out '2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb'", + "Submodule 'deps/development-manager' (ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Development-Manager.git) registered for path 'deps/development-manager'", + "Cloning into '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation/modules/ui/deps/development-manager'...", + "Submodule path 'deps/development-manager': checked out 'bd3a7de57a3a9e1a4495b94c5fa5c08d71e7be8c'", + "Submodule 'development/guidelines' (ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Development-Guidelines.git) registered for path 'development/guidelines'", + "Cloning into '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation/modules/ui/deps/development-manager/development/guidelines'...", + "Submodule path 'development/guidelines': checked out '2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb'", + "[worktree] materialize: recursive component checks passed", + "[worktree] after-sync: recursive component checks passed", + "admit: ok", + "[evidence] WARN JSON receipt line 172: unverified: recorded component state was not at its pinned SHA ——关键检查须重跑并以 make evidence 重新写入", + "[synced] sync_base=eef7e5e778cabcb6216f74b74213a3d7511f383b tested_head=6b5806078f5119fd016465eba71f8fca490479fe checked_at=2026-10-18T18:47:17Z changed=1 sync_target=main", + "[next] 跑正式验收(make test 等),证据写入任务卡", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'" + ], + "output_sha256": "1eb6e513009a02e1a0d606f2539454e81f799fe4a2b5c1e3b932ac528f58cd07", + "receipt_sha256": "655b4efe3346053c96ef2361246295488f388ab4e366cfcd9116ee656ee684ae", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/655b4efe3346053c96ef2361246295488f388ab4e366cfcd9116ee656ee684ae.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:47:52.335693+00:00", + "command": "make test && make build", + "exit_code": 2, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation", + "before_head": "6b5806078f5119fd016465eba71f8fca490479fe", + "after_head": "6b5806078f5119fd016465eba71f8fca490479fe", + "branch": "feature/norm-remediation", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "ok modules/backend logging-system", + "ok deps/agent-native agent-intent", + "warn: deps/agent-native deps/development-guidelines is 26b562d2e34f, Dev is a15db5085585", + "ok deps/agent-native error-code-system", + "ok deps/agent-native logging-system", + "ok deps/agent-squad agent-intent", + "ok deps/agent-squad agent-native", + "warn: deps/agent-squad deps/development-guidelines is 26b562d2e34f, Dev is a15db5085585", + "ok deps/agent-squad error-code-system", + "ok deps/agent-squad logging-system", + "warn: deps/agent-intent deps/development-guidelines is 26b562d2e34f, Dev is a15db5085585", + "ok deps/agent-intent error-code-system", + "ok deps/agent-intent logging-system", + "make[1]: *** [Makefile:201: test-pins] Error 1", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'" + ], + "output_sha256": "106c862765dde13ad3e284b434c5f1678f259de28f9ac3ebfd20f71e38a47608", + "receipt_sha256": "4247d20139741ec1b96f2213cf5194d3e05cd536da53939e1d68e21097c873cf", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/4247d20139741ec1b96f2213cf5194d3e05cd536da53939e1d68e21097c873cf.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:56:50.650054+00:00", + "command": "python3 -m unittest tests.test_check_pins && make test-pins && git diff --check", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation", + "before_head": "f52df0e2e6bd52d1b60989ac079826787fef3307", + "after_head": "f52df0e2e6bd52d1b60989ac079826787fef3307", + "branch": "feature/norm-remediation", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "ok modules/backend logging-system", + "ok deps/agent-native agent-intent", + "warn: deps/agent-native deps/development-guidelines is 26b562d2e34f, Dev is a15db5085585", + "ok deps/agent-native error-code-system", + "ok deps/agent-native logging-system", + "ok deps/agent-squad agent-intent", + "ok deps/agent-squad agent-native", + "warn: deps/agent-squad deps/development-guidelines is 26b562d2e34f, Dev is a15db5085585", + "ok deps/agent-squad error-code-system", + "ok deps/agent-squad logging-system", + "warn: deps/agent-intent deps/development-guidelines is 26b562d2e34f, Dev is a15db5085585", + "ok deps/agent-intent error-code-system", + "ok deps/agent-intent logging-system", + "pins ok", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'" + ], + "output_sha256": "abd51bb25efcdd4dcfef701a353d7e5f35641345297e1e33bdc2292f237b8c14", + "receipt_sha256": "df2b0d485c916e6477da5364acf963343e153d5a7c3def2027e8f458ae0f9acb", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/df2b0d485c916e6477da5364acf963343e153d5a7c3def2027e8f458ae0f9acb.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T18:57:33.241013+00:00", + "command": "make test && make build", + "exit_code": 2, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation", + "before_head": "f52df0e2e6bd52d1b60989ac079826787fef3307", + "after_head": "f52df0e2e6bd52d1b60989ac079826787fef3307", + "branch": "feature/norm-remediation", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "用法:identity --instance-id ID [--dbref REF] [--data DIR] [--dsn-file FILE]", + "ensured instance_id=svc-t1-abcdef123456 dbref=sqlite", + "identity verify: system identity mismatch: 库内 instance_id=\"svc-t1-abcdef123456\" dbref=\"sqlite\" ≠ 期望 \"svc-t1-zzzzzz999999\" / \"sqlite\"", + "identity verify: system identity mismatch: 库内 instance_id=\"svc-t1-abcdef123456\" dbref=\"sqlite\" ≠ 期望 \"svc-t1-abcdef123456\" / \"pg\"", + "identity verify: identity database missing: /tmp/TestVerifyMissingDBCreatesNothing3410590918/001/absent/agent-system.db", + "--- FAIL: TestDSNFileMode (0.00s)", + " identity_test.go:149: 0644 的 dsn 文件必须拒绝", + "ensured instance_id=svc-dsnenv-abcdef123456 dbref=sqlite", + "verified instance_id=svc-dsnenv-abcdef123456 dbref=sqlite schema_version=50", + "FAIL", + "FAIL\tagent-system-backend/pkg/identity\t0.108s", + "? \tagent-system-backend/web\t[no test files]", + "FAIL", + "make[1]: *** [Makefile:150: test-backend] Error 1", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'" + ], + "output_sha256": "54498f0a9e266fc85b0d82ab4ef90b019fcad772710ec316d72be3681328b15f", + "receipt_sha256": "b0ceece861607937be75e2bb2bd105e099356e25ee58063cd0e523ed0aa621c0", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/b0ceece861607937be75e2bb2bd105e099356e25ee58063cd0e523ed0aa621c0.json" +} +``` + + +```json +{ + "summary_schema": 1, + "source": "SELF", + "timestamp": "2026-10-18T19:05:24.855184+00:00", + "command": "umask 022; (cd modules/backend && go test ./pkg/identity -run TestDSNFileMode -count=1) && make test -o test-governance -o test-boundaries -o test-pins -o test-libs -o test-core && make build", + "exit_code": 0, + "cwd": "/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation", + "before_head": "f52df0e2e6bd52d1b60989ac079826787fef3307", + "after_head": "f52df0e2e6bd52d1b60989ac079826787fef3307", + "branch": "feature/norm-remediation", + "target_branch": "main", + "target_head": "eef7e5e778cabcb6216f74b74213a3d7511f383b", + "root_target": "main", + "immediate_target": "main", + "before_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "after_tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "output_tail": [ + "dist/assets/index-CNWTiBCO.js 213.14 kB │ gzip: 70.03 kB", + "dist/assets/xlsx-CkFp8p6R.js 429.53 kB │ gzip: 143.07 kB", + "dist/assets/index-Bbgu8RGY.js 739.00 kB │ gzip: 229.89 kB", + "dist/assets/pptx-preview.es-C3efH30Z.js 1,255.23 kB │ gzip: 401.46 kB", + "", + "(!) Some chunks are larger than 500 kB after minification. Consider:", + "- Using dynamic import() to code-split the application", + "- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks", + "- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.", + "✓ built in 5.23s", + "rm -rf modules/backend/web/dist && mkdir -p modules/backend/web/dist && cp -r modules/frontend/dist/* modules/backend/web/dist/ && touch modules/backend/web/dist/.gitkeep", + "cd modules/backend && CGO_ENABLED=0 go build -trimpath -ldflags \"-s -w -X main.version=v0.4.0-941-gf52df0e+core=1a5ef6b+backend=646dc57+frontend=1650b4f+ui=5c2b103+agentcmd=4f583ca+documents=1e036e9+native=c348441+squad=490b0b2+intent=343b1e6\" -o ../../bin/agent-system ./cmd/agent-system", + "cd modules/backend && CGO_ENABLED=0 go build -trimpath -ldflags \"-s -w\" -o ../../bin/storage-migrate ./cmd/storage-migrate", + "OK: bin/agent-system(版本 v0.4.0-941-gf52df0e+core=1a5ef6b+backend=646dc57+frontend=1650b4f+ui=5c2b103+agentcmd=4f583ca+documents=1e036e9+native=c348441+squad=490b0b2+intent=343b1e6;随附插件目录 plugins/)+ bin/storage-migrate(协作存储 U4 迁移工具)", + "make[1]: Leaving directory '/home/weichen/projects/Agent-System-Dev-wt-feature-norm-remediation'" + ], + "output_sha256": "8301e75d16b6f4ecaac861c9a25ac51d93fb7431a5f3201a84d1c89e641d0296", + "receipt_sha256": "1356559c9425ba978cb3c9c720d8373ddbca2a562c3cc7bab876343e188a9108", + "receipt_path": "/home/weichen/.cache/agent-system-dev/evidence/1356559c9425ba978cb3c9c720d8373ddbca2a562c3cc7bab876343e188a9108.json" +} +``` + +## 闭合记录(合入前折叠) + +- 本卡在 PR #616(https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/616)合入前闭合并归档,随该 PR 一同合入。 +- 该 PR 的合并提交即本任务的落地记录。 +- 合入后无需回填,也无需单独的闭合 PR。 diff --git a/AGENT-EXECUTION.md b/AGENT-EXECUTION.md index 8d5113c2..e3c0b7fd 100644 --- a/AGENT-EXECUTION.md +++ b/AGENT-EXECUTION.md @@ -18,8 +18,8 @@ increment host fallback authorizations. Safe exhaustion without an emergency candidate ends `unavailable` or `blocked`, and that result does not authorize the host to write the unit. The legacy value `all-unavailable` remains parseable and likewise grants no host write authority. Cursor uses -`cursor-agent-stable`. `model: auto` lets each CLI use its configured default -model; PI therefore uses the operator-selected MiniMax default. The +`cursor-agent-stable`. `model: auto` lets a CLI use its configured default +model; any other value is passed to that CLI as its model selector. The `integration` block declares the host-exempt paths and the transport, admission, and cleanup entry points. @@ -60,7 +60,7 @@ profiles: pi: adapter: pi executable: pi - model: auto + model: minimax-cn/MiniMax-M3 enabled: true codex: @@ -78,3 +78,38 @@ integration: cleanup: command: make task-finish ``` + +## Model preferences (project choice) + +Generic rules for bounded packets, context budgeting, phase handoff and +requested-versus-effective model identity live in the shared +[standards/agent-execution.md](deps/development-guidelines/standards/agent-execution.md) +and [workflows/handoff.md](deps/development-guidelines/workflows/handoff.md); +project procedure is in +[policy/delivery.md](policy/delivery.md#有界单元包上下文与阶段交接). This +section records only this project's choices. + +- Planning, design and implementation plans: Codex or Claude when available, + in the host or a fresh CLI session. This is a preference, not a routing role. +- Bounded implementation, repair and test units: the existing normal + candidates in the configured role order above. Requested models: + - PI: `minimax-cn/MiniMax-M3`, passed as `--model provider/id`. PI + actual events have reported `minimax-cn` / `MiniMax-M3` and the local + offline catalog lists that profile; each run still records the effective + model from that run's events. + - Cursor: `auto` (Cursor Auto). + - agy: Gemini 3.8 Flash is requested, but its exact selectable model ID and + effective identity are `NOT_VERIFIED` (the `agy` models query needs a + login that was not available). The profile therefore stays `model: auto` + until an observed model ID exists; do not claim Gemini 3.8 Flash is active. +- Assigning a different candidate or model to a unit needs a concrete, + task-specific reason recorded in the task record, and stays within the + existing roles and candidate lists. It is not a silent permission or routing + change. Role lists, attempt limits, timeouts, output limits, the single + Codex emergency attempt, stage isolation and `fallback_to_host: never` are + unchanged by these preferences. +- A requested model is prompt/config intent. Only the model reported in a + run's actual event envelope counts as effective; when the CLI does not report + it, record `not-observable`. Unknown context capacity, a missing agy login + or missing automatic compaction are recorded facts, not global fail-closed + conditions. diff --git a/AGENTS.md b/AGENTS.md index 6b7d522a..5dbbe54c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,95 +1,81 @@ # Agent 工作说明(Agent-System-Dev) -本仓开发遵循 Development-Guidelines(见 `deps/development-guidelines`,gitlink 固定版本): -核心规则与术语见其 `core/`,按任务触发阅读 `standards/` 专题;通用交付依据为 `workflows/delivery.md`。 -本项目的需求、设计试验、实施调整及交付**必读 [policy/delivery.md](policy/delivery.md)**(交付规则唯一权威): -目标、范围和交付终点一次确认;范围内的分析、方案、实施、修复、验证、代码 Review、PR 合入及安全收口由智能体连续推进。 -只在目标/范围/验收条件变更、存在会影响用户结果的重大取舍、触及高后果操作,或业务验收必须由用户判定时请求人工决策。 -智能体解决冲突并确认当前 HEAD 的 CI 通过后执行合入,无需另请人工操作。本文件是入口摘要,细则以所链文档为准。 +本文件是入口摘要:只列强制原则与触发式必读,细则与确切命令以所链权威文档为准。 +通用规范为固定版本的 Development-Guidelines(`deps/development-guidelines`):核心规则与术语见 `core/`, +按任务触发阅读 `standards/` 专题,通用交付依据为 `workflows/delivery.md`。 -## 立即生效的最高优先级(强制) +## 必读路由(强制) -- **先交付用户产物**:在既有授权与强制安全边界内,先交付用户明确要求的产物;不得用基础设施、工具治理、通用化、加固、重构或“以后可能用到”替代当前目标,禁止把简单 Markdown、配置或说明修改擅自升级成平台、框架、自动化系统或大规模工程。 -- **最小充分实现**:目标可用、系统可用并通过最小直接验证后立即停止并交付;体验、结构、性能与治理完善作为后续独立增量,不得拖延当前结果。 -- **只修阻塞项**:只修复直接阻塞目标、可用性、明确验收条件或强制安全门禁的问题;高价值非阻塞项只简记,低影响噪音直接忽略;Review/迭代只围绕阻塞项,阻塞项闭合即结束。 -- **测试与风险成比例**:实施中只跑最小直接检查;完整/长耗时套件仅在最终候选、规范明确要求或影响面确有必要时运行一次,禁止每个小文档、配置或 adapter 修改后重复全量测试。 -- **编排成本不得超过任务本身**:按 AGENT-EXECUTION.md 的候选和尝试上限调度,不限制单次 CLI 的推理时长、工具次数、规划方式或首次写文件时间;除非用户明确要求或有证据证明直接阻塞,不新增嵌套控制器、额外 worktree、复杂收据体系或新架构;执行路由不能成为推迟用户产物的理由。细则见 [最高优先级](policy/delivery.md#最高优先级目标兑现与最小充分交付)。 +| 触发 | 权威 | +|---|---| +| 需求、设计试验、实施调整与交付;阶段、决策点、优先级、分支拓扑、Review/合入、合入调度、清理 | [policy/delivery.md](policy/delivery.md)(交付规则唯一权威) | +| 任务分流、启动/恢复、同步、PR 检查、证据写入、任务卡闭合、回收及其确切命令 | [policy/governance.md](policy/governance.md) | +| 实施/修复/测试/Review 的候选、模型、尝试上限、fail-stop 与会话路由 | [AGENT-EXECUTION.md](AGENT-EXECUTION.md) 与 [standards/agent-execution.md](deps/development-guidelines/standards/agent-execution.md) | +| 引用外部 API、Git/SSH 端点或凭据可用性 | [policy/facts.md](policy/facts.md) | +| 任何界面生成或修改 | [DESIGN.md](DESIGN.md) | +| 项目组成、构建与验证命令、方向规划入口 | [PROJECT.md](PROJECT.md) | -## 任务目标与范围(强制) +## 最高优先级:目标兑现与最小充分交付(强制) -- 优先级固定为:授权与安全边界 → 本次任务目标和系统可用 → 阻塞目标的必要问题 → 非阻塞改进。完成标准是已批准验收条件成立且受影响系统保持可用,不是清零所有附近问题。 -- 绝对禁止私自扩大范围:影响目标的问题只在既有授权内作最小充分修复;必要修复超出范围时,说明阻塞、最小扩围和影响,取得授权后再做。“相关”“顺手”“更完美”不构成扩围授权。 -- 新发现按影响分流:阻塞验收、导致不可用/明确回归、违反安全边界或必需门禁失败的必须处理;其余不阻塞交付,只有明确影响和后续价值的才记录。先实现端到端可用的最小闭环,非阻塞 Review 意见不阻止交付(见 [问题分流](policy/delivery.md#目标优先范围控制与问题分流))。 +- 优先级固定为:授权与安全边界 → 本次任务目标和系统可用 → 阻塞目标的必要问题 → 非阻塞改进。 +- 目标、范围和交付终点一次确认;范围内的分析、方案、实施、修复、验证、代码 Review、PR 合入及安全收口由智能体连续推进。 +- 只在目标/范围/验收条件变更、影响用户结果的重大取舍、高后果操作,或业务验收必须由用户判定时请求人工决策。 +- 先交付用户明确要求的产物;不得用基础设施、工具治理、通用化、加固、重构或“以后可能用到”替代当前目标,不把简单修改升级成平台、框架或自动化工程。 +- 最小充分实现:目标与受影响系统可用、最小直接验证通过即停止并交付;完善作为后续独立增量。 +- 绝对禁止私自扩大范围:只修复阻塞目标、可用性、明确验收或强制安全门禁的问题;必要修复超出范围时,说明阻塞、最小扩围和影响,取得授权后再做。“相关”“顺手”“更完美”不构成授权;非阻塞高价值项只简记,噪音忽略,Review 只围绕阻塞项。 +- 测试与编排成比例:实施中只跑最小直接检查,完整套件只在最终候选或确有必要时运行一次;编排成本不得超过任务本身。 +- 细则:[最高优先级](policy/delivery.md#最高优先级目标兑现与最小充分交付)、[授权与决策点](policy/delivery.md#授权决策点与连续执行)、[问题分流](policy/delivery.md#目标优先范围控制与问题分流)。 -## 实施执行路由(强制) +## Agent 实施与独立 Review(强制) -- 调度**必读 [AGENT-EXECUTION.md](AGENT-EXECUTION.md)**(唯一项目执行路由配置);通用契约遵循 guidelines [core/baseline.md(硬边界 8)](deps/development-guidelines/core/baseline.md) 与 [standards/agent-execution.md](deps/development-guidelines/standards/agent-execution.md)。产生仓库变更的实施按可独立实施的单元拆分,任务卡记 `实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)`。 -- 外部 Agent 在新进程/隔离工作区执行一个有界单元;host 负责计划、拆解、范围审阅、权威验证、规范提交、PR/CI/合入、证据与任务元数据及安全清理。Worker 不得提交、推送、开或合 PR、部署、改路由或派遣同伴。 -- 实施、修复与测试的正常候选只有 Cursor、Antigravity(`agy`)和 PI,顺序以配置为准;每个候选对安全可恢复失败(`unavailable`,或外部无副作用已被机制证明的 `failed-clean`)最多 3 次全新尝试后才换候选。`network=false`/`external_cost=false` 只是声明;没有强制隔离或可信对账时,已启动的失败尝试记 `failed-unknown`。成功须从该 CLI 实际事件信封解出有效的 `completed` 终态记录,缺失或无效即 fail closed。 -- 三者都安全耗尽后才允许一次全新的 Codex CLI 紧急尝试;Codex 平时只用于规划、方案设计、实施计划或独立 Review。`fallback_to_host` 为 `never`(旧值 `all-unavailable` 同样不授权):安全耗尽不发出 host-fallback-authorized,终态为 unavailable 或 blocked,host 不撰写该单元。 -- 显式 fail-stop 立即停止,不重试、不换候选、不启动紧急尝试:`failed-unknown`、`scope-expansion`、`blocked`、`cancelled`、无效配置、未对账 Git 状态与安全失败;总超时及 stdin/stdout/stderr 都无活动的空闲超时归入 `failed-unknown`。一次尝试只在显式终态失败、进程退出、总超时或空闲超时时结束,CLI 可在目标、范围与安全边界内自主检查、计划、测试并迭代。 -- 实施变更只经成功 transport 由 `scripts/agent-exec apply` 集成,或由 pre-commit `admit` 按精确内容核对。本地 HMAC 历史与该钩子只是本地完整性/篡改证据(SELF),不是 GATE、可信身份,也不防同一主机 OS 用户;本地记录缺失或无效仍拒绝,交付信任只来自精确 HEAD 的 CI 或独立 REPLAY。host 直接管理的豁免只有 `.task/**`、`docs/superpowers/specs/**` 与 `docs/superpowers/plans/**`,host 不直接撰写实施单元。细则见 [policy/delivery.md](policy/delivery.md#agent-实施单元与主模型职责) 与 [policy/governance.md](policy/governance.md);不削弱 goal-first、严格范围、先可用、独立 Review、证据、平台准入与高后果双钥匙规则。 +- 产生仓库变更的实施按可独立实施的单元交给外部 Agent,任务卡记 `实施执行:agent-per-unit(配置:AGENT-EXECUTION.md)`。 +- host 负责计划、拆解、范围审阅、权威验证、规范提交、PR/CI/合入、证据与任务元数据及安全清理;worker 不得提交、推送、开或合 PR、部署、改路由或派遣同伴。 +- `fallback_to_host` 为 `never`:候选安全耗尽时终态为 unavailable 或 blocked,host 不撰写该单元;显式 fail-stop 立即停止;成功须有有效的 `completed` 终态记录。 +- 实施变更只经成功 transport 的 `scripts/agent-exec apply` 或 pre-commit `admit` 精确核对进入分支;host 直接撰写的豁免只有 `.task/**`、`docs/superpowers/specs/**`、`docs/superpowers/plans/**`。 +- 不同阶段、不同单元各用全新会话;Review 由当前主模型或全新独立 CLI session 执行,禁止实施者经 continue/resume/session 等机制续作评审;无法证明会话独立时不得标为独立 Review。 +- 细则:[Agent 实施单元](policy/delivery.md#agent-实施单元与主模型职责)、[会话隔离与独立 Review](policy/delivery.md#阶段会话隔离与独立-review);通用契约见 guidelines [core/baseline.md(硬边界 8)](deps/development-guidelines/core/baseline.md)。 -### 阶段与会话隔离(强制) +## 任务生命周期(强制) -- 需求分析、方案、设计、计划、实施、测试、Review 与验收默认各启动一个全新会话;不同阶段、不同实施单元不得复用同一 CLI 会话、conversation、thread 或 session。 -- Review 由当前主模型或**全新且独立**的 CLI session 执行;绝对禁止实施者通过 `continue`、`resume`、conversation/session ID、session 路径或等价机制继续充当评审者。 -- 评审只接收固定的目标/需求、base/head、实际 diff、测试与证据,默认不转交实施聊天、思维过程、中间自评或原会话历史;确需补充时只提供可复核产物。 -- 每阶段记录工具、模型、stage、attempt/session 身份及 `fresh_session`;工具不暴露 session ID 时标 `not-observable`,但仍须新进程并禁用恢复/续聊;无法证明会话独立时不得标为独立 Review(见 [会话隔离](policy/delivery.md#阶段会话隔离与独立-review))。 - -## UI 设计门禁(强制) - -- 本仓一切界面生成/修改**先读根目录 `DESIGN.md`**(四原则:美观、现代、简洁、易用)再动手;违反视为设计系统缺陷,不是风格偏好。 -- 颜色/圆角/字号只消费 `@ui/tokens.css` 令牌(字号走 `--fs-ui-*` 标尺);动效只用 `@ui/motion.css` 的 `mk-*` 工具类;折叠一律用 `@ui` Collapsible 原语,禁止 `
`。 -- 守护测试(UI 仓与 Frontend 仓各一份 `design-guard.test.ts`)以棘轮拦截新增裸色值、私有 `@keyframes` 与行内字号;风格调整须在同一变更集中关联 UI 令牌 PR 与 Dev 的 `DESIGN.md`/gitlink 组合 PR,并验证精确组件 SHA(跨仓不要求两个文件出现在同一 PR)。 -- 异步状态只有一个所有者。事件按 seq 处理,过期事件丢弃。 - -## 任务启动门禁(强制) - -- **新需求分流**:创建分支前以远端为准运行 `make task-triage REQ='<需求>' TRIAGE_RECEIPT=<路径>` 检查接纳中的大任务(`branch_kind=initiative` 且 `accepting_requirements=true`);有候选时运行 `make task-triage-decide TRIAGE_RECEIPT=<路径> VERDICT=JOIN|NEW … REASON='<理由>'`;`make task-start … TRIAGE_RECEIPT=<路径>` 核验收据通过后才建分支。`JOIN` 建子任务(`KIND=child INITIATIVE=feature/ NAME=feature/-`),`NEW` 建普通任务或新大任务(`[KIND=initiative] NAME=feature/`),`DECISION_REQUIRED` 为有候选时的待决策状态,改变范围/优先级的加入由人工决策。已登记在父卡 `## 子任务登记` 的计划内 L2 子任务可免分流收据。 -- **启动检查**:启动或恢复后、第一次项目写操作前运行一次 `RECEIPT=/tmp/<本次唯一名称>.json CMD="make precheck" make evidence` 保存完整脱敏收据并按输出完成必读;`make task-start` 省略 `PRECHECK_RECEIPT` 时自动捕获,恢复在原 worktree 用 `make task-resume PRECHECK_RECEIPT=<路径>`。`SHARED_WORKSPACE_BUSY / SYNC_REQUIRED / REMOTE_UNVERIFIED` 均不得进入写入流程,失败后排除原因再启动,不把缓存远端当最新事实。默认要求严格 READY,任务中途不重复;局部恢复见 [开发门禁](policy/governance.md#启动与恢复)。 -- **分支层级**:一律从权威 `origin/main` 切,同批互不依赖的子任务用同一 R0 的兄弟分支。确有顺序依赖的堆叠例外:加 `PARENT=feature/<父slug> PARENT_PR=<父PR链接>`,卡记主干 R0、父分支及 SHA、父 PR,命名 `/<父slug>-<子题>` 不嵌 SHA,合入顺序父→子,父合入后子须 `make task-sync` 并把 PR base 改回 main 才可合入(见 [policy/delivery.md](policy/delivery.md) 与 [开发门禁](policy/governance.md#启动与恢复))。 -- **同步与 PR 门禁**:验收、请求审核、合入前各运行一次 `make task-sync` 与 `make task-pr-check`(大任务收口用 `FINAL=1`);三段完成证据由 `make evidence` 写入任务卡。层级、任务通道与分级门禁见 [层级与分级门禁](policy/delivery.md#层级任务通道与分级门禁),多分支交付的最终窗口见 [合入调度](policy/delivery.md#合入调度)。 -- **回收**:合入后核对所有写者退出;能从当前会话、进程、锁与工作区状态证明时,智能体在**已登记任务 worktree 根目录**运行 `TASK_WRITERS_STOPPED=1 make task-finish`,无法证明时才请 owner 确认。父子仓改动、未跟踪/ignored 唯一文件先保存或显式处置;删除被拒时保留现场,禁止强删兜底。放弃未合入工作用 `make task-finish ABANDON="--abandon <原因>"` 并记录原因,不豁免现场保护。 -- **任务卡闭合**:默认在最终同步/合入前于任务 worktree 运行 `make task-close SELF=1`(卡改为“已完成(合入前闭合)”、写入本 PR 链接并归档,随同一 PR 合入;会提交并推送本分支,只处理本分支的卡)。post-merge 钩子仅提示。主检出 `make task-close` 或 `FOLD=--fold` 只用于已合入未闭合的遗留卡,须核对只含目标卡,其闭合 PR 同样经智能体 Review、冲突处置与当前 HEAD CI 后合入。precheck `[cards]` 欠账验收前须清零。 +- 新需求建分支前以远端为准分流;启动或恢复后、第一次项目写操作前保存一次启动检查收据。 +- 新建任务须以权威远端分流并取得严格 READY(`SHARED_WORKSPACE_BUSY / SYNC_REQUIRED / REMOTE_UNVERIFIED` 均不得建任务),不把缓存远端当最新事实;已登记任务的恢复与局部继续按 [启动与恢复](policy/governance.md#启动与恢复),为消除落后所需的同步按 [被测对象](policy/governance.md#被测对象) 执行,READY 不是执行该同步的前提。正式 Review 与合入须先完成新鲜的权威目标同步并满足适用交付门禁。LOCAL_READY 只是 SELF 局部工作记录,不能替代建任务、正式交付、CI、GATE 或 REPLAY 的准入或证据;脏现场、未对账或副作用未知时仍须停止。 +- 分支从权威远端目标切出(子任务从所属大任务分支),同批互不依赖的子任务用同一 R0 的兄弟分支;堆叠例外按门禁文档执行。 +- 验收、请求审核、合入前各同步一次并做 PR 检查;任务卡默认合入前闭合并随同一 PR 合入;precheck `[cards]` 欠账验收前须清零。 +- 智能体完成 Review、解决冲突并确认当前 HEAD 必需 CI 通过后执行合入,无需另请人工操作。 +- 合入后仅在已登记任务 worktree 根目录、确认所有写者退出后回收;父子仓改动与未跟踪/ignored 唯一文件先保存或处置,删除被拒时保留现场,禁止强删兜底。 +- 放弃未合入工作用 `make task-finish ABANDON="--abandon <原因>"` 并记录原因,不豁免现场保护。 +- 首次克隆后运行 `make setup` 启用本地钩子;治理脚本改动须运行 `make test-governance`(见 [持续回归](policy/governance.md#持续回归))。 +- 命令与细则:[启动与恢复](policy/governance.md#启动与恢复)、[回收](policy/governance.md#回收)、[两级分支与分流](policy/delivery.md#两级分支交付模型与分流检查)、[分级门禁](policy/delivery.md#层级任务通道与分级门禁)、[合入调度](policy/delivery.md#合入调度)、[Review 与合入](policy/delivery.md#智能体-review-与-pr-合入)。 ## 证据与验证门禁(强制) -- **声明不是证据**:「声明已就位」≠「能力已就位」;任何来源的结果声明都不是证据,证据 = 命令 × 被测对象 SHA × 可复核输出(guidelines `standards/verification.md`「声明与证据来源」)。 -- **来源三分**:通过类断言标注 `GATE`(可核验身份及固定对象的 CI/受信执行器)/ `REPLAY`(独立审阅者的重跑记录)/ `SELF`(作者或本地工具记录,含 precheck)。工具写入、摘要和标签不提升可信等级,本地 evidence 只生成 SELF,不能靠 `SRC` 升级;合入与验收须核验 GATE 的运行链接/身份及 SHA,或 REPLAY 的审阅者、命令和对象,与重验不一致以重验为准。 -- **证据由工具写入**:命令输出只经 `CMD="<命令>" make evidence` 写入(卡内写摘要块,完整脱敏收据一次性存于仓外,见 [开发门禁](policy/governance.md#本地记录与独立证据));凭据只经环境或受保护文件引用。手抄、转写、凭记忆重构的输出无效,补写输出与伪造测试同级红线。 -- **完成声明三段输出**(precheck / task-sync / 测试)由 evidence 写入任务卡,PR 描述引用不另手抄;测试通过的权威源是 CI(`.gitea/workflows/ci.yaml` 在 PR HEAD 上 `make test`+`make build`),本地输出只是辅助线索。 -- **验收即重放**:请求审核后,审阅者先在评审环境重放最小重放集(关键门禁命令 + 变更直接相关检查)再读作者证据;不一致直接阻塞并登记为失实事件。 -- **外部事实零信任**:用户/文档/旧报告中的“可用/已就位”须先执行 `policy/facts.md` 对应验证命令或当场实测才能写入产出物,无法复核标 `[未经证实]`;登记项带 `last_verified` 与 TTL,precheck `[facts]` 报告过期项,`make facts-verify` 逐条重验。 -- **诚实出口**:未执行/未验证是合法状态,如实标注(NOT_RUN/未验证)优于补写。 +- 声明不是证据:证据 = 命令 × 被测对象 SHA × 可复核输出;用户、文档、旧报告或工具摘要中的“可用/已就位”都须先复核。 +- 来源三分 `GATE` / `REPLAY` / `SELF`;本地 evidence 只生成 SELF,标签不提升可信等级;交付信任只来自精确 HEAD 的必需 CI 或独立 REPLAY。 +- 命令输出只经 `make evidence` 写入任务卡;手抄、转写或凭记忆重构的输出无效,补写输出与伪造测试同级红线。 +- 各层级要求的证据与 CI 以 [分级门禁表](policy/delivery.md#层级任务通道与分级门禁) 为准:L2→L1 不要求三段证据;推进 `main` 的交付仍须三段输出(precheck / task-sync / 测试)由 evidence 写入任务卡,PR 描述引用不另手抄。测试通过的权威源是 PR HEAD 上 `.gitea/workflows/ci.yaml` 的 CI。 +- 验收即重放:审阅者先重放最小重放集再读作者证据,不一致即阻塞并登记失实。 +- 诚实出口:未执行/未验证如实标注 NOT_RUN/未验证;无法复核的外部事实标 `[未经证实]`。 +- 等待 CI 只用 `make ci-wait SHA=<完整 SHA>`;禁止手写 sleep + curl 轮询,禁止对同一 SHA 重复或并行轮询。 +- 细则:[本地记录与独立证据](policy/governance.md#本地记录与独立证据)、guidelines `standards/verification.md`「声明与证据来源」。 + +## 高后果操作双钥匙(强制) + +改写历史、解除/变更分支保护、删除归档引用、发布版本:Agent 只准备方案与命令,由用户确认并亲手执行。 + +## Gitea 与 Git 访问事实 + +- 端点、SSH、API 与 token 可用性只以 [policy/facts.md](policy/facts.md) 为准:引用前先跑其验证命令,凭据类核验只用该表登记的探针;历史卡片或旧脚本中的端点不作为事实。 +- 凭据值不入仓、不回显,不进命令行字面量、日志、证据或产出物(baseline 硬边界 2);只经环境变量或受保护文件引用。CI 侧见 [CI 凭据边界](policy/governance.md#ci-凭据边界)。 ## 提交与 PR -- 主题行:英文祈使句一行(≤100 列,不以句点结尾);body 写 `Why: / Evidence: / Impact: / Refs:` 四段;任务卡与 PR 描述为同一份文档。 -- 跨仓 gitlink 变更须经组合验证(`make test`)后在 Dev 仓推进;受保护分支经 PR 合入。 -- **等待 CI** 只用一条阻塞命令 `make ci-wait SHA=<完整 SHA>`(15 s 轮询、失败即停、默认 20 分钟超时); - 禁止手写 `sleep` + `curl` 轮询循环,禁止对同一 SHA 重复/并行轮询;等待期间去做其他独立工作。 -- **高后果操作双钥匙**:改写历史、解除/变更分支保护、删除归档引用、发布版本——Agent 只准备方案与命令,由用户确认并亲手执行;Agent 不得自行实施(baseline 硬边界 1 的授权范围要求)。 -- 模块边界、公开契约与隔离档位见 `policy/modules/`(如有)与 PROJECT.md;验证命令见 PROJECT.md「验证」节。 -- 首次克隆本仓后运行 `make setup` 启用本地 pre-commit 钩子。 -- 治理脚本改动须运行 `make test-governance`(临时真实仓库覆盖正常、拒绝及异常路径,已纳入 `make test`);证据、工作区和 CI 凭据边界细则见 `policy/governance.md`。 +- 主题行为英文祈使句一行(≤100 列,不以句点结尾);body 写 `Why: / Evidence: / Impact: / Refs:` 四段;任务卡与 PR 描述为同一份文档。 +- 跨仓 gitlink 变更经组合验证(`make test`)后在 Dev 仓推进;受保护分支经 PR 合入。模块边界与验证命令见 [PROJECT.md](PROJECT.md)。 -## Gitea 与 Git 访问事实(2026-09-30 实测;登记表 `policy/facts.md`) +## UI 设计门禁(强制) -机器可核的登记表在 `policy/facts.md`(每条带验证命令、last_verified 与 TTL); -引用任一事实前先跑其验证命令。以下为叙述版: - -历史任务卡与旧脚本里的 `git-panel.u1680832.nyat.app:30319` 已失效(TLS 握手失败), -见到一律按过期处理,不要再试。当前权威通道: - -- **Web / API(HTTPS)**:`https://git-home.u1680832.nyat.app:30319`(Gitea 1.27.3, - API 前缀 `/api/v1`;偶发 broken pipe / 超时,重试即可)。 -- **Git over SSH**:`ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/.git` - (.gitmodules 与各仓 origin 均为此格式)。 -- **API token**:`~/.secrets/gitea` HOME/IMC 段各有 `- URL_PREFIX=`/`- ACCESS_TOKEN=<40 hex>`,本仓取 HOME 段(F2): - `awk '/^HOME:/{h=1;next} /^IMC:/{h=0} h&&sub(/^- ACCESS_TOKEN=/,""){print;exit}' ~/.secrets/gitea`,作 `Authorization: token <值>` 头。**token 值不入仓、不回显、不进产出物**(baseline 硬边界 2)。 -- **SSH key**:`~/.ssh/home_gitea`(ED25519,公钥指纹 - `SHA256:GYXaOXIMeloQ1fKSfdJ/ndgPQzs/Ix7Hk62ekfkfQ20`,Gitea 侧密钥名 GMKtec); - `~/.ssh/config` 已有 `git-ssh.u1680832.nyat.app` 主机条目,克隆/推送直接可用。 -- 任务卡闭合 PR(标准收口中运行 `make task-close`)经 API 建 PR 时以 - `GITEA_TOKEN` 环境变量传入同上的 token。 +- 一切界面生成/修改先读 [DESIGN.md](DESIGN.md)(四原则:美观、现代、简洁、易用);违反视为设计系统缺陷,不是风格偏好。 +- 颜色/圆角/字号只消费 `@ui/tokens.css` 令牌(字号走 `--fs-ui-*`);动效只用 `@ui/motion.css` 的 `mk-*`;折叠只用 `@ui` Collapsible,禁止 `
`。 +- 守护测试(UI 仓与 Frontend 仓各一份 `design-guard.test.ts`)以棘轮拦截新增裸色值、私有 `@keyframes` 与行内字号。 +- 风格调整按 DESIGN.md「演进规则」在同一变更集中关联 UI 令牌 PR 与 Dev 组合 PR,并验证精确组件 SHA(跨仓不要求同一 PR)。 +- 异步状态只有一个所有者;事件按 seq 处理,过期事件丢弃。 diff --git a/PROJECT.md b/PROJECT.md index 3fada607..ba6ece9b 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -1,71 +1,11 @@ # Agent-System-Dev 项目事实 - 用途:开发集成父仓(gitlink 权威);非分发单元。 -- 组合:modules/{core,backend,frontend,ui,documents} + deps/{development-guidelines,agent-native,agent-squad,agent-intent,agent-command,error-code-system,logging-system}。 +- 组合:`modules/`(产品组件)+ `deps/`(共享依赖);完整组件清单以 [`.gitmodules`](.gitmodules) 为准。 - 部署:非容器;Releases 仓从本仓固定提交导出版本。 -## 方向与规划入口 - -长期方向、目标及跨任务规划从过程仓的 [项目方向与规划索引](modules/documents/docs/proposals/INDEX.md) -查找主记录、里程碑和关联任务;原始正文沿用 [方案与规划目录](modules/documents/docs/proposals/)。 -确立或调整具体方向时,在本入口维护其主记录链接;正文、确认依据和进展仍以该主记录为准。 -目录中的既有文档不因被收录而自动成为已批准规划。记录、任务关联及收尾建议按 -[交付规范的长期方向与任务衔接](policy/delivery.md#长期方向与任务衔接) 执行。 - -- [分层分支交付与规范精简 r1](modules/documents/docs/proposals/2026-10-18-delivery-model-v3.md):2026-10-18 用户批准实施。固定两级分支、任务卡合入前折叠闭合、按层级分配门禁、任务分类通道与规范精简;由治理大任务 `feature/delivery-model-v3` 推进。 - -- [文档职责矩阵整改 r2](modules/documents/docs/proposals/2026-10-15-rdm-duty-matrix-rework.md):项目设置的文档职责改为三视图分配,不创建文档、不执行审签。2026-10-15 按实施方案执行;部署与真实账号验收未授权。 - -- [svc 插件组合部署(--with)方案 r1](modules/documents/docs/proposals/2026-10-14-svc-plugin-compose-deploy.md):待评审。部署时把登记插件与 Dev 基座编成一个二进制并校验激活。收录不构成批准。 - -- [用户、职位角色与分层权限统一方案 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; - [U1 实施计划](modules/documents/docs/proposals/2026-10-12-fast-slow-channel-display-u1-plan.md) 待阶段 5 批准后启动业务代码; - 业务实施、模型实验、运行验收与部署 NOT_RUN。 - -新增方向:[Generative UI 调研与插件方案 r2](modules/documents/docs/proposals/2026-10-10-generative-ui-plugin.md)。 -2026-10-11 用户裁定首试点(对话内分析页)、可信编译插件交付与试验参数;U1 受控试验当日完成 -(结构分化 6/6、生成走工具参数通道、首期自研 renderer,详见方案「U1 结果与 r2 修订」); -U2 起实施与部署分别评审。 - -- [多人协作存储与分布式演进方案](modules/documents/docs/proposals/2026-10-11-collaboration-storage-and-distribution.md): - 10–100 人、多项目、多会话并行;PostgreSQL 团队档、迁移恢复与多实例执行。方案 r1 已批准 - (2026-10-11,用户指示「按方案开始改造」),实施自首选增量 U1+U2 启动。 - -- [RDM 两树一网集成、完善与追溯审计展示方案](modules/documents/docs/proposals/2026-10-11-rdm-two-trees-trace-audit-integration.md): - 每项目一套笔记树、文档树与追溯网;项目绑定与读取授权闭环、原子事件历史、统一审计读服务 - 与四入口工作区。方案 r1 已获用户评审通过(2026-10-11,五阶段一并,KTD4 双层管理员裁定); - U1–U5 全部实施合入(RDM PR #195/#197/#201/#213;Dev PR #368,2026-10-11;超管槽待基座能力 - 接线,申请见 RDM 仓 docs/proposals/2026-10-11-u1-base-extension-requests.md); - U6 团队存储独立后续;运行验收属部署环境。 - -- [DHF 模板接入 RDM 文档生成管线 r0.1](modules/documents/docs/proposals/2026-10-14-dhf-template-generation-plan.md):2026-10-14 获用户批准开始实施;RDM 独立分支已保存 U1 资产合同与 44 份原件目录,E1 与获批追加试验共为 14 份 DOC 生成转换候选。owner 已分别接受项目建议书、F01、F02、设计输入规格书四份确切四哈希候选包,四份均未发布;设计输入规格书的动态 SR/子系统分组使用已裁定的 v2 内容块合同,原型和完整包分别获评审。F01/F02 为独立过程确认用途、默认阶段10,owner 兼任体系专员,公开字典与存量实例升级合同仍需独立评审。14 份 DOC 的 Word 保真待反馈。正式模板发布数为 0;U1 整体、U2–U6、正式浏览器验收、迁移和部署均未完成,复用在途两树合同并按阶段人工评审。 - -- [RDM 模板与两树一网对话文档生成方案 r1.1](modules/documents/docs/proposals/2026-10-11-rdm-template-dialog-generation.md):69 类历史资产盘点、模板版本与章节实例化、持久提案、文档级审签及 DOCX;待用户评审,业务实施与运行验收 NOT_RUN。 首批项目建议书/竞品分析、随后扩展全部文档已确认(2026-10-11),其余方案与实施仍待评审。 - -- [RDM 最终目标核对与完整交付方案 r1](modules/documents/docs/proposals/2026-10-11-rdm-final-target-completion.md):系统级项目、上限内权限覆盖、私域协作、全部文档三权与完整业务链;34 UR/68 SR/10 F 逐条映射;方案已随 #340 合入(2026-10-11),U1 需求基线核对经 RDM #199/#200 落地,U2 起未启动。 - -- [服务重启与全量数据恢复方案 r3.1](modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md): - svc 启停、异常进程/主机退出后的依赖、配置/数据身份、任务/HITL/副作用、文件与组合备份恢复; - r2 已批准。r3.1 增补 D1 实例身份收尾、D2 可回滚切换、D3 真实组合门禁,并记录 2026-10-12 rdmcand 数据归属:会话不在 dev 库的 rdmcand 窗口内,领域事件为空,不跨实例搬迁。 - D1 与 D2 已合入 Backend #184、RDM #291、Dev #485。D3 的 `make test-lifecycle` 跑真实组合用例;登录仍是 NOT_RUN,依赖账户方案 U7/U5,不在本轮公共化账户 CLI。r3 其余未批条款仍待人工评审。 - 2026-10-12 用户批准 r3 新增(R11/R12、KTD13–15、U8):SQLite 活动服务锁与耐久核验已实施,故障注入、部署与运行验收仍 NOT_RUN。 - -- [svc 统一部署与一次性登录 Key 方案 r1](modules/documents/docs/proposals/2026-10-12-svc-unified-deploy-and-login-key.md): - 二进制/源码统一部署、环境准备、无初始密码开户与账户恢复;一次性 Key 仅兑换设密受限会话; - 2026-10-12 已批准;U1/U4 实施启动中;规格见 `docs/svc-deploy/`。 - -上述账户/恢复方案已有 U1/U4 与 recover/watchdog 基础源码。2026-10-12 的 dev 8788 续查形成账户 r2、恢复 r3 **候选草案**:帮助与能力发现、账户事务/审计/登录接线、SQLite 活动服务隔离及备份恢复增补。新增内容待人工评审;Documents 独立分支 `feature/svc-account-durability-plan` 仅本地保存,不推进本仓 gitlink;本入口原链接仍指固定版。候选交接与核查见[续审记录](docs/svc-deploy/2026-10-12-account-durability-plan-review.md)。U8 规格见 `docs/recovery/u8-sqlite-instance-guard.md`。 - ## 验证 -多个分支向 Dev main 交付时按[合入调度](policy/delivery.md#合入调度)执行:等待分支不追 main,轮到后同步一次、固定候选、运行当前 HEAD 的完整 CI,再由智能体合入。实际采纳范围见[采纳记录](policy/adoption.md)。 - `make test` 先核对子模块工作树与固定指针,再运行治理脚本、公开边界、跨仓指针、库及各模块 测试。实际目标以根 Makefile 为准。`make test-governance` 可独立检查开发门禁,使用临时 真实仓库,不访问生产数据;操作与证据要求见 [开发门禁](policy/governance.md)。 @@ -79,22 +19,49 @@ U2 起实施与部署分别评审。 未加 go.work:工作区最小版本选择会抬高 backend 单独构建使用的部分 `golang.org/x` 版本。 发布消费方须从固定 Dev 提交递归物化后运行 `make build`,再核验制品与实际运行状态。 -- [渐进工作台界面重设计 r0.2](modules/documents/docs/proposals/2026-10-12-work-companion-ui-design.md):用户已授权连续实施;[计划 r1](modules/documents/docs/proposals/2026-10-12-work-companion-ui-plan.md)覆盖任务摘要、对话/输入、窄屏和真实状态投影;进展见对应任务卡。 - -- [系统项目、成员工作入口与私域协作闭环 r1](modules/documents/docs/proposals/2026-10-13-system-project-membership.md):2026-10-13 用户批准阶段 1–9 连续推进;统一全局项目身份与名称、成员个人会话、每项目两树一网和私域权限;当前组合验证/CI/tag 消费推进中,部署与线上修复未授权。 +多个分支向 Dev main 交付时按 [合入调度](policy/delivery.md#合入调度) 执行;实际采纳范围见 [采纳记录](policy/adoption.md)。 ## 法规知识库组合 -[法规知识库计划](docs/superpowers/plans/2026-10-18-regulations-knowledge-base-plan.md) U8 的固定组合: - -- `modules/backend` 56a7f9f(EXT-20 法规检索/搜索扩展合同)、`modules/documents` 5bb4877(EXT-20 提案)、 - `modules/frontend` 50ec836 与 `modules/ui` 5c2b103(法规引用渲染)。 -- RDM ece91da(含 kbreg 组合)与 KB-Regulations 5dddad2,固定在 - `tests/integration/regulations-kb/components.pin`;KB 经 RDM 的 `pins/agent-system-kb-regulations` - gitlink 接入,门禁要求该 gitlink 是 `kb_sha` 的祖先。 +- 计划:[法规知识库计划](docs/superpowers/plans/2026-10-18-regulations-knowledge-base-plan.md)(U8 固定组合);进展与批准状态以该计划、任务卡与 PR 为准。 +- 固定版本:Dev 侧模块以 gitlink 与 `.gitmodules` 为准;RDM 与 KB-Regulations 提交以 + `tests/integration/regulations-kb/components.pin`(`rdm_sha`、`kb_sha`)为准。KB 经 RDM 的 + `pins/agent-system-kb-regulations` gitlink 接入,门禁要求该 gitlink 是 `kb_sha` 的祖先。 - 凭据变量:`KBREG_DATABASE_URL`、`KBREG_EMBED_API_KEY`、`KBREG_SEARCH_ZHIPU_API_KEY`、`KBREG_MINERU_TOKEN` (测试库用 `KBREG_TEST_DATABASE_URL`)。按环境注入、轮换与撤销见 KB 仓 `docs/operations/runbook.md`;值不入仓、不回显。 -- 门禁:`make test-regulations-kb`(需 `KBREG_TEST_DATABASE_URL`、`SVC_RDM_SRC_DIR`、`KB_SRC_DIR`,不纳入 `make test`), - 说明见 `tests/integration/regulations-kb/README.md`。真实搜索/嵌入/MinerU 调用 NOT_RUN,待 owner 批准。 +- 门禁:`make test-regulations-kb` 需 `KBREG_TEST_DATABASE_URL`、`SVC_RDM_SRC_DIR`、`KB_SRC_DIR`, + 独立于 `make test`、不纳入其中;说明见 [tests/integration/regulations-kb/README.md](tests/integration/regulations-kb/README.md)。 + 该门禁通过不代表部署、版本发布或业务/法务验收完成。 -部署、版本发布与业务/法务验收是后续独立门禁,本组合门禁通过不代表其中任何一项完成。 +## 方向与规划入口 + +长期方向、目标及跨任务规划从过程仓的 [项目方向与规划索引](modules/documents/docs/proposals/INDEX.md) +查找主记录、里程碑和关联任务;原始正文沿用 [方案与规划目录](modules/documents/docs/proposals/)。 +确立或调整具体方向时,在本入口维护其主记录链接;记录、任务关联及收尾建议按 +[交付规范的长期方向与任务衔接](policy/delivery.md#长期方向与任务衔接) 执行。 + +下列条目只是主题索引:批准、进展、验收与部署状态一律以所链主记录、任务卡与 PR 为准, +本入口不复制;收录不构成批准。 + +- [分层分支交付与规范精简](modules/documents/docs/proposals/2026-10-18-delivery-model-v3.md):两级分支、任务卡合入前闭合、分级门禁、任务分类通道与规范精简。 +- [文档职责矩阵整改](modules/documents/docs/proposals/2026-10-15-rdm-duty-matrix-rework.md):项目设置的文档职责三视图分配。 +- [svc 插件组合部署(--with)](modules/documents/docs/proposals/2026-10-14-svc-plugin-compose-deploy.md):部署时把登记插件与 Dev 基座编成一个二进制并校验激活。 +- [用户、职位角色与分层权限统一](modules/documents/docs/proposals/2026-10-13-identity-role-permission-plan.md): + [I1 权限合同](modules/documents/docs/proposals/2026-10-13-iam-i1-permission-contract.md)、 + [I1 实施候选与评审记录](modules/documents/docs/proposals/2026-10-13-iam-i1-review.md)。 +- [系统项目、成员工作入口与私域协作闭环](modules/documents/docs/proposals/2026-10-13-system-project-membership.md):全局项目身份与名称、成员个人会话、每项目两树一网与私域权限。 +- [快慢通道展示与动态工作摘要](modules/documents/docs/proposals/2026-10-12-fast-slow-channel-display.md):对话即时答复、后台动态发现与来源、四层阅读、真实状态、并发/恢复; + [U1 实施计划](modules/documents/docs/proposals/2026-10-12-fast-slow-channel-display-u1-plan.md)。 +- [渐进工作台界面重设计](modules/documents/docs/proposals/2026-10-12-work-companion-ui-design.md): + [计划](modules/documents/docs/proposals/2026-10-12-work-companion-ui-plan.md) 覆盖任务摘要、对话/输入、窄屏和真实状态投影。 +- [服务重启与全量数据恢复](modules/documents/docs/proposals/2026-10-12-service-restart-data-recovery.md):svc 启停、异常退出后的依赖、配置/数据身份、任务/HITL/副作用、文件与组合备份恢复; + U8 规格见 [SQLite 实例守护](docs/recovery/u8-sqlite-instance-guard.md)。 +- [svc 统一部署与一次性登录 Key](modules/documents/docs/proposals/2026-10-12-svc-unified-deploy-and-login-key.md): + 二进制/源码统一部署、环境准备、无初始密码开户与账户恢复;规格见 `docs/svc-deploy/`, + 账户与恢复续审见 [续审记录](docs/svc-deploy/2026-10-12-account-durability-plan-review.md)。 +- [Generative UI 调研与插件](modules/documents/docs/proposals/2026-10-10-generative-ui-plugin.md):对话内生成界面与可信编译插件交付。 +- [多人协作存储与分布式演进](modules/documents/docs/proposals/2026-10-11-collaboration-storage-and-distribution.md):多项目、多会话并行;PostgreSQL 团队档、迁移恢复与多实例执行。 +- [RDM 两树一网集成、完善与追溯审计展示](modules/documents/docs/proposals/2026-10-11-rdm-two-trees-trace-audit-integration.md):每项目笔记树、文档树与追溯网,项目绑定与读取授权、原子事件历史、统一审计读服务与四入口工作区。 +- [DHF 模板接入 RDM 文档生成管线](modules/documents/docs/proposals/2026-10-14-dhf-template-generation-plan.md):DHF 模板资产合同与文档生成转换。 +- [RDM 模板与两树一网对话文档生成](modules/documents/docs/proposals/2026-10-11-rdm-template-dialog-generation.md):历史资产盘点、模板版本与章节实例化、持久提案、文档级审签及 DOCX。 +- [RDM 最终目标核对与完整交付](modules/documents/docs/proposals/2026-10-11-rdm-final-target-completion.md):系统级项目、权限覆盖、私域协作、全部文档三权与完整业务链的需求映射。 diff --git a/deps/development-guidelines b/deps/development-guidelines index 2688fd2b..a15db508 160000 --- a/deps/development-guidelines +++ b/deps/development-guidelines @@ -1 +1 @@ -Subproject commit 2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb +Subproject commit a15db5085585f199c3c399824f6cf24c3144bbd5 diff --git a/deps/development-manager b/deps/development-manager index 67784f43..6c019595 160000 --- a/deps/development-manager +++ b/deps/development-manager @@ -1 +1 @@ -Subproject commit 67784f43eb0430bf596c2cda685cbb4946d2e032 +Subproject commit 6c019595fa02aa2d0a1a9b8f98fc224e69d2a901 diff --git a/docs/superpowers/plans/2026-10-18-0849-fix-development-norm-remediation-plan.md b/docs/superpowers/plans/2026-10-18-0849-fix-development-norm-remediation-plan.md new file mode 100644 index 00000000..847d8d88 --- /dev/null +++ b/docs/superpowers/plans/2026-10-18-0849-fix-development-norm-remediation-plan.md @@ -0,0 +1,289 @@ +--- +title: Development Norm Remediation - Plan +type: fix +date: 2026-10-18 +artifact_contract: ce-unified-plan/v1 +product_contract_source: ce-plan-bootstrap +execution: code +--- + +# Development Norm Remediation - Plan + +## Goal Capsule + +- **Objective:** Dev contributors can identify the authoritative rule for each workflow, recover from safe remote interruptions, and still distinguish local progress from formally verified delivery. +- **Means:** Move portable policy to Development-Guidelines, keep Dev commands and choices in Dev, and use risk-specific outcomes instead of propagating every recoverable condition as a write ban. +- **Authority:** This artifact authorizes planning only. It does not authorize implementation, platform changes, credential operations, or release actions. Implementation requires a separate user instruction. +- **Stop conditions:** Preserve authorization, scope, secret-handling, unknown-side-effect, current-target, exact-head CI, independent review, and high-consequence boundaries. Stop for an unresolved decision that changes those boundaries. +- **Execution profile:** The work spans Development-Guidelines and Agent-System-Dev. Each repository retains its own branch, task record, review, CI, and merge lifecycle. Merge the shared rule change before updating Dev's guideline pin. +- **Finishing:** A future implementation run owns verification and PR delivery. Platform branch protection, publishing a Guidelines release tag, and credential rotation remain owner-controlled. + +--- + +## Product Contract + +### Summary + +This plan corrects ownership and contradictions in the shared and Dev development rules. It also defines where authorized local work may continue without implying that the branch is ready for formal delivery. + +### Problem Frame + +The previous standards audit found that the shared delivery workflow hard-codes Dev's initiative/child branch model, the root entry repeats detailed policy, and the startup summary conflicts with the existing `LOCAL_READY` recovery path. It also found an outdated adoption record and unsafe or overly broad external-fact verification instructions. + +The V3 delivery implementation is already merged by Dev PR #608. This plan does not repeat that work or run its future metrics review M. + +### Requirements + +#### Rule Ownership and Entry Points + +- R1. Shared standards define reusable outcomes and boundaries without requiring Dev-specific branch names, task-card fields, or `make` commands. +- R2. Dev's `AGENTS.md` and `PROJECT.md` route readers to one detailed source for each rule or changing project status. +- R3. Dev task channels express priority and grouping without deciding branch depth when integration need does not require it. + +#### Readiness and Continuation + +- R4. Readiness states distinguish new-task startup, authorized local continuation of an existing task, and formal delivery. +- R5. A safe existing task may continue bounded local work when the authoritative remote is temporarily unavailable, provided its task identity, recorded baseline, authorization, and local workspace remain verifiable. +- R6. Local continuation never claims a fresh target, permits a new task to bypass remote triage, or replaces synchronization, review, current-head CI, or platform admission. +- R7. Dirty or shared workspaces, invalid authorization/configuration, scope expansion, secret exposure, unknown external side effects, and unresolved Git operations remain hard stops. + +#### Adoption and External Facts + +- R8. Adoption status, execution capability, and per-case conformance evidence remain separate; missing or unrun evidence stays `NOT_RUN`. +- R9. Credential checks do not print secrets or place expanded secret values in process-visible arguments, logs, or saved receipts. +- R10. An external-fact check supports the exact claim it records; an unrelated network failure cannot refresh a more specific causal claim. + +### Acceptance Examples + +- AE1. An existing, authorized task with a clean registered worktree and locally available R0 may continue local work during an origin outage, but cannot pass formal synchronization or merge gates. +- AE2. A new task cannot create a branch or worktree when remote triage is unavailable or the target cannot be verified. +- AE3. A dirty workspace, invalid task identity, unknown side effect, or scope expansion still stops the affected operation. +- AE4. A fake-token test proves that credential values do not appear in command arguments, output, logs, or receipts; it makes no request to Gitea. +- AE5. DNS failure or timeout does not verify the historical TLS-specific cause recorded for a retired endpoint. + +### Scope Boundaries + +**Included:** The pinned Development-Guidelines source and its Dev adoption; Dev's task readiness and recovery flow; Dev's delivery-routing policy; Dev's facts and credential instructions; and the affected root/project entrypoints. + +**Deferred to Follow-Up Work:** Adoption by other repositories; a separate decision on a direct-authoring path for simple documentation; Dev V3 metrics review M, scheduled approximately two weeks after its merge; any broader governance automation. The newly requested CLI/subagent task-sizing and context-lifecycle work is now planned as U7 below; it does not expand U1-U6 implementation scope. + +**Outside this plan:** Re-implementing V3 P1-P3; changing protected-branch rules; publishing a Guidelines release tag; rotating Gitea credentials; enabling an automatic merge queue; allowing host fallback for code or unknown worker outcomes; changing product runtime code. + +--- + +## Planning Contract + +### Key Technical Decisions + +- KTD1. Keep one authority per rule: Guidelines owns reusable contracts; Dev `policy/` owns local behavior and commands; `AGENT-EXECUTION.md` owns machine-readable route selection; root files remain entrypoints and stable project facts. (session-settled: user-approved — chosen over a Dev-only correction: the approved plan covers both shared and project rule ownership.) +- KTD2. Use a three-way readiness distinction. Strict readiness applies to new task startup and formal delivery. Local readiness permits only authorized local work in an existing registered task with a safe workspace and locally verifiable recorded baseline. (session-settled: user-approved — chosen over blocking every write on remote drift: preserve core hard boundaries while allowing safe local progress.) +- KTD3. Preserve hard stops for authorization, scope, secrets, unknown side effects, dirty shared workspaces, and unresolved Git state. A remote outage may defer formal delivery without automatically discarding safe local progress. +- KTD4. Keep task category separate from integration topology. A fix can still receive priority; an initiative is required when multiple units need shared integration or acceptance, not only because work is labeled “new” or “improvement”. +- KTD5. Keep the agent-per-unit and no-host-fallback contract for code, policy semantics, routing, security, and execution configuration. This plan does not introduce a direct host-write exception; any non-normative documentation-only exception requires a separate explicit decision. +- KTD6. Change Guidelines in its own repository first. Update Dev's gitlink and adoption record only after the shared change is merged, then verify the pinned combination. +- KTD7. Preserve `active`/`manual` as separate adoption/capability dimensions, but reconcile them with the exact success and negative-case evidence required by the pinned adoption workflow. Never promote `NOT_RUN` from prose alone. +- KTD8. Keep F4 token-free and record only what the check proves. Retain the TLS cause as a dated historical observation unless a verifier can prove that specific condition. + +### High-Level Technical Design + +```mermaid +flowchart TD + A[Request or existing task] --> B{Existing registered task?} + B -->|No| C[Verify remote and triage] + C -->|Unavailable or unverified| D[Stop task creation; allow planning/read-only analysis] + C -->|Verified| E[Start against authoritative target] + B -->|Yes| F{Local identity and workspace safe?} + F -->|No| G[Hard stop and preserve workspace] + F -->|Yes| H{Remote fresh and target current?} + H -->|No| I[LOCAL_READY; allow bounded local work only] + H -->|Yes| J[READY / formal delivery path] + I --> K[Fresh fetch and sync required before review or merge] + J --> L[Exact-head review, required CI, platform admission] + K --> L +``` + +`LOCAL_READY` is a continuation state, not a weaker merge gate. `failed-unknown`, scope expansion, invalid authorization, and unowned workspace state do not enter the local-continuation branch. + +### Alternatives Considered + +| Approach | Assessment | +|---|---| +| Keep all current startup failures as global write blocks | Preserves simplicity but contradicts existing local recovery policy and prevents safe in-flight work during remote outages. | +| Treat every remote failure as permission to continue or start locally | Reduces blocks but can bypass initiative discovery, baseline freshness, and ownership checks. Rejected. | +| Permit local continuation only for an existing, authorized, verifiable task; retain strict new-start and final-delivery gates | Recommended. Preserves formal trust boundaries while allowing bounded local progress. | +| Let the host write implementation units whenever external agents fail | Rejected. It contradicts the adopted execution contract and `fallback_to_host: never`. | + +### System-Wide Impact + +The shared Guidelines change affects only consumers that adopt its new pinned commit. Dev must update its own gitlink and adoption record in a separate PR. Other repositories remain on their pinned versions until separately reviewed and adopted. + +The readiness change affects task startup, recovery, evidence validation, and contributor instructions. Consumers must continue to distinguish `LOCAL_READY` from `READY`; CI and Gitea remain authoritative for formal merge admission. + +### Risks & Dependencies + +- The remote Guidelines `main` was observed at `6c8e6cdd08d4fb6009c83be43b75dfead9e9f7d9` on 2026-10-18; Dev currently pins `2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb`. Re-fetch both repositories before implementation and merge decisions. +- Guidelines CONTRIBUTING.md treats changes to required conditions or authorization/validation semantics as a major behavior change, even while the source is in draft versioning. Review the version bump and migration note before merging. +- Allowing local continuation without a fresh fetch requires proving the existing task identity and saved baseline locally. If that cannot be verified, keep the operation blocked. +- `make facts-verify` executes shell commands from `policy/facts.md`. Tests must use synthetic credentials and stubbed requests; they must not read real secrets or access Gitea. +- An active adoption record does not establish platform enforcement. Runner isolation, workflow trust, and branch protection still require their own GATE or REPLAY evidence. + +### Deferred Questions + +- Whether a narrow direct-authoring path should exist for simple non-normative documentation changes remains separate from this remediation. The current plan preserves the adopted agent-per-unit rule for normative and behavior-bearing changes. + +### Sources & Research + +- Dev baseline: `f682afe8839828f2222eb8891c1fb5823ea26c29`. +- Dev Guidelines pin: `2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb`. +- Guidelines remote `main` observed at `6c8e6cdd08d4fb6009c83be43b75dfead9e9f7d9` on 2026-10-18; implementation must revalidate it. +- V3 implementation reference: Dev PR #608 and `.task/archive/feature-delivery-model-v3.md`; P1-P3 are merged, metric M is future work. +- Current rule ownership and adoption references: `deps/development-guidelines/standards/documentation.md`, `core/baseline.md`, `workflows/delivery.md`, `workflows/adoption.md`, and Dev `policy/delivery.md`, `policy/governance.md`, `policy/adoption.md`. +- Current readiness and evidence references: `scripts/task.sh`, `scripts/evidence.py`, `scripts/task_triage.py`, `scripts/facts-verify.sh`, `policy/facts.md`, and `tests/test_task.py`, `tests/test_evidence.py`, `tests/test_task_triage.py`. +- No applicable files were found under `docs/solutions/`; no Compound Packs are declared. + +--- + +## Implementation Units + +### U1. Generalize shared delivery ownership + +- **Goal:** Make shared delivery guidance portable while leaving Dev's branch hierarchy and tool commands in the Dev project policy. +- **Requirements:** R1, R3, R4, R6 +- **Dependencies:** None +- **Target repository:** Development-Guidelines +- **Files:** `workflows/delivery.md`, `core/glossary.md`, `VERSION` +- **Approach:** Replace the mandatory Dev-specific initiative/child lifecycle in shared delivery guidance with reusable integration-target and recovery principles. Keep initiative/child/standalone fields and concrete project flows in Dev policy. Do not alter the shared agent-per-unit hard boundary or host-fallback contract in this unit. +- **Patterns to follow:** `CONTRIBUTING.md` version semantics; `AGENTS.md` and `standards/documentation.md` single-authority and project-independence rules. +- **Test scenarios:** + - A shared-workflow reader can apply the delivery guidance without adopting Dev's `main`, `feature/`, or task-card schema. + - The Dev policy still has a clear place to define its two-level branch and initiative process. +- **Verification:** Relative links and cross-references resolve; no project-specific endpoint, branch, secret, or command leaks into shared text; Guidelines version change reflects the actual normative impact. + +### U2. Enable safe local continuation for existing tasks + +- **Goal:** Allow a registered, authorized task to continue bounded local work when origin is unavailable or ahead, without claiming formal readiness. +- **Requirements:** R2, R4, R5, R6, R7 +- **Dependencies:** U1 +- **Files:** `scripts/task.sh`, `scripts/evidence.py`, `policy/governance.md`, `tests/test_task.py`, `tests/test_task_recovery.py`, `tests/test_evidence.py` +- **Approach:** Separate existing-task local recovery from new-task startup and formal delivery. Prove authorization from the registered task record and the request's match to its recorded goal/scope; a missing or contradictory authorization record is not proof. Reject closed or abandoned tasks, a different task/branch identity, and requests outside the recorded scope. Then validate the local R0 object, current HEAD ancestry, and absence of active Git operations or ambiguous workspace changes before returning a local-only status. The local receipt records remote freshness as unavailable; it does not require or invent a current remote target SHA. New task creation and formal delivery continue to require a fresh authoritative target. If R0 is missing or local state is ambiguous, stop. +- **Patterns to follow:** Existing `TASK_WORK_MODE=local`, `LOCAL_READY`, `resume-local` receipt validation, task-card target metadata, and remote-verification errors. +- **Test scenarios:** + - An existing task with a valid card, clean worktree, available R0 commit, matching branch, and HEAD descended from R0 can resume local work when fetch fails, with an explicit local-only result and unchanged baseline. + - Local continuation still works when the remote-tracking ref is absent but the task's recorded R0 object is available locally; it hard-stops when the R0 object is unavailable. + - An existing task behind remote can continue locally, but a fresh remote sync is required before formal review or merge. + - New task startup remains blocked without verifiable remote triage and does not create a branch or worktree. + - A closed or abandoned task, missing authorization record, request outside the recorded scope, or conflicting task identity cannot enter local continuation. + - Dirty or mismatched task state, an active Git operation, invalid authorization, unknown side effects, or scope expansion remains blocked. + - A local receipt cannot validate as `READY`, `REPLAY`, or `GATE` and cannot satisfy the final merge check. +- **Verification:** Focused task, recovery, and evidence tests prove both the allowed continuation and retained hard stops. The change must not weaken `task-pr-check`, `task-sync`, exact-head CI, or platform admission. + +### U3. Route project task lanes by integration need + +- **Goal:** Keep task-type priority while choosing standalone versus initiative based on shared integration and acceptance needs. +- **Requirements:** R1, R3, R4 +- **Dependencies:** U1 +- **Files:** `policy/delivery.md` +- **Approach:** Define the conditions that require an initiative and those that allow independently acceptable work to remain standalone. Preserve repair/hotfix priority and explicit remote triage for new tasks. Keep branch, command, and queue details local to Dev. +- **Patterns to follow:** Current two-level model, `JOIN`/`NEW` triage, and the existing layered gate table. +- **Test scenarios:** + - A small independent enhancement can remain standalone even when labeled “improvement” or “new”. + - Work with multiple dependent units and one shared final acceptance enters an initiative even when one unit is a repair. + - A repair remains priority work without being forced onto `main` when it belongs to an active initiative's integration contract. + - Triage still refuses to create a branch when remote initiative discovery is unverified. +- **Verification:** Review the decision rules against the scenarios above and confirm that task type controls priority only where intended, not target topology. + +### U4. Make credential and fact checks truthful + +- **Goal:** Prevent credential disclosure in the documented verification path and ensure each fact check proves the claim it records. +- **Requirements:** R7, R9, R10 +- **Dependencies:** None +- **Files:** `policy/facts.md`, `scripts/facts-verify.sh`, `tests/test_facts_verify.py` +- **Approach:** Reuse the existing token-safe Gitea request path where practical, or otherwise pass credentials without placing expanded values in arguments or output. Narrow F4's claim to what its check can prove, or make the verification assert the specific historical failure before refreshing the fact. Keep the legacy endpoint prohibition separate from claims about its failure cause. +- **Patterns to follow:** `scripts/task_triage.py` HOME-section parsing, token validation, and exception redaction; `tests/test_task_triage.py` synthetic-token fixtures. +- **Test scenarios:** + - A synthetic HOME/IMC secrets file selects only the intended token without printing either value. + - A stubbed HTTP client receives the credential without exposing the expanded value in child argv, stdout, stderr, or saved receipts. + - Missing or malformed synthetic credentials fail without revealing the supplied value. + - DNS failure, timeout, generic connection error, HTTP response, and the specifically claimed TLS condition do not collapse into the same fact-update result. + - No test reads `~/.secrets/gitea` or contacts Gitea. +- **Verification:** Focused offline tests cover the facts verifier and token handling. A live fact refresh is not part of the regression suite and may only use the protected credential path with normal evidence redaction. + +### U5. Consolidate Dev entrypoints and project index + +- **Goal:** Keep the root entry concise and the project index focused on stable facts and navigation. +- **Requirements:** R1, R2, R7 +- **Dependencies:** U1, U2, U3, U4 +- **Files:** `AGENTS.md`, `PROJECT.md` +- **Approach:** Keep mandatory principles and trigger links in `AGENTS.md`; leave detailed workflow and command behavior in the policy owners. Keep `PROJECT.md` as a stable project/index page and link changing initiative status to its task record. Remove the duplicate token extraction procedure from the root entry and point to the safe project fact/helper instead. +- **Patterns to follow:** `deps/development-guidelines/standards/documentation.md` entrypoint and single-source guidance; Dev's existing adoption matrix and archived V3 task record. +- **Test scenarios:** + - Each root rule points to exactly one detailed authority; no operational rule requires the reader to choose between paraphrases. + - Token instructions point to a safe retrieval mechanism and do not print secret values. + - Project proposal summaries link to the status authority rather than duplicating mutable progress. +- **Verification:** Check links, rule consistency, adoption references, and sensitive-data exposure. Do not change the protection, release, or credential-rotation authority. + +### U6. Reconcile adoption evidence and verify the pinned combination + +- **Goal:** Reconcile adoption evidence, pin the reviewed Guidelines revision in Dev, and verify the combined repository behavior. +- **Requirements:** R1, R2, R6, R7, R8 +- **Dependencies:** U1, U2, U3, U4, U5 +- **Files:** `deps/development-guidelines`, `policy/adoption.md`, `policy/guidelines-adoption.md` +- **Approach:** Only after U1 is merged to Guidelines main, update Dev's gitlink and adoption records to that exact SHA. Preserve and cite the adoption success and four negative cases already recorded for Dev; assess `active`/`manual` against the evidence required by the newly pinned workflow, not against unrelated future checks. Any additional schema-conformance or platform-side negative checks not run in Dev remain explicitly `NOT_RUN`: their absence alone neither invalidates the existing adoption record nor proves enforcement. Replace stale closeout references with verified merged and archived evidence. Reconcile the resulting combined tree before final verification. Do not update other consumers or alter platform branch protection in this plan. +- **Patterns to follow:** `policy/guidelines-adoption.md`, `policy/adoption.md`, `make test-worktree`, and the existing cross-repository pin checks. +- **Test scenarios:** + - The Dev checkout materializes exactly the approved Guidelines SHA and no unrelated component pin changes. + - The adopted Guidelines text and Dev project rules agree on generic versus project-specific behavior. + - Existing recorded adoption success and four negative cases remain traceable to their evidence; separately unrun schema/platform checks stay `NOT_RUN`, and neither an `active`/`manual` label nor missing extra checks is used to claim enforcement or automatically invalidate adoption. + - Adoption, execution capability, and individual conformance results remain separately represented; an unrun case cannot appear passed. + - The local continuation cases still reject formal delivery until the current authoritative base and required checks are verified. +- **Verification:** Run `make test-governance` for governance behavior and `make test` for the final pinned combination. Obtain independent review and current-head CI GATE before any authorized PR merge. + +### U7. Make subagent and CLI work fit bounded model capacity + +- **Goal:** Improve completion quality for subagents and CLI agents with smaller or uncertain context windows by giving them short, explicit, independently verifiable work packages and preserving only the handoff facts needed across stages. +- **Requirements:** New follow-up to this plan, explicitly requested on 2026-10-18. It supplements the original Product Contract without changing R1-R10 or the U1-U6 acceptance conditions. +- **Dependencies:** U1 and U6 define the shared-rule ownership and adopted Guidelines baseline. U7 may be implemented after U1 is merged; it must not block the independent U2-U5 work. +- **Target repositories:** Development-Guidelines for reusable subagent/CLI contracts; Agent-System-Dev for project routing preferences, model assignments, task-pack conventions, and local validation. Other repositories are later adopters, not part of this delivery. +- **Files (candidate):** Guidelines `standards/agent-execution.md`, its directly linked templates/schema only if fields are needed, and the relevant adoption/version documents; Dev `AGENT-EXECUTION.md`, `policy/delivery.md`, and focused prompt/config tests. Confirm actual ownership and existing coverage before editing. +- **Approach:** + - Require one bounded outcome per CLI task packet: one objective, explicit allowed paths/contracts, non-goals, dependencies, and a short verification command. Split work when it crosses independent ownership boundaries, needs unrelated background, or cannot be reviewed as one diff; avoid arbitrary tiny-unit counts. + - Include only the minimum task context in the packet. Link to authoritative files and quote only necessary sections; exclude unrelated logs, prior chat, speculative alternatives, and full plans. When context capacity is known, reserve a substantial share for inspection, implementation, iteration, and verification; when unknown, size conservatively for a 100K context and state that this is a planning assumption, not a discovered model limit. Avoid a universal token ceiling that ignores task risk or model differences. + - Treat phase boundaries as fresh sessions under the existing isolation rule. At meaningful long-task boundaries, emit a concise handoff containing objective/status, decisions, changed files and SHAs, commands/results, unresolved blockers, next action, and source links. Use provider-supported compaction only after a completed phase and only when it preserves those facts; never require hidden or unsupported auto-compression. Keep a durable handoff outside transient chat when work crosses sessions or agents. + - Separate generic standards from Dev's concrete routing. In Dev, record the requested preference: Codex or Claude for planning/design when available; PI with MiniMax-M3, Cursor Auto, and Antigravity (`agy`) with Gemini 3.8 Flash for bounded intermediate implementation/test work, selected in the existing configured order; other profiles may be chosen for a clear task-specific reason. Record requested and effective model/profile when observable, and mark unavailable/unobservable values honestly. Do not hardcode these providers or models into Guidelines. + - Make verification part of each packet and scale it to risk. The worker runs narrow direct checks; the host independently checks the integrated diff and runs the authoritative required suite once on the final candidate. Cross-file contracts, security, routing, and deploy-sensitive changes need explicit positive and negative cases; no worker summary or local receipt upgrades SELF to GATE/REPLAY. + - Preserve the existing agent-per-unit, single-writer, fresh-session, transport/admission, no-host-fallback, scope, authorization, independent-review, exact-head CI, and high-consequence boundaries. A prompt saying "do not edit concurrently" is guidance, not proof of enforcement; distinguish observed behavior from enforcement supplied by a tool. Do not add a controller or context-budget infrastructure unless current evidence shows the standard cannot be followed without it. +- **Test scenarios:** + - A sample packet for a modest 100K-context CLI agent can identify its goal, relevant files, allowed scope, and verification without needing the entire initiative plan or long chat history. + - A task spanning independent modules is split into reviewable packets with explicit dependency/handoff outputs; a cohesive local change is not fragmented unnecessarily. + - A known or unknown context window results in a transparent budget assumption and enough reserved capacity for implementation and tests, without claiming a provider's undocumented actual limit. + - A long task can hand off after a completed phase and resume in a fresh session from the concise artifact without the prior conversation; unsupported automatic compaction remains optional and is not a completion gate. + - Generic Guidelines remain provider-neutral, while the Dev configuration carries its requested model/profile preferences and records effective model when available. + - Positive and negative tests or conformance examples verify packet completeness, scope boundaries, fresh-session handoff requirements, and truthful model/context metadata; existing route validation remains unchanged unless a versioned schema extension is justified. + - Worker local checks, host integrated checks, independent review, and exact-head CI remain separate evidence classes and are all represented accurately. +- **Verification:** Review the standard and project config independently for ownership; validate any changed schema/config with existing validators; run focused prompt/config conformance tests and relevant governance tests. Test packet examples at both a small-context/unknown-model assumption and a larger-context model. Do not claim automatic context compression, model identity, or single-writer enforcement without observable evidence. Full combined `make test` remains part of final U6 validation, not a repeated check for every document edit. + +--- + +## Verification Contract + +| Scope | Verification outcome | +|---|---| +| Guidelines U1 | Relative links, rule ownership, rule strength, adoption lifecycle, and version change are internally consistent. No repository business tests are applicable to documentation-only source changes. | +| Dev U2-U5 | Focused task/recovery/evidence/token/facts tests prove permitted local continuation and retained stop conditions. Synthetic secrets and stubbed requests only. | +| Guidelines/Dev U7 | Existing schema/config validators and focused packet/prompt conformance tests cover bounded scope, stage handoff, model/context metadata, and provider-neutral ownership. | +| Combined pin U6 | `make test-governance` passes; `make test` validates the fixed component combination; `scripts/dm governance check --repo .` and `scripts/agent-exec validate --repo .` report accurate adoption/configuration state. | +| Delivery | Independent review and exact-head CI are evaluated on the final candidate. `SELF` is not promoted to `GATE` or `REPLAY`; no unavailable or `NOT_RUN` check is represented as passing. | + +--- + +## Definition of Done + +- Every original requirement R1-R10 maps to one or more implementation units and every unit has an observable completion condition; U7 has explicit follow-up acceptance scenarios. +- Local-only continuation is limited to a verifiable, authorized existing task; new-task startup and formal delivery retain strict remote and exact-candidate gates. +- Unknown side effects, authorization failures, scope expansion, dirty/unowned state, and secret disclosure remain stop conditions. +- Generic rules live in Guidelines; Dev command, branch, evidence, and adoption details remain project-owned. +- Credential and fact tests use synthetic values and stubbed external calls only. +- Adoption state is supported by specific evidence; unrun conformance remains `NOT_RUN` and enforcement is not inferred from text. +- CLI/subagent packets are sized for model capacity and task complexity, preserve concise cross-session handoffs, and keep concrete model preferences project-local; unsupported context or concurrency enforcement is not claimed. +- The updated Dev gitlink points to the merged Guidelines revision, and combined governance tests, independent review, and exact-head CI pass before any authorized merge. +- The plan's experimental or rejected implementation code is not part of its scope; the plan file and task card contain no implementation claims. diff --git a/policy/adoption.md b/policy/adoption.md index 97238e44..86603caa 100644 --- a/policy/adoption.md +++ b/policy/adoption.md @@ -1,13 +1,14 @@ # 通用规范采纳记录(Agent-System-Dev 组合) - 项目/负责人:Agent-System 组合 / weichen -- 状态:active(2026-09-30 经 PR weichen/Development-Guidelines#1 采纳 draft.7;机制经 Dev PR#1 合入 ef89fc8) +- 状态:active(首次采纳:2026-09-30 经 PR weichen/Development-Guidelines#1 采纳 draft.7,机制经 Dev PR#1 合入 ef89fc8;当前固定版本见下) - 规范位置:deps/development-guidelines(gitlink 固定版本) - 过程文档位置:modules/documents(docs/records/) - 版本权威:父仓 gitlink -- 批准/生效依据:Dev PR#1(19/19 正负例 + make test);guidelines PR#1 -- 规范源码版本:`0.1.0-draft.12`,实际合入提交 `2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb`(Guidelines PR #7);本仓 gitlink(`deps/development-guidelines`)固定该源码版本,版本权威为父仓 gitlink。 -- 合入调度通用版本:`0.1.0-draft.11`,实际合入提交 `2f5b19872be2a4597838a4131ad75d1be089ee5e`(Guidelines PR #5);draft.12 未修改其合入调度文件(standards/merge-queue.md、templates/merge-queue.md)。Dev 采用 [manual 串行合入规则](delivery.md#合入调度):并行开发,轮到后才同步、跑最终 CI 并由智能体合入。该规则不要求先完成全写者审计或第二轮试行,也不宣称自动互斥已经实现。 +- 批准/生效依据:历史——Dev PR#1(19/19 正负例 + make test)、guidelines PR#1;治理 kit 采纳——Dev PR #602(merge `f563ed612a9574e798ee3421c010a9e2ceb416a0`,归档卡 `.task/archive/feature-governance-v2-closeout.md`);当前固定版本随 feature/norm-remediation 交付,Review/CI 以该交付 PR 的 exact-head 记录为准 +- 规范源码版本:`0.1.0-draft.14`,Guidelines PR #11(含 U1 #10)合入提交 `a15db5085585f199c3c399824f6cf24c3144bbd5`;本仓 gitlink(`deps/development-guidelines`)固定该提交,版本以其 VERSION 为准,版本权威为父仓 gitlink。 +- 共享执行器版本:`deps/development-manager` 固定 Development-Manager PR #27(含 #26 单写者提示)合入提交 `6c019595fa02aa2d0a1a9b8f98fc224e69d2a901`;首次治理 kit 采纳时为 `0552b7358d6955057d6eea090326f83317ed992a`(Dev PR #602)。 +- 历史固定版本:draft.12 `2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb`(Guidelines PR #7);合入调度通用条文首次采纳于 draft.11 `2f5b19872be2a4597838a4131ad75d1be089ee5e`(Guidelines PR #5)。Dev 采用 [manual 串行合入规则](delivery.md#合入调度),不宣称自动互斥已经实现。 - 过程计划固定:Documents PR #96 实际合入 `129cc3579d9c9e3edfb46c99270d65f3e1dad0a0`,包含既有主干文档及获批 U1–U6 计划;当前进展以任务/PR 记录为准。 | 规范/工作流 | 适用条件与范围 | 执行方式/证据 | @@ -19,13 +20,21 @@ | standards/verification.md | 验收 | 本地 make test-governance + 组合测试;当前证据与历史 scratch 记录分开 | | standards/multi-repo.md | 跨仓/gitlink | manual;受控历史迁移通道已于 2026-09-30 行使一次(documents/records/2026-09-30-history-migration.md) | | 工作区回收 | 交付终点 | advisory(task-finish);负例:合入前 finish 拒绝输出 | -| policy/delivery.md(项目收紧) | 需求、长期方向、试验、实施迭代及交付 | goal-first:目标/范围/终点一次确认,范围内连续推进验证、Review、合入和安全收口;实施写入按 agent-per-unit(配置 AGENT-EXECUTION.md;实施/修复/测试正常候选仅为 Cursor、agy、PI,每候选对安全可恢复失败最多 3 次全新尝试,全部安全耗尽后一次 Codex CLI 紧急尝试;Codex 平时只用于规划、设计、计划或独立 Review;显式 fail-stop 立即停止;不限制推理时长、工具次数、规划方式或首次写文件时间;集成须成功 transport 的 agent-exec apply,或 pre-commit admit 精确内容来源;host 豁免仅为 .task/** 与 docs/superpowers/specs/**、plans/**;host 不直接实施;fallback_to_host 为 never;安全耗尽不发出 host-fallback-authorized,终态为 unavailable 或 blocked);仅真实决策点需人工;任务卡提供记录,post-merge 默认只提示,行为回归见 tests/test_task.py | -| AGENT-EXECUTION.md + agent-exec | 项目执行路由 | 本地 SELF:parser/adapter/controller 假 CLI 契约由共享执行器自身测试强制(Development-Manager internal/agentexec,经 deps/development-manager 钉住),并由 CI 中的 `scripts/agent-exec validate --repo .` 覆盖;本地薄入口拒绝路径由 tests/test_agent_exec_entry.py 强制;已安装 provider/model 真实身份待 U5 探针,此前不接受为已就位;远端平台准入仍为 GATE | +| policy/delivery.md(项目收紧) | 需求、长期方向、试验、实施迭代及交付 | goal-first:目标/范围/终点一次确认,范围内连续推进验证、Review、合入和安全收口;仅真实决策点需人工。实施写入按 agent-per-unit,候选、尝试上限、fail-stop、集成来源与 host 豁免以 [AGENT-EXECUTION.md](../AGENT-EXECUTION.md) 和 [Agent 实施单元](delivery.md#agent-实施单元与主模型职责) 为准(`fallback_to_host` 为 never)。任务卡提供记录,post-merge 默认只提示,行为回归见 tests/test_task.py | +| AGENT-EXECUTION.md + agent-exec | 项目执行路由 | 本地 SELF:parser/adapter/controller 假 CLI 契约由共享执行器自身测试强制(Development-Manager internal/agentexec,经 deps/development-manager 钉住),CI 运行 `scripts/agent-exec validate --repo .`;本地薄入口拒绝路径由 tests/test_agent_exec_entry.py 强制。模型身份逐次运行区分 requested/effective,CLI 未报告时记 not-observable,不视为已就位;远端平台准入仍为 GATE | | 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 合入调度 | active/manual;Dev main 使用 #518 排序和单一最终窗口 | 等待分支不追 main;窗口任务同步一次、固定 HEAD、跑当前必需 CI、由智能体合入并回读结果。Issue 不是原子锁,平台保护仍承担最终准入 | -| draft.12 工程 Agent 执行标准、终态收据 Schema、两级交付与分流(硬边界 8、agent-execution、A8) | 实施、交付、分流与 PR 目标;任务启动前 | active/manual:`make task-triage` 收据为 `make task-start … TRIAGE_RECEIPT=…` 的前置,远端不可核验时 REMOTE_UNVERIFIED(exit 6)并拒绝建分支;child PR 由 `make task-pr-check` 本地核验与 CI `PR base gate` 门禁;AGENT-EXECUTION.md 已声明 `integration` 块。draft.12 Schema 正负例与平台侧负例在项目中的真实运行本次未执行(NOT_RUN),不由本行推断 | +| draft.12 起:工程 Agent 执行标准、终态收据 Schema、两级交付与分流(硬边界 8、agent-execution、A8) | 实施、交付、分流与 PR 目标;任务启动前 | active/manual:`make task-triage` 收据为 `make task-start … TRIAGE_RECEIPT=…` 的前置,远端不可核验时 REMOTE_UNVERIFIED(exit 6)并拒绝建分支;child PR 由 `make task-pr-check` 本地核验与 CI `PR base gate` 门禁;AGENT-EXECUTION.md 已声明 `integration` 块。规则采纳不等于各 Schema 用例已在项目中运行,见下节 | +| draft.14:有界单元包、上下文预算与阶段交接(standards/agent-execution.md、workflows/handoff.md) | 实施、修复、测试与 Review 单元的派遣和跨阶段交接 | active/manual(默认采纳):通用规则供应商无关,留在共享规范与 [有界单元包、上下文与阶段交接](delivery.md#有界单元包上下文与阶段交接);模型偏好只是 [AGENT-EXECUTION.md](../AGENT-EXECUTION.md) 的项目配置,不是路由角色。上下文容量未知、无自动压缩或 CLI 未登录只作事实记录,不构成全局停止;不新增 fail-close 或强制门禁 | + +## 采纳证据与未运行项 + +- 历史正例与四类负例(SELF,非本次新运行):Dev PR #602 归档卡 `.task/archive/feature-governance-v2-closeout.md` 的 evidence 收据记录 `python3 docs/superpowers/plans/evidence/governance-adopt-cases.py .` 在源提交 `271d95ff688a1104f1717f19a7d3458332059dc6`(后于 `437a32ead266c0ab56053f2749e2a34f4ecfc177` 重放)ALL PASS 5 例:`positive-run-apply`(success 后 apply 成功)、`missing-config`(invalid-config)、`bad-receipt`(无效收据,apply 拒绝且 HEAD 不变)、`out-of-scope-path`(scope-expansion、无 transport)、`unknown-side-effect`(failed-unknown、无 transport)。该 PR 头 `0e27c7b5d70bdcebf063379845d0384fbb72f718` 的必需 CI(run 3684)只是当时那个 head 的 GATE,不成为当前 exact-head GATE。 +- 来源分层:agent-exec apply/admit 的本地完整性记录(HMAC)只证明内容来源,属 SELF;交付信任仍来自精确 HEAD 的必需 CI 与独立 Review(见 [本地记录与独立证据](governance.md#本地记录与独立证据)),二者分别登记。 +- 规则采纳、manual/advisory 执行能力与逐例证据分开登记:缺少与上述用例无关的额外用例不使既有 active 记录失效,也不据此宣称新的强制能力。 +- NOT_RUN:draft.12/draft.14 Schema 正负例与平台侧负例在本项目中的新运行;真实模型的采纳前后质量对比测试。 ## 项目差异 diff --git a/policy/delivery.md b/policy/delivery.md index 23993d1c..9d5e8b9f 100644 --- a/policy/delivery.md +++ b/policy/delivery.md @@ -154,13 +154,37 @@ host 可以直接编写并提交的豁免只有 `.task/**`、`docs/superpowers/s - 会话隔离不要求为简单任务增加新平台或复杂编排。平凡文档/配置任务仍按最小闭环执行;需要独立 Review 时只额外启动一个最小、一次性的评审 session,不复用实施 session,也不扩展任务范围。 +### 有界单元包、上下文与阶段交接 + +通用规则见共享 [standards/agent-execution.md](../deps/development-guidelines/standards/agent-execution.md) +与 [workflows/handoff.md](../deps/development-guidelines/workflows/handoff.md);本节只写本仓做法,模型偏好见 +[AGENT-EXECUTION.md](../AGENT-EXECUTION.md#model-preferences-project-choice)。 + +- **单元包**:用现有单元包 JSON 的 `goal`/`constraints`/`verification` 写清:一个结果及验收条件、允许路径与 + 须遵守的契约、非目标、前置提交 SHA、最小必读清单、直接验证命令、上下文容量及其来源(未知时按保守的 + 100K 假设)和为执行预留的余量。不新增 schema、运行时字段或控制器。 +- **容量**:100K/200K/300K/500K 只是规划参考,不是对任何 provider 能力的声明;容量更大不授权更宽的任务, + 单元仍按一个结果拆分。容量未知不构成失败条件,也不做自动容量强制。 +- **阶段交接**:长任务每完成一个里程碑,host 自动在现有任务记录中写一份简短的持久交接:目标、状态、关键决定、 + 文件与 SHA、命令与结果、阻塞项、下一步和来源链接;下一个 CLI 阶段或单元以全新会话启动,只读取交接与最小 + 必读材料,不为交接等待人工。 +- **上下文压力**:provider 支持时,只在阶段完成后使用其上下文压缩;没有自动压缩不阻塞工作。普通上下文压力用 + 交接加 host 在既有授权内重新打包单元处理;只有真实扩围才按既有 `scope-expansion` fail-stop。 +- **如实区分**:请求模型与实际事件报告的有效模型;可观察的 session 身份与 `not-observable`;worker 局部自检 + (SELF)与 host 集成验证、独立 Review、精确 HEAD CI(GATE)。 +- **验证分工**:worker 只跑直接检查;host 在最终候选上运行一次所需的集成套件。涉及安全、契约或部署的改动, + 须写明正向、负向与回归用例。 +- **单写者与强制边界**:同一单元同一时刻只有一个写者,host 在运行期间不改规范检出。记录时区分三类约束: + 提示词指导(单元包内容、容量、交接、模型偏好)、观察到的行为(事件报告的模型与 session),以及工具强制 + (配置校验、尝试上限、超时、transport/admit)。本节不新增门禁,不放宽凭据、范围、未知副作用与 CI 边界。 + ### 两级分支交付模型与分流检查 本仓采用两级业务交付模型: - **`main`**:稳定基线,仅接纳已完成并通过最终组合准入的结果。 - **大任务集成分支(initiative)**:从最新 `origin/main` 建立,根目标与即时目标均为 `main`。负责跨单元目标的阶段集成、组合验证与最终交付。合入主干后关闭、归档与回收,不作为长期存在的 develop 分支。 - **子任务分支(child)**:从远端所属大任务分支的当前头建立(须等于分流收据核验的 head,`make task-start` 校验),即时目标为大任务分支。负责一个有界单元的实施、独立 Review 与局部验证,通过 PR 合入大任务分支。 -- **普通任务分支(standalone)**:无需阶段集成的小需求或独立修复,直接从最新 `origin/main` 建立并向 `main` 交付。 +- **普通任务分支(standalone)**:可独立验收、无需阶段集成与组合验证的工作(含修复、完善与新能力),直接从最新 `origin/main` 建立并向 `main` 交付。 每个任务卡记录 `root_target` 与 `immediate_target`(子任务为所属大任务分支,其余为 `main`)。`make task-resume`、`make task-sync` 与 `make task-finish` 以 `immediate_target` 为准;任务卡默认在合入前以 `make task-close SELF=1` 闭合并随本 PR 合入(归档后上述命令仍从 `.task/archive/` 读取合入前闭合的卡);遗留卡的 `make task-close` 仅在 `FOLD=--fold` 时以其为闭合 PR base。 @@ -190,12 +214,13 @@ L1 章程与运行: - 子任务可继承 L1 章程中的全部或部分领域规则,并在子任务卡注明继承范围。 - L1 不追 `main`,只在最终窗口、需要 `main` 上某修复、或长周期 L1 的里程碑/每周一次时运行 `make task-sync`。 -任务通道按 修复/完善/扩展/新增 分类: -- **修复**:`fix/`(P0 生产事故用 `hotfix/`),standalone 直达 `main`,在合入队列中优先;hotfix 可在 #518 写明原因后插队。 -- **完善**:进入所在领域的“完善列车” L1,按周或按批合入 `main`。 -- **扩展**:优先 JOIN 同主题在途 L1,没有时新建。 -- **新增**:建立 L1 并写章程。 -- 治理与规范调整集中到一个治理 L1。 +任务通道分两步判定,类别与拓扑互不替代: +- **类别只决定优先级**:修复用 `fix/`(P0 生产事故用 `hotfix/`),在合入队列中优先;hotfix 可在 #518 写明原因后插队。完善、扩展、新增不设额外优先级。 +- **拓扑只按集成与验收需要决定**,不因工作被称为新增、完善或规模大而建 L1: + - **standalone**:工作有自己的验收、没有依赖单元、不共享集成契约、也不需要协同发布验证时,从 `main` 切出并直达 `main`;小完善与新能力同样适用。可独立验收的修复默认为 standalone。 + - **L1**:多个相互依赖的单元、跨单元集成或一个共同最终验收需要阶段集成与组合验证时,建立 L1 并写章程。 + - **child**:工作(含修复)属于某在途 L1 的共同范围、依赖链或验收时,作为其子任务。只有该 L1 已记录的目标与验收确实覆盖此工作、且加入不会悄然改变其已约定范围时才 `JOIN`;否则按上两条判定 `NEW`。远端发现不可核验时不得得出 `NEW`、不建分支。 +- 治理与规范调整同样按上述拓扑判定;需要 L1 时集中到一个治理 L1。 - 只改记录(任务卡、报告)的变更不单独推进 `main`,随所属任务 PR 合入。 - 组件 pin 跟随用到它的 L1/任务一起推进,不单独开 pin PR(紧急修复除外)。 @@ -206,7 +231,7 @@ L1 章程与运行: | 单元→L2 | worker 自检 + host 最小直接检查(`make test-affected`) | host 审阅实际 diff | 无 | 无 | 无 | | L2→L1 | `make task-sync` 同步 L1 头 + `make test-affected` | 一次全新会话独立 Review | 轻量 CI(PR base 非 `main` 时只跑 `test` 作业,`test-pg`/`svc-realcombo` 跳过)作为 GATE | 不排 #518 窗口、不需分流收据(已登记子任务)、不要求三段证据;任务卡合入前 `make task-close SELF=1` | 无 | | L1 的 WIP PR | — | — | 完整 CI,作为非阻塞预警 | — | — | -| L1 或修复→main | 最终窗口内 `make task-sync` + 完整组合验证 | 整体独立 Review | 完整 CI(`test`、`test-pg`、`svc-realcombo`) | #518 窗口、三段证据、合入前折叠闭合 | L1 最终一次用户专属验收(如有) | +| L1 或 standalone→main | 最终窗口内 `make task-sync` + 完整组合验证 | 整体独立 Review | 完整 CI(`test`、`test-pg`、`svc-realcombo`) | #518 窗口、三段证据、合入前折叠闭合 | L1 最终一次用户专属验收(如有) | 人工审批只保留 L1 章程确认、L1 最终用户专属验收与高后果双钥匙操作;等待 CI 一律 `make ci-wait SHA=<完整 SHA>`。 diff --git a/policy/facts.md b/policy/facts.md index c1469f79..3fcf83fa 100644 --- a/policy/facts.md +++ b/policy/facts.md @@ -7,12 +7,17 @@ 把 PASS 项的 last_verified 刷新为当天并写回本表(随 PR 合入)。 - 表内验证命令不得含竖线 `|`(保持表格与解析可核);新增事实先实测再登记,不得凭记忆填日期。 - precheck 的 `[facts]` 行报告过期项;AGENTS.md「Gitea 与 Git 访问事实」节为本表的叙述版。 +- 凭据类验证命令不得把凭据展开进命令行、输出或证据:经 `scripts/facts-verify.sh` 探针子命令在进程内读取并发送。 +- 旧端点 git-panel.u1680832.nyat.app:30319 已退役,一律不得使用——这是政策禁令,不随 F4 核验结果变化; + F4 只登记可观测的 TLS 握手失败现象,DNS 失败、超时、连接失败或握手成功都判 FAIL,不刷新 last_verified。 +- F4 现有 last_verified(2026-10-14)是历史日期,来自旧的宽泛检查,不证明当前 TLS 层握手失败判据; + 经该判据独立重验前不得当作已核实。 | ID | 事实 | 验证命令 | last_verified | TTL(天) | |---|---|---|---|---| | F1 | Gitea Web/API 可达:https://git-home.u1680832.nyat.app:30319(1.27.3) | `curl -fsS -m 10 https://git-home.u1680832.nyat.app:30319/api/v1/version` | 2026-10-14 | 30 | -| F2 | API token 存在且可用(值在 ~/.secrets/gitea,HOME/IMC 两段各一组 `- ACCESS_TOKEN=…`,本条取 HOME 段;不入仓不回显) | `t=$(awk '/^HOME:/{h=1;next} /^IMC:/{h=0} h&&sub(/^- ACCESS_TOKEN=/,""){print;exit}' ~/.secrets/gitea); test -n "$t" && curl -fsS -m 10 -o /dev/null -H "Authorization: token $t" https://git-home.u1680832.nyat.app:30319/api/v1/user` | 2026-10-14 | 30 | +| F2 | API token 存在且可用(值在 ~/.secrets/gitea,HOME/IMC 两段各一组 `- ACCESS_TOKEN=…`,本条取 HOME 段;不入仓不回显,不进子进程 argv) | `bash scripts/facts-verify.sh api-token https://git-home.u1680832.nyat.app:30319/api/v1/user` | 2026-10-14 | 30 | | F3 | Git over SSH 可达:ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/<Repo>.git(key ~/.ssh/home_gitea) | `GIT_SSH_COMMAND="ssh -i ~/.ssh/home_gitea -o BatchMode=yes" git ls-remote ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/Development-Guidelines.git HEAD` | 2026-10-14 | 30 | -| F4 | 旧端点 git-panel.u1680832.nyat.app:30319 已失效(TLS 握手失败),不得使用 | `! curl -fsS -m 8 https://git-panel.u1680832.nyat.app:30319/api/v1/version` | 2026-10-14 | 180 | +| F4 | 旧端点 git-panel.u1680832.nyat.app:30319 TCP 可连通但 TLS 握手失败(仅 TLS 层握手错误判 PASS) | `bash scripts/facts-verify.sh tls-handshake-fails git-panel.u1680832.nyat.app 30319` | 2026-10-14 | 180 | | F5 | 基座 users 表存有姓名(name 列,CreateAccount/ListUsersAdmin 读写;EXT-19 目录自 P3 起向授权管理读面下发) | `cd modules/backend && go test ./internal/app/ -run TestInprocHostSearchAccounts -count=1` | 2026-10-14 | 180 | | F6 | 基座账号无停用位(users 表无 disabled;EXT-19 Valid 恒为存在性)——停用机制未实现,不得当已具备 | `cd modules/backend && ! grep -rn -e disabled -e Disabled internal/store/auth.go pkg/extension/extension.go` | 2026-10-14 | 180 | diff --git a/policy/governance.md b/policy/governance.md index 41904cbe..3fe29728 100644 --- a/policy/governance.md +++ b/policy/governance.md @@ -42,7 +42,10 @@ make task-start NAME=feature/my-task TRIAGE_RECEIPT=/tmp/my-task-triage.json PRE 恢复已有登记任务先捕获本次启动检查,再在原任务 worktree 用 `make task-resume PRECHECK_RECEIPT=/tmp/my-task-resume.json` 验证并原样导入;不重新建分支。 默认 `TASK_WORK_MODE=delivery`,继续要求严格 READY 和当前目标一致。 -工作区忙或落后时先由 owner 保存现场并同步;远端不可达时只继续不依赖新鲜基线的阅读和分析。 +工作区忙或落后时先由 owner 保存现场并同步。远端不可达时,delivery 恢复仍以 REMOTE_UNVERIFIED 拒绝; +显式 `TASK_WORK_MODE=local` 可在先核验收据、活动任务、原 R0/HEAD/工作区范围后导入 LOCAL_READY 收据, +继续已授权局部工作(子任务仅在远端不可查询时以已登记的本地父分支为参考,且须包含记录的 `parent_head_sha`)。 +远端可达但目标或已登记父分支缺失(即使本地同名分支存在)、回退/分叉或无法比较时仍以 REMOTE_UNVERIFIED 拒绝,不导入收据。离线不得建任务、同步、PR 检查、Review/合入或交付,本地参考不视为最新或可交付。 局部恢复接口已具备,正式交付仍按[合入调度](delivery.md#合入调度)的单一最终窗口执行。 启用后,既有任务可以显式选择 local,保持原 R0,标记落后,继续已授权局部工作: @@ -53,7 +56,17 @@ TASK_WORK_MODE=local make task-resume PRECHECK_RECEIPT=/tmp/my-local-resume.json ``` LOCAL_READY 只用于局部恢复;新任务入口始终校验严格 READY,正式验收与合入另行同步。 -恢复核验实际 worktree/卡片/分支、R0 祖先关系及源码外原始收据;脏现场、Git 操作、组件漂移和未知远端仍拒绝。 +恢复核验实际 worktree/卡片/分支、R0 祖先关系及源码外原始收据;脏现场、Git 操作和组件漂移仍拒绝。 +`resume-local` 只接受 LOCAL_READY 收据,且须同时满足:卡片位于本 worktree 的 `.task/` 且状态未以“已”开头 +(已完成/已放弃等闭合任务拒绝);收据内 `[task-state]` 的登记路径与 R0 等于当前登记任务;收据前后与当前 HEAD +一致、工作区干净且组件状态未变;收据 `root_target/immediate_target` 等于卡片范围;目标只允许快进落后,回退或分叉拒绝。 +远端跟踪目标 ref 缺失时,local 模式的 precheck 与 `resume-local` 只在当前登记任务身份、本地 R0 对象、R0 为本地 HEAD 祖先、 +工作区干净、分支/worktree、组件与范围全部核验通过时继续。缺失或过期的远端 ref 从不证明新鲜度;只要有任一目标历史可用, +其祖先关系须一致。local precheck 刷新失败时,远端不可查询按上述本地核验继续;远端可查询时,以缓存目标(无缓存时为活动任务卡 +登记的 R0)为基线,通告提交须本地存在且为基线后代才继续,目标 ref 缺失、回退/分叉或通告提交本地缺失无法比较均判 REMOTE_UNVERIFIED。 +正式 Review 与合入仍须先完成新鲜的权威同步。 +`resume-delivery` 仍要求 READY 且收据目标等于当前权威目标。LOCAL_READY 始终是 SELF 本地工作记录, +不能用于建任务、正式交付、Review、合入、CI、GATE 或 REPLAY。 旧收据允许作为历史证据原样导入,不自动成为当前恢复或交付准入;格式继续为 schema 1。 堆叠(standalone)任务使用 `PARENT=feature/parent PARENT_PR=<父PR链接>`;子任务改用 `KIND=child`。父分支须包含本批主干 R0; diff --git a/policy/guidelines-adoption.md b/policy/guidelines-adoption.md index ada1ae8b..c81a1ae0 100644 --- a/policy/guidelines-adoption.md +++ b/policy/guidelines-adoption.md @@ -5,8 +5,8 @@ - 规范位置:`deps/development-guidelines` - 规范性文档目录:`policy/`(项目规则与制度放此处;只收紧不放宽上游规范) - 过程文档位置:任务卡 `.task/`,归档 `.task/archive/`,规划记录 `docs/superpowers/plans/`;任务主记录在对应 PR 中 -- 版本权威:本仓 gitlink `deps/development-guidelines` @ `2688fd2be6a5`(完整提交 `2688fd2be6a52c0bf110e7e6366b6f291ab5e4eb`);可读标签:无 -- 批准/生效依据:`feature/governance-v2-closeout PR(Agent-System-Dev)`;在该 PR 合入前,证据、Review 与 CI 须先记录于 `.task/feature-governance-v2-closeout.md` +- 版本权威:本仓 gitlink `deps/development-guidelines` @ `a15db5085585`(完整提交 `a15db5085585f199c3c399824f6cf24c3144bbd5`,`0.1.0-draft.14`,Guidelines PR #11);可读标签:无;版本以该提交的 VERSION 为准,历史固定版本见 [policy/adoption.md](adoption.md) +- 批准/生效依据:Dev PR #602 已合入(merge `f563ed612a9574e798ee3421c010a9e2ceb416a0`),证据、Review 与 CI 记录见归档卡 `.task/archive/feature-governance-v2-closeout.md`;当前固定版本随 feature/norm-remediation 交付 - 替代范围和迁移说明:无。既有 [policy/adoption.md](adoption.md) 的逐条决策保持不变,本文件不替代其内容。 逐条规范的采纳、不适用理由与执行方式以 [policy/adoption.md](adoption.md) 为准,本文件不重复其表格。 @@ -17,7 +17,7 @@ | 规范/工作流 | 适用条件与范围 | 采纳或不适用理由 | 执行方式/证据 | |---|---|---|---| -| [standards/agent-execution.md](../deps/development-guidelines/standards/agent-execution.md) | 工程 Agent CLI 执行路由、会话隔离与受控集成 | 凡允许 AI 协助必须采纳;配置见 `AGENT-EXECUTION.md` | advisory:`AGENT-EXECUTION.md` + `scripts/agent-exec`(validate / admit);CI 步骤「Validate AGENT-EXECUTION.md with the pinned shared executor」与「Governance check with the pinned kit」(`scripts/dm governance check --repo .`)。分支保护要求 governance check 为必需检查之前,不宣称为强制(enforced)门禁 | +| [standards/agent-execution.md](../deps/development-guidelines/standards/agent-execution.md) | 工程 Agent CLI 执行路由、会话隔离与受控集成 | 凡允许 AI 协助必须采纳;配置见 `AGENT-EXECUTION.md` | advisory:`AGENT-EXECUTION.md` + `scripts/agent-exec`(validate / admit;本地完整性记录属 SELF);draft.14 起含有界单元包、上下文与阶段交接,项目只配置模型偏好;CI 步骤「Validate AGENT-EXECUTION.md with the pinned shared executor」与「Governance check with the pinned kit」(`scripts/dm governance check --repo .`)。分支保护要求 governance check 为必需检查之前,不宣称为强制(enforced)门禁 | ## 项目差异 @@ -27,5 +27,5 @@ - 入口:根目录 `AGENTS.md`、`PROJECT.md`、`AGENT-EXECUTION.md`、`scripts/dm`、`scripts/task`、`.task/templates/task-card.md`。 - 相对链接:规范文档统一经 `../deps/development-guidelines/...` 引用;本仓内文档使用仓内相对路径。 -- 版本获取:`deps/development-guidelines` gitlink 固定于上表版本;`deps/development-manager` gitlink 固定于 `0552b7358d6955057d6eea090326f83317ed992a`,由 `scripts/dm` 按该提交构建共享 dm-server。 -- 验证:在采纳 PR 的临时副本中运行 `scripts/dm governance check --repo .` 与 `scripts/agent-exec validate --repo .`,结果与 `make test-governance` 记录于 `.task/feature-governance-v2-closeout.md`。 +- 版本获取:`deps/development-guidelines` gitlink 固定于上文版本;`deps/development-manager` gitlink 固定于 `6c019595fa02aa2d0a1a9b8f98fc224e69d2a901`(Development-Manager PR #27,首次采纳时为 `0552b7358d69`),由 `scripts/dm` 按该提交构建共享 dm-server。 +- 验证:首次接入时 `scripts/dm governance check --repo .`、`scripts/agent-exec validate --repo .`、正例与四类负例及 `make test-governance` 的 SELF 结果见归档卡 `.task/archive/feature-governance-v2-closeout.md`(摘要见 [policy/adoption.md](adoption.md#采纳证据与未运行项)),不作为当前 exact-head 证据;当前固定版本以交付 PR 当前 HEAD 的必需 CI 为准。 diff --git a/scripts/check-pins.sh b/scripts/check-pins.sh index 219a017e..cab88a2b 100755 --- a/scripts/check-pins.sh +++ b/scripts/check-pins.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # 组合指针一致:容器仓在 Dev 钉住的提交上,deps/ 里的每个 gitlink -# 必须等于 Dev 顶层对应指针。development-guidelines 只告警。 +# 必须等于 Dev 顶层对应指针。development-guidelines / development-manager +# 是治理规范与工具,版本由各项目固定采纳自行决定,不是运行时依赖,只告警。 set -euo pipefail root=$(cd "$(dirname "$0")/.." && pwd) cd "$root" @@ -48,10 +49,10 @@ for container in "${containers[@]}"; do while read -r mode _typ sha name; do [ "$mode" = "160000" ] || continue base=${name#deps/} - if [ "$base" = "development-guidelines" ]; then - dev=$(pin_of deps/development-guidelines) + if [ "$base" = "development-guidelines" ] || [ "$base" = "development-manager" ]; then + dev=$(pin_of "deps/$base") if [ "$sha" != "$dev" ]; then - echo "warn: $container deps/development-guidelines is ${sha:0:12}, Dev is ${dev:0:12}" >&2 + echo "warn: $container deps/$base is ${sha:0:12}, Dev is ${dev:0:12}" >&2 fi continue fi diff --git a/scripts/evidence.py b/scripts/evidence.py index a40a5e84..3e3d7e92 100644 --- a/scripts/evidence.py +++ b/scripts/evidence.py @@ -27,6 +27,7 @@ BRANCH = re.compile(r"[A-Za-z0-9][A-Za-z0-9._/-]{0,199}\Z") # 可选的分支目标字段:两者须同时出现或同时缺省(旧收据没有)。 TARGET_FIELDS = ("root_target", "immediate_target") PRE_MERGE_CLOSED = "已完成(合入前闭合)" +TASK_STATE = re.compile(r"^\[task-state\] registered=(.+) initial_base=([0-9a-f]+)$", re.M) BASE_FIELDS = {"schema", "source", "timestamp", "command", "command_sha256", "cwd", "common_dir", "before", "after", "after_error", "exit_code", "output", "output_sha256", "receipt_sha256"} @@ -240,7 +241,7 @@ def read_receipt(filename): return value -def task_context(current=None): +def task_context(current=None, require_target=True): """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() @@ -269,10 +270,11 @@ def task_context(current=None): 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"]: + if not current["target_head"] and require_target: raise EvidenceError("Task target is unverified; fetch it first") for tip in (current["head"], current["target_head"]): - git("merge-base", "--is-ancestor", r0, tip) + if tip: + 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: @@ -295,12 +297,13 @@ def validate_precheck(receipt, purpose="start"): 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"]: + if after["target_branch"] != current["target_branch"] or ( + not after["target_head"] and purpose != "resume-local"): 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) + context = task_context(current, require_target=purpose != "resume-local") try: Path(receipt["cwd"]).resolve().relative_to(context["root"]) except ValueError: @@ -308,12 +311,38 @@ def validate_precheck(receipt, purpose="start"): 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 after["target_head"]: + 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 + if after["target_head"] and current["target_head"]: + 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 + validate_local_scope(receipt, current, context) + + +def require_active_task(root): + """Closed, archived or abandoned cards cannot authorize local continuation.""" + card = find_card() + status = re.search(r";状态:\s*([^;\s][^;\n]*)", card.read_text(encoding="utf-8")) + if card.resolve().parent != root / ".task" or not status or status.group(1).startswith("已"): + raise EvidenceError("Local continuation requires an active registered task") + + +def validate_local_scope(receipt, current, context): + """LOCAL_READY continues one active registered task; it remains SELF local-work evidence only.""" + require_active_task(context["root"]) + states = TASK_STATE.findall(receipt["output"]) + if len(states) != 1 or Path(states[0][0]) != context["root"] or states[0][1] != context["r0"]: + raise EvidenceError("Precheck belongs to a different task identity or initial baseline") + for obj in (receipt["before"], receipt["after"]): + if obj["status"] or obj["untracked"] or obj["tracked_diff_sha256"] != digest(b""): + raise EvidenceError("Precheck workspace was not clean") + if obj["submodules"] != current["submodules"]: + raise EvidenceError("Precheck component state has changed") + if tuple(receipt.get(key) for key in TARGET_FIELDS) != task_targets(): + raise EvidenceError("Precheck authorization scope differs from the registered task") def find_card(): @@ -443,8 +472,10 @@ 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() + if sys.argv[1:] in (["--local-context"], ["--local-context", "--active"]): + context = task_context(require_target=False) + if sys.argv[2:]: + require_active_task(context["root"]) print("[task-state] registered=" + str(context["root"]) + " initial_base=" + context["r0"]) return 0 if sys.argv[1:]: @@ -457,8 +488,10 @@ def main(): if imported or validated: receipt = read_receipt(imported or validated) if validated: - validate_precheck(receipt, os.environ.get("VALIDATE_RECEIPT_PURPOSE", "start")) - report(receipt, "precheck validated") + purpose = os.environ.get("VALIDATE_RECEIPT_PURPOSE", "start") + validate_precheck(receipt, purpose) + report(receipt, "local continuation validated (local work only; not delivery evidence)" + if purpose == "resume-local" else "precheck validated") else: card = find_card() append_card(card, receipt) diff --git a/scripts/facts-verify.sh b/scripts/facts-verify.sh index 490ed92d..61d4200c 100755 --- a/scripts/facts-verify.sh +++ b/scripts/facts-verify.sh @@ -3,7 +3,86 @@ # 规则依据:AGENTS.md「证据与验证门禁」(外部事实零信任);verification.md「声明与证据来源」。 # 用法:make facts-verify [ARGS=--update] (--update:把 PASS 项 last_verified 刷为当天并写回) # 表内验证命令约定不含竖线;含 "\|" 转义时按字面还原后执行。 +# 登记表引用的探针子命令(单独运行时在 stderr 说明判定,不输出凭据): +# api-token URL URL 须逐字等于登记端点 https://git-home.u1680832.nyat.app:30319/api/v1/user, +# 校验通过后才进程内读 ~/.secrets/gitea HOME 段 ACCESS_TOKEN 并 GET(不跟随重定向); +# 0=接受 3=缺失 4=格式不符 5=请求失败/被拒/重定向 6=非登记端点 +# tls-handshake-fails HOST PORT 仅当 TCP 已连通且 TLS 层握手报错时 0; +# 3=DNS 失败 4=超时 5=连接失败 6=握手成功(端点仍可用) set -uo pipefail +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + +case "${1:-}" in + api-token) + [ $# -eq 2 ] || { echo "usage: facts-verify.sh api-token URL" >&2; exit 2; } + exec python3 - "$SCRIPT_DIR" "$2" <<'PY' +import sys, urllib.request +sys.path.insert(0, sys.argv[1]) +import task_triage + +ENDPOINT = "https://git-home.u1680832.nyat.app:30319/api/v1/user" +if sys.argv[2] != ENDPOINT: + print("api-token: URL 不是 policy/facts.md F2 登记端点,拒绝发送凭据", file=sys.stderr) + sys.exit(6) + + +class NoRedirect(urllib.request.HTTPRedirectHandler): + def redirect_request(self, *args, **kwargs): + return None + + +urllib.request.install_opener(urllib.request.build_opener(NoRedirect)) +url = ENDPOINT +token = task_triage.secrets_file_token() +if not token: + print("api-token: ~/.secrets/gitea HOME 段 ACCESS_TOKEN 缺失", file=sys.stderr) + sys.exit(3) +if not task_triage.TOKEN_RE.fullmatch(token): + print("api-token: ~/.secrets/gitea HOME 段 ACCESS_TOKEN 格式不符(应为单个无空白值)", file=sys.stderr) + sys.exit(4) +try: + task_triage.http_get_json(url, token) +except task_triage.Stop as stop: + print("api-token: " + stop.message, file=sys.stderr) + sys.exit(5) +print("api-token: accepted by " + url, file=sys.stderr) +PY + ;; + tls-handshake-fails) + [ $# -eq 3 ] || { echo "usage: facts-verify.sh tls-handshake-fails HOST PORT" >&2; exit 2; } + exec python3 - "$2" "$3" <<'PY' +import socket, ssl, sys + +host, port = sys.argv[1], int(sys.argv[2]) + + +def verdict(code, text): + print(f"tls-handshake-fails {host}:{port}: {text}", file=sys.stderr) + sys.exit(code) + + +try: + sock = socket.create_connection((host, port), timeout=8) +except socket.gaierror: + verdict(3, "DNS resolution failed (not a TLS observation)") +except TimeoutError: + verdict(4, "TCP connect timed out (not a TLS observation)") +except OSError as exc: + verdict(5, f"TCP connect failed ({type(exc).__name__}; not a TLS observation)") +try: + with sock, ssl.create_default_context().wrap_socket(sock, server_hostname=host) as tls: + version = tls.version() +except ssl.SSLError as exc: + verdict(0, f"TLS handshake failed ({type(exc).__name__}: {getattr(exc, 'reason', None) or 'unknown'})") +except TimeoutError: + verdict(4, "TLS handshake timed out (not a TLS-layer error)") +except OSError as exc: + verdict(5, f"connection failed during handshake ({type(exc).__name__}; not a TLS-layer error)") +verdict(6, f"TLS handshake succeeded ({version}); endpoint still answers") +PY + ;; +esac + F="${FACTS:-policy/facts.md}" [ -f "$F" ] || { echo "ERROR: 事实登记表 $F 不存在" >&2; exit 1; } python3 - "$F" "${1:-}" <<'PY' diff --git a/scripts/task.sh b/scripts/task.sh index a64e6d01..4cfdccea 100755 --- a/scripts/task.sh +++ b/scripts/task.sh @@ -18,6 +18,44 @@ fetch_target() { git rev-parse --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" >/dev/null \ || { echo "[verdict] REMOTE_UNVERIFIED — target commit missing" >&2; return 6; } } +origin_transport_unavailable() { # $1=失败的远端命令 stderr → 仅明确的传输失败(DNS/连接超时/拒绝/重置/无路由,或本地缺失仓库路径)返回 0;认证/授权/权限与未知错误返回 1 + local err=$1 url path + if printf '%s\n' "$err" | grep -qiE 'authentication failed|permission denied|unauthori[sz]ed|forbidden|HTTP[^0-9]*40[13]|\b40[13]\b|could not read (username|password)|terminal prompts disabled|invalid credentials|access denied|host key verification failed'; then + return 1 + fi + if printf '%s\n' "$err" | grep -qiE 'could not resolve (host|hostname|proxy)|name or service not known|temporary failure in name resolution|nodename nor servname|connection timed out|operation timed out|timed out after|connection refused|connection reset|no route to host|network is unreachable|couldn.t connect to server'; then + return 0 + fi + printf '%s\n' "$err" | grep -qF 'does not appear to be a git repository' || return 1 + url=$(git remote get-url origin 2>/dev/null) || return 1 + case "$url" in + file://*) path=${url#file://} ;; + /*|./*|../*) path=$url ;; + *) return 1 ;; + esac + [ ! -e "$path" ] +} +target_history() { # $1=可选基线(如任务 R0;无缓存目标时代替比较)→ 输出 ok(通告提交为基线后代;传输不可达时缓存目标为基线后代或无缓存目标)、rewound(回退或分叉)或 uncomparable(认证/授权/未知错误、目标 ref 缺失、无基线、提交本地缺失或无法比较) + local base=${1:-} cached listing advertised rc=0 + cached=$(git rev-parse -q --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" 2>/dev/null) || cached= + if ! listing=$(git ls-remote origin "refs/heads/${TARGET_BRANCH}" 2>&1); then + if ! origin_transport_unavailable "$listing"; then echo uncomparable + elif [ -z "$cached" ]; then echo ok + elif [ -z "$base" ]; then echo uncomparable + else + git merge-base --is-ancestor "$base" "$cached" 2>/dev/null || rc=$? + case "$rc" in 0) echo ok ;; 1) echo rewound ;; *) echo uncomparable ;; esac + fi + return 0 + fi + [ -n "$cached" ] || cached=$base + advertised=$(printf '%s\n' "$listing" \ + | awk -v ref="refs/heads/${TARGET_BRANCH}" '$2 == ref && !seen { print $1; seen = 1 }') + [ -n "$advertised" ] && [ -n "$cached" ] || { echo uncomparable; return 0; } + git cat-file -e "${advertised}^{commit}" 2>/dev/null || { echo uncomparable; return 0; } + git merge-base --is-ancestor "$cached" "$advertised" 2>/dev/null || rc=$? + case "$rc" in 0) echo ok ;; 1) echo rewound ;; *) echo uncomparable ;; esac +} 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() { @@ -31,23 +69,38 @@ cmd_precheck() { need_repo local mode mode=$(work_mode) - fetch_target - local branch head rmain dirty sub_drift sub_uninit + # local 模式刷新失败只标记远端新鲜度不可用,不拿缓存远端引用冒充最新;delivery 保持 REMOTE_UNVERIFIED。 + local remote_fresh=1 fetch_err="" + if [ "$mode" = local ]; then + git check-ref-format "refs/heads/${TARGET_BRANCH}" >/dev/null || die "invalid target branch" + fetch_err=$(fetch_target 2>&1 >/dev/null) || remote_fresh=0 + else + fetch_target + fi + local branch head rmain="" dirty sub_drift sub_uninit branch=$(git rev-parse --abbrev-ref HEAD) head=$(git rev-parse HEAD) - rmain=$(git rev-parse --verify "origin/${TARGET_BRANCH}") + [ "$remote_fresh" -eq 1 ] && rmain=$(git rev-parse --verify "origin/${TARGET_BRANCH}") dirty=$(git status --porcelain | { grep -v '^.. \.zcode/' || true; } | wc -l) sub_drift=$(git submodule status --recursive 2>/dev/null | { grep -cE '^\+|^U' || true; }) sub_uninit=$(git submodule status --recursive 2>/dev/null | { grep -cE '^-' || true; }) - local behind ahead - behind=$(git rev-list --count "HEAD..origin/${TARGET_BRANCH}") - ahead=$(git rev-list --count "origin/${TARGET_BRANCH}..HEAD") + local behind=0 ahead=0 echo "[repo] branch=${branch} head=$(echo "$head" | cut -c1-10)" - echo "[remote] origin/${TARGET_BRANCH}=$(echo "$rmain" | cut -c1-10) ahead=${ahead} behind=${behind}" + if [ "$remote_fresh" -eq 1 ]; then + behind=$(git rev-list --count "HEAD..origin/${TARGET_BRANCH}") + ahead=$(git rev-list --count "origin/${TARGET_BRANCH}..HEAD") + echo "[remote] origin/${TARGET_BRANCH}=$(echo "$rmain" | cut -c1-10) ahead=${ahead} behind=${behind}" + else + echo "[remote] origin/${TARGET_BRANCH}=unavailable — refresh failed; remote freshness unverified (cached ref not used)" + [ -n "$fetch_err" ] && printf '%s\n' "$fetch_err" | sed -e '/^\[verdict\]/d' -e 's/^/[remote] /' + fi echo "[tree] $([ "$dirty" -eq 0 ] && echo clean || echo "dirty(${dirty})") submodules_drift=${sub_drift} uninit=${sub_uninit}" local wt merged wt=$(git worktree list | tail -n +2 | wc -l) - merged=$(git branch --merged "origin/${TARGET_BRANCH}" 2>/dev/null | { grep -vE 'main|master|\*' || true; } | tr -d ' +') + merged="" + if git rev-parse -q --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" >/dev/null 2>&1; then + merged=$(git branch --merged "origin/${TARGET_BRANCH}" 2>/dev/null | { grep -vE 'main|master|\*' || true; } | tr -d ' +') + fi [ "${sub_uninit:-0}" -gt 0 ] && echo "[warn] ${sub_uninit} 个子模块未初始化——构建/测试前需物化(不阻断写入)" echo "[worktrees] linked=${wt}" [ -n "$merged" ] && echo "[recycle] merged un-recycled branches: $(echo $merged)" @@ -116,7 +169,9 @@ 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(树脏=他人或未收尾现场,不得写入)")" - if [ "$mode" = local ]; then + if [ "$mode" = local ] && [ "$remote_fresh" -eq 0 ]; then + echo "[target] unavailable(remote freshness unverified; task initial baseline is reported separately)" + elif [ "$mode" = local ]; then echo "[target] ${rmain}(observed; task initial baseline is reported separately)" else echo "[R0] $(echo "$rmain" | cut -c1-10)(完整:$rmain)" @@ -126,7 +181,19 @@ cmd_precheck() { exit 3 fi if [ "$mode" = local ]; then - python3 "${SCRIPT_DIR}/evidence.py" --local-context || return 2 + if [ "$remote_fresh" -eq 0 ]; then + # 以活动任务卡登记的 R0 为比较基线(不把它当作远端最新值);先于任务上下文核验,使缓存目标与 R0 矛盾时给出明确判定。 + case "$(target_history "$(card_r0 "$(branch_card "$branch")")")" in + rewound) + echo "[verdict] REMOTE_UNVERIFIED — origin/${TARGET_BRANCH} rewound or diverged from the known target; preserve local work and reconcile the target history" + return 6 ;; + uncomparable) + echo "[verdict] REMOTE_UNVERIFIED — origin/${TARGET_BRANCH} is missing or its advertised commit cannot be compared with the known target; preserve local work and retry the refresh" + return 6 ;; + esac + fi + python3 "${SCRIPT_DIR}/evidence.py" --local-context --active || return 2 + [ "$remote_fresh" -eq 1 ] || echo "[remote-freshness] unavailable — LOCAL_READY rests on local task context only" echo "[verdict] LOCAL_READY — existing task may continue authorized local work; final delivery still requires latest-target sync and checks" return 0 fi @@ -442,9 +509,9 @@ cmd_resume() { need_repo local mode mode=$(work_mode) - fetch_target + [ "$mode" = local ] || fetch_target [ -n "${PRECHECK_RECEIPT:-}" ] || die "PRECHECK_RECEIPT required: capture the current task precheck before resume" - local receipt + local receipt remote_fresh=1 receipt=$(realpath -- "$PRECHECK_RECEIPT") VALIDATE_RECEIPT="$receipt" VALIDATE_RECEIPT_PURPOSE="resume-${mode}" bash "${SCRIPT_DIR}/evidence.sh" # 立即目标:子任务为其父 initiative(delivery 下取远端头),其余为根目标。 @@ -452,14 +519,54 @@ cmd_resume() { branch=$(git rev-parse --abbrev-ref HEAD) card=$(branch_card "$branch") [ -n "$card" ] && immediate=$(card_immediate_target "$card") + if [ "$mode" = local ]; then + # local 先核验收据与活动任务再刷新:刷新成功按新目标复核;失败只在远端确实不可达时以登记 R0 继续, + # 远端可达而目标缺失、回退/分叉或无法比较时保持 REMOTE_UNVERIFIED,不导入收据。 + git check-ref-format "refs/heads/${TARGET_BRANCH}" >/dev/null || die "invalid target branch" + if fetch_target 2>/dev/null; then + VALIDATE_RECEIPT="$receipt" VALIDATE_RECEIPT_PURPOSE=resume-local bash "${SCRIPT_DIR}/evidence.sh" + else + remote_fresh=0 + local r0 ls_rc=0 + [ -n "$card" ] || die "no registered task card for ${branch}; receipt not imported" + r0=$(card_r0 "$card") + [ -n "$r0" ] || die "task card has no initial baseline; receipt not imported" + git ls-remote --exit-code --heads origin "refs/heads/${TARGET_BRANCH}" >/dev/null 2>&1 || ls_rc=$? + if [ "$ls_rc" -eq 2 ]; then + echo "[verdict] REMOTE_UNVERIFIED — origin is reachable but ${TARGET_BRANCH} is missing; preserve local work, receipt not imported" >&2 + return 6 + fi + case "$(target_history "$r0")" in + rewound) + echo "[verdict] REMOTE_UNVERIFIED — origin/${TARGET_BRANCH} rewound or diverged from the known target; preserve local work, receipt not imported" >&2 + return 6 ;; + uncomparable) + echo "[verdict] REMOTE_UNVERIFIED — advertised origin/${TARGET_BRANCH} cannot be compared with the known target; preserve local work, receipt not imported" >&2 + return 6 ;; + esac + echo "[remote-freshness] unavailable — resuming on registered R0=${r0} for authorized local work only; delivery still requires a fresh target" + fi + fi if [ "$immediate" != "${TARGET_BRANCH}" ]; then - observed=$(resolve_parent_ref "$immediate") || die "immediate target ${immediate} unavailable; receipt not imported" recorded=$(card_field "$card" parent_head_sha) + if [ "$mode" = local ]; then + # local 续作只以登记的父头为比较基准:缺失、非完整 SHA 或本地无此提交时不得回落到本地父分支。 + local sha_len=40 + [ "$(git rev-parse --show-object-format 2>/dev/null)" = sha256 ] && sha_len=64 + if ! [[ "$recorded" =~ ^[0-9a-f]+$ ]] || [ "${#recorded}" -ne "$sha_len" ] \ + || ! git cat-file -e "${recorded}^{commit}" 2>/dev/null; then + echo "[verdict] REMOTE_UNVERIFIED — registered parent_head_sha for ${immediate} is missing, malformed or not available locally; preserve local work, receipt not imported" >&2 + return 6 + fi + fi + observed=$(resolve_parent_ref "$immediate") || die "immediate target ${immediate} unavailable; receipt not imported" if [ -n "$recorded" ] && ! git merge-base --is-ancestor "$recorded" "$observed"; then die "immediate target ${immediate} no longer contains recorded parent head ${recorded}; receipt not imported" fi - else + elif [ "$remote_fresh" -eq 1 ]; then observed=$(git rev-parse "origin/${TARGET_BRANCH}") + else + observed=unavailable fi IMPORT_RECEIPT="$receipt" bash "${SCRIPT_DIR}/evidence.sh" echo "[resumed] work_mode=${mode} source_head=$(git rev-parse HEAD) observed_target=${observed} immediate_target=${immediate}" @@ -538,6 +645,7 @@ card_status() { sed -n 's/^.*;状态:\([^;]*\).*/\1/p' "$1" | head -1; } # 两级分支元数据行:'- 分支类型:;initiative_id:…;root_target:…;immediate_target:…;accepting_requirements:…'。 # 旧卡没有该行:按 standalone、root/immediate 均为目标分支、不接收新需求处理。 card_field() { sed -n "s/^- 分支类型:.*;$2:\([^;]*\).*/\1/p" "$1" 2>/dev/null | head -1; } +card_r0() { sed -n 's/^- 权威仓:.*;R0(initial_base_sha)= \([0-9a-f]\+\)[[:space:]]*$/\1/p' "$1" 2>/dev/null | head -1; } card_branch_kind() { local v; v=$(sed -n 's/^- 分支类型:\([^;]*\).*/\1/p' "$1" 2>/dev/null | head -1); printf '%s\n' "${v:-standalone}"; } card_initiative_id() { card_field "$1" initiative_id; } card_root_target() { local v; v=$(card_field "$1" root_target); printf '%s\n' "${v:-${TARGET_BRANCH}}"; } @@ -595,7 +703,7 @@ except Stop as error: sys.exit("ERROR: " + error.message) sys.exit(0 if any(row["子任务分支"].strip("`").strip() == sys.argv[2] for row in rows) else 1)' "$SCRIPT_DIR" "$1" } -resolve_parent_ref() { # $1=父 initiative 分支 → 打印用于同步/核验的提交(delivery 仅远端;local 取远端与本地包含对方的一侧) +resolve_parent_ref() { # $1=父 initiative 分支 → 打印用于同步/核验的提交(delivery 仅远端;local 取远端与本地包含对方的一侧,远端可达但父分支缺失返回 6,仅远端不可查询时取本地) git check-ref-format "refs/heads/$1" >/dev/null || die "invalid parent branch $1" if [ "$(work_mode)" = delivery ]; then # delivery 只认 origin 上刚取回的父分支头:本地领先/分叉意味着未准入的提交,不得被同步或合入。 @@ -608,17 +716,32 @@ resolve_parent_ref() { # $1=父 initiative 分支 → 打印用于同步/核验 printf '%s\n' "$remote_head" return 0 fi - local remote="" local_ref="" rc=0 - git ls-remote --exit-code --heads origin "refs/heads/$1" >/dev/null 2>&1 || rc=$? + local remote="" local_ref="" rc=0 ls_err + ls_err=$(git ls-remote --exit-code --heads origin "refs/heads/$1" 2>&1 >/dev/null) || rc=$? if [ "$rc" -eq 0 ]; then if ! git fetch -q origin "refs/heads/$1:refs/remotes/origin/$1"; then echo "[verdict] REMOTE_UNVERIFIED — cannot refresh origin/$1; preserve local work and retry" >&2 return 6 fi remote=$(git rev-parse --verify "refs/remotes/origin/$1^{commit}") - elif [ "$rc" -ne 2 ]; then - echo "[verdict] REMOTE_UNVERIFIED — cannot query origin/$1; preserve local work and retry" >&2 + elif [ "$rc" -eq 2 ]; then + # 远端可达而登记的父分支缺失:权威侧已不认该父分支,本地同名分支不得替代。 + echo "[verdict] REMOTE_UNVERIFIED — origin is reachable but $1 is missing; preserve local work and reconcile the parent initiative" >&2 return 6 + elif ! origin_transport_unavailable "$ls_err"; then + # 认证/授权/权限或未知错误不等于远端不可达:权威侧状态未知,不得以本地父分支替代。 + echo "[verdict] REMOTE_UNVERIFIED — cannot verify origin/$1 (non-transport remote error); preserve local work and retry" >&2 + return 6 + else + # 远端传输不可达时只把已登记的本地父分支作为局部续作参考;不声称其为最新或可交付,调用方仍核验 parent_head_sha。 + local_ref=$(git rev-parse -q --verify "refs/heads/$1^{commit}" 2>/dev/null || true) + if [ -z "$local_ref" ]; then + echo "[verdict] REMOTE_UNVERIFIED — cannot query origin/$1 and no local $1 exists; preserve local work and retry" >&2 + return 6 + fi + echo "[remote-freshness] origin/$1 unavailable — using local $1 for authorized local continuation only; not current or delivery-ready" >&2 + printf '%s\n' "$local_ref" + return 0 fi local_ref=$(git rev-parse -q --verify "refs/heads/$1^{commit}" 2>/dev/null || true) if [ -n "$remote" ] && [ -n "$local_ref" ]; then diff --git a/tests/test_check_pins.py b/tests/test_check_pins.py new file mode 100644 index 00000000..009281ae --- /dev/null +++ b/tests/test_check_pins.py @@ -0,0 +1,117 @@ +"""check-pins.sh:治理 gitlink 只告警,运行时依赖仍严格校验。""" +import os +import shutil +import subprocess +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "check-pins.sh" +CONTAINERS = ("modules/backend", "deps/agent-native", "deps/agent-squad", "deps/agent-intent") + + +class CheckPinsTests(unittest.TestCase): + def setUp(self): + self.tmp = Path(tempfile.mkdtemp()) + self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True) + cfg = self.tmp / "gitconfig" + cfg.write_text("") + self.env = dict( + os.environ, + GIT_CONFIG_GLOBAL=str(cfg), + GIT_CONFIG_NOSYSTEM="1", + GIT_AUTHOR_NAME="t", + GIT_AUTHOR_EMAIL="t@example.invalid", + GIT_COMMITTER_NAME="t", + GIT_COMMITTER_EMAIL="t@example.invalid", + ) + self.dev = self.tmp / "dev" + (self.dev / "scripts").mkdir(parents=True) + shutil.copy(SCRIPT, self.dev / "scripts" / "check-pins.sh") + self.git(self.dev, "init", "-q") + + def git(self, cwd, *args): + return subprocess.run( + ["git", *args], cwd=cwd, env=self.env, check=True, capture_output=True, text=True + ).stdout.strip() + + def repo(self, rel, *contents): + """在 Dev 内建嵌套仓,按内容逐次提交,返回各提交 SHA。""" + path = self.dev / rel + path.mkdir(parents=True) + self.git(path, "init", "-q") + shas = [] + for text in contents: + (path / "f").write_text(text) + self.git(path, "add", "f") + self.git(path, "commit", "-qm", text) + shas.append(self.git(path, "rev-parse", "HEAD")) + return shas + + def link(self, repo, rel, sha): + self.git(repo, "update-index", "--add", "--cacheinfo", f"160000,{sha},{rel}") + + def build(self, backend_deps, dev_pins): + """backend_deps/dev_pins:{deps 名: sha};其余容器无 deps。""" + for rel, sha in dev_pins.items(): + self.link(self.dev, rel, sha) + for container in CONTAINERS: + path = self.dev / container + if not path.exists(): + self.repo(container, container) + if container == "modules/backend": + for name, sha in backend_deps.items(): + self.link(path, f"deps/{name}", sha) + self.git(path, "commit", "-qm", "deps") + self.link(self.dev, container, self.git(path, "rev-parse", "HEAD")) + + def run_pins(self): + return subprocess.run( + ["bash", "scripts/check-pins.sh"], cwd=self.dev, env=self.env, capture_output=True, text=True + ) + + def test_governance_mismatch_warns_and_passes(self): + g_old, g_new = self.repo("deps/development-guidelines", "g1", "g2") + m_old, m_new = self.repo("deps/development-manager", "m1", "m2") + self.build( + {"development-guidelines": g_old, "development-manager": m_old}, + {"deps/development-guidelines": g_new, "deps/development-manager": m_new}, + ) + res = self.run_pins() + self.assertEqual(res.returncode, 0, res.stderr) + self.assertIn("warn: modules/backend deps/development-guidelines", res.stderr) + self.assertIn("warn: modules/backend deps/development-manager", res.stderr) + self.assertIn("pins ok", res.stdout) + + def test_runtime_mismatch_fails(self): + old, new = self.repo("deps/error-code-system", "e1", "e2") + self.build({"error-code-system": old}, {"deps/error-code-system": new}) + res = self.run_pins() + self.assertEqual(res.returncode, 1) + self.assertIn("pin: modules/backend deps/error-code-system is", res.stderr) + + def test_runtime_match_passes(self): + (sha,) = self.repo("deps/error-code-system", "e1") + self.build({"error-code-system": sha}, {"deps/error-code-system": sha}) + res = self.run_pins() + self.assertEqual(res.returncode, 0, res.stderr) + self.assertIn("ok modules/backend error-code-system", res.stdout) + + def test_runtime_missing_object_fails(self): + (sha,) = self.repo("deps/error-code-system", "e1") + self.build({"error-code-system": "1" * 40}, {"deps/error-code-system": sha}) + res = self.run_pins() + self.assertEqual(res.returncode, 1) + self.assertIn("is missing locally", res.stderr) + + def test_runtime_without_dev_pin_fails(self): + (sha,) = self.repo("deps/logging-system", "l1") + self.build({"logging-system": sha}, {}) + res = self.run_pins() + self.assertEqual(res.returncode, 1) + self.assertIn("has no Dev pin", res.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_evidence.py b/tests/test_evidence.py index 01fc018b..fac6bae3 100644 --- a/tests/test_evidence.py +++ b/tests/test_evidence.py @@ -3,6 +3,7 @@ import hashlib import json import os from pathlib import Path +import re import subprocess import tempfile import unittest @@ -477,6 +478,215 @@ class EvidenceTests(unittest.TestCase): self.assertNotEqual(self.import_receipt().returncode, 0) self.assertEqual(self.card.read_text(), "# Test task\n") + def local_task(self, status="进行中"): + """Register a clean linked task worktree whose precheck prints the real local task context.""" + wt = self.base.resolve() / "task-wt" + self.git("worktree", "add", "-qb", "feature/local", str(wt), self.head) + (wt / ".task").mkdir() + (wt / ".task/feature-local.md").write_text( + f"# Local task\n任务主记录:fixture;状态:{status};owner:Evidence Test\n" + f"- 权威仓:fixture;目标分支:main;R0(initial_base_sha)= {self.head}\n" + f"- 工作区:{wt}(worktree);分支:feature/local\n") + (wt / "Makefile").write_text( + f"VERDICT ?= LOCAL_READY\nprecheck:\n\t@python3 {SCRIPTS / 'evidence.py'} --local-context\n" + "\t@echo '[verdict] $(VERDICT) — fixture'\n") + self.git("add", ".task", "Makefile", cwd=wt) + self.git("commit", "-qm", "Register local task", cwd=wt) + return wt + + def validate(self, wt, purpose="resume-local", **values): + return self.run_evidence(cwd=wt, VALIDATE_RECEIPT=self.receipt, + VALIDATE_RECEIPT_PURPOSE=purpose, **values) + + def advance_target(self): + head = self.git("commit-tree", "-p", self.head, "-m", "Advance target", self.head + "^{tree}") + self.git("update-ref", "refs/remotes/origin/main", head) + + def test_local_continuation_accepts_registered_task_with_known_lag(self): + wt = self.local_task() + self.capture("make precheck", cwd=wt) + result = self.validate(wt) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("local continuation validated", result.stdout) + self.assertIn("src=SELF", result.stdout) + self.advance_target() + lagging = self.validate(wt) + self.assertEqual(lagging.returncode, 0, lagging.stderr) + + def test_local_continuation_accepts_registered_task_without_remote_tracking_ref(self): + wt = self.local_task() + self.capture("make precheck", cwd=wt) + task_head = self.git("rev-parse", "HEAD", cwd=wt) + self.git("update-ref", "-d", "refs/remotes/origin/main") + # The R0 commit recorded in the task card must remain reachable locally + # even after the remote-tracking ref is removed. + self.git("cat-file", "-e", self.head) + result = self.validate(wt) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("local continuation validated", result.stdout) + self.assertEqual(self.git("rev-parse", "HEAD", cwd=wt), task_head) + + def test_local_continuation_accepts_when_target_ref_reappears(self): + """A resume-local receipt captured with no remote-tracking target ref + validates after a forward target ref reappears, provided R0 stays locally + reachable and the reappeared target is a forward descendant of R0. + + The receipt records target_head=None; the rewind check is skipped because + either side is missing, and the reappeared ref (a forward descendant of R0) + must satisfy the ancestry guard that the registered task is still current.""" + wt = self.local_task() + head_before = self.git("rev-parse", "HEAD", cwd=wt) + # Drop refs/remotes/origin/main BEFORE capture so the receipt records + # target_head=None. R0 (the recorded initial baseline) must remain + # reachable as a local object. + self.git("update-ref", "-d", "refs/remotes/origin/main") + self.git("cat-file", "-e", self.head) + self.capture("make precheck", cwd=wt) + captured = json.loads(self.receipt.read_text()) + self.assertIsNone(captured["after"]["target_head"]) + # Recreate refs/remotes/origin/main as a forward descendant of R0. + forward = self.git("commit-tree", "-p", self.head, + "-m", "Forward target ref reappeared", self.head + "^{tree}") + self.git("update-ref", "refs/remotes/origin/main", forward) + # Validation must succeed: receipt has no target_head (rewind check is + # skipped) and the reappeared target descends from R0 (ancestry guard + # accepts a forward target ref). + result = self.validate(wt) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("local continuation validated", result.stdout) + self.assertEqual(self.git("rev-parse", "HEAD", cwd=wt), head_before) + + def test_local_precheck_captures_without_remote_tracking_ref(self): + wt = self.local_task() + head_before = self.git("rev-parse", "HEAD", cwd=wt) + # Remove the remote-tracking ref BEFORE capturing, so the precheck fixture + # itself must tolerate the missing refs/remotes/origin/main. The existing + # --local-context rejects this and the capture fails; recording that + # expected red result here. + self.git("update-ref", "-d", "refs/remotes/origin/main") + # The R0 commit recorded on the task card must remain reachable locally + # even after the remote-tracking ref is removed. + self.git("cat-file", "-e", self.head) + result = self.run_evidence("make precheck", cwd=wt, RECEIPT=self.receipt) + self.assertEqual(result.returncode, 0, result.stderr + result.stdout) + self.assertTrue(self.receipt.exists(), "capture must write a receipt") + data = json.loads(self.receipt.read_text()) + self.assertEqual(data["exit_code"], 0, data["output"]) + self.assertIn("[verdict] LOCAL_READY", data["output"]) + states = re.findall(r"^\[task-state\] registered=(.+) initial_base=([0-9a-f]+)$", + data["output"], re.M) + self.assertEqual(len(states), 1, data["output"]) + registered, initial_base = states[0] + self.assertEqual(Path(registered).resolve(), wt.resolve()) + self.assertEqual(initial_base, self.head) + self.assertEqual(self.git("rev-parse", "HEAD", cwd=wt), head_before) + + def test_local_ready_never_authorizes_start_delivery_or_promoted_source(self): + wt = self.local_task() + self.capture("make precheck", cwd=wt) + for purpose in ("start", "resume-delivery"): + with self.subTest(purpose=purpose): + result = self.validate(wt, purpose) + self.assertNotEqual(result.returncode, 0) + self.assertIn("Precheck receipt is not READY", result.stderr) + for source in ("GATE", "REPLAY"): + with self.subTest(source=source): + result = self.validate(wt, SRC=source) + self.assertNotEqual(result.returncode, 0) + self.assertIn("Local evidence is SELF", result.stderr) + + def test_delivery_resume_still_requires_ready_and_current_target(self): + wt = self.local_task() + self.capture("make precheck", cwd=wt, VERDICT="READY") + local = self.validate(wt) + self.assertNotEqual(local.returncode, 0) + self.assertIn("Precheck receipt is not LOCAL_READY", local.stderr) + current = self.validate(wt, "resume-delivery") + self.assertEqual(current.returncode, 0, current.stderr) + self.assertIn("precheck validated", current.stdout) + self.advance_target() + stale = self.validate(wt, "resume-delivery") + self.assertNotEqual(stale.returncode, 0) + self.assertIn("authoritative target has changed", stale.stderr) + + def test_local_continuation_rejects_closed_task(self): + wt = self.local_task(status="已完成(合入前闭合)") + self.capture("make precheck", cwd=wt) + result = self.validate(wt) + self.assertNotEqual(result.returncode, 0) + self.assertIn("active registered task", result.stderr) + + def test_local_continuation_rejects_dirty_workspace_or_changed_head(self): + wt = self.local_task() + self.capture("make precheck", cwd=wt) + (wt / "extra").write_text("unsaved\n") + dirty = self.validate(wt) + self.assertNotEqual(dirty.returncode, 0) + self.assertIn("Task workspace is not ready", dirty.stderr) + (wt / "extra").unlink() + self.git("commit", "--allow-empty", "-qm", "Advance task", cwd=wt) + moved = self.validate(wt) + self.assertNotEqual(moved.returncode, 0) + self.assertIn("source HEAD has changed", moved.stderr) + + def test_local_continuation_rejects_mismatched_identity_baseline_workspace_or_scope(self): + wt = self.local_task() + original = self.capture("make precheck", cwd=wt) + identity = "different task identity or initial baseline" + cases = ( + ("task identity", identity, + lambda d: d.update(output=d["output"].replace(str(wt), str(self.base / "other")))), + ("initial baseline", identity, + lambda d: d.update(output=d["output"].replace(self.head, "0" * 40))), + ("missing identity", identity, + lambda d: d.update(output="[verdict] LOCAL_READY — fixture\n")), + ("dirty receipt", "workspace was not clean", + lambda d: d["after"].update(status="?? extra\n", untracked={"extra": None})), + ("tracked diff", "workspace was not clean", + lambda d: d["before"].update(tracked_diff_sha256="1" * 64)), + ("target scope", "authorization scope", + lambda d: d.update(immediate_target="feature/other")), + ("missing scope", "authorization scope", + lambda d: [d.pop(key) for key in ("root_target", "immediate_target")]), + ) + for name, message, change in cases: + with self.subTest(case=name): + data = json.loads(json.dumps(original)) + change(data) + data["output_sha256"] = hashlib.sha256(data["output"].encode()).hexdigest() + self.resign(data) + result = self.validate(wt) + self.assertNotEqual(result.returncode, 0) + self.assertIn(message, result.stderr) + + def test_local_continuation_rejects_receipt_target_branch_mismatch(self): + """Resume-local must refuse a receipt whose before.target_branch or + after.target_branch points at a different branch than the registered + task target. Re-sign the fixture receipt with the existing helper so + the digest still validates, and assert the existing production + rejection that flags the receipt as belonging to a different task + branch (or, when the after snapshot already disagrees, the + authoritative-target-changed guard that fires first).""" + wt = self.local_task() + original = self.capture("make precheck", cwd=wt) + cases = ( + ("before.target_branch", + "Precheck belongs to a different task branch", + lambda d: d["before"].update(target_branch="feature/other")), + ("after.target_branch", + "Precheck authoritative target has changed", + lambda d: d["after"].update(target_branch="feature/other")), + ) + for name, message, change in cases: + with self.subTest(case=name): + data = json.loads(json.dumps(original)) + change(data) + data["output_sha256"] = hashlib.sha256(data["output"].encode()).hexdigest() + self.resign(data) + result = self.validate(wt) + self.assertNotEqual(result.returncode, 0) + self.assertIn(message, result.stderr) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_facts_verify.py b/tests/test_facts_verify.py new file mode 100644 index 00000000..5b3d9d2a --- /dev/null +++ b/tests/test_facts_verify.py @@ -0,0 +1,399 @@ +"""外部事实重验契约(scripts/facts-verify.sh + policy/facts.md 的 F2/F4 验证命令)。 + +全部离线:合成 HOME/IMC 凭据、sitecustomize 桩拦截 urllib 处理器网络边界(保留真实重定向判定) +与 socket/ssl(真实连接一律拒绝)、 +PATH 桩记录 curl/wget argv、临时 Git 仓库与临时登记表;不读取真实 ~/.secrets,不连接 Gitea 或旧端点。 +""" +import datetime +import json +import os +import re +import shutil +import subprocess +import tempfile +import unittest +from pathlib import Path + +SOURCE = Path(__file__).resolve().parents[1] +REGISTRY = SOURCE / "policy" / "facts.md" +HOME_TOKEN = "h0me" + "1" * 36 +IMC_TOKEN = "1mc0" + "2" * 36 +ENV_DECOY = "dec0" + "3" * 36 +OLD_DATE = "2026-01-01" +SECRETS = [HOME_TOKEN, IMC_TOKEN] + +STUB = r''' +import http.client, io, json, os, socket, ssl, subprocess, urllib.request, urllib.response + + +def _log(kind, **data): + with open(os.environ["FACTS_STUB_LOG"], "a", encoding="utf-8") as fh: + fh.write(json.dumps({"kind": kind, **data}) + "\n") + + +def _deny(*args, **kwargs): + raise RuntimeError("live network disabled in facts-verify tests") + + +socket.socket.connect = _deny +socket.socket.connect_ex = _deny + +_popen_init = subprocess.Popen.__init__ + + +def _popen(self, args, *rest, **kwargs): + argv = [args] if isinstance(args, (str, bytes, os.PathLike)) else list(args) + _log("exec", argv=[os.fsdecode(a) if isinstance(a, (bytes, os.PathLike)) else str(a) for a in argv]) + _popen_init(self, args, *rest, **kwargs) + + +subprocess.Popen.__init__ = _popen + + +def _response(url, code, body=b"", location=None): + headers = http.client.HTTPMessage() + if location: + headers["Location"] = location + resp = urllib.response.addinfourl(io.BytesIO(body), headers, url, code) + resp.msg = "stub" + return resp + + +REDIRECT_TARGET = "redirect-target.invalid" + + +# Intercept only the handlers' network boundary so the opener's real redirect and +# error processing still decides whether a follow-up request is made. +def _open(self, request): + url = request.full_url + auth = request.get_header("Authorization") + _log("http", url=url, authorization=auth) + if REDIRECT_TARGET in url: + return _response(url, 200, b'{"login": "redirect-target"}') + mode = os.environ.get("FAKE_HTTP", "200") + if mode == "200": + return _response(url, 200, b'{"login": "fixture"}') + if mode == "leak": + raise ValueError("Invalid header value " + repr(auth)) + code = int(mode) + location = "https://" + REDIRECT_TARGET + "/collect" if 300 <= code < 400 else None + return _response(url, code, location=location) + + +urllib.request.HTTPSHandler.https_open = _open +urllib.request.HTTPHandler.http_open = _open + + +class _Conn: + def close(self): + pass + + def version(self): + return "TLSv1.3" + + def __enter__(self): + return self + + def __exit__(self, *exc): + return False + + +def _create_connection(address, *args, **kwargs): + _log("connect", host=address[0], port=int(address[1])) + mode = os.environ.get("FAKE_NET", "") + if mode == "dns": + raise socket.gaierror(socket.EAI_NONAME, "Name or service not known") + if mode == "timeout": + raise TimeoutError("timed out") + if mode == "refused": + raise ConnectionRefusedError(111, "Connection refused") + return _Conn() + + +socket.create_connection = _create_connection + + +def _wrap_socket(self, sock, *args, **kwargs): + mode = os.environ.get("FAKE_NET", "") + if mode == "tls": + raise ssl.SSLError(1, "[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure") + if mode == "reset": + raise ConnectionResetError(104, "Connection reset by peer") + if mode == "tls-timeout": + raise TimeoutError("handshake timed out") + return _Conn() + + +ssl.SSLContext.wrap_socket = _wrap_socket +''' + +SHIM = """#!/bin/sh +for a in "$0" "$@"; do printf '%s\\n' "$a"; done >> "$FACTS_ARGV_LOG" +exit "${FAKE_CURL_EXIT:-0}" +""" + +# Exit codes the retired-endpoint curl check would see for each simulated condition. +CURL_EXIT = {"dns": "6", "timeout": "28", "refused": "7", "reset": "35", "tls-timeout": "28", + "tls": "35", "ok": "0"} + + +def registry_row(fid): + for line in REGISTRY.read_text(encoding="utf-8").splitlines(): + if re.match(r"^\|\s*" + fid + r"\s*\|", line): + return line + raise AssertionError(fid + " missing from policy/facts.md") + + +def registry_command(fid): + cells = [c.strip() for c in registry_row(fid).strip().strip("|").replace("\\|", "\x00").split("|")] + return cells[2].strip("`").replace("\x00", "|") + + +def with_date(line, date): + return re.sub(r"\|\s*\d{4}-\d{2}-\d{2}\s*\|(\s*\d+\s*\|)\s*$", "| " + date + r" |\1", line) + + +class FactsVerifyTests(unittest.TestCase): + def setUp(self): + tmp = tempfile.TemporaryDirectory(prefix="facts-verify-") + self.addCleanup(tmp.cleanup) + self.base = Path(tmp.name) + self.repo = self.base / "repo" + (self.repo / "scripts").mkdir(parents=True) + for name in ("facts-verify.sh", "task_triage.py"): + shutil.copy2(SOURCE / "scripts" / name, self.repo / "scripts" / name) + self.git("init", "-q", "-b", "main") + self.git("add", "scripts") + self.git("-c", "user.name=Facts Test", "-c", "user.email=facts@example.invalid", + "commit", "-qm", "Initial") + self.home = self.base / "home" + (self.home / ".secrets").mkdir(parents=True) + self.write_secrets(f"HOME:\n- URL_PREFIX=https://home.invalid\n- ACCESS_TOKEN={HOME_TOKEN}\n" + f"IMC:\n- URL_PREFIX=https://imc.invalid\n- ACCESS_TOKEN={IMC_TOKEN}\n") + self.stub_dir = self.base / "stub" + self.stub_dir.mkdir() + (self.stub_dir / "sitecustomize.py").write_text(STUB, encoding="utf-8") + self.shim_dir = self.base / "bin" + self.shim_dir.mkdir() + for name in ("curl", "wget"): + shim = self.shim_dir / name + shim.write_text(SHIM, encoding="utf-8") + shim.chmod(0o755) + self.stub_log = self.base / "stub.log" + self.argv_log = self.base / "argv.log" + self.facts = self.base / "facts.md" + self.today = datetime.date.today().isoformat() + + def git(self, *args): + subprocess.run(["git", *args], cwd=self.repo, check=True, capture_output=True) + + def write_secrets(self, text): + (self.home / ".secrets" / "gitea").write_text(text, encoding="utf-8") + + def write_registry(self, *fids): + rows = [with_date(registry_row(fid), OLD_DATE) for fid in fids] + rows.append(f"| F8 | 对照:总能通过 | `true` | {OLD_DATE} | 30 |") + rows.append(f"| F9 | 对照:总是失败 | `false` | {OLD_DATE} | 30 |") + self.facts.write_text("| ID | 事实 | 验证命令 | last_verified | TTL(天) |\n|---|---|---|---|---|\n" + + "\n".join(rows) + "\n", encoding="utf-8") + + def env(self, net="", http="200"): + env = {k: v for k, v in os.environ.items() + if k not in ("CMD", "SRC", "RECEIPT", "IMPORT_RECEIPT", "VALIDATE_RECEIPT", "TAIL")} + curl_exit = CURL_EXIT.get(net, "0") if net else ("0" if http == "200" else "22") + env.update({ + "HOME": str(self.home), "GITEA_TOKEN": ENV_DECOY, "FACTS": str(self.facts), + "PATH": str(self.shim_dir) + os.pathsep + env.get("PATH", ""), + "PYTHONPATH": str(self.stub_dir), "FACTS_STUB_LOG": str(self.stub_log), + "FACTS_ARGV_LOG": str(self.argv_log), "FAKE_NET": net, "FAKE_HTTP": http, + "FAKE_CURL_EXIT": curl_exit, "AGENT_EVIDENCE_DIR": str(self.base / "evidence"), + "GIT_CONFIG_NOSYSTEM": "1", + }) + return env + + def run_script(self, *args, net="", http="200"): + return subprocess.run(["bash", "scripts/facts-verify.sh", *args], cwd=self.repo, + env=self.env(net, http), capture_output=True, text=True, timeout=120) + + def run_evidence(self, command, receipt, net="", http="200"): + env = self.env(net, http) + env.update({"CMD": command, "RECEIPT": str(receipt)}) + return subprocess.run(["python3", str(SOURCE / "scripts" / "evidence.py")], cwd=self.repo, + env=env, capture_output=True, text=True, timeout=120) + + def stub_events(self, kind): + if not self.stub_log.exists(): + return [] + events = [json.loads(line) for line in self.stub_log.read_text(encoding="utf-8").splitlines()] + return [e for e in events if e["kind"] == kind] + + def dates(self): + found = {} + for line in self.facts.read_text(encoding="utf-8").splitlines(): + match = re.match(r"^\|\s*(F\d+)\s*\|.*\|\s*(\d{4}-\d{2}-\d{2})\s*\|\s*\d+\s*\|\s*$", line) + if match: + found[match.group(1)] = match.group(2) + return found + + def assert_no_secrets(self, *texts): + exec_argv = "\n".join("\0".join(e["argv"]) for e in self.stub_events("exec")) + shim_argv = self.argv_log.read_text(encoding="utf-8") if self.argv_log.exists() else "" + for label, text in [("child argv", exec_argv), ("curl/wget argv", shim_argv), + *((f"output#{i}", t) for i, t in enumerate(texts))]: + for secret in SECRETS: + self.assertNotIn(secret, text, f"credential leaked into {label}") + + def assert_status(self, output, fid, status): + self.assertRegex(output, r"(?m)^\[" + fid + r"\] " + status + r"\b") + + # --- F2:凭据只在进程内使用 ------------------------------------------------- + + def test_f2_pass_sends_home_token_in_process_and_leaks_nowhere(self): + self.write_registry("F2") + receipt = self.base / "receipt.json" + result = self.run_evidence("bash scripts/facts-verify.sh --update", receipt) + self.assertEqual(result.returncode, 1, result.stdout + result.stderr) # F9 control fails + self.assert_status(result.stdout, "F2", "PASS") + requests = self.stub_events("http") + self.assertEqual(len(requests), 1, requests) + self.assertEqual(requests[0]["url"], "https://git-home.u1680832.nyat.app:30319/api/v1/user") + self.assertEqual(requests[0]["authorization"], "token " + HOME_TOKEN) + self.assertEqual(self.dates(), {"F2": self.today, "F8": self.today, "F9": OLD_DATE}) + self.assertTrue(receipt.exists(), result.stderr) + self.assert_no_secrets(result.stdout, result.stderr, receipt.read_text(encoding="utf-8"), + self.facts.read_text(encoding="utf-8")) + + def test_f2_missing_or_malformed_credentials_fail_without_echo(self): + self.write_registry("F2") + cases = { + "no HOME token": f"HOME:\n- URL_PREFIX=https://home.invalid\nIMC:\n- ACCESS_TOKEN={IMC_TOKEN}\n", + "malformed HOME token": f"HOME:\n- ACCESS_TOKEN={HOME_TOKEN} {IMC_TOKEN}\n" + f"IMC:\n- ACCESS_TOKEN={IMC_TOKEN}\n", + "missing file": None, + } + for label, text in cases.items(): + with self.subTest(label): + self.stub_log.unlink(missing_ok=True) + self.argv_log.unlink(missing_ok=True) + secrets = self.home / ".secrets" / "gitea" + secrets.unlink(missing_ok=True) + if text is not None: + self.write_secrets(text) + self.write_registry("F2") + receipt = self.base / f"receipt-{len(label)}.json" + result = self.run_evidence("bash scripts/facts-verify.sh --update", receipt) + self.assert_status(result.stdout, "F2", "FAIL") + self.assertEqual(self.stub_events("http"), [], "no request without a valid HOME token") + self.assertEqual(self.dates()["F2"], OLD_DATE) + direct = self.run_script("api-token", "https://git-home.u1680832.nyat.app:30319/api/v1/user") + self.assertNotEqual(direct.returncode, 0) + self.assertIn("ACCESS_TOKEN", direct.stderr) + self.assert_no_secrets(result.stdout, result.stderr, direct.stdout, direct.stderr, + receipt.read_text(encoding="utf-8")) + + def test_f2_rejected_or_failing_request_does_not_refresh_or_leak(self): + for http in ("401", "leak"): + with self.subTest(http): + self.stub_log.unlink(missing_ok=True) + self.write_registry("F2") + result = self.run_script("--update", http=http) + self.assert_status(result.stdout, "F2", "FAIL") + self.assertEqual(self.dates(), {"F2": OLD_DATE, "F8": self.today, "F9": OLD_DATE}) + direct = self.run_script("api-token", "https://git-home.u1680832.nyat.app:30319/api/v1/user", + http=http) + self.assertNotEqual(direct.returncode, 0) + self.assertNotIn("Authorization", direct.stderr) + self.assert_no_secrets(result.stdout, result.stderr, direct.stdout, direct.stderr) + + def test_f2_rejects_unregistered_destination_before_reading_credentials(self): + bad_urls = [ + "http://git-home.u1680832.nyat.app:30319/api/v1/user", + "https://evil.invalid/api/v1/user", + "https://git-home.u1680832.nyat.app:30320/api/v1/user", + "https://git-home.u1680832.nyat.app/api/v1/user", + "https://user:pw@git-home.u1680832.nyat.app:30319/api/v1/user", + "https://git-home.u1680832.nyat.app:30319@evil.invalid/api/v1/user", + "https://git-home.u1680832.nyat.app:30319/api/v1/user#frag", + "https://git-home.u1680832.nyat.app:30319/api/v1/user?next=https://evil.invalid", + "https://git-home.u1680832.nyat.app:30319/api/v1/users", + "https://git-home.u1680832.nyat.app:30319/api/v1/user/../admin", + "https://git-home.u1680832.nyat.app:30319/api/v1/user ", + ] + for secrets_present in (True, False): + if not secrets_present: + (self.home / ".secrets" / "gitea").unlink() + for url in bad_urls: + with self.subTest(url=url, secrets_present=secrets_present): + self.stub_log.unlink(missing_ok=True) + direct = self.run_script("api-token", url) + self.assertEqual(direct.returncode, 6, direct.stderr) + self.assertEqual(self.stub_events("http"), [], "no request to an unregistered destination") + self.assertNotIn(url.strip(), direct.stdout + direct.stderr) + self.assertNotIn("evil.invalid", direct.stdout + direct.stderr) + self.assertNotIn("ACCESS_TOKEN", direct.stderr, "destination is checked before credentials") + self.assert_no_secrets(direct.stdout, direct.stderr) + + def test_f2_refuses_redirect_without_forwarding_authorization(self): + for code in ("301", "302", "303", "307", "308"): + with self.subTest(code): + self.stub_log.unlink(missing_ok=True) + direct = self.run_script("api-token", "https://git-home.u1680832.nyat.app:30319/api/v1/user", + http=code) + self.assertEqual(direct.returncode, 5, direct.stderr) + requests = self.stub_events("http") + self.assertEqual([r["url"] for r in requests], + ["https://git-home.u1680832.nyat.app:30319/api/v1/user"], "no follow-up request") + self.assertNotIn("redirect-target", direct.stdout + direct.stderr) + self.assert_no_secrets(direct.stdout, direct.stderr) + + self.stub_log.unlink(missing_ok=True) + self.write_registry("F2") + result = self.run_script("--update", http=code) + self.assert_status(result.stdout, "F2", "FAIL") + self.assertEqual(self.dates()["F2"], OLD_DATE) + self.assertEqual(len(self.stub_events("http")), 1) + self.assert_no_secrets(result.stdout, result.stderr) + + # --- F4:只有 TLS 层握手失败才支持该事实 ----------------------------------- + + def test_f4_only_tls_handshake_failure_passes_and_refreshes(self): + expected = {"tls": "PASS", "dns": "FAIL", "timeout": "FAIL", "refused": "FAIL", + "reset": "FAIL", "tls-timeout": "FAIL", "ok": "FAIL"} + for net, status in expected.items(): + with self.subTest(net): + self.stub_log.unlink(missing_ok=True) + self.write_registry("F4") + result = self.run_script("--update", net=net) + self.assert_status(result.stdout, "F4", status) + self.assertEqual(self.dates(), {"F4": self.today if status == "PASS" else OLD_DATE, + "F8": self.today, "F9": OLD_DATE}) + connects = self.stub_events("connect") + self.assertEqual([(c["host"], c["port"]) for c in connects], + [("git-panel.u1680832.nyat.app", 30319)]) + + def test_f4_helper_reports_each_condition_distinctly(self): + codes = {} + for net in ("tls", "dns", "timeout", "refused", "reset", "tls-timeout", "ok"): + result = self.run_script("tls-handshake-fails", "git-panel.u1680832.nyat.app", "30319", net=net) + codes[net] = result.returncode + self.assertTrue(result.stderr.strip(), net + " must explain its verdict") + self.assertEqual(codes["tls"], 0) + self.assertEqual(len({codes["dns"], codes["timeout"], codes["refused"], codes["ok"]}), 4, codes) + self.assertNotIn(0, [code for net, code in codes.items() if net != "tls"]) + + def test_registry_separates_retired_endpoint_policy_from_tls_observation(self): + text = REGISTRY.read_text(encoding="utf-8") + policy_lines = [line for line in text.splitlines() + if "git-panel" in line and not line.startswith("|")] + self.assertTrue(any("不得使用" in line for line in policy_lines), + "retired-endpoint prohibition must stand outside the F4 verification row") + self.assertNotIn("不得使用", registry_row("F4")) + for fid in ("F2", "F4"): + command = registry_command(fid) + self.assertNotIn("curl", command) + self.assertNotIn("Authorization", command) + self.assertNotIn(".secrets", command) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_task_recovery.py b/tests/test_task_recovery.py index f37dc9c5..ab2966db 100644 --- a/tests/test_task_recovery.py +++ b/tests/test_task_recovery.py @@ -1,6 +1,8 @@ """Local recovery is task-scoped and cannot grant delivery readiness.""" import json import os +import shlex +import subprocess import sys import unittest @@ -43,6 +45,24 @@ class TaskRecoveryTests(unittest.TestCase): f.git(self.wt, "add", str(self.card.relative_to(self.wt))) f.git(self.wt, "commit", "-m", "Update fixture registration") + def local_authorization_scope(self): + """Capture the explicit registered in-progress task used as local-mode authorization. + + The card carries the task scope (worktree path, branch, target branch, and R0 + initial baseline) that authorize LOCAL_READY under a registered task, regardless + of whether the authoritative origin is currently reachable. This small synthetic + fixture exposes that scope as a single value so offline-local tests can name it.""" + f = self.fixture + branch = f.git(self.wt, "rev-parse", "--abbrev-ref", "HEAD").stdout.strip() + r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + return { + "wt": self.wt, + "card": self.card, + "branch": branch, + "target_branch": "main", + "r0": r0, + } + def test_local_precheck_allows_known_lag_without_changing_head(self): f = self.fixture head = f.git(self.wt, "rev-parse", "HEAD").stdout @@ -88,17 +108,148 @@ class TaskRecoveryTests(unittest.TestCase): self.save_card_change() self.assertNotEqual(self.precheck().returncode, 0) - def test_local_precheck_rejects_dirty_state_and_unknown_remote(self): + def test_local_precheck_distinguishes_dirty_local_ready_and_remote_unverified(self): + """A registered in-progress task with explicit authorization and scope distinguishes + SHARED_WORKSPACE_BUSY (dirty), LOCAL_READY (offline, registered, HEAD unchanged), + and REMOTE_UNVERIFIED (delivery, origin unavailable).""" f = self.fixture + scope = self.local_authorization_scope() + self.assertEqual(scope["branch"], "feature/test") + self.assertTrue(scope["card"].is_file()) + self.assertEqual(scope["target_branch"], "main") + + # (1) Dirty workspace still yields SHARED_WORKSPACE_BUSY under the registered scope + # even when the authoritative origin is reachable. Clean before (2). (self.wt / "tracked").write_text("preserve current edit\n") - result = self.precheck() - self.assertNotEqual(result.returncode, 0) - self.assertIn("SHARED_WORKSPACE_BUSY", result.stdout) + dirty = self.precheck() + self.assertNotEqual(dirty.returncode, 0, dirty.stdout) + self.assertIn("[verdict] SHARED_WORKSPACE_BUSY", dirty.stdout) + self.assertNotIn("[verdict] LOCAL_READY", dirty.stdout) f.git(self.wt, "commit", "-am", "Save owner edit") + + head_before = f.git(self.wt, "rev-parse", "HEAD").stdout 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) + + # (2) Same unavailable origin; local mode against a registered in-progress task + # returns LOCAL_READY and leaves HEAD untouched. + local = self.precheck(mode="local") + self.assertEqual(local.returncode, 0, local.stdout) + self.assertIn("[verdict] LOCAL_READY", local.stdout) + self.assertNotIn("[verdict] REMOTE_UNVERIFIED", local.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before) + + # (3) Same unavailable origin; delivery mode continues to refuse with + # REMOTE_UNVERIFIED because no read has refreshed the authoritative target. + delivery = self.precheck(mode="delivery") + self.assertNotEqual(delivery.returncode, 0, delivery.stdout) + self.assertIn("[verdict] REMOTE_UNVERIFIED", delivery.stdout) + self.assertNotIn("[verdict] LOCAL_READY", delivery.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before) + + def test_local_precheck_rejects_closed_task(self): + """A closed registered task (已完成(合入前闭合)) must not advertise LOCAL_READY. + + The current local precheck emits LOCAL_READY for any registered in-progress + card text and never inspects the card status, so this test records the + expected red result: the verdict is wrong and the exit code is wrong.""" + f = self.fixture + self.card.write_text(self.card.read_text().replace("状态:进行中", + "状态:已完成(合入前闭合)")) + self.save_card_change() + self.assertIn("已完成(合入前闭合)", self.card.read_text()) + head_before = f.git(self.wt, "rev-parse", "HEAD").stdout + local = self.precheck() + self.assertNotEqual(local.returncode, 0, local.stdout) + self.assertNotIn("[verdict] LOCAL_READY", local.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before) + + def test_local_precheck_rejects_reachable_target_rewind(self): + """Origin is reachable but its target was rewound after the task cached a + newer descendant. Local precheck must not emit LOCAL_READY under that + divergence — the cached refs/remotes/origin/main is no longer trustworthy. + + The current implementation lets fetch_target() surface a non-FFREMOTE_UNVERIFIED + line, then unconditionally prints LOCAL_READY in local mode. The exit code + is wrong and the verdict is wrong; this test records both expected failures.""" + f = self.fixture + r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + # (1) Advance the remote and let local precheck refresh the cached origin/main. + f.advance_remote() + refresh = self.precheck() + self.assertEqual(refresh.returncode, 0, refresh.stdout) + forward = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + self.assertNotEqual(forward, r0) + # (2) Rewind the bare remote target back to R0; the worktree cache still + # points at the forward descendant because we have not fetched since. + f.git(f.remote, "update-ref", "refs/heads/main", r0, forward) + self.assertEqual(f.git(f.remote, "rev-parse", "refs/heads/main").stdout.strip(), r0) + self.assertEqual(f.git(self.wt, "rev-parse", "origin/main").stdout.strip(), forward) + head_before = f.git(self.wt, "rev-parse", "HEAD").stdout + # (3) Local precheck sees a reachable origin whose target is older than the + # cache. fetch_target() refuses the non-fast-forward update; the precheck + # must still refuse LOCAL_READY. + local = self.precheck() + self.assertNotEqual(local.returncode, 0, local.stdout) + self.assertNotIn("[verdict] LOCAL_READY", local.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before) + # After the failed fetch the cache still points at the forward descendant + # so a follow-up run can repeat the same check. + self.assertEqual(f.git(self.wt, "rev-parse", "origin/main").stdout.strip(), forward) + + def test_local_precheck_rejects_reachable_target_divergence(self): + """Origin is reachable but its target was replaced by a divergent commit + that shares R0 with the cached forward target without descending from it. + Local precheck must refuse LOCAL_READY and surface the divergence so the + owner can reconcile the target history instead of trusting the cache. + + Both the cached forward target and the divergent commit are locally + available so target_rewound() can compare them via merge-base.""" + f = self.fixture + r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + # (1) Advance the remote and let local precheck refresh the cached origin/main. + f.advance_remote() + refresh = self.precheck() + self.assertEqual(refresh.returncode, 0, refresh.stdout) + forward = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + self.assertNotEqual(forward, r0) + # (2) Build a divergent commit on top of R0 in the main checkout; it shares + # R0 with the cached forward target but does not descend from it. + 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() + # Both targets must share R0 (otherwise they would just be unrelated refs). + self.assertEqual(f.git(f.repo, "merge-base", forward, divergent).stdout.strip(), r0) + # The cached forward target must NOT be an ancestor of the divergent commit. + self.assertEqual(f.git(f.repo, "merge-base", "--is-ancestor", forward, + divergent, check=False).returncode, 1) + # Both commits must be locally available so target_rewound() can inspect them. + self.assertEqual(f.git(self.wt, "cat-file", "-e", forward + "^{commit}", + check=False).returncode, 0) + self.assertEqual(f.git(self.wt, "cat-file", "-e", divergent + "^{commit}", + check=False).returncode, 0) + # (3) Push the divergent commit so the bare remote's object database sees it, + # then replace refs/heads/main on the bare remote with the divergent commit. + f.git(f.repo, "push", "origin", "HEAD:refs/heads/divergent") + f.git(f.remote, "update-ref", "refs/heads/main", divergent, forward) + self.assertEqual(f.git(f.remote, "rev-parse", "refs/heads/main").stdout.strip(), divergent) + # The worktree cache still points at the forward descendant because we have + # not fetched since. + self.assertEqual(f.git(self.wt, "rev-parse", "origin/main").stdout.strip(), forward) + head_before = f.git(self.wt, "rev-parse", "HEAD").stdout + # (4) Local precheck: fetch_target() refuses the non-fast-forward update to + # the divergent commit, target_rewound() then surfaces the divergence, and + # the precheck must not advertise LOCAL_READY. + local = self.precheck() + self.assertNotEqual(local.returncode, 0, local.stdout) + self.assertNotIn("[verdict] LOCAL_READY", local.stdout) + self.assertIn("[verdict] REMOTE_UNVERIFIED", local.stdout) + self.assertIn("rewound or diverged", local.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before) + # After the failed fetch the cache still points at the forward descendant + # so a follow-up run can repeat the same check. + self.assertEqual(f.git(self.wt, "rev-parse", "origin/main").stdout.strip(), forward) def test_unknown_work_mode_is_rejected(self): result = self.precheck(mode="anything") @@ -240,6 +391,936 @@ class TaskRecoveryTests(unittest.TestCase): "VALIDATE_RECEIPT_PURPOSE": "ignore"}) self.assertNotEqual(result.returncode, 0, result.stdout) + def test_local_resume_continues_when_origin_is_unreachable(self): + """Standalone: capture a valid TASK_WORK_MODE=local precheck receipt, + point the disposable worktree's origin URL at a nonexistent path, + then assert (a) `task resume` in local mode imports the receipt and + leaves HEAD unchanged and (b) the same receipt under delivery mode + rejects with REMOTE_UNVERIFIED and does not import. + + Currently RED on (a): cmd_resume calls fetch_target unconditionally, + so the unreachable origin aborts the resume before the receipt can + be imported (set -e trips, returncode != 0, no [resumed], receipt + bytes unchanged, card untouched). Post-fix the local path must + skip or survive the unreachable fetch and import the receipt on + the registered in-progress task context. + + Delivery must remain strict under the same offline origin — the + RED-capture only records what (a) requires; (b) stays correct.""" + f = self.fixture + receipt = self.capture() + original = receipt.read_bytes() + receipt_sha = json.loads(original)["receipt_sha256"] + head = f.git(self.wt, "rev-parse", "HEAD").stdout + # Break the disposable worktree's origin URL so the offline + # scenario is real (the fetch in cmd_resume loses its target). + # The bare-remote fixture and the registered HEAD remain + # identifiable on the worktree fs; only the authoritative URL + # is changed for the duration of this test. + f.git(self.wt, "remote", "set-url", "origin", str(f.root / "missing.git")) + # (1) Local mode must continue authorized work by importing the + # captured receipt under the registered in-progress task context. + local = self.resume(receipt, mode="local") + self.assertEqual(local.returncode, 0, + f"local resume should import the captured receipt under the " + f"registered task (RED: fetch_target aborts the resume before " + f"IMPORT_RECEIPT; returncode={local.returncode}); stdout={local.stdout}") + self.assertIn("[resumed]", local.stdout) + self.assertIn("work_mode=local", local.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head) + self.assertEqual(receipt.read_bytes(), original) + self.assertIn(receipt_sha, self.card.read_text()) + # (2) Same receipt under delivery mode must stay strict under + # the same offline origin: reject with REMOTE_UNVERIFIED, no + # `[resumed]` line, the card is not updated to claim delivery + # authorization, and HEAD remains untouched. + card_before_delivery = self.card.read_bytes() + delivery = self.resume(receipt, mode="delivery") + self.assertNotEqual(delivery.returncode, 0, delivery.stdout) + self.assertIn("[verdict] REMOTE_UNVERIFIED", delivery.stdout) + self.assertNotIn("[resumed]", delivery.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head) + self.assertEqual(self.card.read_bytes(), card_before_delivery) + + def test_local_child_resume_uses_local_parent_when_origin_is_unreachable(self): + """Child: configure the task card as child with + immediate_target=feature/parent, create that parent branch locally + at the recorded parent_head_sha, capture a local receipt, make + origin unreachable, and assert that `task resume` in local mode + imports the receipt using the authorized local parent reference + (the local refs/heads/feature/parent tip = parent_head_sha). + + Currently RED: cmd_resume reaches fetch_target before resolving + the parent reference, so the unreachable origin aborts the + resume before the receipt can be imported (returncode != 0, no + `[resumed]`, receipt bytes unchanged, child card untouched). + Post-fix the local path must skip or survive the unreachable + fetch AND let resolve_parent_ref fall back to the authorized + local parent branch when origin is unqueriable.""" + f = self.fixture + r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + # (1) Create the parent initiative branch in the main repo so + # the parent has a deterministic tip commit, push it to the + # bare remote so the worktree can fetch it once at the start, + # then create the child branch on top of the parent inside the + # worktree and switch the worktree to it. + f.git(f.repo, "checkout", "-b", "feature/parent") + (f.repo / "parent-tip").write_text("authorized parent initiative tip\n") + f.git(f.repo, "add", "parent-tip") + f.git(f.repo, "commit", "-m", "Parent initiative tip commit") + parent_head = f.git(f.repo, "rev-parse", "HEAD").stdout.strip() + f.git(f.repo, "push", "origin", "feature/parent") + # Bring the parent branch into the worktree, branch off the + # parent at the recorded tip, then check the worktree out on + # the child branch. + f.git(self.wt, "fetch", "origin", + "feature/parent:refs/remotes/origin/feature/parent") + f.git(self.wt, "branch", "feature/parent-child", parent_head) + f.git(self.wt, "checkout", "feature/parent-child") + # (2) Write the child card on the child branch so branch_card + # finds it (the file is named after the branch slug) and the + # resume's immediate_target + parent_head_sha fields resolve + # to feature/parent and the recorded parent tip. + child_card = self.wt / ".task" / "feature-parent-child.md" + self.assertFalse(child_card.exists()) + child_card.parent.mkdir(exist_ok=True) + child_card.write_text( + "# feature/parent-child\n" + "任务主记录:fixture;状态:进行中;owner:Lifecycle Test\n" + f"- 权威仓:fixture;目标分支:main;R0(initial_base_sha)= {r0}\n" + f"- 工作区:{self.wt}(worktree);分支:feature/parent-child\n" + "- 分支类型:child;initiative_id:feature-parent;root_target:main;" + "immediate_target:feature/parent;accepting_requirements:false;" + "parent_branch:feature/parent;" + f"parent_head_sha:{parent_head};" + "parent_pr:无;parent_source:local(非正式准入)\n" + ) + f.git(self.wt, "add", str(child_card.relative_to(self.wt))) + f.git(self.wt, "commit", "-m", "Register child fixture task") + # (3) Capture a local receipt on the child branch. + receipt = self.capture() + original = receipt.read_bytes() + receipt_sha = json.loads(original)["receipt_sha256"] + head = f.git(self.wt, "rev-parse", "HEAD").stdout + # (4) Break the origin URL so the offline scenario is real. + f.git(self.wt, "remote", "set-url", "origin", str(f.root / "missing.git")) + # (5) Local resume must import the receipt using the + # authorized local parent reference and leave HEAD unchanged. + result = self.resume(receipt, mode="local") + self.assertEqual(result.returncode, 0, + f"local child resume should import the captured receipt via the " + f"local parent ref (RED: fetch_target aborts the resume before " + f"resolve_parent_ref / IMPORT_RECEIPT; returncode={result.returncode}); " + f"stdout={result.stdout}") + self.assertIn("[resumed]", result.stdout) + self.assertIn("work_mode=local", result.stdout) + self.assertIn(f"observed_target={parent_head}", result.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head) + self.assertEqual(receipt.read_bytes(), original) + self.assertIn(receipt_sha, child_card.read_text()) + + def test_local_child_resume_rejects_reachable_missing_parent(self): + """Child: configure the task card as child with + immediate_target=feature/parent, create that parent branch on the + bare remote AND locally at the recorded parent_head_sha, capture + a local receipt, then remove ONLY refs/heads/feature/parent from + the bare remote while keeping origin reachable (the bare remote + still serves refs/heads/main). `task resume` in local mode must + refuse with REMOTE_UNVERIFIED instead of silently accepting the + local parent reference: the registered immediate parent branch + is absent remotely, so the local-only reference cannot be + reconciled against the authoritative origin even though it + exists in the worktree. + + Currently RED: resolve_parent_ref() does not distinguish + `ls-remote rc=2` (ref missing on a reachable origin) from the + unreachable branch. The function falls through the + `elif [ $rc -ne 2 ]` guard and lets `local_ref` satisfy the + resolver, so cmd_resume reaches IMPORT_RECEIPT and prints + `[resumed]` even though origin is fully reachable and just + missing the registered parent ref. Post-fix the local path must + print `[verdict] REMOTE_UNVERIFIED — origin is reachable but + feature/parent is missing; preserve local work, receipt not + imported` and refuse to import the receipt. + + The fixture mirrors + test_local_child_resume_uses_local_parent_when_origin_is_unreachable + but adds the local refs/heads/feature/parent branch and only + deletes the bare remote ref — the worktree URL stays on the + disposable bare remote and main remains fetchable, so the only + authority gap is the absent remote parent ref.""" + f = self.fixture + r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + # (1) Create the parent initiative branch in the main repo and + # push it to the bare remote so the worktree can fetch it once + # at the start, then create the local refs/heads/feature/parent + # branch inside the worktree (the post-fix path must refuse + # even when this local branch exists), branch off the parent + # tip into feature/parent-child, then switch the worktree to + # the child branch. + f.git(f.repo, "checkout", "-b", "feature/parent") + (f.repo / "parent-tip").write_text("authorized parent initiative tip\n") + f.git(f.repo, "add", "parent-tip") + f.git(f.repo, "commit", "-m", "Parent initiative tip commit") + parent_head = f.git(f.repo, "rev-parse", "HEAD").stdout.strip() + f.git(f.repo, "push", "origin", "feature/parent") + f.git(self.wt, "fetch", "origin", + "feature/parent:refs/remotes/origin/feature/parent") + # The shared refs/heads namespace means the worktree already + # sees refs/heads/feature/parent (it was created in f.repo via + # `checkout -b feature/parent`); the test must reject even + # when this local branch exists, so leave it intact. + self.assertEqual( + f.git(self.wt, "rev-parse", "--verify", + "refs/heads/feature/parent").stdout.strip(), parent_head) + f.git(self.wt, "branch", "feature/parent-child", parent_head) + f.git(self.wt, "checkout", "feature/parent-child") + # (2) Write the child card on the child branch so branch_card + # finds it (the file is named after the branch slug) and the + # resume's immediate_target + parent_head_sha fields resolve + # to feature/parent and the recorded parent tip. + child_card = self.wt / ".task" / "feature-parent-child.md" + self.assertFalse(child_card.exists()) + child_card.parent.mkdir(exist_ok=True) + child_card.write_text( + "# feature/parent-child\n" + "任务主记录:fixture;状态:进行中;owner:Lifecycle Test\n" + f"- 权威仓:fixture;目标分支:main;R0(initial_base_sha)= {r0}\n" + f"- 工作区:{self.wt}(worktree);分支:feature/parent-child\n" + "- 分支类型:child;initiative_id:feature-parent;root_target:main;" + "immediate_target:feature/parent;accepting_requirements:false;" + "parent_branch:feature/parent;" + f"parent_head_sha:{parent_head};" + "parent_pr:无;parent_source:local(非正式准入)\n" + ) + f.git(self.wt, "add", str(child_card.relative_to(self.wt))) + f.git(self.wt, "commit", "-m", "Register child fixture task") + # (3) Capture a local receipt on the child branch BEFORE + # removing the bare remote ref, so the receipt reflects the + # state where the remote parent ref is still present. + receipt = self.capture() + original = receipt.read_bytes() + receipt_sha = json.loads(original)["receipt_sha256"] + head = f.git(self.wt, "rev-parse", "HEAD").stdout + child_card_before = child_card.read_bytes() + # (4) Remove ONLY refs/heads/feature/parent from the bare + # remote. The worktree's origin URL still points at the + # disposable bare remote and refs/heads/main stays intact, so + # the only gap in authority is the absent remote parent ref. + # The local refs/heads/feature/parent branch remains in the + # worktree (this is the "even if a local parent branch + # exists" half of the scenario). + self.assertEqual( + f.git(f.remote, "rev-parse", "--verify", + "refs/heads/feature/parent").stdout.strip(), parent_head) + f.git(f.remote, "update-ref", "-d", "refs/heads/feature/parent") + # Confirm the bare remote now refuses to serve the parent ref + # while still serving main (so origin is reachable, just + # missing the registered immediate parent ref). + self.assertNotEqual( + f.git(f.remote, "show-ref", "--verify", "--hash", + "refs/heads/feature/parent", check=False).returncode, 0) + self.assertEqual( + f.git(f.remote, "rev-parse", "--verify", + "refs/heads/main").stdout.strip(), r0) + # (5) Local resume must refuse with REMOTE_UNVERIFIED instead + # of silently accepting the local parent ref. No [resumed] + # line, the receipt is not imported (card bytes unchanged and + # receipt_sha256 not embedded), the receipt file is byte-for- + # byte unchanged, and HEAD is preserved. + result = self.resume(receipt, mode="local") + self.assertNotEqual(result.returncode, 0, + f"local child resume must reject when origin is reachable " + f"but the registered parent ref is missing; current code " + f"falls through resolve_parent_ref's rc=2 case and accepts " + f"the local parent, returning 0 and printing [resumed]; " + f"stdout={result.stdout}") + self.assertIn("[verdict] REMOTE_UNVERIFIED", result.stdout, + "the resume must surface REMOTE_UNVERIFIED for the reachable-but-" + "missing-parent scenario so the owner can reconcile the parent " + f"initiative instead of silently resuming on an unsynced tip; " + f"stdout={result.stdout}") + self.assertNotIn("[resumed]", result.stdout, + "the resume must NOT import the receipt under a reachable " + "origin whose registered parent ref is missing — accepting " + "the local-only parent would advertise delivery readiness " + f"for an initiative the worktree cannot reconcile; stdout={result.stdout}") + self.assertEqual(child_card.read_bytes(), child_card_before, + "the child card must not be updated with receipt_sha256 when " + "the resume refuses to import the receipt") + self.assertEqual(receipt.read_bytes(), original, + "the receipt file must be byte-for-byte unchanged when the " + "resume refuses to import it") + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head, + "the child HEAD must not move when the resume refuses to " + "import the receipt") + self.assertNotIn(receipt_sha, child_card.read_text(), + "the child card must not embed the receipt sha256 when " + "the resume refuses to import the receipt") + + def test_local_precheck_rejects_reachable_uncomparable_target(self): + """Origin advertises a target SHA but the advertised object is + unavailable locally, so ancestry cannot be compared against the + cached refs/remotes/origin/main. Local precheck must not advertise + LOCAL_READY under that unverifiable state — the cached target + cannot be trusted as authoritative without an ancestry check. + + A disposable test-only git wrapper fails `fetch`, makes + `ls-remote origin refs/heads/main` advertise a synthetic SHA, and + makes `cat-file -e ^{commit}` fail; all other git calls + delegate to the real binary so the registered in-progress task + context stays observable. + + The current implementation lets target_rewound() bail out at the + cat-file -e step (return 1), then unconditionally prints + LOCAL_READY in local mode because the `&& target_rewound` guard + short-circuits. The verdict is wrong; this test records the + expected red result.""" + f = self.fixture + head_before = f.git(self.wt, "rev-parse", "HEAD").stdout + cached_before = f.git(self.wt, "rev-parse", "refs/remotes/origin/main").stdout + + # Find the real git to delegate everything but the three intercepted calls. + real_git = subprocess.check_output( + ["bash", "-c", "command -v git"], env=f.env, text=True).strip() + synthetic = "1" * 40 + bin_dir = f.root / "uncomparable-bin" + bin_dir.mkdir(exist_ok=True) + wrapper = bin_dir / "git" + wrapper.write_text( + "#!/usr/bin/env bash\n" + "# Disposable fixture wrapper for the reachable-but-uncomparable\n" + "# scenario: `fetch` fails, `ls-remote origin refs/heads/main`\n" + "# advertises a synthetic SHA, `cat-file -e ^{commit}`\n" + "# fails. Anything else delegates to the real git so the\n" + "# registered task context is observable normally.\n" + f"REAL_GIT={shlex.quote(real_git)}\n" + f"SYNTHETIC={shlex.quote(synthetic)}\n" + "WRAPPER_BIN=$(cd \"$(dirname \"$0\")\" && pwd)\n" + "stripped=\"\"\n" + "old_ifs=\"$IFS\"\n" + "IFS=':'\n" + "for p in $PATH; do\n" + " [ \"$p\" = \"$WRAPPER_BIN\" ] && continue\n" + " if [ -z \"$stripped\" ]; then stripped=\"$p\"; else stripped=\"$stripped:$p\"; fi\n" + "done\n" + "IFS=\"$old_ifs\"\n" + "export PATH=\"$stripped\"\n" + "case \"$1\" in\n" + " fetch)\n" + " echo 'fatal: unable to access origin' >&2\n" + " exit 128 ;;\n" + " ls-remote)\n" + " if [ \"${2:-}\" = origin ] && [ \"${3:-}\" = refs/heads/main ]; then\n" + " printf '%s\\trefs/heads/main\\n' \"$SYNTHETIC\"\n" + " exit 0\n" + " fi\n" + " exec \"$REAL_GIT\" \"$@\" ;;\n" + " cat-file)\n" + " if [ \"${2:-}\" = -e ] && [ \"${3:-}\" = \"${SYNTHETIC}^{commit}\" ]; then\n" + " echo \"fatal: Not a valid object name ${SYNTHETIC}^{commit}.\" >&2\n" + " exit 1\n" + " fi\n" + " exec \"$REAL_GIT\" \"$@\" ;;\n" + "esac\n" + "exec \"$REAL_GIT\" \"$@\"\n") + wrapper.chmod(0o755) + wrapper_path = f"{bin_dir}{os.pathsep}{f.env.get('PATH', os.environ['PATH'])}" + wrapper_env = f.env | {"PATH": wrapper_path} + local = f.task(self.wt, "precheck", check=False, + env=wrapper_env | {"TASK_WORK_MODE": "local"}) + self.assertNotEqual(local.returncode, 0, local.stdout) + self.assertNotIn("[verdict] LOCAL_READY", local.stdout) + self.assertIn("[verdict] REMOTE_UNVERIFIED", local.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before) + self.assertEqual(f.git(self.wt, "rev-parse", "refs/remotes/origin/main").stdout, + cached_before) + + def write_no_cache_wrapper(self, bin_dir, advertised, log_name="git.log", + auth_fail=False): + """Create a disposable git wrapper for the missing-tracking-ref and + reachable-auth-failure scenarios. The wrapper's intercept set is + parameterised so a single helper produces both no-cache (default) + and auth-failure fixtures; each test still owns its own bin_dir so + no fixtures are shared across tests. + + When auth_fail=False (default, no-cache scenario): + fetch -> fail (128); the wrapper + writes the standard "unable to access origin" stderr line so + the precheck captures the same `fetch_err` it would observe + against a real unreachable remote + ls-remote origin refs/heads/main -> serve ADVERTISED (or + nothing when ADVERTISED is empty, simulating an origin that + has no configured target branch) + branch --merged origin/main -> DELEGATED to REAL_GIT. + The earlier stub masked a production-pipeline bug: with the + cached refs/remotes/origin/main deleted, the real git call + fails under set -e/pipefail and the precheck silently exits + before the verdict is printed. The stub has been removed so + the tests now exercise the actual missing-ref strict-shell + path; the post-fix implementation must reach LOCAL_READY (or + REMOTE_UNVERIFIED) without the helper stub, and the test + records the expected red result on current code. + + When auth_fail=True (reachable-but-auth-failure scenario): + fetch -> fail (128) and write + "fatal: Authentication failed for origin (HTTP 401)" + ls-remote origin refs/heads/main -> fail (128) and write + "fatal: Authentication failed (HTTP 401)"; no value served. + This simulates a reachable origin that refuses to disclose + its target head — the production code must NOT promote the + registered task to LOCAL_READY (or [resumed] for resume) just + because ls-remote returned an auth error instead of an + advertised SHA. + + All other git calls delegate to the real binary via the resolved + REAL_GIT absolute path (and a stripped PATH so the wrapper cannot + recurse into itself) so the registered task context stays + observable normally. + + Returns (bin_dir, wrapper_log_path).""" + f = self.fixture + real_git = subprocess.check_output( + ["bash", "-c", "command -v git"], env=f.env, text=True).strip() + bin_dir.mkdir(exist_ok=True) + wrapper_log = bin_dir / log_name + wrapper = bin_dir / "git" + if auth_fail: + header_log = "echo \"configured auth-failure-mode\" >> \"$LOG\"\n" + fetch_block = ( + " fetch)\n" + " echo \"fetch-auth-failure\" >> \"$LOG\"\n" + " echo 'fatal: Authentication failed for origin (HTTP 401)' >&2\n" + " exit 128 ;;\n" + ) + ls_remote_block = ( + " ls-remote)\n" + " if [ \"${2:-}\" = origin ] && [ \"${3:-}\" = refs/heads/main ]; then\n" + " echo \"ls-remote-auth-failure advertised-target=\" >> \"$LOG\"\n" + " echo 'fatal: Authentication failed (HTTP 401)' >&2\n" + " exit 128\n" + " fi\n" + " exec \"$REAL_GIT\" \"$@\" ;;\n" + ) + else: + header_log = f"echo \"configured advertised-target=$ADVERTISED\" >> \"$LOG\"\n" + fetch_block = ( + " fetch)\n" + " echo \"fetch-intercepted\" >> \"$LOG\"\n" + " echo 'fatal: unable to access origin' >&2\n" + " exit 128 ;;\n" + ) + ls_remote_block = ( + " ls-remote)\n" + " if [ \"${2:-}\" = origin ] && [ \"${3:-}\" = refs/heads/main ]; then\n" + " echo \"ls-remote-served advertised-target=$ADVERTISED\" >> \"$LOG\"\n" + " if [ -n \"$ADVERTISED\" ]; then\n" + " printf '%s\\trefs/heads/main\\n' \"$ADVERTISED\"\n" + " fi\n" + " exit 0\n" + " fi\n" + " exec \"$REAL_GIT\" \"$@\" ;;\n" + ) + wrapper.write_text( + "#!/usr/bin/env bash\n" + "# Disposable fixture wrapper for the missing-tracking-ref and\n" + "# reachable-auth-failure scenarios. See write_no_cache_wrapper\n" + "# docstring for the intercept set.\n" + f"REAL_GIT={shlex.quote(real_git)}\n" + f"LOG={shlex.quote(str(wrapper_log))}\n" + f"ADVERTISED={shlex.quote(advertised)}\n" + "WRAPPER_BIN=$(cd \"$(dirname \"$0\")\" && pwd)\n" + + header_log + + "stripped=\"\"\n" + "old_ifs=\"$IFS\"\n" + "IFS=':'\n" + "for p in $PATH; do\n" + " [ \"$p\" = \"$WRAPPER_BIN\" ] && continue\n" + " if [ -z \"$stripped\" ]; then stripped=\"$p\"; else stripped=\"$stripped:$p\"; fi\n" + "done\n" + "IFS=\"$old_ifs\"\n" + "export PATH=\"$stripped\"\n" + "case \"$1\" in\n" + + fetch_block + ls_remote_block + + "esac\n" + "exec \"$REAL_GIT\" \"$@\"\n") + wrapper.chmod(0o755) + return bin_dir, wrapper_log + + def test_local_precheck_without_cached_ref_rejects_uncomparable_remote(self): + """Cached refs/remotes/origin/main is absent; origin advertises a + SHA whose object is unavailable locally. Local precheck must not + advertise LOCAL_READY because ancestry cannot be compared against + any locally known target — the cached target cannot be trusted + as authoritative without an ancestry check, and the advertised + SHA is not locally reachable so no forward-descendant + relationship can be established either. + + The current implementation lets target_history() short-circuit at + the missing cached-ref step (return "ok") and unconditionally + print LOCAL_READY in local mode. The verdict is wrong; this test + records the expected red result.""" + f = self.fixture + head_before = f.git(self.wt, "rev-parse", "HEAD").stdout + r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + # Detach the registered branch from its (now stale) upstream so + # no in-process code accidentally queries HEAD@{u} / remote.merge + # against the deleted cached ref. + f.git(self.wt, "branch", "--unset-upstream", "feature/test") + f.git(self.wt, "update-ref", "-d", "refs/remotes/origin/main") + + synthetic = "1" * 40 + bin_dir, wrapper_log = self.write_no_cache_wrapper( + f.root / "no-cache-uncomparable-bin", synthetic) + path_env = f"{bin_dir}{os.pathsep}{f.env.get('PATH', os.environ['PATH'])}" + wrapper_env = f.env | {"PATH": path_env} + + local = f.task(self.wt, "precheck", check=False, + env=wrapper_env | {"TASK_WORK_MODE": "local"}) + + # Proof the wrapper was actually used: the configured marker + # names the advertised SHA the test asked the wrapper to serve, + # `fetch` was rejected (so the unreachable remote is observable + # to the production pipeline), and `ls-remote origin refs/heads/main` + # was answered with the synthetic SHA the wrapper was configured + # to serve. The last assertion is required because reachable + # advertised cases must verify the wrapper served the expected + # ls-remote response, not only that the wrapper initialized. + log = wrapper_log.read_text() + self.assertIn(f"configured advertised-target={synthetic}", log) + self.assertIn("fetch-intercepted", log) + self.assertIn(f"ls-remote-served advertised-target={synthetic}", log) + + self.assertNotEqual(local.returncode, 0, local.stdout) + self.assertNotIn("[verdict] LOCAL_READY", local.stdout) + self.assertIn("[verdict] REMOTE_UNVERIFIED", local.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before) + + def test_local_precheck_without_cached_ref_rejects_missing_remote_target(self): + """Cached refs/remotes/origin/main is absent; origin returns no + configured target branch (ls-remote outputs nothing). Local + precheck must not advertise LOCAL_READY because nothing can be + verified against the registered task's R0 — the cached target + is the only handle on what origin claimed, and without it (and + without an ls-remote response) the precheck has no + authoritative signal to confirm the target even exists. + + The current implementation lets target_history() short-circuit at + the missing cached-ref step (return "ok") and unconditionally + print LOCAL_READY in local mode. The verdict is wrong; this test + records the expected red result.""" + f = self.fixture + head_before = f.git(self.wt, "rev-parse", "HEAD").stdout + r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + f.git(self.wt, "branch", "--unset-upstream", "feature/test") + f.git(self.wt, "update-ref", "-d", "refs/remotes/origin/main") + + bin_dir, wrapper_log = self.write_no_cache_wrapper( + f.root / "no-cache-missing-target-bin", "") + path_env = f"{bin_dir}{os.pathsep}{f.env.get('PATH', os.environ['PATH'])}" + wrapper_env = f.env | {"PATH": path_env} + + local = f.task(self.wt, "precheck", check=False, + env=wrapper_env | {"TASK_WORK_MODE": "local"}) + + log = wrapper_log.read_text() + # Empty advertised value means the wrapper serves no ls-remote line. + # The configured marker still records the empty advertised value, + # and the served marker is logged with an empty advertised-target so + # the test can confirm the wrapper was actually invoked for the + # ls-remote call (not only that the wrapper binary was loaded). + self.assertIn("configured advertised-target=", log) + self.assertIn("fetch-intercepted", log) + self.assertIn("ls-remote-served advertised-target=", log) + + self.assertNotEqual(local.returncode, 0, local.stdout) + self.assertNotIn("[verdict] LOCAL_READY", local.stdout) + self.assertIn("[verdict] REMOTE_UNVERIFIED", local.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before) + + def test_local_precheck_without_cached_ref_allows_comparable_forward_target(self): + """Cached refs/remotes/origin/main is absent; origin advertises + a forward descendant of the task's R0 (the descendant is + locally available so ancestry can be compared against the + registered baseline). Local precheck must continue to advertise + LOCAL_READY — the explicit missing-tracking-ref continuation + contract — because the registered task context is the same as + the cached ref would have represented, and the ls-remote + response can be compared against the registered baseline to + confirm origin is at or past the task's initial state. + + The current implementation short-circuits at the missing + cached-ref step (return "ok"), which coincidentally returns + LOCAL_READY for any registered task. The verdict is right; this + test records the expected green result so the post-fix + implementation preserves the contract when the ls-remote check + is added against the registered R0.""" + f = self.fixture + head_before = f.git(self.wt, "rev-parse", "HEAD").stdout + r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + # Advance the bare remote so the advertised target is a real + # forward descendant of R0 (not R0 itself). The descendant must + # be locally available so the wrapper can advertise it AND the + # production `cat-file -e` check inside target_history() succeeds + # when the descendant is compared against the registered R0. + f.advance_remote() + forward = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + self.assertNotEqual(forward, r0) + self.assertEqual(f.git(f.repo, "merge-base", r0, forward).stdout.strip(), r0) + f.git(self.wt, "branch", "--unset-upstream", "feature/test") + f.git(self.wt, "update-ref", "-d", "refs/remotes/origin/main") + + bin_dir, wrapper_log = self.write_no_cache_wrapper( + f.root / "no-cache-comparable-bin", forward) + path_env = f"{bin_dir}{os.pathsep}{f.env.get('PATH', os.environ['PATH'])}" + wrapper_env = f.env | {"PATH": path_env} + + local = f.task(self.wt, "precheck", check=False, + env=wrapper_env | {"TASK_WORK_MODE": "local"}) + + log = wrapper_log.read_text() + self.assertIn(f"configured advertised-target={forward}", log) + self.assertIn("fetch-intercepted", log) + # Reachable advertised cases must verify the wrapper actually + # served the expected ls-remote response (a forward descendant of + # the registered R0), not only that the wrapper binary was loaded. + self.assertIn(f"ls-remote-served advertised-target={forward}", log) + + self.assertEqual(local.returncode, 0, local.stdout) + self.assertIn("[verdict] LOCAL_READY", local.stdout) + self.assertNotIn("[verdict] REMOTE_UNVERIFIED", local.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before) + + def test_local_child_resume_rejects_missing_parent_sha(self): + """Child: configure the task card as child with + immediate_target=feature/parent and parent_branch=feature/parent, + create that parent branch locally (no origin fetch), branch off + it into feature/parent-child, but DELIBERATELY OMIT + parent_head_sha from the child card so the recorded parent head + is empty. Make origin unreachable, capture a local receipt, + then assert `task resume` in local mode refuses to import the + receipt. + + The registered task cannot be reconciled against any + authoritative parent head when parent_head_sha is missing + AND origin is unreachable: the local branch tip is the only + handle, but without a recorded parent_head_sha the production + code cannot verify the local tip is the tip the child branched + off of. The resume must refuse with REMOTE_UNVERIFIED, must + not emit `[resumed]`, must not import the receipt (the child + card stays byte-for-byte unchanged and the receipt_sha256 is + not embedded), and HEAD must remain untouched. + + Currently RED on current code: cmd_resume only runs the + parent_head_sha ancestry check when `[ -n "$recorded" ]` is + true; an empty parent_head_sha short-circuits the check and + lets the resume fall through to IMPORT_RECEIPT and the + `[resumed]` line. This test records the expected red result so + the post-fix implementation enforces the parent_head_sha + presence contract under the local-outage fallback path. + + Preserves test_local_child_resume_uses_local_parent_when_origin_is_unreachable + (the real-outage-with-valid-parent case) and + test_local_child_resume_rejects_reachable_missing_parent (the + reachable-known-missing-parent case).""" + f = self.fixture + r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + # (1) Create the parent initiative branch in the main repo so + # the parent has a deterministic tip commit, push it to the + # bare remote so the worktree can fetch it once at the start, + # then create the child branch on top of the parent inside the + # worktree and switch the worktree to it. We do NOT push or + # fetch parent in the worktree because the test explicitly + # makes origin unreachable before the resume step. + f.git(f.repo, "checkout", "-b", "feature/parent") + (f.repo / "parent-tip").write_text("authorized parent initiative tip\n") + f.git(f.repo, "add", "parent-tip") + f.git(f.repo, "commit", "-m", "Parent initiative tip commit") + parent_head = f.git(f.repo, "rev-parse", "HEAD").stdout.strip() + f.git(f.repo, "push", "origin", "feature/parent") + # The worktree shares the refs/heads namespace with f.repo, + # so `checkout -b feature/parent` in f.repo already materialises + # refs/heads/feature/parent inside the worktree. The test must + # refuse even when this local branch exists, so leave it intact. + f.git(self.wt, "branch", "feature/parent-child", parent_head) + f.git(self.wt, "checkout", "feature/parent-child") + # (2) Write the child card on the child branch so branch_card + # finds it (the file is named after the branch slug) and the + # resume's immediate_target + parent_branch fields resolve to + # feature/parent. The card DELIBERATELY OMITS parent_head_sha so + # the recorded parent head is empty. + child_card = self.wt / ".task" / "feature-parent-child.md" + self.assertFalse(child_card.exists()) + child_card.parent.mkdir(exist_ok=True) + child_card.write_text( + "# feature/parent-child\n" + "任务主记录:fixture;状态:进行中;owner:Lifecycle Test\n" + f"- 权威仓:fixture;目标分支:main;R0(initial_base_sha)= {r0}\n" + f"- 工作区:{self.wt}(worktree);分支:feature/parent-child\n" + "- 分支类型:child;initiative_id:feature-parent;root_target:main;" + "immediate_target:feature/parent;accepting_requirements:false;" + "parent_branch:feature/parent;" + "parent_pr:无;parent_source:local(非正式准入)\n" + ) + f.git(self.wt, "add", str(child_card.relative_to(self.wt))) + f.git(self.wt, "commit", "-m", "Register child fixture task without parent_head_sha") + # (3) Capture a local receipt on the child branch BEFORE + # making origin unreachable. + receipt = self.capture() + original = receipt.read_bytes() + receipt_sha = json.loads(original)["receipt_sha256"] + head = f.git(self.wt, "rev-parse", "HEAD").stdout + child_card_before = child_card.read_bytes() + # (4) Break the origin URL so the offline scenario is real. + f.git(self.wt, "remote", "set-url", "origin", str(f.root / "missing.git")) + # Confirm the local parent branch still exists at the recorded + # tip (resolve_parent_ref would otherwise fall through to a + # bare error before the missing parent_head_sha check matters). + self.assertEqual( + f.git(self.wt, "rev-parse", "--verify", + "refs/heads/feature/parent").stdout.strip(), parent_head) + # (5) Local resume must refuse with REMOTE_UNVERIFIED because + # the recorded parent_head_sha is empty: the local parent tip + # alone is not a sufficient handle for authorized continuation + # when origin is unreachable. No [resumed] line, the receipt is + # not imported (card bytes unchanged and receipt_sha256 not + # embedded), the receipt file is byte-for-byte unchanged, and + # HEAD is preserved. + result = self.resume(receipt, mode="local") + self.assertNotEqual(result.returncode, 0, + f"local child resume must reject when origin is unreachable " + f"and the recorded parent_head_sha is empty; current code " + f"short-circuits the empty recorded check and lets IMPORT_RECEIPT " + f"run, returning 0 and printing [resumed]; " + f"stdout={result.stdout}") + self.assertNotIn("[resumed]", result.stdout, + "the resume must NOT import the receipt when the registered " + "parent_head_sha is empty and origin is unreachable — the local " + "parent tip alone is not a sufficient handle for authorized " + f"continuation under the offline fallback path; stdout={result.stdout}") + self.assertIn("[verdict] REMOTE_UNVERIFIED", result.stdout, + "the resume must surface REMOTE_UNVERIFIED for the unreachable-" + "origin-and-missing-parent_head_sha scenario so the owner can " + "backfill the recorded parent head before retrying; " + f"stdout={result.stdout}") + self.assertEqual(child_card.read_bytes(), child_card_before, + "the child card must not be updated with receipt_sha256 when " + "the resume refuses to import the receipt") + self.assertEqual(receipt.read_bytes(), original, + "the receipt file must be byte-for-byte unchanged when the " + "resume refuses to import it") + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head, + "the child HEAD must not move when the resume refuses to " + "import the receipt") + self.assertNotIn(receipt_sha, child_card.read_text(), + "the child card must not embed the receipt sha256 when " + "the resume refuses to import the receipt") + + def test_local_resume_rejects_reachable_auth_failure(self): + """Standalone: capture a valid TASK_WORK_MODE=local precheck + receipt against the real bare remote (origin is reachable and + serves refs/heads/main normally), then point the worktree at a + disposable wrapper that makes `fetch` and + `ls-remote origin refs/heads/main` both fail with explicit + "Authentication failed (HTTP 401)" and run `task resume` in + local mode. + + The wrapper proves the wrapper was actually used (configured + marker, fetch-auth-failure, ls-remote-auth-failure). The resume + must NOT yield the local-continuation positive signal: it + must refuse with REMOTE_UNVERIFIED, must not emit `[resumed]`, + must not import the receipt (the standalone card stays byte- + for-byte unchanged and the receipt_sha256 is not embedded), + the receipt file must be byte-for-byte unchanged, and HEAD + must remain untouched. + + Currently RED on current code: when fetch and ls-remote both + fail (auth error), `target_history` sees an empty listing and + short-circuits to "ok", so the unreachable-fallback branch + falls through to IMPORT_RECEIPT and the `[resumed]` line even + though the authoritative target head cannot be confirmed. + This test records the expected red result so the post-fix + implementation refuses the auth-failure scenario instead of + silently promoting the registered task to local continuation. + + The test owns its own disposable git wrapper so the auth + failure scenario does not leak into the wrapper shared with + the no-cache tests; the existing + test_local_resume_continues_when_origin_is_unreachable + (unreachable origin with the bare remote URL rewritten) and + test_local_child_resume_uses_local_parent_when_origin_is_unreachable + (unreachable origin with a child task) are preserved.""" + f = self.fixture + # (1) Capture a valid local receipt while origin is reachable + # so the receipt reflects the registered task state under the + # disposable bare remote. The capture itself runs without the + # wrapper so the receipt is a real artifact, not a fake. + receipt = self.capture() + original = receipt.read_bytes() + receipt_sha = json.loads(original)["receipt_sha256"] + head = f.git(self.wt, "rev-parse", "HEAD").stdout + card_before = self.card.read_bytes() + # (2) Build the disposable auth-failure wrapper so the wrapper + # intercepts fetch (128, Authentication failed) and + # ls-remote origin refs/heads/main (128, Authentication failed, + # no value served) while everything else delegates to the real + # git. The wrapper's own marker line proves it was loaded. + bin_dir, wrapper_log = self.write_no_cache_wrapper( + f.root / "auth-failure-bin", "", log_name="auth-failure.log", + auth_fail=True) + path_env = f"{bin_dir}{os.pathsep}{f.env.get('PATH', os.environ['PATH'])}" + wrapper_env = (f.env | {"PATH": path_env} + | {"TASK_WORK_MODE": "local", + "PRECHECK_RECEIPT": str(receipt)}) + # (3) Local resume under the auth-failure wrapper must refuse. + result = f.task(self.wt, "resume", check=False, env=wrapper_env) + # Proof the wrapper was actually used. + log = wrapper_log.read_text() + self.assertIn("configured auth-failure-mode", log) + self.assertIn("fetch-auth-failure", log) + self.assertIn("ls-remote-auth-failure", log) + # Auth-failure scenarios must assert the wrapper actually + # served the expected (auth) response, not only that the + # wrapper initialized. + self.assertNotEqual(result.returncode, 0, + f"local resume must reject when fetch and ls-remote both " + f"return Authentication failed (HTTP 401); current code " + f"falls through target_history's empty-listing short-" + f"circuit and reaches IMPORT_RECEIPT, returning 0 and " + f"printing [resumed]; stdout={result.stdout}") + self.assertIn("[verdict] REMOTE_UNVERIFIED", result.stdout, + "the resume must surface REMOTE_UNVERIFIED when the reachable " + "origin returns Authentication failed (HTTP 401) instead of an " + "advertised target head; the post-fix implementation must not " + "promote the registered task to local continuation under an " + f"authoritative-but-unverifiable remote; stdout={result.stdout}") + self.assertNotIn("[resumed]", result.stdout, + "the resume must NOT import the receipt when the reachable " + "origin returns Authentication failed (HTTP 401); accepting " + "the registered task would advertise delivery readiness for a " + f"target head that cannot be confirmed against origin; " + f"stdout={result.stdout}") + self.assertEqual(self.card.read_bytes(), card_before, + "the registered task card must not be updated with " + "receipt_sha256 when the resume refuses to import the receipt") + self.assertEqual(receipt.read_bytes(), original, + "the receipt file must be byte-for-byte unchanged when the " + "resume refuses to import it") + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head, + "the task HEAD must not move when the resume refuses to " + "import the receipt") + self.assertNotIn(receipt_sha, self.card.read_text(), + "the registered task card must not embed the receipt sha256 " + "when the resume refuses to import the receipt") + + def test_local_precheck_rejects_reachable_auth_failure(self): + """Standalone: capture a valid TASK_WORK_MODE=local precheck + receipt against the real bare remote (origin is reachable and + serves refs/heads/main normally), then point the worktree at a + disposable wrapper that makes `fetch` and + `ls-remote origin refs/heads/main` both fail with explicit + "Authentication failed (HTTP 401)" and run `task precheck` in + local mode. + + The wrapper proves the wrapper was actually used (configured + marker, fetch-auth-failure, ls-remote-auth-failure). The + precheck must NOT yield the local-continuation positive signal: + it must refuse with REMOTE_UNVERIFIED, must not advertise + LOCAL_READY, and HEAD must remain untouched. + + Currently RED on current code: when fetch and ls-remote both + fail (auth error), `target_history` sees an empty listing and + short-circuits to "ok", so the unreachable-fallback branch falls + through to LOCAL_READY even though the authoritative target + head cannot be confirmed. This test records the expected red + result so the post-fix implementation refuses the auth-failure + scenario instead of silently promoting the registered task to + local continuation. + + The test owns its own disposable git wrapper so the auth + failure scenario does not leak into the wrapper shared with + the no-cache tests; the existing + test_local_resume_rejects_reachable_auth_failure (unreachable + origin under resume) and test_local_child_resume_rejects_missing_parent_sha + (unreachable origin with a child task missing parent_head_sha) + are preserved.""" + f = self.fixture + head_before = f.git(self.wt, "rev-parse", "HEAD").stdout + # (1) Build the disposable auth-failure wrapper so the wrapper + # intercepts fetch (128, Authentication failed) and + # ls-remote origin refs/heads/main (128, Authentication failed, + # no value served) while everything else delegates to the real + # git. The wrapper's own marker line proves it was loaded. + bin_dir, wrapper_log = self.write_no_cache_wrapper( + f.root / "auth-failure-precheck-bin", "", + log_name="auth-failure-precheck.log", + auth_fail=True) + path_env = f"{bin_dir}{os.pathsep}{f.env.get('PATH', os.environ['PATH'])}" + wrapper_env = (f.env | {"PATH": path_env} + | {"TASK_WORK_MODE": "local"}) + # (2) Local precheck under the auth-failure wrapper must refuse. + local = f.task(self.wt, "precheck", check=False, env=wrapper_env) + # Proof the wrapper was actually used. + log = wrapper_log.read_text() + self.assertIn("configured auth-failure-mode", log) + self.assertIn("fetch-auth-failure", log) + self.assertIn("ls-remote-auth-failure", log) + # Auth-failure scenarios must assert the wrapper actually + # served the expected (auth) response, not only that the + # wrapper initialized. + self.assertNotEqual(local.returncode, 0, + f"local precheck must reject when fetch and ls-remote both " + f"return Authentication failed (HTTP 401); current code " + f"falls through target_history's empty-listing short-" + f"circuit and reaches LOCAL_READY, returning 0; " + f"stdout={local.stdout}") + self.assertIn("[verdict] REMOTE_UNVERIFIED", local.stdout, + "the precheck must surface REMOTE_UNVERIFIED when the reachable " + "origin returns Authentication failed (HTTP 401) instead of an " + "advertised target head; the post-fix implementation must not " + "promote the registered task to local continuation under an " + f"authoritative-but-unverifiable remote; stdout={local.stdout}") + self.assertNotIn("[verdict] LOCAL_READY", local.stdout, + "the precheck must NOT advertise LOCAL_READY when the " + "reachable origin returns Authentication failed (HTTP 401); " + "accepting the registered task would advertise local " + "continuation for a target head that cannot be confirmed " + f"against origin; stdout={local.stdout}") + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before, + "the task HEAD must not move when the precheck refuses to " + "advertise LOCAL_READY") + + def test_local_outage_rejects_cached_target_diverged_from_r0(self): + """Origin transport is unavailable and the cached origin/main does not + descend from the registered R0. Local precheck must surface + REMOTE_UNVERIFIED and local resume must not import the receipt, instead + of continuing on a cache that already contradicts the task baseline.""" + f = self.fixture + receipt = self.capture() + original = receipt.read_bytes() + card_before = self.card.read_bytes() + r0 = self.local_authorization_scope()["r0"] + divergent = f.git(self.wt, "commit-tree", r0 + "^{tree}", "-m", + "Unrelated cached target").stdout.strip() + f.git(self.wt, "update-ref", "refs/remotes/origin/main", divergent) + self.assertEqual(f.git(self.wt, "merge-base", "--is-ancestor", r0, divergent, + check=False).returncode, 1) + f.git(self.wt, "remote", "set-url", "origin", str(f.root / "missing.git")) + head_before = f.git(self.wt, "rev-parse", "HEAD").stdout + + local = self.precheck() + self.assertNotEqual(local.returncode, 0, local.stdout) + self.assertIn("[verdict] REMOTE_UNVERIFIED", local.stdout) + self.assertIn("rewound or diverged", local.stdout) + self.assertNotIn("[verdict] LOCAL_READY", local.stdout) + + resumed = self.resume(receipt) + self.assertNotEqual(resumed.returncode, 0, resumed.stdout) + self.assertNotIn("[resumed]", resumed.stdout) + self.assertEqual(self.card.read_bytes(), card_before) + self.assertEqual(receipt.read_bytes(), original) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before) + if __name__ == "__main__": unittest.main()