登记素材上传配对的踩坑与覆盖边界:断言打在生产函数上,端到端 UI 未覆盖
- `pitfalls.md` 新增「素材上传不要把『上传前读到的 revision』贴到『上传后读到的清单』上」:现象(每次上传都先吃一条拒收提示)、根因(上传本身推进 revision,错配必然发生而非竞态)、处理(上传动作与配对读收进同一个函数,调用方拿不到中间那个 revision,也就没机会贴错)、同类文案坑(`unresolved` 三条进入路径里有两条重读是**成功**的,写成"读取磁盘清单失败"就是误报)、验证与变异,并记录关联文件与提交。 - 覆盖边界(按当下口径写清,防止后人误读):断言打在生产函数 `uploadProjectAssetFilesAndReadSnapshot` 上 —— 它同时拥有上传与配对读,所以配对语义是真测的;但 AGC 侧**没有**「资源面板上传 → 拒收提示条」的端到端 UI 用例(现有 harness 只有聊天入口的 `/asset.upload`,资源面板 file input 无用例),因此「界面上不再出现拒收提示」是推理结论而非端到端断言,禁止当成端到端覆盖引用。 - 同一条边界同时写进 `projectResourceLiveUpdateModel.test.ts` 用例组头部,随断言一起走,避免只看测试文件的人误读。 - 纯文档 + 测试注释,无功能改动。
This commit is contained in:
@@ -340,6 +340,14 @@ describe('清单快照被拒收后的可见性与恢复', () => {
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* ⚠️ **覆盖边界**:下面这组断言打在生产函数 `uploadProjectAssetFilesAndReadSnapshot` 上 ——
|
||||
* 它同时拥有「上传」与「配对读」,所以整条配对语义是真测的;但 AGC 侧**没有**"资源面板上传 →
|
||||
* 拒收提示条"的端到端 UI 用例(现有 harness 只有聊天入口的 `/asset.upload` 通路,资源面板的
|
||||
* file input 没有用例,补一条要渲染整个 `ProjectDevelopmentView`)。因此「界面上不再出现拒收
|
||||
* 提示」这一层是**推理结论**(配对正确 ⇒ merge 判 accepted ⇒ 不产生拒收决策 ⇒ 不渲染提示条),
|
||||
* 不是端到端断言。别把它当成端到端覆盖引用。
|
||||
*/
|
||||
describe('素材上传后的清单快照必须配对', () => {
|
||||
/**
|
||||
* 假后端:上传推进 revision,其余读命令返回当前磁盘状态。
|
||||
|
||||
@@ -5308,4 +5308,14 @@
|
||||
- **处理**:**每新增或改名一个源文件后,必须重跑一次完整 typecheck**;门禁顺序固定为「改完 → 全量 typecheck → 定向测试 → 其余门禁」。**不要**因为 pre-commit 绿或定向测试绿就认为类型没问题。
|
||||
- **验证**:修复(`useRef<HTMLDivElement | null>`)后重跑 `npm run ai-game-creator-shell:typecheck` → `exit 0`;修复前同命令 `exit 2`。定向测试修复前后都是 `22 passed`——**说明测试通过完全推不出类型正确**。
|
||||
- **推广**:同类漏检还包括未使用的 import、只在类型层违约的 props 不匹配、以及只有 `tsc` 才发现的 `.tsx` 泛型推断失败;这几类都不会让 eslint 或 vitest 变红。
|
||||
- **关联**:`apps/ai-game-creator-shell/src/view/project-development/ResourceFilterPanel.tsx`、`package.json` 的 `ai-game-creator-shell:typecheck` 与 `format:staged`(lint-staged)、提交 `5f03f050a`。
|
||||
- **关联**:`apps/ai-game-creator-shell/src/view/project-development/ResourceFilterPanel.tsx`、`package.json` 的 `ai-game-creator-shell:typecheck` 与 `format:staged`(lint-staged)、提交 `5f03f050a`。
|
||||
|
||||
## 2026-09-11 素材上传不要把「上传前读到的 revision」贴到「上传后读到的清单」上
|
||||
|
||||
- **现象**:用户每上传一次素材,就先看到一条「资源清单更新被拒收(同一版本号上的清单内容不一致),正在重新读取磁盘清单…」。资源最终能出来,但每次上传都要多一轮读盘 + 一次误报。
|
||||
- **原因**:上传路径读盘与写盘的**配对错位**。代码在上传**前**读 `get_local_game_project_revision` 拿到 revision,上传完再把那个**旧 revision** 交给重载函数;重载函数自己重新读盘拿的是**新清单**,于是"旧版本号 + 新内容"被 `mergeProjectManifestSnapshot` 判成 `revision-conflict`(同 revision 不同指纹)。上传本身会推进 revision(Rust `advance_agent_runtime_project_revision_locked`),所以这个错配是**必然发生**而不是竞态。同一个提交里其实已经有正解 `rereadAuthoritativeProjectManifestSnapshot`(同一次读里配对 revision + 清单,并再读一次 revision 确认没被写盘插队),只是上传路径没用它。
|
||||
- **处理**:把「上传动作 + 配对读」收进**同一个函数** `uploadProjectAssetFilesAndReadSnapshot`,调用方拿不到中间那个 revision,也就没有机会贴错。凡是"命令会推进 revision、之后要重载清单"的路径都按这个形状办:**要么命令自己返回提交后的 revision,要么用配对读,绝不用写盘前读到的 revision。**
|
||||
- **同类文案坑**:拒收后的恢复阶段 `unresolved` 有三条进入路径,其中两条**重读是成功的**(读到的一对比手上旧 / 读到的是撕裂的一对)。文案不能写成"重新读取磁盘清单失败"——那是误报;写"未能按磁盘清单重新对齐"才对所有路径都成立。
|
||||
- **验证**:`projectResourceLiveUpdateModel.test.ts` 用假后端(上传时推进 revision)断言 merge 判 `accepted` 且 `projectManifestMergeRejectionDecision === null`,并附一条对照钉子显式写出旧形状、断言它必判 `revision-conflict`。变异验证:把修法还原成旧形状 → 配对用例变红。
|
||||
- **覆盖边界(重要,别误读)**:上述断言打**在生产函数 `uploadProjectAssetFilesAndReadSnapshot` 上**,它同时拥有上传与配对读,所以整条配对语义被真测;但 **AGC 侧没有"资源面板上传 → 拒收提示条"的端到端 UI 用例**(现有 harness 只有聊天入口的 `/asset.upload` 通路,资源面板的 file input 没有用例,补一条要渲染整个 `ProjectDevelopmentView`)。因此「用户界面上不再出现拒收提示」这一层**未被端到端覆盖**,它是"配对正确 ⇒ merge 判 accepted ⇒ 不产生拒收决策 ⇒ 不渲染提示条"的推理结论。**不要把它当成端到端断言引用。**
|
||||
- **关联**:`apps/ai-game-creator-shell/src/view/project-development/projectResourceLiveUpdateModel.ts`、`index.tsx` 的 `uploadResourcePanelFiles`、`src/features/app-shell/WorkspaceLauncher.tsx`、提交 `006e9cc2f`。
|
||||
Reference in New Issue
Block a user