Commit 5a31d006f6b7a56671ee86a2106bfc29508563e9
1 parent
06fbc9ff
docs(27-P3C): 补齐交接文档里留空的 3 条复核结论
lemonboss-68 写交接文档时只确知 1 条,其余 3 条刻意留「待补」而不是 凭二手转述硬写 —— 那个判断是对的,记错了反倒会变成新的事实来源。 这里由批 3 作者按 17:00 那次对抗复核的原文补齐,每条都有探针实跑证据。 四条: - N1 🔴 取消排队留下还在烧 token 的孤儿子 run(唯一有直接金钱成本的) - L1 ⚠️ 恢复路径对 manual 不成立(调度器故意不碰 manual,于是坑空着没人接) - N2 🟠「退回队列」对用户完全不可见(200 + 按钮全灰 + 图消失 + 一句假提示) - N3 🟠 isSlotRaceError 的判据注释被探针证伪,且吞真错误不打日志 其中 3 条是同一个模式:注释承诺的前提被本轮自己的改动改没了,注释还留着。 27-P3C §十三.3 / §十四.4 已就这个模式立过两次案,这是第三次。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Showing
1 changed file
with
36 additions
and
5 deletions
docs/todo/system-design/27-p3c-batch3-handover.md
| ... | ... | @@ -36,11 +36,42 @@ main 现在躺着一份**已知有缺陷**的代码,而那笔提交的说明 |
| 36 | 36 | |
| 37 | 37 | **已知缺陷(来自 17:00 的对抗复核):4 条 —— 1 条半修 + 3 条新引入。** |
| 38 | 38 | |
| 39 | -本文档作者**只确知其中 1 条**,其余 3 条的准确描述以 lemonboss-78 / lemonboss-89 手上的复核结论为准, | |
| 40 | -不要以本文档为准: | |
| 41 | - | |
| 42 | -- ⚠️ **「取消排队」会留下一个还在烧 token 的孤儿子 run**。这条有实际成本,优先级最高。 | |
| 43 | -- 其余 3 条:**待补**(本文档作者没拿到原文,刻意不复述二手转述,避免记错了反倒变成新的事实来源)。 | |
| 39 | +> **2026-08-19 由 lemonboss-78(批 3 作者)补齐**:以下 4 条是 17:00 那次对抗复核的原文结论, | |
| 40 | +> 每条都有独立探针实跑证据。本文档作者原先只确知第 1 条、其余刻意留空——那个做法是对的。 | |
| 41 | + | |
| 42 | +**N1 🔴「取消排队」会留下一个还在烧 token 的孤儿子 run**(优先级最高,唯一有直接金钱成本的一条) | |
| 43 | +`company.js:1768` 的注释说「对一条还没开跑、没有子 run 的排队 run,`stopRun` 是多余的一圈」。 | |
| 44 | +**这个前提被批 3 自己的 `requeueBlockedRootRun` 改没了** —— `run_status='queued'` 的 run 现在可以是 | |
| 45 | +跑过一半的(有 success 节点、有 ready 节点、有活着的子 run)。探针实跑:取消后 root=stopped / | |
| 46 | +task=cancelled,而节点残留 `aa2:ready, aa1:success`(没批量 skipped),子 run 残留 | |
| 47 | +`paused / stop_requested=0`(没走 `suspendRunTree`)。 | |
| 48 | +失败路径:预算撞顶时子 run 可能仍是 `running`(引擎只把 root 置 blocked,子 run 要到节点边界才落 paused)→ | |
| 49 | +人点「启动」→ 退回队列 → 改主意点「取消排队」→ **那条 running 子 run 既没收到停止信号也没被结算,继续调 agent**。 | |
| 50 | +前端 `goal-task-model.js:171` 的按钮 title 还在说「它还没开跑,所以不会有半截产物」。 | |
| 51 | +命中入口三个:`/tasks/:id/cancel` 的 queued 分支、blocked 分支①、`DELETE /tasks/:taskId`。 | |
| 52 | + | |
| 53 | +**L1 ⚠️ 半修 · 恢复路径对 manual 任务不成立** | |
| 54 | +批 3 的修法是「恢复撞坑满 → 退回队列」,对 **auto 真的成立**(探针:T2 收敛后 T1 无人干预自动跑起来)。 | |
| 55 | +但 `tickInner` 第 4 步 `filter(t => t.execution_mode === 'auto')` 故意不碰 manual(§五.2bis「manual 不堵队」), | |
| 56 | +于是 manual 任务:人点「启动」→ 退回队列 → 坑空了 → **没人来接**,任务躺着、sweep 每 60s 空转。 | |
| 57 | +而 `graph-engine.js:2345` 的注释白纸黑字写着「**「T1 永远回不来」这个后果因此消失**」——**对 manual 不成立**。 | |
| 58 | + | |
| 59 | +**N2 🟠「退回队列」这件事对用户完全不可见** | |
| 60 | +`startRunSync` 早返回 `{ok:true, runStatus:'queued', requeued:true}` 走 **200**。 | |
| 61 | +`GraphRunPane.act()` 只在 `!r.ok` 时写 `err` → 界面零提示;`can('start')` 因 `queued ∉ ALLOW.start` 把按钮全部置灰; | |
| 62 | +task 翻 queued 后 `hasStartedRun()` 返回 false → `GraphConsole.vue:248` 把 run 面板换成 org graph **定义视图**, | |
| 63 | +提示语「节点状态一律 pending」**是假的**(这条 run 有 success 节点、有活着的子 run)。 | |
| 64 | +`requeued` 这个字段**全前端零处读取**(`grep -rn requeued web/src` 无命中)。 | |
| 65 | +用户点了「启动」,得到的是:什么都没说、按钮全灰、图不见了。 | |
| 66 | + | |
| 67 | +**N3 🟠 `isSlotRaceError` 第三条判据的注释被探针证伪,且它吞真错误还不打日志** | |
| 68 | +`work-graph-db.js:110` 声称 `InvalidTransitionError` = 「这条 run 已被别人从 queued 推走了」。 | |
| 69 | +实跑证伪:被抢走后再 CAS **返回 0,不抛异常**;真正抛 `InvalidTransitionError` 的是**调用方声明了非法转换** | |
| 70 | +(`expected = from` 是调用方声明值,`canTransitionRun` 判的是 `expected → to` 而不是 `cur.run_status → to`)。 | |
| 71 | +所以这条判据把「调用方写错了转换」当成竞态吞掉,且不留日志。 | |
| 72 | + | |
| 73 | +> 这 4 条里有 3 条是同一个模式:**注释承诺的前提被本轮自己的改动改没了,注释还留着**。 | |
| 74 | +> 27-P3C §十三.3 / §十四.4 已经就这个模式立过两次案,这是第三次。 | |
| 44 | 75 | |
| 45 | 76 | > 提交说明里的「126 文件 / 1201 用例全绿」**需要更正**:这句话准确描述了 16:54 那次测试运行, |
| 46 | 77 | > 但它压在一份尚未通过复核的代码上,读起来像质量背书,替 lemonboss-78 说了它没说过的话。 | ... | ... |
-
mentioned in commit 60095724e2c9cbf399d8422e837c71e9630a59f1