# 47 · `acceptance.md` 结构性产不出来,itemized 子任务物化形同虚设 > **状态**: ✅ **已修复**(2026-09-02)。按下面「三、修法」实现:`maybeMaterializeItemizedSubtasks` > 新增 `findGoalBenchmarkFile`/`parseGoalBenchmarkDimensions` 兜底路径,`acceptance.md` 缺失时 > 改读共享 `artifacts/goal-benchmark.json` 的 `dimensions[]`。单测见 > `server/tests/unit/task-decomposition-materialize.test.js`(5 条,2 条新增) > **日期**: 2026-09-02 > **发现场景**:[46 号文档](./46-mandatory-planning-and-task-decomposition.md) Phase 0 的真实端到端验证——新建 `p0verify0901` 公司,走一条真实 `itemized` 任务(贪吃蛇游戏,真实 Claude CLI,非 mock),全程 19 分钟零回退零报错收敛到终态,但子任务物化了 0 条 > **与 43/44 号文档的关系**:43 号文档设计了物化机制本身,44 号文档修的是产出闸门误判 `goal-benchmark` 没产出;本文档修的是**物化机制依赖的产物本身在当前常见子图形状下从来不存在**——三份文档各自独立,但环环相扣:44 号文档修完之后,`goal-benchmark.json` 才第一次被证实能稳定产出,本文档才有了一个现成的兜底数据源可用 --- ## 一、现象 46 号文档 Phase 0 真实回归的结果(详见该文档「Phase 0 实现记录」): | 检查项 | 结果 | |---|---| | task-planning 判定 | `itemized`(未诱导) | | `goal-benchmark`/`planner-validator` 是否真正跑完 | ✅ 两者都 success(44 号文档修复生效) | | `acceptance.md` 是否产出 | ❌ 全程未产出 | | 子任务是否物化 | ❌ 0 条 | | 父 task 终态 | `completed`(按 holistic 规则收敛,不是设计中的"全子任务 completed 才 success") | 系统自己检测到了这一点,timeline 里有一条明确记录: ```json {"kind":"node.output","summary":"task.acceptance_mode='itemized' 但没找到 acceptance.md,跳过子任务物化(仍按 holistic 收尾)","payload":{"degraded":"acceptance_md_missing"}} ``` **这不是这次真跑运气差**——是结构性打不通,见下面根因。 --- ## 二、根因 `org-planner/agent.md` 硬约束第 3 条要求它交四份产物:`brd.md`/`prd.md`/`sdd.md`/`acceptance.md`。但 org 层节点全是 `nodeKind='subgraph'`(无一例外),org-planner 实际执行靠 `workgraph-planning` 现场规划出的子图。 本次真实回归里,`workgraph-planning` 规划出的子图是: ``` org-planner-prd → org-planner-sdd → org-planner-benchmark → org-planner-validator ``` **没有 brd-planning,也没有任何一个节点的职责是写 `acceptance.md`。** `org-planner-benchmark`(本体 `goal-benchmark`)产出的是 `goal-benchmark.json`——R-19-B 契约的 `benchmark` 段(44 号文档处理的那套机制),是一份结构化的验收维度清单,不是 `acceptance.md`。这两份产物名字不同、来源不同、格式不同(一个 Markdown 表格、一个结构化 JSON),历史上就没打算是同一份东西——它们只是恰好都能回答"这个任务怎么算通过"这个问题。 `server/services/graph-engine.js` 的 `maybeMaterializeItemizedSubtasks`(`findAcceptanceMdFile` 全节点扫描)**只认 `acceptance.md` 这一种产物**,扫不到就静默降级为 holistic——设计上是"软失败不拖垮硬产出"(33 号文档的教训:软产物不能当硬闸门),出发点合理,但代价是:只要 `workgraph-planning` 现在这套子图形状(prd/sdd/benchmark/validator,没有专门写 `acceptance.md` 的节点)持续是常态,`acceptance_mode='itemized'` 这条路径就永远物化不出子任务——这正是 46 号文档记录的"生产库 `itemized` 零命中"现象背后的确切原因。 `org-planner/agent.md`、`org-planner/tools.md` 里"四份产物含 acceptance.md"的文档描述本身可能已经过时,没跟上 `goal-benchmark.json`/R-19-B 契约这套后来演进出的机制——这是本文档范围之外的一处文档陈旧,暂不处理(见「五、遗留」)。 --- ## 三、修法 不改 org-planner 的产物约定(不强推它必须再产出一份 `acceptance.md`,那需要改 prompt/工具且不保证 `workgraph-planning` 每次都会规划出对应节点),改**物化机制去认已经证实稳定产出的那份数据**——`goal-benchmark.json`。 `goal-benchmark.json` 的 `dimensions[]` 天然就是一份验收条目清单,结构比 `acceptance.md` 的 Markdown 表格更丰富(`name`/`weight`/`pass_threshold`/`rubric`/`evidence_hint`),还带一份 `traceability[]` 把每个维度映射回原始需求条目原文(`task_goal_item`),物化子任务时用它当 `title` 比维度英文名(如 `direction_control`)对人更友好。 `server/services/graph-engine.js` 新增两个函数: - `findGoalBenchmarkFile(run)`:查 `runRootDir(run)/artifacts/goal-benchmark.json`(共享区,`runRootDir` 只认 `root_run_id`,传根 run 还是子 run 对象都能解析到同一路径,不用像 `findAcceptanceMdFile` 那样扫全部节点)。 - `parseGoalBenchmarkDimensions(text)`:解析出跟 `parseAcceptanceMd` 同形状的行(`{seq, item, method, passCondition}`),下游 `createTask` 循环不用改。 `maybeMaterializeItemizedSubtasks` 改成两段式:先按原逻辑找 `acceptance.md`(保留向后兼容——万一以后哪个模板真的规划出了写 `acceptance.md` 的节点,原路径优先生效),找不到再退而认 `goal-benchmark.json`;两者都没有才真正判定"没有可物化的数据源"。 ``` acceptance.md 存在 → 用它(不变) acceptance.md 不存在 goal-benchmark.json 存在 → 用它的 dimensions[](新增) goal-benchmark.json 也不存在 → 仍按 holistic 收尾(不变,软失败) ``` --- ## 四、验证 `server/tests/unit/task-decomposition-materialize.test.js` 新增 2 条用例(原 3 条 + 新 2 条 = 5 条全过): 1. `acceptance.md` 不存在但 `goal-benchmark.json` 存在(共享 `artifacts/` 区)→ 按维度物化子任务,`title` 取 `traceability` 里的原始需求条目原文,`description` 含 `evidence_hint` 与 `pass_threshold`。 2. `acceptance.md` 和 `goal-benchmark.json` 都存在时,`acceptance.md` 优先,不走兜底(向后兼容确认)。 测试数据用 46 号文档 Phase 0 真实回归产出的 `goal-benchmark.json`(`data/companies/p0verify0901/runs/run_d1c66d98c5bf/artifacts/goal-benchmark.json`)精简后的真实形状,不是凭空编的 fixture。 全量单测:267(graph-engine + devloop)+ 1136(全量 unit)全过,唯二失败(`env-example-secrets.test.js`、`test-id-stability.test.js`)与本次改动无关,改动前即存在。 **未做**:没有再起一次真实(非 mock)端到端回归去确认修复在真实生产链路里也生效——单测覆盖的是"给定一份真实捕获的 `goal-benchmark.json`,解析和物化逻辑对不对",不是"下一次真实 itemized 任务真的会走到这条兜底路径并成功物化"。原因:一次真实回归要 20 分钟量级的真实 Claude CLI 调用,而这次修复的逻辑本身是纯函数级别的、输入数据已经是真实捕获的产物,单测的把握足够高;如果后续 46 号文档 Phase 1 及以后需要一次新的真实回归验证,可以顺带把这条也验一遍。 --- ## 五、遗留 - `org-planner/agent.md`、`org-planner/tools.md` 里"四份产物含 acceptance.md"的描述与当前 `workgraph-planning` 常规划出的子图形状不一致,属于文档陈旧,本文档未处理。 - 这个兜底修复解决的是**43 号文档设计的物化机制**(org-planner 收尾后物化子任务)的可用性问题。46 号文档 Phase 1 打算把"分解"提前到执行开始之前(新增独立深度规划阶段),如果那条路径落地,本文档修的这条兜底路径可能会变成"旧机制的安全网"而不是主路径——不影响本次修复的必要性(旧机制在 Phase 1-5 落地前还是唯一在跑的路径),但后续设计 Phase 1 时应该想清楚要不要保留这条兜底、还是彻底换成新机制。