Commit 411af4e569684b8ac773b1835772b31ec4fe1159

Authored by yibo
1 parent ab57a0af

docs(27-P4): 照代码逐条核对任务表 —— 阶段 C 与 T1 早就做完了

按任务表逐条 grep 代码,发现这份文档落后于实现相当多:

· **T1 已完成**(P3C §15.5-2):SessionPool.maxSessions + scope key,两条守卫盯着。
· **T3 后半已完成**(P3C 批 4):单 active root run 的唯一索引已 DROP,
  换成四步事务内准入(BEGIN IMMEDIATE → 重读 → 计数 → 条件更新),8 进程实测零超发。
· **阶段 C 整个已完成,但形态换了**:产物索引做成了 graph_files **表**而不是 files.json。
  表比文件多拿到两样:部分唯一索引保证「同一路径只有一个当前版本」,
  superseded_by 让历史版本可共存。守卫 graph-files-index.test.js 七条。
· 产物也早就按 run/node 分目录了(runs/{rootRunId}/nodes/{nodeRunId}/),
  §3.6 里「按 agent 分目录、同一 agent 不同任务混在一起」那条已不成立。

§3.6 那两个 ❌ 改成校准块 —— 保留原文(它记录了当时的判断依据),
但当前状态以校准块为准。

**真正剩下的只有阶段 B**:max_concurrency 列、advanceBatch、三层额度取小、
并行执行与部分失败降级,以及 T10 的控制台可视化。

> 这是「文档落后于代码」的又一例。判断一个阶段还剩多少工作量时,
> **先照着任务表逐条 grep 代码**,别照文档估 —— 照文档估会把已完成的三项当成待办。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/todo/system-design/27-p4-parallel-and-artifacts.md
@@ -115,10 +115,25 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_graph_runs_one_active_root @@ -115,10 +115,25 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_graph_runs_one_active_root
115 115
116 ### 3.6 产物目录现状 116 ### 3.6 产物目录现状
117 117
  118 +> ⚠️ **本节写于 2026-08-16,下面两个 ❌ 已经不成立了**(2026-08-20 校准)。
  119 +> 留着原文是因为它记录了当时的判断依据;**当前状态看这一段的校准块**。
  120 +
118 - ✅ `goal-contract.js` 已实现验收契约的跨节点传递(R-15.6 验收先行)。 121 - ✅ `goal-contract.js` 已实现验收契约的跨节点传递(R-15.6 验收先行)。
119 -- ✅ `graph-engine.js:283/407/444` 有节点级产物路径:`data/companies/{cid}/agents/{systemAgentId}/.../attempt-{n}.json`。  
120 -- ❌ **没有 `files.json`**(全仓库无此文件、代码无引用)。  
121 -- ❌ 产物按 **agent** 分目录,不是按 **task** 分目录——同一 agent 在不同任务下的产出混在一起。 122 +- ✅ `graph-engine.js` 有节点级产物路径。
  123 +- ~~❌ 没有 `files.json`~~ → **✅ 阶段 C 已落地,但形态换了**:产物索引做成了
  124 + `graph_files` **表**(`recordFile` / `findFiles` / `rebuildFilesFromNodes`),
  125 + 不是 `files.json` 文件。表比文件多拿到两样东西:部分唯一索引保证「同一路径只有一个当前版本」,
  126 + 以及 `superseded_by` 让历史版本可共存。守卫 `graph-files-index.test.js` 七条,
  127 + 含「删掉索引后能从节点行重建」和「两个 task 的同名产物不互相覆盖」。
  128 +- ~~❌ 产物按 agent 分目录~~ → **✅ 已按 run/node 分目录**:
  129 + `data/companies/{cid}/runs/{rootRunId}/nodes/{nodeRunId}/`,
  130 + 跨 org node 的共享产物在 `runs/{rootRunId}/artifacts/`。
  131 + 同一 agent 在不同任务下的产出因此天然不混。
  132 +
  133 +> **2026-08-20 顺带修掉一个存量库缺陷**:生产库里还留着产物索引加版本之前的
  134 +> **无条件**唯一索引 `idx_graph_files_path`,导致重试时同路径第二次写必撞、
  135 +> 被降级成一条 timeline —— **产物索引在任何有重试的 run 上都不全**,而且不报错。
  136 +> 详见 [27-P3B §六ter](./27-p3b-real-run-runway.md)。
122 137
123 --- 138 ---
124 139
@@ -485,14 +500,14 @@ R-19 之后这两件事有实体(`graph_edge_contracts` × `graph_files`), @@ -485,14 +500,14 @@ R-19 之后这两件事有实体(`graph_edge_contracts` × `graph_files`),
485 500
486 | # | 任务 | 文件 | 依赖 | 501 | # | 任务 | 文件 | 依赖 |
487 |---|---|---|---| 502 |---|---|---|---|
488 -| **T1** | `SessionPool` 加 `maxSessions` 上限 + 调用方传 `scope`(`gr:{nodeRunId}`) | `server/src/core/session-pool.js`、`server/src/core/agent-manager.js` | 无 | 503 +| ~~**T1**~~ ✅ | `SessionPool` 加 `maxSessions` 上限 + 调用方传 `scope` —— **已完成**(27-P3C §15.5-2,守卫 `session-pool-max.test.js` / `session-pool-scope-key.test.js`) | `server/src/core/session-pool.js` | 无 |
489 | **T2** | DevLoop 规格:§12 第一条不变量放宽为 `(pid, agent, scope)`。**改 HTML 后必须跑 `npm run spec:extract`** | `docs/prd/newfunction/*.html` | 无 | 504 | **T2** | DevLoop 规格:§12 第一条不变量放宽为 `(pid, agent, scope)`。**改 HTML 后必须跑 `npm run spec:extract`** | `docs/prd/newfunction/*.html` | 无 |
490 -| **T3** | `graph_runs` 加 `max_concurrency` 列;DROP `idx_graph_runs_one_active_root`,改为事务内计数检查 | `server/services/work-graph-db.js` | 无 | 505 +| **T3** | `graph_runs` 加 `max_concurrency` 列(**未做**);~~DROP `idx_graph_runs_one_active_root`,改为事务内计数检查~~ ✅ **后半已完成**(27-P3C 批 4:四步准入 `BEGIN IMMEDIATE` → 事务内重读 → 事务内计数 → 条件更新,8 进程实测零超发) | `server/services/work-graph-db.js` | 无 |
491 | **T4** | `advanceBatch(runId, {maxConcurrency})`:事务内批量拾取并置 running;`advanceOne` 降级为薄封装 | `server/services/graph-engine.js` | T3 | 506 | **T4** | `advanceBatch(runId, {maxConcurrency})`:事务内批量拾取并置 running;`advanceOne` 降级为薄封装 | `server/services/graph-engine.js` | T3 |
492 | **T5** | 并发额度计算(三层取小)+ 额度为 0 时的等待语义 | `server/services/graph-engine.js` | T1, T3, T4 | 507 | **T5** | 并发额度计算(三层取小)+ 额度为 0 时的等待语义 | `server/services/graph-engine.js` | T1, T3, T4 |
493 | **T6** | 并行执行 + 错误降级(照搬 `orchestrator.executeWithDependencies` 的 `Promise.allSettled` 策略,单分支失败不终结整批) | `server/services/graph-engine.js` | T4 | 508 | **T6** | 并行执行 + 错误降级(照搬 `orchestrator.executeWithDependencies` 的 `Promise.allSettled` 策略,单分支失败不终结整批) | `server/services/graph-engine.js` | T4 |
494 -| **T7** | `files.json` 服务:追加、查询、rebuild | `server/services/company-files-index.js`(新增) | 无,可并行 |  
495 -| **T8** | 产物落盘路径改为 `tasks/{taskId}/`;节点产出后自动写 `files.json` | `server/services/graph-engine.js`、`goal-contract.js` | T7 | 509 +| ~~**T7**~~ ✅ | 产物索引:追加、查询、rebuild —— **已完成,形态换成 `graph_files` 表**(见 §3.6 校准块),不是 `files.json` | `server/services/work-graph-db.js` | — |
  510 +| ~~**T8**~~ ✅ | 产物按 run/node 分目录 + 节点产出后自动进索引 —— **已完成**(`runs/{rootRunId}/nodes/{nodeRunId}/`) | `server/services/graph-engine.js` | — |
496 | **T9** | node agent 的 prompt 注入 `files.json` 检索说明 | `system/templates/base/agent.md` 等 | T7 | 511 | **T9** | node agent 的 prompt 注入 `files.json` 检索说明 | `system/templates/base/agent.md` 等 | T7 |
497 | **T10** | 控制台:fan-in 等待态可视化(「在等哪几个节点」)+ 并行运行中的多节点高亮 | `web/src/views/GraphConsole.vue` | T6 | 512 | **T10** | 控制台:fan-in 等待态可视化(「在等哪几个节点」)+ 并行运行中的多节点高亮 | `web/src/views/GraphConsole.vue` | T6 |
498 | **T11** | 测试:批量拾取的并发幂等(两个 `advanceBatch` 不拾取重叠集合)、部分失败的 fan-in 停等、额度上限生效、`files.json` rebuild 一致性 | `server/tests/unit/` | T4-T8 | 513 | **T11** | 测试:批量拾取的并发幂等(两个 `advanceBatch` 不拾取重叠集合)、部分失败的 fan-in 停等、额度上限生效、`files.json` rebuild 一致性 | `server/tests/unit/` | T4-T8 |
@@ -501,12 +516,18 @@ R-19 之后这两件事有实体(`graph_edge_contracts` × `graph_files`), @@ -501,12 +516,18 @@ R-19 之后这两件事有实体(`graph_edge_contracts` × `graph_files`),
501 516
502 | 阶段 | 含 | 交付 | 可否独立上线 | 517 | 阶段 | 含 | 交付 | 可否独立上线 |
503 |---|---|---|---| 518 |---|---|---|---|
504 -| **A · 隔离** | T1, T2 | 同一 agent 可安全并发调用 | ✅ 可以,此时引擎仍串行,但隔离能力已具备 | 519 +| **A · 隔离** | ~~T1~~ ✅, T2 | 同一 agent 可安全并发调用 —— **T1 已完成**,只剩 T2(DevLoop 规格放宽) | 基本具备 |
505 | **B · 并行** | T3-T6, T10 | fan-out 真正并发;解除单 root run 约束 | ✅ 依赖 A | 520 | **B · 并行** | T3-T6, T10 | fan-out 真正并发;解除单 root run 约束 | ✅ 依赖 A |
506 | **C · 产物索引** | T7-T9 | `files.json` + 按 task 分目录 | ✅ **完全独立于 A/B**,可最先做 | 521 | **C · 产物索引** | T7-T9 | `files.json` + 按 task 分目录 | ✅ **完全独立于 A/B**,可最先做 |
507 522
508 **阶段 C 与 A/B 无依赖**,如果想先看到效果,可以先做 C(它是用户明确要求过的第 3 项)。 523 **阶段 C 与 A/B 无依赖**,如果想先看到效果,可以先做 C(它是用户明确要求过的第 3 项)。
509 524
  525 +> **2026-08-20 校准**:C 已经完成,A 的 T1 也完成了(都是在 P3C 那几批里顺手落的,
  526 +> 当时没回来更新这份文档)。**真正剩下的是 B**:`max_concurrency` 列、`advanceBatch`、
  527 +> 三层额度取小、并行执行与部分失败降级,以及 T10 的控制台可视化。
  528 +> 这一段是「文档落后于代码」的又一例 —— 判断 P4 还剩多少工作量时,
  529 +> **先照着任务表逐条 grep 一遍代码**,别照文档估。
  530 +
510 --- 531 ---
511 532
512 ## 六、验收标准 533 ## 六、验收标准