合并主线并解决无限画布冲突

合并主线 Runtime、编辑器与 SpacetimeDB 最新改动
保留无限画布资源投影、布局与焦点恢复合同
统一共享画布历史和生成对话框类型
同步更新技术方案、项目记忆与回归测试
This commit is contained in:
2026-08-07 09:33:27 +08:00
176 changed files with 31497 additions and 1855 deletions
@@ -2991,7 +2991,7 @@
"none",
"pixelArt"
],
"description": "可选生成后处理风格,当前识别 none 与 pixelArt。省略、null、空字符串或 none 按无风格处理;pixelArt 仅支持普通图片(kind 省略)和 character。未知字符串或不支持该风格的 kind 按 none 继续生成并返回 unsupported-image-style 告警;非字符串值返回 400。"
"description": "可选生成风格,当前识别 none 与 pixelArt。省略、null、空字符串或 none 按无风格处理,提交给 provider 的提示词与未带该字段时逐字一致;pixelArt 仅支持普通图片(kind 省略)和 character,会在提示词末尾追加一行像素风约束(普通图片为「画面为像素风格」,character 为「角色主体为像素风格」)并在回图后执行像素规整。未知字符串或不支持该风格的 kind 按 none 继续生成并返回 unsupported-image-style 告警;非字符串值返回 400。"
},
"size": {
"type": "string",
@@ -3047,7 +3047,7 @@
"type": "array",
"items": {
"type": "string",
"description": "当前账号的 objectKey、项目资源 ID 或素材 ID;本地临时图必须先上传 OSS 再提交。禁止 Data URL / Blob URL。普通生成最多使用前 5 张,数组上限为 9。"
"description": "当前账号的 objectKey、项目资源 ID 或素材 ID;本地临时图必须先上传 OSS 再提交。禁止 Data URL / Blob URL。普通生成最多 5 张;kind=quick-edit 时 gpt-image-2 最多 5 张、nanobanana2 最多 9 张。超限返回 400,不会静默截断。"
},
"maxItems": 9
},
@@ -3241,7 +3241,7 @@
"type": "array",
"items": {
"type": "string",
"description": "当前账号的 objectKey、项目资源 ID 或素材 ID;本地临时图必须先上传 OSS。禁止 Data URL / Blob URL。"
"description": "当前账号的 objectKey、项目资源 ID 或素材 ID;本地临时图必须先上传 OSS。禁止 Data URL / Blob URL。sourceImageSrc 占用 1 张 provider 容量,因此 gpt-image-2 最多再提交 4 张、nanobanana2 最多再提交 8 张;超限返回 400,不会静默截断。"
},
"maxItems": 8
},
@@ -3372,7 +3372,7 @@
"type": "array",
"items": {
"type": "string",
"description": "额外图标素材参考图的稳定引用:objectKey、项目资源 ID 或素材 ID;本地临时图必须先上传 OSS。禁止 Data URL / Blob URL。最多 8 张。"
"description": "额外图标素材参考图的稳定引用:objectKey、项目资源 ID 或素材 ID;本地临时图必须先上传 OSS。禁止 Data URL / Blob URL。referenceImageSrc 占用 1 张 provider 容量,因此 gpt-image-2 最多再提交 4 张、nanobanana2 最多再提交 8 张;超限返回 400,不会静默截断。"
},
"maxItems": 8
},
@@ -3405,7 +3405,7 @@
"none",
"pixelArt"
],
"description": "可选生成后处理风格,当前识别 none 与 pixelArt。省略、null、空字符串或 none 按无风格处理;pixelArt 启用图标图集像素规整。未知字符串按 none 继续生成并返回 unsupported-image-style 告警;非字符串值返回 400。"
"description": "可选生成风格,当前识别 none 与 pixelArt。省略、null、空字符串或 none 按无风格处理,提交给 provider 的提示词与未带该字段时逐字一致;pixelArt 会在提示词末尾追加一行「每个图标素材均为像素风格」并启用图标图集像素规整。未知字符串按 none 继续生成并返回 unsupported-image-style 告警;非字符串值返回 400。"
},
"model": {
"type": "string",
@@ -3496,7 +3496,7 @@
"type": "array",
"items": {
"type": "string",
"description": "额外 UI 素材参考图的稳定引用:objectKey、项目资源 ID 或素材 ID;本地临时图必须先上传 OSS。禁止 Data URL / Blob URL。最多 5 张。"
"description": "额外 UI 素材参考图的稳定引用:objectKey、项目资源 ID 或素材 ID;本地临时图必须先上传 OSS。禁止 Data URL / Blob URL。sourceImageSrc 占用 1 张 provider 容量,因此 gpt-image-2 最多再提交 4 张、nanobanana2 最多再提交 5 张;超限返回 400,不会静默截断。"
},
"maxItems": 5
},
File diff suppressed because it is too large Load Diff
@@ -81,9 +81,19 @@ cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml real_
修改 game-chat release flavor 后,至少执行壳配置门禁、AppSurface game-chat 定向测试、前端类型检查、AppData / 诊断日志 / release flavor 相关 Rust 定向测试、`npm run check:encoding` 和 `git diff --check`。打包 smoke 必须确认:安装信息和产物版本为 `0.1.1`;无参数启动直接进入且只能停留在 game-chat 页面;停止或断开 `api-server` 后本地工作台仍能打开;普通 dev / release 与 debug game-chat 仍走原认证入口;独立 AppData 生效。预览 smoke 应先让当前 run 成功验证 revision N,确认 Tauri registry 启动一个 server 且 iframe 自动出现;在 validate 后、start 取得锁前推进项目 revision,必须确认原子 `expectedRevision` 门禁拒绝启动且授权保留等待新证据;再验证 revision N+1,确认 server 进程和 loopback origin 不变、iframe 显示新版本且响应为 `no-store`。same-run steer 还要覆盖旧验证不消费新授权、旧异步 attempt 不清新 generation;Runner registry 单独 running、失败 / 相同 / 更低 revision 均不能触发用户预览或重复刷新,停止后顶部显示“预览未启动”。独立包退出时必须通过 `runner.shutdown_for_client_exit` 先进入 draining 再结束本 boot,保留 durable sidecar 供下次 reconciliation,不把中断任务写成 completed;Windows Runner 必须 `CREATE_SUSPENDED -> AssignProcessToJobObject -> ResumeThread`,分配或恢复失败时 kill + wait,客户端持有 kill-on-close Job 兜底,关闭主窗口后 Runner、MCP、command、ConPTY 及其后代都应消失。普通 dev / release 和 CLI 继续使用 `runner.shutdown_if_idle`。
game-chat 迭代还必须确认以下行为:Supervisor ready 输出、`design-director / art-director / art-asset-plan / code-director / code-prototype / preview-readiness / preview-playtest` 七个专业 Agent 的 durable `final-reply`,以及 Rust 明确生成 `eventId + publicText` 的每条公开 Runtime 输出都作为独立 assistant 消息逐条固化在项目聊天。消息通过顶层 `messageId` 幂等追加,事件 / 轮询 / hydration 重放不重复。前端禁止从原始 `summary / detail`、tool plan、Provider / Runner 元数据、命令输出或路径自行拼接持久消息;UI 把一个父 Run 统一显示为“本轮生成进度”,最新状态也不得暴露内部“第 N 轮”,完整 GUI / CLI 任务图可显示 `x/14`,首版快车道显示 `x/7`,终态不保留运行中进度卡;可信 `project-supervisor-game-chat` 一轮完成 `preview-playtest` 后直接收束,不请求下一次 Provider tool-plan;所有调度入口均不得暴露或启动 `publish-strategy` / `publish-package`。game-chat 必须配置可用的 External Editor API,按 `art-spec.png -> icon-spritesheet -> art-spritesheet.png + iconImageSrcs 本地切片 -> code-prototype Canvas 使用四类切片` 推进;缺少任一环节都必须失败关闭。
game-chat 迭代还必须确认以下行为:Supervisor ready 输出、本轮实际启动的 `design-director / art-director / art-asset-plan / code-director / code-prototype / preview-readiness / preview-playtest` 专业 Agent 各自产生 durable `final-reply`,条件路由跳过的节点只要求 manifest 正确投影为 completed,不伪造 Agent 回复;Rust 明确生成 `eventId + publicText` 的每条公开 Runtime 输出都作为独立 assistant 消息逐条固化在项目聊天。消息通过顶层 `messageId` 幂等追加,事件 / 轮询 / hydration 重放不重复。前端禁止从原始 `summary / detail`、tool plan、Provider / Runner 元数据、命令输出或路径自行拼接持久消息;UI 把一个父 Run 统一显示为“本轮生成进度”,最新状态也不得暴露内部“第 N 轮”,完整 GUI / CLI 任务图可显示 `x/14`,首版快车道显示 `x/7`,终态不保留运行中进度卡;可信 `project-supervisor-game-chat` 一轮完成 `preview-playtest` 后直接收束,不请求下一次 Provider tool-plan;所有调度入口均不得暴露或启动 `publish-strategy` / `publish-package`。固定关键词与资产探测只进入 Supervisor 的 advisory context;没有持久 Supervisor 路由时不得启动任何 child。只有程序侧审计判定存在真实缺口或 Supervisor 明确选择整体重做时才要求可用 External Editor API,并按 `art-spec.png -> icon-spritesheet -> art-spritesheet.png + iconImageSrcs 本地切片 -> code-prototype Canvas 使用四类切片` 补齐;`use-existing-art` 不得重复生成或扣费,但仍须通过相同正式资产和代码可见使用门禁。
game-chat 素材完整快车道采用七任务口径。父 Run 与全部 child Run 共用 4200 秒软预算和 4500 秒累计硬上限;先确定性生成并登记 `art-spec.png`,再由 `art-asset-plan` 通过专用 icon-spritesheet 路由生成透明 `art-spritesheet.png`,下载并持久化响应中的独立切片,最后才允许 code-prototype 写入或局部修复入口。软预算后只允许使用已登记图集、当前 resourceId 对应切片清单的确定性本地 fallback、`game.static_smoke` 和 `preview.validate`;不得退回普通生图、猜测 atlas 网格或纯代码核心画面。完成门要求活动 Canvas 分别绘制 player、blocks-and-targets、obstacles-and-scene、feedback-effects 四类不同切片;整图 `<img>`、CSS background、完整图集直绘、单个猜测裁切和路径诱饵均失败。对应定向测试至少包括:
Project Supervisor 根后台任务还必须验证公开消息硬门:任务先落为不可执行的 `preparing / public-status-pending`,项目 conversation 中存在且仅存在一条同 run 的 `runtime-public-status-* / accepted` 后才能转为 `pending / queued`;恢复 preflight 只验证并临时分类可恢复状态,不改写 task/conversation,真实 resume 持有 Agent 锁后才可把 accepted preparing 持久提升为 `pending / queued`;否则不得执行。若该消息无法持久化,task 必须进入 `failed / public-status-write-failed` 且 Provider、工具与 child 调度均为零。这类 Runtime 公开状态不得合入 Agent prompt。根 Supervisor 通过正式失败 / 预算耗尽收束或 game-chat 绝对硬期限进入 reconciliation 时,即使后续 task/event/state 写入失败,也必须已经存在一条脱敏终态失败消息;专业 Agent 命中全局硬期限时,项目 conversation 仍必须幂等保留根终态,但 child 终态只能写入与权威 task 的 agent/run/session、两个 parent 和 binding 全部一致的私有 Session;自称根 agent/run 却 session 或 parent 不符时失败关闭。只有根 Supervisor 的 `turn.started / turn.failed / turn.budget_exhausted` 不得再形成第二条聊天消息,专业 Agent 的公开启动事件仍应显示。非 Supervisor 专业 Agent 的失败状态留在对应 Agent Session,不得把私有诊断送进项目 conversation。定向复验至少运行:
提交前还要覆盖启动崩溃窗口:仅 `preparing` 时 preflight/resume 均不执行;用户消息已持久但 accepted 缺失时,preflight 只报可恢复且 task/conversation 不变,resume 才补写 accepted 并转为 `pending / queued`。还要验证用户消息或 accepted 已落盘但审计失败均继续入队、根终态首次公开写入瞬时失败会用同 ID 重试、receipt / isolated-join 失败只有一条公开结果,以及同秒连续任务通过共享的 run 关联摘要按 `user -> accepted -> terminal` 逐任务展示。
```bash
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml public_start_status -- --test-threads=1
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml structured_plan_state_write_failure_stops_before_context_and_audit -- --test-threads=1
npm run test -- apps/ai-game-creator-shell/tests/agentRuntimeModel.test.ts apps/ai-game-creator-shell/tests/appSurface.test.ts
```
game-chat 条件快车道仍采用七任务口径,但执行顺序由持久合同控制。父 Run 与全部 child Run 共用 4200 秒软预算和 4500 秒累计硬上限;Supervisor 首轮先调用 `agent.route_manifest`,决策前零 child。`audit-existing-first` 的首波仅为 `design-director + code-director`,已登记但无效的旧派生视觉不得阻断这两项审计启动;code-director 必须先 `asset.list`,读取 Supervisor 已持久化的 authoritative 决策,再提交正式资产覆盖合同。覆盖完整时跳过图片生成,存在缺口时只开放缺口对应 owner,`regenerate-art` 才强制重新开放两个美术 owner;已登记但校验失败的固定资产只允许当前路由绑定的 canonical owner 原位替换。新生成仍按 `art-spec.png -> art-spritesheet.png + 独立切片` 推进,最后才允许 code-prototype 写入或局部修复入口。软预算后只允许使用已登记图集、当前 resourceId 对应切片清单的确定性本地 fallback、`game.static_smoke` 和 `preview.validate`;不得退回普通生图、猜测 atlas 网格或纯代码核心画面。完成门要求活动 Canvas 分别绘制 player、blocks-and-targets、obstacles-and-scene、feedback-effects 四类不同切片;整图 `<img>`、CSS background、完整图集直绘、单个猜测裁切和路径诱饵均失败。对应定向测试至少包括:决策前零 child、旧无效视觉不阻断首波、首波无美术 child、code-director 收到真实持久策略、完整覆盖零生成、仅缺图集只运行 `art-asset-plan`、无效已登记资产由 owner 原位替换、art spec 缺失导致引用合同失效时同时补齐两个槽位、显式重做原位替换两个正式资产、旧 root/fingerprint/缺口或重复路由失败关闭,以及补齐后恢复 code-prototype。
失败续跑还必须覆盖同 Session 同 source 继承、跨 Session / 跨 source 不继承、首次与连续 successor 的 effective task / contract / scheduler 一致性,以及中英文纯继续短语使用同一识别函数。非占位入口的新 `code-prototype` 必须先产生本人 mutation 再 smoke;连续只读 smoke 不得收束。占位 fallback 只允许显式支持的真实玩法模板,俄罗斯方块必须验证棋盘、下落、旋转、锁定和消行语义,未知玩法必须失败关闭。
@@ -654,7 +664,7 @@ npm run ai-game-creator-shell:agent-runtime:supervisor-swarm-tool-plan-handoff-r
## AI 游戏创作长耗时与退出恢复验证
- 修改 autonomous 调度时,必须证明缺省 policy 不产生 manifest 之外的首波专业委派,Editor Key、已有/损坏视觉资产均不能改变该事实;显式项目 policy 只验证“原样尊重”,不能由 Runtime 隐式扩充。
- 修改 autonomous 调度时,必须证明缺省 policy 不产生 manifest 之外的首波专业委派,Editor Key、已有/损坏视觉资产均不能改变该事实;game-chat 还必须证明持久 Supervisor 决策前零 child,决策后的首波只有 `design-director + code-director`,固定关键词不能直接改 Graph。显式项目 policy 只验证“原样尊重”,不能由 Runtime 隐式扩充。
- 修改预览完成门时,分别覆盖 child `preview-readiness` 当前 revision smoke、child `preview-playtest` 到根 Supervisor 合同的 browser receipt,以及 WebSocket 启动前退出的稳定基础设施分类。基础设施错误必须在一次浏览器调用后让 run 失败,不能只做到后续调用快速失败而继续消耗 Provider 轮次。
- 修改 game-chat Runner 生命周期时,至少覆盖 busy durable sidecar 拒绝 client-exit、拒绝后 `draining=false` 可继续执行、清空后 idle shutdown、重复 shutdown 幂等;Windows target check 继续保留,不能以删除 Job Object 或放任后台继续来规避 reconciliation。
- LLM 配置回归必须穷举全部规范 Agent,校验无遗漏/重复、显式 patch 覆盖默认,并锁定 GUI 展示映射和 Rust resolver 一致;模板与 GUI 初始草稿不得把规范默认持久化成 `agentLlm` 显式覆盖。`--llm-status` 要输出逐 Agent 实际 reasoning/timing/retry 值。运行日志与当前配置冲突时,先区分 durable run snapshot 和后来修改的文件,不能按当前文件反推历史请求。
+121 -4
View File
@@ -62,6 +62,77 @@
- 验证:使用 deferred commit/layout Promise,依次在请求后切项目、切 run/overview、新开 session、改选择和改筛选;断言 manifest 只更新对应项目,新资源仍进入投影/布局,但所有失效守卫都不切中央状态、不改选择、不清查询。条件未变化且资源可见时才自动定位。
- 关联:`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`。
## Runtime 状态写失败不能发生在公开失败消息之前
- 现象:用户提交长任务后只看到运行失败或任务直接消失,聊天里一条有用消息都没有;另一些失败又同时出现 Runtime event 和 conversation 两条近似提示。
- 原因:启动确认依赖实际 `turn.started`,任务只入队或 Runner 在 start transition 前失败时没有公开回执;终态失败先写 task/event/state,最后才由各 main-loop 分支尽力追加 assistant。坏掉的若正是状态文件,流程会在公开消息前返回;散落的 `let _` 又无法提供幂等身份。
- 处理:用户直接投递的 Project Supervisor 根任务先落为不可执行的 `preparing / public-status-pending`,持久化同 run 的 `runtime-public-status-* / accepted` 后才转为 `pending / queued`;恢复 preflight 只验证业务状态,不改写 task/conversation,仅 resume 持有 Agent 锁后才可持久提升。根 Supervisor 通过正式失败 / 预算耗尽收束或 game-chat 绝对硬期限进入 reconciliation 时,先写相同协议的终态消息,再处理 Runtime 其它投影。专业 Agent 命中全局硬期限时,应由权威根 task 在项目 conversation 中幂等写根终态,child 仅在身份完整匹配时写私有 Session;根 agent/run 的 session 或两个 parent 冲突时必须失败关闭。消息正文只能来自封闭脱敏映射,prompt 构建必须按稳定前缀排除;前端按前缀标记 Runtime-owned,只过滤根 Supervisor 的 `turn.started / turn.failed / turn.budget_exhausted`,不吞掉专业 Agent 启动进度。模型仍负责业务 commentary/final,Runtime 的两条硬门不扩展成业务判断器。
- 恢复补充:不能把“accepted 还没写完”等同于“用户从未投递”。用户消息已持久时,真实 resume 必须补写 accepted 后才入队;用户消息或 accepted conversation 已存在时,后续审计失败不得留下“正在启动”但永不执行的假状态,但同 message ID 的 role/content 冲突必须把 task 明确收束为 `conversation-write-failed`。根终态首次公开写入的瞬时失败必须在终态投影后用相同 message ID 重试。带 parent 的 Supervisor continuation 不得同时产生 Session 终态和 Runtime 事件两条公开消息;秒级时间戳下必须以 task/status message ID 共享的 run 关联摘要排序,不能用不同消息类别的计数猜测顺序。
- 验证:任务 journal 必须显示 `preparing -> pending`,仅有 accepted 时恢复才可提升;破坏项目 conversation 时断言任务为 `public-status-write-failed` 且无可运行 pending;破坏 Runtime state 路径时断言公开失败已经存在;重复写同一 run/status 只有一条 message ID;渲染实际 prompt 断言不包含 Runtime 公开状态;AppSurface 证明根启动/失败事件不重复,专业 Agent 启动仍可见。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs`、`agent/runtime_driver/task_start.rs`、`src/features/agent-runtime/model.ts`、`src/features/project-workspace/SupervisorChatOnlyView.tsx`。
## manifest 被旧快照写回 Pending 时不能让父 Run 抛下真实运行中的 child
- 现象:`code-prototype` 已有确定性 child run、running journal 和工具事件,父 Supervisor 却在几秒后以 fixed graph stalled 失败;child 随后完成代码与静态检查,但 completion gate 持续报告 `task=code-prototype status=pending`,最后 `loop-budget-exhausted`。
- 原因:并发 hydration 或其它旧 manifest 快照把 scheduler 已写的 running 覆盖为 pending;父 Run 把“当前不宜调度新 child”错误等同于“没有 child 需要等待”,并只以 manifest 状态判断 DAG 活性。父先终态后,真实 child 也失去正常投影窗口。
- 处理:调度与等待分离。新调度可以被派生视觉修复等门禁阻止,但当前最新活跃根 Run 下,只要 durable child 具有确定性 runId、scheduler source、正确父链接且 journal 仍处于 queued/running,父 Run 就继续等待;同一 child 的完成门可容忍 manifest pending,但仍执行正式产物、revision、静态检查与试玩证据门禁。更新根 Run、GUI/CLI、终态/确认/reconciliation child 或绑定冲突全部失败关闭。
- 验证:人工把当前 child 的 manifest 状态回写 pending,断言父 DAG 仍 in progress、父上下文可持久化为 `waiting-for-manifest-tasks`、child 可投影 completed;随后创建更新根 Run,断言旧 child 不再保持 DAG 活性且 completion blocker 恢复 `status=pending`。不要靠增加 loop 次数或伪造 completed 掩盖竞态。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`。
## 固定关键词提示不能代替 Supervisor 选择条件任务图
- 现象:用户只说“现在没有用到任何美术资源”,Graph 就在 Supervisor 输出任何计划前自动打开美术节点;或者用户想复用现有素材,Runtime 直接按关键词预完成节点。Supervisor 无固定计划时随即 `fixed-task-graph-stalled`,看起来像模型不理解意图,实际上模型根本没有获得决策机会。
- 原因:同一套关键词函数同时承担 prompt hint、Graph reset、baseline 豁免和历史试玩类型继承,启发式信号越过 Supervisor 成为了控制面真相;main loop 又在 Provider 请求前优先调度 ready task。
- 处理:启发式结果只序列化成 `advisoryOnly=true` 的 Supervisor context。Scheduler 以持久 `GameChatWorkflowDecision` 为首轮前置门;Supervisor 先选审计或整体重做,code-director 再用成功 `asset.list` 和 Runtime 复核的覆盖合同选择复用或精确补缺。Runtime 可以拒绝过期、伪造、遗漏或重复 route,但不得替 Supervisor 补写决定。
- 验证:直接使用用户原句,断言 hint 命中但 manifest 全部保持 pending、决策前零 child、Provider request 包含路由工具;决策后首波只有 design/code,code-director 未完成 asset.list/route 时不能完成;再分别覆盖完整复用、真实缺口和显式重做。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/game_chat_fast_path.rs`、`runtime_driver/main_loop.rs`、`runtime_driver/task_start.rs`、`runtime_protocol/autonomous_completion.rs`。
## 既有正式产物不能同时被快车道视为已完成、被本轮 baseline 门视为未变化
- 现象:增量任务已有完整美术图集,`art-asset-plan` 每轮都返回零 action 和“已验证交付”,但 completion gate 每轮都报告 `assets/manifest.art.json(unchanged-from-run-baseline)`;最终 child `loop-budget-exhausted`,随后 Graph 和父 Run 失败。日志中没有本轮 Provider request、tool plan 或 action receipt。
- 原因:Graph reset 无差别重新打开稳定的美术 owner 节点;快车道按“当前产物有效”判断完成,owner 完成合同则按“本轮必须修改 baseline 产物”判断完成,两套语义互相冲突。增加 loop 预算、伪造版本号或机械改写 manifest 都不能消除冲突,还会引入 verification loop、字段丢失或错误复用旧主题。
- 处理:关键词和资产探测只作为 Supervisor 的 advisory context,不能直接修改 Graph。根 Run 先持久化 `audit-existing-first / regenerate-art` 决策;前者只启动 `design-director + code-director`,由 code-director 在成功 `asset.list` 后提交权威覆盖合同和精确缺口。Runtime 验证合同后才把已有 owner 投影为 completed,或只打开缺口 owner;根完成门只对持久路由明确复用的 art manifest 忽略 baseline 相同,所有结构、Canvas、私有回执、切片和可见使用验收继续失败关闭。
- 验证:先断言 Supervisor 决策前零 child、固定关键词不会预完成节点,再覆盖完整复用、仅缺图集和明确重做。还要直接经过父完成门,证明合法持久路由不再出现 art baseline gap,并证明删除切片后覆盖合同拒绝复用;旧 root、错误 fingerprint、虚构或遗漏缺口和重复改写路由都应失败关闭。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/game_chat_fast_path.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`。
## 执行锁移交给未确认启动的异步 future 会制造永久 queued
- 现象:父 Supervisor 与 Runner 一直显示运行中、heartbeat 正常,专业 Agent 已有 `background_task.queued` 和 `autonomous_ready_task.scheduled`,对应执行锁也被 Runner 持有,但该 child 永远没有 running journal、`turn.started` 或后续 Runtime event;其它同批 Agent 可能已经完成。
- 原因:ready scheduler 把 per-Agent 执行锁直接 move 进 fire-and-forget Tokio future,并在 future 首次 poll 前返回成功。锁移交不是启动确认;future 未进入 start transition 时,常规 wake/recovery 又拿不到同一把锁,queued task 因而没有任何接管者。预先占用 child locks 的测试会绕过真实 spawn 路径,无法发现该缺口。
- 处理:实际 Runner 必须在项目写锁外同步完成 pending -> running 与 started journal,成功且 execution worker 已开始轮询后才移交执行锁;启动/接管失败必须持锁完成 child failed 与 manifest Graph failed 投影,再释放锁并让 parent 收到错误。scheduled 诊断审计失败不能阻断 child 启动,external client 只负责 wake Runner。UI 另以 durable `startedAt` 和父/子最大活动时间显示运行态时长及疑似停滞;task-record fallback 不得把最新 record 时间写成 startedAt,父 Run terminal 后也不得被 child 晚到事件继续增加时长。
- 验证:使用真实空闲 child lanes 一次调度 `design-director / art-director / code-director`,在有界时间内逐一断言 running/`turn.started`,并验证幂等重调度不新增逻辑 Run;禁止只断言 scheduled 记录、锁文件或 Runner heartbeat。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_start.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_queue.rs`、`apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx`。
## Canvas 视觉门不能在没有资产词法候选时运行整套 JavaScript 语义分析
- 现象:Supervisor 已写入 `turn.started`,但第一条 planning 进度长期不出现;Runner 无 Provider 连接,单个 Tokio worker 持续占满一核。对项目现场复现时,15 KiB 的经典脚本在检查一个根本未被引用的切片文件时,超过 60 秒仍未返回。
- 原因:视觉门先为每个候选切片无条件执行模块依赖分析和函数可达性分析,最后才判断图片 `.src` 或已绑定 DOM 元素是否能指向目标资产。没有 `import` 关键字、没有目标文件名且没有已绑定图片元素时,这些全程序分析不可能产生有效视觉证据,属于纯浪费;复杂闭包与 alias 图会把浪费放大成看似 Runtime 停滞。
- 处理:仅做单向安全短路:JavaScript 原文同时没有大小写精确的 `import` 与 `export` 字节序列时,跳过模块依赖语义分析;纯 `export ... from` / `export * from` 仍是模块图依赖,不能误跳过。当前脚本不含目标文件名或任一已绑定 DOM 图片元素 ID 时,先低成本解码 `\\xNN`、`\\uNNNN`、`\\u{...}`、简单转义和续行;解码后仍无候选才直接判定没有绘制证据,解码不确定则保守进入 Oxc。这样必须保留 computed `s\\x72c`、转义资产 URL 与转义 `getElementById/querySelector`,同时不能因 HTML 中存在某个绑定元素让所有无关 JavaScript 单元进入重分析。
- 验证:无候选现场的同一切片检查必须有界返回且保持 `missing-visible-art-slice-use` 结论;同时覆盖相对路径、转义 URL、computed/转义属性与 DOM 方法、绑定 DOM 图片元素、含无关正则转义的脚本单元、路径大小写、纯重导出模块图、动态 import namespace 写入、未调用函数、恒假分支和真实可达 `drawImage`,证明短路只拒绝不可能命中的输入,不扩大验收权限。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`。
## ready-task 对账取消后不能让 successor 永久继承 failed Graph
- 现象:未知工具结果按安全边界进入 `needs-reconciliation`,人工核对后取消原 child;manifest 随即把该节点投影为 failed,父 game-chat 固定任务图明确失败。随后重试父 Run 虽创建同源 successor,却原样继承 failed manifest,几秒内再次失败,原项目无法继续。
- 原因:取消原 reconciliation Run 只负责安全释放 Agent 队列屏障,并不等于 manifest 任务完成;continuation 完成合同保留既有 Graph 进度,却没有区分“普通失败”和“已经人工核对、保留 cancel tombstone 的 reconciliation 取消”。
- 处理:旧 action 继续禁止重放或伪造 observation;旧 child 与父 Run 先真实终态。新 Supervisor continuation 仅扫描同 Session、同 source、同有效任务合同的历史根 Run,并要求对应 ready-task 同时存在 `failed / needs-reconciliation` 记录、最终 `cancelled` 记录和 durable cancel tombstone,才把当前 manifest 的同一 failed 节点恢复为 pending,让 scheduler 创建新 child Run。manifest 的读取、筛选、child 证据重验和写回放在同一项目写锁内;每个 task journal 只读取一次并按 parent Run 建索引。较新的无 child Run 默认阻断旧凭证,只有其 root journal 精确证明为旧 failed Graph 在进入 scheduler 前即失败时才允许向前查找;scheduler 自身失败不得被当成该兼容场景。
- 验证:构造 reconciliation child、人工 cancel tombstone、failed manifest 和终态父 Run,证明同源 continuation 只重排该节点;并列普通 failed 节点保持 failed,完成合同继续继承原任务 SHA 与项目 baseline,旧 pending action 不恢复。追加覆盖“旧 failed Graph 未调度”的中间 Run 可以跨过,而较新的 scheduler failure 即使没有 child journal 也会阻断更老 tombstone。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs`。
## `timeout_at` 不能替代显式的预算耗尽预检
- 现象:给完美像素加端点级并发闸后,预算已经耗尽的请求仍然能拿到许可,白占一个名额继续去打几轮全账号 SpacetimeDB 扫描,直到下载那步才失败。
- 原因:`tokio::time::timeout_at` 会先 poll 一次内层 future 再判超时。信号量有空闲许可时 `acquire_owned()` 首次 poll 就绪,于是即使 deadline 早已过去,返回的仍是 `Ok(Ok(permit))` 而不是超时。既有 `acquire_editor_pixel_art_cpu_permit` 里那句 `if Instant::now() >= processing_deadline` 正是为此存在,新写的许可函数漏掉后被单测抓出。
- 处理:所有「先判预算、再等资源」的获取函数都必须在 `timeout_at` 之前显式判一次 `Instant::now() >= deadline` 并直接返回超时错误;这句不是冗余防御。同理,进入排队计数之前也要先做这个预检,避免为注定失败的请求占用队列名额。
- 验证:在有空闲许可时用已过期的 deadline 调用获取函数,只断言返回 `504` 而不是许可;`504` 已足以证明显式预检没有被 `timeout_at` 的首次 poll 绕过。禁止在该用例里读取进程级队列 Atomic 的 before/after;相对断言同样会被并行测试插入。仅靠「信号量占满时超时」的用例发现不了这个问题。
- 关联:`server-rs/crates/api-server/src/editor_project.rs`(`acquire_editor_pixel_art_snap_permit`、`acquire_editor_pixel_art_cpu_permit`)。
## 有界等待队列的计数递减必须写在 Drop 里
- 现象:给同步端点加「最多 N 个等待者」的保险丝时,若把计数递减写在正常返回路径上,客户端断连或超时触发会让等待中的 future 被丢弃而跳过递减;计数只增不减,最终队列永久判定为满,接口对所有人返回 `503` 且不会自愈。
- 原因:Rust 的 async future 可以在任意 await 点被取消,取消时只保证 `Drop` 会跑,不保证后续代码会执行。有界队列的入场与离场天然不对称。
- 处理:把递增封进一个 guard 结构体,递减放在它的 `Drop` 实现里;递增本身用 `fetch_update` 的 CAS,不能用「先读后加」——两个线程同时读到 `max - 1` 各自加一就会越界。拿到资源后立即 `drop(guard)` 让出队列名额,不要让它跟着许可一起活到请求结束。
- 验证:单测覆盖 CAS 边界(满了返回失败且计数不越界、上限为 0 时任何进入都失败),并由独立用例覆盖 guard 离开作用域后的计数归还。预算耗尽路径只断言 `504`,不得通过另一个测试也会修改的进程级 static before/after 来推断“未入队”,也不得用串行锁或 `--test-threads=1` 掩盖隔离问题。
- 关联:`server-rs/crates/api-server/src/editor_project.rs`(`try_enter_bounded_queue`、`EditorPixelArtSnapQueueGuard`)。
## Linux 生产脚本门禁不能假设本地也是 GNU userland
- 现象:macOS 本地运行维护页、生产 API 部署和 Rust 产物门禁时,依次出现 `mv: illegal option -- T`、`mapfile: command not found`、`/usr/bin/cp` / `/usr/bin/chmod` 不存在,以及 `.rlib` 明明含有 `.o` 却报告“没有可扫描成员”;安全修复计划还会把 `/var/folders` 到 `/private/var/folders` 的系统别名误判为用户符号链接。
@@ -389,10 +460,18 @@
- 现象:产品要求画板 `生成角色动作` 返回后按透明序列帧播放和下载,但旧实现或旧测试可能继续把结果当作预览视频处理。
- 原因:后端仍需要先生成 `previewVideoPath` 再抽帧、绿幕去背和落 OSS;如果前端把预览视频当主媒体,就会绕过已经扣绿幕的 PNG 帧,也无法按序列帧打包下载。
- 处理:角色动作结果图层主 `src` 使用 `frames[0].imageSrc`,`mediaType` 固定为 `image-sequence`,`assetKind` 固定为 `character-animation`,完整帧列表写入 `imageSequenceFrames`,`previewVideoPath` 只作为来源信息保留。单图层下载必须生成序列帧 ZIP;画布素材 ZIP 中角色动作写入 `sequences/<编号-标题>/frames/`。不得移除后端原有视频生成、抽帧、绿幕去背和帧落盘流程。
- 验证:`ImageCanvasGenerationLayerModel` 应断言动作结果 `src` 为首帧且 `mediaType="image-sequence"`;画布集成测试应出现 `画布序列帧:角色动作` 图片播放器,不应出现角色动作 `<video>`;导出测试应断言角色动作下载和画布素材导出都包含序列帧 ZIP / frames 目录。
- 处理:角色动作结果图层主 `src` 使用 `frames[0].imageSrc`,`mediaType` 固定为 `image-sequence`,`assetKind` 固定为 `character-animation`,完整帧列表写入 `imageSequenceFrames`,`previewVideoPath` 只作为来源信息保留。生成端确认每帧对象后必须把该帧 `objectKey` 与 `assetObjectId` 一起写入正式 payload 和 `generation_inputs_json.characterAnimation.frames`。单图层下载必须生成序列帧 ZIP;画布素材 ZIP 中角色动作写入 `sequences/<编号-标题>/frames/`。不得移除后端原有视频生成、抽帧、绿幕去背和帧落盘流程。
- 验证:`ImageCanvasGenerationLayerModel` 应断言动作结果 `src` 为首帧且 `mediaType="image-sequence"`;画布集成测试应出现 `画布序列帧:角色动作` 图片播放器,不应出现角色动作 `<video>`;导出测试应断言角色动作下载和画布素材导出都包含序列帧 ZIP / frames 目录;生成测试还应断言首帧与非首帧的稳定引用都被保留。
- 关联:`src/components/image-editor/ImageCanvasGenerationLayerModel.ts`、`src/components/image-editor/ImageCanvasWorldView.tsx`、`src/components/image-editor/ImageCanvasExportModel.ts`、`server-rs/crates/api-server/src/character_animation_assets.rs`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
## OSS 导出兜底必须覆盖响应体读取阶段
- 现象:浏览器控制台显示 OSS `206 Partial Content` 后紧跟 `net::ERR_FAILED`,画布预览或素材导出失败,但没有出现预期的 `/api/assets/read-bytes` 兜底请求。
- 原因:`fetch(signedUrl)` 可能先返回一个 `ok` 的 `Response`,网络、浏览器 Range 缓存或传输错误随后才在 `arrayBuffer()` / `blob()` 消费响应体时暴露;如果直连保护边界只包住 `fetch` 和状态码,响应体失败会绕过 fallback。
- 处理:私有素材字节读取必须在直连 OSS 分支内完整消费响应体并重新构造可重复读取的 `Response`;换签、请求、非成功状态或响应体读取任一阶段失败时统一回退同源 `/api/assets/read-bytes`。Abort 仍应直接上抛,不能转化为额外服务器读取。
- 验证:前端服务测试模拟 OSS 返回 `206/ok`、但 `blob()` reject,断言随后请求 `/api/assets/read-bytes` 并返回 fallback 完整字节;同时保留直连成功、局部分片、直连非成功、请求 reject 和 Abort 边界。
- 关联:`src/services/assetReadUrlService.ts`、`src/services/assetReadUrlService.test.ts`、`src/components/image-editor/ImageCanvasExportModel.ts`。
## 图片画布序列帧播放不要复用普通图片淡入样式
- 现象:角色动作序列帧播放时看起来像每帧之间在渐变或闪烁。
@@ -3284,9 +3363,9 @@
- 现象:release 上 api-server 周期性出现全量 `spacetime_stage="pool_acquire" elapsed_ms=45000` 业务超时,`/readyz` 503(`reason=spacetime_unhealthy, stage=pool_acquire`),`/healthz` 仍 200,只有重启能恢复,过若干小时复发。
- 原因:旧 `PooledConnectionLease` 只能显式 `release_connection` 归还;HTTP 请求方在等待 StDB 回包期间断开时 handler future 被取消,permit 自动归还但槽位 `in_use` 永不复位。后续 acquire 在拿到 permit 后进入无界 `loop + yield_now` 扫描空闲槽位,泄漏积累到 pool_size 后整池挂死。
- 处理:租约持有 `Arc<SpacetimeConnectionPool>` 并实现 `Drop` 统一复位槽位/归还连接;槽位改 `AtomicBool` CAS 抢占,删除自旋循环(持有 permit 必然命中空闲槽位)。任何新的"显式归还"资源在 async 取消语义下都要先想 Drop 兜底。
- 处理:租约持有 `Arc<SpacetimeConnectionPool>` 并实现 `Drop` 统一复位槽位/归还连接;槽位改 `AtomicBool` CAS 抢占,删除自旋循环(持有 permit 必然命中空闲槽位)。任何新的"显式归还"资源在 async 取消语义下都要先想 Drop 兜底。该保证只覆盖本地 lease / slot / permit 回收;RPC 已发出后,handler timeout/drop 不会取消或回滚远端 procedure,结果仍须按 unknown 读取权威事实。
- 验证:`cargo test -p spacetime-client --manifest-path server-rs/Cargo.toml --lib`(`dropped_lease_releases_slot_and_permit`、`acquire_times_out_at_pool_acquire_when_pool_is_busy`)。
- 关联:`server-rs/crates/spacetime-client/src/lib.rs`、`docs/【后端架构】SpacetimeDB连接池租约Drop兜底与取消安全-2026-06-11.md`。
- 关联:`server-rs/crates/spacetime-client/src/active.rs`、`docs/【后端架构】SpacetimeDB连接池租约Drop兜底与取消安全-2026-06-11.md`。
## 后台灰度配置不能从 SpacetimeDB 本地表缓存读取
@@ -4207,6 +4286,38 @@
- 处理:使用完整 JSON Schema validator 校验原始 catalog schema,不手写 required/type 子集;native parser、fingerprint enrichment 与实际 MCP 调用边界复用同一校验器。enrichment 错误必须映射回 classified `arguments-schema` repair,不能以普通字符串直接终止 run;执行点重验用于阻断升级前已经落盘的 schema 外 pending。关闭网络和文件 `$ref` 解析,schema 无法安全编译时不广告或不执行。`serde` 类型错误会包含实际字符串值,catalog miss 也会包含模型提交的 server/tool,因此这两类错误同样只能返回稳定类别,不能拼接原始错误、参数值或 schema 内容。
- 验证:覆盖 required、additionalProperties、type、enum、本地 `$defs/$ref`、HTTP/file 外部引用、无效 schema、错误脱敏,证明 legacy wrapper 在注入 fingerprint 前进入 repair,并证明带旧有效 fingerprint 的历史 pending 在实际调用前仍被 schema 拒绝。
## 2026-08-05 不要把 static smoke 当作完整专业交付
- 现象:code-prototype 已通过 `game.static_smoke`,但完成门明确报告 `missing-visible-art-slice-use`;随后每轮 thinking summary 都是“已取得验证证据”,没有新 action,最终 loop-budget-exhausted。
- 根因:game-chat 快车道只看 verification gate 就返回确定性交付,完整 autonomous completion blocker 直到空 action 的最终收束阶段才被发现;Provider 因而永远拿不到下一轮修复机会。失败的 `file.patch` 也会因验证凭证保守失效而推进 revision,若快车道只比较 `mutationRevision`,会把“文件未修改”误认成本 Run 已修改。已有未完成计划收到 steer 后若再追加一整套新步骤,还会与 retained completed 步骤合并成超过 8 步,随后稳定重复 `runtime.plan_update blocked`。
- 处理:确定性交付与自动 plan completion 都必须先通过完整 completion gate,并要求当前 Run 最后一条同 mutation 工具调用与 Agent DB 中严格绑定当前身份的 `status=ok` receipt 一致;pending action 的 steer cursor fingerprint 也必须一致,失败 patch 或旧 Run receipt 不能取得交付资格。新 blocker 不回退旧 completed 步骤:已有非终态步骤时用明确 repair step 替换首个非终态步骤,其余保持 pending;只有全 completed 且仍有容量时才追加。8 步已满时进入外部 repair lane;计划已有 failed 步骤时立即失败关闭。回归同时覆盖失败 patch、跨 Run receipt、非零 steer cursor、8 个 completed 与 blocker,以及 failed plan 在 ownership/blocker 不同组合下都不会继续空转。
## 2026-08-05 Runtime 时间戳必须验证 Date 范围并保持来源身份
- 现象:极大但有限的持久时间值会让 `toISOString()` 抛 `RangeError`,或让界面显示 `Invalid Date`;实时回复又借用其它 Runtime 的最近活动时间,文字继续流入时仍显示几分钟前,缺失时还随前端定时器漂移。
- 处理:秒/毫秒归一化后必须再检查 `Date#getTime()`;不可表示的值统一显示“时间未知”并省略 `datetime`。实时回复只使用 response stream 自己的 `updatedAt`,不能借父/子 Runtime 活动时间或 `Date.now()`。
## 2026-08-05 Pending manifest 容错必须覆盖真实终态时序
- 现象:手工把内存 state 改为 Completed 的测试通过,但真实 finalization 先写 durable Completed、再投影 manifest 时仍被 Pending 状态门拒绝;或 stale manifest 全 Completed 后,父 Run 忽略仍在运行的真实 child。
- 根因:测试没有写 durable terminal record,Pending 容错只验证了非终态 journal;DAG 又把 manifest `completed=true` 放在 active child 之前。终态投影和收束前检查使用了不同事实时序。
- 处理:测试必须按真实顺序分别写 durable Running 和 durable Completed。Pending 漂移只允许 state/journal 的 Running-Running 或 Completed-Completed 对;queued/waiting/failed/reconciliation 一律拒绝。active child 在无 Failed 时独立保持 DAG 活跃,项目 mutation gate 在写锁内核对当前 root,防止旧 child 污染新根 Run。
## 2026-08-05 Canvas 可达性不能在扇入调用图中回退 visited
- 现象:game-chat 的 code-director 长期显示 queued,Runner 单核持续高 CPU,durable cancel 也无法被事件循环处理;manifest 已提前显示 running,用户看起来像“稳定卡死”。
- 根因:大 classic script 虽使用了 bounded direct-call graph,但 `javascript_named_function_is_reachable` 在递归返回时删除 visited,只阻止当前环,不记忆已经遍历的祖先。render/update 图的大量重复调用让同一节点指数重算;父完成门又在 code-prototype 未完成时提前深验四个 Canvas 切片,使第一次 wake 就同步阻塞,200 次外层重试预算完全没有机会推进。
- 处理:单次可达性查询每个 function node 最多访问一次;全 `None` alias 历史直接返回,稳定外层初始化使用有调用前置证明的快路。父完成门只深验 Completed seed task,wake 预算耗尽写入 reconciliation。格子游戏的符号坐标只在唯一数值 `COLS / ROWS / CELL` 与画布范围能共同证明时接受,普通无界动态坐标继续拒绝。
- 验证:永久 fixture 至少包含 48 层重复扇入调用、IIFE 外层素材初始化、格子常量绘制、无界坐标反例和整画布尺寸引用;真实项目的全部四个切片还要在同一轮秒级返回 true。禁止用延长 queued timeout、Tokio timeout 或 synthetic 小脚本通过来替代真实大脚本复验。
## 2026-08-05 Canvas clamp 与 parent wake 不能走字符串或易失兜底
- 现象:通用 game-chat fallback 明明把玩家坐标限制在当前 Canvas 内,完成门仍报 `missing-visible-art-slice-use`;反向放开任意动态坐标又会让离屏绘制或错误 Canvas 假通过。
- 根因:Canvas owner 收紧后正确禁用了含尺寸成员的字符串兜底,但 AST 数值区间器尚不认识嵌套 `Math.min / Math.max` clamp。若只查源码包含 `canvas.width`,无法证明该 Canvas 创建了当前 context,也无法排除局部伪造 `Math`。
- 处理:只在 semantic 证明未遮蔽全局 `Math`、上界读取当前 context 所属 Canvas、下界为 `0` 时生成有限区间;加入错误 Canvas、遮蔽 Math 和无界坐标负向回归。不要用字符串包含、变量名白名单或把未知动态值当 `0`。
- 现象:parent wake 的 200 次瞬态预算耗尽后 Runtime 仍长期显示 running,或 lane 忙、取消、child 前进、manifest 损坏时 reconciliation 被静默丢弃或覆盖新状态。
- 处理:预算耗尽错误必须向上传递;lane 忙先持久化 deferred signal,再在 lane + 项目锁内重检最新事实。结构损坏路径使用不依赖 manifest hydration 的专用 journal/state 写入,CAS 失败转为继续对账,绝不覆写并发取消或 DAG 进展。可解析的空对象/空 runId 仍是损坏身份,只有完整有效的新 Run 才能阻止旧 marker;event/audit 的同键记录必须完整比对并拒绝冲突或重复。旧 task 已终态、Runtime 非 waiting 或新 Run 接管时,deferred signal 必须写 resolved/superseded,不能留给后续 wake 永久重复 settle。
- 测试注意:autonomous child fixture 先 linked Pending、后正式 Running;终态 runId 必须拒绝复用。判断 Completed-only 诊断时按每个 seed task 的实际状态分析,不能因为 `code-prototype` Pending 就忽略已经 Completed 的 `art-asset-plan` 深验。
## macOS 安全路径测试必须使用规范化临时目录(2026-08-05)
- 现象:调用仓库上下文、Runtime context bundle 或 pending recovery 的 Rust 测试在 macOS 报“Repository root and its ancestors must not be symbolic links”,Linux CI 却可能通过;本地 HTTP 恢复夹具在完整串行测试中还可能偶发 `WouldBlock`。
@@ -4272,3 +4383,9 @@
- 原因:把 Pointer 事件接线、宿主生命周期胶水和可复用 history 算法放在同一组件中,误以为没有复制整个画布目录就已经满足共享源码边界。
- 处理:两宿主直接消费共享 `useCanvasHistory`;共享 snapshot 统一覆盖 viewport、selection、图层位置和 width/height,宿主只声明本地媒体是否允许安全移除/重做。Tauri 仍可保留 Pointer capture/epoch/host callback 接线,但选择、平移、缩放、变换、renderer 和 history 状态机不得在宿主重写。
- 验证:主站 history 定向测试覆盖 resize undo/redo;Tauri 新建、导入、编辑、撤销重做和 durable commit 用例必须在同一共享 hook 下通过。
## 编辑器生成不能把传输重试、参考图截断和客户端 provenance 当成独立小问题(2026-08-05)
- 现象:生成 POST 首次已经入队但响应丢失时,客户端自动重试产生第二个任务;第 6 张或更多参考图仍显示在 UI / 元数据里,却没有送给 provider;直接构造请求还能把任意资源 ID 写成最终素材引用。
- 原因:客户端虽在重试中复用 `x-request-id`,队列入口却用随机 job id 生成 dedupe key;前端允许无限追加,api-server 和 provider 用 `.take(...)` 静默截断;`generationInputs.references` 被当成可信持久 provenance。
- 处理:主站生成 POST 禁止自动重试,把显式复用的稳定 request id 接到队列唯一键并校验 replay payload;所有边界显式拒绝超限,前端还要预留主图槽位、统计在途上传,并在上传完成前拒绝模型切换、画布选图、提交生成、关联源图删除 / 剪切 / 素材删除和面板切换 / 关闭;reservation 必须绑定原面板上下文,批量部分失败时不能丢弃已经持久化的成功项。入队、完美像素及直接创建资源 / 素材时删除客户端 references,执行时按真实参考源和 owner 资源记录重建权威引用。历史任务比较必须兼容仅差已删除 references 的旧 payload,不能只保留旧 hash 却让 payload 比较误报冲突。
- 验证:覆盖同键同 payload / 不同 payload、普通图片第 6 张、带主图的 GPT-image-2 第 5 张额外引用、provider 6 / 15 张边界、伪造引用删除和 owned 资源 / 素材重建。
@@ -166,7 +166,7 @@ assetObjectId > objectKey > sourceAssetId > src
4. 对每个素材源读取 Blob:
- `data:image/...` 直接转换为 Blob。
- 同源或可访问 URL 使用 `fetch` 拉取 Blob。
- 私有 generated / OSS 素材先走 `/api/assets/read-url` 换签并由浏览器直接 `fetch` OSS 签名 URL;只有换签或 OSS 字节读取失败时,才 fallback 到同源 `/api/assets/read-bytes`。
- 私有 generated / OSS 素材先走 `/api/assets/read-url` 换签并由浏览器直接 `fetch` OSS 签名 URL;必须在同一保护边界内完整消费响应体,换签、请求、状态码或响应体读取任一阶段失败时,才 fallback 到同源 `/api/assets/read-bytes`,不得在只拿到 `2xx/206` 响应头后提前视为读取成功。
- `mediaType="image-sequence"` 逐帧读取 `imageSequenceFrames`,写入 `sequences/<编号-标题>/frames/`。
- 画布素材 ZIP 内的 `mediaType="image-sequence"` 同步写入 `skeleton.json`,供 Spine Editor 或 runtime 以 slot attachment timeline 方式播放序列帧。
- 单图层普通序列帧导出写入 `preview.gif`,由前端基于成功读取的帧生成动画预览,不依赖压缩包读取时再临时播放 PNG。
@@ -194,6 +194,7 @@ assetObjectId > objectKey > sourceAssetId > src
- 图层的锁定、翻转、分组状态写入元数据。
- `data:image` 图片不经过网络请求即可导出。
- URL 图片 fetch 失败时不中断其他素材导出。
- OSS 返回 `2xx/206`、但响应体读取失败时,自动改走 `/api/assets/read-bytes`,并使用 fallback 的完整字节生成 Blob。
- 动作图层右键菜单把 `导出为` 作为一级入口,二级菜单提供 `序列帧导出(zip)` 和 `Spine 导出(zip)`。
- 单图层普通序列帧 ZIP 包含 `frames/`、`preview.gif`、`metadata.json` 和 `manifest.txt`,且不包含 `skeleton.json`。
- 序列帧导出的 `skeleton.json` 可被独立验证器解析并预览。
File diff suppressed because one or more lines are too long
@@ -32,7 +32,14 @@
- Windows Runner 私有文件初始化:父 AppData 已归当前 `TokenUser` 后,新建 `agent-runner.lock`、endpoint 临时文件、project-owner 诊断临时文件与 real-E2E 私有文件的 owner 仍可能采用 token 默认 owner `Administrators`。固定 stale lock 只有在父目录已验证为当前用户 protected 私有 DACL、Windows 不共享独占句柄已取得、且句柄确认普通文件、非 reparse point、链接数为一时才允许修复;其它三类文件只允许在本进程 `create_new` 成功且仍持有同一独占句柄时初始化 `TokenUser` owner / DACL,再写入、原子安装并严格复核,初始化失败必须清理刚创建的文件。既有 durable endpoint / diagnostic 读取不得自动接管;活锁不得截断,只有 sharing / lock violation `32/33` 表示占用,access denied 等其它错误立即返回。父进程观察到 Runner 子进程退出后立即返回错误,不等待完整 30 秒 deadline。
- 启动诊断:独立 release 的 `startup.log` 和 `agent-runner.log` 只记录有界、脱敏的阶段与 stdout / stderr 摘要,凭据、AppData 路径和其它绝对路径不得原样落盘;单文件达到 256 KiB 后只轮转保留一份 `.previous.log`。`startup.log` 优先写独立 AppData,目录不可写时回退到系统 TEMP 下的 `Genarrative-Game-Chat-Diagnostics`;Tauri context、窗口 URL、AppData、Runner 或 `.setup()` / `.build()` 初始化失败时,Windows 必须显示可见错误对话框并给出诊断日志位置,不能只在无控制台 release 中静默退出。
- 对话与事件:窗口固定使用 `project-supervisor + autonomous-game-build`,继续复用 active Session、External Runner、持久 conversation、流式回复、same-run steer、工具确认与用户追问。以 `/` 开头的输入必须继续走现有内置命令解析,例如 `/preview` 只能生成 `preview.start` 确认卡,不得作为自主构建任务投递给 Supervisor。界面聚合当前 Supervisor 父 run 及其直接委派专业 Agent 的最新原始事件,按时间倒序稳定去重并标注 Agent;默认显示 4 条,可展开至最新 20 条。原始 `summary / detail` 仍只作 Runtime 状态投影,不直接写入 conversation。需要进入聊天的事件必须由 Rust 同步生成唯一 `eventId` 与安全 `publicText`;前端只按这两个字段形成独立 assistant 消息,无 `eventId`、空 `publicText`、legacy 事件和内部 tool / Provider / Runner 协议一律忽略。
- 公开消息硬门:模型仍负责 Supervisor / 专业 Agent 回复的业务语义,Runtime 不根据 tool 或 Provider 事件自行补写业务结论;但用户直接投递的 Project Supervisor 根后台任务必须先落为不可执行的 `preparing / public-status-pending`,再以 `runtime-public-status-*` 稳定 message ID 把“任务已接收,正在启动处理”写入项目 conversation,成功后才转为 `pending / queued`;恢复预检只读,只能在验证到同 run accepted 消息后把该任务临时分类为可恢复,真实 resume 持有 Agent 锁后才可持久提升为 `pending / queued`;写入失败则落为 `failed / public-status-write-failed`,不得继续执行。这些 Runtime 公开状态只供 UI 展示,prompt 构建器必须按稳定前缀排除。根 Supervisor 通过正式失败 / 预算耗尽收束或 game-chat 绝对硬期限进入 reconciliation 时,必须在 task、event、state 等其它终态投影之前先幂等写入一条脱敏、用户可理解的失败消息;专业 Agent 命中该全局硬期限时,也必须通过权威 Run Profile 和根 task 将同一根终态写入项目 conversation,同时保留 child 私有 Session 状态;状态文件本身写坏也不能导致零公开结果。当前 Runtime 自称根 agent/run 时,其 session 和两个 parent 字段必须与权威根 task 一致;任一身份冲突必须失败关闭,不得以另一 session 派生第二条项目终态。前端把该前缀识别为 Runtime-owned,同秒时排在触发它的 Supervisor 用户消息之后,不二次持久化;仅根 Supervisor 的 `turn.started / turn.failed / turn.budget_exhausted` 只保留在 Runtime 详情和进度投影中,不能再生成第二条聊天消息,专业 Agent 的公开启动事件仍可见。该硬门不改变 final-reply 的唯一性;非 Supervisor 专业 Agent 的失败消息继续留在对应 Agent Session,不把私有诊断写进项目 conversation。
- 启动恢复和续跑边界:本条取代上一条中“只有 accepted 才可恢复”的窄口径。若进程在 Supervisor 用户消息已持久、accepted 未持久之间崩溃,只读 preflight 可以把该 `preparing` 识别为可恢复,但不改写 task/conversation;真实 resume 持有 Agent 锁后必须先幂等补写 accepted,再提升为 `pending / queued`。用户消息或 accepted conversation 已落盘而辅助审计失败时,以 conversation 为公开真相继续入队,不留下“已接收但永不执行”的任务;根终态首次公开写入的瞬时失败必须在终态投影后用相同 message ID 重试。receipt / isolated-join 等带 parent 的 Supervisor continuation 不再另写 Session 终态,只保留单一后端公开事件;`runtime-task-*` 与 `runtime-public-status-*` 共享同 run 的不透明关联摘要,秒级时间戳下多个连续任务必须按实际 run 对应的 `user -> accepted -> terminal` 顺序交错展示。
- Supervisor 进度播报:聊天消息流内保留且只保留一条当前 run 的 Runtime-owned 播报卡,由客户端从 manifest 任务图、Supervisor 结构化计划、`loopIteration`、当前动作、直接委派专业 Agent 及其持久事件确定性整理;显示当前轮次、任务 / 计划进度、活跃 Agent、最近试玩与静态检查、返工决定、代码修改和截图检查证据。同一 run 原位更新,切换 run 时替换,不调用额外模型、不追加持久 conversation,也不改变最终 assistant 回复的唯一性;任意详情必须有界且不展示绝对路径、Provider 元数据或内部指纹。
- ready-task 启动活性:`background_task.queued`、`autonomous_ready_task.scheduled`、Runner heartbeat 或执行锁已移交都不等于 child 已启动。实际持有执行权的 Runner 必须在释放项目写锁后同步写入 child 的 running task、`turn.started` 与 started journal,再把已启动 state 和 per-Agent 执行锁交给已确认开始轮询的独立 execution worker;同步启动或 worker 接管失败时,要在仍持有执行锁期间依次把 child 和 manifest Graph 节点明确落为 failed,再释放锁并让 parent 收到调度错误。`autonomous_ready_task.scheduled` 只作诊断审计,其写入失败不能阻断 durable child 启动;external client 只 wake Runner,不在客户端抢占执行。Supervisor 进度卡通过 durable `startedAt`(旧 Run 从完整 task journal 恢复,最新 task-record fallback 保持 0)显示真实持续时间,并以父 Run 与当前关联专业 Agent 的最大事件时间计算运行态活跃度:运行超过 5 分钟无新事件时显示“运行中 · 疑似停滞”和静默时长;等待用户、等待确认、Provider retry、视觉资产、进程会话、pausing 与 paused 不误报。父 Run terminal 后,持续时间冻结在父 Run 自身最后活动,不随 child 晚到收口事件增长。消息时间统一校验为 JavaScript 可表示的 Date;越界值显示“时间未知”且不写无效 `datetime`。实时回复只显示 response stream 自己的 `updatedAt`,缺失时同样显示“时间未知”,不能借用其它 Runtime 活动时间或随前端时钟漂移。该提示只提供可观测性,不改变 Runtime/manifest 正式状态。
- ready-task manifest 漂移:父 Supervisor 必须分别判断“能否调度新节点”和“是否存在必须等待的工作”。派生视觉需要父规划修复时不再调度新 child,但当前最新且活跃的根 Run 下,只要存在确定性 runId、scheduler source、正确父绑定且 durable journal 为 queued/running 的 ready child,父 Run 就保持 `waiting-for-manifest-tasks`,不能因旧 hydration 快照把 manifest running 覆盖成 pending 而提前 fixed-graph-stalled。game-chat child 可在相同严格身份下容忍 pending 漂移;正式产物、Canvas、revision、`game.static_smoke` 与 `preview.validate` 门禁不放宽。GUI/CLI、旧父 Run、终态、确认/用户输入/reconciliation、伪造绑定或非确定性 runId 全部失败关闭;更新根 Run 后旧 child 不得继续维持新 DAG 或投影完成。
- Supervisor 主导的条件美术路由:game-chat 的关键词、用户是否报告“美术未接入”、占位状态和当前资产探测只形成 `advisoryOnly=true` 的补充上下文,不得直接重置 Graph、预完成美术节点、选择复用/生成分支或继承历史试玩类型。当前根 Run 没有持久化 Supervisor 决策时,scheduler 不启动任何 manifest child,Supervisor Provider 必须先通过 auto-safe 的 `agent.route_manifest` 选择 `audit-existing-first` 或用户明确要求整体重做时的 `regenerate-art`。决策后首波只开放 `design-director + code-director`;`code-director` 必须先成功调用 `asset.list`,再以同一工具提交 `use-existing-art / generate-missing-art / regenerate-art` 与精确 `missingAssetSlots`。Runtime 只负责校验 root/child 身份、当前 revision、Canvas 登记、私有图集合同、四张语义切片、art manifest、覆盖 fingerprint 和路由 fingerprint,并据此把 `art-director / art-asset-plan` 投影为 completed 或 pending;缺少 art spec 若同时使图集引用合同失效,两个槽位都属于真实缺口,不能为了少调一个 Agent 伪称图集可复用。只有持久路由明确复用 `art-asset-plan` 时,根完成门才忽略 `assets/manifest.art.json(unchanged-from-run-baseline)`;其余产物、可见代码使用与试玩门禁不放宽。明确 `regenerate-art` 时两个正式图片 owner 使用严格绑定当前 root Run 的原位替换授权。纯“继续”仍走既有正式 continuation 合同;普通美术措辞不得借用更老项目的具体试玩场景。不得以增加 loop 预算、伪造 revision、机械改写 manifest 或重放历史图片 action 代替 Supervisor 决策和程序侧审计。
- ready-task 对账取消续跑:未知工具结果仍停在 `needs-reconciliation` 且禁止自动重放;人工核对后显式取消原 child,保留 cancel tombstone,旧 child 和旧父 Run 按真实终态收口。若随后创建同 Session、同 Supervisor source、同有效任务语义的 continuation,新完成合同只对同时具有历史 `failed / needs-reconciliation`、最终 `cancelled` 和 durable tombstone 的 ready-task,把当前 manifest 对应 failed 节点恢复为 pending,并由 scheduler 创建全新 child Run。manifest 的读取、failed 筛选、每任务一次的 child journal 索引、证据重验和写回必须位于同一项目写锁域;较新的无 child 根 Run 只有在 durable journal 精确表明为旧 failed Graph 在进入调度前即失败时才能跨过,scheduler 自身失败必须阻断借用更老 tombstone。普通失败、无 tombstone、不同 source/Session/任务语义或证据冲突均保持失败关闭;不得复活旧 pending action、补造 observation 或把取消任务标成 completed。
- 完成门静态分析预算:Canvas 视觉门必须先做只会提前拒绝的词法预检。经典或模块脚本同时不含大小写精确的 `import` 与 `export` 字节序列时,不运行模块依赖语义分析;纯 `export ... from` / `export * from` 仍须进入正式模块图分析。当前脚本不含目标文件名或任一已绑定 DOM 图片元素 ID 时,先低成本解码 `\\xNN`、`\\uNNNN`、`\\u{...}`、简单转义和续行;解码后仍无候选才不运行完整 Canvas alias / 函数可达性分析,解码不确定则保守进入 Oxc。存在任一候选时仍执行原 parser、semantic binding、解码后的 computed 属性/StringLiteral 路径、可达 `drawImage`、可见 Canvas、路径大小写和动态 namespace 写入门禁;HTML 中存在某个绑定元素不得使所有无关 JavaScript 单元进入重分析,禁止把词法命中当作通过条件。
- Provider 故障展示:Provider retry 的“是否可重试”继续使用 `upstream-5xx` 等稳定类别判断,但 durable retry record 保留安全的精确 `upstream-<HTTP status>` 身份。等待态必须从真实 record 显示 HTTP 状态、`nextAttempt/maxRetries` 与当前持久退避剩余秒数,例如“Provider 上游返回 HTTP 503,准备自动重试 1/3;预计 8 秒后重试”;不得以动画或前端自增计时伪造 attempt。重试耗尽的 Runtime 私有错误只保存 `kind/httpStatus/fingerprint/chars/retryAttempt/maxRetries/retryState`,前端和持久 conversation 仅在字段顺序、范围、状态一致且无尾随正文时派生“上游服务返回 HTTP 503;自动重试已耗尽(3/3)”;其它错误使用固定安全摘要。Provider 响应正文、URL/query、凭据、本地绝对路径、fingerprint、字符数和 `[redacted ...]` 占位符均不得进入用户可见消息。
- 跨轮阶段记录:game-chat 父 run 进入真实 completed / failed / cancelled 终态后,客户端等待 `design-director / art-director / art-asset-plan / code-director / code-prototype / preview-readiness / preview-playtest` 七项首版任务也全部投影到 completed / failed 终态,再把本轮、任务 / 计划完成度、最新试玩 / 静态检查、最近返工决定和已登记成果图片路径整理成一条 `【Supervisor 阶段记录】` 项目 assistant 消息。父 run 先终态而 manifest 仍在 hydration 时不得用陈旧 `0/7` 提前归档,要暂存终态 Runtime 并在 manifest 刷新后重试。页面初始 hydration 若直接读到缺少阶段记录的真实终态 run,也必须补写,但 `idle` 不是可归档终态。每个“项目 + 父 run”最多追加一次,进入现有 `conversation.write` 权限与项目 conversation 持久化链路,下一轮及重载后继续保留。阶段记录不是 Supervisor Runtime 正式回复,不写入 Agent Session、不增加 final assistant 数量,也不逐条复制原始事件或内部正文。
- 图片成果:当前 manifest 新增或恢复已登记的 PNG / JPEG / WebP 资源时,聊天消息流同步显示 Runtime-owned “Supervisor 成果图片”卡,最多展示最新 4 张并随 manifest 原位更新。图片必须通过现有 `read_local_project_image_preview` 读取,只允许当前授权项目中 `assets/` 下的已登记资源,继续执行 `file.read` auto 权限、真实格式、大小、尺寸、普通文件、祖先目录和项目根边界校验;前端只接受返回路径、媒体类型和 `data:` 前缀与请求完全一致的结果。缩略图点击后使用独立模态查看器,支持按钮与滚轮缩放、指针拖拽、双击 / 按钮复位、Esc / 按钮 / 遮罩关闭,移动端占满视口;不得在聊天卡下方追加展开区。图片卡不写入 conversation,不解析 assistant 文本中的任意 Markdown / 绝对路径,也不开放 `.agent` 验收截图读取。
@@ -52,26 +59,27 @@
## 2026-07-31 game-chat 输出、单轮预览与平台美术资源
- 对话输出:game-chat 的 Supervisor `ready` response stream 继续以稳定身份显示;`design-director / art-director / art-asset-plan / code-director / code-prototype / preview-readiness / preview-playtest` 的 `requestKind=final-reply` 且 `status=ready|committed` 的非空安全回复也分别以 Agent、Session、run、request slot 和 response revision 形成 durable message ID,并带 Agent 标签逐条追加到项目聊天。每条 Rust `eventId + publicText` 公开输出同样形成独立 durable 消息。所有这些消息通过 `append_local_conversation_message` 的顶层 `messageId` 幂等写入,事件、轮询、React StrictMode 和 hydration 重放不重复;tool-plan、半成品 stream、原始事件 detail、命令正文、绝对路径、Provider / Runner 元数据、哈希和凭据不得进入聊天。普通 `supervisor-chat` 保持原有 transient response 行为。
- 对话输出:game-chat 的 Supervisor `ready` response stream 继续以稳定身份显示;七任务目录中本轮实际启动的专业 Agent,其 `requestKind=final-reply` 且 `status=ready|committed` 的非空安全回复分别以 Agent、Session、run、request slot 和 response revision 形成 durable message ID,并带 Agent 标签逐条追加到项目聊天。条件路由跳过的美术节点只要求 manifest 正确投影为 completed,不伪造 Agent 回复。每条 Rust `eventId + publicText` 公开输出同样形成独立 durable 消息。所有这些消息通过 `append_local_conversation_message` 的顶层 `messageId` 幂等写入,事件、轮询、React StrictMode 和 hydration 重放不重复;tool-plan、半成品 stream、原始事件 detail、命令正文、绝对路径、Provider / Runner 元数据、哈希和凭据不得进入聊天。普通 `supervisor-chat` 保持原有 transient response 行为。
- 对话输出中的 `eventId + publicText` 只指需要独立进入聊天的进度事件;`turn.started` 和根 Run 终态失败事件由上一条 `runtime-public-status-*` 硬门覆盖,不得同时转成事件消息。专业 Agent child 的失败消息继续留在其 Agent Session,根项目聊天只接收 Supervisor 终态失败、明确公开进度和安全 final-reply,避免一项失败被 Runtime event 与 conversation 各播报一次。
- 单轮收束:game-chat source 只生成至 `preview-playtest` 的 manifest seed task,试玩完成后父 Run 直接进入完成门,不再调度 `publish-strategy` / `publish-package`;`agent.schedule_ready` 必须按当前 Supervisor Run 的持久 source/profile 选择同一 source-aware scheduler,不能绕过该边界。`task.list` 对同一 root source 必须从任务行、readyTaskIds 和统计中排除两个发布节点,`agent.delegate` 也必须按 root binding 拒绝直接委派这两个节点,不能让 Provider 用“读取完整 DAG 后手工委派”恢复已裁掉的发布阶段。完成门满足且 collaboration、Provider batch、进程会话、视觉资源等非验证屏障全部清零后,Runtime 必须用确定性回复直接收束结构化计划并结束父 Run,不再请求下一次 Provider 工具计划。普通 GUI / CLI 仍执行完整发布 DAG。
- 轮次展示:`loopIteration` 只是同一父 Run 内的 Provider / 工具规划循环,用于委派、回执、返工和验收,不是用户发起的游戏生成轮次。game-chat 的进度卡、当前工作和“最新状态”事件统一显示“本轮”,整个页面不向用户显示“第 N 轮”;完整 GUI / CLI pre-publish 任务图仍可显示 `x/14`,但首版只按七项任务显示 `x/7`(详见 2026-08-03 小节),不得把两个发布节点计入任一分母。父 Run 终态后移除运行中进度卡,只保留终态阶段记录与预览。
- 平台美术资源:game-chat 恢复正式视觉 DAG。`art-director` 先通过平台 `images/generations(kind=spec)` 生成并登记 `assets/art-spec.png`;`art-asset-plan` 再以该规范图的稳定 resourceId 调用 `icon-spritesheets/generations` 生成真实透明 `assets/art-spritesheet.png`,并把响应中的 `iconImageSrcs` 下载为本地独立切片,写入 `assets/art-spritesheet-slices/manifest.json`。`code-prototype` 必须等待图集与切片清单,并在活动 Canvas 中分别绘制玩家、方块/目标、障碍/场景与反馈四类切片;规范图只作 reference,纯代码核心画面、猜测图集等分网格、整图 `<img>` / CSS 背景、完整图集直绘或只出现路径均不得完成。
- 平台美术资源:game-chat 保留正式视觉 DAG,但是否进入生成节点由 2026-08-06 的持久条件路由决定。现有正式资源覆盖完整时直接复用;存在真实缺口或 Supervisor 明确选择整体重做时,`art-director` 才通过平台 `images/generations(kind=spec)` 生成并登记 `assets/art-spec.png`,`art-asset-plan` 再以该规范图的稳定 resourceId 调用 `icon-spritesheets/generations` 生成真实透明 `assets/art-spritesheet.png`,并把响应中的 `iconImageSrcs` 下载为本地独立切片,写入 `assets/art-spritesheet-slices/manifest.json`。`code-prototype` 必须等待复用或补齐后的图集与切片清单,并在活动 Canvas 中分别绘制玩家、方块/目标、障碍/场景与反馈四类切片;规范图只作 reference,纯代码核心画面、猜测图集等分网格、整图 `<img>` / CSS 背景、完整图集直绘或只出现路径均不得完成。
- 验证:前端运行时模型定向测试、Rust completion/source/asset 合同测试、`cargo fmt --check`、`npm run check:encoding` 与 `git diff --check` 必须全部执行;Windows 文件锁竞态只可作为既有测试失败单独记录,不得将其改写为本次改动的通过证据。
## 2026-08-01 game-chat 首版七任务素材完整快车道与美术硬门
- 首版任务边界:game-chat 首版只展示 `design-director`、`art-director`、`art-asset-plan`、`code-director`、`code-prototype`、`preview-readiness`、`preview-playtest` 七项任务,进度统一显示为 `x/7`;首波并行激活三个 Director,`art-asset-plan` 依赖 `design-director + art-director`,`code-prototype` 同时等待三个 Director 与 `art-asset-plan`,后续验证串行推进。不把完整 GUI / CLI 任务图的其它节点投影到该页面,也不显示内部 Provider / child loop 轮次。
- 首版任务边界:game-chat 首版只展示 `design-director`、`art-director`、`art-asset-plan`、`code-director`、`code-prototype`、`preview-readiness`、`preview-playtest` 七项任务,进度统一显示为 `x/7`;当前根 Run 在 Supervisor 持久路由前零 child,`audit-existing-first` 决策后的首波只激活 `design-director + code-director`。code-director 审计后,完整资源把两个美术节点投影为 completed;真实缺口或整体重做才按依赖开放对应美术 owner,随后 `code-prototype` 与验证节点串行推进。不把完整 GUI / CLI 任务图的其它节点投影到该页面,也不显示内部 Provider / child loop 轮次。
- 时间预算:从 game-chat 父 Run 接受用户请求开始,素材完整首版使用 `4200` 秒软预算;父 Run 与其全部 child Run、等待和回收阶段共享从 root `bound_at` 计算的 `4500` 秒绝对硬上限。该值来自现有两次串行生成各自最长 35 分钟的客户端等待合同,并为代码兜底、静态检查和双视口试玩保留 5 分钟。整个 Runtime pass 受同一 `timeout_at` 约束;硬上限内未通过完成门必须失败关闭,不得为了守时跳过图集、换普通生图或回退纯代码核心画面。即使完成证据恰好在上限后到齐,单轮确定性收束也必须再次检查累计预算并拒绝写入 `single_round_converged`。
- Provider 次数:首波 `design-director / code-director` 的规划请求与 `art-director` 的确定性规范图生成并行;规范图和设计方向就绪后,Runtime 确定性派发 `art-asset-plan` 的 icon-spritesheet 生成。图集登记后 `code-prototype` 最多执行一次 Provider 首版写入请求;软预算耗尽时只允许生成真实加载并裁切图集的确定性本地兜底。Provider 成功返回后由 Runtime 依次执行确定性的 `game.static_smoke` 与 `preview.validate`。
- Provider 次数:Supervisor 先用一个 Provider turn 理解目标并持久化条件路由;`audit-existing-first` 的首波仅请求 `design-director / code-director`,code-director 成功 `asset.list` 并提交覆盖合同后,Runtime 才确定性派发真实缺口对应的 `art-director / art-asset-plan`。完整复用不得调用图片生成接口;图集复用或登记完成后 `code-prototype` 最多执行一次 Provider 首版写入请求。软预算耗尽时只允许使用已登记图集和当前 resourceId 切片清单的确定性本地兜底,Provider 成功返回后由 Runtime 依次执行确定性的 `game.static_smoke` 与 `preview.validate`。
- 可玩兜底:软预算或首版 Provider 无法及时完成时,只能为已显式实现真实语义的玩法生成完整、自包含、无远程运行依赖的中文 HTML 模板;未知玩法失败关闭,不能只替换标题后套用固定收集游戏。俄罗斯方块模板必须包含 10×20 棋盘、下落、移动、旋转、锁定、消行和触顶失败;收集模板只匹配明确收集类目标。模板必须从 `ready` 开始,包含真实 Canvas 绘制、`requestAnimationFrame`、键盘 / 触控主要操作、唯一可见且启用的 start / primary-action / restart 控件,状态 JSON 只随真实输入、状态迁移或模拟状态变化推进,并能在 primary-action 后保持 `playing`、在 restart 后稳定回到 `ready | playing`;不得在开始前固定进入 `lost`,不得通过固定失败冒充试玩通过,也不得由纯渲染帧空转 `sequence`。兜底只允许写入缺失或精确初始化占位的 `game/index.html`;存在非占位入口时,当前 `code-prototype` 必须读取并实际 patch,取得本人 `mutationRevision` 后再静态检查和试玩,不得反复用只读 smoke 冒充续作。
- 平台图片:`art-spec.png` 只用于约束色板、材质、形状与后续派生,不是运行时背景、角色、目标或图集。`art-asset-plan` 必须以其稳定资源 ID 走专用 icon-spritesheet 路由,透明像素、generation route/kind、同画布归属和 reference resource ID 继续由 Runtime 验证。完整透明图集在编辑器合同中即使产生 `sliceWarning` 仍可登记,但 game-chat 不能据此猜测 atlas 坐标;图集 resourceId 缺失或空白时必须在任何本地写入前失败,四个切片均须保留与整图完全一致的 `sourceResourceId`,切片数量也必须在正式图集登记前严格等于四,多或少都失败关闭。四类唯一性按尺寸与解码后的规范 RGBA 像素摘要判断,不能用不同 PNG 压缩或 ancillary chunk 冒充不同素材。主图、四张 canonical 切片和切片清单必须共同提交,并把主图/切片摘要及 Canvas 身份冻结到 `.agent/runtime/art-spritesheet-contract.json` 私有回执;后续主图安装、回执或登记失败时恢复旧合同,避免出现旧图集绑定新切片。进程在固定主图落盘后崩溃时,保留的 generation 账本只允许按远端预期摘要幂等补齐同一组合同,路径内容冲突继续失败关闭。全部切片下载累计最多 `32 MiB`。完成门重新读取本地素材时仍执行相同文件大小、解码内存、内容摘要、规范像素摘要、可见 alpha 与四类唯一性校验,并要求公开切片清单、当前 Canvas 登记与私有回执完全一致,后续代码 Agent 不能通过同时改写素材和公开清单重定义合同。`postprocess-failed-source-preserved`、无真实 alpha、缺文件或缺登记同样失败关闭。首版只在 `preview-playtest` 后单轮收束,不进入发布节点。
- 关联验收:快车道必须分别验证三 Director 首波并行、`art-asset-plan` 在代码前完成、`x/7` 投影、七个专业 Agent 安全 final-reply、4200 / 4500 秒累计预算、规范图到 icon-spritesheet 的真实引用、`iconImageSrcs` 本地持久化与资源 ID 绑定、失败续跑目标继承、非占位入口禁止整文件覆盖、纯代码核心画面、猜测单个 atlas 裁切与整图展示失败、四类独立切片可见使用通过、action-driven `sequence`,以及当前 revision 的静态 smoke 与浏览器试玩。
- 关联验收:快车道必须分别验证 Supervisor 决策前零 child、审计首波只有 design/code、完整覆盖零图片生成、精确缺口只启动对应 owner、显式重做两个正式 owner、`art-asset-plan` 在代码前完成、`x/7` 投影、实际启动的专业 Agent 安全 final-reply、跳过节点正确终态投影、4200 / 4500 秒累计预算、规范图到 icon-spritesheet 的真实引用、`iconImageSrcs` 本地持久化与资源 ID 绑定、失败续跑目标继承、非占位入口禁止整文件覆盖、纯代码核心画面、猜测单个 atlas 裁切与整图展示失败、四类独立切片可见使用通过、action-driven `sequence`,以及当前 revision 的静态 smoke 与浏览器试玩。
## 2026-08-03 game-chat 开发态同源与持久输出修复
- 开发态启动必须在 Tauri CLI 之前预检固定 `3080`。现有 marker 只包含 API target,不能证明监听器属于当前 worktree;因此只有端口空闲时才允许继续,任何已存在的 AGC Vite、非 HTTP 监听器或其它服务都必须在原生窗口创建前失败关闭。启动器不擅自终止无法证明归属的旧服务,也不得把当前 Rust 壳 / Runner 与其它 worktree 的旧 Vite 前端混用。Tauri CLI 任意退出后,外层启动器必须有界收束已启动的客户端进程树,避免 `beforeDevCommand` 失败后留下假在线窗口。
- game-chat root binding 的 `source` 必须精确为 `project-supervisor-game-chat`。只有该持久 source 才能选择首波并行 `design-director + art-director + code-director`,随后按 `art-asset-plan → code-prototype → preview-readiness → preview-playtest` 推进七任务 lane、规范图派生图集美术门、单轮确定性收束和自动预览;若绑定为 `project-supervisor-gui`,必须视为启动链路错误,不能用完整 16 节点 DAG 的运行状态伪装 game-chat 进度。
- source-aware lane 的首波 ready child 可能在 UI hydration 写回时短暂恢复为 `Pending`。该例外必须从当前 root source 的种子 lane 解析全部零依赖任务,不得硬编码某个 Agent;当前 game-chat 首波是 `design-director / art-director / code-director`,后续 code prototype / preview child 仍严格拒绝 `Pending` 收束。
- game-chat root binding 的 `source` 必须精确为 `project-supervisor-game-chat`。只有该持久 source 才能进入 Supervisor 决策前零 child 的条件 lane;`audit-existing-first` 路由后先运行 `design-director + code-director`,再按覆盖合同选择复用或补齐美术,最后推进 `code-prototype → preview-readiness → preview-playtest`、单轮确定性收束和自动预览。若绑定为 `project-supervisor-gui`,必须视为启动链路错误,不能用完整 16 节点 DAG 的运行状态伪装 game-chat 进度。
- source-aware lane 的 ready child 可能在 UI hydration 写回时短暂恢复为 `Pending`。该例外必须从当前 root source、持久工作流决策和资产路由解析本轮已开放节点,不得硬编码旧三 Director 首波;当前审计首波是 `design-director / code-director`,后续美术、code prototype 与 preview child 仍严格拒绝未授权或不满足依赖的 `Pending` 收束。
- 专业 Agent 的非流式 final reply 继续由既有 finalization journal 重建并提交 `responseStream`。`streaming / ready` 投影仍必须匹配当前项目 revision;已经 finalization 提交的 `committed` 回复以 Agent / Session / run / request slot / response revision 稳定身份为准,不得因后续阶段推进项目 revision 而从 game-chat 查询中消失。
## Runtime 边界
@@ -903,6 +911,15 @@ game-project/
- game-chat release 在 `CloseRequested / ExitRequested` 前复用 Runner durable idle probe;只要存在 process session、pending/finalization/provider/tool-plan handoff 或非终态 Agent queue/phase,就阻止关闭并提示先完成、暂停或取消。不可撤销的最终 `Exit` 不再作为唯一保护点,Windows Job Object 的 child-owned 安全边界保持不变。
- 规范 Agent 默认推理档覆盖全部 21 个角色:核心规划、生成、设计/美术/代码原型和质量角色使用 `high`,协调与结构化交付使用 `medium`,确定性预览 gate、音频总监和发布策略使用 `low`;显式 `agentLlm.<id>.reasoningEffort` 始终最高优先。规范默认由 Runtime resolver 解析,模板与 GUI 初始草稿保持 `agentLlm` 为空,避免默认值被误判成角色独立 LLM 路由;GUI 必须显示每个角色的实际默认档。全局与逐 Agent status/CLI 必须同时显示实际解析后的 reasoning、request timeout、max retries 和 retry backoff,区分运行快照与后来配置。
## 2026-08-05 game-chat 修复计划与 manifest child 生命周期补充
- code-prototype 的确定性交付需要同时满足当前 Run 存在 `status=ok` 的真实 mutation action、本人当前 mutation revision 的 `game.static_smoke=passed` 与完整 autonomous completion gate 无阻塞;失败 patch 即使因保守失效旧凭证而推进 revision,也不能取得 mutation ownership。mutation ownership 使用最后一条同工具调用,并严格匹配当前 Agent/task/session/run/actionId/actionFingerprint/tool 的 durable receipt;pending action 的 `plannedSteerCursor` 必须进入 recent tool-call 与 receipt 的同一 fingerprint,不能因非零 steer cursor 把真实成功误判为外来动作。static smoke 通过但仍有素材或正式产物缺口时,Runtime 对尚未完成的计划用单一 in-progress repair 替换首个非终态步骤并保留后续 pending;只有计划全 completed 且未满 8 步时才追加 repair。下一轮交还 Provider 生成实际 `file.patch`,完整门未通过时禁止自动完成计划,也不得因 retained completed 步骤与 steer 新计划合并超限而进入空转;结构化计划只要已经包含不可改写的 failed 步骤,就在快车道入口明确失败关闭,不再依赖 mutation ownership 或完成门诊断是否仍存在。
- manifest Pending 漂移仅在当前最新活跃 game-chat 根 Run 内容忍,并要求确定性 child runId、父绑定及 state/durable journal 的 Running-Running;child 终态投影要求 Completed-Completed。queued、waiting、failed、needs-reconciliation、旧根 Run 和不可信 source 不得借用容错。
- durable active child 的权威性高于 stale Completed manifest 快照,但 Failed manifest 仍立即失败关闭。所有 ready child 的文件、patchset、命令产物和其它项目 mutation 在写锁内再次核对当前根 Run;新根 Run 建立后旧 child 只能安全收束/审计,不能再修改项目或推进 revision。
- 大 classic 游戏脚本的函数可达性查询必须对固定 invocation graph 使用整轮 visited,每个 function node 最多访问一次;递归栈只负责去环、返回时删除节点会在 render/update 扇入图中指数回溯并阻塞 Runner 事件循环。Canvas alias 全空历史、稳定祖先初始化、整画布尺寸引用和可证明的 `COLS × ROWS × CELL` 格子目标使用有界静态路径,普通未知动态坐标继续失败关闭。
- 根 Supervisor 只对 manifest 中已经 Completed 的 seed task执行正式产物、素材和 Canvas 深验;未完成状态本身已经阻塞根完成,禁止在 code-prototype 尚未开始时提前扫描四类素材。自动唤醒连续耗尽 200 次瞬态预算后进入 `needs-reconciliation`,不得静默保留 running。
- Canvas 可见性区间证明按 Oxc semantic symbol 与当前绘图 context 的 Canvas owner 解析:函数 alias 重绑定、可达调用参数、计数循环和作用域内数值常量都必须保持身份。无法化简的格子索引只在唯一 `COLS / ROWS / CELL` 合同下覆盖完整棋盘轴;已知越界实参继续拒绝。动态 clamp 只接受未遮蔽全局 `Math.min(currentCanvas.width|height - size, Math.max(0, dynamic))`,错误 Canvas、遮蔽 Math 和普通未知坐标失败关闭。
- parent wake 达到瞬态预算上限时先形成 durable reconciliation signal;lane 忙保留 deferred 状态,随后在同一 execution lane 与项目写锁内重读原始 state、最新 durable task、取消墓碑和 DAG 进展。manifest 结构损坏时使用不依赖 manifest hydration 的专用 CAS journal 与原子 state 写入,不能吞掉持久化错误,也不能用过期终态覆盖并发取消或 child 进展。durable terminal reconciliation task 是后续 state / queue / event / audit 的提交标记;重启恢复必须在其它 resume 动作前幂等补齐这些投影,task journal 读取损坏不得当作任务不存在。恢复只在 raw state 具有完整 Agent/task/Session/run/source/profile/binding/task 身份时选择其当前 run;state 缺失、JSON 损坏、空对象或 runId 等关键身份为空时只考虑 journal 最后一个 logical run,完整有效的新 Run 继续阻止历史 marker 覆盖。event 与 Agent DB audit 只有完整稳定 payload 唯一匹配时才视为已投影,同键冲突或重复必须失败关闭;旧 task 已 completed/cancelled、Runtime 已离开 waiting 或新 Run 已接管时,deferred signal 必须追加 run-scoped resolved/superseded 终局。
## 2026-08-04 manifest 与工作台一致性收口
- `.agent/manifest.json` 的存储写边界使用同目录持久文件锁跨线程、跨进程串行化;锁必须覆盖旧 manifest 读取、不可变版本前缀校验、临时文件安装和安装后回读一致性校验。锁文件拒绝符号链接、非普通文件和异常所有权 / 硬链接;Windows 使用不共享写句柄,Unix 使用 `O_NOFOLLOW + flock`。旧快照在新版本安装后只能被拒绝,不能覆盖已追加版本。
@@ -2,7 +2,7 @@
- 日期:2026-06-11
- 关联故障:release 环境 api-server 周期性全量 `spacetime_stage="pool_acquire" elapsed_ms=45000` 超时,`/readyz` 503(`reason=spacetime_unhealthy, stage=pool_acquire`),重启后临时恢复。
- 涉及代码:`server-rs/crates/spacetime-client/src/lib.rs`
- 涉及代码:`server-rs/crates/spacetime-client/src/active.rs`
## 故障根因
@@ -28,6 +28,8 @@
3. acquire 改为 CAS 抢占槽位:持有 permit 即保证并发持有者不超过 `pool_size`,扫描一轮必然命中空闲槽位,彻底删除自旋循环;建连失败直接返回错误,槽位由租约 Drop 复位。
4. `release_connection` 退化为 `drop(lease)`,显式与隐式归还共用同一条兜底路径。
这里的“取消安全”只指本地连接租约、槽位与 permit 可回收。RPC 已发出后,handler timeout/drop 不会取消或回滚远端 procedure,结果必须按 unknown 读取权威事实对账;`dropped_lease_releases_slot_and_permit` 只覆盖本地 Drop,不构成远端取消测试。
## 验收
- `cargo test -p spacetime-client --manifest-path server-rs/Cargo.toml --lib`(44 通过,含上述连接池与缓存连接测试)
File diff suppressed because one or more lines are too long
@@ -123,7 +123,7 @@ POST /api/profile/api-keys
DELETE /api/profile/api-keys/{keyId}
```
前端入口位于登录后个人中心的 `我的 → 开发者 API Key`,用于查看当前 Key、创建新 Key、复制一次性明文和撤销已创建 Key。外部 OpenAPI 只描述 `/api/external/v1` 下可由 API Key 调用的接口,不混入登录态 API Key 管理接口。
前端入口位于登录后个人中心的 `我的 → 开发者 API Key`,用于查看当前 Key、创建新 Key、复制一次性明文和撤销已创建 Key。Key 卡片中的创建时间和最近使用时间必须格式化为 `YYYY-MM-DD`,不得直接展示后端时间戳。外部 OpenAPI 只描述 `/api/external/v1` 下可由 API Key 调用的接口,不混入登录态 API Key 管理接口。
## 版本与兼容策略
@@ -15,7 +15,7 @@
允许撤销的典型操作包括移动图片、移动生成结果、调整层级、组合与取消组合、删除或剪切图片、隐藏图片、锁定与解锁、翻转、修改素材类型和调整画布视图。删除、剪切和隐藏允许撤销,是因为目标只会让内容重新出现。
添加素材、上传到画布、粘贴、创建副本、生成图片、扩图新增结果、显示隐藏图片、替换图片以及其它会让当前结果消失的撤销必须被安全检查阻止。`Ctrl+C`、选择变化、滚轮或抓手视口移动、导出下载、项目重命名、素材库后端删除和生成任务副作用不进入画布历史;素材库后端删除发生后,同时剪除撤销栈和恢复栈中所有包含关联图层的目标快照,不能让更早的画布历史复活已删除素材。
添加素材、上传到画布、粘贴、创建副本、生成图片、扩图新增结果、完美像素新增结果、显示隐藏图片、替换图片以及其它会让当前结果消失的撤销必须被安全检查阻止。`Ctrl+C`、选择变化、滚轮或抓手视口移动、导出下载、项目重命名、素材库后端删除和生成任务副作用不进入画布历史;素材库后端删除发生后,同时剪除撤销栈和恢复栈中所有包含关联图层的目标快照,不能让更早的画布历史复活已删除素材。
恢复同样执行动态安全检查。移动、层级、分组、锁定、翻转和视图等不会减少内容的操作可以恢复;重新执行删除、剪切、隐藏、删除生成结果或替换当前素材时必须被阻止。
@@ -24,6 +24,7 @@
- 撤销栈和恢复栈均保存操作类型、目标 `CanvasHistorySnapshot` 和创建时间,分别最多保留 60 条。
- 新画布操作把操作前快照写入撤销栈并清空恢复栈;成功撤销把当前快照写入恢复栈,成功恢复把当前快照写回撤销栈。
- 操作类型用于生成用户提示,并对添加、上传、生成和替换等明确会移除当前结果的撤销做保护;其它操作能否应用由当前快照与目标快照的差异检查决定。
- 已有图片完美像素化成功落入画布前记录独立 `perfect-pixel` 历史类型,中文提示使用“完美像素”。该类型与 `generate-image`、`expand-image`、`remove-background` 一样属于新增结果保护操作:撤销不得删除派生 PNG,源图继续保留也不改变这一保护语义。像素处理失败、超时或不适用时不写历史。
- 安全检查以稳定的 `layer.id` 判断当前图层是否仍存在;内容身份优先比较对象存储 key、对象标识和媒体地址,序列帧结果比较完整帧列表。`resourceId`、`sourceResourceId`、`sourceAssetId` 等内部关联 ID 的延迟回填不视为图片替换。
- 恢复历史快照时,相同 ID 的图层以当前对象为权威,只从目标快照覆盖 `x`、`y`、`zIndex`、`groupId`、`assetKind`、`hidden`、`locked`、`flipX`、`flipY`。当前图层的资源关联、内容、媒体、生成元数据、`width` / `height` / `originalWidth` / `originalHeight` 和标题必须保留,不能被异步回填前的旧快照覆盖。
- 当前生成对话框和非活动生成结果按稳定 ID 纳入内容存在性检查,避免恢复操作删除当前生成结果。相同 ID 的生成对话框只从目标快照恢复占位框 `x` / `y` 以及 active / inactive 槽位对应的 `composerOpen`,当前占位框的 `width` / `height` / `originalWidth` / `originalHeight`、当前比例与清晰度等参数、`generating` / `failed` / 完成态、提示词、参考图和任务结果继续以当前状态为准,不能被旧历史快照降级。
@@ -34,6 +35,7 @@
- 修改素材类型的撤销与恢复仍以当前图层内容为权威,但每次成功恢复类型后都要创建与恢复类型一致的正式项目 resource,再回填新 `resourceId`。同一图层的 resource 创建请求使用单调版本号,迟到的旧类型响应不得覆盖更晚的 undo / redo 结果。
- 鼠标拖动在按下时暂存操作前快照;屏幕位移达到点击阈值后才开始改变画布坐标并只提交一条历史,阈值内的指针抖动和单击都不产生位移或历史记录。
- 本地即时结果与后端项目快照结果都必须在生成图层加入画布前写入一条生成历史;生成完成后的自动适合视图不再额外压入视口历史,保证用户第一次撤销就命中生成保护。
- 完美像素的关闭 composer 占位沿用 generation dialog 的内容存在性保护。占位删除已先持久化时,后端 completion 不得用旧 placeholder 复活占位或派生图层;回包时本地占位已删除则前端不应用完成快照或写 `perfect-pixel` 历史,已经持久化的 project resource / 账号素材仍可由资源或素材入口读取。现有布局 CAS 没有 deletion tombstone,completion 先提交、删除保存后冲突的极端竞态仍按权威快照收口。
- 顶部消息复用 `PlatformRuntimeStatusToast`,成功使用中性色,被阻止使用警告色;连续触发会替换消息并重新开始 3 秒计时。
## 验收重点
@@ -55,3 +57,5 @@
15. 图层移动后从素材库删除关联素材,随后撤销或恢复都不得把已删除图层重新加入画布;普通画布删除未伴随素材库删除时仍可撤销。
16. 打开“修改图片”并输入未提交提示词后,从按钮等非输入控件触发撤销不得关闭弹窗或回退当前草稿。
17. 修改素材类型后立即撤销或快速撤销再恢复,最终只允许最新类型的 resource 响应回填;刷新项目后类型与最后一次成功历史操作一致。
18. 完美像素结果加入画布后只写一条 `perfect-pixel` 历史;第一次撤销命中保护提示,源图和派生 PNG 都不消失。
19. 完美像素处理中删除占位后,本次完成回包不得在本地重新应用结果或写 `perfect-pixel` 历史;删除已先持久化时,后端不得复活占位或结果图层;已成功持久化的资源或账号素材允许保留。
@@ -69,6 +69,21 @@ worker 完成生成任务时,本次先用读取时 revision 调用 CAS 保存
重复 completion 必须返回同一资源、layer 和 dialog 终态,不得重复插入,也不能因 dialog 暂时缺失而返回 `changed=false` 后仍把任务标记完成。任一步失败时整笔业务写回回滚,任务保留可诊断的失败或可重试状态。
### 3.6 免费同步栅格派生完成
`POST /api/editor/images/pixel-art-snaps` 的完美像素化不是 external job completion:它免费、在当前 HTTP 请求内 inline 执行,不创建任务行,也没有 `job_id / worker_id / lease_token`。前端仍须先创建关闭 composer 的右侧 generation dialog,再解析或上传源图以取得稳定引用,随后 flush 包含该占位的当前布局,最后把稳定源媒体引用和带非空 `dialogId` 的 `canvasCompletion` 一次提交;结构化 / legacy canvas 的完成分流继续由后端决定,前端不能直接写表或本地补造正式 layer。
端点级并发排队、像素读取、静态 PNG / JPEG / WebP 编码门禁、解码、输入限制、legacy 网格步长估算、CPU 并发排队、规整和 PNG 编码全部发生在持久化前。两层排队的位置不同:端点级闸在首次 IO 之前,因此队列满的 `503` 早于任何 SpacetimeDB 读取和 OSS 下载返回;CPU 排队仍在下载之后、规整之前,等待超预算返回 `504`。两者都在持久化前失败,零写入结论不变。strict 与生成风格使用同一 profile、峰值估算、单轴步长补全、walker、采样和编码;仅在横纵两轴都未检测到步长、legacy 即将进入统一网格兜底时拒绝,任一轴已检测到步长时行为和输出完全一致。任一步失败、超时或不适用时不执行最终 OSS PUT,不创建 asset object、`editor_project_resource`、`editor_asset` 或结果 layer;不得保存原图副本、逻辑低分辨率图、诊断图或前后对比图冒充结果。处理成功时只 PUT 一张最终 PNG,并至多各创建一个 project resource 和一个账号素材;源图已有正式 project resource 时,结果资源的 `source_resource_id` 指向该资源。
该零写入保证只覆盖首个最终 PNG PUT 前的可预判与处理阶段。进入持久化后,PNG / asset object、project resource、账号素材与 canvas completion 仍跨 OSS 和多个 SpacetimeDB procedure,沿用既有非事务顺序;后段失败可以保留此前已经确认的对象或记录,不做自动删除补偿,也不由客户端重放请求。调用方应按 `task_id / object_key / resource_id` 重新读取权威项目和素材快照后显式收口。
同步 completion 写画布前必须读取当前 revision 和 dialog,而不能信任请求中提交时的旧 placeholder:
1. dialog 仍存在时,在同一当前布局上完成一个结果 layer,保留源图,并关联结果 resource;
2. dialog 的删除已先持久化时,跳过 layer / dialog 写回,不重建占位、不复活派生图层;已成功持久化的 project resource / 账号素材允许保留;
3. CAS 冲突时不得拿新 revision 原样重放旧整包;按当前项目保存冲突规则重新读取权威快照并显式收口;
4. 客户端回包时若本地 dialog 已删除,不应用完成快照或写历史;现有布局 CAS 没有 deletion tombstone,completion 先提交、删除保存后冲突的极端竞态仍按权威快照收口。客户端不得为该 unsafe POST 配置 `EDITOR_REQUEST_RETRY_OPTIONS`。请求字节可能已发出后的 transport 异常或 `408 / 425 / 429 / 502 / 503 / 504` 不自动重放,先通过 GET 核对项目 / 素材快照,再由用户显式决定是否再次执行;Bearer 中间件在 handler 前拒绝请求后的既有认证恢复继续保留。
## 4. 存量迁移
迁移按 canvas 执行 `backfill → hash 核对 → activate`,并保持幂等:
@@ -110,5 +125,6 @@ SpacetimeDB 必须先于依赖新 procedure / bindings 的 API 发布;前端
- structured 模式下 typed 列而非扩展 JSON 决定几何、层级、分组、显示 / 锁定、资源引用、`asset_kind_override` 和 dialog 状态;标签展示和类型能力判断统一按 `override ?? resource default`。修改当前图层标签与清除覆盖都保持 `resource_id` 和资源行数量不变;复制共享同一资源并复制 override,随后各副本可独立修改 override。两个客户端基于同一 revision 写入时只允许一个成功,冲突方重载后端最新快照,不换上新 revision 原样重放旧整包。细粒度 batch mutation 是取消 2 MiB 兼容入口的后续项,不冒充为本次已完成。
- worker completion 当前以读取时 revision 做 CAS,冲突时拒绝覆盖;V2 保存和保存后快照在同一 procedure 结果内返回,避免“已提交但后续 GET 失败”的不确定结果。lease-fenced 资源 / layer / dialog / job 单事务 completion 仍是后续收口项。
- structured 快照刷新后,上传参考图、生成结果、占位与 dialog 状态均可恢复;资源存在但布局写入失败时不会伪装为保存成功。
- 完美像素处理失败 / 超时时 OSS、resource、asset 和 layer 均无新增;成功时只有一个最终 PNG、至多一个 project resource 和一个账号素材。处理中占位删除已先持久化时,完成请求不复活 dialog 或结果 layer;回包时本地占位已删除则不应用完成快照,已成功创建的资源 / 素材仍可读取;传输结果未知时客户端不自动重放 unsafe POST。
- 回滚重组结果经 schema 校验、canonical hash / 资源引用核对且不超过 2 MiB;超限或不一致时明确拒绝且 structured 快照仍可读取。
- 完成 `npm run spacetime:generate`,确认 Rust 表字段、migration、生成 bindings、HTTP DTO 与前端 `assetKindOverride` 形状一致;再运行 `npm run check:spacetime-runtime-access`、`npm run check:spacetime-schema`、相关 Rust / API / 前端定向测试、`npm run check:encoding` 和 `git diff --check`。
@@ -46,7 +46,7 @@
- `model`:支持 `gemini-3.1-flash-image-preview`(UI 显示 `nanobanana2`)和 `gpt-image-2`,默认 `nanobanana2`。
- `aspectRatio`:按 `x:y` 展示,选项跟随模型。
- `imageSize`:按 `0.5K / 1K / 2K` 展示,选项跟随模型。
- `style`:可选生成后处理风格;未勾选像素艺术时传 `"none"`,勾选时传 `"pixelArt"`。
- `style`:可选生成风格,同时影响提交给 provider 的提示词和回图后的像素规整;未勾选像素艺术时传 `"none"`,勾选时传 `"pixelArt"`。
- `priceMudPoints`:按当前模型和尺寸从编辑器生成计费配置计算;`nanobanana2 1K` 为 `12`,`gpt-image-2 1K` 为 `3`、`gpt-image-2 2K` 为 `5`。前端只提交配置函数计算值,后端用 `editor_generation_config` 校验,不允许素材生成面板自行写死价格。
- 模型与尺寸选项:
- `nanobanana2`:比例 `1:1 / 4:3 / 3:2 / 2:3 / 9:16 / 16:9`;大小 `0.5K / 1K / 2K`。后端走 `/v1beta/models/{model}:generateContent`,把图标规范图作为 `inline_data`,并把 `aspectRatio` / `imageSize` 写入 `generationConfig.imageConfig`;`0.5K` 按 VectorEngine 文档传 `"512"`。
@@ -65,6 +65,7 @@
- 图标素材面板增加紧凑的 `像素艺术` 勾选项。选择保存于现有生成器快照,并可随现有请求和队列 payload 传递;不写入用户可见 `generationInputs`、素材元数据或新建的持久化记录。
- `style` 省略、为 `null`、空字符串或 `"none"` 时按内部 `None` 处理且不告警;`"pixelArt"` 启用像素规整。未知字符串按 `None` 继续生成,并通过既有通用 `warning` 返回 `unsupported-image-style`;非字符串 JSON 仍返回 `400`。
- 2026-08-01 修订:`"pixelArt"` 不再只是后处理,同时向提交给 provider 的提示词末尾追加独立一行约束。图标链路使用「每个图标素材均为像素风格」,**不得**使用「画面为像素风格」——图集生成后要按纯色抠像,绿幕底必须保持平整,画面级像素化要求会与同一段提示词里的「纯色背景必须平整无纹理、无渐变」互相拆台;一张图内是多个彼此分离的素材,需要逐个点名,避免模型只把其中一部分做成像素块。注入发生在 `build_editor_icon_spritesheet_prompt` 返回之后,该函数签名和输出契约不变。约束句只随工程化提示词写入**原图 spritesheet** 的 `editor_project_resource` prompt 列;透明结果的 prompt 列是 `"去除纯色背景"`,自动拆分的切片是 `"自动拆分图集"`,两者都不含约束句。与普通图片和角色形象不同,本链路**响应体**的 `prompt` 字段返回的也是含约束句的工程化提示词,而不是用户输入——图标请求本身没有 `prompt` 字段(收的是 `iconDescriptions`),因此调用方(含外部 API v1)能直接看到绿幕子句、间距要求和本次新增的像素约束。以上 prompt 列写入与响应字段规则都是既有行为,与 `web/master` 一致,本次只是让被回传的模板多了一行。不新增 OSS PUT、项目资源、图集画布项或切片画布项。尚未约束各素材共用同一像素块大小(`estimate_step_size` 取全图相邻峰间距的第 30 百分位,块大小不一时步长估计会偏),等实测。
- 图标链路以已持久化的带纯色背景 provider 原图实际尺寸为基准;BgFilter 正常成功后,把 Alpha 蒙版回贴到该同尺寸平底原图,再执行像素规整。网格分析源使用平底 provider 原图,RGBA 采样源使用 Alpha 已回贴的透明图;规整结果不再经过独立的最终尺寸处理,直接上传透明 spritesheet,成功后才进入原有连通域自动拆分。
- 首版固定参数为分析色数 `16`、Alpha 覆盖阈值 `0.375`、像素格尺寸自动检测、固定色板关闭、K-means 最大采样 `262144`。单格颜色按 `Σ(A × RGB) / ΣA` 进行 Alpha 加权;覆盖率 `Σ(A / 255) / N >= 0.375` 且 `ΣA > 0` 时输出硬 Alpha `255`,否则输出严格 `[0,0,0,0]`。分析色数不限制最终输出色数。
- 像素规整 CPU 工作使用进程级最大并发 `2`;取得并发许可的排队时间与实际处理时间共享最多 `30` 秒预算,同时不得晚于当前请求 deadline,最终以两者中更早者为准。输入图片任一边不得超过 `10000` 像素,总像素不得超过 `8294400`;超限、排队超时或处理超时均保留 Alpha 已回贴的透明图并走非致命降级,随后仍可进入原有自动拆分。
@@ -93,7 +94,7 @@
- 默认提示文本会完整进入 prompt;用户输入不再被解析为素材数量。例如“各种敌人头像:骷髅 哥布林 强盗 龙 蝙蝠等”只是一段完整需求,不代表必须生成或拆出 `6` 个素材。
- 默认打开图标素材面板时选中 `nanobanana2 / 1:1 / 1K`;模型切换后,角色和图标素材面板之间沿用上次选择的模型。
- 图标素材生成请求必须带 `model`、`aspectRatio` 和 `imageSize`;`nanobanana2` 请求体必须包含 `generationConfig.imageConfig.aspectRatio/imageSize`,`gpt-image-2` 请求必须包含文档映射后的 `size`。
- 图标素材面板可选择 `style: "none" | "pixelArt"`;`none` 完整保持原处理路径,`pixelArt` 在 Alpha 回贴后、自动拆分前执行内存像素规整,最终 OSS PUT、项目资源、图集画布项和切片画布项数量不得因此增加。
- 图标素材面板可选择 `style: "none" | "pixelArt"`;`none` 完整保持原处理路径,且提交给 provider 的提示词与未带该字段时逐字一致,`pixelArt` 在提示词末尾追加「每个图标素材均为像素风格」并在 Alpha 回贴后、自动拆分前执行内存像素规整,最终 OSS PUT、项目资源、图集画布项和切片画布项数量不得因此增加。
- 图标素材生成可以上传普通参考图;提交时图标规范图仍走 `referenceImageSrc`,普通参考图走 `referenceImageSrcs`,二者都必须是稳定引用(`objectKey` / 项目资源 ID / 素材 ID),禁止 Data URL / Blob URL,并写入 `generationInputs.references`。
- 透明背景处理和自动拆分都成功后,画布同时出现透明 spritesheet 主图、其右侧的 provider 原图,以及从原图右侧铺开的全部有效连通域图标图层,图标依次命名为 `素材 N`;透明图集成功但拆分失败时仍出现透明主图与右侧原图,透明背景处理最终失败时只出现 provider 原图。
- 选中透明图集图层时显示 `拆分图集`;点击后源图集显示扫描蒙层与 `拆图中` 状态,工具栏按钮同步切换为旋转图标和 `拆图中` 并禁用重复提交。完成后恢复工具栏,不新增第二张图集,只在 provider 原图右侧追加自动识别的独立素材,并同步写入素材库。
@@ -65,6 +65,7 @@
- 角色面板增加紧凑的 `像素艺术` 勾选项,请求使用可选字符串字段 `style`:未勾选传 `"none"`,勾选传 `"pixelArt"`。该选择可以随现有生成器快照和队列 payload 保存,但不写入用户可见 `generationInputs`、素材元数据或新建的持久化记录。
- `style` 省略、为 `null`、空字符串或 `"none"` 时按内部 `None` 处理且不告警;`"pixelArt"` 在 `kind="character"` 时启用像素规整。未知字符串按 `None` 继续生成,并通过既有通用 `warning` 返回 `unsupported-image-style`;非字符串 JSON 仍返回 `400`。同一图片生成请求 DTO 被其它 `kind` 复用时,只有普通图片和 `character` 支持 `"pixelArt"`,其它 `kind` 收到该值也按不支持风格降级。
- 2026-08-01 修订:`"pixelArt"` 不再只是后处理,同时向提交给 provider 的提示词末尾追加独立一行约束。角色链路使用「角色主体为像素风格」,**不得**使用「画面为像素风格」——角色生成后要按纯色抠像,绿幕底必须保持平整,同一段提示词里已写死「纯色背景必须平整无纹理、无渐变」,画面级像素化要求会与之互相拆台;且该提示词已禁止出现角色以外的场景内容,因此只需点名角色本身。注入发生在 `build_editor_character_image_prompt` 返回之后,该函数签名和输出契约不变。约束句**不会**进入角色链路的任何 `editor_project_resource`:原图 resource 的 prompt 列存的是 `role_setting`(用户原文),透明结果的 `output_prompt` 在抠图成功后被无条件覆盖为 `"去除纯色背景"`;完整提交提示词是否留存取决于 provider:`persist_editor_provider_source_image` 写原图 asset object 元数据时用的是 `actual_prompt.unwrap_or(prompt)`,provider 未回 `actualPrompt` 时才存 `submitted_prompt`(含约束句),此时排障可按 `object_key` 查;provider 回了 `actualPrompt` 就存 provider 改写后的文本,该次生成的 `submitted_prompt` 在系统内一处都不落——外部 API 审计的 `request_payload` 只记 `promptChars` 字符数,没有提示词原文。响应体返回的是用户原文,前端显示不变。以上 prompt 列写入与 asset object 元数据规则都是既有行为,与 `web/master` 逐行一致,本次未改动。角色提示词里既有的「严格基于图1的角色美术视觉规范的美术风格」与像素约束存在潜在冲突,本次未改写,等实测。
- 角色 provider 回图先按统一业务像素矩阵执行交付尺寸归一:允许无放大恢复时使用 Lanczos 重采样并居中裁切,无法安全恢复时保留 provider 实际尺寸并返回非阻断告警。归一后的带纯色背景图先持久化并作为 BgFilter 输入;BgFilter 正常成功后,把 Alpha 蒙版回贴到这张同尺寸平底原图,再执行像素规整并上传透明主图。网格分析源使用已收口到实际交付尺寸的平底原图,RGBA 采样源使用 Alpha 已回贴的透明图;软 Alpha 只参与单格覆盖率和 Alpha 加权 RGB 计算,输出 Alpha 硬化为 `0 / 255`。
- 首版参数固定为分析色数 `16`、Alpha 覆盖阈值 `0.375`、像素格尺寸自动检测、固定色板关闭、K-means 最大采样 `262144`。单格覆盖率 `Σ(A / 255) / N >= 0.375` 且 `ΣA > 0` 时输出 `A=255`,颜色按 `Σ(A × RGB) / ΣA` 计算;否则输出 `[0,0,0,0]`。分析色数不限制最终输出色数。
- 像素规整 CPU 工作使用进程级最大并发 `2`;取得并发许可的排队时间与实际处理时间共享最多 `30` 秒预算,同时不得晚于当前请求 deadline,最终以两者中更早者为准。输入图片任一边不得超过 `10000` 像素,总像素不得超过 `8294400`;超限、排队超时或处理超时均保留 Alpha 已回贴的透明图并走非致命降级。
@@ -118,7 +119,7 @@
- `从画布中选择` 后点击已有画布图片可绑定为角色规范,`Esc` 可退出点选状态。
- 上传常规参考图后缩略图右下角显示序号。
- 输入角色设定并生成时,请求包含 `kind: "character"`、角色设定 prompt、参考图数组、`model`、`screenColor`、`aspectRatio` 和 `imageSize`。
- 角色面板可选择 `style: "none" | "pixelArt"`;`none` 的处理路径和产物保持不变,`pixelArt` 在 Alpha 回贴后执行内存像素规整,最终 OSS PUT、项目资源和画布图层数量不得增加。
- 角色面板可选择 `style: "none" | "pixelArt"`;`none` 的处理路径和产物保持不变,且提交给 provider 的提示词与未带该字段时逐字一致,`pixelArt` 在提示词末尾追加「角色主体为像素风格」并在 Alpha 回贴后执行内存像素规整,最终 OSS PUT、项目资源和画布图层数量不得增加。
- 默认打开角色生成面板时选中 `nanobanana2 / 1:1 / 1K`;切换到 `gpt-image-2` 后再次打开角色或图标素材面板应沿用该模型。
- 生成成功后在占位图位置创建 `assetKind: "character"` 图层,右上角显示 `角色` 标签,布局保存包含该字段。