-
28-followups §3.1。**审计结论与那条待办原来的写法不一样,两列要分开处理。** ## source_kind:删 与 origin 一一映射(global_agent↔builtin / company_hired↔created), 实查生产库 76 行零例外。全仓唯一读侧是 migrate() 里那条 origin 回填, 而那是给「origin 列出现之前写下的行」用的一次性迁移。 顺序:**先回填、再删列**,两步都用 hasColumn 守着(幂等)。反过来做不报错 —— 库能开、测试能过,只是那几行 origin 悄悄是错的。守卫 source-kind-drop-migration.test.js 走存量库重启那条路,专门钉这个顺序, 反向验过:弄反立刻红。 写侧散在 3 处生产 INSERT + 35 个测试文件。批量改的时候正则误伤了 4 处 (吃掉了 JS 默认参数、位置占位符、ON CONFLICT 的赋值行),逐个修回并复查了 diff —— **这种跨几十个文件的机械改动,靠「全量跑一遍」兜底比靠正则写得对更可靠**。 ## legacy_path:不能删,它还没死 原待办说「所有读取点都已改成从 id 推导」——那半句对新行成立,对存量行不成立: · 新行 legacy_path 恒等于 system/agents/{id}(确实冗余); · **存量行里存着批次 4 之前的公司侧路径**,而 graph-seed-service 判「悬空行 / 孤儿行」 靠的正是这个路径指向的目录还在不在。从 id 推导出来的路径永远指向 system/agents/, 那条判据会整个失效。 实查生产库:76 行里还有 2 行是老路径(org-planner / org-coder 指着已不存在的 data/companies/mycompany/agents/)—— 正是这条判据要认出来的那种行。 退出条件写进了 graph-db.js 的建表注释(不只留在文档里): 库里不再有 legacy_path <> 'system/agents/' || id 的行。 ## 顺带记一条:getDb() 会就地迁移生产库 删列那次,生产库的列是被一条**查状态用的**一行 node -e 删掉的 —— 服务当时没重启,人也没打算迁移。结果是对的(幂等;与备份逐行核对 76 行一致、 origin 全部按映射回填正确),但「读一下状态」不该顺手改 schema 这件事值得知道。 闸门只挡测试进程,故意不挡普通脚本(挡了服务自己也起不来)。 注释里给了只读的正确姿势。 全量:140 文件 / 1299 测试全绿。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-
## 1. 子 run 转终态不回写父节点 → 任务永久卡死(界面走查发现) 复现:org 节点的子 work run 第一次失败 → 父节点判 fail → 入口节点没有 fail 出边 → blocked → 用户在中栏把子 run 修好跑到 completed → **父节点仍钉在 blocked, root run 也钉住,org 层永远推不下去**。唯一人工出路是对 org 节点点「重试」, 而那会把整张子图重置到下一个 attempt,刚跑完的成果全丢。 根因:propagateToParent 的幂等守卫写的是 `parentNode.status !== 'running' → return`, 判据用错了维度——它想防「同一个结果被结算两次」,实际拦掉的是「子 run 换了个结果」。 subgraph 节点的状态本来就是从子 run 派生的,子 run 换了终态就该重新派生。 修法:新增 reviveParentFromChild,只认「转好」(fail/blocked → success), 且要求 parentNode.child_run_id 正是这个子 run。恢复走 **blocked → ready → running → success 三步合法转换**,而不是给全局放宽 NODE_TRANSITIONS——转换表是给所有调用方的 安全网,不该为一条恢复路径把 blocked → success 对外开放;三条 node.status 事件 也让「这个节点是被救回来的」在 timeline 上看得见。 补测试时才暴露的第二半:父 run 多半也钉在 blocked,而 RUN_TRANSITIONS 没有 blocked → completed,settleRun 在转换非法时是**静默跳过**的。所以复活时要先把 父 run 从 blocked 拉回 running(这条合法),否则节点复活了 run 还是卡着。 ## 2. 边界守卫把并行 agent 的写算到当前节点头上(真 CLI 冒烟的断点) 真跑实测:招聘模板库里没有的角色走 hired_from_base,自动流水线起 agent-creator, 而它的 CLI workDir 就是 system/agents/agent-creator/——一个被 git 跟踪的目录。 它在里面重写自己的 todo.md、删一条 scheduled_tasks,被整仓 git diff 扫到, 归属算到当时正在跑的 work 节点,一个**真正成功**的节点被判 fail。 只要这些运行态文件还被 git 跟踪,每次含 hired_from_base 的招聘都稳定复现。 修法:splitUnattributable 把 agent 运行态文件(todo.md / plan.md / memory.md / _lessons.md / .claude/scheduled_tasks.json)从越界证据里摘出去,单独记一条 unattributable,不参与判罚。摘除范围刻意窄——同目录下的 agent.md / config.json 等源码仍然算越界。 路径提取按「首个空白之前」切而不是固定偏移:diffRepoStatus 已去掉前导空格, porcelain 状态位长度不定(修改类 "M path" 1 字符、未跟踪类 "?? path" 2 字符), 固定 slice(3) 恰好对**修改类**失效——而 agent-creator 改自己 todo.md 正是修改类。 这条单独立了一个回归用例。 ponytail: 这是缓解不是根治。根治要把运行态文件从 git 移出去(27-P3B W13), 本次只保证守卫不再冤枉节点;同一次招聘里 agent 若写了运行态文件之外的源码, 仍会算到当前节点头上。 测试 760/760 全绿(72 文件,起点 753/71):新增 graph-engine-revive-parent.test.js 4 条 + graph-boundary.test.js 3 条。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>