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