在hydrate调用点写明投影修复先于身份校验的前提
裁决为不修,并把改动前提与非重入锁陷阱留在代码旁 第 18.3 节要求身份校验(第 3 步)先于 session 前滚(第 5 步)与 index/pending 重建(第 6 步),而 hydrate 的实际顺序相反。本次裁决为不修,理由与前提写进注释: 现在没有后果,是因为那道比对恒过——App 的全部 init/import 调用点都传同一个常量 seedManifest.projectId(local-project-draft),本机每个项目的 manifest 写的都是它, projectId 分辨不出任意两个项目。该常量不属本工作包管辖。 触发后的后果也已复核为可忽略:命令仍正确返回 PLAN_PROJECT_ID_MISMATCH,前端拿不 到错数据;报错前写入的 pending/session/index 均为幂等或可重建投影,usage fold 有 按 fact 比对的幂等守卫,session.previous.json 是改名而非删除且只在 primary 缺失时 发生(即本来就该做的恢复)。 一旦 projectId 改成每项目唯一,这道校验才真正开始工作,届时顺序必须一起改,否则 hydrate 会先对一棵属于别的项目的 planning 树做完投影修复才发现认错人。注释里连同 陷阱一并写明:不能把 reconcile 直接挪到 hydrate 取锁之后——它自取项目锁,而 .agent/project.lock 是 create_new(true) 非重入的,调用方持锁再进去会死等满重试预算 然后失败;可行路径是锁外 projectId 预检,或把 reconcile 拆成薄壳 + _at_locked 由 hydrate 在自己的锁内调用(同型拆法见 observe_agent_runtime_agent_delegate)。 把提醒放在代码旁而不是只留在 decision-log,是因为真正会改 projectId 方案的人不在 本领域,不会来读这份文档。 cargo fmt --check、check:encoding 通过;纯注释与文档,无行为改动。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -424,6 +424,25 @@ pub(crate) fn hydrate_game_creator_plan_gdd_state_at(
|
||||
));
|
||||
}
|
||||
|
||||
// 这里的顺序与方案 §18.3 的固定顺序相反,是刻意保留的现状,改动前先读完这段。
|
||||
//
|
||||
// §18.3 要求身份校验(第 3 步)先于 session 前滚(第 5 步)与 index/pending 重建
|
||||
// (第 6 步)。本函数反过来:先 `reconcile`、再前滚 session、再重建 index,最后才在
|
||||
// `build_state_view_locked` 里比对 GDD/session 的 projectId 与 manifest。
|
||||
//
|
||||
// 现在没有后果,是因为那道比对恒过——App 的全部 init/import 调用点都传同一个常量
|
||||
// `seedManifest.projectId`(`local-project-draft`),本机每个项目的 manifest 里写的
|
||||
// 都是它,所以 projectId 分辨不出任意两个项目。该常量不属本模块管辖,此处不改。
|
||||
//
|
||||
// **一旦 projectId 改成每项目唯一,这道校验才真正开始工作,本顺序就必须一起改**:
|
||||
// 否则 hydrate 会先对一棵属于别的项目的 planning 树做完投影修复,才发现认错了人。
|
||||
//
|
||||
// 改的时候有个坑:**不能把 `reconcile` 直接挪到 hydrate 取锁之后**。它自己要取项目
|
||||
// 锁,而 `.agent/project.lock` 是 `create_new(true)` 的非重入文件锁,调用方已持锁再
|
||||
// 进去会死等满重试预算然后失败。可行的两条路是:在 `reconcile` 之前加一次锁外的
|
||||
// projectId 预检;或把 `reconcile` 拆成取锁薄壳 + `_at_locked` 核心,由 hydrate 在
|
||||
// 自己那把锁内调用(仓库已有同型拆法,见 `observe_agent_runtime_agent_delegate`)。
|
||||
//
|
||||
// 直接传播:`PlanningStorageError` 的 Display 已是 `"{code}: {detail}"`,
|
||||
// 再用同一个 code 把 `to_string()` 当 detail 重包一次,只会渲染出
|
||||
// `CODE: CODE: detail`,code 与 detail 都没有变化。
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
- **测试陷阱(值得记)**:写「占住项目锁」的 fixture 时必须给锁 JSON 填**真实** `createdAt`。失效锁回收的年龄判定读的是该 JSON 字段而**不是**文件 mtime(`project_write_lock_age_seconds`),填 0 会让锁显得约 1.7e9 秒老、越过 600 秒阈值被当场回收删除,hydrate 反而成功。第一版 fixture 正是这样自证失败的。
|
||||
- **验证**:Rust `planning_` 组 **155 passed / 0 failed**(原 154 + 本次 1 条);`appSurface.test.ts` **383 passed / 0 failed**(378 原有 + 5 条新增);三条新回归均经变异验证,逆转对应修复即变红。`cargo fmt --check`、`agc:typecheck`、ESLint `--max-warnings 0`、`check:encoding`、`git diff --check` 通过。
|
||||
- **撤回一条此前的审查发现**:曾判定 hydrate 读 manifest 缺符号链接判定(因其走裸 `root.join` 而非 `resolve_local_project_path`)。复核后**不成立**:`read_manifest` 自身在 `metadata.file_type().is_symlink()` 处即拒(`manifest.rs`),防护在另一层;`.agent` 目录本身为符号链接的残差也无窗口,紧随其后的 `resolve_planning_path` 同样逐段判定。未据此改动代码。
|
||||
- **仍未修**:① hydrate 在校验 GDD/session 的 projectId 与 manifest 一致之前已执行落盘投影修复,违反第 18.3 节固定顺序(修它须注意 `reconcile` 自取项目锁、`.agent/project.lock` 不可重入,不能把检查直接挪到 hydrate 取锁之后);② design 组展示名仍有 `agentPresentation.ts` 的 `groupConfigs` 与 `view/project-development/index.tsx` 的 `summarizeAgent` 两处硬编码「策划 Agent」,注意该两处与 `taskGroupLabels` **命名体系不同**(「X Agent」对「X组」),直接替换会连带改掉另外五个分组名,需先定命名口径。
|
||||
- **仍未修**:① hydrate 在校验 GDD/session 的 projectId 与 manifest 一致之前已执行落盘投影修复,违反第 18.3 节固定顺序。**已裁决为不修**:它唯一有后果的前提是 projectId 变成每项目唯一,而该常量方案不属本工作包管辖;单独为一个不受控的假设改动权威读取路径不划算。触发后的实际后果也已复核为可忽略——命令仍正确返回 `PLAN_PROJECT_ID_MISMATCH`,写入的 pending/session/index 均为幂等或可重建投影,`session.previous.json` 是改名而非删除且只在 primary 缺失时发生。裁决与改动前提(含「不能把 `reconcile` 直接挪到 hydrate 取锁之后」这个非重入锁陷阱)已作为注释写在 `planning_hydrate.rs` 调用点旁,使提醒与会坏掉的代码同处,而不是只留在本文档里;② design 组展示名仍有 `agentPresentation.ts` 的 `groupConfigs` 与 `view/project-development/index.tsx` 的 `summarizeAgent` 两处硬编码「策划 Agent」,注意该两处与 `taskGroupLabels` **命名体系不同**(「X Agent」对「X组」),直接替换会连带改掉另外五个分组名,需先定命名口径。
|
||||
- **新记一条既有问题(非 M1D 引入)**:`seedManifest.projectId` 是常量 `local-project-draft`,App 的 5 个 init/import 调用点全传它,因此**本机所有项目 projectId 相同**。第 18.3 节第 1 步依赖的「manifest 与 projectId 校验」因此分辨不出任意两个项目——把 A 项目的 `.agent/planning/**` 整体拷入 B 项目仍会通过。该门当前近乎恒真,须单独立项处置。
|
||||
|
||||
## 2026-08-18 M1D 审查修复:GDD 审批决定失败路径与恢复期弹层门控
|
||||
|
||||
Reference in New Issue
Block a user