diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 72f32df85..d5336eb7b 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -16,6 +16,21 @@ --- +## 2026-07-31 图集切片按需编码并批量确认持久化 + +- 背景:`2026-07-29 图集切片必须受前置容量和有界 CPU 保护` 收口了连通域数量与 CPU 并发,但切片仍在一次循环里全部裁剪并编码,最多 64 份 PNG 字节连同整张 RGBA 同时驻留内存;持久化又按切片逐个调用 procedure,N 片至少 2N 次写入外加一次 cohort 完成,任一片失败都会留下已确认的部分记录。手动拆分入口另有一处重复鉴权:`get_editor_project` 已经取回并定位了来源资源,随后仍走 `parse_editor_reference_image` 按注册 ID 再解析一次,触发全账号项目与素材库扫描。 +- 编码与内存决策:`platform-image` 把切片拆成 `prepare` 与 `encode` 两步,`prepare` 只计算带 padding 的裁剪边界并持有 `Arc`,`encode(index)` 被调用时才裁剪并编码单片。裁剪阶段累计 padding 后像素,超过调用方传入的上限即在任何编码前返回 `TotalCropPixelLimitExceeded`;api-server 传 `EDITOR_ICON_SPRITESHEET_MAX_TOTAL_CROP_PIXELS = EDITOR_ICON_SPRITESHEET_MAX_PIXELS * 4`(`16777216` 像素),映射为 `422` 与 `crop-pixel-limit-exceeded`。编码与上传由 `buffer_unordered(EDITOR_ICON_SPRITESHEET_UPLOAD_MAX_CONCURRENCY)`(`2`)串起,同时最多两片 PNG 在内存中。 +- 准入决策:新增独立于既有 CPU 信号量的 `EDITOR_ICON_SPRITESHEET_MEMORY_LIMITER`(`EDITOR_ICON_SPRITESHEET_MEMORY_MAX_CONCURRENCY = 2`)。手动拆分在创建下载客户端和发起下载**之前**取得该许可,许可覆盖「下载 → prepare → 逐片编码 → 逐片上传」整段,在进入 SpacetimeDB 批量调用前显式释放,避免数据库慢调用继续占用整张 RGBA。门限不可用返回 `503`、等待超预算返回 `504`,两者共用既有 `slice-processing-timeout` code。自动生成路径复用同一许可,但其源图此前已在内存中,该许可只保护解码与连通域阶段,不覆盖下载。 +- 持久化决策:新增 procedure `persist_editor_spritesheet_slice_batch_and_return`,在单个事务内依次确认每片的 asset object、可选项目资源、可选账号素材,并在存在 `group_task_id` 时一并完成 cohort;每次拆分请求只调用一次。批次上限 `EDITOR_SPRITESHEET_SLICE_BATCH_MAX_ITEMS = 64`,写入前校验数量与 `expected_asset_count` 一致、批内 `assetObjectId / objectKey / resourceId / assetId` 不重复、`source_resource_id` 指向的既有资源存在且同 owner 同 project;需要完成 cohort 的批次必须每项都创建素材。切片记录 ID 由 `(ownerUserId, taskId, 切片序号)` 经 SHA-256 确定性派生,重放得到相同 ID,且只有既有记录与新输入逐字段一致时才幂等复用,否则报幂等键冲突。 +- 鉴权决策:手动拆分不再调用 `parse_editor_reference_image`,直接用已随 owner-scoped 项目读取取得的 `source_resource` 取 objectKey,典型路径的 SpacetimeDB 调用从 3 次降为 1 次。作为替代,新增显式三重断言——项目属于当前 owner、资源属于当前 owner、资源属于当前 project——任一不符返回 `403`。结构断言禁止该区间再出现 `parse_editor_reference_image` 或 `list_editor_projects`。 +- 传输边界:新增 `build_editor_spritesheet_http_client(connect, request)`,下载与上传共用同一组常量 `EDITOR_ICON_SPRITESHEET_UPLOAD_CONNECT_TIMEOUT = 10s`、`EDITOR_ICON_SPRITESHEET_UPLOAD_REQUEST_TIMEOUT = 60s`。 +- 影响范围:`server-rs/crates/platform-image/src/generated_asset_sheets/`、`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`server-rs/crates/spacetime-client/src/editor_project.rs` 及生成的 module bindings;图标图集手动拆分与自动拆分链路。新增 SpacetimeDB procedure 与输入输出类型,需要重新生成绑定。 +- 验证方式:`platform-image` 覆盖 prepare 不编码且 `Send + Sync`、并发编码多个 index 结果不变、累计裁剪像素在编码前拒绝;`api-server` 覆盖切片记录 ID 稳定且按 owner / index 分区、自动路径保留处理超时告警码、上传超时释放内存许可;`spacetime-module` 覆盖批次校验的完整 cohort、重复 objectKey、来源资源同 owner 同 project、部分 cohort 拒绝与重放只在内容一致时复用。 +- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、本文件 `2026-07-29 图集切片必须受前置容量和有界 CPU 保护`。 +- 补记说明:本条为事后补写,记录提交 `cf1a02312` 已落地的行为,不改变其任何决策。 + +--- + ## 2026-07-30 抠图实际后端作为 generationInputs 顶层内部元数据保存 - 背景:角色、图标图集和 UI 图集抠图派生资产需要保留最终实际执行的处理后端,供后台诊断 BgFilter、阿里云通用抠图和本地键色的降级结果;把抠图模型写成 `generationInputs.fields` 的“处理模型”会进入图片信息,与用户可见输入快照语义冲突,而覆盖正式资产 `model` 又会丢失源生图模型。