• 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>
    yibo authored
     
    Browse File »

  • ## 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>
    yibo authored
     
    Browse File »