统一画布快速编辑白名单与入口行为 #140
Reference in New Issue
Block a user
Delete Branch "fix/quick-edit-whitelist"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
使用白名单控制快速编辑功能的显示
同步浮动工具栏、右键菜单、打开入口和提交门禁
禁用角色动作序列帧(整体)、音频和单个图标(explain: backend reject)的快速编辑
把快速编辑api改为只接受resource Id | asset Id,删除 image_src 参数, 以保证校验
其中包含生产数据迁移。迁移用于清理历史普通图片错误持久化的 assetKind="image",覆盖 asset → project-resource → showcase → canvas 四个作用域,并包含 operator 鉴权、分批 dry-run、批次 SHA-256 绑定 apply、active 结构化画
请求修改。当前 head 的前端入口与定向测试本身基本一致,但仍有两个合同阻塞项:一是正向白名单只落在前端,后端图片编辑 BFF 仍仅拒绝 icon,角色动作、音频、视频及未知类型可绕过 UI 进入 queue/inline 图片编辑链路;二是 decision-log 把单个拆分图标的支持状态和测试结论写反。另请整理 head 合并提交“合并主分支”的空正文,使其符合中文标题后逐行说明变更的仓库提交规范。当前四组 CI 均已绑定 73608a83 通过;修复并更新 head 后请重新请求 review。
@@ -6110,0 +6110,4 @@## 2026-08-04 图片画布快速编辑改用统一正向白名单(2026-08-05 修订)- 背景:画布生成结果统一显示快速编辑,但部分角色动作 / 序列帧和音频结果会进入不受支持的图片编辑链路;不同入口各自判断时也容易继续漂移。- 决策:快速编辑只支持普通静态图片、角色图、规范图、完整图标图集、单个拆分图标、UI 设计图、宣发图和视频。角色动作 / 序列帧、音效与背景音乐不支持;新增媒体或素材类型默认不开放。浮动工具栏、图层右键菜单、独立图片菜单、打开面板入口和提交门禁统一调用同一个正向白名单。[P2] 这里把单个拆分图标的合同和测试结论写反了
本行称“单个拆分图标”支持快速编辑,后面的验证说明也称已覆盖其开放与提交;但当前白名单明确没有
icon,模型测试断言false,提交工作流也断言直接拒绝,另外两份当前编辑器文档同样写不支持。请将长期 decision log 改为“单个拆分图标不支持”,并同步修正验证描述,避免后续按错误共享记忆反向修改实现。@@ -596,3 +596,2 @@export function isQuickEditUnsupportedAssetKind(layer: CanvasLayer) {return layer.assetKind === 'icon';const QUICK_EDIT_SUPPORTED_MEDIA_TYPES = new Set<CanvasLayer['mediaType']>([[P1] 后端正向门禁缺失,接口可绕过前端白名单
这里新增的白名单只约束浏览器入口和前端提交函数;后端
ensure_editor_image_edit_asset_kind_allowed仍对除icon外的所有值返回允许,queue 与 inline 两条/api/editor/images/edits路径都会继续执行。因此认证用户可直接提交角色动作、音频、视频或未来新增类型,角色动作首帧还可能真实进入图片编辑链路。请把同一正向规则下沉到 api-server,基于目标图层的有效assetKind + mediaType做端点级校验,并用表驱动测试覆盖全部现役类型和未知类型;视频快速编辑应只走视频生成接口。73608a833dto73fa39ab19请求修改。旧 review 中的目标图层后端正向门禁与 decision-log 单图标合同已经修复,但当前 head 新增了两个功能回归:画布 Agent edit_image 任务会被新白名单拒绝;前端媒体类型与素材类型按两个独立集合判断,会把静态图片 + video 标签误路由到视频生成接口。MVP 文档还保留与新合同相反的角色动作快速编辑表述。另请清理 6ed1cfa0、84df62ea、b4e1fdf7 三个空正文“合并主分支”提交,使提交历史符合中文标题后逐行说明变更的仓库规范。CI run 711 已绑定当前 head 四组通过;修复并更新 head 后请重新请求 review。
@@ -19,6 +19,7 @@- 鼠标中键拖拽始终平移画布;长按 Space 临时进入抓手模式,松开后恢复原工具。- 图片拖拽时显示水平 / 垂直吸附参考线,吸附到其它图层、生成占位框或画板的边缘与中心线;当移动元素接近两个同轴元素形成的等距位置时,支持横向或纵向等距吸附。- 生成资源右上角显示元数据按钮,点击打开独立元数据窗口。图片信息页不展示后端组装后的生图 Prompt,也不提供复制 Prompt;只展示该图片生成时用户在面板里提交的输入快照,包括普通生成提示词、规范表单字段、角色设定、图标素材描述、快速编辑提示词、重绘提示词,以及角色规范 / 常规参考图 / 图标规范 / 编辑参考图等参考图卡片,并提供“复制信息”复制当前可见字段。参考图输入快照只保存 `refType/refId` 行引用,其中 `refType="project-resource"` 指向 `editor_project_resource.resourceId`,`refType="asset"` 指向 `editor_asset.assetId`;不得把图片 Data URL、普通 URL 或 `objectKey` 写入 `generationInputs.references`。旧数据或上传图片没有输入快照时显示 `-`,禁止回退展示内部 Prompt。- 快速编辑使用统一正向白名单:只支持普通静态图片、角色图、规范图、完整图标图集、UI 设计图、宣发图和视频;单个拆分图标、角色动作 / 序列帧、音效与背景音乐不支持。浮动工具栏、图层右键菜单、独立图片菜单、打开面板入口和提交门禁必须复用同一白名单,新增媒体或素材类型默认不开放。`/api/editor/images/edits` 的 queue 与 inline 路径还必须基于目标图层的有效 `assetKind + mediaType` 执行后端正向门禁;视频快速编辑只能走视频生成接口,不得进入图片编辑 BFF。下文关于“角色动作素材按当前帧图片快速编辑”的历史表述由本条取代,角色动作只能通过对应的动作生成链路继续编辑。[P2] 不能只声明旧表述“由本条取代”,应直接消除冲突正文
同一文档后面的第 24 行仍写着“角色动作素材的快速编辑按当前帧图片走图片编辑”,与本行、decision-log 和当前实现的“角色动作只能走动作生成链路”相反。RAG 分块检索时旧句仍可能被单独命中。请直接删除或改正后文旧句,使权威文档只保留一个当前合同。
@@ -2918,3 +2947,3 @@.filter(|value| !value.is_empty())else {return ensure_editor_image_edit_asset_kind_allowed(payload.asset_kind.as_deref());return ensure_editor_image_edit_source_kind_allowed(payload.asset_kind.as_deref(), None);[P1] 这个 fallback 会把画布 Agent 的 edit_image 任务全部拒绝
画布 Agent 在 editor_agent/tool.rs 构造的同一 EditorImageEditRequest 固定为 projectId=当前项目、targetLayerId=None、assetKind="editor_agent_edit_image"。任务进入 external-generation worker 后会调用 edit_editor_image_for_owner 并命中这里;该内部合成类型不在新正向白名单中,因此任务会直接以“当前素材类型不支持图片快速编辑”失败。请按可信调用来源或真实源资源类型保留 Agent 编辑路径,同时继续对画布快速编辑失败关闭,并增加从 Agent payload 到 worker 执行门禁的回归测试。
@@ -672,0 +685,4 @@'video',]);export function isQuickEditSupportedLayer(layer: CanvasLayer) {[P1] 两个独立集合会放过非法类型组合并调用错误接口
素材标签菜单允许把任意静态图片改成 assetKind="video"。此时 mediaType="image" 与 assetKind="video" 分别命中两个集合,所以这里返回 true;提交层的 isVideoQuickEditSource 又因 assetKind 为 video,把真实图片当视频上传并走视频生成接口。请改为显式合法组合判断,例如仅在 mediaType="video",或兼容旧数据时 mediaType 缺失且 assetKind 为 video,才采用视频快速编辑,并补充交叉组合测试。
类似意见在 #117 里也出现了, 问题在于前端手动调整标签assetkind override在保存时会无条件接受,导致真正的assetkind被篡改, #117 在前后端做了校验
请求修改。review 133 中的三条功能问题已复核修复,但当前 head 与 live master 仍存在内容冲突,Gitea mergeable=false,本地 merge-tree 也确认冲突位于图片画布编辑器 MVP 文档。请先同步 master 并人工保留双方当前合同,在新 head 上重跑完整 CI。提交历史同时仍包含 908d2afe、a083a762、6ed1cfa0、84df62ea、b4e1fdf7 等空正文 merge commit,请在本次重整历史时一并改为符合“中文标题后逐行说明变更”的提交。当前 CI run 741 的 Repository、Frontend、Backend 已通过,Native shell 仍在运行;冲突解决后的新 head 需要重新验证并重新请求 review。
@@ -19,8 +19,9 @@- 鼠标中键拖拽始终平移画布;长按 Space 临时进入抓手模式,松开后恢复原工具。- 图片拖拽时显示水平 / 垂直吸附参考线,吸附到其它图层、生成占位框或画板的边缘与中心线;当移动元素接近两个同轴元素形成的等距位置时,支持横向或纵向等距吸附。- 生成资源右上角显示元数据按钮,点击打开独立元数据窗口。图片信息页不展示后端组装后的生图 Prompt,也不提供复制 Prompt;只展示该图片生成时用户在面板里提交的输入快照,包括普通生成提示词、规范表单字段、角色设定、图标素材描述、快速编辑提示词、重绘提示词,以及角色规范 / 常规参考图 / 图标规范 / 编辑参考图等参考图卡片,并提供“复制信息”复制当前可见字段。参考图输入快照只保存 `refType/refId` 行引用,其中 `refType="project-resource"` 指向 `editor_project_resource.resourceId`,`refType="asset"` 指向 `editor_asset.assetId`;不得把图片 Data URL、普通 URL 或 `objectKey` 写入 `generationInputs.references`。旧数据或上传图片没有输入快照时显示 `-`,禁止回退展示内部 Prompt。- 快速编辑使用统一正向白名单:只支持普通静态图片、角色图、规范图、完整图标图集、UI 设计图、宣发图和视频;单个拆分图标、角色动作 / 序列帧、音效与背景音乐不支持。浮动工具栏、图层右键菜单、独立图片菜单、打开面板入口和提交门禁必须复用同一白名单,新增媒体或素材类型默认不开放。`/api/editor/images/edits` 的 queue 与 inline 路径还必须基于目标图层的有效 `assetKind + mediaType` 执行后端正向门禁;视频快速编辑只能走视频生成接口,不得进入图片编辑 BFF;角色动作只能通过对应的动作生成链路继续编辑。画布 Agent 的 `edit_image` 只接收图片输入,新任务按普通静态图片提交空 `assetKind`,不得再生成 `assetKind="editor_agent_edit_image"`;worker 仅对 `dedupeKey` 属于可信 `editor-agent:` 命名空间的历史排队任务把该旧 synthetic 值归一为空,普通请求直接提交旧值仍失败关闭。已持久化项目资源中的该旧值在从真实目标图层解析后按普通静态图片兼容,不把该兼容扩大到客户端请求字段。[P1] 当前文件与 live master 存在内容冲突,PR 无法合并
当前 base 已推进到 281c84b7,而本分支 merge-base 仍是 a03f13d0;Gitea mergeable=false,本地 merge-tree 也在本文件报 content conflict。请同步最新 master 后人工解决该段冲突,既保留本 PR 的快速编辑、画布 Agent 和角色动作合同,也保留 master 在同一长段落加入的最新编辑器约束;解决后的新 head 必须重新跑完整 CI,不能沿用当前 head 的绿灯。
908d2afe02toa85522c015空正文 merge commit
请求修改。当前 head 已修复旧 review 中的画布 Agent 编辑路径、前端错误路由、文档冲突和提交正文问题,但后端正向白名单仍未把实际 sourceImageSrc 绑定到被校验的目标图层,调用方可以借允许的目标图层编辑同账号内被禁止类型的来源;门禁还继续把未经归一的请求 assetKind 写入结果。与此同时,这项约束会改变 /api/external/v1/editor/images/edits 的请求语义和 400 条件,权威 OpenAPI 与契约测试没有同步。当前 base 为 9dab1f53,merge-tree 无冲突,当前 head 四组 CI 均通过;请闭合真实来源身份与权威类型,并同步 External v1 契约后重新请求 review。
@@ -2868,3 +2867,1 @@return Ok(());};if asset_kind != "icon" {pub(crate) fn ensure_editor_image_edit_source_kind_allowed([P2] External v1 的新请求约束没有同步权威契约
这个正向白名单也在
/api/external/v1/editor/images/edits入队前执行,已经改变EditorImageEditRequest.assetKind的允许值、目标图层解析语义和返回 400 的条件;但docs/openapi/genarrative-external-v1.openapi.json仍把assetKind定义为任意 string/null,也没有覆盖该允许列表与目标/来源关系的契约测试。请在同一变更中同步 OpenAPI 和对应测试,避免外部 Agent 按当前权威契约提交必然被拒的请求。@@ -2954,3 +2995,3 @@.map_err(map_editor_project_error)?;let project = editor_project_payload_from_record(project);let asset_kind = resolve_editor_image_edit_target_layer_asset_kind(let (asset_kind, media_type) = resolve_editor_image_edit_target_layer_source_kind([P1] 被校验的目标图层与实际编辑来源没有绑定
这里仅用
targetLayerId查目标图层的assetKind + mediaType,但后续真正送给图片模型的是请求中的sourceImageSrc,两者没有做 resource/objectKey 一致性校验。直接调用者可以选择一个允许的静态目标图层,同时把同账号内角色动作帧等被禁止来源作为sourceImageSrc,从而通过门禁;后续持久化又继续采用请求里的assetKind,还能写入与权威目标不一致或未知的类型。请解析目标图层的权威资源,校验sourceImageSrc/sourceResourceId与其一致,并把解析出的有效类型返回给入队、执行和结果持久化,而不是只返回()。把快速编辑api改为只接受resource Id | asset Id,删除 image_src 参数, 以保证校验
3592658633to21209910dd请求修改。当前 head 05641ab1 已不再是标题和正文描述的“快速编辑白名单”补丁:相对 merge-base 的实际差异是 34 个提交、72 个文件、+6789/-2341,主体为图标规范与图标图集生成重构。请恢复 PR 140 到原任务,或为当前图标规范变更另开 PR 并重写标题/正文;旧 head 上的 reviews 128、133、140、142 不能作为当前差异的审查记录。
当前差异还原地破坏了 External v1 兼容性:图标图集请求删除原必填 referenceImageSrc,替换为必填且 ID-only 的 referenceId,但路径仍是 /api/external/v1、OpenAPI info.version 仍为 1.0.0。仓库权威兼容策略明确把新增请求必填字段和收紧既有字段语义视为 breaking,不能只同步 OpenAPI/Skill;请保留 v1 兼容字段并走弃用流程,或新增 v2 并冻结 v1。
最后,live master 已前进到 6c53eda7,Gitea mergeable=false,本地 merge-tree 确认 editor_project.rs 与 external_generation_worker.rs 存在内容冲突。CI run 797 虽四组通过并绑定 05641ab1,但发生在 master 前进前;解决冲突并更新 head 后必须重新跑完整 CI,再重新请求 review。
@@ -3539,3 +3539,3 @@"type": "object","required": ["referenceImageSrc","referenceId",[P1] 不要在 External v1 中直接替换现有必填字段。基线契约这里要求 referenceImageSrc;当前改为必填 referenceId,并把语义收紧为只接受已登记的 icon-spec ID,而路径仍是 /api/external/v1、info.version 仍为 1.0.0。
docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md:161-168明确把新增请求必填字段、收紧语义定义为 breaking,且仅更新 OpenAPI 不构成合规流程。现有 OpenAPI 客户端、Python Skill 调用和 MCP tool 参数都会直接失效。请保留 v1 字段并标记弃用,或新增 v2 并冻结 v1。05641ab19eto359265863363a5d7991bto346ebcf8cb请求修改。当前 head 已解决旧 review 中的合并冲突、来源与目标绑定、Agent 调用和提交正文问题,且四项 CI 全绿;但仍有三处当前阻塞:一是前端按覆盖后的素材标签开放快速编辑,而后端先按资源默认类型拒绝,导致
icon默认资源改标为scene/spec后出现可点击但必然 400 的正常 UI 路径;二是本次已加入scene的前后端白名单,但 External v1 权威 OpenAPI 仍声明只允许到ui-design、其他类型返回 400;三是同一 v1 schema 把sourceImageSrc替换成新的必填sourceReferenceId,却没有兼容字段、v2 路径或基于当前线上调用方状态的新豁免决策。请统一标签覆盖语义、同步完整 OpenAPI 白名单,并按现役版本策略处理 breaking change 后重新请求 review。@@ -1088,6 +1088,7 @@],"operationId": "editExternalEditorImage","summary": "重绘/调整编辑器图片素材","description": "主来源只接受当前账号已登记的项目资源 ID 或素材 ID(sourceReferenceId);objectKey、URL、Data URL 与 Blob URL 即使归属当前账号也返回 400。服务端从命中的业务记录派生 canonical objectKey、assetObjectId 与权威类型,只允许普通静态图片(类型为 null)、spec、character、icon-spritesheet、icon-spec、publication-material 或 ui-design,其他及未知类型返回 400。提供 targetLayerId 时必须同时提供 projectId,目标图层必须关联有效项目资源;双方都有 assetObjectId 时必须相同,否则回退比较 canonical bucket/objectKey。同一对象的来源默认类型与目标资源默认类型冲突、目标有效类型或媒体类型不允许、来源或目标不存在/越权/缺少对象时均返回 400,任务不会入队。referenceImageSrcs 仍只作为辅助参考图。",[P1] External v1 允许类型说明漏掉
scene当前 head 已允许
scene图片快速编辑,但这里的权威契约仍只列到ui-design,并明确称其他类型返回 400。依赖 OpenAPI/MCP 的调用方会把实际合法请求判成非法。请把scene同步进允许列表,并让契约测试精确校验完整白名单,避免目前只检查若干描述关键词而漏掉枚举漂移。@@ -3458,3 +3459,3 @@"minLength": 1},"sourceImageSrc": {"sourceReferenceId": {[P1] 不能在 External v1 中直接替换必填请求字段
本次把既有必填
sourceImageSrc删除并改成新的必填sourceReferenceId,严格客户端会立即失败;这正属于现役版本策略列出的 breaking change。文档中的唯一历史豁免只基于 2026-07-31 当时没有外部第三方调用方,不能自动覆盖今天的新改动。请保留并弃用兼容字段、开/v2,或先用当前线上 API Key/调用证据重新确认豁免仍成立并形成明确的新决策记录;同时修正本 schema 下游仍称sourceImageSrc占用 provider 容量的陈旧说明。@@ -676,0 +690,4 @@'video',]);export function isQuickEditSupportedLayer(layer: CanvasLayer) {[P1] 标签覆盖后前后端快速编辑门禁不一致
这里仅按覆盖后的
layer.assetKind判断。图片族标签菜单允许把默认类型为icon的层改标为scene/spec,此处会显示并允许快速编辑;但提交仍使用原resourceId,后端在应用目标层 override 前先按资源默认assetKind="icon"拒绝,形成正常 UI 可达但必然返回 400 的路径。请统一双端语义:若默认 icon 永远禁用,前端也必须检查resourceAssetKind;若同媒体族 override 应生效,后端需按同一有效类型判断。请补“icon 默认类型 + scene/spec override”的端到端测试。复审当前 head
9ba3cda0b4ac188d93c5b9a7a4f6c62458ec59bf后,仍有两项需要收口:ImageCanvasUploadUtils.ts已把普通图片的持久化assetKind改为undefined,但useImageCanvasUploadWorkflow.test.tsx仍要求assetKind: "image"。这不是单纯的 CI 波动,而是当前实现与既有上传工作流契约不一致;请确认最终语义并同步实现、测试和相关契约。"image" → null规范化,实际范围明显超出“统一画布快速编辑白名单与入口行为”。请至少更新 PR 标题/正文和相关文档,明确迁移范围、兼容语义及验证;更建议把全局迁移拆成独立变更。完成后请更新 head 并重新请求审查。
请求修改:请先解决普通图片
assetKind新语义与上传工作流现有测试/契约不一致的问题;同时明确或拆分当前分支包含的全局"image" → null持久化迁移范围。完成后请更新 head 并重新请求审查。解释: 在codex审查时发现前端对assetkind: "image" 没有加入到白名单里, 我之前做动作迁移的时候有关注过assetkind的类型, 一般的图片是作为"未知"处理

我追踪这个新增的"image":
发现我在把这个表达式重构成switch时(
a03f13d0)想当然地加了"image" 而不是原来的undefinedcodex因此在后端也加了这个assetkind
a03f13d0提交没有上线, 不必迁移, 我彻底清理一下
迁移脚本
scripts/spacetime-clean-editor-image-asset-kind.mjs请求修改(复审 head
44604cf82c0d642a0c5a620125d1d1679211acb9):[P1]普通图片 assetKind 清理会改写项目资源、legacy 双份 layout 和结构化图层,但apply_editor_image_asset_kind_canvas_plan没有同步更新 activeeditor_canvas_layout_migration的structured_layout_sha256、integrity 与 verified revision。迁移完成后,V2 结构化画布下一次保存会被verify_migration_layout判定为“结构化数据已漂移”。请同步维护迁移记录,并补充“active structured canvas 清理后仍可正常保存”的回归测试。[P1]迁移清零后,登录态create_editor_project_resource/create_editor_asset以及 SpacetimeDB storage 仍会原样接受assetKind="image"。旧页面、滞留请求或直接 API 调用可立即重新制造被废弃值,而快速编辑白名单仍拒绝它。请在 API 与 storage 权威写边界将精确"image"归一为None,或明确失败关闭,并增加写入门禁测试。[P2]权威运维文档要求运行npm run spacetime:editor-image-asset-kind:clean -- ...,但当前package.json没有该 script,迁移按文档无法启动。请补齐 npm script,或把权威文档及运维门禁统一到真实可执行的node scripts/spacetime-clean-editor-image-asset-kind.mjs。当前迁移已具备 operator 鉴权、分批 dry-run、批次哈希绑定和 apply 后零残留复核,方向合理;请修复上述迁移一致性与入口问题后更新 head,并重新请求审查。另请更新 PR 正文,明确当前 59 文件、
+3452/-599中包含的生产数据迁移和维护窗口范围。请求修改。当前 head 已补上普通图片类型迁移命令、创建入口写入归一化和画布迁移摘要刷新,但仍有两处会破坏迁移安全与数据不变量:cleanup 在校验旧 migration 前直接用当前数据重签摘要,能够把既有漂移洗成有效;legacy 画布保存仍能从客户端 layers 中提取 assetKind="image" 并重新写回 project resource。请按行内方案先验证旧凭证再做受控重签,并把普通图片类型归一化下沉到所有持久化写边界后重新请求 review。
@@ -8358,6 +8400,10 @@ fn normalize_optional(value: Option<String>) -> Option<String> {.filter(|item| !item.is_empty())}fn normalize_editor_persisted_asset_kind(value: Option<String>) -> Option<String> {[P1] 把
image → None不变量覆盖到 legacy 画布保存写路径这个 helper 目前只用于 resource/asset 创建,
persist_editor_project_layout_inner → migrate_canvas_layer_asset_metadata → migrate_project_resource_asset_metadata仍会把客户端 layer 中的assetKind="image"写回空的editor_project_resource.asset_kind。建议在 legacy 字段提取时区分“字段存在”和“归一化结果”:先记录had_asset_kind,删除字段后对值调用本 helper;即使结果为None也要把布局标记为 changed,保证旧字段被移除。同时在migrate_project_resource_asset_metadata写表前再次调用本 helper作为持久化边界兜底。请补save_editor_project_layout_v2回归:legacy canvas/resource 初始asset_kind=None,提交含assetKind="image"的 layer 后,保存布局不再含该字段且 resource 仍为None。@@ -12430,0 +13163,4 @@&resource_asset_kinds,)?;let integrity = canvas_layout_integrity(structured_layout_json.as_str())?;let migration = refresh_editor_image_asset_kind_layout_migration([P1] 清理前先验证旧 migration,不能直接用现状重签
这里应把流程改为“校验旧状态 → 计算受控 patch → 写入 → 校验新状态 → 更新摘要”。在任何写入前,按 migration status 对修改前数据执行校验:
active必须同时核对 legacy shadow 的 source hash,以及当前 structured revision/hash/count/resource refs;backfilled必须核对 legacy 与 structured 两侧仍与原凭证一致且语义等价;rolled_back必须沿用重复 rollback 的完整复检。只有旧凭证有效且差异仅来自本次删除精确image字段时,才允许重算摘要并保留原 status。当前refresh后再调用verify_migration_layout只是用刚写入的值自证,发现不了既有漂移。请补真实 DB 状态机测试,至少覆盖 active stale 拒绝、backfilled 双侧漂移拒绝、rolled_back 重入拒绝,以及合法 cleanup 后 save/activate/rollback 仍通过。请求修改。当前 head 已修复 review 204 中“先重签后校验”和 legacy 保存回写
assetKind=image的两项问题,且 CI run 971 四组全绿、与 live master 的 merge-tree 无冲突;但生产清理仍有两处失败关闭缺口:结构化 generation-dialog 的权威dialog_json没有进入扫描与 patch,能够残留旧值并错误通过最终零残留复核;project-resource阶段又会静默跳过缺 migration 记录的结构化画布,先清资源再留下不可正常保存的半迁移状态。请把两类权威状态纳入 dry-run、批次 hash、原子 apply 与回归测试后更新 head,再重新请求审查。@@ -12430,0 +13340,4 @@};let structured_layout_json = build_structured_canvas_layout_json(ctx, &canvas)?;verify_editor_image_asset_kind_cleanup_migration_state_allowing_prior_resource_cleanup(&canvas,[P1] 结构化画布缺 migration 时不能直接跳过
同文件的状态校验已经明确把
layout_storage_version=STRUCTURED且 migration 缺失视为错误,但这里对所有缺记录的 canvas 都直接continue。若旧值只在 project-resource 行,当前批次会先把资源清空;后续 canvas 扫描又会因没有显式字段命中而直接返回,最终零残留复核可通过,但该结构化画布仍无法正常保存。请只允许 legacy version 0 无记录,对 structured + missing migration 失败关闭,并把该状态绑定进 dry-run/hash/apply 回归,证明资源行不会被修改。@@ -12430,0 +13457,4 @@.find(&canvas.project_id);let mut layer_rows = ctx.db.editor_canvas_layer()[P1] 把结构化 generation-dialog 权威副本纳入清理
这里的 canvas plan 只读取
editor_canvas_layer,遗漏了editor_canvas_generation_dialog.dialog_json。结构化解析会把 generation-dialog 的顶层扩展字段原样存入该列,因此历史assetKind=image可以留在权威 structured 副本;当前 apply 只改 legacy JSON 和 layer rows,随后还能用仍含旧值的 structured JSON 重签 migration。下一轮扫描继续不读 dialog rows,最终matched=0会误报零残留。请把 dialog rows 纳入扫描、批次 hash 和原子 patch,并补 active 画布回归,证明 rebuilt structured layout 不再残留该字段。