修复重复生成占位的对账判定
收集项目快照中全部同 ID 的生成占位原始记录。 通用队列链在任一重复占位未收口时执行第二次项目读取。 完美像素遇到重复操作占位时失败关闭为冲突。 补充两条缺陷定向测试并同步技术方案与决策记录。
This commit is contained in:
@@ -6093,7 +6093,7 @@
|
||||
|
||||
- 缺陷一(对账把真成功判成失败):对账用「同 ID 的 generation-dialog 是否还在权威快照里」判定成败,而服务端成功回填时**保留**该 dialog 并就地改写——`apply_editor_canvas_generation_items` 置 `status: "idle"`、`composerOpen: false`、写入 `generatedLayerId`、清掉 `errorMessage`,该行为另有服务端测试断言 `dialog["generatedLayerId"]` 钉住。所以响应丢失但服务端其实已完成时,判据反向:用户被告知「画布未收到完美像素结果,请确认素材库」,而结果早已在画布上,重做一遍就造出第二份;这条分支还刻意不套用快照,本地也看不到那个新图层。
|
||||
- 逃过测试的原因要单独记:那条「未知但实际成功」的用例夹具写的是 `layers: []`,是服务端永远不会产生的形状。**测试不是漏了,是主动为错误判据背书**——用一个假前提把反向逻辑测成了正确的。修复顺序因此定为「先改夹具、看它变红、再改判据」,让这件事显式暴露一次而不是被新判据顺手掩盖。
|
||||
- 决策一:判据改看 `status` / `generatedLayerId`,并复用既有语义。`projectHasUnresolvedGenerationDialog` 早就是本仓库对「这个生成收口了没有」的定义,只是原先埋在队列轮询里;提取为 `findCanvasGenerationDialogRecord` + `isUnresolvedCanvasGenerationDialogRecord` 两处共用,不自创新判据——自创正是本次出错的起点。取原始 record 而不 hydrate:hydrate 会给缺失 status 补 `idle`,把「服务端没写」和「服务端写了 idle」混成一种。
|
||||
- 决策一:判据改看 `status` / `generatedLayerId`,并复用既有语义。`projectHasUnresolvedGenerationDialog` 早就是本仓库对「这个生成收口了没有」的定义,只是原先埋在队列轮询里;原始 record 查询现由收集全部同 ID 记录的 `findCanvasGenerationDialogRecords` 与 `isUnresolvedCanvasGenerationDialogRecord` 两处共用,不自创新判据——自创正是本次出错的起点。取原始 record 而不 hydrate:hydrate 会给缺失 status 补 `idle`,把「服务端没写」和「服务端写了 idle」混成一种。
|
||||
- 三态处置:dialog 不存在 → 占位在处理期间被删(本会话或另一标签页),completion 返回 `Ok(None)`,资源与素材已落库但快照不含结果图层,清本地占位并提示素材库,**不套用快照**(此前会套用一份不含结果的快照并写受撤销保护的历史,用户既看不到结果也撤不回);dialog 在且未收口 → 画布确实没收到,只给文案不同步;dialog 在且已收口 → 真成功,套用快照并写 `perfect-pixel` 历史。前两态都保留「用户已删本地占位则删除意图胜出」的检查。
|
||||
- 缺陷二(网关合成响应被当确定失败):分类前提是「拿到 `ApiClientError` ⇒ 服务端明确表态过 ⇒ 结果已知」。该前提对 Pingora 自造的错误体不成立——它只有 `code` / `message`、没有 `details`,因此既不是 transport 异常也拿不到 `resultPersistenceStarted`,直接跳过对账;而 `ConnectTimedout / ReadTimedout / WriteTimedout → 504`、`ErrorSource::Upstream → 502` 都可能发生在 api-server 已完成 OSS PUT 之后。
|
||||
- 决策二:新增 `isGatewayUnknownOutcomeError`,放在 `services/apiClient.ts` 而不是 image-editor——网关在所有接口前面,任何有副作用的 inline 写接口都有同一问题。只收 `GATEWAY_UPSTREAM_ERROR` / `GATEWAY_UPSTREAM_TIMEOUT` / `GATEWAY_PROXY_ERROR` 三类。`GATEWAY_RATE_LIMITED` / `GATEWAY_CONCURRENCY_LIMITED` / `PAYLOAD_TOO_LARGE` 是在网关就被拒、根本没到应用,属于确定失败,收进来会让普通节流也弹出「请核对素材库」,变成与本条镜像的反向谎报。
|
||||
@@ -6221,3 +6221,10 @@
|
||||
- 时序边界:`claim` 仍紧挨占位创建且早于任何 await;`release` 仍位于 `finally`。version 只负责 React 通知,不替代同步 Set,也不清理 observed recovery key;否则可能在 live Promise 尚未退出时启动第二条 GET。重复 claim / release 为幂等 no-op,不额外触发 effect。
|
||||
- 验证:hook 定向测试使用生产 ownership hook、固定 dialogs 数组和固定 callbacks,并包在 StrictMode 中;单次 render 后先 claim 取消到期 timer,推进到超窗仍不清理,再仅 release 唤醒 effect并清理一次。重复 claim / release 分别保持 version `1 / 2`,删除与通知均只发生一次。hook 定向测试 10 条、generation workflow 定向测试 81 条通过;`npm run typecheck`、全仓 `npm run lint:eslint`、`npm run check:encoding` 与 `git diff --check` 通过。未追加其它测试或全量测试套件。
|
||||
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
|
||||
|
||||
## 2026-08-04 项目快照对账不再按重复 dialog ID 首项短路
|
||||
|
||||
- 缺陷:项目快照查询 helper 只返回第一个同 ID generation dialog。legacy / External API 画布若含重复 ID,首条已收口、后条仍 unresolved 时,通用 queued completion 会漏掉用于收口的第二次 GET;完美像素还可能把不满足后端唯一性契约的快照误判为成功。
|
||||
- 决策:快照 helper 返回全部同 ID 原始记录。通用 queued completion 只要任一记录未收口就执行既有第二次 GET;完美像素要求 operation dialog 唯一,命中多条时失败关闭为 `conflict`,不按其中任意一条猜测结果。无需改变 hydrate、删除、后端或 OpenAPI。
|
||||
- 验证:两条 `duplicate` 定向用例通过;`npm run typecheck`、改动文件级 ESLint 与 `npm run check:encoding` 通过,未运行目录或全仓测试套件。
|
||||
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
|
||||
|
||||
Reference in New Issue
Block a user