|
| 1 | +# Dyro missing-origin / 诚实性 / 任务关闭 会审 |
| 2 | + |
| 3 | +Date: 2026-08-31 |
| 4 | + |
| 5 | +Scope: 未发布开发线的 doctor/next 行为、`task create` 多仓与门禁、`events.jsonl` 轮转、`--dry-run` / 复核退出码、`task close` 与 leftover receipt。不是 `task review` PASS,不是 Proof,不是发版。 |
| 6 | + |
| 7 | +SSOT: 当前工作区源码(`feat/dev_0814` 工作树,相对生产基线的未提交变更)。本记录不是交付门。 |
| 8 | + |
| 9 | +## Rules |
| 10 | + |
| 11 | +1. 每位评审员只写自己的签字章节,不改写他人章节。 |
| 12 | +2. 源码和现场契约高于会话里的设计口头约定与同批席位意见。 |
| 13 | +3. 无法证明标 `须人工核`。 |
| 14 | +4. 仲裁只去重、裁定冲突、输出 P0/P1/P2 与 Go/No-Go。 |
| 15 | +5. 本会审不 merge、不 signoff、不发布。commit/push 是用户本轮另授的交付动作,不是会审授权。 |
| 16 | + |
| 17 | +## Frozen Baseline (2026-08-31) |
| 18 | + |
| 19 | +| Object | Ref | SHA | Date | |
| 20 | +| --- | --- | --- | --- | |
| 21 | +| 生产基线 | `origin/main` | `f321e39df4b3994657e483d58e27d191a6765011` | 2026-08-23 | |
| 22 | +| 当前开发线 | `feat/dev_0814`(与 `origin/main` 同 SHA,其上为本波未提交修复) | 同上 + working tree | 2026-08-31 | |
| 23 | +| `origin/feat/dev_0814` | 落后本工作树所基于的 main | `6986f63de6bc65ee0a33cb6dc6d3fbe89368a22d` | — | |
| 24 | +| 当前 `origin/release` | **不存在** | — | — | |
| 25 | +| 历史 `origin/release/v0.6.1` | 历史线,不是当前生产 | `d90835e701658d31e78c196ab252825ead1c96f9` | — | |
| 26 | + |
| 27 | +**不得默认 main 的说明:** 协议默认生产是 `origin/release`。本仓当前没有 `origin/release`,生产线按 `origin/main` @ `f321e39` 记录。`main` 相对该生产基线领先 0 commit。先前 delivery-physics 会审曾用当时的 `origin/main` 当已发布基线;本轮因缺少现行 `origin/release` 继续用 `origin/main`,并显式写出原因。 |
| 28 | + |
| 29 | +`origin/feat/dev_0814` 落后 `origin/main` 22 commit。本波是在已与 `origin/main` 对齐的 `feat/dev_0814` 上的行为修复,不是从过期远端功能分支分叉。 |
| 30 | + |
| 31 | +## 先前会审 |
| 32 | + |
| 33 | +已检索 `docs/reviews/` 与 `docs/superpowers/reviews/`。 |
| 34 | + |
| 35 | +- 同开发线、**同主题**(missing-origin / dry-run 诚实性 / `task close`):**已检索·无先前会审**。 |
| 36 | +- 同仓不同主题:`docs/reviews/2026-08-19-slash-review-and-task-merge.md`(斜杠包装层);`docs/superpowers/reviews/2026-08-15*` 与 `2026-08-16-delivery-physics-shipped-implementation-adversarial-review-board.md`(交付物理 / Proof)。那些 P0/P1 不在本波范围,**不记为本波已闭环**。 |
| 37 | + |
| 38 | +本目录沿用 `docs/reviews/`(与 2026-08-19 记录同一约定)。 |
| 39 | + |
| 40 | +## 席位 |
| 41 | + |
| 42 | +| Seat | Dispatch | Window | Status | |
| 43 | +| --- | --- | --- | --- | |
| 44 | +| security | 第一批派出 | ≥10 min,首派开始计时 | 交卷 ~15.7 min | |
| 45 | +| silent-failure | 第一批派出 | 同上 | 交卷 ~11.3 min | |
| 46 | +| python-cli | 第一批派出 | 同上 | 交卷 ~13.3 min | |
| 47 | + |
| 48 | +无 `逾期未交`。主席在交卷后对席位主张做了源码复核,并修了交卷后仍成立的 P1(见仲裁「交卷后补丁」)。 |
| 49 | + |
| 50 | +--- |
| 51 | + |
| 52 | +# Security Review Section |
| 53 | + |
| 54 | +Reviewer: security |
| 55 | +Time: 2026-08-31 |
| 56 | +Verdict: Conditional Go |
| 57 | + |
| 58 | +P0: none. P1: two (close confused-deputy; merge `--dry-run` mutates the line). Leftover `receipt.md` symlink delete-through and event-archive glob injection: 无 P0/P1 (disproved or fail-closed). |
| 59 | + |
| 60 | +### SEC-P1-01 — `close_task` jail is check/use split; `--force` can retarget the line worktree |
| 61 | + |
| 62 | +Severity: P1. Path: `src/dyro/tasks.py` remove loop vs jail only in validate loop. |
| 63 | + |
| 64 | +Trigger: Task is `failed` (or `done`). Loop 1 sees a real task worktree. Before loop 2, `destination` is replaced with a symlink pointing at the line worktree. Then `task close --yes`. |
| 65 | + |
| 66 | +Impact: Loop 2 did not re-run path jail. `git worktree remove --force` can realpath-match the line worktree. |
| 67 | + |
| 68 | +Disprove attempt: Static symlink-at-mount and symlink-at-task-root are refused. Tests only cover that static case. |
| 69 | + |
| 70 | +Must-fix: Re-apply path jail immediately before `worktree remove`. |
| 71 | + |
| 72 | +### SEC-P1-02 — `task merge --dry-run` is a real merge on the line, not a no-op |
| 73 | + |
| 74 | +Severity: P1. Path: `src/dyro/tasks.py` dry-run merge; `src/dyro/cli.py` `--yes` copy. |
| 75 | + |
| 76 | +Trigger: `dyro task merge <id> --dry-run`. |
| 77 | + |
| 78 | +Impact: Dry-run runs `git merge --no-ff --no-commit` on the line worktree, then abort. Overlay is not written; git is mutated until abort. |
| 79 | + |
| 80 | +Disprove attempt: Product CHANGELOG discloses probe-and-abort. `--help` at seat time still said dry-run does not write Git. |
| 81 | + |
| 82 | +### P2 (security) |
| 83 | + |
| 84 | +- SEC-P2-01 local `run_gates(dry_run=True)` executes argv (documented). |
| 85 | +- SEC-P2-02 leftover receipt unlink is POSIX-safe for symlink delete-through; leftover only deletes `task.directory/receipt.md`. |
| 86 | +- SEC-P2-03 event archives name-scoped and fail-closed. |
| 87 | +- SEC-P2-04 line-branch check does not walk parent symlinks. |
| 88 | +- SEC-P2-05 review binding CLI `failed` ≠ task status `failed`. |
| 89 | + |
| 90 | +Hunts 无 P0: leftover symlink delete-through disproved; event glob injection disproved as P0/P1; static close jail mitigated, TOCTOU was P1-01. |
| 91 | + |
| 92 | +Independent check complete. |
| 93 | + |
| 94 | +--- |
| 95 | + |
| 96 | +# Silent-Failure Review Section |
| 97 | + |
| 98 | +Reviewer: silent-failure |
| 99 | +Time: 2026-08-31 |
| 100 | +Verdict: Conditional Go |
| 101 | + |
| 102 | +任务路径 9 项已闭环。席位交卷时:**开发线 merge/sync 仍把 `probed.append` 放在 `git()` 之后**。无 P0「CLI/overlay 把失败报成成功」。 |
| 103 | + |
| 104 | +### SF-1 — P1(交卷时开放) |
| 105 | + |
| 106 | +位置:`src/dyro/workspace.py` `_merge_line_repositories_locked`。`line merge` / `line sync`(含 `--dry-run`)在 `git merge --no-ff --no-commit` 超时后,`probed.append` 在 `git()` 之后,当前仓可留下 `MERGE_HEAD`。CLI 不会假成功。任务 merge 路径当时已先 append 再 git。 |
| 107 | + |
| 108 | +### SF-2 — P2 |
| 109 | + |
| 110 | +`set_status` 写入 status 后 `append_event` 失败:状态文件已变;overlay 缺 `task_status` 行且在日志仍合法时 `complete=True`。CLI 不假成功。Ledger 有 `event_append_failed` + `error_code`。 |
| 111 | + |
| 112 | +### 已闭环(席位对照当时源码) |
| 113 | + |
| 114 | +1. `set_status` 吞掉 `EventLogError` 无痕迹 — 已闭环(静默无痕迹)。Overlay 完整性见 SF-2。 |
| 115 | +2. 缺 current `events.jsonl` 却有 archive 当成空且 complete — 已闭环。 |
| 116 | +3. `_event_archive_files` 吃进 `.bak` — 已闭环。 |
| 117 | +4. 任务 merge dry-run 超时留下 `MERGE_HEAD` — 任务路径已闭环。开发线路径为 SF-1。 |
| 118 | +5. `close_task` `-d` 测 overlay HEAD — 已闭环(对 `line.branch` 做 ancestor,再 `-D`)。仍非多仓原子。 |
| 119 | +6. 绑定失败 raise 导致监督 UNCERTAIN — 已闭环(`return "failed"` → `ActionStatus.FAILED`)。 |
| 120 | +7. external dry-run 执行门禁 argv — 已闭环。 |
| 121 | +8. cwd-git 走到 overlay — 已闭环。 |
| 122 | +9. 重复 `diff-check` 名 — 已闭环。 |
| 123 | +10. leftover receipt symlink 跳过 — 已闭环(`unlink` 链接本身)。 |
| 124 | + |
| 125 | +Independent check complete. |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +# CLI Contract Review Section |
| 130 | + |
| 131 | +Reviewer: python-cli |
| 132 | +Time: 2026-08-31 |
| 133 | +Verdict: Conditional Go |
| 134 | + |
| 135 | +五条指定契约在写路径上成立。交卷时剩下的是 CLI 诚实性缺口。 |
| 136 | + |
| 137 | +### 交卷时 P1 |
| 138 | + |
| 139 | +1. 全局 `--dry-run` 帮助仍声称不写 Git,但 `task merge --dry-run` 会真实 `git merge`。`src/dyro/cli.py` `_add_common`。 |
| 140 | +2. `task create --dry-run` 不执行空 adapters 拒绝。空 adapter 检查在 dry-run early-return 之后。 |
| 141 | + |
| 142 | +### 交卷时 P2 |
| 143 | + |
| 144 | +- `task merge --dry-run --push` 文案声称并推送,push 探测被 `git(..., dry_run=True)` 跳过。 |
| 145 | +- `task loop` / `task daemon` 对复核 `"failed"` 不非零退出。 |
| 146 | +- 事件归档 `str.isdigit()` 会把 Unicode 数字当归档名。 |
| 147 | +- `done` 关闭的 dirty / ancestor 门没有自动化测试。 |
| 148 | +- 轮转测试有一句恒真断言。 |
| 149 | + |
| 150 | +### 已闭环(席位) |
| 151 | + |
| 152 | +1. `--dry-run` 不写 Dyro overlay。 |
| 153 | +2. 本地 gate dry-run 仍执行 argv;外部 dry-run 不执行 argv。 |
| 154 | +3. `task review` 拒绝(含 binding mismatch)非零退出;不 `set_status(failed)`。 |
| 155 | +4. 受监督 apply 把 `"failed"` 映射为 FAILED 而非 UNCERTAIN。 |
| 156 | +5. `task close`:done 要干净且已是 `line.branch` ancestor;failed 用 `--force`;拒绝 symlink mount。 |
| 157 | +6. 空 `verify` 每仓唯一门禁名;空 adapters 在写路径拒绝。 |
| 158 | +7. 事件轮转 `events.jsonl.<seq>`;忽略 `.bak`;缺 current 仍缝归档。 |
| 159 | + |
| 160 | +Independent check complete. |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +# Chair independent checks |
| 165 | + |
| 166 | +主席在席位交卷后对照**当前**源码复核,不把席位票当事实。 |
| 167 | + |
| 168 | +1. 无现行 `origin/release`。生产基线 = `origin/main` `f321e39`。已确认。 |
| 169 | +2. `close_task` 静态 symlink / 任务根 symlink:源码 `_assert_task_worktree_path` 沿 leaf→root 拒绝 symlink,且 resolved 必须严格落在 worktree_root 下。测试 `test_task_close_refuses_symlink_mount_and_keeps_line_worktree`、`test_task_close_refuses_symlinked_task_root` 绿。 |
| 170 | +3. SEC-P1-01 双循环 TOCTOU:交卷时成立。交卷后 mutate 循环在 `worktree remove` 前再次 `_assert_task_worktree_path` + `_validate_task_worktree`。同源进程在两次循环之间替换路径会被第二次 jail 拦住。仍不是跨进程锁。 |
| 171 | +4. SEC-P1-02:本波产品契约就是 merge `--dry-run` 做真实 `--no-ff --no-commit` 探测再 abort(CHANGELOG / README 已写)。这不是「声称没跑 Git」。`--help` 交卷后已改成与 README 一致。不升为 P0,不阻断本波 commit。 |
| 172 | +5. SF-1 开发线 merge:交卷时成立。交卷后 `workspace.py` 改为先 `probed.append` 再 `git()`。测试 `test_line_merge_timeout_still_aborts_merge_head` 绿。 |
| 173 | +6. `task create --dry-run` 空 adapters:交卷后检查移到 early-return 之前。同一测试覆盖写路径与 dry-run,均 exit 2。 |
| 174 | +7. 事件归档 Unicode digit:交卷后 `suffix.isascii() and suffix.isdigit()`。 |
| 175 | +8. 恒真断言:交卷后改为 `records[0]["seq"] == 1`。 |
| 176 | + |
| 177 | +## 交卷后补丁(相对席位读到的树) |
| 178 | + |
| 179 | +| 项 | 动作 | |
| 180 | +| --- | --- | |
| 181 | +| SF-1 开发线 merge 超时 | `workspace.py` 先登记 probed | |
| 182 | +| python-cli P1 空 adapters dry-run | `cmd_task_create` 先拒绝再 dry-run return | |
| 183 | +| python-cli P1 `--help` | `--dry-run` 帮助与 README 对齐 | |
| 184 | +| python-cli P2 Unicode 归档名 | `isascii()` | |
| 185 | +| python-cli P2 恒真断言 | 断言 seq 从 1 起 | |
| 186 | +| SEC-P1-01 remove 前再 jail | mutate 循环再次校验路径 | |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +# Final Arbitration |
| 191 | + |
| 192 | +Arbiter: 会审主席 |
| 193 | +Time: 2026-08-31 |
| 194 | + |
| 195 | +Final verdict: **Go for commit + push `feat/dev_0814`。No-Go for merge / tag / release / 生产。** |
| 196 | + |
| 197 | +会审 Go 不构成 merge、signoff、发版。用户本轮另外授权了 commit 与 push。 |
| 198 | + |
| 199 | +## P0 |
| 200 | + |
| 201 | +无。没有已证实的「本 CLI 在操作员未确认时会自己 push / 发布 / 删掉开发线 worktree(静态路径)」路径。SEC-P1-01 的静态 symlink 已拒绝;交卷后 remove 前再 jail。同用户进程 TOCTOU 降为残余 P2:同一用户已能直接删 line worktree。 |
| 202 | + |
| 203 | +## P1(本波已闭环) |
| 204 | + |
| 205 | +1. 未 push 的 `origin/<line.branch>` 不再把 next/start 卡成 needs_repair;doctor WARN。 |
| 206 | +2. `line spawn` 从父线本地 HEAD 起。 |
| 207 | +3. `task create --repository` 可重复;Profile `verify` 进门禁;空 verify 使用 `diff-check-<repo_id>`。 |
| 208 | +4. leftover `receipt.md` 在新一轮执行开始时删除(含 symlink)。 |
| 209 | +5. `events.jsonl` 按 `events.jsonl.<seq>` 轮转;读者忽略非 seq 后缀;缺 current 仍缝归档。 |
| 210 | +6. `set_status` 在事件追加失败时保留状态写入并 ledger `error_code`。 |
| 211 | +7. `task review` 绑定/哈希不匹配返回 `failed`,CLI 非零,监督层 FAILED 而非 UNCERTAIN;任务可留在 `review` 以便改 `review.md`。 |
| 212 | +8. 外部 Profile 的 gates `--dry-run` 不在本机执行 argv。 |
| 213 | +9. `task close` 拒绝 symlink mount / 任务根 symlink;done 要求干净且已合入 `line.branch`;failed 才 `--force`;remove 前再校验路径。 |
| 214 | +10. 任务 merge 与开发线 merge 的 dry-run 探测:先登记 probed,超时仍 abort `MERGE_HEAD`。 |
| 215 | +11. 空 adapters 在写路径与 `--dry-run` 均拒绝。 |
| 216 | + |
| 217 | +## P1(已接受残差,不阻断本波 push) |
| 218 | + |
| 219 | +- **Merge `--dry-run` 会短暂改 line 的 index / `MERGE_HEAD` 再 abort。** 这是本波明确的冲突探测契约,不是静默成功。抛开探测改成 `merge-tree` / 一次性 clone 是后续产品选择,不是本波 P0。 |
| 220 | + |
| 221 | +## P2 |
| 222 | + |
| 223 | +- overlay 在 `event_append_failed` 后仍可对缺行日志报 complete(CLI 不假成功)。 |
| 224 | +- `task loop` / `task daemon` 对 review `"failed"` 仍退出 0。 |
| 225 | +- `task merge --dry-run --push` 不真正预检 push。 |
| 226 | +- done 关闭的 dirty / 未合入路径缺专项测试(实现已有门)。 |
| 227 | +- 本地 gates `--dry-run` 仍执行 argv(已文档化)。 |
| 228 | + |
| 229 | +## Go / No-Go |
| 230 | + |
| 231 | +| 对象 | 结论 | |
| 232 | +| --- | --- | |
| 233 | +| 提交本波到 `feat/dev_0814` | **Go**(用户本轮授权 commit) | |
| 234 | +| 推送到 `origin/feat/dev_0814` | **Go**(用户本轮授权 push;push ≠ 发布) | |
| 235 | +| 合并进 `origin/main` / 打 tag / 发版 | **No-Go** | |
| 236 | +| 用本会审代替 `task review` / Proof | **No-Go** | |
| 237 | + |
| 238 | +## 测试证据 |
| 239 | + |
| 240 | +命令(无 pipeline 吞退出码): |
| 241 | + |
| 242 | +```text |
| 243 | +.venv/bin/python -m unittest tests.test_review_remediation |
| 244 | +``` |
| 245 | + |
| 246 | +摘要:`Ran 26 tests in 13.826s` / `OK`(交卷后、remove 再 jail 之前的全模块)。 |
| 247 | + |
| 248 | +```text |
| 249 | +.venv/bin/python -m unittest tests.test_review_remediation tests.test_tasks tests.test_workspace tests.test_events tests.test_cli.StartTests tests.test_blueprint tests.test_continuation_supervision tests.test_hub |
| 250 | +``` |
| 251 | + |
| 252 | +摘要:`Ran 215 tests in 109.964s` / `OK` / exit 0。含 remove 再 jail 之后的工作树。stdout 中有预期的 `错误:任务 T1 复核拒绝` 与 `错误:未配置任何 Agent adapter,无法创建任务`(断言非零退出)。 |
| 253 | + |
| 254 | +未执行:完整 `tests/` 全仓、CI、真实多仓工作区上的 `task close` 对抗替换。标 **未执行**。 |
| 255 | + |
| 256 | +`test_legacy_board_status_stays_on_protocol_id` 不在本命令内。历史会话曾在隔离运行时看到 JSON `UNSAFE_FILE`;本波未改 integrations,**须人工核** 是否为环境问题。 |
| 257 | + |
| 258 | +## 须人工核 |
| 259 | + |
| 260 | +1. Windows junction:`Path.is_symlink()` 对 junction 可能为假。本波在 macOS 上验证。 |
| 261 | +2. 真实 `subprocess.TimeoutExpired` 杀 git 子进程后,abort 是否总成功。测试用「merge 返回后再抛」模拟。 |
| 262 | +3. 把 merge dry-run 改成完全不碰 line worktree,是否值得下一刀产品变更。 |
| 263 | + |
| 264 | +## Next human Dyro command |
| 265 | + |
| 266 | +无发明。本会审不查询未命名工作区的 `dyro next`。交付动作是用户已授权的 commit + push `feat/dev_0814`。 |
0 commit comments