• ## 起因
    
    e2e-demo 真实任务卡住:org-planner-sdd 自己判 input_underspecified
    并点名 org-planner-prd,但这张图里 sdd 没有任何 fail 出边——精确匹配、
    兜底边两条路都接不住,节点直接 blocked,靠人工点了「重试」才续上。
    
    ## 根因
    
    fail-fallback-edge 校验(27-P3/P4 §三.7 第五项)只在节点已经画了别的
    fail 边时才检查兜底边齐不齐。一条 fail 边都没有的节点完全不受约束——
    workgraph-planning/agent.md 原文明说这是「合法设计」。但系统里所有本体
    共用 _base 的四段式协议,VERIFY 阶段任何 agent_call 节点都可能自判
    失败,不止专职判词节点。四个骨架模板审计后发现 6 个中间节点存在同一个
    漏洞(sdd 只是撞上的那一个)。
    
    ## 决策
    
    除图的唯一入口节点外,每个节点必须留一条兜底边(判据是 forward 入度,
    不是节点名字模式)。org 层暂不动——node 全是 subgraph 类型,失败来源
    机制不同,且套用同一条规则会让三个 org 模板立刻不合法,属于 27-p3b
    「验收9」还没收口的链路,留给那边一起做。
    
    中途发现无条件收紧会炸 46 个用例/13 个文件(createWorkGraph 被大量
    测试当成建最小图的通用工具)。改法是加 strict 开关,默认关闭(行为
    不变),只在 workgraph-planning 真实提交图落库的那个唯一入口
    (createWorkGraphSafely)打开。
    
    ## 改动
    
    - work-graph-db.js / graph-engine.js:新规则 + strict 开关
    - planner.json / executor-fanout.json:补 6 条兜底边
    - workgraph-planning/agent.md:约束三等四处措辞同步
    - 新增单测:strict 模式 7 例 + 真实链路回归用例(真实引擎代码+真实
      DB+只换最外层 CLI 子进程,复现 sdd 自判失败后引擎自动路由到 prd,
      全程零 retryNode 调用)——比重跑一次真实任务更严格,可回归、进 CI
    
    978/978 单测通过。需求与决策记录见
    docs/todo/system-design/31-work-graph-fail-fallback-coverage.md。
    
    Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
    yibo authored
     
    Browse File »

  • 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 »
  • ## 验收 4:从「静态成立」变成真的跑过一次
    
    原状态是 🟡「静态成立,未真跑」—— 人读代码把
    fixture verdict → FAILING_VERDICTS → readNodeProduct → callAgentNode 判 fail
    这条链读通了,但没有任何一次真跑或真链路证据。
    
    现在 graph-real-chain-smoke ⑥ 走完整条:fixture 产出 verdict="attributed"
    → 节点 fail、fail_reason=input_underspecified、沿 fail 边点亮下游、
    判词落在**下游节点**行上(判词的载体是「点亮它的那次状态转换」,不是失败节点自己)。
    反向验过:verdict 改成 passed 立刻红。
    
    写 fixture 时踩到一条值得记的:fail_reason 是封闭域 ATTRIBUTION_REASONS,
    第一版编了个 contract_invalid,被**按设计降级成 null** 并留一条 timeline ——
    引擎不认识的归因一律不信。判据没错,是 fixture 错。
    
    ## 验收 2 / 8:由 2026-08-20 那次真跑验掉(§六ter)
    
    · 2 —— 19 次真 agent 调用之后 `git status --porcelain` 完全为空,交付物都在项目根内。
      这正是 §六bis「后半被并行 agent 打破」那条的干净环境重验。
    · 8 —— 判据取「它引用了只可能来自提示词的内部 id」:triager 判词原文写着
      「org-coder-impl 私有目录 rn_96a8cd696dcc 下只有 _research.md」,
      rn_96a8cd696dcc 是 graph_run_nodes.id,agent 没有别的途径知道它。
    
    ## 验收 9:产出端通了,消费端没等到那个形状
    
    真跑里 triager **确实产出了两半地址**(orgNode + workNode)——
    这一条本身有分量:27-P3/P4 §八.22 第 13 处记的正是「引擎四环全通,
    但产出方 agent.md 里根本没有这个键」,那半条现在通了。
    
    紧接着「忽略跨层地址」那条**不是缺陷,是判据在正常工作**:这次回退发生在
    work 层内部,edge.to 本身就是 agent_call 节点,已是最细粒度,没有跨层可言。
    缺的是**那个形状**:一次 org 层回退边、且目标是 subgraph 节点。
    
    别把「机制没验」和「机制没有」混为一谈 —— 定点重置的逻辑侧有
    graph-cross-layer-address.test.js 三例注入式覆盖,缺的只是真跑里发生过一次。
    
    ## 顺带
    
    · template-verdicts 的样本非空那条补 ENOENT 容忍。它扫**活目录**,
      而 graph-real-chain-smoke 会在 system/agents/{aid}/{cid}/ 下建了又清运行态 ——
      文件在「列出名字」和「读它」之间消失是正常的。walk() 与 violations() 早就容忍了,
      只有这一处漏了,实测偶发红过一次。**扫活目录的守卫,每一处读都要容忍 ENOENT。**
    · 真跑 timeline 里发现一条未修的:每个节点的产物索引写入都撞
      UNIQUE idx_graph_files_path 失败并被降级成日志(重试时同路径再写一次)。
      后果是 files.json 产物索引在有重试的 run 上静默不全 —— 归 P4 一起处理,记在 §六ter。
    
    全量:139 文件 / 1297 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • agent_call 与 subgraph 是**两段不同的默认实现**(defaultAgentExecutor /
    defaultPlanWorkGraph),各有各的 onAcpEvent 累加、各有各的守卫开关分支。
    上一轮只覆盖了前者,「规划子图」那半条链仍然是零真实覆盖。
    
    现在走完:defaultPlanWorkGraph → AgentManager → ClaudeCliProtocol(mock)
    → extractPlannedGraph 解析回复 → createWorkGraphSafely → 建子 run。
    
    断言两处假 planner 天然绕开的接线:
    · 子 run 的定义确实来自 fixture 那张图(节点名 / systemAgentId 逐个对)
    · 规划那一轮烧的 token 也进了预算 —— 走的是 defaultPlanWorkGraph 自己那条
      onAcpEvent,与 agent_call 那条不是同一段代码
    ③ 的守卫断言补上 subgraph 分支的正反对照。
    
    新增 fixture workgraph-planning.json:图 JSON 同时放进 chunks 与 result
    (mock 下 output 取哪一半由 AgentManager 决定,不该由 fixture 来赌)。
    
    反向验过:抽掉 fixture 里的图 JSON、或把它的 usage 归零,对应用例分别变红。
    
    **覆盖边界写在用例注释里**:跳过 resolvePlanningAgentName 里的按需 hire
    (预塞一行 company_agents)—— hire 要一条真实公司记录,那条路径另有
    company-hire-org-layer.test.js 盯着;这条 smoke 验的是规划链路,不是招聘。
    
    全量:139 文件 / 1296 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »
  • ## 一、W7「零边图」不是待办 —— 它早修了,是注释在骗人
    
    `company-service.js` 的判据注释仍写着「判据用**有没有边**」,并留着一段
    「已知残留:单 org node 的最小图没有边,判据③不生效……真跑到那一步再看」。
    
    **那个残留在 `58a2865`(08-17)就修掉了**:`orgGraphMembership` 现在的主判据是
    `authored_by='planner'` 的节点存在(「orggraph-planning 提交过一张图」的直接证据,
    零边也认),边数降为存量行的回落判据;守卫 `company-hire-org-layer.test.js` 的
    `CO_ZEROEDGE` + `W7-fix` 两条盯着。
    
    注释没跟着改,于是它把读的人**引向一个已经修好的缺陷**去重修一遍 —— 我就是这么被
    带偏的:读到那段注释就没再往 `orgGraphMembership` 里跟,还据此往待办清单里写了一条。
    这是「指路牌失效」那一族,和 CLAUDE.md 死链同型,只是方向相反(那边指向不存在的东西,
    这边描述已不存在的洞)。
    
    判据的措辞也改了:**「成型」的判据在 graph-service.orgGraphMembership(),不在这里**,
    免得下一个人照注释理解。P3B 文档与 docs/README.md 的对应条目一并更正。
    
    ## 二、还上「真实路径 smoke 进 CI」那笔欠账的一半(27-P3/P4 §十 硬规矩 2)
    
    原话是「凡是靠注入点验的能力,都要有一条真实路径的 smoke 进 CI 才算验收完成 ——
    这条欠账仍未还」。此前唯一走真实路径的 `scripts/real-pm-smoke.js` 要真 API 额度、
    人工跑、而且验的是 DevLoop 不是图执行。
    
    做法是把「真 LLM」和「真代码路径」拆开:**只把最外层的 CLI 子进程换成 fixture 回放**,
    setAgentExecutor / setPlanWorkGraph 一个都不调。链路完整走
    advanceOne → defaultAgentExecutor → AgentManager → ClaudeCliProtocol
    → onAcpEvent → 落盘 → readNodeProduct → settleNode → spendBudget → 边界守卫。
    
    三条断言各盯一处**假 executor 天然绕开**的接线:
    · ① mock/files 真的落到节点产物目录(假 executor 直接 return,这一环整个不存在)
    · ② token 用量真的从 session/result.usage 数进预算 —— **就是 §六 验收 10 卡了两轮的那一维**
    · ③ boundaryGuardApplicable 在这条路径上为真(注入假 executor 时它恒为假,
         那条唯一有实际后果的分支实际零覆盖);带反向对照,注入后必须变假
    
    反向验过:断掉①的落盘、断掉②的累加,对应用例分别变红。
    
    为此改了一处 mock:session/result 的 usage 原来硬编码 {input:0,output:0},
    现在 fixture 可指定(缺省仍是 0,既有 fixture 行为一个字不变)。
    **不改这一处,token 那一维在 CI 里永远产不出数** —— 验收 10 当初就是卡在这儿。
    
    **没还清的部分写在文档里,别当成还清了**:真 LLM 仍进不了 CI;
    subgraph 那一支(defaultPlanWorkGraph)仍只有注入式覆盖;
    多节点 / 回退边 / 熔断没进这条 smoke,它只推一个节点。
    
    afterAll 清掉真实链路建出的公司态目录(gitignore 挡着不进库,但留着会让别的
    用例遍历 system/agents/ 时多扫一份)。
    
    全量:139 文件 / 1295 测试全绿。
    
    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
    yibo authored
     
    Browse File »