Files
suzmii ff42fff61c
Project CI / Repository checks (pull_request) Successful in 3m18s
Project CI / Frontend tests (pull_request) Successful in 4m3s
Project CI / Backend tests (pull_request) Successful in 7m25s
Project CI / Native shell tests (pull_request) Successful in 19m35s
按评审意见把写锁等待挪出 runtime worker 并收紧 wait_exhausted 记账
- agc_write_file 写路径改经 bridge_write_file_in_blocking_pool 走 tokio::task::spawn_blocking:有界等待是同步轮询(最多约 10 秒),直接在 async handler 里跑会占住 tokio worker,争用窗口内同一轮并行写多个文件时会波及共享同一 runtime 的只读端点与 UI 命令
- 新增用例用默认 current_thread runtime 加心跳任务锁住该性质;把 handler 临时改回同步直调时该用例按预期失败,确认有区分度
- project.write_lock.wait_exhausted 与终态改判一起改为只在真的等过(max_attempts > 1)时发生:hydrate 的单次试探不再写 waitedMs 近似 0 的“耗尽”日志
- 技术方案、decision-log、pitfalls 同步这两条,并补“同步有界等待不能直接跑在 async handler 里”的排障经验
2026-09-10 21:38:48 +08:00

8210 lines
2.1 MiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 决策记录
> 用途:记录已经确认、会影响后续开发的长期技术/产品/协作决策。短期讨论不要写在这里。
> 当前口径:历史条目的旧路径、旧版本和已退役对象只用于追溯,不构成现行实现依据;如与当前代码或 `docs/README.md` 冲突,以当前代码和最新专题文档为准。
## 记录格式
```md
## YYYY-MM-DD 决策标题
- 背景:为什么需要这个决策
- 决策:最终决定是什么
- 影响范围:涉及哪些模块/文档/流程
- 验证方式:如何确认决策仍有效
- 关联文档:相关 PRD、技术文档、提交或 Issue
```
## 2026-09-05 Planning V2 一次性写路径对齐 V1 的项目锁等待窗口
- 背景:V2 审批修改意见后立即 `continue_planning_session_v2`。审批、回合启动、策略落盘和 hydrate 原先无等待取锁,和 GUI 重灌或其它写操作撞车就返回 `项目正在被其他写操作占用`,前端再映射成总控失败。V1 已用完整/短窗口处理同一形状。
- 决策:V2 审批、回合启动、策略落盘、失败投影和 GDD 认领使用完整等待窗口;V2 hydrate 使用短窗口。不引入可重入项目锁,不放宽失效回收。
- 影响范围:`planning_policy_v2.rs`、`planning_session_v2.rs`、V2 hydrate 前端瞬时争用处理。
- 验证方式:Rust 定向测试覆盖短暂占用下的审批、修订续跑和 hydrate。
- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`docs/project-memory/shared-memory/pitfalls.md`。
## 2026-09-05 本进程新建 Windows 私有对象不因继承 DACL 自动 UAC
- 背景:#211 要求 sidecar 满足当前用户独占、禁止继承的 DACL。新建文件会先继承父目录 ACE,生产路径把这种短暂不合格送进 UAC;`project.lock` 还在独占句柄上 harden。含空格项目路径上提权 ArgumentList 被拆开,修复以 exit 1 失败。GDD 审批改意见因此弹权限,V1 锁创建不会。
- 决策:`harden_new_game_creator_private_path` 只在本进程收紧 owner/DACL,失败则删除刚创建的对象,不 UAC 接管。项目锁先写再释放句柄再 harden,并用内容回读防换绑;UAC 仍只用于允许范围内的已有外人本对象。提权 helper 的 ArgumentList 改为一条按 Windows 规则加引号的字符串。
- 影响范围:`config.rs` 的新建 harden 与提权命令行、`project/write_lock.rs` 的项目锁创建;不改变锁竞争、失效回收、Drop 删除,也不放宽 symlink / reparse / 外人本 fail-closed。
- 验证方式:Windows 定向测试覆盖 `Genarrative GameAgent\gameagent-*` 取锁与私有 DACL,以及带空格路径的 quoted ArgumentList。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/pitfalls.md`。
## 2026-09-05 Planning V2 的 3 轮策略与 8 个问题门禁有意不对称
- 决策:模型提示最多提问 3 轮,并在达到 3 后要求出稿;Runtime `question_limit` 默认 8,对偏离模型策略的合法问题保留接收空间,达到 8 才拒绝新 question。前者是模型行为指令,后者是运行时接收边界,数值有意不同,不是缺陷或配置不一致。
- 评审口径:Session/UI 的 8 是容量,不是必须问满的配额;正常路径在 3 轮或更早出稿符合设计。不得仅因数值不同,把模型提示改为按 `question_limit` 出稿、把 3 改成可继续到 8 的软目标,或把 Runtime 门禁收紧为 3。
- 影响范围:仅补充文档解释,现有提示词、运行逻辑、校验和测试保持不变。
- 关联文档:[策划会话 Runtime V2 接入与旧链路退役方案 §1.3](../../technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md#13-问询策略与-runtime-门禁的不对称设计)。
## 2026-09-04 Planning V2 将决定 ID 从 Provider 输入移回 Runtime
- 背景:`plan_submit_gdd` 原先要求模型生成 `decisions[].id` 及原型验证项引用 ID。该字段既不是方案内容,又容易出现 `initial_request`、`initialRequest` 或错误层级,导致合法 GDD 在 Runtime 事后校验阶段失败。
- 决策:Provider-facing `plan_submit_gdd` schema 和入参删除决定/原型验证项 ID。Runtime 按决定数组顺序生成首项 `initial-request`、后续 `decision-{序号}`,并按 `prototype_pending` 决定顺序给原型验证项绑定同一 ID。最终持久化 `plan-gdd.v2` 仍保留 ID,供审批、引用和 fingerprint 使用。V2 尚未上线,不为旧 Provider 输入或历史 V2 artifact 增加兼容转换;不符合新契约的历史数据按现有失败策略处理。
- 影响范围:`planning_policy_v2.rs` 的工具 schema、Provider 入参解析、Runtime 产物构建与定向测试;Planning V2 技术方案。
- 验证方式:定向 Rust 测试确认 schema 不含 ID、无 ID 输入可生成 Runtime ID;并运行 `cargo fmt --check`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_policy_v2.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_session_v2.rs`。
## 2026-09-04 Planning V2 持久化每次 Provider 尝试的诊断产物
- 背景:Provider 已成功返回但策略解析失败时,原 V2 只保留最终错误,无法核对实际请求、原始工具参数和单次重试结果。
- 决策:在 `.agent/planning-v2/debug/<call-id>/` 保存每次尝试的 request、response 和分类事件;诊断文件不进入会话上下文,不参与恢复、重试或 GDD 业务判断,写入失败不改变主流程结果。
- 影响范围:`planning_session_v2.rs` 的 Provider 调用外围和 Planning V2 技术方案持久化目录说明。
- 验证方式:通过 Provider 请求/响应产物可还原每次尝试及 `toolCalls.arguments`,并确认主流程仍按原有解析、重试和状态转换执行。
- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_session_v2.rs`。
## 2026-09-04 Planning V2 把 `gdd.vN.json` 创建成功当作提交点
- 背景:V2 persist 先 create-only 写入不可变 GDD,再更新 index、Markdown、conversation 和 session。后续任一步失败会把 session 标成 `provider_failed`,但不回滚已创建文件;重试会重新生成 UUID/时间戳并撞上“已存在且内容不同”,hydrate 又只信 `current_artifact_version`,项目会卡死。
- 决策:`gdd.v{N}.json` 创建成功即提交点,禁止回滚不可变文件。persist / hydrate / 回合启动若发现 session 指针的下一个连续版本已在磁盘,必须读取既有 GDD 补投影,不得用新的 LLM 入参重建身份。session 指针写成功前的投影失败仍可返回 persist 错误,但恢复路径必须认领该版本。
- 影响范围:`planning_policy_v2.rs` persist/认领、`planning_session_v2.rs` hydrate 与回合启动;V2 技术方案。
- 验证方式:Rust 测试覆盖孤儿 GDD 重试认领、hydrate 认领、成功提交后仍分配下一版本;`cargo fmt --check`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`。
## 2026-09-04 PlanningSessionRuntime V2 用协议工具输出问询和 GDD
- 背景:原型已验证 `plan_ask_question` / `plan_submit_gdd` 两个协议工具、深层 schema、提示词只留策略、`tool_choice=auto` 可跑通;生产 V2 仍解析正文 `{kind,question|gdd}` JSON,并把形状骨架写在 system prompt 里。浅 schema + 正文 JSON 会误导模型把 GDD 写成普通文本;`tool_choice=required` 与 DeepSeek thinking 不能同时使用。
- 决策:V2 Provider 请求固定挂这两个协议工具,`tool_choice=auto``strict=false`。模型必须恰好调用其中一个;Runtime 解析 `toolCalls` 归一为 Question/Artifact,正文 JSON 视为非法。system prompt 只保留问询/出稿策略和当前问询进度,不再附 JSON 骨架或数量清单。入参不再要求模型回声 `schemaVersion`,落盘 GDD 仍由 Runtime 写入 `plan-gdd.v2`。既有结构门禁(含 `initial-request` 首项、`validate_plan_game` 数量/字数)不变,失败仍回灌一次。不把协议工具写入 `capabilities.tools`,不执行 MCP/Skill,不把 `tool_call`/`tool_result` 写入会话消息。
- 影响范围:`planning_session_v2.rs` 请求构造、重试文案与 Provider 结果投影;`planning_policy_v2.rs` 工具 schema、解析和入参 `schemaVersion`V2 技术方案。
- 验证方式:Planning V2 定向 Rust 测试覆盖工具解析、缺 `schemaVersion` 的合法 GDD、正文 JSON 拒收、工具 schema 含嵌套 `game` 字段、提示词不再含骨架;`cargo fmt --check`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`。
## 2026-09-03 新建策划会话采用 PlanningSessionRuntime V2,旧 Supervisor 链路直接退役
- 背景:现有“做方案”依赖 `project-supervisor-plan` 根 Run、`project-planning` 子 Run、静态委派、delivery、Acceptance Graph 和审批前 evidence。新策划 Agent 只需要单 Agent 会话、问询、GDD 和审批;继续在旧 Runtime 上逐条放宽会保留身份/编排耦合。未来策划 Agent 可能支持无限多轮、MCP 和 Skill,需要避免把当前 8 题/GDD/no-tools 固化为 Runtime 根结构。
- 决策:新增独立 `PlanningSessionRuntime`,复用 Provider/流式、会话持久化、项目锁、原子写和基础错误恢复;当前启用 `mode=gdd`、最多展示 8 个有效问题、GDD 审批和用户修改。新建“做方案”会话不创建 Supervisor root、planning child、delegation 或 acceptance evidence。V2 使用独立 `.agent/planning-v2/` 与 V2 schema,继续输出 `game/fast_gdd.md`;不自动转换旧会话。
- 兼容性:Session 保存 `mode`、可空 `questionLimit`、`capabilities.tools/skills`;完整会话记录与 Provider 请求上下文分离;消息模型预留 tool/skill 事件类型但本期不执行 MCP/Skill。无限问询、上下文摘要、多产物和能力执行以后作为策略/能力层扩展,不重新引入 Supervisor 身份模型。
- 当前进度:P0 合同冻结、P1 会话内核和 P2 GDD/审批核心已落地;P3 正式入口/UI 接入已开始,P5 旧链路退役尚未开始。
- 退役:V2 切换时旧链路直接封存;所有未完成旧会话投影为 `legacy_retired` 失败,禁止继续问询、审批、恢复或 continuation。旧 GDD、approval、conversation 和 `.agent/planning` 文件只读保留;旧入口 caller 关闭,但不删除旧代码、旧测试或旧数据。
- 影响范围:AGC 做方案入口、Rust/Tauri planning session/Provider adapter、GDD/审批 V2、前端 planning lane、阶段任务与 BDD 验收;做游戏/做素材 DirectProject 不变。
- 验证方式:按 `docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md` 的 P0P5 阶段验收执行;至少覆盖第 8 个问题、上限后 question 抑制、Provider 失败、非法输出、批准/修改/退回、重启恢复、旧会话切换强制失败、迟到 Provider 结果丢弃和当前空能力快照。
- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`。
## 2026-09-03 PlanningSessionRuntime V2 统一 Agent 推断语义
- 背景:旧 V1 使用 `default_pending` / `answerSource=default` 表示未提问时由 Agent 按默认建议补齐的字段;该语义会让 V2 的 Agent 推断看起来像产品默认值,也会造成原型与生产字段不一致。
- 决策:V2 只使用 `confirmed`、`assumption_pending`、`prototype_pending` 三种决定状态;`assumption_pending` 的来源统一为 `agent_inferred`。`answerSource` 仍不是独立阻断项,缺失或不一致时按状态归一为 `user_freeform`、`user_option` 或 `agent_inferred`。V1 的 `default_pending` / `default` 校验和历史数据保持不动,不作为 V2 合同的一部分。
- 问询策略:V2 出稿前必须确认玩家核心行为、单局目标/核心循环、MVP 制作边界;其中任一仅由 Agent 推断时继续问一个关键问题。`questionLimit` 是 Runtime 对已展示问题数的硬上限,提示词中的“默认最多三轮”只是策略偏好,不要求与硬上限数值一致。
- 影响范围:V2 GDD 输入/产物、Provider system prompt、前端 V2 类型与决定状态展示;旧 Supervisor/V1 存储、校验和历史产物不变。
- 验证方式:V2 解析 `assumption_pending` 不报错并落盘为 `assumption_pending/agent_inferred``default_pending` 不作为 V2 合法状态;核心三项未确认时提示词要求继续问询;相关 Rust/TS 定向测试、类型和编码检查通过。
- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_policy_v2.rs`。
## 2026-09-04 PlanningSessionRuntime V2 将既有输出阻断原因回灌给 Provider
- 背景:原型已将会导致输出拒收的字段、类型、数量和长度契约写入提示词,并在校验失败重试时回灌具体原因;生产 V2 仍只有简要 GDD 形状提示,模型可能重复犯同一结构错误。
- 决策:生产 V2 只同步当前已经存在的 question/GDD 校验契约到 Provider system prompt,并在现有一次重试中明确列出本次阻断原因、要求逐项修复;不扩大校验范围、不新增门禁、不增加重试次数,也不把 `answerSource` 变成阻断条件。
- 影响范围:`planning_session_v2.rs` 的 Provider prompt 与现有非法输出重试提示;`planning_policy_v2.rs` 校验逻辑、问询上限和持久化契约不变。
- 验证方式:运行 Planning V2 定向 Rust 测试、`cargo fmt --check`、`git diff --check`,确认提示词构造和现有校验路径通过;不改变既有校验结果。
- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_session_v2.rs`。
## 2026-09-04 PlanningSessionRuntime V2 提示词只保留形状和策略
- 背景:把逐字段长度、数量、类型清单写入 system prompt 后,提示词与 JSON 骨架、Rust 校验器三重叠,模型也难以消化长清单;精确数字已由校验失败的一次重试回灌。
- 决策:V2 system prompt 只保留问询/GDD 骨架、`game` 与 `decisions` / `prototypeValidationItems` 同级边界、状态枚举、首条 `initial-request` 约束,以及一行易错数量范围(options / keywords / pillars / coreLoop / mvpSystems / outOfScope / oneLiner)。不把逐字段长度、控制字符、label 去重等校验细则写入 prompt;校验范围、门禁和重试次数不变。
- 影响范围:`planning_session_v2.rs` 的 Provider system prompt`planning_policy_v2.rs` 校验逻辑与非法输出重试路径不变。
- 验证方式:提示词含骨架与同级边界、不含逐字段长度清单;现有 Planning V2 定向测试、编码和 diff 检查通过。
- 关联文档:`docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_session_v2.rs`。
## 2026-09-03 AGC 登录 route event 使用 handler 已验证主体归属
- 背景:登录请求进入时尚未拥有 `AuthenticatedAccessToken`,通用 tracking middleware 无法从响应 extensions 归属登录成功用户;将 AGC marker 直接写入按用户/业务日幂等的 `daily_login` 又会受到不同来源登录顺序影响。
- 决策:密码登录和手机号登录 handler 在认证及 session 创建成功后,仅对合法 AGC marker 请求向响应 extensions 附加一次性 `TrackingLoginSubject`。tracking middleware 在现有 `ExternalApiPrincipal`、`AuthenticatedAccessToken` 之后使用该主体生成登录 route event 的 `user_id`、`owner_user_id` 和 User scope。`daily_login` 保持原有 event key、幂等键、业务日和 metadata 语义,不承担 AGC 来源归因。
- 安全边界:主体只来自后端认证服务返回的用户 ID;Header 仅决定是否进行 AGC 来源归因,不参与身份计算;不解析、不记录 access token、refresh token 或 Cookie。
- 影响范围:`api-server` 登录 handler、资产读取 handler、tracking middleware、Issue225 技术方案和定向集成测试;不修改 SpacetimeDB schema、migration、bindings、OpenAPI、后台页面或认证响应协议。
- 资产读取边界:`/api/assets/read-url` 和 `/api/assets/read-bytes` 继续支持匿名公开读取;有效 Bearer 复用同一次可选鉴权结果,在成功响应 extensions 中传递 `AuthenticatedAccessToken`,供 AGC route tracking 归属用户。External API Key/Admin 路由继续使用各自主体和审计链路。
- 验证方式:密码/手机号登录真实 `build_router` 链路分别验证 AGC route event 的 marker、真实用户归属和 outbox 落盘;资产读取主体保留、响应 extension 与匿名不附加单测;tracking identity 单测、`cargo check --locked -p api-server`、相关 `cargo test --locked -p api-server`、格式、编码和 diff 检查通过。
- 关联文档:`docs/technical/【后端架构】Issue225登录成功AGC用户归属修复方案-2026-09-03.md`、Issue #225、`server-rs/crates/api-server/src/tracking.rs`、`server-rs/crates/api-server/src/app.rs`、`server-rs/crates/api-server/src/assets.rs`。
## 2026-09-03 server-rs workspace 保留独立 platform-agent 排除边界
- 背景:主站 #251 合并清理旧玩法表后,`server-rs/Cargo.toml` 的旧 crate 排除清单被收窄;`platform-agent` 仍位于 `server-rs/crates/` 下,但实际由 AGC 独立 Cargo workspace 通过路径依赖使用。若不显式排除,主 workspace 的 `cargo fmt --all` 会把它识别为“位于 workspace 内但不是 member”的非法包并直接失败。
- 决策:继续将 `crates/platform-agent` 放在 `server-rs` workspace 的 `exclude` 中。它不加入主 workspace,也不在其 manifest 中新增平行 `[workspace]`AGC 的独立 Cargo manifest 继续负责该 crate 的构建边界。
- 影响范围:`server-rs/Cargo.toml` 与仓库 Rust 格式检查;不改变 `platform-agent` 源码、AGC 依赖关系或主站运行时。
- 验证方式:`npm run check:rustfmt` 通过;`cargo fmt --all --manifest-path server-rs/Cargo.toml -- --check` 不再报告 `platform-agent` workspace 错误。
- 关联材料:Repository checks #5755、主站合并提交 `025f62729`、`server-rs/Cargo.toml`、AGC `apps/ai-game-creator-shell/src-tauri/Cargo.toml`。
## 2026-09-03 Native shell 检查清单只维护现役 HostBridge 文件
- 背景:#251 退役并删除了 H5 个人中心 QR 扫码弹层及其测试,同时移除了不再有源码消费者的生命周期 / 网络 wrapper`scripts/check-native-shells.mjs` 和根 Vitest include 仍保留旧路径,导致 Native shell tests 在最后的 H5 HostBridge 调用链扫描阶段失败。
- 决策:Native shell 静态检查、H5 HostBridge 定向测试和根 Vitest include 只列出现役文件;已删除的 QR 扫码组件/测试及生命周期、网络 wrapper 从清单移除,不恢复已退役实现,也不为历史路径增加兼容占位文件。
- 影响范围:`scripts/check-native-shells.mjs`、`vitest.config.ts` 和 Native shell 检查门禁;不改变移动壳现役 `QrScannerOverlay` 或 HostBridge 公共契约。
- 验证方式:运行 `npm run check:native-shells`,确认 H5 HostBridge 调用链静态扫描不再引用已删除文件;同时运行编码、格式和 diff 检查。
- 关联材料:Native shell tests #5758、主站合并提交 `025f62729`、已删除的 `PlatformProfileQrScannerModal` 文件。
---
## 2026-09-02 Direct 过程卡按回合阶段状态驱动
- 背景:DirectProject 结果卡把工具活动词、中间文本和真实回复增量都当成“实时回复”,标题随最近一次事件跳动;上游常整包返回正文时还叠加合成打字机,用户看到的是行为名而非当前阶段。
- 决策:Direct 过程卡顶部标题只由 `GameCreatorDirectTurnUpdateStatus` 决定(accepted=需求已接收 / running=任务执行中 / streaming=回复生成中 / finalizing=结果整理中 / completed=回复已生成 / failed=处理失败),小字只展示当前正在执行的具体内容并统一加“正在”前缀;真实回复增量(AccumulatedText)才标记 streaming,计划、推理、工具输出与 Activity 一律 running。生成中的累计回复直接作为 assistant 消息气泡在会话列表中原位更新,不再拼进过程卡;进入 finalizing / completed 时保留完整累计回复直到正式消息接管,失败时清除未完成正文。移除合成打字机回放;工具说明/中间文本不再触发 streaming。计划/推理通知收敛为 `preparing` 活动并在界面显示“正在思考中”,原始推理/计划正文不进入 UI,思考期的心跳按 1.2s 限流。命令/文件/工具执行细节与回复流解耦,`stream=false` 时仍展示在过程卡;MCP 工具按用户语义显示(例如 `agc_write_file` 为“正在写入文件:<项目相对路径>”、图片/素材/搜索/试玩分别显示生成、导入、搜索、试玩等动作),未知工具只显示“正在调用工具”不暴露内部工具名;命令显示“正在执行命令:<命令>”,验证类命令显示“正在验证游戏:<命令>”。同一活动后续无正文的心跳不得用通用文案覆盖已展示的具体工作。展开/收起是同一 `project + clientTurnId` 内的持久状态,内容更新不重置,切换新回合才收起;展开详情的滚动条轨道和角落保持透明。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs` 的 DirectProject observer、`apps/ai-game-creator-shell/src/App.tsx` 的事件投影、`ProjectSupervisorView` 过程卡渲染与对应 AppSurface 回归。
- 验证方式:Rust 单测证明只有开启流式时的 AccumulatedText 是 streaming、preparing 通知只产生 thinking 活动词且不携带原始推理文本、执行细节在 `stream=false` 时仍保留,并覆盖全部 AGC MCP 工具语义、未知工具不泄漏、绝对路径 / 上跳路径不展示;AppSurface 覆盖接受态、preparing 显示“正在思考中”、running 长文本展开、command-exec 与写文件心跳不覆盖具体工作、streaming 正文进入 assistant 气泡且过程卡只显示阶段、同一回合后续 running 不覆盖正文也不收起、失败后清除未完成正文、正式消息接管不重复;样式核对确认展开详情的滚动条轨道与角落透明;AGC typecheck、全量 appSurface、rustfmt、`npm run check:encoding`、`git diff --check` 通过。
- 关联文档:`docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`、分支 `feat/agc-llm-router-official-chain`。
---
## 2026-09-02 GDD 审批卡的后台 hydrate 不抢占已加载决定
- 背景:项目页首次加载和运行态刷新可能并发 hydrate。卡片已经显示后,短暂的 `hydrateBusy` 会让已打开的评论弹层提交按钮瞬时变灰,用户无法提交已输入的修改意见。
- 决策:`hydrateBusy` 只控制恢复区的重试按钮;已加载审批卡的决定按钮和评论弹层继续依据 `canDecide` 与 `decisionBusy` 门控。只有权威状态显式返回 `recoveryPending=true` 时才禁止决定,并保留弹层中的输入内容。
- 影响范围:AGC GDD 审批卡前端、Fast GDD 审批交互文档;不改变 hydrate command、审批 DTO 或后端状态机。
- 验证方式:运行评论弹层恢复竞态回归、完整 `appSurface.test.ts`,并执行类型、编码和 diff 检查。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`、`apps/ai-game-creator-shell/src/features/project-workspace/GddApprovalCard.tsx`。
## 2026-09-02 旧玩法表采用两阶段退役清理
- 背景:旧创作模板的业务代码已退出现役编译链,但 SpacetimeDB 中的历史表仍需先完成数据清理;直接删除表定义会扩大 schema 迁移和客户端兼容风险。
- 决策:阶段一只在 `spacetime-module/src/migration.rs` 增加受 `database_migration_operator` 保护的 `clear_retired_database_tables` procedure。procedure 使用固定的 63 张旧玩法表清单,不接受动态表名;`dry_run=true` 只返回逐表行数统计,`dry_run=false` 在同一事务内逐表清空,任一失败整体回滚。阶段一不删除表定义、不修改 `legacy_schema/**`、migration 导入导出白名单或生成 bindings。
- 阶段边界:清理清单包含旧 gameplay、`custom_world`、Puzzle / Puzzle Clear、Bark Battle、Match3D、Jump Hop、Wooden Fish、Square Hole、Visual Novel 和 Big Fish 表;`runtime_setting`、`runtime_snapshot`、`user_browse_history`、`creation_entry_config` 等现役表明确排除。阶段二只有在备份、客户端兼容性和运行态确认完成后,才评估从 module 定义与 migration 白名单移除空表,并按 schema / bindings 流程发布;固定清单旁保留 TODO。
- 影响范围:SpacetimeDB migration procedure、`spacetime-client` 生成 bindings、后端数据契约和本决策记录;禁止新增 SQL `DROP TABLE`、`--delete-data=always` 或直接写系统表的实现。
- 验证方式:固定清单测试确认数量为 63 且不含现役表;本地数据库以已授权 operator 执行 dry-run,确认 63 张表均返回 0 行且未写入;apply 的单事务回滚由 procedure 实现,实际 apply 仅在另行授权的维护窗口执行。另运行 bindings 生成、SpacetimeDB schema / runtime 检查、编码和 diff 门禁。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`server-rs/crates/spacetime-module/src/migration.rs`。
- 补充落地:阶段一同时删除此前仅因退役而保留的旧玩法业务实现、未挂载 API handler/router/worker、旧客户端 facade/mapper、旧领域 crate、`retired/legacy-creation-templates/**` 归档及 `packages/shared/src/contracts/**` 中已无仓库内消费者的旧玩法公共契约;`legacy_schema/**`、生成表 bindings、现役 shared contracts 和持久化 schema 不在本次删除范围。
## 2026-08-31 DirectProject 客户端扩展按独立 Skill/MCP 导入
- 背景:DirectProject 需要使用用户在 AGC 客户端导入的市面原生 Skill、MCP 和 Plugin 内容,但第三方内容不应直接安装到运行时 Codex,也不应要求用户转换为 AGC 自定义格式。
- 决策:客户端提供一个全局“扩展”入口,统一接受文件、目录、zip 和标准 Plugin;目录、zip、Plugin 只是导入来源,发现出的每个 Skill 和每个 MCP Server 分别成为独立扩展项,分别列表、重命名、启用、禁用和删除。已识别项导入后默认启用,下次 DirectProject Codex 启动时按原生 Skill root 和 MCP 配置注入。
- 命名:客户端列表名称与 Codex 运行时名称使用同一个原生标识,不维护 display/runtime 两套名称;重复或同名项保留为新的独立项并自动追加 `-2`、`-3`。Skill 重命名只修改客户端运行时副本中的有效名称,原始导入内容不修改。
- Plugin 边界:Plugin 只作为导入容器提取 Skill/MCP;当前 DirectProject 关闭的 hooks、apps、remote plugin 和完整 Plugin Runtime 不接入。单个可执行文件或脚本不提供手动指定为 MCP 入口的功能。
- 信任边界:不审核第三方 Skill 文案、脚本、二进制、MCP tool 或网络行为;导入阶段不执行内容。客户端只做标准结构识别、必要配置解析和 zip staging 路径边界处理,且不向第三方扩展注入 AGC 凭据或内部路径。
- 影响范围:AGC 客户端扩展设置 UI、客户端本地扩展存储、DirectProject Codex app-server 启动准备和 pool fingerprint;不新增 HTTP 服务、SpacetimeDB schema、公开 API 或独立 Plugin Runtime。
- 当前实现:客户端导入/list、Skill 临时 root 和 MCP 隔离配置注入均已落地。第三方 MCP 只从客户端已启用独立项生成本次隔离 `CODEX_HOME/config.toml`,每项固定非 required;配置错误或 app-server 启动状态失败只更新对应 `last_error`,内置 `agc_tools` 继续由客户端单独注入。客户端已启用 Skill/MCP 的名称、来源路径和内容指纹共同参与 DirectProject app-server pool identity。
- 验证方式:分三阶段验收:先验证导入拆分和完整列表,再验证 Skill 运行时发现和重命名,最后验证 MCP 配置合并、Plugin 提取和失败隔离;只增加对应的定向测试、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【技术方案】DirectProject客户端Skill与MCP扩展导入方案-2026-08-31.md`、`apps/ai-game-creator-shell/src/features/runtime-config/RuntimeConfigDialog.tsx`、`apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs`。
## 2026-08-30 批准 GDD 直接进入做游戏链路
- 背景:立项策划 GDD 批准后需要给用户一个进入做游戏的自然出口,产品决策改为点击按钮后直接开始建造。
- 决策:批准态 GDD 交付行提供“做成游戏”按钮。点击后读取当前项目的权威 `game/fast_gdd.md`,直接创建自动游戏工作区、导入 `text/markdown` 参考附件,并以固定建造指令自动启动 Direct Codex;不再回首页等待用户二次提交。该动作不复制原项目的 `approvedGddRef`、planning sidecar 或 approval receipt。
- 影响范围:AGC 前端 GDD 交付行与现有自动建项/附件导入/Direct Codex 链路;移除首页 RichInputArea 的 GDD 一次性预填链路;不新增 HTTP API、SpacetimeDB schema、迁移、OpenAPI 或正式构建绑定。
- 验证方式:批准态按钮直接创建工作区、导入附件、携带固定首条指令进入项目工作台且重复点击不重复创建的 appSurface 回归;类型检查、编码检查和 `git diff --check` 通过。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`。
---
## 2026-08-31 Direct 回合把 Codex item 落成有界行为账本
- 背景:sidecar 已让模型看见本轮附件路径,但 native 读 / MCP / 写文件只存在于隔离 `CODEX_HOME` 的瞬时 stdout,回合结束即删。无法判断「没读附件」还是「读了仍走默认收集类」。
- 决策:GUI DirectProject 每个 `clientTurnId` 追加 `.agent/runtime/direct-codex/turns/<id>.jsonl`,并在 `agent.db` 写一条 `direct.codex.turn` 摘要。记 sidecar 提供的路径与文件 hash、`item/completed` 的 Read/List/Search/MCP/写文件(不含 stdout、patch、MCP result),以及 `offeredRead` / `firstDesign`。审计 fail-open,不阻断做游戏。Home、CLI、Supervisor 收据模型不接。不灌附件正文,不强制读取,不为 GDD 开特例。
- 影响范围:`direct_codex_audit.rs`、Direct GUI command 边界、Codex collect 循环;前端 / jsonl 气泡 / sidecar 文案不变。
- 验证方式:Rust fixture 覆盖 turn_start hash、绝对路径相对化、stdout/diff 不落盘、art brief 保留、list/search 不算已读、firstDesign 顺序、256 条截断、写盘失败不 panicsidecar 渲染与 Direct 活动词测试保持通过。
- 关联文档:`docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`、issue #212。
## 2026-08-31 Direct 本轮附件只映射路径,不灌正文、不区别 GDD
- 背景:issue #212。首页附件已经复制到 `assets/uploads/` 并登记,但 Direct 首轮只把用户原文发给 Codex,原文件名不是磁盘路径,模型会另起一套玩法。
- 决策:Home 与 Project 共用 `DirectCodexTurnAttachment`。有项目路径或导入状态时,只在发给 Codex 的 user prompt 末尾附有界 sidecar(原名 → 项目相对路径、类型、大小、状态);无路径且无状态时保持首页元数据文案。不灌正文、不强制读取、不按 GDD 开特例。做成游戏固定 prompt 不改,同一条 Direct 首轮附件链自动吃到 sidecar。jsonl 与工作台气泡仍只写用户原文。
- 影响范围:`direct_codex_attachments.rs`、Direct command 边界、首页建项 latch、工作台首轮 invokeSupervisor / 做方案首轮忽略附件 sidecar。
- 验证方式:Rust 渲染测试(Home 逐字兼容、Project 映射、非法路径);home.suite 附件 Direct invoke 含 `localPath`;无附件不出现 `attachments` 键;做方案首轮仍走 Supervisor 且无 sidecar;后续手打消息不带 attachments。
- 关联文档:`docs/technical/【技术方案】DirectProject本轮附件路径映射-2026-08-31.md`、issue #212。
## 2026-08-26 运行中自主扩图提案留在编排层
- 背景:`agent-runtime-orchestration` 已能构造和调度动态 DAG,但 LLM 在执行中发现缺少步骤时没有通用的安全扩图合同。
- 决策:新增严格 serde 的 `GraphProposal``TaskProposal` + `GraphEdge`)和 `GraphLimits`,由 `TaskGraph::apply_proposal` / `expand_with_proposal` 在内存中构造不可变候选图;新节点默认 `Pending`,边方向为前置 `from` → 依赖方 `to`。
- 安全与一致性:所有 Agent、端点、重复引用、环、节点/边/深度/扇出预算在候选返回前一次校验;边只能指向新节点,禁止给已运行任务原地追加依赖。任一失败保留旧图。成功后的 epoch、基图版本、proposal 幂等和持久化由宿主负责,crate 不调用 LLM/Provider/ToolHost/Runner,也不写 `.agent/runtime/**`。
- 验证:非游戏 conformance 覆盖有效扩图、ready/wave 重算、未知 Agent/端点、重复边、已有任务修改、环、预算、严格 JSON 和原子失败;关联文档为 `docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` V1.55。
## 2026-08-26 通用多 Agent DAG 编排与执行内核分层
- 背景:`agent-runtime-core` 已承接 catalog、run/action 生命周期、lane、宿主 ToolHost、spawn/all-join 和 Provider 契约,但动态任务图的 ready 选择、依赖波次与返工下游闭包仍混在 `platform-agent::game_creation`,其它产品无法复用且非法环会被合并成伪 wave。
- 决策:新增纯 Rust `agent-runtime-orchestration`,依赖方向固定为 `agent-runtime-orchestration -> agent-runtime-core`。公共层只持有任务 ID、Agent ID、通用状态和依赖边,统一负责构图校验、ready、active/satisfied 波次、下游闭包和全量/返工选择;动态构图仍必须是 DAG,跨轮循环通过新的 pass / epoch 表达。
- 产品边界:16 个游戏任务、六组角色、产物/验收条件、Evaluator Markdown 和中文语义路由继续留在 `platform-agent`AGC 组合根使用公共层校验任务图与 `AgentCatalog`。Runtime store、Runner、Provider、权限、ToolHost、委派 journal、isolated write scope 和 `.agent/runtime/**` 不迁移、不双写。
- 验证方式:非游戏 conformance 覆盖并行分支、汇合、repair closure、AgentCatalog 和非法图失败关闭;`platform-agent` 锁定种子 DAG 与现役波次/返工顺序,并验证环拒绝和 catalog 注入。根检查脚本必须执行新 crate 测试。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` V1.54。
## 2026-08-27 `plan.submit_gdd` 拒绝无审批决定的 `user_revision`
- 背景:结构校验允许 `round=0 + user_revision + confirmed`,提交闸原先只做结构、身份和 Session CAS。Provider 可在首次 collecting、澄清续跑或提交前质量返工里把未确认项标成用户审批修改,审批卡显示「已确认」。
- 决策:新版本 create 时,payload 含 `user_revision` 则当前 session 的 `lastDecisionRef.action` 必须是 `revise` 或 `reject`;否则 `PLAN_INVALID_REQUEST`。同 `submissionId` replay 不重判。不恢复 session 前缀逐项相等,不把 `user_revision` 与审批意见正文对齐,也不在这次处理 `round≥1` 的 `user_option` 伪造。
- 影响范围:`planning_submit.rs` 提交闸;Fast GDD 技术方案第 5.1 / 8.2 / 12 节。
- 验证方式:首次 collecting 带 invented-confirmation 必须拒绝且不落 GDDreject continuation 再交 `user_revision` 的 v2 仍成功。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`。
## 2026-08-28 planning continuation 必须沿当前 delivery 游标推进
- 背景:`lastDecisionRef.action=revise/reject` 在用户修订后的质量返工中必须继续有效,但仅凭该历史指针无法证明当前 `agent.delegate` 选择的是本次 planning session 的当前分支。
- 决策:不新增用户修订授权字段,也不在 `plan.submit_gdd` 重复遍历 approval receipt/GDD lineage。已有 planning session 创建新 child 时,`repairOfDelegationId` 必须直接等于旧 session 的 `latestDelegationId`;不一致即在 Provider 启动前以 `PLAN_NEEDS_RECONCILIATION` 拒绝。合法用户修订及其后质量返工继续保留 `lastDecisionRef`,成功提交新的 GDD 后仍由 submit successor 清理该指针。
- 影响范围:`planning_coordinator.rs` continuation 投影门;Fast GDD 技术方案第 8.2 节和提交步骤;不改变静态委派通用返工合同或 `PlanSessionV1` schema。
- 验证方式:新增当前游标 continuation 正向/旧 delivery 负向回归;CI 继续验证首次伪造 `user_revision` 拒绝、用户修订后质量返工提交成功及现有澄清/返工 lineage。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_coordinator.rs`。
## 2026-08-27 退款 emergency spool 容量溢出保持可恢复
## 2026-08-27 退款 emergency spool 容量溢出保持可恢复
- 背景:本机 emergency spool 仅作为 SpacetimeDB 完全不可达时的最后恢复路径,原有 `MAX_BYTES` 分支会直接返回 `Dropped`,导致扣费已经完成但没有可重放记录。
- 决策:达到普通 outbox `MAX_BYTES` 时,将退款记录写入同一持久目录的 `refund-overflow-*` 文件;该文件与普通 pending 文件一样由启动恢复和后台 worker 重放到 SpacetimeDB,且按 refund ledger id 保持幂等。溢出文件不计入普通阈值,但必须触发容量告警;底层磁盘写入失败仍进入关键退款人工补偿流程。
- 影响范围:api-server wallet refund emergency spool、资产失败退款日志、loadtest / 预览 Compose 持久卷、后端架构与开发运维文档。
- 验证方式:运行 api-server `wallet_refund_outbox` 定向测试,确认超限写入并保留 overflow 文件;运行 SpacetimeDB profile 测试、Compose 配置校验、编码和 diff 门禁。
## 2026-08-27 短期认证状态进入共享 typed projection
- 背景:短信验证码和微信 OAuth state 仍只存在 API 进程内 HashMap,多节点请求或 API 重启会直接丢失,无法满足无粘性会话的鉴权恢复要求。
- 决策:`AuthStoreProjectionView` 增加 `phone_codes` 与 `wechat_states` typed 字段,由 `auth_store_projection_meta` 以 JSON 投影持久化;启动恢复、CAS 同步和失败后的权威刷新都覆盖这两类短期状态。验证码哈希使用部署级稳定盐(当前复用 `GENARRATIVE_JWT_SECRET`),各 API 节点必须一致;发码前先刷新权威投影并用占位验证码记录做一次 projection CAS,只有占用成功才调用短信 provider,避免跨节点冷却竞态;认证 handler 在发码、消费验证码、创建/消费微信 state 后都要完成 projection sync,失败即返回服务错误;所有会读取或变更本机认证工作集的认证主链路(登录、刷新、`/me`、会话管理、密码、绑定和微信 state)在领域操作前先从正式投影做一次受 CAS 保护的只读刷新,受保护 Bearer 中间件也会在进入业务 handler 前执行同样的刷新,刷新失败时 fail closed,不能依赖粘性会话;同步遇到 CAS 冲突时,若本次尝试期间没有新的本地变更则恢复正式快照,若仍有待同步 revision 则由后续认证请求重试,避免节点永久卡在 pending。微信 OAuth state 设置有界活动数量,避免单个 JSON 投影无界膨胀。短期状态仍由 `module-auth` 内存工作集执行领域校验,但不再把本机 HashMap 当作持久化或跨节点真相。
- 影响范围:`module-auth` projection、`spacetime-module` auth schema/procedure、`spacetime-client` bindings/facade、api-server 手机号 / 微信 handler、认证架构与运维文档。
- 验证方式:运行 module-auth projection roundtrip(验证码可跨恢复校验、微信 state 可跨恢复消费)、SpacetimeDB schema/runtime/DDD 门禁、api-server 定向测试、编码和 diff 检查。
---
## 2026-08-27 外部生成历史采用受控保留清理
- 背景:`external_generation_job`、`external_generation_job_summary` 与 `external_generation_job_event` 都是持久化表;摘要和 payload 边界收紧后,已确认的终态历史仍会继续占用 SpacetimeDB 常驻内存,且事件审计链会随任务数量增长。
- 决策:新增仅 migration operator 可调用的 `prune_external_generation_job_history_and_return`。默认按 `source_module=editor-canvas`、30 天保留期和 `job_id` 游标分批运行;只删除主任务与摘要状态一致、属于 completed / failed / cancelled、摘要已有 `notification_acknowledged_at` 且终态时间达到 cutoff 的任务。事件、摘要和主任务仍按同一事务顺序删除,但每次事务最多删除 256 条事件;事件未删完时保留任务与摘要并返回同一个 job cursor,维护脚本下一次继续,避免单个任务形成无界事务写集。默认 dry-run,必须固定 dry-run 返回的 cutoff 后再 applypending / running、未确认通知、摘要缺失或状态不一致的数据永不删除。其他 source module 必须显式指定并单独评估;资产对象和钱包流水不随任务历史删除;不新增自动定时器或 runtime 清理权限。
- 影响范围:`server-rs/crates/spacetime-module/src/external_generation.rs`、外部生成事件 job_id 单列索引、SpacetimeDB 生成 bindings、`scripts/spacetime-maintain-external-generation-jobs.mjs`、架构与生产运维文档。
- 验证方式:覆盖终态 / 活跃态 / 已确认与未确认摘要、状态或身份不一致、cutoff 边界测试;运行 SpacetimeDB module tests/check、bindings 生成、schema/encoding/diff 门禁,并在维护窗口先 dry-run 再 apply。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、PR #203。
## 2026-08-27 SpacetimeDB 工具链统一升级到 2.8.3
- 背景:SpacetimeDB 2.8.0 引入 TypeScript submodule 与调度延迟观测,2.8.1 修复 v1 WebSocket 订阅移除死锁、TypeScript SDK `array<u8>` 读缓存别名和 Rust string 默认值支持,2.8.2 修复 table accessor 改名自动迁移,2.8.3 修复 scheduled function 从实际执行时间重排导致的长期漂移。仓库若继续锁定 2.7.0,会保留这些已知运行时与 SDK 问题。
- 决策:`server-rs/Cargo.toml` 的 `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 精确锁定 2.8.3;本地 CLI / standalone、Rust bindings、worker smoke 本地镜像、官方容器压测镜像和生产 provision 下载根同步对齐 `v2.8.3`CLI / standalone commit 门禁为 `8e410d28...`。2.8.3 不再使用 2.7.0 的 hotfix3 特殊资产标签口径,但同版本 commit 校验继续保留。
- 影响范围:Rust workspace lockfile、SpacetimeDB bindings、本地 dev 版本门禁、容器 smoke / loadtest、server provision Jenkins 与项目 SpacetimeDB skills / 文档;现役 module 未使用 submodule,本次不修改 schema 或 migration。
- 验证方式:核对 CLI 版本和 commit,重新生成 Rust bindings,运行 `npm run check:spacetime-schema`、相关 Cargo check / tests、server provision 工具测试、dev 调度测试、encoding 和 diff 门禁。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-08-26 Fast GDD 修订后先取证再允许再次委派
- **现象**:GDD v1 经用户选择“修改”后,策划子 Agent 正确提交 v2,但 plan 根 Supervisor 的 `Delegated` 阶段仍同时广告 `agent.delegate` 与审批前置工具;模型可能在 Acceptance Graph 重新取证前重复创建修订 delivery,随后被 `PLAN_PROVIDER_USAGE_DEFERRED` 拦停。
- **决策**plan 根阶段增加轻量的 `AwaitingAcceptanceEvidence` 状态。当前根最新 GDD 无 approval receipt/pending、session `latestSubmittedRef` 精确指向该提交、delivery 已由根认领且 Acceptance Graph 返回 `NeedsEvidence` 时,只广告 `file.read`、`agent.acceptance_update`、`agent.run_status`;只有用户真正对最新审批卡选择修改/退回后,才恢复 `agent.delegate`。
- **边界**:不放宽 Provider usage 门禁,不重构 delegation/repair lineage,不自动生成证据或审批 pending;审批 pending 仍只由既有 acceptance gate 在 `agent.acceptance_update` 成功后创建。
- **验证**:新增一条阶段工具面回归,并通过 15 条 M1C-2a acceptance gate 定向测试、plan root 原生工具目录测试、`cargo check --all-targets`、格式与 diff 检查。
- **锁边界修正(2026-08-27**:阶段判定拆为 `plan_root_supervisor_stage_at_locked` 与负责取得一次项目锁的外层入口;Provider tool-plan builder 已持有项目锁时直接复用 locked 入口。Acceptance Evidence 判据和阶段工具面不变,禁止在持锁调用链中再次获取 `.agent/project.lock`。
- **回归验证**planning submit 定向测试 68 passed、Provider request builder 定向测试 17 passed、Tauri `cargo check` 与 `cargo fmt --check` 通过。
## 2026-08-24 AGC Direct 媒体能力只通过客户端语义工具开放
- 背景:资源页已经补齐视频、角色动画、音效和背景音乐的 create/derive 能力,但 Direct Codex 只能准备标准美术包,无法查询已登记源资源或表达新增媒体意图。直接开放 Tauri invoke 会把项目路径、revision、operation、幂等键、登录态和事务权力交给模型。
- 决策:只新增 `agc_list_registered_assets` 与 `agc_create_or_derive_resource` 两个语义工具。Codex 只能提交资源过滤条件或 kind/mode/localAssetId/prompt/name;客户端权威解析 manifest 来源,生成并恢复稳定 operation/idempotency,串行付费调用,执行权限、项目锁、画布/素材目录准备、下载校验和 manifest CAS。完全匹配的 pending 请求自动恢复,不创建替代付费请求。
- 输出边界:资源查询和生成结果只投影相对路径、稳定 Canvas/resource/asset/task 身份、序列帧身份、pending 状态及脱敏告警;不返回完整 manifest、prompt、model、provider route、绝对路径、URL、Token、Cookie 或 API Key。角色动画及视频/音频新请求统一携带同名画布与素材目录上下文;已有冻结请求不迁移、不重写。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`apps/ai-game-creator-shell/src-tauri/resources/agc-skills/agc-client-projection/SKILL.md`。
## 2026-08-23 External 去背景绑定权威静态来源与真实画布尺寸
- 背景:External v1 去背景曾把调用方 `assetKind` 原样带入持久化,并允许 `sourceImageSrc=A + targetLayerId=B` 覆盖不同资源;Python helper 又为所有画布完成请求固定生成 `1024×1024` 占位,导致非方形透明结果按占位尺寸拉伸。
- 决策:入队前从当前 owner 的项目资源或素材库解析来源权威语义类型,资产对象存储类型只参与非静态媒体门禁;显式项目资源 ID / 素材 ID 优先于 objectKey 回退,同一纯 objectKey 对应的候选权威元数据不一致时返回 `400` 并要求用 `sourceResourceId` 或业务 ID 消歧,禁止按列表首条决定类型。请求类型冲突或任一记录属于视频、音频、动画、图片序列时返回 `400`,队列只保存服务端解析出的静态语义类型。无 `canvasCompletion` 的原位替换优先比较双方 `assetObjectId`,任一缺失时回退 canonical `(bucket, objectKey)`,并要求默认类型一致;纯 objectKey 省略 `sourceResourceId` 时自动绑定目标图层资源并写入队列,由 Worker 复验同一绑定。helper 使用画布会话时必须取得真实源宽高或显式 `canvasWidth + canvasHeight`,不再猜测方形尺寸。
- 影响范围:External v1 去背景入队与 worker 复验、OpenAPI、Python helper、外部编辑器 skill 和相关契约测试;不修改 SpacetimeDB schema、BgFilter 协议或去背景输出尺寸语义。
- 验证方式:覆盖非静态类型与权威类型冲突、来源/目标不同对象拒绝及同对象通过、非方形 helper completion;运行 api-server 定向测试、helper self-test、OpenAPI 解析、编码与 diff 门禁。
## 2026-08-20 UI Editor LLM 递归输出与参考图单文件限制
- 背景:结构识别、界面语义建议和多图合并直接把 LLM 工具 arguments 反序列化为递归树;结构识别与语义建议还在 async command 中同步读取并 base64 编码参考图。模型异常输出或过大图片可能造成不受控内存、栈和 async worker 占用。
- 决策:三个工具调用的 arguments 统一限制为 `1 MiB`,先解析通用 JSON 并迭代检查,再进入递归业务类型。结构识别按每棵树独立限制 `512` 个 LLM 节点 / `32` 层,不跨树求和且不计 Rust 页面根;语义建议限制 `4` 节点 / `4` 层;合并计划限制 `512` 节点 / `32` 层。超限整次拒绝,不截断或交付部分结果,日志不记录 arguments 正文。
- 输入边界:`merge_ui` 继续直接接收 `State`,不修改 Tauri/frontend IPC 参数;进入 Rust 后、发起 LLM 前按每棵源树独立限制 `512` 节点 / `32` 层,不跨树求和,并限制 `2 MiB` 序列化投影。UI 设计参考图只设单张 `5 MiB` 上限,不设批次合计或像素数上限;元数据检查、有限读取和 base64 编码进入 blocking worker,不新增命令超时。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-19 M1 审查后四项可靠性修复
- **审批 stale 收口**`status` 或 `phase` 为 `needs-reconciliation` 的策划根不再满足审批所需的 active 身份;审批命令返回 `PLAN_STALE_APPROVAL`,并且不得创建 approval receipt。
- **rollout capability**AppData 配置增加 `planning.capabilityEnabled`,默认 `true`。关闭后拒绝新的 plan 根 run、`plan.submit_gdd`、approval pending 和 decision mutationhydrate 仅返回已有 sidecar 的只读视图,不执行恢复写入。
- **profile/source 边界**durable run-profile binding 集中拒绝 `project-supervisor-plan + autonomous-game-build`;自主构建的 scheduler 与 completion consumer 使用不含 plan 的精确 source matcher,普通 plan `standard` 仍属于通用 trusted matcher。
- **边界**:不新增既有项目首次进入策划入口;不改做游戏、做素材路径。回归覆盖 capability、stale receipt、错项目 sidecar 写前失败、非法 binding 不落盘与新项目 ID 形状。
## 2026-08-19 更正 M1D-2 首页入口映射
- **更正**`bf2185fba` 把“做游戏”误接为 `standard + project-supervisor-plan`,并额外增加“直接开建”按钮;这与既有产品决定“做方案入口独立成链,不动做游戏路径”冲突。
- **当前入口合同**:仅首页“做方案”新建项目以 `standard + project-supervisor-plan` 进入立项策划;“做游戏”和“做素材”保持 `autonomous-game-build` 直接开建。目录提交与 Enter 自动创建使用同一映射。
- **边界**:项目页新建、打开既有项目和 Godot 导入不新增策划入口;已有 planning sidecar 或 active plan lineage 只恢复其原有链路。
- **回归**:覆盖做游戏目录提交、做方案目录提交、做方案 Enter 自动创建,以及既有做素材 Enter 自动创建,分别断言首个 Supervisor run 的 profile/source。
## 2026-08-18 M1D 收口:design 组展示名改完
- **背景更正(先于结论)**:此前把这条的严重度建立在「新项目默认主路径上『立项策划』与『策划 Agent』同屏共存」上,**该说法未经验证且不成立**。用 appSurface harness 实测策划路径:`子 Agent 状态栏` 根本不渲染,`策划 Agent` 出现 0 次、`立项策划` 出现 2 次。结构上二者确在 `launcherView === 'project-development'` 分支的同一棵树里(`ProjectDevelopmentView` 的 dock + 作为 `supervisor` 传入的 `ProjectSupervisorView`),但未能把用例驱动到该分支,故不作为事实主张。若真会撞,也是**批准后进入完整制作**那一段(此时 `state=approved`,阶段进度卡守卫仍放行),比原描述窄得多。
- **仍然要改的理由**:与撞不撞名无关。`bf2185fba` 只把 `taskGroupLabels.design` 改成「设计实现组」,另两本同概念字典没动,于是同一个 design 组在开发者面板/文本汇总里叫「设计实现组」、在工作台状态栏里叫「策划 Agent」——**这个语义不一致是该提交引进来的**。第 18.2 节要求本就是「design 组用户名称改为设计实现组」,与新阶段区分只是其动机之一。改完比回退便宜(回退还需挑拣 `c6a08ef98` 里混着的按钮改名与 prettier 重排,并反改第 18.2 节)。
- **口径选择**:采最小方案,保持兄弟项的「X Agent」体系(美术 Agent / 程序 Agent / …),design 取「设计实现 Agent」。**不**把 `taskGroupLabels` 直接塞进 `groupConfigs`——两本字典命名体系不同(「X组」对「X Agent」,且音乐组/音频 Agent、运营组/发布 Agent 连词都不一样),直接替换会连带改掉另外五个分组名。消灭重复字典属视觉改版,单独立项。
- **改动**`agentPresentation.ts` 的 `groupConfigs`、`view/project-development/index.tsx` 的 `summarizeAgent` 默认名与同文件分组头像字(`策`→`设`)、`model.ts` 中 `agentId.includes('design')` 的个体名兜底(`design-director` 走这里;`design-foundation` 的「玩法策划 Agent」单列在前,不变)。内部 agent id 与 `design` 分组键均未动。
- **测试**:原估「3 处断言」严重低估。实跑发现 `project-development.suite.ts` 有 16 处派生断言需跟改——「X 文本回执」由 `App.tsx` 的 `${candidate.label} 文本回执` 拼出、「历史成果 · X」由 `resourceProjectionModel.ts` 拼出、dock 的 article accessible name 亦然。替换时用后行否定守住 `玩法策划 Agent`(它含 `策划 Agent` 子串),替换前后该串恒为 6 处。`agentRuntimeModel.test.ts` 与 `projectResourceProjectionModel.test.ts` 里剩余 4 处是测试自造的输入 fixture、不由字典派生,保持不动。
- **验证**`appSurface.test.ts` **383 passed / 0 failed**`agentRuntimeModel` / `agentTraceSummary` / `projectResourceProjectionModel` / `projectResourceLiveUpdateModel` 合计 39 passed`agc:typecheck`、ESLint `--max-warnings 0`、`check:encoding`、`git diff --check` 通过。
## 2026-08-18 M1D 审查修复补充:锁错误脱敏与澄清轮次口径
- **锁错误回传绝对路径**`acquire_project_write_lock` 的 Err 内嵌 `.agent/project.lock` 真实绝对路径,违反第 18.3 节「返回值不包含绝对路径……或内部诊断」。补 `redact_agent_runtime_project_paths` 的三处是前端审批卡真正会显示的那条链:`reconcile_plan_gdd_approval_projections_at`hydrate 在取自己的锁之前调它)、hydrate 自己的锁、`decide_plan_gdd_at`(其错误与 hydrate 的错误渲染在同一个错误区)。planning 另有 14 个取锁点沿用未脱敏写法,属 M1B/M1C 既有模式,本次不扩面。脱敏不破坏 `项目正在被其他写操作占用:` 前缀,`project_gates.rs` / `provider_recovery.rs` 两处按前缀分类的判据不受影响。
- **澄清轮次差一格**`clarificationRound` 与 `awaitingAnswerFor.round` 都由 `static_delegate_lineage_counters` 派生,该函数排除目标自身,是 0-indexed 的「已答轮数」;后端判上限用的是 `current_round + 1`。阶段进度原样渲染成「轮次 X/3」整体差一格,问最后一轮时显示「轮次 2/3」,字面暗示还剩一轮。**只改前端文案,不动 DTO 语义**:等待回答时显示「第 N+1 轮 / 共 3 轮」(此时 `latestDelegationId` 就是当前 delivery,+1 恰好等于后端校验用的轮次),其余状态退回「已完成 N/3 轮澄清」,不猜当前轮。
- **顺带**`planning_hydrate.rs` 里 `reconcile` 的错误原本用同一 code 把 `to_string()` 当 detail 重包一层,而 `PlanningStorageError` 的 Display 已是 `"{code}: {detail}"`,渲染出 `CODE: CODE: detail`code 与 detail 均无变化,改为直接 `?` 传播,并把「不重复拼 code」钉进回归。
- **测试陷阱(值得记)**:写「占住项目锁」的 fixture 时必须给锁 JSON 填**真实** `createdAt`。失效锁回收的年龄判定读的是该 JSON 字段而**不是**文件 mtime`project_write_lock_age_seconds`),填 0 会让锁显得约 1.7e9 秒老、越过 600 秒阈值被当场回收删除,hydrate 反而成功。第一版 fixture 正是这样自证失败的。
- **验证**Rust `planning_` 组 **155 passed / 0 failed**(原 154 + 本次 1 条);`appSurface.test.ts` **383 passed / 0 failed**378 原有 + 5 条新增);三条新回归均经变异验证,逆转对应修复即变红。`cargo fmt --check`、`agc:typecheck`、ESLint `--max-warnings 0`、`check:encoding`、`git diff --check` 通过。
- **撤回一条此前的审查发现**:曾判定 hydrate 读 manifest 缺符号链接判定(因其走裸 `root.join` 而非 `resolve_local_project_path`)。复核后**不成立**`read_manifest` 自身在 `metadata.file_type().is_symlink()` 处即拒(`manifest.rs`),防护在另一层;`.agent` 目录本身为符号链接的残差也无窗口,紧随其后的 `resolve_planning_path` 同样逐段判定。未据此改动代码。
- **仍未修**:① hydrate 在校验 GDD/session 的 projectId 与 manifest 一致之前已执行落盘投影修复,违反第 18.3 节固定顺序。**已裁决为不修**:它唯一有后果的前提是 projectId 变成每项目唯一,而该常量方案不属本工作包管辖;单独为一个不受控的假设改动权威读取路径不划算。触发后的实际后果也已复核为可忽略——命令仍正确返回 `PLAN_PROJECT_ID_MISMATCH`,写入的 pending/session/index 均为幂等或可重建投影,`session.previous.json` 是改名而非删除且只在 primary 缺失时发生。裁决与改动前提(含「不能把 `reconcile` 直接挪到 hydrate 取锁之后」这个非重入锁陷阱)已作为注释写在 `planning_hydrate.rs` 调用点旁,使提醒与会坏掉的代码同处,而不是只留在本文档里;② design 组展示名仍有 `agentPresentation.ts` 的 `groupConfigs` 与 `view/project-development/index.tsx` 的 `summarizeAgent` 两处硬编码「策划 Agent」,注意该两处与 `taskGroupLabels` **命名体系不同**(「X Agent」对「X组」),直接替换会连带改掉另外五个分组名,需先定命名口径。
- **新记一条既有问题(非 M1D 引入)**`seedManifest.projectId` 是常量 `local-project-draft`App 的 5 个 init/import 调用点全传它,因此**本机所有项目 projectId 相同**。第 18.3 节第 1 步依赖的「manifest 与 projectId 校验」因此分辨不出任意两个项目——把 A 项目的 `.agent/planning/**` 整体拷入 B 项目仍会通过。该门当前近乎恒真,须单独立项处置。
## 2026-08-18 M1D 审查修复:GDD 审批决定失败路径与恢复期弹层门控
- **审查范围与基线**:对 `14c00017c..bf2185fba` 的 M1D-1/M1D-2 全量改动做规格对照审查(技术方案第 13、18 节)。审查完成后分支又前进了 `4624fd795`(文档同步)与 `c6a08ef98`(前端文案回归断言修复)两条,二者都不改 `src/**` 生产代码,审查结论不受影响。
- **修复一:决定失败也必须重灌权威状态**。`decidePlanGdd` 原来只在成功分支 hydrate`catch` 只写错误后 rethrow。后端 `decide_plan_gdd_at` 有多条真实 `PLAN_STALE_APPROVAL` 分支(GDD 已不在当前 lineage、identity 不符、版本被更新版本取代、pending 丢失或不一致),命中后卡片停在已失效的 pending 身份上、三个决定按钮仍可点,且 `recoveryPending` 永不翻真导致「重试恢复」入口不渲染,卡内没有任何恢复路径。现在失败分支同样 hydrate,落实第 18.3 节「approval decision 返回后调用 hydrate」(该句不区分成功与失败)。**两句顺序已被回归钉死**`hydratePlanGddState` 入口会 `setPlanGddError(null)`,必须先 hydrate 再写决定错误,写反会把这条错误擦掉。
- **修复二:responseId 复用键纳入 comment**。原键为 `approvalRequestId:action`,不含 comment,违反第 13.2 节「用户改变 action/comment 后必须生成新 responseId」。在第 14 节恢复矩阵承认的「receipt 已提交但 command response 丢失」构造下,用户改写修改意见后重提会带着旧 responseId,命中后端「同 responseId 的审批意图不一致」硬拒,改写后的原因永远落不了盘。现在键挂在 `approvalRequestId` 上并比对 `{action, comment}` 完整意图。**判据方向为宁可多换不可少换**:receipt 已存在时多换的最坏后果是 `replayed` 降级成 `already-decided`(两者都是 Ok,且 already-decided 正是第 18.2 节要求的刷新态),少换则是硬错误。
- **修复三:`recoveryPending` 必须挡住已经打开的评论弹层**。第 18.2 节要求恢复期只允许重试同一 ID、不允许提交决定;原实现只把 `canDecide` 接到三个触发按钮上,而弹层是打开之后才可能被后台 hydrate 翻掉决定资格的,其「提交决定」按钮只看 `busy || !comment.trim()`,仍可提交。现在 `submitComment` 与该按钮都判 `canDecide`,并在弹层内说明原因。**刻意不自动关弹层**,否则会丢掉用户已经写好的修改意见。
- **测试**appSurface harness 新增 `hydrate_game_creator_plan_gdd_state` / `decide_game_creator_plan_gdd` 两个分发分支与 `createPlanGddStateView` fixture;未配置策划状态时 hydrate 与接入前一样抛出,既有用例行为不变。新增 `tests/appSurface/plan-gdd.suite.ts` 三条回归,并逐条做过变异验证——把对应修复单独逆转后三条各自以自己的断言变红(修复二的变异是**部分逆转**:保留新 Map 结构、只删掉 comment 比对,因此该用例钉住的是 comment 这一维本身而非那次重构)。
- **验证**`appSurface.test.ts` **381 passed / 0 failed**378 既有 + 3 新增);`agentTraceSummary` 与 `rememberCommand`(另两个 import `src/App` 的用例文件)13 passed`agc:typecheck` 通过;6 个改动/新增文件 ESLint `--max-warnings 0` 通过;`check:encoding` 5409 文件通过。不改 Rust——三条全在前端,后端语义已经正确。
- **对既有记录的更正**:M1D-1 与 M1D-2 两条记录分别称「Shell TypeScript typecheck 仍被仓库既有依赖缺失阻断」「appSurface UI suite 受仓库现有缺失 Tauri plugin 依赖阻断,未把该基线失败归因于本包」,在原分支主工作树上都不成立:`agc:typecheck` 干净退出,appSurface 378 条全绿;两道门分别位于 CI 的 `check:native-shells`(且 typecheck 排在 cargo test 之前)与 Frontend tests 内,一直是活的。实际情况与记录相反——`bf2185fba` 改名 `taskGroupLabels.design` 后,appSurface 有 8 个用例文件的断言变红,随后由 `c6a08ef98` 修复;把该套件记为「基线阻断、不归因本包」正是让这条自带回归合入的原因。**隔离工作树的依赖缺失不能作为跳过门禁的依据,须回原工作树复跑后再下结论。**
- **未修的审查发现(本次不并入,单列后续)**:① hydrate 在校验 GDD/session 的 projectId 与 manifest 一致之前,已执行 `reconcile_plan_gdd_approval_projections_at`、session previous 提升与 index 重建等落盘修复,违反第 18.3 节固定顺序,其中 `session.previous.json` 的提升+删除不可逆(触发需外部篡改 `.agent/`,App 自身流程造不出该分歧);② 项目写锁竞争时 `acquire_project_write_lock` 的错误原文内嵌项目绝对路径,被原样回传前端,违反第 18.3 节「返回值不包含绝对路径」,常态可达;③ design 组展示名只改了 `taskGroupLabels` 一本字典,`agentPresentation.ts` 的 `groupConfigs` 与 `view/project-development/index.tsx` 的 `summarizeAgent` 仍硬编码「策划 Agent」,与新阶段「立项策划」同屏共存,违反第 18.2 节;④ 阶段进度「轮次 X/3」直接透传 0-indexed 的 `clarificationRound` 未 +1(后端自己用的是 `current_round + 1`),最后一轮显示「轮次 2/3」,字面暗示还剩一轮。
## 2026-08-18 M1E 隔离工作树:Fast GDD submit 有界拒绝与覆盖审计
- **范围与结论**:在 `codex/genarrative-isolated` 上按 M1E 只接受具备完整触发链的缺陷。确认 Provider 连续输出不合法 `plan.submit_gdd` 时,Runtime 原有「rejected observation → 同 child run 续跑」链没有次数上限,模型可反复请求 tool-plan 并累积历史 observation;这是可达的 prompt 膨胀与资源消耗链。
- **有界收束**:为 Runtime state 新增 durable `planSubmitGddRejectionCount`。仅本次 Provider input / 候选 GDD 触发的 `PLAN_INVALID_REQUEST`、`PLAN_SIZE_LIMIT` 计数;前四次维持既有 batch abort、observation 与 same-run continuation,第五次仍先完整落 rejected observation,再将该 planning child 终态失败并记录专用失败 audit,不发起第六次 Provider tool-plan。字段以 serde default 向后兼容,进程重启不会重置;新 child run 才从零开始,普通工具 observation 不计入。
- **自审修复**`PLAN_SIZE_LIMIT` 也可能来自读取既有不可变 GDD/receipt,而非 Provider 输入;`PLAN_VERSION_LIMIT_REACHED` 则由既有 lineage 已达 128 版或其读取异常决定。若只按 error code 分类,会把 durable authority 异常误当可纠正模型输出,最多多跑五次。现将这些读取/版本边界转为 `PLAN_NEEDS_RECONCILIATION`Provider input/候选 GDD 的大小限制仍可按上项重试,不扩大改变其它 authority 错误语义。
- **覆盖审计**:第 21 节要求的澄清三轮/回答绑定/continuation、submit→receipt 的 replay 与投影恢复、审批后修订、hydrate 空态与恢复均已有真实 Runtime/存储回归。未发现能低成本构成完整新断链的前端或跨层缺陷,因而未为拼接既有单测新增大而脆的 E2E。
- **自审收口**:复核第五次 rejected observation 已持久化、但终态失败写入前进程中断的窗口:原先恢复会再次进入 Provider loop,形成第六次请求。恢复入口现读取 durable counter,达到 5 时直接复用同一终态失败与 audit 收束,不依赖内存 continuation;该修复与原有计数边界完全同域。未发现其它具备完整触发链、且可在 M1E 或此前范围内明确修复的问题;不扩展至 M2 的 `approvedGddRef` 或完整构建绑定。
- **已验证**:新增“第五次终止”“第五次后恢复仍终止”“超限既有 GDD 转 reconciliation”及“lineage 版本上限不作 Provider feedback”四条回归;随后 Rust `planning_` 定向组 **157 passed / 0 failed**`cargo check --offline --all-targets`、`cargo fmt --check`、`npm run check:encoding`6608 files)和 `git diff --check` 均通过。仓库既有 Rust warnings 未在本包扩修。M1E 完成。
## 2026-08-18 M1D-2 隔离工作树实现:入口分流与阶段进度
- **隔离范围**:在 `codex/genarrative-isolated`、基线 `5b11a0530` 上开工;只接入口分流、阶段进度和实际项目总控页面的现有审批卡挂载,不接 M2 `approvedGddRef` 构建绑定、完整构建按钮或 M1E 端到端故障注入。
- **入口合同(已被 2026-08-19 更正)**:本条原将游戏新项目默认接为 `standard + project-supervisor-plan`,现改为仅首页“做方案”进入该路径;做游戏/做素材保持既有直接构建。项目打开/项目页新建不注入 start mode,保持老项目行为。
- **页面接线**`ProjectSupervisorView` 现在复用 M1D-1 的 hydrate、GDD 审批卡和阶段进度;阶段进度只消费 `plan-gdd-state-view.v1`,显示 `轮次 x/3`、当前版本与状态徽章。`project-planning` 显示为“立项策划 Agent”,设计组展示名改为“设计实现组”。
- **Runtime 路由**:项目总控聊天提交在规划入口或已读到 `project-supervisor-plan` 时继续使用 `standard + project-supervisor-plan`;规划 Run 尚未终态时不另起通用聊天 Run,要求先完成当前策划步骤。直接开建及非规划入口保留原提交 profile/source。
- **自审边界与验证**:只接受有完整触发链路的问题;本包不改变 Runtime source 门禁、审批命令、构建准入或老项目 direct-build 语义。改动文件 ESLint、Shell TypeScript 两套 typecheck、`agentRuntimeModel.test.ts`29 passed)、编码检查与 `git diff --check` 通过;appSurface UI suite 受仓库现有缺失 Tauri plugin 依赖阻断,未把该基线失败归因于本包。自审未发现测试失败或修复边界明确的 M1D-2 之外缺陷;M2 approved-GDD 构建绑定与 M1E 跨层故障注入保留。
- **合入状态**M1D-2 以 `bf2185fba``完成M1D-2入口分流与阶段进度`fast-forward 合入 `feat/five_min_design`;自审中发现 `WorkspaceLauncher` 漏解构/传递新增的 `createHomeDraftDirectBuild`,会使 Shell typecheck 直接失败,已在同一提交修复。
## 2026-08-18 M1D-1 隔离工作树实现:hydrate/read model 与 GDD 审批卡
- **隔离基线与范围**:在 `codex/genarrative-isolated`、基线 `14c00017c` 上开工;只实现 M1D-1 的前端 hydrate/read model 与 GDD 审批卡,不接 M1D-2 入口分流、完整构建按钮、构建准入或 M1E 下游链路。
- **Runtime hydrate**:新增 `hydrate_game_creator_plan_gdd_state` 与 `plan-gdd-state-view.v1`。command 通过严格 `deny_unknown_fields` 的 `{projectPath}` JSON 输入并经过项目权限校验;Rust 只读取 canonical GDD/receipt/session/pending/index authority,空 planning 项目返回 `not_started` 且不创建目录,页面不扫描 sidecar/Markdown/index。
- **审批卡链路**:工作台在项目打开、Runtime 状态推进、窗口恢复时 hydrate;待审 GDD 由 hydrate 提供,审批决定调用既有 `decide_game_creator_plan_gdd`,按 `(approvalRequestId, action)` 复用 `gdd-response-<uuid>`,决定返回后再次 hydrate。卡片提供 approve/revise/reject,后两者必须填写原因;正文通过独立详情弹层展示,不在卡片下无限堆叠。
- **恢复与安全边界**`recoveryPending` 时卡片只显示恢复重试,禁止决定;审批 pending 只有验收门已落下的 `gdd-approval` sidecar 才能成为可操作事实,不能把 `awaiting_gdd_approval` session 误当验收通过。未审批 GDD 缺失或错绑 session successor 时返回 `PLAN_SESSION_RECOVERY_REQUIRED`pending/receipt 身份不一致 fail-closedhydrate 响应使用序列号丢弃过期并发结果。
- **必要回归与验证**:保留一条必要 Rust 回归,验证已初始化但无 planning 目录 hydrate 返回完整空 view 且不创建存储;该测试通过。`cargo check --offline --all-targets --target-dir target-m1d1`、`cargo fmt --check`、`git diff --check` 通过。前端 TypeScript 复用原工作树依赖 junction 做检查,新增代码无类型错误;仓库现有缺少 `@tauri-apps/api/event`、`@tauri-apps/plugin-http`、`@tauri-apps/plugin-clipboard-manager`、`@tauri-apps/plugin-opener` 依赖的问题仍保留。
- **自审保留项与合入状态**:真实验收门等待期间 pending 尚未建立时不显示可操作审批卡;M1D-1 不新增完整跨层故障注入或构建链测试,留给 M1E。所有检查以完整触发链路为准,未发现需要扩大到 M1D-2/M1E 的问题。实现已由 `0052a80da` 合入 `feat/five_min_design`,其后 ESLint 修正为 `5b11a0530`。
## 2026-08-18 `M1C-2c` 隔离工作树开工:A/B 决策卡语义与信封合同收口
- **隔离基线**:在 `codex/genarrative-isolated` 上从 `0199fb6e4` 开工;`M1C-2b` 已合回 `feat/five_min_design`,本包不回改其三轮上限、continuation 幂等、答案绑定或预算折叠。
- **本包范围**:把 planning 决策卡从“同一推荐的三种采纳程度”改为 A/B 平行方案 + 固定“需要原型验证”;Runtime 按 label 形状校验并确定性映射 `state` / `answerSource` / `answerSummary`,同步更新 planning role brief、final-reply 收束提示与定向回归。
- **冻结映射**A、B → `confirmed / user_option`;固定第三项 → `prototype_pending / user_option`;自由填写 → `confirmed / user_freeform``default_pending / default / round=0` 只允许由未提问默认项产生。`answerSummary` 必须逐字等于用户选中的 label 或自由填写原文。
- **信封合同**:每张 planning 卡必须恰好三项;第一项 label 以 `A` + `·`/`:`/`-` 开头,第二项同形以 `B` 开头,第三项逐字为“需要原型验证”;形状不符 fail-closed,不建立 pending,不改变 session。
- **提示词纪律**:B 必须是真实、形状不同且说明代价的平行路线;第三项 description 要给出可执行的 30~90 分钟微型原型验证;平台事实和 MVP 已排除项不提问;改口保留用户原文并标注被哪一轮推翻。
- **纠正旧记录**:此前 M1C-2b 条目把“第四轮”写成正常进入 reconciliation;代码核查确认正常路径在 `agent.delegate` 边界硬拒并返回 failed observation`planning_coordinator` 的超三轮 reconciliation 仅是损坏血缘的纵深防御,后续文档收口时一并更正。
- **当前进度**Runtime 映射、role brief、Supervisor playbook/final-reply 提示、A/B/自由填写/非法信封回归已落地;`planning_clarification_*` **13 passed / 0 failed**`project_planning` prompt 定向回归 **5 passed / 0 failed**`planning_submit` 定向回归通过,prompt bundle、`cargo fmt --check`、离线 `cargo check --offline --all-targets --target-dir target-m1c2c`、`npm run check:encoding`5406 files)及 `git diff --check` 均通过。实现已由提交 `6e4bd9703` 合回 `feat/five_min_design`。
## 2026-08-17 M1C-2b 隔离工作树实现完成:策划澄清中转、链路派生与预算注入
- **当前基线**`M1C-1`、`M1C-2a` 已合回 `feat/five_min_design`;本隔离分支开工后又以 merge commit `9f12d8467` 合入原分支截至 `a8215a599` 的全部已提交改动,包含 P4 的 Fast GDD 识别顺序修复与 P5 的委派栅栏 detail 等价性锁定。原工作树未提交的 `planning_approval.rs` 不属于该合并且未触碰。M1C-2a 的固定 Goal Contract、验收图与审批前置门作为既有前置,不在本包回改。
- **本包范围**:只把已发布的 `AGC_NEEDS_USER_INPUT_V1` 子 Agent → Supervisor 中转链接入 Fast GDD 的 planning session:回答以 `(requestId, answersSha256)` 原子绑定原 delivery 后,严格派生唯一 continuation identity、更新 `appliedAnswers` / session phase / active run 投影,并让 planning 子 Agent 的后续 Provider 请求获得当前澄清轮次与累计活跃时间的受控上下文。
- **编码前裁决**:现役 answer sidecar 只有题目、原始回答及 transport hash,不能提供 session schema 对 `decisionsSummary` / `prototypeValidationItems` 的必需字段;若等 continuation Provider 补字段,该 Provider 又会先被 `appliedAnswers.length != clarification_round` 拒绝。故冻结 Runtime 的确定性派生:从固定问题前缀提取 topic,按三个固定选项/自由填写映射 state、answerSource、answerSummary;“需要原型验证”同步生成固定四字段 30~90 分钟微型原型项。派生失败或已有同 ID 内容不一致均失败关闭,不请求 Provider 猜测。
- **开工验证计划**:先以现有澄清中转回归为基础,补 planning 专属的三轮边界、答案冲突、重复 wake/continuation 幂等、session 链与预算注入断言;随后运行对应 Rust 定向测试、格式/编码/diff 门禁。实现和验证结论在本条持续补充。
- **当前实现**:新增 planning coordinator,在首个 `project-planning` child 落 revision 1 initial-request session`NeedsUserInput` delivery 投影为 `awaiting_user_input`;回答绑定且 continuation child durable 后,在同一项目写锁内严格派生 `appliedAnswers`、`decisionsSummary`、`prototypeValidationItems`、`collecting + activeRunId`。固定三选项及自由填写均按技术方案映射,问题必须是单题、`第N轮·关键决定`、固定三选项且 N 为 1~3;第四轮在建立 Supervisor pending 前失败关闭。
- **恢复与锁序**:本条只约束 **M1C-2b 新增的 planning 澄清写投影路径**,不把结论扩大到整个 Agent Runtime。Supervisor 直接回答先以只读候选判别是否为 planning 澄清,再按 `project write lock → execution lock` 重取并在双锁内重读 pending`answer-prepared` 恢复先释放旧 execution lock,再按同一顺序重取,期间旧候选若已被并发回答、替换或清理,只按 obsolete candidate 让路,不把合法前滚误标为 reconciliationplanning parent-wake 先把父 run 持久化为 `waiting-for-delegate-receipts`,待主循环返回并释放 execution lane 后,再在 lane 外按 `project → execution` 创建唯一澄清 pendinglane 忙时只 deferred、重放不增加 session revision 或 action。恢复仍在 planning child 进入 Provider 路径前补 session 投影;已精确投影的 retry/provider handoff 只恢复冻结请求,不因 usage fold 的 `Deferred` 误进 reconciliation。**边界说明**`main_loop.rs` 既有通用 completion blocker 仍存在 execution lane 内调用 project-lock wrapper 的路径,它不是 M1C-2b 新增逻辑,也不在本包重构范围;因此本包不得表述为“项目写锁始终先于全部 Session lane / execution lock”。
- **预算事实**Provider 真实 future 的 completed / failed / interrupted 活跃区间以 requestId create-only fact 写入 Agent DB;同 ID 内容冲突失败关闭。下一次新的 planning request 在项目锁内、重建 request 前折叠合法 facts 到 `accumulatedAgentMillis`,因此冻结 binding 不会在 Provider 返回处漂移;项目锁、请求构造、用户/审批等待、retry backoff、handoff、工具执行与停机时间均不计入。fold 发现当前 run 仍有 ready / executing lifecycle 时返回 `Deferred`,不擅自改写 session。末次 `plan.submit_gdd` 的 usage fact 会被 v4 submit batch 暂时挡住;receipt 已完成 session 投影且精确消费 standalone/v4 anchors 后,同一项目锁内再 fold,确保直接 approve 而无下一次 planning request 时该区间也计入 session;任何 deferred/identity/I/O 异常只留 `recoveryPending`,不强写。
- **本轮已修的明确缺陷**:plan 回答读取曾把 opaque `taskId` 误与 Supervisor `agentId` 比较,会令第一轮 continuation 必然失败。现改为读取同一 parent run 的 Supervisor root task,并精确核对 taskId、agentId、sessionId、runId、source、requestId、questionsSha256 与 answersSha256;回答 sidecar 的共用 payload 校验保持完整,不降低普通 user-input 的身份校验。
- **终审修复**:完成至少一轮澄清后,审批 `revise/reject` 会保留 `appliedAnswers`,但新修订 delivery 的身份不再等于最后回答 continuation;旧纯 session 判据会把合法修订 successor 固定拒成 `PLAN_IDENTITY_CONFLICT`。现把独立 schema 校验收窄为“不得回退到已消费问题 delivery”,并在 session 新值、已有 primary/previous、发布后回读及普通读取边界读取真实 static-delivery 谱系:从 latest 回到最后回答 continuation 的**每一条边**都必须由父 delivery 的 `UserRevisionRequested` 状态授权,且 root/agent/session 身份一致、无 Unknown、缺节点或循环;质量返工边不得借路径中其它用户修订继续保留旧回答。正向回归同时覆盖 `revise/reject` 后 continuation、轮次/回答/决定保留与 Provider 注入;负向回归证明混入质量返工边时,即使重算合法 session fingerprint 仍失败关闭。
- **门禁中修复的测试缺陷**:并发整组首次复跑时,锁序测试把“回答线程开始”误当成“已得到调度”,180ms 内未观察到 project lock 竞争而失败;同用例精确复跑通过。测试只将调度观察窗口放宽到 2 秒,断言仍要求真实 project lock 竞争发生后才释放被占用的 execution lane,未改变生产锁序或放松结果判据。
- **纠正旧观察**:第四轮澄清委派在 `agent.delegate` 工具边界就按持久化血缘轮次上限硬拒,返回普通 failed observation,正常路径不会进入 reconciliation`planning_coordinator` 的 `clarification_round > 3` 分支仅是损坏/篡改血缘的纵深防御。Supervisor 可据失败 observation 改走 `plan.submit_gdd`,不需要在 M1C-2b 另加自动 submit 状态机。
- **最终门禁证据**`planning_clarification_*` **11 passed / 0 failed**(原 9 条之外新增真实 main-loop 释放 execution lane 后 parent-wake 回归,以及已回答后 `revise/reject` 修订回归);`tests::collaboration::static_deliveries::*` **44 passed**planning storage **13 passed**(含重新计算 fingerprint 的混合质量返工谱系负例);`planning_submit` **53 passed**`planning_provider_usage` **4 passed**;真实末次 submit usage receipt 回归 **1 passed**`barrier_detail_*` **3 passed**。`cargo fmt --check`、`cargo check --offline --all-targets --target-dir target-m1c2b`、`npm run check:encoding`7810 files)及整个工作树 `git diff --check` 均通过。**M1C-2b 本包实现及门禁已完成,并已快进合回 `feat/five_min_design`;审批 UI、hydrate、构建准入和下游完整构建仍后置。**
## 2026-08-17 立项策划决策卡改为 A/B 平行方案:`default_pending` 收回为未提问默认项唯一来源,改口不改合同,立包 `M1C-2c`
- **裁决**:决策卡三选项从「接受推荐 / 暂按推荐 / 需要原型验证」(同一条推荐的三种采纳程度)改为「方案 A(推荐)/ 方案 B(真实可行、形状不同的平行备选)/ 固定『需要原型验证』」,仍恒定三项,第 3 项的 description 须给出这一题可执行的验证方式。A、B 均记 `confirmed / user_option``需要原型验证` 仍记 `prototype_pending` 并要求同 ID 微型原型项;自由填写仍记 `confirmed / user_freeform`。`default_pending` 不再由任何选项产生,只表示**未提问、由子 Agent 按默认建议填写**的字段(`answerSource=default, round=0`),与 `plan.submit_gdd` 校验「前缀之后只允许追加 `default_pending` 默认决定」完全一致——三个决定状态各只有一个来源。状态映射按 label(A/B 前缀 + 第 3 项固定文案)而非位置,Runtime 校验信封形状;`answerSummary` 直接落所选 label,台账自描述。
- **依据**:以 DeepSeek v4-flash 做的本地原型多轮实测(原型不入库、只用于开发调试):选 1 与选 2 产出的 GDD 一字不差,差别只是一个不进任何机制的标签,却消耗一轮问询名额(上限 3);台账只落「接受推荐」三个字,用户下一轮改口推翻上一轮已确认决定时,Supervisor 转述与子 Agent 出稿只能靠改写文本消化。改为 A/B 后两轮冒烟:B 均为带独立代价说明的真实岔路;模型一次擅自把第 3 项换成自定义方案 C,被信封形状校验拒回并自行改正。(首版曾允许第 3 项按问题性质省略,产品拍板改回恒定三项。)
- **改口(已确认决定被后续自由填写推翻)**:M1 内不改合同——台账前缀不可变,两条 `confirmed` 并存;Supervisor 转述时必须在被推翻的那条后注明「已被第 N 轮回答推翻,以后者为准」且用户答案原文逐字保留(不得改写、拆分或搬轮次),子 Agent 按后者出稿并在新决定 topic 中写明推翻关系。实测三次改口 Supervisor 均能消化,但一次靠改写用户原文(转述保真审计报「内容缺失」),因此该规则必须写进 Supervisor prompt。`supersedes` 字段进 `plan-gdd.v1` 列为 M2 候选。
- **提问纪律补一条**:平台事实已定的事(含移动/桌面优先级)与 MVP 规则已排除的事(多人/联机/商城/服务器)不作为问题;A/B 格式会诱使模型问"天然二选一"但无价值的问题,实测一轮 3 张卡 2 张如此。
- **与 `M1C-2b` 的关系**:拟定本条时 `M1C-2b` 尚在隔离工作树,其合入门禁(3 轮上限、continuation 重放幂等、答案绑定冲突被拒)与选项文案无关,故按当时的 §5.2 映射表实现,并已于同日快进合回(见上一条)——它把「单题、`第N轮·关键决定`、固定三选项」的校验与「三个固定选项/自由填写 → state、answerSource、answerSummary」的确定性派生冻结在 planning coordinator 里。映射翻转、信封形状校验与 prompt 文案由新立的 **`M1C-2c`**(依赖 `M1C-2b`,现在即可开工)承接。**`M1D-1` 前端决策卡直接按新语义实现**(label 动态渲染、默认焦点 A、Other 槽不变),避免做两遍。§5.2 正文、§5.1 prompt 段与 §23.6「仍冻结:固定选项」一句由 `M1C-2c` 一并改写;本轮只在 §5.2 顶部加了指向注、新增 §23.9 与 §23.8 表 `M1C-2c` 行。
- **顺带核对项(归 `M1E`**`plan.submit_gdd` 连续校验失败必须有次数上限。本地实测无界时模型对大载荷序列化出错后连续 40 余次重试、每次重放全部历史、单次 prompt 涨到 15 万 token240/300 秒预算注入兜不住「硬超时后仍连续校验失败」。若 §12 retry 状态机没有该上限,补一个(原型取 5 次)。
- **不改的部分**`user.input_request` strict input 23 项区间、Other 槽 placeholder、`prototype_pending` 同 ID 验证项规则、第 12 节提交校验、§23.7 用户修订不计 `repair_depth``M1C-0`/`M1C-1` 已落地)均不变;生产代码本轮零改动。
- **关联**`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 5.2 节指向注、第 23.8 节 `M1C-2c` / `M1D-1` 行、第 23.9 节。
## 2026-08-15 M1C-2a 隔离工作树实现:固定 Goal Contract 与审批前置门
- **本轮范围**:只实现 Supervisor 根 run 的 Goal Contract / Acceptance Graph 与 Fast GDD 审批前置门;不接 `M1C-2b` 澄清中转、审批 UI、构建准入或下游完整构建。当前变更仍在隔离 worktree,尚未合回原分支。
- **固定合同**`project-supervisor-plan / standard` 的首轮与格式修复请求都必须且只能调用一次 `agent.goal_contract`。按项目变化的字段只有 `outcome / nonNegotiables / forbiddenAssumptions / openQuestions``preferences` 固定空数组,`acceptanceNodes` 固定为唯一 `fast-gdd-serves-intent` 节点,required evidence 固定 `tool:file.read`。其它 source 保持动态合同,即使使用同名 criterionId 也不套 Fast GDD 特例。
- **证据合同**Fast GDD evidence 只接受当前 Supervisor 根 run 自己读取 `game/fast_gdd.md` 的成功 action receipt。receipt 保存规范化路径、完整内容 SHA-256 与行覆盖摘要;多页必须从 `startLine=1` 无缺口、无重叠覆盖到 EOF,全部页同 hash/同总行数,`agent.acceptance_update.evidence` 必须列出所有分页 actionId。普通 Graph 读取只验 durable receipt 形状与完整覆盖;当前 Markdown hash 只在审批前且无 exact pending/receipt 时复核,审批后的状态投影不会让旧 Graph 损坏。
- **取证与返工三态**planning delivery 先由同一根 run 的 `agent.run_status` durable 认领。Graph 缺失、not-observed、project revision/hash 过期或证据不完整属于 `NeedsEvidence`,下一步是 `file.read`,不得提前返工;只有当前完整证据支撑的显式 failed 属 `RepairRequired`,才返回原 delivery 的 `repairOfDelegationId`passed 才幂等创建 `gdd-approval` pending。`run_status` 在持有项目锁时调用 locked gate,并在 Ready 数为零时仍重放,封住旧 action 已认领但 gate 尚未落盘的崩溃窗口;GDD create 到 child/delivery 完成前保持惰性,不抢断 M1B-2 恢复。
- **恢复与完成门**acceptance update、delivery claim、Runner recovery、completion 与 finalization 都消费同一 gate。恢复不重新执行 Provider 或 `file.read` 动作,只复核 durable Graph、动作回执与当前 Markdown hashmissing pending 按原 approvalRequestId/identity 补建,exact pending 与 receipt 优先于 Graph/hash 复核,冲突则失败关闭。当前根没有自己的 GDD 时,上一根遗留 Markdown/Graph 不能绕过完成门。
## 2026-08-15 M1C-1 隔离工作树收口:审批核心与专用完成门已落地,生产前置门保持后置
- **本轮落地**:在 `planning_storage.rs` 增加 `plan-gdd-approval.v1` receipt、`plan-gdd-approval-pending.v1` projection、comment/decision/receipt/pending 的 typed fingerprint 与 strict canonical 校验;审批 observation 固定校验 tool/status、版本摘要、detail 前缀和规范化 comment。`planning_approval.rs` 增加 receipt create-only、三动作幂等(`committed / replayed / already-decided`)、版本/指纹竞态防护、receipt 后 index/Markdown/audit/terminal observation/session 投影与恢复,以及只读的 plan 根专用 completion blocker`commands.rs` 暴露 `decide_game_creator_plan_gdd`。
- **恢复边界**generic `plan.submit_gdd` 的 v5 standalone pending 与 v4 batch 仍是独立恢复锚点。receipt 投影只在 exact planning-submit batchv4、单 action、cursor 已到 1、completed、observation 与 receipt 逐字相等)时清理残留;pending 已缺失但 terminal observation 存在时仍校验/清理该 batch,形状不 exact 则保持 `recoveryPending`,不猜测删除。无 receipt 的 GDD 不由本包自行重建审批 pending。
- **明确后置**:技术方案第 13.0 节要求的 acceptance-gate 取证成功后才创建生产 `gdd-approval` pending;当前创建 helper 仅由定向测试调用,真实 submit/recovery caller 留给 `M1C-2a` 的验收前置门接线。审批 UI、验收图接线和构建准入仍未完成,不能把当前隔离 WIP 宣称为完整产品交付。
- **验证**:专用 target `target-m1c1-current` 下 `cargo test --all-targets planning_submit --no-fail-fast`37 passed,含 completion blocker 正向/阻塞/作用域/identity 回归)、`cargo test --all-targets planning_storage --no-fail-fast`11 passed)、`cargo check --all-targets` 通过;`npm run check:encoding`7848 files)和 `git diff --check` 通过。编译仍有仓库既有 warnings,不作为本包缺陷。
- **关联**`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 13、14、23.6、23.8 节;本条只记录隔离工作树状态,合回原分支前仍需按既定合并流程复核。
## 2026-08-15 CI 五条失败归并为三个根因:修位置,不修症状
栈溢出修复后的全量跑出 5 条失败(`2051 passed / 5 failed`**零栈溢出**,栈修复站住)。逐条定位后归并为 3 个根因,**全部先于本轮工作**:与栈修复、`M1C-0b`、以及刚合入的 master 都无关,master`9f5c84ee7`)自身全绿,`deae1e08c`(栈修复前、`M1C-0b` 前)已全挂。三者形状相同——**新增的检查被放到了链路更靠前的位置,改变的是作用域而非严格程度**,详见 pitfalls 同日条。
- **归属**`tool_plan_handoff_rejects_out_of_order_entries` + 两条 `tests::goal``provider_action_batch_goal_resume_never_rewinds_newer_steer_cursor`、`agent_goal_paused_edit_replans_old_confirmation_in_same_run`)→ `M1B-2``27c3eb847`)的 `validate_next_entry` 判据上提;`response_stream::structured_plan_finalization_without_readable_runtime_state_needs_reconciliation` → 同一提交新增的 `missing_plan_submit_anchor_candidate_at` 强读;`immutable_writer_rejects_symlink_and_hardlink_targets` → `M1B-1``f453c2ca2`)起就没绿过的错误码分类,Windows 上编译都不参与故一直不可见。
- **证据不是推测**:失败用例遗留的临时项目目录里,`.agent/agent.db` 记着 `failureKind=tool-plan-integrity`、`errorChars=55`、`errorSha256=0750f609…`,与 `M1B-2` 新增那句「`tool-plan 成功响应交接 entry 的 Provider/session binding 链身份冲突`」的 SHA-256 与字符数逐位相同;`requestSlot` 由 `loop-1-repair-0` 走到 `loop-2-repair-0`,直接指认是跨 loop 那一跳被误判。
- **裁决一:撤回上提,而不是放宽判据本身。** `same_tool_plan_repair_chain` 含 steer cursor、goal revision/快照与 planning session binding 这些本轮量,进入新 loop 本就意味着它们前进;这条判据只在同 loop 的 repair 之间成立。**撤回不留缺口**:跨 loop 的 durable 身份由 `same_durable_tool_plan_run` 守,binding 漂移由 `provider_retry.rs` 的 `..._drift_fields`(含 `planningSessionBinding`)在**每个请求**层面守,`ledger.rs` 的 `is_later_repair_identity` 一直就把这条判据限定在同 loop——上提是模块内唯一的例外。**未采纳**「保留跨 loop 检查但只比 durable 子集」:`gdd_id` 在策划子 run 内会从 `None` 变 `Some`、`goal_id` 亦非绝对不变,凭空发明一条无测试支撑的新不变量,对一个管 Provider 计费与身份的安全屏障不划算。上提本身也**没有任何测试**(`M1B-2` 在该文件只机械补了一行 `planning_session_binding: None`)。
- **裁决二:探测器不得对自己的前置条件 fail-fast。** `missing_plan_submit_anchor_candidate_at` 是机会性修复,不是门。state 读不出来就不可能匹配它要找的形状,改为 `Ok(None)`;不可读 state 的处置权归下游 `resume_game_creator_agent_finalization_at`(从 task record 重建并 fail-closed 到 `needs-reconciliation`)。**不算掩盖**:紧随其后的那一步照样会读同一个文件并留下 `reason=runtime-state-missing` 的记录。**未采纳**「把探测器挪到 finalization 之后」——那会让 `Recovered`/`Blocked` 分支的 `continue` 直接跳过探测,改动的是语义而不是位置。
- **裁决三:链接一律按不可信路径分类,且模块内统一。** 新增 `resolve_planning_path`:先用模块自己的 `planning_metadata_is_link_or_reparse` 逐组件判链接/重解析点(比通用解析器的 `is_symlink()` 多覆盖 Windows reparse point),命中返回 `PLAN_UNTRUSTED_PATH`,其余仍交通用解析器并保持 `PLAN_INVALID_PATH`。模块内 13 处解析全部改走它。**未采纳**「改测试去迁就现状」——同模块 `ensure_planning_parent`、`verify_regular_planning_file` 都把链接判为 `PLAN_UNTRUSTED_PATH`,测试写的才是既定语义。已确认无生产代码或其它测试对这两个码做分支(全仓库仅这两处断言)。
- **回归钉边界,不只钉拒绝**:新增 `tool_plan_handoff_accepts_new_loop_after_steer_and_goal_revision_advance`——跨 loop 且 steer cursor / goal revision 已前进必须被**接受**。原有用例只钉「什么该拒」,所以判据作用域被放大时无人报警。
- **验证**4 条可在 Windows 复现的用例全绿(含新增回归);Linux-only 那条在 WSL 上跑通。同轮曾出现 3 条 `response_stream` 超时失败,空载单独复跑 3 passed / 0 failed,且三者走 `final-reply` 路径、不经过 `validate_next_entry`,与本次改动无因果——判为负载抖动。
- **未做**:不追查这 5 条各自的完整历史绿/红轨迹(引入点已锁定到单个提交,继续二分无增量);不动 `M1B-2` 的功能面。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/tool_plan_handoff/{identity_order_validation.rs,tests.rs}`、`.../agent/runtime_driver/recovery_scan.rs`、`.../agent/runtime_protocol/planning_storage.rs`pitfalls.md 同日「把校验往链路前面挪」条。
## 2026-08-15 恢复重启栈溢出:主循环专用 worker 从「枚举入口」改为不变量
CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 `tokio-rt-worker` 栈溢出并 SIGABRT。属于本文件 pitfalls「Runtime 后台执行不能让大型 async frame 共用默认 worker 栈」的同一失败类,但暴露出该条目的判据形式本身有缺陷。**Windows 上可直接复现,不必去 Linux/WSL**。
- **是本分支引进的,但根因在 master。** 同机同用例、默认栈 A/Bmaster`9f5c84ee7`)通过,HEAD`deae1e08c`)溢出。二分 `RUST_MIN_STACK` 量化:started 变体 master 需 15361792 KiB、HEAD 需 20482176 KiB(默认 2048**超出不到 128 KiB**);队列 drain`drain_next_*`master 需 12801536 KiB、HEAD 需 17921856 KiB。`M1B-2` 往主循环与恢复扫描加分支消耗 256–576 KiB,而 master 本就只剩 256512 KiB 余量——**边界一直是缺的,只是以前刚好没超**。
- **不是递归**16 MiB 下 2.14s 通过,逐级下探到 2176 KiB 仍通过,符合 pitfalls 记的「大型 async poll frame」而非业务递归。
- **根因:恢复重启是第四个入口,从未被纳入专用 worker。** `recovery_scan.rs` 手写 `tauri::async_runtime::spawn` 直接跑 `drain_game_creator_agent_background_tasks`——正是仓库明确规定必须上 16 MiB 的那个 future。pitfalls 原文按入口枚举三个(普通后台任务、静态委派子任务、manifest ready-task 首次执行),**漏掉一个入口不会产生任何信号**,故判据改写为不变量:所有会进入 Agent 主循环的 future 必须在 `agent-runtime-worker-*` 专用线程上轮询。
- **一并收拢队列 drain。** `spawn_next_..._with_lock` 与 started 变体只差一个 poll 帧(实测 256–384 KiB),却长期分两种栈待遇。HEAD 上它只剩 192–256 KiB 余量,**小于本分支单个工作包的消耗量**,即下一个同量级工作包必然顶穿;且它有 8 个生产调用点,崩在哪条取决于当时路径,比恢复路径更难定位。
- **处置**:统一常量 `AGENT_RUNTIME_BACKGROUND_WORKER_STACK_BYTES``spawn_next_..._with_lock` 改用专用线程,**签名保持 `-> ()`、8 个调用点不动**——该入口是 best-effort 幂等语义(拿不到锁即返回、后续 wake 重试),建线程失败只需记录并随闭包释放锁,无需像 started 入口那样交还锁、也就不需要握手;`recovery_scan.rs` 两处手写 spawn 收敛为 helper 调用。恢复重启改走 `spawn_started_..._with_lock` 顺带补上首轮轮询握手——原写法把执行锁 move 进一个无人保证会被轮询的 future,运行时关停时 run 会永远停在 running 且无主。
- **回归改为钉不变量,不钉余量。** drain 入口在 `#[cfg(test)]` 下记录 `std::thread::current().name()`,用例断言其全部以 `agent-runtime-worker-` 开头。**变异验证**:把 `recovery_scan.rs` 改回手写 spawn 并把 `RUST_MIN_STACK` 抬到 16 MiB(因而不会溢出),用例仍以 `["tokio-rt-worker", "tokio-rt-worker"]` 失败——证明该断言独立于栈余量。只断言「默认栈下没崩」的用例在这次崩溃前全部是绿的。
- **验收标准也随之改变**:不是「默认栈下通过」,而是「主循环栈依赖消失」。修复后两条用例在 `RUST_MIN_STACK=1024 KiB`(半个默认栈)下通过;修复前分别需要 2048+ 与 1792+。
- **未做**:不抬 `RUST_MIN_STACK`pitfalls 明令禁止,CI 有意不配置该变量)、不调大 tauri 全局运行时 worker 栈、不去削 `M1B-2` 的帧——余量不是修复。
- **需回流 master**master 同样存在「恢复重启走默认栈」与「`drain_next_*` 余量偏低」,只是尚未触发;与 `manifest.rs` 的 Windows 构建修复同理。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/{task_queue.rs,recovery_scan.rs}`pitfalls.md 同名条目已按不变量重写。
## 2026-08-15 M1C-1 开工前三条裁决:barrier 独立计数、正向校验的上限、前向兼容粒度另拆 `M1C-0b`
对本文件 2026-08-14「`M1C-0` 合入复核」留下的三条前置逐条裁决,全部读实代码后定稿。**其中第三条订正了 08-14 自己的表述**:原文写的「二选一:给枚举加单条容错,或接受回滚锁死」是伪二选一——「单条跳过并告警」这个选项对 delivery 不安全,已作废。
- **裁决一:补 barrier,且必须是独立的第六个计数 `user_revision_pending_count`。** 不补的后果是 `M1C-1` 写入方一落地,Supervisor 就能在用户修订尚未派出时收束用户任务(收束门 `static_delegate_completion_blocker_at_locked`)。不复用现有两个计数的理由是硬的:`repair_required_count` 带 `repair_of_delegation_id.is_none()`,只算原始委派——因 depth≤1,返工的返工本就不该阻塞;而**用户第 2 次修订的父节点自身就是 repair 节点**,并进去会让第 2 次及以后的修订全部不阻塞,恰好在最需要处失效。`user_input_required_count` 的语义是「等用户回答」,与「用户已发话、等 Supervisor 派发」不同,`detail()` 文案会说反。判据形状照 `user_input_required_count``ClaimedByParent` + 该 status + 无活跃 child**不带** `repair_of.is_none()`)——它正是仓库里「轮次无上限」概念的既有先例。**连带**:除 `is_clear()` 与 `detail()` 外,另有四处调用点逐字段读 barrier 而不走 `is_clear()``autonomous_policy.rs` 两处、`runtime_tools/delivery.rs` 两处),加字段不会自动传播,每处单独裁决。这是 `M1C-0` 那个失败模式的同构版本,载体从 enum 变体换成 struct 字段,编译器同样沉默。
- **裁决二:正向一致性锁死在「只能从 `EvidenceReady` 改写而来」,不得更严。** 复用 `EvidenceReady` 的三条客观证据约束(终态 `completed`、无缺失产物、`verification_required` 时 `verified_revision` 存在);产物覆盖校验与「不得携带 `user_input_questions`」已对所有状态生效,不需改。**重点是实现形状**:把 `validate_static_delegate_structured_result` 里按 `contract_status` 的 if/else-if 链改成穷尽 `match`、不留 `_`——同一失败形状在本仓库已出现两次,靠纪律没拦住。**反向风险**:该函数不是入口过滤器,`read_static_delegate_delivery_at` → `validate_static_delegate_delivery_record` 让它每次读取都跑;过严等于把写入方的一个 bug 变成「该 delivery 永久读不出来」,直接触发裁决三的锁死,故不得再加 `EvidenceReady` 自己都没有的条款(例如要求 `error` 为 `None`)。**另订正 08-14 前置二可能引起的误读**:它不是既有漏洞——专业 Agent 自证「用户要求修订」今天不可达,唯一派生点 `build_static_delegate_structured_result_at` 只能产出三个旧变体,claim 回执的 `structuredResult` 从 delivery 拷贝,均为 Runtime 侧;本条约束的是 `M1C-1` 引入的**第二个写入方**(审批命令)。
- **裁决三:「单条跳过并告警」作废,走第三条路,并另开 `M1C-0b`。** 单条跳过不安全的原因很具体:`list_static_delegate_deliveries_at` 喂的是 barrier 的**计数**,跳过一条损坏的 `Dispatched` 记录会让 `waiting_count` 少 1、barrier 变 clearSupervisor 在仍有未完成委派时收束——把可用性故障换成了正确性故障,比锁死更糟。对照组说明危险面很精确:谱系重放不受影响,缺节点走「上游缺失」出口返回 `(u32::MAX, u32::MAX)` 哨兵,本就 fail closed。**第三条路是把「解析失败」拆两类**:损坏(截断/非 UTF-8/超限)状态未知,维持整体锁死不动;前向不兼容(结构良好、只有一个 enum 值不认识)是已知的未知,解析进显式 `Unknown` 但最大化阻塞(进 barrier、返工门无条件拒、谱系按最保守的「其它」分类,只会拒不会放)。不违反 `M1C-0` 条里「不得静默降级为 `NeedsRepair`」——那条禁的是把记录当正常记录继续走流程,`Unknown` 是显式隔离。半径从「整个项目静态委派面不可用(做游戏链路一起挂)」降到「只冻结携带该状态的那条 lineage」。
- **两条比 08-14 记录更糟的事实。** 锁死半径是**整个项目目录**——`list_static_delegate_deliveries_at(root)` 先读全目录再按 parent run 过滤,任何一条无关 run 的损坏记录毒化所有 run 的 barrier`.json.previous` 备份救不了——`read_agent_runtime_json_sidecar_with_max_bytes` 只在 primary **NotFound** 时才回退,损坏但存在的 primary 不回退。
- **实现坑。** `#[serde(other)]` 用不了:它只允许在 internally/adjacently tagged 枚举上,而 `StaticDelegateContractStatus` 是序列化成纯字符串的 unit-variant 枚举;需自定义 `Deserialize`,且 `Serialize` 必须原样回写原始字符串,否则旧版本任何一次读-改-写都会把未知值抹掉。
- **拆包与门禁。** ①② 进 `M1C-1`(它们约束的正是 `M1C-1` 新增的写入方);③ 拆 `M1C-0b`,与 `M1C-0` 同形状——无写入方、纯读路径、对既有记录零行为变化可证;塞进审批闭环的 diff 就是重犯第 23.8 节「拆包纪律」自己写的错。`M1C-1` 门禁因此为二选一:`M1C-0b` 先落,或直接上线并在 release note 明写「用过策划审批后回滚旧版本会让该项目静态委派面不可用」;**建议前者**,多机/版本不一致不需要用户主动回滚就会发生。`WP1` 的 depth≤1 结论仍未被推翻。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 23.7 节「落地约束」裁决一/二/三与「回归必须覆盖」、第 23.8 节 `M1C-0b` / `M1C-1` 行与「拆包纪律」。
## 2026-08-15 M1C-0b:静态委派 durable status 前向兼容实现完成
- **落地**`StaticDelegateContractStatus` 采用手写 serde。四个已知 durable 值保持原有字符串;结构良好的未知字符串解析为 `Unknown(raw)`,并在再次序列化及读-改-写时原样保留 raw;非字符串输入仍拒绝。该包只改读路径,不新增状态写入方。
- **门禁**`Unknown` 进入 completion barrier 的独立计数与 waiting blocker,不能让 Supervisor 在未知状态未处理时收束;返工入口遇到 `Unknown` 无条件拒绝,即使 lineage depth 为 0lineage 将其按“其它”质量返工分支保守计数(`depth + 1`、`round = 0`),只会拒绝、不会放宽额度。
- **损坏边界**:截断、非法 JSON、非 UTF-8 或超过 128 KiB 的 sidecar 仍维持整目录 fail closed,不改成单条跳过或告警,以免 barrier 少计数而错误放行。
- **范围与依赖**:不包含 `M1C-1` 的审批写入、`gdd-approval` pending、receipt、UI 或构建准入;不 bump durable schema 版本。为覆盖所有既有读路径,补了 planning Provider、自治 liveness 与终态扫描的 fail-closed 消费门,但没有新增或改变 `M1B-*` 功能依赖;该包仍可在 `M1C-0` 基础上独立验证。既有三种状态及历史记录行为保持不变。
- **结论**`WP1` 的 `repair_depth≤1` 结论未被推翻;Unknown 只增加前向不兼容时的保守阻塞,不会放宽普通做游戏链路的返工深度门。
- **验收门禁与关联**:定向回归须覆盖未知 raw round-trip、barrier/waiting、无条件返工拒绝、保守 lineage 分类,以及损坏 sidecar 整体锁死;详见技术方案第 23.7 节裁决三与第 23.8 节 `M1C-0b` 门禁。
## 2026-08-15 完美像素编码前按整数倍 nearest 放大到接近源图
- 背景:2026-08-10 起成功产物直接落逻辑网格 PNG,画布按资源实际宽高显示,结果会明显小于源图。用户要求保持逻辑图宽高比,并把产物放大到接近原图;禁止再走非整数 nearest 拉回精确源尺寸(会让逻辑块宽窄不一)。
- 决策:`style="pixelArt"` 与手动 `POST /api/editor/images/pixel-art-snaps` 仍共用 `snap_pixel_art_with_grid_policy`。检测、切线、采样、Alpha、strict 拒兜底不变。`resample` 之后、`encode_png` 之前,用单一整数 N 做 nearest 放大:`N*` 为 `(C·W + R·H) / (C² + R²)`,在 `floor` / `ceil`(小于 1 当 1)中取距离平方更小者,并列取较小 N;超单边 `10000` 或总像素 `8294400` 则降 N,最低 `N=1`。只持久化这一张 PNG。手动算法指纹升为 `perfect-pixel-v3`。
- 不做:改 walker、透明补边、裁切、横纵不同倍率、Lanczos / bilinear、另存逻辑图、前端框缩放、失败路径、新测试。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/【编辑器】图片画布结构化持久化与迁移回滚方案-2026-07-19.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
- 背景:真实 `gpt-5.6-sol / max` 验收中,`art-director` 失败后已形成 `ready + needs-repair` delivery,但认领、合同读取、claim observation 和完成 blocker 均硬编码为 Supervisor-only;实际直属父 Run `code-prototype` 无法消费回执,随后又发起 29 次 Provider 请求。
- 验证方式:覆盖合法认领与合同精确读取、错误 Agent/Run/delegation 拒绝、delivery 身份篡改阻断、唯一安全默认返工、普通失败零后续 Provider lifecycle,以及新的真实 Provider 空项目轮次。顺带收紧 `validate_executable_inline_javascript_syntax`:正文游离 `<` 不再吞掉后续 `<script>`,未闭合或非标签状 `<` 继续扫描,避免后置脚本被静默跳过语法校验。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-14 AGC 将 reasoningEffort=max 作为独立强度贯通
- 背景:`gpt-5.6-sol / max` 真实验收预检发现,AGC 与 `platform-llm` 只接受到 `high`;同时 canonical Agent 会先用角色默认强度覆盖全局值,空 `agentLlm` 不能证明实际请求使用 `max`。
- 决策:新增独立 `max` 枚举和 wire 值,贯通配置、Provider 适配、Codex 映射、请求指纹、前端类型与配置检查;禁止把 `max` 静默映射成 `high` 或 `x-high`。本轮真实验收使用 `agentMode=provider`,并为 Supervisor、主代码 Agent 和两个条件美术 Agent 显式设置 `max` 覆盖。
- 验证方式:定向验证配置解析、Responses 请求 JSON、Provider 双向适配和 Codex effort;真实 E2E 报告必须同时绑定 `providerModel=gpt-5.6-sol`、`providerReasoningEffort=max`、`providerApiKind=openai_responses` 与 endpoint SHA-256。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-14 首份结构化计划完整替换 legacy 计划脚手架
- 决策:`planRevision=0 -> 1` 是 legacy 到 structured 的一次性迁移边界。第一次有效结构化更新完整替换 legacy `plan / planSteps / activePlanStepIndex`,不继承任何 legacy `completed / failed` 脚手架步骤;只有函数入口处已经存在结构化计划时,才合并并保护历史终态。
- 安全边界:已建立结构化计划后的 `completed / failed` 单调与不可改写语义保持不变,普通失败继续 fail-closed;不新增 smoke receipt 特判恢复通道,不放宽项目 revision、static smoke、desktop/mobile 试玩、身份绑定或最终完成门。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-14 M1C-0 合入复核:三条 M1C-1 前置、一条文档订正、一条已排除假设
合入 `M1C-0`(见本文件同日条)后对该包做整组复核。**结论:包本身可合,「今天行为零变化」成立且可证**;但新变体在 `M1C-1` 写入之日会同时点亮三个默认值,而这三个默认值都不是裁决出来的,是「给用 `==` 比较(而非穷尽 `match`)的 durable enum 加变体,编译器不报,新变体静默落到作者没想过的那一侧」的结果——与本文件「修复 M1A-2 引入的回归」条同源,是同一失败模式的第二次出现。(复核完成后 `M1B-2` 已合入;其对 `delegation.rs` 的改动是 rustfmt 换行、无语义变化,本条结论不受影响。)
- **不可达性已实证,不是靠注释。** `contract_status` 的生产赋值点只有两处(终态回执构造与 `build_static_delegate_structured_result_at`),都从客观事实派生(终态、缺失产物、verification、问题数),模型选不了;`StaticDelegateClaimRecord` 全仓库**唯一**构造点在 `delegation.rs` 的 claim 准备路径,是运行时构造而非 agent 提交的 JSON,所以「claim payload 自带 `contractStatus`」这条路不存在;durable sidecar 位于 `.agent/runtime/delegation-deliveries/`,被 `is_agent_runtime_private_control_path` 覆盖,`file_ops.rs` 与 `project/filesystem.rs` 各三处拒绝写入;前端无 `contractStatus` 消费者。哨兵侧同样成立:`static_delegate_lineage_counters` 的三个 fail-closed 出口(成环、超长、上游缺失)**全部**返回 `(u32::MAX, u32::MAX)`,两个分量都是 MAX,新分支的 `== u32::MAX` 检查全接得住;合法累加上限 31,不会误触。
- **前置一:新变体不进任何 barrier。** `repair_required_count` 的判据是 `== NeedsRepair || (== NeedsUserInput && 有澄清答案)``user_input_required_count` 要求 `== NeedsUserInput``UserRevisionRequested` 两边都不落,因此 `barrier.is_clear()` 可能为真——`M1C-1` 写入方一落地就意味着 **Supervisor run 可以在用户修订尚未派出时完成**。同源的还有 `swarm_cli/terminal_classification.rs` 的 `static_delegate_delivery_has_repairable_contract``is_none_or(|r| == NeedsRepair)` → 新变体判为不可返工,影响面小但同样是默认选的)。`M1C-1` 须显式裁决补或不补。
- **前置二:`validate_static_delegate_structured_result` 缺正向一致性分支。** `EvidenceReady` 有(终态/产物/verification 对账),`NeedsUserInput` 有(问题数 + 指纹),`UserRevisionRequested` 只继承了「不得携带 `user_input_questions`」这条否定约束,于是 `UserRevisionRequested + terminal_status="failed" + 缺产物` 能通过校验落盘。`M1C-0` 新增的 real-gate 回归恰好依赖这一点才能构造 fixture。
- **前置三:前向兼容失败的粒度是整个子系统,不是单条记录。** `list_static_delegate_deliveries_at` 对每条 sidecar 用 `?` 上抛,一条解析失败即整个目录枚举失败,barrier、lineage、返工校验、run status 投影同时不可用。`M1C-0` 当时有意不 bump `STATIC_DELEGATE_DELIVERY_SCHEMA_VERSION`,并在自身范围内把「未知状态 serde 直接报错」记为安全属性;**该前向兼容语义现由 `M1C-0b` 改为显式 `Unknown`,损坏输入仍整体锁死**。注意 bump 版本号救不了损坏输入:版本号校验排在 serde parse 之后。三条均已写进技术方案第 23.7 节「落地约束」与第 23.8 节 `M1C-1` 行。
- **文档订正:`STATIC_DELEGATE_LINEAGE_MAX_HOPS = 32` 限的是链上节点数,不是跳数。** 判据 `chain.len() >= MAX_HOPS` 排在入链之前,故链最多 32 个节点 = **31 跳**。第 23.7 节原写「真实上限就是 32 跳」,已订正为 31。产品阈值 16 远低于两者,不受影响;`M1C-0` 自己的测试注释(「32 条 delivery(31 跳)」)一直是对的。之所以要订正:`M1C-0` 之前 `depth=1` 才是主刹车,用户修订路径上现在只剩这个哨兵,写错的数字从此是承重的。
- **已排除的假设,后续不要重走。** 怀疑过「派一个子委派 → 抑制 → 再派一个」可绕开兄弟检查、在用户修订父节点下无限扩宽度。打不通:`suppress_static_delegate_delivery_at` 对 `ClaimedByParent` 直接原样返回、拒绝抑制,且生产侧唯一调用者是 `suppress_static_delegate_deliveries_for_parent_terminal_at`(父 run 终态清理),彼时同一 `parent_run_id` 已不能再派委派。另确认新分支**没有**跳过兄弟检查:if/else-if 链在前、兄弟检查在后且无提前返回,扇出仍是一条。
- **测试侧本次一并处置。** ① `static_delegate_user_revision_preserves_existing_clarification_round` 名不副实:计数循环只遍历 `chain[..len-1]`(父节点集合),目标自身 status 从不参与判定,而该用例把 `UserRevisionRequested` 放在了**目标**位置,新分支根本没被执行。已改名为 `static_delegate_user_revision_parent_hop_preserves_depth_and_clarification_round`,补一跳真正以用户修订为父的续跑,并加反证(同一条链只把该父节点改回 `NeedsRepair`,结果必须变成 `(2, 0)`);原断言保留为对照组并注明其性质。② `user_revision_continuation_passes_real_gate_at_depth_one_but_stays_single_child`(原 `static_delegate_user_revision_requested_continuation_...`)补兄弟检查断言。③ 新增 `concurrent_user_revision_dispatch_creates_exactly_one_delivery`,与既有 `project_supervisor_concurrent_repair_dispatch_creates_exactly_one_delivery` 同构但父节点为 `UserRevisionRequested` 且链上 depth 已为 1,两侧同时钉住:并发下恰好放行一条,且不是零条(新分支被删则两条都会被 depth 门拒,用例变红)。
- 教训(与 `M1A-2` 回归条合看):**给用 `==`/`!=` 比较的 durable enum 加变体,等于在每一个比较点上替作者做了一次没人复核的裁决。** 这类 PR 即使「无写入方、行为零变化」也必须逐个枚举比较点并写下每处落点,否则这些默认值会在写入方落地的那个 PR 里一次性生效,而那个 PR 的复核者只会看它自己的 diff。
## 2026-08-14 M1B-2 实现合同收口:Provider binding、结构化注入与提交恢复边界
- **状态与基线**:`M1B-1` 已通过门禁并合入,作为 `.agent/planning/` storage 基线;`M1B-2` 工作包已通过本包门禁并以 `27c3eb847` 合入本分支。当前包接 `plan.submit_gdd`、exact planning Provider 请求身份、提交点和恢复;`gdd-approval` planning pending、审批等待、receipt、审批命令与 UI 继续属于 `M1C-1` 及之后。本状态只表示 M1B-2 工作包完成,不表示完整产品可交付。
- **binding 是有自指纹的 exact v1,不是可扩展 map**`plan-provider-session-binding.v1` 在 `runId` 后固定包含 `rootAgentId`,并在 `requestContextFingerprint` 后以 required typed `fingerprint` 收尾;typed value 排除自身 fingerprint 但覆盖 `rootAgentId`base Provider request ID value 同样在 `runId` 后覆盖 `rootAgentId`。`rootAgentId` 与末尾 fingerprint 是本次实现对委派根身份和 binding 自完整性的有意加固,不再称“额外字段”;缺失、重排、未知字段、重算不等或从当前状态补默认值都失败关闭。无 Goal 的唯一合法三元组是 `goalId=null / goalRevision=0 / goalSnapshotFingerprint=""`Agent DB validator 不能先用通用非空 identity 门把这个合法空 fingerprint 拒掉。
- **四类请求共用同一 captured context**exact planning 的 `tool-plan | final-reply | context-compaction | final-reply-context-compaction` 全部写 `game-creator-provider-request-lifecycle.v3`、同一 required binding 和同一 structured injection;只有 `tool-plan` 能产生 action、`game-creator-provider-action-batch.v4` 与 `plan.submit_gdd`,其余三类无 action batch。planning idle context compaction 因没有 active run/session captured context 而明确不支持、fail-closed。request-context 的 `composition` 与 `sourceKind` 都固定为现役 Prompt Bundle 值 `runtime`,不得把 durable source `agent-delegate` 复制进 `sourceKind`MCP 固定为空且 planning builder 不读取项目 MCP catalog,避免“先读后清空”制造额外依赖或漂移。
- **structured injection 的 wire 冻结**`plan-provider-structured-injections.v1` 顶层顺序为 `schemaVersion, clarificationRound, accumulatedAgentMillis, session, platformFacts, approvalObservation`session 顺序为 `phase, decisionsSummary, prototypeValidationItems, latestSubmittedRef, lastDecisionRef`;平台事实复用固定 `PlanPlatformFacts`approval observation 只能为 `null` 或现役 `tool, status, summary, detail` 四字段 strict 对象。canonical compact JSON 最大 64 KiB,以 dedicated user message 真正进入最终 `LlmRunRequest`:第一行 `AGC_PLAN_PROVIDER_STRUCTURED_INJECTIONS_V1`,第二行 JSON,无第三行与尾换行。同一第二行 JSON bytes 同时生成 `structuredInjections.wireBytes/wireSha256`,禁止重建另一份“语义相同”对象再摘要。
- **submit 的包内 policy 与 session 真相**`plan.submit_gdd` 配为 `confirm` 时按 deny 失败关闭,不创建 M1B-2 无法消费的 generic confirmation;显式 deny 同样拒绝。输入的 decisions 先逐项严格匹配 source session 的完整前缀,之后只允许追加 `default_pending/default/round=0` 的默认决定,不能把未提问项伪造成用户已确认。
- **提交点不等于审批等待**M1B-2 在 create-only GDD 提交点之后只重建 index、`game/fast_gdd.md` 与 session successor,再终止策划子 run/delivery;不创建 planning pending 或 waiting 投影。child terminal ensure 按原 action identity 可重入,task/event/delivery/Agent DB audit 各自幂等;delivery 必须发布后 exact 回读,未 durable 前不能先写 `recoveryPending=false` 的 committed audit。原 submit 在提交前已经建立的 generic `game-creator-pending-action.v5` standalone pending 与 v4 action batch 继续保留,供 M1C-1 receipt/terminal observation 按同 action identity 消费。standalone pending 保存 `providerBatchPlanUpdate`,使 pending-only 能精确重建完整 plan/planUpdate/batchIdbatch-only 则补回同 identity pending。两枚 anchor 都在时严格对账;恰缺一枚且另一枚与 immutable GDD/frozen binding 严格匹配时确定性重建缺失投影;两枚都缺失、任一损坏或 identity 漂移时进入 reconciliation,不能只凭 GDD 猜完整 action/batch wire。双缺扫描覆盖 GDD commit 后、child finish 前且 Runtime `pendingToolAction=null` 的真实窗口,重复扫描不重复写 audit;已提交事实优先于其后的 repository/steer 漂移,同 action 只收口原投影、不生成新版本。
- **本包门禁结果**:四类 request 的 binding/lifecycle/wire、structured DTO 字段与 64 KiB 边界、`composition/sourceKind=runtime`、idle compaction 拒绝、submit 业务/身份拒绝、提交点前后恢复、同 submission replay 不增版本、index/Markdown/session/child terminal 断点,以及 generic anchors 双在/单缺/双缺/漂移矩阵均已有定向证据;范围匹配的 Rust 门禁、`cargo check --offline`、`cargo fmt --check`、`npm run check:encoding` 与 `git diff --check` 已通过。本包已合入本分支;审批 pending/receipt/UI/构建准入仍不在本包。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 3、8.1、9、12、21、23.6、23.8 节。
## 2026-08-14 M1C-0:用户修订状态进入静态委派 lineage 分类
- 落地:`StaticDelegateContractStatus` 新增 `UserRevisionRequested`serde durable 值为 `user-revision-requested`)。`static_delegate_lineage_counters` 现在按三类传播:`NeedsUserInput` 只增加 `clarification_round``UserRevisionRequested` 原样继承 `repair_depth` 与 `clarification_round`;其它状态继续按质量返工增加 `repair_depth` 并重置 `clarification_round`。
- 门禁:父 delivery 为 `UserRevisionRequested` 时,后续同链续跑不再被 `repair_depth=1` 的质量返工门误拒,因此连续用户修订可以继续通过;链上 `(u32::MAX, u32::MAX)` fail-closed 哨兵仍拒绝,不得借用户修订分支绕过 32-hop、成环或缺失上游保护。普通做游戏链路没有该状态,`repair_depth≤1` 结论未被推翻。
- 范围:本包没有任何审批状态写入方、`gdd-approval` pending、receipt 或前端状态;`UserRevisionRequested` 仍由后续 `M1C-1` 的审批命令与 receipt 同步写入。本包不 bump `STATIC_DELEGATE_DELIVERY_SCHEMA_VERSION`,也不处理未知 durable status;禁止将未知值静默降级为 `NeedsRepair`,其显式 `Unknown` 前向兼容由后续 `M1C-0b` 收口。
- 回归:新增连续用户修订、保留已有澄清轮次、普通 depth=1 拒绝、32-hop fail-closed 与 `user-revision-requested` serde round-trip;未知 durable variant 的显式 `Unknown` 前向兼容与损坏输入锁死由 `M1C-0b` 单独覆盖,无新状态的历史记录继续按原质量返工分类。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 23.7、23.8 节;后续审批写入依赖 `M1B-2`、`M1C-1`。
- 合入说明:本包在隔离 worktree 上以 `09c7d7af8``M1A-2` 收口)为基线开发,未包含 `M1A-4`、M1A 残余收口、`M1A-2` 回归修复与 `M1B-1`。合回时代码零冲突(本包改的是顶层 `src-tauri/src/delegation.rs``M1A-4` 改的是 `src-tauri/src/agent/runtime_tools/delegation.rs`,同名不同文件),仅两份文档的状态句冲突:合并时以原分支为准保留 `M1A-4` / `M1B-1` 的已落地事实,删去本包基线上「`.agent/planning` 存储仍未实现」「`M1B-1` 及之后仍未开始」两句已被 `M1B-1` 推翻的表述。
## 2026-08-14 修复 M1A-2 引入的回归:未知工具名不是身份违规
- 症状:`background_agent_runtime_persists_receipts_for_rejected_actions` 在 feature 分支恒失败(3/3),`master` 通过。首个 tool-plan 请求能收到,动作被拒绝后**第二次 Provider follow-up 请求不再发出**,测试等待超时。**不是已知的 mock-LLM 本机 flake**,是确定性回归。
- 根因:`M1A-2` 给主循环加身份门时用了 `agent_runtime_tool_allowed_for_agent`,但该函数对**非** `project-planning` 的 Agent 退化成「这个工具名是否已知」(`game_creator_agent_runtime_tool_command_id(..).is_some()`)。于是普通 Agent 调用一个不存在的工具(模型编名字,属常见协议错误)被判成身份违规,`main_loop` 直接 `mark_needs_reconciliation` 并 `return NeedsReconciliation`,整个 run 中断。**现役语义是:未知工具走到执行层产出一条 `rejected` observationrun 继续,由下一轮 tool-plan 收束。**
- 两类必须分开:**「身份禁止某个已知工具」**planning 的 exact allowlist)是安全边界,命中即硬拒;**「工具名根本不存在」**是可恢复的协议错误,不得升级成中断整个 run。混用会把模型一次笔误变成需要人工核对的终态。
- 处置:新增 `agent_runtime_tool_rejected_by_agent_identity`,只在「该 Agent 带 exact allowlist 且工具不在其中」时为真。三个「命中即中断 run 或整体拒绝动作」的调用点改用它——`main_loop.rs`(原为硬中断,本次回归的直接原因)、`provider_action_batch.rs` 的 `identity_block`(原会把普通 Agent 的未知工具从 `rejected` 误判成 `Denied`/`blocked`)、`runtime_tools/policy.rs`(原本就正确限定了 planning,改为复用同一判据以免再次分叉)。`parallel_ledger.rs` 内部的批次资格判定不变——它下一行的 `command_id` 检查本就会拦住未知工具,无过度拒绝。
- planning 侧约束未放松:planning + 未知工具、planning + 越权工具仍判身份拒绝,回归 `planning_identity_still_rejects_unknown_and_out_of_scope_tools` 钉死;`unknown_tool_on_ordinary_agent_is_not_an_identity_rejection` 钉死普通 Agent 侧。
- 教训:给已有主循环插「命中即中断」的门时,判据必须是**专门表达该门语义**的谓词。复用一个名字听起来正确、但对多数输入退化成别的含义的通用函数,会在没人测到的分支上改变现役语义。
## 2026-08-14 M1A 复核残余收口:retry 强判据前置、身份哨兵改为编译期约束
对 `M1A-1``M1A-4` 做整组复核后,除已由 `M1A-4` 解决的一项外,另有两处代码残余与三处文档残余。本条记录代码两处的处置,文档三处随本次提交一并订正。
- **retry 强判据必须排在全部分支之前。** `resolve_game_creator_agent_runtime_retry_configuration_at` 原先把 plan 分支放在 `delegated` 与 `autonomous-game-build` 之后,两支都能绕开 `reject_supervisor_plan_root_retry_without_identity`:① 「plan source + 伪造 parent」落 `delegated` 支,直接返回 `agent-delegate-retry`,强判据根本不执行;② 「`binding.source` 是 plan + autonomous profile」落 autonomous 支,因 plan 在可信集合内而被原样取回,**复活启动路径 `reject_supervisor_plan_autonomous_profile` 明令禁止的组合**。两者都要 durable 状态先畸变才可达,但强判据存在的意义正是对畸变状态 fail closed。处置:把守卫提到函数开头无条件执行(合法 plan 根 run 对它恒真),plan 分支内不再重复读 durable 状态;autonomous 支内另加 `reject_supervisor_plan_autonomous_profile(&binding.source, &run_profile)`,兜住「`task.source` 已损坏但 `binding.source` 是 plan」这一种顶部守卫按 `task.source` 判定所挡不住的情形。回归 `plan_root_retry_identity_guard_precedes_delegated_and_autonomous_branches`,已用变异测试确认去掉任一守卫即变红。
- **`"__all_agents__"` 身份哨兵改为编译期约束。** 四个不带 `agentId` 的 wrapper`build_agent_runtime_native_function_tools`、`parse_agent_runtime_native_tool_calls`、`parse_game_creator_agent_tool_plan_llm_response` 及其 `_with_catalog` / `_with_catalog_classified` 两层)会以哨兵跳过按身份的工具面收窄与原始工具 identity 复核。`M1A-2` 之后它们的全部调用点都只剩测试,但「将来新增生产调用点忘记改用 `_for_agent`」是**静默拿到全量目录**而非编译失败。处置:四个 wrapper 与 `runtime_actions.rs` 的对应 re-export 一并加 `#[cfg(test)]`,漏改即编译期报错。注意该哨兵当时已扩散到两个文件(`agent_native_tools.rs`、`tool_plan_protocol.rs`),属于正在复制的模式而非单点遗留。
- 复核中另外三条按「记录不改」处置,理由见各自条目:`agent-background-task` 空 source 兜底(见本文件 `M1A-3` 条订正段)、plan 根强判据尚缺 planning pending 一维(`M1B-1` / `M1C-1` 回补)、两条写成终态的验收句(属执行稿口径,不影响实现)。
## 2026-08-14 M1A-4plan 根 run 的子 Agent 创建面收窄,并冻结三条已知残留
- **补的是 `M1A-2` 的反方向。** `M1A-2` 只做了「目标是 `project-planning` → 要求父是 plan 根」这一半;反过来「父是 plan 根 → 目标必须是 `project-planning`」当时没做,也没记为 deferred。后果是 plan 根 run 可以委派任意专业 Agent,而被委派者拿的是常规 `standard` 工具面(能写文件、跑命令),第 24 节「策划全程零构建」当时只由 Prompt 兜底、不是机制保证。
- 落地:`observe_agent_runtime_agent_delegate` 补对称分支;`observe_agent_runtime_agent_spawn_isolated` 对 plan 根 run 一律拒绝(**它是第二条造子 Agent 的通道,只堵 delegate 等于留后门**)。两条通道共用 typed `kind=plan-root-child-target-unsupported`。Supervisor 根 run 的 prompt 在 plan source 下不拼 `supervisorIntro` 与 `$visualContract` 两段;不改 `.md` 内容、不新增 composition key(第 4.2 节)。
- **强弱判据分工与 `M1A-3` 一致,不得合并**:弱判据(`task.source` 自称是 plan,经 task journal 读取而非 binding)决定「本约束是否管辖这条 run」;强判据 `validate_project_supervisor_plan_root_binding_at` 决定「它是否合法」。强判据 `Err` **永远落进拒绝分支**,绝不可写成 `else if validate(..).is_ok() { 拒 }`——那会把「binding 损坏」误归为「不是 plan 根」,恰好在 durable 状态损坏时放行任意子 Agent 创建。
- 回归:`plan_root_delegate_rejects_non_planning_targets_but_keeps_planning_path`(含 `project-planning` 正向路径仍通过,避免把功能整个焊死)、`plan_root_delegate_and_spawn_isolated_fail_closed_when_binding_missing_or_corrupted`、`gui_root_delegate_and_spawn_isolated_are_unaffected_by_plan_root_symmetry`、`plan_root_supervisor_prompt_drops_intro_and_visual_contract_sections`、`non_plan_supervisor_prompt_stays_byte_identical_to_the_original_composition`。
**以下三条是经复核后有意保留的取舍,不是待办。后续 PR 不得在未重新裁决的情况下「顺手修掉」。**
1. **`$isolatedAgentTemplates` 仍会向 plan 根 run 列出全部专业角色名。** `art-director` / `design-foundation` / `art-asset-plan` / `code-prototype` 等名字来自 `$base``RUNTIME_PROMPT_RUNTIME_COMPOSITION` 的 `$isolatedAgentTemplates` 段,由 `GAME_CREATOR_AGENT_GROUP_DEFINITIONS` 运行时渲染),用途是广告 `agent.spawn_isolated` 的合法模板 id,**不在本次裁掉的两段之内**。裁掉它要动 `$base` 与隔离模板目录,波及全部 Agent。保留的依据是:`A2` 已对 plan 根 run 硬拒 `spawn_isolated`,该目录对 plan 根 run 是**死文本**,不构成可利用面。**结论:上下文层的收窄边界到此为止;「plan 根 run 的上下文里不出现其它 Agent 名」这一目标 M1 不成立,不要据此写验收句。**
2. **上下文层回归是弱断言。** `plan_root_supervisor_prompt_drops_intro_and_visual_contract_sections` 用「不含某几条独有短句」断言,不是对照组那种字节级 `assert_eq`。已实测非永真。已知漏报场景:若将来 `$visualContract` / `supervisorIntro` 被替换成措辞不同但仍暗示专业组扇出的新文本,这条不会报警。**执行层的 `A1`/`A2` 是该场景的唯一保障**——这也是本包把硬门排在裁段之前的原因。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 4.3、22、23.8、24 节。
## 2026-08-14 M1B-1planning storage 基础与只挡写隔离完成(待合入)
- 范围:在 `M1A-2` 的 planning 子 Agent 工具边界之上,先落地 Runtime-owned `.agent/planning/**` 的 typed storage 基础,不注册、不广告、不执行 `plan.submit_gdd`(该工具仍属于 `M1B-2`)。当前实现位于隔离 worktree 的 `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs`,由 `runtime_protocol.rs` 注册。
- strict 合同:实现 `plan-gdd.v1`、`plan-gdd-index.v1`、`plan-session.v1` 与 `plan.submit_gdd` input 的 `deny_unknown_fields` 结构校验,并复用统一的文本、ID、时间、枚举、数量/字节上限和 `basis=null` 约束。canonical storage bytes 固定为 Rust struct 声明顺序的 compact UTF-8 JSONBOM、尾换行/空白、重复键、字段重排和语义等价但非 canonical 的 bytes 均拒绝。typed 指纹固定使用 `sha256-serde-json-v2:<64 位小写 hex>` 与 domain separationGDD fingerprint 排除外层自身字段。
- durable 边界:GDD/index 等不可变事实使用项目锁 + 同目录临时文件 + `sync_all` + 回读 + OS no-replace 发布;相同 canonical bytes 只返回 replay,其它同路径内容返回 identity conflict。session 使用原子 replace 与单份 `.session.json.previous`,按 `revision + 1` / `previousFingerprint` 链校验;primary 损坏不得静默被 previous 覆盖,只有 primary 缺失且 previous 唯一有效时才允许持锁提升。
- 写入隔离:`.agent/planning/**` 保持 `file.read` / `file.list` 可读,但通用 `file.write`、`file.patch`、`file.delete`、`project.patchset` 和 checkpoint restore 只挡写;`game/fast_gdd.md` 作为 Runtime renderer 的人读投影同样禁止通用写入。专用 writer 另做 `project-planning + agent-delegate + standard + project-supervisor` identity 校验,失败关闭。
- 当前验证:第 9.1 节 golden vector 已逐字节复核(3857 bytes,指纹 `sha256-serde-json-v2:a59856de7ef134cf2f49c4dedd2ba10ae4ab2340a9634d402eb792b6ee5458f0`),planning storage 定向测试 11/11 通过,writer 已按目标 schema 重解析并核对文件名/版本,index 与权威 GDD 逐项对账,新增 index recovery API 在锁内从严格 GDD 链重建缺失、损坏或陈旧 index,GDD 文件枚举/连续链读取、session request/decision/phase 约束及 recovery 分叉矩阵均有回归覆盖;`cargo check --offline`、`npm run check:encoding` 与 `git diff --check` 均通过。实现已提交于隔离分支 `f453c2ca2`,待合回原分支。
- index 的 `statusCache` 在 M1B-1 仍是无 approval receipt 的预审批投影:多版本只把最新版本标为 `ready_for_approval`,旧版本标为 `superseded`。M1B-1 尚无 approval receipt schemaM1C-1 接入 receipt 后必须重建真实的 `approved` / `revise` / `reject` / `superseded` 状态。`clarification_round` 与完整 root/session identity 绑定留给后续 `M1B-2` / `M1C-2b` 接线。
- 边界:本条不代表 GDD 提交点、approval pending/receipt、审批 UI、`game/fast_gdd.md` renderer 或构建 `approvedGddRef` 已可用;这些仍按 `M1B-2` 及后续 `M1CM1E` 交付。上述边界不影响 M1B-1 存储层本身已完成。
- 关联:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 8.310.2、23.6、23.8 节;`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_storage.rs``apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs`。
## 2026-08-13 M1A-2planning 子 Agent 两层工具面与角色 brief 注入
- 落地范围:在 `M1A-1` 的 `project-supervisor-plan` source 基础上,收口两层工具面。Supervisor 根 run 继续使用 `standard` 的现役工具面;`project-planning` 只接受 `source=agent-delegate`、`profile=standard`、父 Agent 为 `project-supervisor` 的静态委派身份。**2026-08-14 订正:「Supervisor 根 run 继续使用现役工具面」这句读起来像决定,实际是要求丢失——本包工作项原本还要求「按 source 收窄 Supervisor 侧委派面:做方案链路只应产生一条指向 `project-planning` 的委派」,该半未实现也未记为 deferred。已由 `M1A-4` 补齐,见本文件同名条。)**
- planning 子 Agent 的当前 native action exact allowlist 只有 `file.read`、`file.list``update_agent_plan` / `respond_to_user` 是协议控制函数,不计入 action capability。MCP catalog 强制为空,`webSearchEnabled=false``collaborationPolicy=null`。`plan.submit_gdd` 刻意未注册、未广告、未执行,留给后续 `M1B-2`,因此本条不代表 GDD 提交、版本存储或审批闭环已完成。
- PromptPrompt Bundle 新增并登记 `project-planning` role briefstandard planning child 的初始请求与 repair/rebuild 请求均注入同一 briefSupervisor 和其它 Agent 不注入该 section。brief 只描述 Fast GDD 澄清、终态 `AGC_NEEDS_USER_INPUT_V1`、三轮边界、平台事实与低幻觉约束,不授予任何写入、命令、MCP、预览、生成或审批能力。
- 纵深拒绝:广告层不再向 planning child 暴露 `user.input_request`Provider parser、action batch/pending、并行只读、执行层和状态恢复均按原始 tool identity 再校验。伪造写入/命令/MCP、`project.search` 等映射为 `file.read` 的 alias、`user.input_request` 都 fail-closed;恢复旧快照不得把 planning 工具面扩回全量目录。委派子 Agent 原有 `validate_user_input_action_owner` 执行层拒绝继续保留。
- 回归与边界:覆盖 planning 函数目录精确集合、brief 只注入 planning、MCP/web search/collaboration 收窄、原始工具身份拒绝及状态归一化不扩权;Supervisor 根 run 的 standard 工具面保持既有行为。M1A-3 的 source 保留、强判据与 retry 语义不改;`M1B-1` 的 planning 存储、strict schema、typed 指纹和写入隔离已完成,`plan.submit_gdd` 提交仍留给 `M1B-2`。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 4.3、6、19.2、23.6、23.8 节。
## 2026-08-13 M1A-3plan 根 run 强判据与 retry 保源
- 落地:新增 `supervisor_plan_root_identity_holds_at`。必须核 durable run-profile binding(含 project/fingerprint 校验),并与 task 的 `agentId/source/profile/parent/delegation/root*` 以及「存在且 runId 相同」的 runtime、尚存 provider action batch 逐项相等。**不得只比较内存 `runtime.source`。** `agent_runtime_supervisor_source_is_plan` 仍只用于拒绝(steer),本函数只用于授予 retry 保源。
- retry`resolve_game_creator_agent_runtime_retry_configuration_at` 在 generic `agent-background-task` 兜底**之前**插入弱候选分支——`task.source == project-supervisor-plan` 时先走强判据;通过则写出 `project-supervisor-plan`,失败返回 `kind=plan-root-retry-identity-unsupported`**不得降级**。`delegated` / `autonomous-game-build` 两支未改。
- 强弱分工:steer 继续用弱判据。不得把 steer 改成强判据——binding 缺失时会判不成 plan,反而 fail-open。
- 本包不做:`.agent/planning/`、`gddId`、plan session revision+1、按 `gdd-approval` kind 禁 retry(现役已拒 `waiting-for-user-input`)。conversation `sessionId` 复用走现役 retry 入队。
- ~~同族 source 重建复核(`rg` 生产路径,测试除外):字面量 `agent-background-task` 的**唯一**构造点仍是本函数兜底分支。`start_game_creator_agent_background_task_with_link_in_session_lane_at` 接受调用方 source、自身不改写;resume / pending_recovery / recovery_scan 续跑既有 `task.source`,不另造 source。~~ **← 本条于 2026-08-14 订正,结论有误,后续 PR 不得沿用。** 生产路径实际还有两处同形状的静默兜底,而且恰好就是原文点名「不改写 / 不另造」的那两个:`task_start.rs` 的 `start_game_creator_agent_background_task_with_link_in_session_lane_at` 内 `if source.trim().is_empty() { "agent-background-task" }``recovery_scan.rs` 内 `if task.source.trim().is_empty()` 同样兜底。两处在 source 非空时都保源,因此今天不可利用;但它们是 `M1A-3` 所堵漏洞的同一类另外两扇门,**且 `recovery_scan` 那条完全不经过 plan 根强判据**。经复核后**有意不改**:空 source 兜底是全 Agent 通用的历史默认,改成 fail closed 会波及现役全部后台任务启动与恢复路径,超出立项策划范围;若将来要收,须单列工作项并对照现役 resume/recovery 回归。
- 对照:`project-supervisor-gui` + `standard` 仍降级为 `agent-background-task``delegated=true` 仍为 `agent-delegate-retry`autonomous 仍从 binding 取回可信 source。
- 回归:`plan_root_identity_requires_durable_binding_not_runtime_source`、`plan_root_retry_rejects_identity_mismatch_instead_of_degrading`、`plan_root_retry_keeps_plan_source_and_goal_contract_authority`、`gui_and_delegate_retry_sources_stay_on_existing_fallback`;既有 `autonomous_supervisor_retry_restores_trusted_source_from_run_profile_binding` 继续绿。
- 关联文档:技术方案第 4.1 节第 5、7 段(本包只落地 source 保源与强判据,不提前实现第 7 段里依赖 M1B/M1C 的 session/gdd 合同)、第 22 节 `plan retry` 行、第 23.8 节 `M1A-3`。
## 2026-08-13 订正 `M1A-1` 的 retry 复核结论;plan 根 run retry 保源单列为 `M1A-3`
- **被订正的是同日 `M1A-1` 条第三项第三类中的 `lifecycle_control.rs` `resolve_game_creator_agent_runtime_retry_configuration_at`。** 原结论「不适用且已被 profile 挡住,本包不改函数」**只对了一半**:该处确实不会让 plan 误得 autonomous 语义(`autonomous-game-build` 分支先判 profile),但 `M1A-1` 的复核模板只问了「plan 进 matcher 后会不会**误得**不该有的语义」,没有问「plan 落到通用兜底后会不会**丢掉**该有的语义」。retry 这个调用点既是判据也是 run 构造器,两个方向都要问。
- **实际缺陷**plan 根 run 是 `standard` + 顶层无 parent,两个特例分支都不命中,落入 `agent-background-task` 字面量兜底。`run_profile` 由 `agent_runtime_run_profile_identity_at` 原样返回、不受影响,**丢的只有 source**。
- **为什么无声**retry 走 `start_game_creator_agent_background_task_with_link_in_session_lane_at`,该启动路径对 source 无门禁;而 supervisor 正规启动路径 `start_game_creator_supervisor_background_task_for_session_at` 有 trusted 检查。`validate_agent_runtime_run_profile_binding_record` 也只在 `profile == autonomous-game-build` 时要求 trusted source`standard` 档照写。于是 retry 造出一个**正规启动路径造不出来的状态**`agentId=project-supervisor` + root binding + `standard` + `source=agent-background-task`,全程零告警。
- **后果分两类**。放行类:`reject_supervisor_plan_root_steer` 只认精确 source 字符串,重试后不再命中,**steer 重新放开**,直接违反第 4.1 / 23.1 节裁决。死路类:`root_control_authority` 判 binding.source 是否 trusted,变 `false` 后广告层删掉 `agent.goal_contract` 与 `agent.acceptance_update`、`root_goal_contract_required` 变 `false`、`validate_goal_contract_record` 也拒绝建约——重试后的根 run **建不出 Goal Contract**,第 13.0 节审批前置门要的验收取证永远收敛不了,且**用户会走到审批那一步才撞墙**。`agent.delegate` 不受 `root_control_authority` 影响,委派仍可发出,所以故障不会在 retry 当场暴露。
- **处置**:不回改已合入的 `M1A-1`,新列 `M1A-3`(见技术方案第 23.8 节)一并交付第 4.1 节要求的两件事——① 所有 plan 根 run 例外共用的**强判据函数**(须核 durable binding 并逐项相等,不得只比较内存中的 `runtime.source``M1A-1` 交付的 `agent_runtime_supervisor_source_is_plan` 是纯字符串比较,用于**拒绝** steer 是安全的,但不足以**授予** retry 的 source 保留);② generic standard fallback **之前**的 exact plan root 分支。`M1A-3` 必须早于 `M1C-2a` 合入。
- **修法边界**`project-supervisor-gui` / `-cli` 根 run 重试同样降级为 `agent-background-task`,这是现役行为,不在本次范围。只能在兜底分支**之前**插精确分支,不得改兜底默认值(第 4.1 节「不扩大现役 retry 的破坏面」)。
- **前瞻风险**:M1 将实现「每个项目同一时刻最多一条非终态 plan lineage」。若该判据按 plan source 判定,重试产物会**隐身**——不占 lineage 名额却实际在跑,用户此时能并发开出第二条策划链路。`M1A-3` 修好 source 保留即消解。
- 关联文档:技术方案第 4.1 节(第 5、7 段)、第 22 节证据表 `plan retry` 行、第 23.8 节 `M1A-3`。
## 2026-08-13 M1A-1`project-supervisor-plan` 进可信 matchersteer 独立否决
- 落地:新增 `AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE = "project-supervisor-plan"` 并加入 `agent_runtime_supervisor_source_is_trusted`。启动路径 `start_game_creator_supervisor_background_task_for_session_at` / Tauri command 接受该 source`standard` 放行,`plan + autonomous-game-build` 返回 `kind=plan-autonomous-profile-unsupported`。旧字面 `project-supervisor-plan-chat` 仍不在 matcher 内。
- steer:独立函数 `reject_supervisor_plan_root_steer` 只认精确 plan source**不咨询 matcher**。Tauri command 在 trusted 检查之前调用;`steer_game_creator_agent_runtime_task_for_profile_at` 读到 task/runtime source 即拒;`goal_contract_root_steer_task_at` 同样先拒再走 trusted。typed 错误 `kind=plan-root-steer-unsupported`。回归覆盖「plan 在 matcher 内仍拒」与「否决不依赖 matcher、不误伤 gui/forged/已作废 plan-chat」。
- 消费点复核(`rg agent_runtime_supervisor_source_is_trusted`,测试除外):
- **适用(plan 进 matcher 后语义正确)**`commands.rs` 启动门、`task_start.rs` 启动门、`goal_contract.rs` 的 `validate_goal_contract_record` / `create_game_creator_agent_runtime_goal_contract_at`、`acceptance_graph.rs` 的 `update_game_creator_agent_runtime_acceptance_graph_at` / `goal_contract_acceptance_completion_blocker_at_locked`、`provider_request_builders.rs` 的 `root_control_authority`、`run_configuration.rs` 根 binding 写入(autonomous 组合另由启动门拒绝)。
- **不适用 → 独立否决**`commands.rs` `steer_game_creator_agent_runtime_task`、`steering.rs` `goal_contract_root_steer_task_at`(及 `steer_..._for_profile_at` 入口)。不得用「不进 matcher」实现。
- **不适用且已被 profile 挡住,本包不改函数**`task_start.rs` `current_autonomous_game_build_root_task_at`(先要求 `run_profile == autonomous-game-build`);~~`lifecycle_control.rs` `resolve_game_creator_agent_runtime_retry_configuration_at`autonomous 分支才读 trusted sourcestandard 走 `agent-background-task`~~ **← 本条已于同日订正,见本文件上方「订正 `M1A-1` 的 retry 复核结论」条:结论只对了「不会误得 autonomous 语义」这一半,漏了「会丢掉 plan 语义」这一半;该函数改由 `M1A-3` 处理,后续 PR 不得沿用此处的「本包不改」结论**`project_gates.rs` `ensure_current_autonomous_ready_child_mutation_at_locked``profile != autonomous` 即 `Ok(())`);`autonomous_completion.rs` 的 `autonomous_game_build_root_run_active_at` / `validate_autonomous_completion_contract` / `ensure_autonomous_completion_contract_for_task_at` 均先看 autonomous profile`failed_terminal_autonomous_root_contract_before_task_at` 由后者以及 `autonomous_effective_root_task_at` 调用,后者先要求已存在完成合同(完成合同只由 autonomous 根写入)。
- 本包不做:工具面、brief、`plan.submit_gdd`、planning sidecar、审批、前端入口。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 23.8 节 `M1A-1`。
## 2026-08-13 checkpoint handoff 私有持久化随 D10 作废;M1 入口前置决策归零
- 处置:原待裁决项「checkpoint handoff 私有持久化」**无需裁决,随 D10 一并作废**。它待的是 `plan-decision-checkpoint` 这份 Provider 响应的专用 handoff schema / path / requestSlot / ledger 排序语义;该请求 kind 是 D10「Runtime 直投」的组成部分(策划节点持续存活于同一 run,用户回答后在同一 run 内再发一次专用请求形成设计解释)。D11 下策划子 Agent 以终态信封退出来提问、该 run 随即结束,解释与下一步由 continuation 子 Agent 的第一个普通 tool-plan turn 完成,专用请求 kind 不存在,其专用 handoff 也就不需要。
- 核实依据:① 现役 `tool_plan_handoff::lookup_at(root, agent_id, run_id, &response_identity)``agent/runtime_actions/provider_tool_plan.rs:367`)按 `(agentId, runId)` 寻址、与 agent 身份无关,任何 Agent 的普通 tool-plan 响应都已被覆盖,continuation 子 Agent 首轮不需要新机制;② 技术方案第 8.6 节为该方案预留的 `supersededCheckpointHandoffs` 全仓库零代码引用,纯设计构想,作废无迁移成本。
- 不受影响:通用 handoff 安全边界(任何 Provider 成功响应必须先过 storage 的大小/控制字符/敏感键/绝对路径/容量/durable identity 门并落盘才可消费;门拒绝或无法 durable 提交时禁止保存不安全正文、补 lifecycle completed 或自动重发)是现役机制,策划链路照用。
- 结果:**M1 入口前置决策归零**。剩余全部是待执行项:该可信 matcher 约 19 处消费点逐点复核(steer 门须实现为独立显式否决)、为 standard 下 plan 根 run 补「不得自行提问」的机制兜底。
## 2026-08-13 plan source 进可信 matcher;做方案验收图取自固定 Fast GDD 合格标准
- 裁决一:`project-supervisor-plan` **进** `agent_runtime_supervisor_source_is_trusted``agent/runtime_driver.rs:113`),做方案链路正常参与 Goal Contract 协议,不做豁免。2026-08-12 条记录的硬门「裁决冻结前,依赖 plan source 可信身份的 M1 代码不得合入」随本条解除。
- 原阻塞理由已失效:2026-08-12 排除该方向的依据是「策划 Agent 按设计一项证据工具都不该有,必然留下永不 passed 的节点」。该推理写于 D6/D9 拓扑(当时 plan run 就是策划 Agent 本身)。D11 拆成两层后两个前提都不成立:① 策划子 Agent 的 exact allowlist 恰好含 `file.read`/`file.list`,均在 `agent_runtime_acceptance_evidence_tools()` 白名单内;② 更根本的是证据不必由它出——`validate_acceptance_evidence_identity_at``agent/runtime_protocol/acceptance_graph.rs:152`)对证据来源只要求 `binding.root_agent_id/root_run_id` 等于合同的根,**不要求是根 Agent 自己的回执**,而委派子 Agent 的 binding 根就是 Supervisor。
- 入口门不是阻塞,只是时序:合同不存在时本轮必须且只能是一个 `agent.goal_contract`,故 turn 1 冻结合同、turn 2 才发委派,代价是多一次 Provider 调用。
- 落地前必须补的功课:该 matcher 约 19 处生产消费点,同时承担 run 启动门、steer 门、Goal Contract 创建权限、根控制面工具授权与验收图完成门等多种语义。本裁决只确定「plan 进入 matcher」,不等于每个消费点对 plan 语义都正确,M1 落地前须逐点复核并对不适用者单独收窄。steer 门已由同日裁决单独否决,且要求实现为独立于本 matcher 的显式否决。
- 裁决二:做方案链路的验收图**取自固定的 Fast GDD 合格标准,不由 Supervisor 每轮自由发挥**。理由:GDD 是否合格与它描述的是什么游戏无关——变的是游戏概念,不变的是字段与字段约束。因此「验收标准必须在产物不存在的 turn 1 冻结且不可改」不构成矛盾。分工:`plan.submit_gdd` 的 strict schema 承担机器可判的字段存在性与约束(硬校验);Goal Contract 验收图承担「产物确实服务了用户这次的意图」,由 Supervisor 以 `tool:file.read` 对 `game/fast_gdd.md` 取证后确认。Goal Contract 中按项目变化的只有 `outcome`/`nonNegotiables`/`forbiddenAssumptions`/`openQuestions` 四项,`acceptanceNodes` 近乎固定——这不算入口门 Prompt 所禁的「照抄固定信号代替理解」,因为「什么算一份合格 GDD」本就不是本轮要理解的东西。
- 本条不改变 D1(游戏支柱按项目生成 2~4 条)与 D8(GDD 不含引擎字段)已冻结的口径。
- 同批更正一处失真表述:技术方案第 4.3 节此前称「Supervisor 不得自行发起策划性提问」这一条「靠机制保证」。经核实不成立——做该校验的 `static_delegate_clarification_pending_matches_delivery_at` 全仓库唯一生产调用点(`agent/runtime_driver/pending_recovery.rs:791`)外层套着 `run_profile == autonomous-game-build`,而做方案链路跑 `standard`,校验不触发。该链路上 Supervisor 既可自行提问也可改写子 Agent 问题原文,Runtime 都不拦。这是产品约束不是机制约束;要变成机制约束须为 standard 下的 plan 根 run 单独接一道等价校验,属 M1 范围。
- M1 入口前置决策至此剩一项:checkpoint handoff 私有持久化。
## 2026-08-13 立项策划 plan 根 run 不允许 steer
- 裁决:plan 根 run`source=project-supervisor-plan`**不接受 steer 替换协议**。原「plan run 是否允许 steer」待裁决项就此关闭。
- 理由不是「交互未验证」,而是确定性的能力损失:D11 把策划的问询轮次预算挂在委派链上——`clarification_round` 沿 `repair_of_delegation_id` 上溯推断,且每一跳强制 `parent_run_id` 等于当前根 run。steer 会终止旧根、另起 replacement root run,换根后 `parent_run_id` 改变,旧链的 continuation 被跨 run 隔离判据直接拒绝,**该策划链路剩余问询轮次全部作废,用户已回答的内容也无法续接**。
- 实现约束(关键,不得靠副作用实现):`goal_contract_root_steer_replacement_run_id``apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs:763`)现在的资格判据是 `agent_runtime_supervisor_source_is_trusted(&task.source)`,而该 matcher **同时**是 Goal Contract 创建权限与根控制面工具授权的判据。因此**不得**用「不把 `project-supervisor-plan` 加进该 matcher」来实现本裁决——那会连带否掉 Goal Contract,正好撞上仍未裁决的「plan source 与 Goal Contract 协议的关系」。本裁决必须是一条**独立于可信 source 判定的显式否决**:steer 入口识别出 plan 根 run 即拒绝并返回 typed 错误,无论该 source 是否在可信 matcher 内。回归须覆盖「在 matcher 内」与「不在 matcher 内」两种情形下 steer 均被拒。
- 产品侧替代路径:本轮问询内回答/自由填写纠偏;GDD 审批卡 `revise` / `reject`;再不行放弃本轮、重开一条 plan lineage(同一时刻只允许一条非终态 lineage,第二条返回 `PLAN_ACTIVE_RUN_EXISTS`)。
- M1 入口前置决策至此剩两项:checkpoint handoff 私有持久化;plan source 与 Goal Contract 协议的关系(后者带硬门——裁决冻结前,依赖 plan source 可信身份的 M1 代码不得合入;2026-08-13 已合入的 `project-planning` 身份登记不触碰该门,它登记的是子 Agent 的 `agentId`,未引入 `project-supervisor-plan` 这个 source)。
## 2026-08-13 立项策划执行计划入档:五步顺序、`WP1`/`WP2` 完成状态与后置清单写进技术方案
- 背景:D6→D9→D11 三轮拓扑改写与 `WP1` 拆分都各自记了决策,但「现在做到哪一步、下一步是什么」一直只存在于会话里,没有任何仓库内载体。直接后果是技术方案出现过时陈述——文首状态行与第 1.1 节第 5 条在 `WP1`/`WP2` 已合入本分支后,仍写着「WP1 落地前问询上限仍是 1 轮」「正在另一个 worktree 并行实现」,读文档的人会以为该工作尚未开始。本条把执行计划本身作为需要维护的对象入档。
- 决策:技术方案新增第 23.5 节(`WP1`/`WP2` 的问题、定稿语义、门禁与完成状态)与第 23.6 节(五步执行计划表 + M1 开工前必须处置的两类事项 + 明确后置清单),并修正上述两处过时陈述。此后每完成一步,须同步更新第 23.6 节的状态列;**设计结论变更与进度状态变更是两件必须分别维护的事,不得只更新前者。**
- `WP1` 的独立立包依据:它改的是 master 已发布的 PR #165 静态委派澄清中转机制,服务对象不止策划链路,因此不并入 M0/M1 门禁,单列第 23.5 节。语义权威定义仍在本文件同日「静态委派返工深度与澄清轮次拆分」相关条目,第 23.5 节只承载问题陈述、门禁与完成状态,不重复展开语义。
- 明确后置、不阻塞任何一步的四项已记入第 23.6 节:run status 观测暴露派生计数;画布替换授权是否收紧为「只有真实质量返工可替换正式图片」(PR #165 之后就存在的既有行为,与本方案诉求无关);跨 run 全局预算缺口(7 跳是单链界、不是项目生命周期累计界,只要不断开新 run 就能不断获得新配额,本阶段只记录不实现);「做游戏」路径改造(删 `design-director`、收窄 `design-foundation`、调整 16 任务 DAG)。
- 未纳入:此前一度使用过的 `WP0`/`WP3`/`WP4`/`WP5` 编号从未落进仓库,为避免出现两套不一致的工作包编号,本次只保留 `WP1`/`WP2` 两个真实存在的包名,其余内容按性质分别归入第 23.6 节的「M1 开工前必须处置」与「明确后置」两类,不再引入新编号。
## 2026-08-13 `project-planning` 的 agentCatalog 登记机制定稿:与 `supervisor` 平级、不进 `groups`,「做游戏链路一行不动」得以成立
- 背景:D11(见下方同日「立项策划 D9 二次作废…」条)继承了 D9「`project-planning` 需登记 agentCatalog」的结论,但登记方式一直是待裁决项——`build.rs` 的 `validate_seed_task_catalog``apps/ai-game-creator-shell/src-tauri/build.rs:23-46`)要求编译出的 `(taskId, groupId, role)` 集合与 `shared_contracts::game_creation_app::new_game_creation_app_seed_tasks()` 完全相等,不等即 `panic!`,直接把 `project-planning` 塞进任何一个专业组会破坏这条一致性校验、污染 16 任务种子 DAG。本条只做只读取证与机制定稿,**不落地任何代码**,工作目录 `C:/wtp`(分支 `feat/plan-agent-catalog-registration`),只改文档。
- 事实基线(详见技术方案 `docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 3.1 节):
- 被 `build.rs` 比对的 `specialist_nodes` 集合只来自 `manifest.agent_catalog.groups[].roles[]``build_support/runtime_prompt_bundle.rs:387-398`),不遍历 `agentCatalog` 的其它顶层键。
- `project-supervisor` 本身就是「catalog 成员但不是种子 DAG 任务」的既有先例:`runtime_adapter.rs` 的 `build_game_creator_runtime_agent_catalog``apps/ai-game-creator-shell/src-tauri/src/agent/runtime_adapter.rs:4-38`)先单独 push 一个 supervisor 的 `AgentDescriptor`,再遍历各组角色。
- `AgentDescriptor::metadata()` 目前零生产调用,`game_creator_runtime_agent_catalog()` 唯一生产调用点(`runtime_state.rs:1491`,即 `normalize_game_creator_runtime_agent_id`)只做 `.get(agent_id).is_some()``AgentCatalog::iter()` 也零生产调用——当前没有任何代码枚举 runtime catalog`project-planning` 登记后不会泄漏进「做游戏」团队清单或路由清单。
- 但仅做 catalog 登记不足以让 D11 可执行:`prompt.rs` 的 `game_creator_agent_role_definition``apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs:661-679`)硬编码「非 supervisor 即 group 角色」二分,`project-planning` 落入 else 分支返回 `None`,两个调用方都会把 `None` 转 `Err` 中断——`provider_request_builders.rs:536-539` 报「未知 Agent 模板:project-planning」,`prompt.rs:416-417` 报「未知 Agentproject-planning」(两处错误文案不同,均已逐行核对代码原文,非同一字符串)——这是本次调研发现的 **blocking** 缺口,不是 catalog 登记本身能解决的。
- 决策:`project-planning` 在 `prompts/runtime/manifest.json` 的 `agentCatalog` 下登记为与 `supervisor` 平级、**不进 `groups` 数组**的独立条目(`id`/`taskId=project-planning`);descriptor metadata 取值——`groupId="project-planning"`(自引用伪 group id,绝不复用 `"design"`,避免与 design 组中文 label「策划组」语义碰撞)、不设 `groupLabel`(照抄 supervisor 先例)、`roleLabel` 跟随角色定义、`toolId="agent.runtime.project-planning"`(跟随 supervisor 的组外单节点命名族)、`capabilityAuthority="game-creator-tool-policy-snapshot"`(与全部现有条目相同,无需新值)。由此 `specialist_nodes`、16 任务种子 DAG、`new_game_creation_app_seed_tasks()`、`build.rs` 三者均不需要改动,「做游戏链路一行不动」得以成立。composition 复用现役 `runtime` composition,不需要单独配置;`briefPathName` 只是运行时文件名 token,缺文件不报错,但格式与全局唯一性仍受编译期 `validate_file_name`/`validate_agent_catalog` 约束。
- 编译链路要改的位置(M1 落地范围,本轮不动):`build_support/runtime_prompt_bundle.rs` 的 `struct AgentCatalog` 加 `planning` 字段、`validate_agent_catalog` 镜像 supervisor 的单 role 约束/去重/防冲突集合/显式校验调用、`compile_manifest` 的 `catalog_task_ids` 链入 `planning.roles`、`render_agent_catalog` 镜像 supervisor 专属四个产物;`runtime_adapter.rs:4-38` 镜像 push 一段 `AgentDescriptor`;连带隐藏耦合 `pass_artifacts.rs` 的 `agent_role_memory_relative_path_for_task`335-347 行)需加第三条 `project-planning` 分支,否则运行时报「未知 Agent 任务」。
- **本轮范围声明(与 M1 的边界)**:本条只冻结登记机制、更新技术方案文档第 3.1 节与第 23.1 节,**不改 `manifest.json`、不改 `runtime_prompt_bundle.rs`、不改 `runtime_adapter.rs`、不改任何 `.rs` 文件**。理由:`project-planning` 目前没有 prompt、没有任何路径能调用它,现在注册 catalog 而不同步处理上面的 blocking 缺口,等于给发布产物加一个「看似已登记、一调用就硬失败」的死重身份;注册代码应与 M1 的 prompt/source 一起落地。
- 保留待处置(M1 范围,非本条待裁决):`prompt.rs` 的 `game_creator_agent_role_definition` 角色身份合成缺口(blocking);`task_ops.rs` 的 group/role 误分类兜底(needs_change);`delegation.rs` 的 `agent.spawn_isolated` 放行面扩权(needs_change);`task_start.rs` 的 `collect_game_creator_agent_runtime_agent_ids` 恢复/steer/对账枚举缺口(needs_change2026-08-13 复核补记);`pass_artifacts.rs` 的内存路径解析缺口(M1 必须同步处理);`runtime_adapter.rs` 的 `game_creator_runtime_agent_catalog_matches_the_existing_role_directory` 测试(91-121 行)断言 catalog 精确等于 `{supervisor} 16 组角色``project-planning` 登记后需同步更新其期望集合,否则 M1 落地当天编译测试即失败。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 3.1 节(新增小节)、第 23.1 节(原「`project-planning` 的编译期 agentCatalog 登记方式与 `build.rs` 一致性校验」待裁决项已降级为「机制已定稿,代码落地属 M1」);关联决策:本文件下方同日「立项策划 D9 二次作废、D10 作废」条(D11、第 1.1 节「D11 新拓扑」第 6 条)。
## 2026-08-13 立项策划 D9 二次作废、D10 作废:改为 Supervisor 静态委派子 Agent(D11);静态委派澄清轮次与返工深度拆分定稿(WP1)
- 背景:产品侧确认「做方案」入口不需要新增编排调度器概念,复用已有静态委派(`agent.delegate`)与 PR #165 已实现的子 Agent 澄清中转链路即可覆盖 D9/D10 试图解决的问题;同时实证发现静态委派返工深度门(`repair_of_delegation_id.is_some()` 即拒绝)对「质量返工」与「澄清 continuation」无差别拒绝,一次澄清会吃掉整条链唯一一次质量返工额度。两条问题合并处置。
- 事实基线(均已逐处打开源码或测试核对,详见技术方案 `docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md` 第 1.1 节「D11 新拓扑」):
- `delegation.rs:1119-1121` 的返工深度门对「质量返工」与「澄清 continuation」无差别拒绝;`apps/ai-game-creator-shell/src-tauri/src/tests/collaboration/static_deliveries.rs` 的 `clarification_continuation_chain_supports_multiple_rounds` 走真实 `observe_agent_runtime_agent_delegate` 生产路径证明:D1→D2 成功、D2→D3 被拒、D3 从未落盘。
- 委派子 Agent 天然带 `parent_agent_id`/`parent_run_id``user.input_request` 被执行层 `validate_user_input_action_owner``user_input.rs:367-394`)在落盘 pending 之前直接拒绝;但广告层不拦——`build_agent_runtime_native_function_tools``agent_native_tools.rs:279`)无 `agent_id` 参数,`standard` profile 下 `tool_policy_snapshot.rs:144-147` 仍把它列为 auto tool,模型看得见、会去调,只是必失败并转成一次失败的 tool observation。
- 委派子 Agent 的 `target_session_id` 每轮解析为同一条持久 active session`resolve_agent_conversation_session_id_at` 传 `None` 时走 `catalog.active_session_id``project/conversation.rs:849-852`),prompt history 按 `(agent_id, session_id)` 组装(`context_compaction.rs:453-490``run_id` 只用于 observation 覆盖计数),故 continuation 子 Agent 是「新 run、同 session」,不失忆;但用户答案物理落在 Supervisor 会话,子 Agent 只能靠 Supervisor 转述,Runtime 只校验 `questionsSha256`/`answersSha256` 哈希绑定,不校验转述内容与已确认答案的语义一致性。
- `normalize_static_delegate_expected_artifact``delegation.rs:1981-1998`)拒绝首段为 `.agent` 的路径,`.agent/planning/**` 在委派发起阶段天然进不来,不需要文档层自律约束;`game/fast_gdd.md` 不受影响,定稿 `expectedArtifacts=["game/fast_gdd.md"]` + `acceptanceCriteria` 承载质性标准,`verification_required=false`。
- `project-planning` 目前不在编译期 agentCatalog 里;`build.rs` 的 `validate_seed_task_catalog``build.rs:23-46`)要求编译出的 `(taskId, groupId, role)` 集合与 `new_game_creation_app_seed_tasks()` 完全相等,不等即 `panic!`。
- 决策(D11,取代 D9,连带作废 D10):立项策划节点改为 Project Supervisor 通过 `agent.delegate` 发起的**静态委派子 Agent**`agentId=project-planning`),不再由 manifest ready-task 调度器启动;问询复用 PR #165 已实现的中转链路:子 Agent 以 `AGC_NEEDS_USER_INPUT_V1` 终态信封退出 → Supervisor 认领 → Supervisor 在自己的 runtime/session 上建 `waiting-for-user-input` pending → 用户在 Supervisor 会话内作答 → 答案经 `questionsSha256`/`answersSha256` 绑回 delivery → Supervisor 发起 continuation 子 Agent 续跑。命名裁决同时冻结:`agentId=project-planning`Supervisor 侧新可信 source 为 `project-supervisor-plan`(不沿用旧预留字符串 `project-supervisor-plan-chat`)。
- D9「立项策划是独立 `agentId` 的下游工作流节点」这一结论方向被 D11 继承,但调度机制被推翻——不再由 ready-task 调度器启动。D10「问询改走 Runtime 转发、两路投影」这一方向也被继承,但落地机制不同:D10 设想的是为 D9 拓扑新造的「直投」,D11 复用的是已经上线的 PR #165 链路,两者不是同一套代码。D10「exact allowlist 主动排除 `user.input_request`」这条论证在 D11 下失效:委派子 Agent 的 `user.input_request` 由执行层 `validate_user_input_action_owner` 兜底拒绝,是 Runtime 机制而非产品自律(但广告层仍放行,须与执行层拒绝一并回归钉死,不能只测一半)。
- **推翻 2026-08-12「子 Agent 澄清回执由 Supervisor 中转(Issue #163)」决策记录中「随后最多创建一次绑定原 `delegationId` 的 continuation child」这一条**(该决策落地为 PR #165,对应本文件本条目下方原文见该日期条目)。实证(`clarification_continuation_chain_supports_multiple_rounds`)证明该限制来自返工深度门的误伤,不是有意设计;该条自本决策起作废,continuation 允许的次数改按下方 WP1 定稿的 `clarification_round` 语义执行,不再是「最多一次」。
- 决策(WP1,静态委派澄清轮次与返工深度拆分):
- 两个维度独立,且都是**运行时派生值,不落盘**:`repair_depth` 上限维持 1(不放松);`clarification_round` 上限按 source 区分。
- **分类判据(唯一权威)**`parent.structured_result.contract_status == NeedsUserInput` ⇔ 这一跳是澄清 continuation;否则是质量返工。
- **传播规则**:根节点(`repair_of` 为 `None``depth=0, round=0`;澄清跳 `round=parent.round+1, depth=parent.depth`**不重置返工深度**,否则可插一次澄清洗掉返工深度、变成无限返工);返工跳 `depth=parent.depth+1, round=0`**重置澄清轮次**,因为返工后策划节点重新开工,不能因返工吃掉预设的 3 轮问询预算)。
- **总跳数上界推导**`repair_depth` 上限 1 意味着链上有 `repair_depth_max + 1 = 2` 个「深度段」,每段各自最多 `clarification_round_max = 3` 次澄清跳,另加 `repair_depth_max = 1` 次返工跳本身。故总跳数上界 = `repair_depth_max + (repair_depth_max + 1) × clarification_round_max = 1 + 2 × 3 = 7` 跳,加根节点共 **8 条 delivery 记录**。**注意是 7 不是 6**——容易漏算的是「连接两层的返工跳本身也算一跳」,只算两段澄清跳(`2 × 3 = 6`)会漏掉这一跳。
- **不得新增持久字段**:给 `StaticDelegateDeliveryRecord` 加 `repair_depth` 字段并用 `#[serde(default)]` 兜底,会让磁盘上已有的返工记录读出 `0`,深度门失效,`tests/collaboration/static_deliveries.rs:977-988` 钉的「返工的返工」漏洞原样复活。方向是 **fail-open,不可接受**。必须改用**链上推断**:每次校验时沿 `repair_of_delegation_id` 向上重放整条链现算。链上推断对历史记录是精确而非仅保守的:PR #165 之前不存在 `NeedsUserInput`,老记录天然被正确分类为「非澄清」,不会误判。
- **最脆弱的回归点**:不是「澄清跳误把 `depth` 清零」(已被设计明确防着),而是**「返工跳漏掉 `depth+1`」**——现有测试要么纯返工链、要么纯澄清链,从未覆盖交替链,若实现写成「有 parent 就继承 `depth`」这种自然默认,`depth` 会永远停在 0,无限乒乓原样重开,而两条现有测试全绿。回归矩阵必须新增一条交替链用例(澄清→返工→澄清→返工…)专门钉死这一路径。
- M0/M1 影响:D11 依赖 WP1 先落地才能生效——WP1 正在另一个 worktree 并行实现,是 D11 的**强制前置**,不是并行工作包;WP1 落地前,D11 描述的「最多 3 轮问询」实际上限仍是 1 轮,且用掉后连一次质量返工都做不了。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`;关联决策:本文件 2026-08-12「立项策划 D6 作废」条、2026-08-12「子 Agent 澄清回执由 Supervisor 中转(Issue #163)」条(其「最多创建一次 continuation」结论已被本条推翻)。
## 2026-08-12 Repository checks 采用 CI 与本地共用的单一门禁入口
- 背景:master run 1037 的 Backend/Frontend 已通过,但 `Repository checks` 因 3 个 `simple-import-sort/imports` 错误失败。原 pre-commit 只运行 PrettierPrettier 不处理 ESLint import 排序;推送前又未运行完整仓库 lint,因此本地与 CI 的覆盖范围长期存在漂移。
- 决策:`npm run check:repository-ci` 成为 Repository checks 唯一仓库入口,统一执行 lint、生产构建、内容检查和基线到候选提交的空白差异检查;Gitea workflow 与 master pre-push 只调用该入口。pre-push 必须验证待推 master SHA 是当前 `HEAD` 且已跟踪工作树干净,无法确认候选内容时失败关闭;feature 分支不运行该重门禁。
- 提交门禁:lint-staged 对 staged JS/TS 先运行按 ESLint 配置过滤 ignored 文件的 autofix wrapper,再运行 Prettier。回归测试覆盖 import 排序、部分暂存恢复、ignored 文件、feature push 跳过、master push 参数绑定,以及 workflow/hook 共用入口。
- 权威边界:本地 hook 可被 `--no-verify` 绕过,不能从制度上保证 master 永远不红。服务端根治要求禁止日常直接 push master,统一走 PR,并要求当前 head 的 Repository/Frontend/Backend/Native 四项检查全部成功后合并;紧急白名单只能最小化保留。
## 2026-08-12 Agent 失败原因使用稳定分类贯穿 Runtime 与正式展示面
- 决策:app-server 只消费协议稳定错误分类和 HTTP 状态,不公开 `message / additionalDetails`Runtime 持久私有诊断继续脱敏,正式 conversation、失败事件 `publicText`、阶段记录、最近任务和所有 Agent 卡片统一从封闭分类派生可行动中文摘要。失败事件只允许后端 `publicText` 进入正式活动详情,缺失时使用固定安全 summary,私有 `detail` 不得展示;旧 Supervisor 与专业 Agent 失败 conversation 也必须经过同一安全映射。`needs-reconciliation` 是停止自动推进并等待人工处置的终态,显示为“待核对”,不得归为普通运行中或普通完成;未知错误保留固定安全兜底。自主构建确定性 final-reply fallback 只允许稳定 `empty-response / deserialize` 回复形状错误,任何鉴权、额度、上下文、策略、sandbox、配置、网络或上游错误都保持失败。
- 安全边界:不得把 Provider 正文、URL/query、API Key、Token、Cookie、本地绝对路径、fingerprint、字符数或 `[redacted ...]` 占位符放入正式 UI、conversation、事件 `publicText` 或阶段记录。前端只消费后端稳定分类或已通过严格门禁的公共摘要,不从自由文本猜测敏感上游错误。
- 未完成恢复项:isolated join 唤醒、isolated child result 发布、manifest terminal projection 和 terminal-unknown reconciliation 在持久化自身失败时仍需要独立 durable marker 与重启扫描协议;这些跨崩溃窗口必须单独设计和验证,不能用 best-effort 事件或日志冒充已恢复。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-12 立项策划 D6 作废:策划改为 Supervisor 下游工作流节点,问询走 Runtime 直投
- 背景:产品侧确定「做方案」入口独立成链——不动做游戏路径,最终产物只有策划方案,且保持 Supervisor 顶层、工作流节点与子 Agent 在下游的结构。据此复核代码后,D6「策划是 Project Supervisor 通道的第三 persona、复用同一 `agentId`、不注册新 agentCatalog 身份」的前提逐条不成立。
- 事实基线(均已逐处打开源码核对):`user.input_request` 在 `autonomous-game-build` 下被广告层(`tool_policy_snapshot.rs:198-215`)与执行层(`main_loop.rs:2604-2616`)两道拦截,**判据只看 run profile、不看 agent 身份,父 Supervisor 自己也被禁**;硬闯的后果是 `pending_execution.rs:1157` 把 `runtime.status` 写成 `failed`,而 `task_start.rs:685-693` 的根活跃判定不认 `failed`,**整条 16 节点工作流永久瘫痪、须人工核对**`run_configuration.rs:264-266` 明文「子 Run 不能切换父 Run 的 Run Profile」,故不能只给策划节点换 profile`user_input.rs:367-394` 拒绝任何带 `parent_agent_id / parent_run_id / delegation_id` 的 run 直接提问。
- 决策(D9,取代 D6):立项策划是独立 `agentId` 的下游工作流节点,由 manifest ready-task 调度器在 Supervisor 下游启动,需登记 agentCatalogProject Supervisor 以新可信 source 承载「做方案」入口并保持唯一顶层 root;父子 run profile 必须同为 `standard`。「复用 standard」这一结论方向从 D6 继承,但成立理由完全不同。
- 决策(D10,问询机制):策划节点不得直接调用 `user.input_request`,改走 **Runtime 直投**——策划节点提交结构化决策字段,Runtime 创建 **owner 为 Project Supervisor** 的 pending,用户在 Supervisor 对话里回答,Supervisor 的 Provider 全程不参与提问。这不是新造机制:`AgentRuntimeUserInputRecord` 已经是唯一事实源并两路投影(`user_input.rs:494-530` 投影成会话消息写进 pending owner 的会话文件,`:595-623` 投影成结构化 observation`:803-807` 有「observation 重算冲突」校验防漂移),直投只是让两路分别落到 Supervisor 与策划节点。
- 依据的产品约束:一、用户侧只有一个对话对象;二、所有呈现给用户的对话内容必须**物理存在于** Supervisor 的会话文件中,不得由前端把多个 Agent 的会话拼成单一视图。第二条不是靠约定满足,而是靠「会话文件按 `agentId` 分目录(`conversation.rs:42`)、消息归属完全由 pending owner 决定」这个物理事实。
- 决策卡冻结范围收窄:冻结三个固定选项及顺序(要确定性映射到 decision state 与 answer source)与 question ID 规则(`decisionId` 由它推导);**问题正文措辞不冻结**,作者是策划节点。保真由「Runtime 不经过任何 Provider 搬运」保证,与模板无关。
- Goal Contract 处置:原四方案 A/B/C/D 随之作废(共同前提是策划 run 自己就是那个 root)。但入口门 `validate_root_goal_contract_control_plan_at` 无 profile 判断,standard root Supervisor 仍受约束,故替换为三条实测约束:Supervisor 第一轮必须且只能提交 `agent.goal_contract`;验收 `requiredEvidence` 锚定 `game/fast_gdd.md` 配 `tool:file.read`**不得指向 `.agent/planning/**`**`reject_agent_runtime_private_control_path`不区分读写,加进去会把`file.read` 一并挡死、出口门永久 blocked,planning 的写保护须用只挡写的独立判据);Supervisor 取证必须在 GDD 落盘之后。
- M0 影响判定:三个代码工作包(`M0A-2` / `M0B-1` / `M0B-2`**全部不受影响、零回退**——无一行按 D6 编写,全仓库检索 `fast_gdd` / `.agent/planning` / `project-supervisor-plan-chat` / `is_exact_supervisor_plan_run_at` 均零命中。只有 `M0A-1` 交付的文档基线失效,以工作包 `M0A-3` 修订,修订完成前不得声称「M0 全部完成」。**注意 `M0A-2` 虽不受影响,其实现也不可复用**`autonomous_owner_artifact_validation_available_for_run_at``autonomous_completion.rs:416-441`)四重绑死 owner 白名单、profile、source 且要求 `parent_agent_id` 为 Project Supervisorstandard 路径必须另建物理独立实现。
- 保留待裁决:策划节点 `agentId` / source 命名(是 `M0A-3` 批二的共同阻塞点,身份常量不定则第 3、8、9、12、13 节无法落笔,第 9.1 节 golden vector 的 SHA-256 必然重算);Supervisor 侧新可信 source 是沿用旧字符串还是取新名;checkpoint handoff 私有持久化(原有项,不受影响);plan run 是否允许 steer,以及 Supervisor 被 steer 时下游策划节点如何收束。
## 2026-08-12 M0 全部完成,并据动态目标验收图收敛 M2 / M3 设计(M1 裁决保留)
- 背景:M0-1M0-4 及 `M0A-1` / `M0A-2` / `M0B-1` / `M0B-2` 四个工作包全部通过门禁并合入 M0 集成分支 `feat/five_min_design`(已推送)。M0 按整体切片交付,不逐工作包直接进 master,因此「未进 master」不作为 M0 未完成的依据。
- 裁决:**M0 全部完成**。M0 期间该分支两次合入上游 master`03a441027` 无限画布、`1363b9374` 动态目标验收图、`51e35468a` 自主构建测试锁竞态修复),`origin/master` 已是分支祖先。M0 完成不表示任何策划功能上线;M1 功能实现尚未开始。
- 触发复审的上游事实:`1363b9374` 引入的 Goal Contract / Acceptance Graph 对**所有可信 root Supervisor**生效且不看 Run Profile,与本方案 M1~M3 的多条前提冲突,故一并收敛下列三条。
- M2 裁决一:确定性收束与 Runtime 内部产物验证**都不构成验收证据**。`design-director` 确定性化后不产生 Provider 回执,M0-3 的内部 owner 验证同样不产生;而 passed 节点必须引用真实成功动作回执且 `requiredEvidence` 须命中 `agent_runtime_acceptance_evidence_tools()`。涉及 GDD 落地的验收标准要么由根 Supervisor 用允许的证据工具自行取证,要么不写成 required 节点。**不得**为使确定性节点可验收而把内部验证工具暴露给 Provider——那会推翻 M0-3 已冻结的 owner 验证边界。
- M2 裁决二:planning baseline 不是「一次冻结永久有效」。steer 替换协议会终止旧根树并另起 replacement root run,该 run 必须在自己的锁/CAS 边界内重新冻结与被替换根**完全相同**的 `approvedGddRef`,即使期间已有更新的 approved 版本也不换稿;无法证明同一 ref 时失败关闭,不得降级为 `mode=direct-build`。
- M2 附带证据:Goal Contract 落在 `.agent/runtime/goal-contracts/{rootAgent}/{rootRun}.json`,按 root run 分文件并绑定 binding fingerprint 与 source SHA-256。这是「自带 root/run 身份」的正面先例,D4 关于 `approvedGddRef` 是否进 manifest 的决策应参照此形态,而非项目级单例。
- 背景:M0B-2 遗留一条挂起项——manifest 没有 root/run 绑定,不能安全决定 cached failed manifest 是否应覆盖活跃 main 状态。本条对该项作出处置,使 M0-4 可以收口。
- 事实基线:`GameCreationAppManifest` / `GameCreationAppTaskState`TS 与 Rust 双侧)不含任何 run/root/agent 身份字段;manifest 是项目级单例文件、跨轮复用;前端唯一读取入口按 `task.id === 'code-prototype'` 做纯字符串过滤。因此"这份 manifest 属于哪一轮"无法从数据本身判定,这是结构性质而非实现疏漏。
- 本阶段不加绑定:不给 manifest 或其投影新增 `statusRunId` / `statusSource` 等身份字段,维持 M0B-2「不修改后端 DTO/schema/delivery/route」的范围声明。补字段的方案必须同时覆盖 `update_manifest_task_status_at` 与 `set_task_status` 两条写入路径,否则会制造"校验通过"的假象(详见 pitfalls 2026-08-12 条)。
- 残余风险(明示保留,不视为回归):当前 main 到达 Runtime 终态 `completed`、而全局 manifest state 尚未被本轮 `manifestInvalidated` 事件刷新时,跨轮残留的 `failed` 仍会被当作本轮结论显示。该窗口实际宽度未量化;三条候选机制(新鲜度门控、root-scoped 永久缓存、manifest 补身份字段)经审查均不可安全落地,故本阶段只记录不实现。
- 归档边界:`【Supervisor 阶段记录】` 只有在 root、唯一 main、当前动态美术 children 与 manifest `code-prototype` 全部终态且无冲突/reconciliation 时才可写入。等待期间按完整 `agent/session/run` 身份保存 root-scoped Runtime 快照,避免 current-by-agent map 被新 root 覆盖后把新旧证据串线;消息 ID 固定绑定 root run,重载与 hydration 幂等。
- 范围:M0B-2 只改前端纯投影、接线、文案与回归,不修改后端 DTO/schema/delivery/route,不迁移历史 manifest,也不提前实现 M1M3。
- 结构性依据:delivery 的 delegationId 由父动作 ID 派生,且 targetRunId 绑定原 child run;通用 retry 铸造的 `retry:{旧run}:{新run}:{纳秒}` 身份在结构上不可能匹配任何现有 delivery。即使只圈住 retry 的写边界,其产出也没有合法消费者。让 retry 继承 lineage 必须引入可变 delivery、换绑或放宽 exact binding,与不可变事实和失败关闭方向冲突,明确不采纳。
- 既有语义确认:同一 main run 内“每个审计缺口最多委派一次”(不含 `Suppressed`、包含终态失败或取消 delivery)是 2026-08-08 单主编排重构的 master 既有防抖语义,本裁决有意保留,不为失败 child 开豁免。同 run 重来与通用 retry 一样被拒绝,恢复只走下一轮 main 重新审计;这与禁止 retry 继承 lineage 是同一设计哲学。
- 实现边界:入口守卫落在 `runtime_driver/lifecycle_control.rs` 的 `retry_game_creator_agent_runtime_task_at`,必须使用不要求 child 仍为 running 的结构身份分类;`resolve_game_creator_agent_runtime_retry_configuration_at` 保持不变。纵深防御落在 `runtime_tools/file_ops.rs` 的动态美术分类入口;严格 lineage/Canvas 授权 predicate 本身不放宽。除这两处与对应测试外,不扩展 M0B-1 生产改动面。
- 验证方式:终态 failed/cancelled 动态美术 child 的 retry 返回类型化错误且不产生新 run;遗留或伪造的 `agent-delegate-retry` 美术 run 对 `file.write`、`project.patchset`、`canvas.asset_generate` 及其它全部可变工具阻断;完整 DAG child、非美术委派和顶层 retry 非回归;失败或取消 child 的回执被认领后,同一 main run 对同一 target 的新 action 仍拒绝且零新 delivery,下一轮 main 重新 `asset.list` 并路由同一缺口后允许新委派,且新 delegationId、targetRunId、parent run、delivery 与 assets-only 授权全链一致。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`。
## 2026-08-11 固定 owner 产物验证与可玩验收分离
- 背景:真实新项目初始化后没有 `package.json`,默认 `game/index.html` 只是无 `<canvas>` 的占位页。`design-foundation`、`balance-seed`、`art-asset-plan`、`audio-asset-plan` 位于 `code-prototype` 上游,只负责策划、数值、美术清单和音频清单;若要求它们执行 `project.verify` 或 `game.static_smoke`,前者没有可执行合同,后者只能检查尚未生成的占位游戏并必然失败。曾在测试中预先写入 `fake_llm_game_draft()` 会把占位入口替换成可玩页面,从而掩盖这条真实新项目死锁。
- 决策:完整 GUI / CLI 16 任务 DAG 的四个 pre-code artifact-only owner 在尝试收束时,由 Runtime 内部按固定 owner 合同验证正式产物,不向 Provider 新增工具或 commandId,也不要求模型自行调用验证命令。内部验证类型固定为 `runtime.owner_artifacts_validate`;它写入现有 verification gate 的普通 `verifiedRevision`,不写 `staticSmokeVerifiedRevision`,也不生成 smoke / preview command trace。
- 固定 owner 合同:`design-foundation` 对应 `memory/project.md` 与 `game/game_design.md``balance-seed` 对应 `game/balance.json``art-asset-plan` 对应 `assets/manifest.art.json``audio-asset-plan` 对应 `assets/manifest.audio.json`。同一份 canonical owner 映射同时驱动文件 write / patch / delete / patchset 边界、正式产物完成检查和 Runtime 内部验证,避免路径权限与完成合同漂移。验证要求普通文件有界读取且非空、JSON 可解析、文本不存在 incomplete marker,并相对根完成合同的 baseline 确认本轮产物确有变化。
- 身份与失效:内部验证只接受完整 16 任务 DAG 中由 `agent-ready-task-scheduler` 启动的确定性直接 child、当前活跃 GUI / CLI Supervisor 根、正确 project / source / profile / Agent / run / parent / root / binding。错误 source、delegated run、历史或终态根、非当前活跃根、跨 Agent/run 凭证和身份不完整一律失败关闭。owner 再次 mutation 后旧 `verifiedRevision` 立即失效,恢复只能在相同完整身份和当前事实下确定性重验。
- 验证方式:使用真实 `init_local_game_project_at` 证明无 `package.json`、占位入口 smoke 失败、owner 产物不齐时阻断、产物齐全后 Runtime 内部验证收束、无 smoke trace 且 `staticSmokeVerifiedRevision` 为空;再覆盖四个 owner 的路径矩阵、再次 mutation 失效、错误 source/run/root/parent/终态拒绝、恢复重验、跨 Agent/run 不可借用、`code-prototype` / `preview-readiness` smoke 非回归,以及 `art-director` 有/无 Key 的条件角色分类。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`。
## 2026-08-10 立项策划 Agent 使用 Fast GDD 版本审批作为完整构建的可选基线
- 背景:当前普通完整构建从简短需求直接进入 autonomous manifest,缺少用户在消耗完整构建成本前确认玩法方向、MVP 范围和原型验证项的正式环节;现有 `design-director` 是只读协调任务,`design-foundation` 又会自行补齐玩法定位,用户意图与实现之间没有可版本化、可审批、可恢复的信任根。
- 阶段合同基线:durable source 为 `project-supervisor-plan-chat`Rust 常量为 `AGENT_RUNTIME_SUPERVISOR_PLAN_CHAT_SOURCE`Prompt composition/source kind 为 `supervisorPlanChat` / `SupervisorPlanChat`,原生工具为 `plan.submit_gdd`strict submit/回答解释 checkpoint/provider binding 为 `plan-submit-gdd-input.v1` / `plan-decision-checkpoint.v1` / `plan-provider-session-binding.v1`。exact plan Provider lifecycle/action batch 目标写入 `game-creator-provider-request-lifecycle.v3` / `game-creator-provider-action-batch.v4`;普通 tool-plan 只要有 action,即使 sole action也强制 durable 写 v4 batchuser-input/submit 各有唯一 membercheckpoint/final-reply 无 action 才不建 batch。非 plan 继续写 v2/v3,旧 lifecycle v1/v2 与 batch v1/v2/v3 只按非 plan 语义双读,不能补 binding 恢复成 plan。独立 pending kind/schema 为 `gdd-approval` / `plan-gdd-approval-pending.v1`,审批与 hydrate 命令为 `decide_game_creator_plan_gdd` / `hydrate_game_creator_plan_gdd_state`hydrate view 为 `plan-gdd-state-view.v1`;阶段名为“立项策划”,persona 名为“立项策划 Agent”,现有 design 组用户名称改为“设计实现组”。checkpoint handoff 的私有 schema/path/slot/ledger 排序不在本次非交付检查点选择实现方案,必须在 M1 对应代码合入前另行冻结。
- 持久化与信任:`.agent/planning/gdd.v{N}.json` 和 `.agent/planning/approvals/v{N}.json` 使用 no-replace create-only 发布,磁盘 JSON 固定 compact UTF-8 且无结尾换行,分别以 GDD durable create 和 receipt durable create 作为提交点;receipt 是构建信任根,index、session 的已提交摘要、Markdown、pending、审计、event 和 observation 都不能反向覆盖不可变事实。Markdown 有有效 approved 时固定投影最高 approved,否则投影最新严格 submitted GDD 并显示推导状态,不能在 revise/reject 后残留未决文案。planning GDD/回答解释 checkpoint/审批 intent/receipt/session/pending/comment 使用 domain-separated typed serde `sha256-serde-json-v2:<hex>`;现役 `actionFingerprint`、`runProfileBindingFingerprint`、answer hash 与 handoff response fingerprint 继续使用裸 64 hex,不做全局迁移,两类值不得互相比较。
- 对话 checkpointplan 专用 `user.input_request` 保持现役 questions-only sole actionRuntime 先创建或复用与 v4 batch identity 全等的 pending sidecar,再把完整问题、hash 与 request/action/provider identity 写入 session.activeQuestion,最后展示决策卡。activeQuestion 尚未落时,session 仍等于 batch binding 才安装同一问题;session 已是合法 successor 且尚无 standalone pending/card/answer 时,旧问题未被消费,必须先补 lifecycle completed、supersede 并回读旧 batch、删除 exact pending sidecar并确认 absent,再清理 batch和从 successor 请求新问题,不能把合法 steer 一律送入 reconciliation。activeQuestion durable 后只允许 v4 batch `ready/nextActionIndex=0/唯一 approved member`standalone pending 从 absent 直接进入 exact `auto + waiting-for-user-input + observation:null`。若在 runtime/card 发布前崩溃,只能按相同身份补齐或按上述 successor 清理顺序前滚;其它 sidecar/member/cursor/pending 状态和无法证明的 session 漂移失败关闭。用户回答先停在 `answer-prepared`;启动 checkpoint 前还必须重验 exact v4/standalone waiting anchor。随后同一 run 发起只广告 plan 专用 strict `update_agent_plan` 的 `plan-decision-checkpoint` Provider turnAgent 用 `plan-decision-checkpoint.v1` 形成 answerSummary 和需要时的微型原型项。success handoff durable 后,Runtime 以 session revision/fingerprint CAS 追加 decisionsSummary/prototypeValidationItems/appliedAnswers 并增加 roundsUsed;新 session primary durable 是该轮解释的线性化点,此后才发布原 input observation。handoff 与 current session binding 相等时只重放 handoff;若 current 已是仍保留同题/答案的合法 successor,旧 handoff 不能跨 context 应用,replacement binding 必须以 `supersededCheckpointProviderRequestIds` 传递闭包记录旧 request;最终 appliedAnswers 根据完整 handoff 生成并保存对应 `supersededCheckpointHandoffs` 最小摘要后,旧 handoff 才稳定收口为被替换历史并可清理。长期读取只信 sessionFingerprint 保护的摘要,不要求历史 handoff 文件存在;链缺口、分叉或 identity 不同失败关闭。answerResponseId 只在所属 requestId 域内幂等,不同 request 合法复用同一文本值;下一题/submit 使用新 session 的另一普通 tool-plan 请求。原始回答、选项说明或聊天正文单独都不能猜解释,同 ID 同 checkpoint 只补投影,不同 answer/checkpoint identity 失败关闭。
- 审批与恢复:Runtime 在 GDD 提交前冻结 `approvalRequestId`UI 为一次 GDD 审批决定生成并在传输重试中复用 `gdd-response-<uuid>` responseId;它与现役 user-input answer transport 的同名 responseId 属于不同幂等域,不能跨域比较或恢复。approvalResponseId 只在所属 GDD ref/action 域内幂等,decision audit 使用 `(recordType,gddId,version,responseId)` 复合键,不同版本允许复用同一文本值。审批等待只写 `.agent/planning/pending.json`,不升级或重写 `game-creator-pending-action.v5`;该投影可由唯一待审 GDD 重建,receipt 后 observation 可确定性重建。最新版本已有任一 approve/revise/reject receipt 后才允许下一版本;revise/reject 由原 run 继续,approve 后必须由用户显式开始新 plan continuation,旧批准在新版本 approve 前继续有效。receipt 后固定修复 index/Markdown、`agent-runtime-plan-gdd-decided.v1` 专用幂等 decision audit、原 action terminal observation 和 session;原 run durable 消费 observation 后才清理 planning pending。提交点之后的投影失败仍返回成功 outcome,并以 `recoveryPending=true` 表示派生投影未齐,不回滚、覆盖或重编号不可变事实。
- 安全不变量:plan run 必须通过 durable top-level `project-supervisor + standard + project-supervisor-plan-chat` exact bindingaction tool 仅 `file.read`、`file.list`、`user.input_request`、`plan.submit_gdd`MCP 为空且 `webSearchEnabled=false`Prompt/tool-plan/checkpoint/action batch/batch recovery/repair/context/completion 都跳过 Supervisor collaboration 合同。submit 是 sole action,并通过 main-loop 专用审批等待分支;checkpoint 无 action batch。每个 Provider request 的 effective model、api kind、stream、当前 apiKind 实际生效的 official fallback/Anthropic strict/OpenAI Chat token budget field、输出 token、reasoning/verbosity、tool choice、messages、结构化注入、实际工具目录、requestContextFingerprint 与 durable session binding 必须来自同一 captured session;不适用于当前 apiKind 的 adapter 字段固定为 null,除明确排除的 timeout/retry/backoff/log/URL/header/秘密外,任一实际请求语义变化都产生新 context fingerprint 与 base ID。Provider request ID、handoff ID 和 superseded 控制元数据只进 binding/base identity,禁止进入 messages/tools/structured injection;同 session protocol repair 原样继承 superseded 数组。协议无效且已通过 handoff storage 安全/容量门的 Provider 成功响应仍先持久化 raw response handoff 并补 lifecycle completed;恢复只重放验证并进入同一 repair,不重发原 request,而该 raw handoff 未通过 checkpoint validator,不能追加到 superseded 数组。raw handoff 因超限、敏感键、绝对路径、容量、identity 或 durable write 失败而无法安全提交时,不保存正文、不补 completed、不自动 repair/retry,只写安全诊断并进入 reconciliation。同 session/context 的下一 attempt 也必须先把旧 attempt durable 闭合为已知 failure 的 failed,或在 owner/lease/boot 证明物理请求已终止后闭合为 interrupted;合法 successor 的 replacement 同样只能在 handler 已取得并丢弃旧 response,或证明旧调用终止并回读 interrupted 后启动。任何旧终态回读前都不得新建 started。started 后只有在不存在后续业务消费证明时,合法 session successor 才会 interrupt 旧请求、supersede 尚未执行的 ready batch,或为 checkpoint 使用 `decision-round-{N}-session-{revision}-repair-0` 与新 base attempt 0 自动前滚;同 submission GDD、精确 activeQuestion 和精确 appliedAnswers 是优先于 stale 的三类消费证明,`started + ready batch` 先补真实 completedbinding 损坏则只进入 reconciliation。Runtime 在 session revision 1 固定写入不可由 Provider 改写的 `initial-request`,后续决定和最多 3 个原型项逐项匹配 session 真相。plan 无 command、smoke、preview、canvas、任务图、委派或通用文件写能力;retry 只在旧 run 已终态且无 pending 时保留原 plan source/profile,不能降级为 background。`game/fast_gdd.md` 是 Runtime 内部投影,不推进代码 mutation revision。前端只经 hydrate command 从严格 GDD/receipt/session 推导 `not_started|draft|ready_for_approval|revision_requested|approved|rejected`receipt 隐藏 stale pending,合法投影未齐只返回 `recoveryPending=true`。完整构建仍由用户动作启动,直接开建必须显式声明,不能因 ref 缺失静默降级。
- 影响范围:AI 游戏创作客户端、Project Supervisor、Agent Runtime、Prompt Bundle、本地项目 sidecar、项目开发工作台和后续完整构建准入。
- 验证方式:M0 验证 tracked 技术方案、索引、注册表、golden 指纹、提交/恢复合同和决策记录自包含一致;M1~M3 分别按关联技术方案的阶段门禁执行,不能以文档合入冒充功能完成。
- 关联文档:`docs/technical/【技术方案】立项策划AgentFast GDD-2026-08-10.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`。
## 2026-08-10 资源管理评审阻塞项按第二轮正式合同修复
- 背景:资源管理第一轮实现后,人工验证继续暴露 WebView 默认缩放、预览队列饥饿、过滤后媒体残留播放、外层滚动串 scope、超深依赖坐标越过 Rust 上限和暂时错误无法重试等问题。部分 PRD / 技术方案仍描述第一轮的中央媒体预览、单全局 Overlay 和统一 section scope,已经与第二轮代码及验收结论冲突。
- 决策:以当前代码、2026-08-10 最新资源管理决定和第二轮人工验证为正式合同。`Ctrl/Cmd + wheel` 使用原生 `{ passive: false }` 委托监听;预览固定 `play > detail > visible` 且总队列上限 `96`,主动请求可替换最低优先级预取;终态缓存同时限制为 `48` 项与 `64 MiB`IPC data URL 立即转成可撤销 Blob URL;项目 / mode 使用单调 scope epoch 隔离逻辑队列、缓存与异步回调,但物理读取由 Tauri 进程级唯一 `3` 槽管理器统一限制。每次挂载或 scope 切换生成不复用的 `scopeId`,每次 IPC 读取生成唯一 `requestId`;切换和卸载通过窄取消命令中止旧 scope 的排队 / 分块读取,取消任务不得进入 base64 / Blob 阶段。成功、失败和取消后清理活动 request / scope registry,同时保留有界 tombstoneseen request 上限 `8192`;非活动 cancelled scope 预算 `1024`,活动取消 scope 为防复活可临时钉住,结束后立即重新收敛。搜索隐藏立即暂停媒体;外层滚动按 `projectId + mode`、内层按 `projectId + mode + section` 隔离;前后端坐标共同限制为 `0..=1_000_000`,超深 dependency 只饱和显示列并保留原始深度;失败预览区分 transient / permanent,仅用户意图重试 transient。
- 展示边界:图片、安全 SVG 和视频主体只在资源卡内展示;中央详情以元数据、同类型依赖和版本信息为主,文档正文与按意图读取的音频控件保留。每个分区拥有自己的 SVG plane;单端离屏显示边界继续线,两端离屏隐藏。
- 兼容与边界:不修改 manifest、Rust 资源图、SpacetimeDB、sidecar schema、布局 CAS 或媒体安全读取门禁;不恢复资源卡拖动。过期文档在同一次变更内同步修正,避免实现通过测试但评审继续依据旧合同。
- 验证方式:新增原生 wheel 取消、队列优先级 / 硬上限、Blob / 总字节 LRU、连续媒体浏览、挂起旧 scope / ABA epoch、原生全局活动峰值 `<= 3`、等待与分块取消、取消前无 base64 / Blob、活动 registry 清零及 `8192 / 1024` tombstone 有界淘汰、transient 重试、过滤后暂停、外层滚动 scope、坐标边界及 Rust 安全读取回归;继续运行 AppSurface、纯布局模型、Tauri 定向测试、编码检查和 `git diff --check`。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-10 任务流退出画布连线且 dependency 层间距为精确引用留走线区
- 背景:人工验收确认灰色聚合 task-flow 虚线与橙色精确引用叠加后增加噪音,但用户在画布上只需要判断资源间的真实精确引用。原 dependency 自动布局沿用 type 模式 `16px` 行列间距,目标箭头预留 `10px` 后,相邻卡片间的橙线线身只剩约 `6–14px`;菱形 / 分叉簇又把单节点层与多节点层顶部对齐,形成同组一侧很短、另一侧明显过长。
- 决策:Rust read model、详情数据和布局拓扑继续保留合法 task-flow;前端仍用流节点的 `O(S+T)` 成员关系参与同类型连通簇和中位数排序,但 `ResourceDependencyOverlay`、SVG marker、CSS 虚线和画布 `aria-describedby` 关系说明不再渲染或宣告 task-flow。画布关系层只显示同类型 `asset-reference` 的橙色实线箭头。
- 间距与对齐:dependency 自动布局使用独立的 `48px` 列间距和 `40px` 行间距,type 模式继续使用 `16px`。每个相关簇以最大层行数确定高度;同簇资源较少的层增加稳定的半差偏移,在最大层高度中居中。Rust `dependencyDepth` 仍唯一决定横向层级,稳定 ID 仍决定平局,不压缩深度、不截断真实端点;已有历史手动坐标原样保留并继续优先占位。
- 边界:本次只调整前端可派生自动坐标与显示关系集合,不修改 manifest、Rust resource graph、SpacetimeDB、sidecar schema、CAS、scope epoch、单写者 FIFO 或搜索不重排合同,不恢复资源卡拖动。自动 dependency 坐标会通过现有协调流程按新间距重派生;type 坐标不变。
- 验证方式:布局纯模型覆盖 dependency / type 间距隔离、菱形窄层居中、手动坐标避让、确定性和 4096 项性能;Overlay / AppSurface 覆盖 task-flow 零 path / marker / 关系说明、橙色箭头最小可辨认走线区、跨分区、自环、滚动 / 缩放绑定和完整前端回归。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-10 依赖 SVG 改为分区 plane 所有并使用逻辑坐标路由
- 背景:全局 SVG 与四个独立 section plane 是不同滚动 / transform 坐标系。旧实现通过 `getBoundingClientRect + requestAnimationFrame + React state` 把卡片屏幕 rect 反投影到全局图层;触摸板缩放或连续滚动时,浏览器先在合成层移动卡片,异步测量后的线才追赶,因此会出现线与卡片分离、同一关系忽隐忽现。四 viewport 的并集 clipPath 还允许某区线段泄漏到另一分区。大贝塞尔控制柄进一步放大了视觉绕行。
- 决策:固定四区各自把 `ResourceDependencyOverlay` 挂在拥有卡片的 `.game-resource-plane` 内。SVG 与卡片直接消费同一 sidecar 逻辑坐标、`180×128` 卡片常量、CSS scale 和 viewport scroll;浏览器原生 transform / overflow 负责机械同步和分区裁剪,主路径不再依赖 DOM 屏幕坐标测量。每区只用一个 `ResizeObserver` 与 RAF 维护逻辑 viewport,四区合计最多四个;一端离屏时对称输出 incoming / outgoing 边界继续线,两端离屏时隐藏。
- 路由:精确引用在同行 / 同列时使用直线,需要转向时使用正交线段与最大 `10px` 二次曲线小圆角;仅自引用保留卡片外侧贝塞尔闭环。同侧稳定端口和同类型过滤不变。该条最初保留的灰色聚合 task-flow 虚线已被上方“任务流退出画布连线”决定替代,task-flow 只保留布局超边语义。
- 边界:只改变前端派生 SVG 的所有权、显示裁剪和路径形状;Rust resource graph、`dependencyDepths`、producer 截断降级、manifest、SpacetimeDB、dependency / type sidecar、CAS、scope epoch、单写者 FIFO、搜索不重排和历史手动坐标均不变。没有恢复卡片拖动。
- 验证方式:SVG 定向回归覆盖直线 / 小圆角、橙色 marker、自环、同侧端口、task-flow 零渲染、缩放同 plane、incoming / outgoing、两端离屏、四区互不串线及单区滚动隔离;ProjectDevelopment / AppSurface 继续覆盖模式和项目销毁、媒体卡、分区缩放与页面骨架。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-10 资源画布只展示同类型关系且详情以信息为主
- 背景:人工验收发现跨分类精确引用在四个独立 viewport 中只能显示成断裂长线;同一美术卡多入 / 多出时,多条精确引用与聚合任务流复用卡片中心端口,线段重叠;点击美术卡后大图占满中央视窗,路径、来源和依赖信息被挤到首屏之外。
- 决策:Rust 资源图、`dependencyDepths`、manifest 引用和 producer 降级语义保持不变;前端布局拓扑、无障碍关系说明与 SVG 只消费两端同属 `document / version / art / audio` 之一的关系。跨分类 reference edge 与跨分类-only task-flow 不形成布局簇、边界偏置或连线。相同分区内,同一卡片同侧的精确边按对端坐标与稳定边 ID 有界分配独立端口;横向层级仍优先左右连接,同列或空间不足才上下连接。源端可见而目标离屏时保留关系 DOM,但画面只显示边界短继续箭头。该条中的 task-flow 显示口径已由上方最新决定替代为仅参与布局。
- 详情合同:点击资源卡后,元数据、Rust 权威依赖层级、同类型精确上下游、同类型任务流和版本信息位于内容首部;美术图片 / 视频不在详情重复读取或放大,仍在列表卡本体中展示。文档正文和按播放意图读取的音频控制位于元数据之后。
- 兼容与复杂度:不修改 Rust read model、manifest、SpacetimeDB 或 sidecar schema;历史手动坐标、type 布局、搜索不重排、分区高度 / 倍率及单写者 FIFO 不变。过滤与端口分配为线性收集加稳定排序,task-flow 继续按超边成员处理,不构造资源笛卡尔积。
- 验证方式:覆盖跨分类精确引用不渲染 / 不聚类、跨分类-only task-flow 不聚类、全部 task-flow 不绘线、同侧多边端口确定性、离屏长线收敛、详情无大图 / 视频且元数据与依赖字段可见,并继续运行 4096 布局、媒体卡、分区缩放与 AppSurface 回归。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-10 分区内容缩放与持续关系线替代阶段二旧显示口径
- 背景:阶段二最初把“缩放”只解释为分区可视高度,并让 SVG 只为完整位于 viewport 的卡片建立端点。人工验收发现触摸板无法缩放内容、精确引用箭头在裁剪边界不稳定、纵向长线容易被截断,以及同一关系随分区滚动时有时无。
- 决策:保留已有分区高度,同时新增互不替代的 `50%..200%` 内容倍率;按钮、触摸板 `Ctrl/Cmd + wheel` 和 WebKit 捏合共用确定性 clamp,普通双指仍滚动。高度与倍率都按 `projectId + mode + section` 留在会话态,不进入 sidecar。SVG 与卡片共用分区 plane 和 CSS scale;合法关系不再因端点没有“完整可见”而卸载,一端离屏时在可见区边界显示同语义继续箭头。精确引用恢复橙色实线实心箭头,不同层优先左右端口横连,只有同列或横向间距不足时才上下端口纵连;自环继续在卡片外侧。
- 替代关系:本条替代下方 2026-08-07 阶段二中“缩放只等于可视高度”和“只为完整可见卡片建立端点”的显示口径;其中曾采用的全局 SVG、四 viewport 联合 clip 和单全局 Observer 又由上方“依赖 SVG 改为分区 plane 所有”决定替代。高度模型、四分区、会话隔离、内外滚动和无布局 CAS 等其它决定继续有效。阶段一媒体卡、阶段三确定性聚类、Rust `dependencyDepths`、producer 截断降级、历史手动坐标与 type sidecar 均不变。
- 验证方式:纯倍率模型覆盖按钮 / wheel 边界;AppSurface 覆盖项目、mode、section 隔离、普通 wheel、Ctrl wheel、WebKit gesture 与零布局写入;SVG 覆盖同 plane 倍率、橙色 marker、直线 / 小圆角横纵路由、自环间隙、双向边界继续线、分区原生裁剪、每区单 Observer、task-flow 零渲染和 4096 精确关系有界输出。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-11 Tauri 无限画布以可证明事务和分类恢复收口
- 终态与队列:资源编辑账本正式区分可继续阶段、`reconciliation-required`、`remote-failed` 和 `archived`。远端明确失败只保存稳定分类与终态时间,不得再 POST、轮询或重新扣费;只有该终态能由用户显式归档并移出活动恢复队列,归档保留账本且不伪装 `committed`。结果未知和需对账项继续失败关闭。
- 本地提交:派生 asset 提交新增 `game-creator-resource-edit-asset-transaction.v1` durable journal,冻结 operation/project/source、最终路径/摘要、manifest before/after 和 project revision before/after,以 `prepared -> media-installed -> manifest-written -> revision-written -> committed` 推进。只有文件、manifest、revision 与 journal 全部回读相等才提交 ledgermanifest after/revision before 只前向补 revision,无法证明的组合进入对账,不做猜测回滚。durable committed 后遗留 staging 只有在 staging 与正式媒体摘要一致、manifest 按 asset ID 或路径唯一命中且精确等于 journal asset 时才尽力清理;删除 I/O 失败不改变 committed,身份或媒体漂移则保留 staging,并把 journal 与 ledger 转入对账。
- 版本提交:派生子版本 journal 冻结 project revision before/after 摘要和目标 after 记录。manifest 已有目标子版本但 journal 缺失时失败关闭;旧 journal 缺少 revision 身份时,只有当前 revision 仍为 base 才允许补齐身份,revision 已推进且无法证明由同一事务写入时必须进入 `reconciliation-required`;缺少上述 journal 证明时,不得仅以子版本存在或 revision 数值已到达推断 committed。
- 服务身份:普通模式新生成账本写入 `official-platform-v1 + 固定官方 origin + ownerUserId`,高级 External v1 模式写入显式 service originAccess Token 与 Developer API Key 都只负责授权而不拥有 operation。两种模式的已受理任务只恢复原 GET,prepared 任务只精确重放冻结 POST;普通模式 owner 不匹配时零网络,高级模式 origin 不匹配时失败关闭。
- 前端一致性:generation progress、保存队列、生成/提交回包和延迟草稿读取共用单调 revision 门禁;当前 scope 低 revision 不得回退已落地草稿,同 revision 只接受完整相等的幂等回包。Tauri 指针与键盘 Shift 选择共用 `resolveLayerPointerSelection`;移动、缩放和平移只在首次真实变化时 capture history,零位移不生成 undo、documentVersion 或保存。
- 交互恢复:工作台用独立 modal 展示全部后端权威 operation,允许选择任意可恢复项;`remote-failed` 只提供归档,`reconciliation-required` 只读展示。读取失败显式重试,操作后重读后端,项目切换后丢弃迟到结果。`canvas.failed` 统一携带 `generation / draft-save / asset-commit / recovery / cancellation` 五类 operation,只有生成失败显示“返回修改/重新确认”。
- 产品语义:当前仍禁用“新增资源”,只对现有资源做非破坏性派生编辑;新结果追加为新文件、asset 或子版本,源资源、源文件和原版本保留不变。
## 2026-08-10 Tauri 客户端远端资源编辑统一复用平台生成链路
- 凭据与配置:普通客户端的 Access Token 只通过专用 session install/clear 协议进入 GUI/Runner 内存,普通生成/恢复命令参数和运行时配置页不携带或展示 Token、URL、Developer Key。高级模式使用隔离 AppData 的 `editorApi.baseUrl/apiKey`,不与普通 release 自动共享。
- 路由边界:普通图片编辑、视频、音效和 BGM 使用对应 `/api/editor/*`,上传/确认/换签使用 `/api/assets/*`,状态查询使用 `/api/runtime/external-generation/jobs/*`;高级模式使用对应 External v1 路由。SVG、UTF-8 文档、代码、Agent 回执与项目版本仍是本地派生。
- 可靠性边界:两种模式都携带原稳定 `Idempotency-Key`;普通站内响应兼容 `200 + queueState.operationId` 与 inline 完成,高级 External v1 固定 `202 + operationId`。`prepared` 只重放原正文和原键,`accepted/running` 只查询原 operation。账本绑定调用模式、服务 origin 和普通模式 ownerUserId,不绑定 Token/Key;换号后旧 owner 账本零网络、零安装。
- 关联:`apps/ai-game-creator-shell/src/features/asset-canvas/tauriImageCanvasHostAdapter.ts`、`apps/ai-game-creator-shell/src/view/project-development/resourceEditModel.ts`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs`、`apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs`。
## 2026-08-10 客户端参考媒体直传复用已授权私有前缀
- 根因:客户端素材画布和全类型资源编辑把内部用途目录 `asset-canvas-references`、`resource-editor-references` 直接作为 `legacyPrefix`api-server 只接受 `platform-oss` 权威白名单,因此请求在票据阶段返回 `400`,OSS 上传、对象确认、生成提交和扣费都没有发生。
- 存储决策:继续复用合法私有前缀 `generated-character-drafts`,不扩大 legacy 白名单、不增加平行上传接口。图片画布使用 `editor/asset-canvas-references/<projectId>/<draftId>/<generationId>`,全类型资源编辑使用 `editor/resource-editor-references/<projectId>/<operationId>` 作为 `pathSegments`;生成请求只消费 confirm 后的稳定 `objectKey`。
- 错误边界:图片参考资源准备分别投影 `reference-material-invalid`、`reference-ticket-failed`、`reference-object-upload-failed`、`reference-confirm-failed``401/403` 继续收敛为 `authentication-required`。这些错误只返回安全阶段,不暴露 ticket host、formFields、policy、signature、Token、API Key 或 Provider 内部正文;取得稳定对象前必须保持 `operationId` 和生成 request body 为空。
- 验证:客户端端到端夹具锁定 ticket → OSS form POST → object confirm → image edit POST 顺序,并断言源图片文件和 manifest asset 保留、派生图片追加;失败夹具锁定票据失败后零生成 POST。api-server 契约测试锁定精确前缀、目录和私有对象 key。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs`、`apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs`、`server-rs/crates/api-server/src/assets.rs`、`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`。
## 2026-08-10 客户端现有资源编辑扩展到全部现役类型
- 产品入口:继续禁用“新增资源”,资源聚焦态的 manifest asset、已完成任务产物、上传附件、Agent 文本回执和项目版本统一显示“编辑资源”。静态图片复用 refine 图片画布,其他类型进入同一资源编辑壳,不建立平行资源总览。
- 非破坏性边界:图片、SVG、视频、音频、文档和代码生成新的本地文件与 manifest asset;Agent 原回执保持不变,派生文档引用回执身份;版本只追加继承资源绑定的子版本。任何路径都不得覆盖、删除或重排源记录。
- 能力分流:SVG/文本/代码走结构化 LLM 内容派生与格式复核;视频使用源视频稳定引用;音效/BGM 因现役接口无源音频字段,固定定义为基于源语义的派生重制,不能宣称波形级编辑;项目版本追加子版本。
- 信任边界:前端能力提示不是授权事实,Tauri 在提交前按 manifest、已完成任务或上传登记重新核验来源并复核项目 revision / 源内容摘要。远端生成继续使用稳定幂等身份、`202` 轮询与稳定 object/resource/asset 身份,签名 URL 不落 manifest。
- 队列与结果边界:Tauri 媒体派生统一经 External v1 进入现役生成队列;图片 refine、视频、音效和 BGM 复用同一逻辑请求的稳定 `Idempotency-Key`,路由不得丢弃。完成结果只返回裁剪后的稳定 object/resource/asset 引用与必要媒体元数据,不暴露 provider、worker、队列内部字段或临时签名 URL。
- 验证边界:资源投影回归必须把已完成任务在 `artifacts` 中明确登记的音频归入音乐音效资源,同时继续排除未登记音频和未知二进制;画布工具栏数量断言必须与全部现役工具清单同步;文本预览回归必须覆盖 HTML 与现役代码扩展名;委派回执测试应在显式进入等待态前完成同 action 幂等断言,避免后台 parent-wake 与断言竞争。
- 关联:`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`apps/ai-game-creator-shell/src/view/project-development/resourceEditModel.ts`、`apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs`。
## 2026-08-10 客户端素材画布收敛为现有图片非破坏性编辑
- 产品决策:资源总览“新增资源”禁用,普通用户只从现有图片资源进入“编辑资源”。
- 编辑语义:refine 自动绑定源图片,Tauri 通过现役 `/api/external/v1/editor/images/edits` 派生新结果;结果创建独立 asset 和文件,源 asset/文件保持不变,新资源以 `referenceResourceIds` 登记源资源血缘。
- 可靠性决策:公开生成状态每次落盘推进草稿 revision,并同步私有账本、进度事件、staging 和 commit;已受理或结果未知的 operation 遇到鉴权失效时保留原 operation。Developer API Key 轮换不创建替代任务;升级前 Key-bound 指纹无法直接验证时通过显式服务 origin 确认后继续原 operation。
- 交互决策:dirty 返回必须通过独立确认面板选择保留或放弃,默认保留并先 flush;图层选择和缩放同时提供指针与键盘路径,禁止嵌套交互元素。
- 关联:`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx`、`apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs`。
## 2026-08-06 Game Agent 画布视觉以主站 token 与共享 chrome 为唯一来源
- 背景:网站与 Tauri 已经共同消费 `image-canvas-core/react` 的 viewport、selection、renderer 和 history,但客户端素材画布仍维护独立的文字工具栏、按钮和状态外观,`1280×800` 下会出现动作逐字换行、主次不清和工作台四区视觉漂移。直接复制主站 `src/components/image-editor` 或整包 `src/index.css` 会重新形成宿主分叉和隐式全局依赖。
- 决策:保留 Game Agent 左导航、中央主视窗、右侧 Supervisor 和底部 Agent Dock 四区布局;平台颜色、边框、文本和 surface 继续以 `packages/shared/src/theme.css` 为事实源,画布动作按钮、工具栏、分组与分隔符进入现有 `@genarrative/image-canvas-react`。主站和 Tauri 均直接消费同一 chrome,Tauri 宿主只保留业务接线和布局适配。
- 依赖边界:共享组件只接受 React props、图标节点、短文案和事件,不读取账号、钱包、项目、生成、Tauri 或 HTTP。主站 `EditorIconButton` 只保留 Lucide/特殊浮层薄适配,主站业务 CSS 只保留定位和专属状态;Tauri 禁止导入网站完整 CSS。内部错误码、开发者 API 配置和本机绝对路径不作为普通用户默认视觉内容。
- 分期:阶段一、二更新权威合同、抽取共享 chrome 并让主站消费;阶段三、四已统一中央素材画布、Supervisor 与 Agent Dock;阶段五在同一变更中补齐测试、真实视口测量和发现回归修复。
- 保存字段:`name` 属于用户输出命名,继续可编辑;`assetKind` 属于机器 subtypecreate 只允许 Runtime 权威目录中的 `game-art / icon-spec / ui-prototype / art-spritesheet` 并显示中文用途,refine 继承源 kind 且锁定。未知历史 kind 只按原值保存,不在精修中迁移或改写。工具与保存设置显式分为两行,主保存动作不可压缩。
- 路径展示:业务内部继续持有和校验绝对项目路径,普通工作区状态只投影项目名称;显式目录选择、非空目录确认和开发诊断仍可按权限边界使用真实路径。该变化不修改项目身份或 Tauri command 参数。
- 验证:共享 chrome 与 Tauri Surface 定向测试 `20/20` 通过,覆盖可访问性、pressed/expanded/disabled、工具组/分隔符、生成/失败/保存/取消和非裁剪 CSSAppSurface 横屏合同 `1/1` 通过,锁定 Supervisor composer、Agent Dock 与 `100dvh` 两行工作台。客户端/全仓 typecheck、定向 ESLint、编码和 diff 检查通过。完整 AppSurface 本次运行是 `352/354` 通过:preview 权限用例单跑通过,属于全量异步顺序波动;仍失败的“画板 API Key”运行配置用例不涉及本次 diff,留在原业务范围处理。应用内浏览器 `1280×800` 实测 window/document/body client 与 scroll 均为 `1280×800`;真实入口没有登录会话时保持登录门禁,不增加测试绕过。
- 关联:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`packages/image-canvas-react/`。
## 2026-08-05 无限画布阶段三以 Tauri 持久草稿和项目 mutation 事务形成独立图片闭环
- 决策:阶段三新增独立 `AssetCanvasSurface` 和 Tauri Host Adapter,直接消费共享 core/React/ports;前端状态固定为 `canvas.creating / editing / saving / recovering / failed`generation 明确使用 unsupported mock。本阶段不接资源总览入口、保存后自动选中或真实 AI,避免提前修改工作台状态合同。
- 草稿边界:`.agent/workbench/asset-canvas/` 使用持久 OS 文件锁和 draft revision CAS,不使用 React state、localStorage 或进程内 mutex 作为正式状态。主草稿损坏时,固定 recovery JSON 必须通过针对实际字节的 SHA-256、schema/身份和全部受控媒体复核才可恢复;连续保存产生的 revision 直接从 CAS 结果推进。
- 提交边界:正式提交复用项目 mutation 写锁,锁内按身份/revision 重检、prepared journal/ledger、最终图片安装、manifest、project revision、回读验证、ledger/草稿终态推进。`commitId/idempotencyKey` 与完整请求指纹绑定;同请求重放返回同一 asset/event,同 revision 竞争不得覆盖。释放锁后才发布至少一次 Tauri eventemit 失败不回滚已提交 manifest。
- 恢复边界:最终文件分为 absent、matches、mismatch。只有 absent 或摘要/大小/尺寸完全匹配时可自动回滚;mismatch 保留现场并标记 `reconciliation-required`。manifest after/revision before 前向补 revision,二者 after 时补 ledger、草稿和事件。只能删除能证明属于当前事务的新文件。
- 精修与异步边界:refine 源必须是 manifest 唯一登记且通过签名、完整解码、容量、普通文件、符号链接/硬链接门禁的图片;源 asset/文件永久保留,新 `canvas-<commitId>` 通过规范 resourceId 引用源。前端以 project/draft/session epoch 丢弃迟到 Promise/事件,以 eventId 去重 command/event 投影。
- 验证:阶段三 Surface `8/8`、共享 core/React `7/7`、AppSurface `351/351`、Rust 素材画布定向测试 `11/11`,并执行客户端 typecheck、Rust check、编码与 diff 门禁。
- 关联:`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-05 无限画布阶段二以共享包成为网站与 Tauri 唯一核心源码
- 决策:把现役网站画布的纯交互模型、history/viewport/stage hooks 和通用 React 容器迁到 `packages/image-canvas-core`、`packages/image-canvas-react`;原 `src/components/image-editor` 同名模型/hook 只保留 re-export。网站 Stage/World/Dock/Portal 已直接使用共享 React,Tauri 壳只建立共享源码编译入口和作用域 CSS,不在本阶段接素材创作业务闭环或修改资源总览。
- 依赖边界:core 只含纯 TypeScriptReact 包只依赖 core、React/ReactDOM 和注入端口。project、asset、generation、completion 四组 ports 不知道 auth、wallet、payment、routing、`editorProjectClient` 或 Tauri `invoke/listen`。网站账号、计费、云端项目、素材库、上传、生成、媒体换签与业务面板继续留在网站 adapter/renderer 边界。
- 宿主构建:根站与 AI 游戏创作壳分别 alias 同一 `packages/` 源码,Vite 去重 React/ReactDOMTauri dev server 显式允许 repo root。共享 CSS 只使用 `.genarrative-image-canvas` 作用域,Tauri 不导入网站 `src/index.css`。
- 实例隔离:小地图优先在当前 viewport 内解析,多实例时禁止全局取第一个;wheel、ResizeObserver、animation frame 与 portal 均按实例挂载清理。重复 mount/unmount 和双画布隔离由共享 React 测试锁定。
- 当前未解耦:网站 `ImageCanvasEditorView` 仍负责完整业务编排,媒体换签、视频/音频/序列帧 renderer、Agent 与生成表单未下沉;旧 `ImageCanvasEditorTypes` 和 `ImageCanvasEditorModel` 仍含网站扩展类型与序列化/素材规则。后续接 Tauri 时应通过 ports 和 renderer 插槽继续缩小这些边界,禁止把该目录整体搬入客户端。
- 验证:共享 core/React 7 项测试与现役 ImageCanvas 74 个文件、892 项测试通过;网站和 AI 游戏创作壳 typecheck 通过。构建、编码和 diff 门禁以本次任务最终记录为准。
- 关联:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`。
## 2026-08-05 素材创作无限画布共享源码并以本地事务形成图片闭环
- 背景:资源管理阶段七已经完成 manifest 实时投影、dependency/type 布局、依赖图和中央只读聚焦,但“新增资源/精修资源”仍缺少草稿、生成、正式回写、血缘、布局和焦点竞态的完整合同。网站已有成熟图片画布,若直接复制进 Tauri 会形成两份长期分叉的画布内核和通用 UI。
- 产品决策:项目工作台中央主视窗把 `resource-overview` 与 `asset-canvas(create|refine)` 建模为两个状态。“新增资源”和“精修资源”进入素材画布,资源总览卡片继续不可拖动,素材画布图片图层必须支持平移/缩放、选择/移动/缩放、撤销重做、导入、基础编辑、生成、导出和本地回写。首版只正式闭环图片;高级抠图、图集、角色动画、视频和音频编辑后续分期。
- 架构决策:现役网站画布抽取到 `packages/image-canvas-core` 与 `packages/image-canvas-react`,网站和 Tauri 实际 import 同一源码,通过 Web/Tauri Host Port 注入差异。Web 保留账户、钱包、服务端 editor project 和云端素材库;Tauri 保留本地项目、受控文件、manifest、项目 revision 和 External Editor API。禁止复制整个 `src/components/image-editor/` 到客户端。
- 持久化决策:Tauri 草稿使用 `.agent/workbench/asset-canvas/` 下的 `game-creator-asset-canvas-draft.v1`,以 `expectedProjectId + expectedDraftRevision` 在 OS 句柄锁内 CASJSON 最大 `2 MiB`、最多 `4096` 层,媒体只保存受控引用。正式 `commit_local_project_asset` 同时绑定项目 `expectedRevision`、草稿 revision、`commitId/idempotencyKey` 和 staging 摘要,按 prepared journal、最终图片、manifest/revision 可恢复更新、回读、ledger/草稿、最后 Tauri event 推进;事件 `game-creator-local-asset-committed` 至少一次并按固定 eventId 去重。
- 血缘与并发:refine 永远保留源文件/asset 并创建 `canvas-<commitId>`。源没有外部 resourceId 时补齐 `local-asset:<manifestAssetId>`,新资产只通过规范 `referenceResourceIds` 引用,禁止混用裸 manifest asset ID。两个窗口基于同一 project/draft revision 最多一个成功;相同提交返回 already-committed,同键不同请求失败关闭。恢复只依据 journal、before/after 摘要和 ledger,不按文件存在、mtime 或 PID 猜测。
- 投影与焦点:提交返回完整当前 manifest;同项目仍活动时立即更新 manifest 投影、依赖图输入和两种布局,不要求刷新或重开。自动选中还必须复核 project/path、asset-canvas session/draft/intent、selection epoch 和 search/filter epoch;用户已切项目、切模式、选其它资源或新资源被条件隐藏时不得抢焦点,隐藏时保留条件并提供显式清除/定位动作。
- 影响范围:下一阶段的共享画布包、网站 adapter、`apps/ai-game-creator-shell` 前端与 Tauri Rust 本地持久化;不修改 SpacetimeDB schema,不把草稿或资源布局 sidecar 变成 manifest 业务真相。
- 验证方式:按权威专题的 28 项矩阵覆盖 Web/Tauri 共用源码、新增/精修、生成响应丢失、重复提交、两窗口并发、事务各崩溃点、草稿恢复、切项目/切状态/改选择/改筛选迟到结果和不刷新即时投影。
- 关联文档:`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
- 验证方式:覆盖路由后只启动 `code-prototype`、既有素材零美术委派、精确缺口才允许单个对应 child、child 的 `game/**`、memory 和 manifest 写入拒绝而 `assets/**` 写入允许、回执恢复同一主 Run,以及主 Agent 的接入、静态 smoke 与双视口试玩;追加真实 scheduler 对旧 canonical task 的同 Run 恢复、旧固定美术 child 及其历史 isolated 后代对 Canvas/memory/manifest/project scope 零 mutation、伪只读 `agent.run_status` 阻断、嵌套美术 child 硬截止对账回执;另跑完整 DAG 非回归。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-07 资源管理阶段三以确定性分层聚类收口
- 背景:阶段一已经统一 `180×128` 资源卡本体与媒体播放,阶段二已经把四个固定资源分区改为独立可视高度和内部滚动;原 dependency 自动布局仍只按 Rust `dependencyDepth` 横向分层、同层按名称排布,容易让已有合法关系形成长线和交叉。
- 决策:`dependencyDepths` 继续是唯一横向层级真相,前端只消费现有 Rust resource graph 的合法 `asset-reference` 与未截断 producer mapping 下的聚合 `task-flow`。每个固定分区迭代建立弱连通分量,task-flow 以流节点连接成员而非展开资源笛卡尔积;相关簇按最小深度、最小稳定资源 ID 排列,孤立资源统一紧凑置后。簇内固定执行两轮左右中位数扫描,精确引用使用上下游相邻层 rank,task-flow 使用另一端成员的中位 rank,平局回退稳定资源 ID。原“跨分区边界偏好”已由 2026-08-10 同类型关系决定替代,跨分类关系不再进入前端布局。搜索仍只过滤可见卡片 / SVG,不参与布局;环继续依赖 Rust SCC 深度,成员可连续排列、环后资源继续前进。
- 兼容与复杂度:只重派生 `manuallyPlaced=false` 的 dependency 坐标,历史手动位置原样保留且先占用避让;type 坐标不改。资源协调签名加入由稳定 ID 的规范端点 / task-flow 成员序列导出的固定大小摘要,因此邻接改变即使深度不变也会写入同一 sidecar FIFO / CAS,不增 schema、manifest、SpacetimeDB 或第二存储。遍历为迭代式 `O(V + E)`,排序扫描轮数固定,流成员不构成全量配对。
- 三阶段状态:阶段一资源卡本体与单媒体播放、阶段二分区可视高度 / 内外滚动和阶段三依赖聚类均已实施;它们共用既有卡片尺寸、SVG 几何、sidecar、scope epoch 与单写者 FIFO,均不恢复卡片拖动。
- 验证方式:布局模型与 Hook 覆盖同区关系簇、独立簇与孤立项、多入多出 / 聚合流、环 / 自环、跨区、不变输入确定性、手动坐标避让、type 不变、邻接签名更新与 4096 项性能;依赖图 / SVG、ProjectDevelopment / AppSurface 和阶段一 / 二回归继续作为整体验收。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-07 资源管理阶段二以分区可视高度完成独立缩放
- 背景:阶段一已把 `180×128` 资源卡本体、预览调度与单媒体播放统一到 dependency / type 两种布局。“分区缩放”旧文档一度定义为卡片 `50%..200%`,会破坏本体卡尺寸、媒体比例与依赖端点。
- 决策:阶段二固定为调整四个资源分区的可视高度,而非任何卡片、媒体、文字或浏览器缩放。会话内存 Map 以 `projectId + mode + section` 隔离状态,不写 localStorage / sessionStorage、manifest 或 `game-creator-resource-layout.v1`,也不发布局 CAS。上限为中央资源画布的当前可用高度,窗口变小时对当前项目两种 mode 的既有值同步且永久夹取;不在放大窗口时回弹旧超界值。
- 布局与图层:分区是正常文档流 grid item,标题与内部 viewport 分离;仅 viewport 内滚动超出内容,外层画布仍在分区之间滚动。本条原先采用的单全局 SVG、单 Observer、完整可见端点和四 viewport 合集裁线已被 2026-08-10 的分区 plane 自有 SVG 决定替代;分区正常文档流和内外滚动合同继续有效。
- 验证:纯高度模型回归锁定分区键隔离、最小一排卡片与中央画布上限;AppSurface 覆盖四区独立、两 mode / 两项目隔离、详情与内外滚动恢复、resize 夹取与零 CASSVG 回归覆盖单 observer、RAF 合帧、viewport 裁线与卸载清理。
## 2026-08-07 资源管理三项改造串行落地且先完成资源卡本体化
- 背景:固定四区资源投影、dependency / type 布局、依赖 SVG、中央详情和受控文档 / 媒体读取已落地,但列表卡片仍只显示图标、名称、来源与路径,不利于直接辨认资源主体。后续还需要分区缩放和依赖布局聚类,三项不能各自发明尺寸、几何或资源真相。
- 决策:按“本体卡 -> 分区缩放 -> 依赖聚类”串行实施。阶段一让两种布局共用同一个卡片与画布级预览 Controller,用现有受控 Tauri 命令按可见性有界读取图片、视频首帧和文档摘要,音频只在用户播放后读取;卡上详细文本后置到中央详情,详情与播放使用合法同级按钮。同时播放上限为一,项目 / mode / 详情 / 运行视图 / 资源身份变化时统一收口媒体状态。
- 后续约束:阶段二只调整分区 viewport 高度,保持卡片 rect 和逻辑坐标不变;SVG 对每个 viewport 做可见端点与裁线。阶段三的依赖聚类只从当前 Rust 只读图按分区派生稳定弱连通组,搜索不参与坐标派生。两者都不修改 manifest、SpacetimeDB 或 `game-creator-resource-layout.v1`,不恢复资源卡拖动。
- 验证方式:AppSurface 覆盖两种布局的本体卡、图片 / 视频 / 音频 / 文档 / 版本、合法点击与键盘语义、单媒体播放、可见性懒加载、去重 / 并发 / LRU、迟到丢弃、失败占位、隐藏字段搜索和详情信息保留;布局纯模型与 SVG 回归继续锁定同一卡片尺寸。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-03 资源管理阶段七以完整 CI 与可重复界面合同收口
- 背景:飞书资源管理需求的阶段零至阶段六已经分别完成资源卡禁拖、固定资源投影、中央聚焦、安全文档 / 媒体预览、依赖深度与正式版本只读模型;最后需要统一复核需求边界并用当前主分支完整门禁排除集成回归。
- 决策:阶段七不新增平行功能,只补齐视频原生控件和资源读取策略失败空态的 AppSurface 证据;美术编辑继续等待画板回写、血缘登记、新资源自动选中与邻近布局闭环。
- 影响范围:`apps/ai-game-creator-shell` 资源管理测试、跨平台 CI / 运维测试脚本、工作台 PRD、AI 游戏创作实施计划和共享项目记忆;不修改 SpacetimeDB schema、manifest 业务合同或资源布局 sidecar。
- 验证方式:根目录全量 Vitest、lint、build、Rust workspace test / check、schema、原生壳、内容 / 编码和生产运维门禁均已完成。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-08-03 正式项目版本阶段六落在 manifest 追加不可变记录
- 背景:阶段一至五已经完成资源卡禁拖、固定分类投影、中央聚焦、依赖关系图和引用深度排列,但“项目版本”仍只能接受未接线的前端 read modelcheckpoint、布局 sidecar 和项目 mutation revision 都不能代表正式可追溯版本。
- 决策:本地 `.agent/manifest.json` 新增可选 `versions` 数组,旧项目缺失时只读为空。版本父子图使用父先于子的追加序列,Rust 在读写边界验证完整合同,并在覆盖 manifest 前要求已有磁盘版本是新版本数组的相等前缀,以此禁止修改、删除和重排。前端只从 manifest 投影版本卡;绑定 `resourceId` 固定解释为 manifest asset ID,点击版本在两种布局中高亮仍存在的资产卡。
- 边界:阶段六只实现版本卡、父子关系、聚焦详情、引用资源高亮和不可变存储门禁;不自动回填版本,不创建下一版本,不做资源替换、运行版本切换、回滚、测试切片或运行态消费。SpacetimeDB、checkpoint、project revision 与布局 sidecar 均不改变。
- 验证方式:共享 Rust / TypeScript 契约测试覆盖 camelCase 与缺省兼容;Tauri manifest 测试覆盖合法追加和历史修改拒绝;前端资源投影与 AppSurface 覆盖版本卡、父子详情、缺失历史资产和 dependency / type 绑定高亮,并运行 shell typecheck、编码检查与 `git diff --check`。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-03 资源依赖阶段五以引用 SCC 深度驱动自动排列
- 背景:资源关系图已经能展示精确引用与聚合任务流,但 dependency 自动布局只消费可信 producer 对应的任务 DAG 深度;同一任务生成的派生资源、没有 producer 审计的 manifest 资源和资源引用环均无法稳定体现“被引用资源在前、引用资源在后”的顺序。
- 决策:`read_local_project_resource_graph` 把 producer assignment 与布局深度拆成两个只读字段。Rust 先压缩完整任务图并把可信 producer 的任务深度作为资源下限,再对精确资源引用图做迭代式 SCC 压缩和确定性最长层级传播;同一引用环共享深度,环后资源递增一层,没有引用关系的资源保持默认深度 `0`。前端只校验并消费 `dependencyDepths`,沿用现有自动位置协调和 SVG 几何,不自行推导关系。
- 边界:不修改 manifest、layout sidecar、External Editor API、api-server 或 SpacetimeDB;不恢复资源卡拖动。已有 `manuallyPlaced=true` 坐标继续保留,只有可派生自动坐标会按新深度重算;任务流继续按任务对聚合,不展开资源笛卡尔积。
- 验证方式:Rust 定向测试覆盖无 producer 的精确引用、任务深度下限、资源引用环 SCC、环后资源与 4096 任务链;前端 DTO、布局 Hook、纯布局和 SVG 测试覆盖独立深度字段、自动重排与手动坐标保留,并运行 shell typecheck、编码检查和 `git diff --check`。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-03 资源聚焦阶段四采用只读受控文档与媒体链路
- 背景:阶段一至三已经完成资源卡禁拖、固定四类资源投影与中央聚焦容器,但只有 PNG / JPEG / WEBP 和合法 Agent 文本回执具备真实主体预览;本地文档、SVG / 视频与音频仍只有路径和元数据。飞书需求同时把“编辑并生成新资源”写为条件项,而当前仓库尚未具备从画板返回后的血缘登记与自动选中闭环。
- 决策:本地文档和媒体统一通过 Tauri 只读命令消费当前 manifest / 已完成任务登记范围,执行 `file.read` auto 权限、路径边界、普通文件、链接、大小、读取漂移与文件身份复核;文本限白名单格式和 UTF-8,Markdown 不执行 HTML、不加载远程图片、不提供活动外链;媒体按文件签名校验,SVG 拒绝活动内容与外部引用,音视频使用 WebView 原生控件。Agent 文本回执继续直接使用对话投影。当前不增加半成品美术编辑按钮,必须等画板回写、保留原资源、`referenceResourceIds`、新资源自动选中和邻近自动布局可一次闭环时再开放。
- 边界:不修改 manifest、SpacetimeDB、资源投影身份、布局 sidecar、资源卡拖动或 External Editor API;不提供资源聚焦工具栏 / 工具侧边栏,不做音频编辑 / 替换或美术资源重生成。
- 验证方式:Rust 单测与命令测试覆盖 UTF-8 / 扩展名、文件签名、活动 SVG、符号链接 / 硬链接、未登记资源、类别错配和权限;AppSurface 覆盖本地 Markdown 安全渲染、SVG data URL、音频播放器和中央聚焦状态;追加 shell typecheck、Rust 定向测试、编码检查与 `git diff --check`。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-03 资源卡手动拖动暂缓并完成固定资源投影与中央聚焦
- 背景:飞书《陶泥儿GameAgent-V1.0 项目开发界面需求》曾要求资源卡可拖动,并由后续 PRD、技术方案和实现扩展为手动布局 CAS、依赖线拖动预览与性能验收;mentor 最新决定明确资源卡暂时禁止拖动,资源聚焦也不需要工具栏或工具侧边栏。
- 决策:当前资源卡不绑定 `pointerdown / pointermove / pointerup / pointercancel` 拖动入口,title、cursor、`touch-action` 与 class 只表达可点击;Pointer Move 不改变坐标、不更新依赖线、不提交手动布局。资源投影固定为文档、项目版本、美术资源、音乐音效资源四区;未知任务产物不兜底为版本,版本只接受显式 read model,音频只接受正式登记资产或已导入附件。资源身份不使用显示名称。
- 保留边界:项目内 dependency / type 布局 sidecar、历史坐标读取、资源集合自动协调、scope FIFO、跨窗口系统锁、Tauri/Rust CAS 与命令式 SVG preview 基础设施可以保留;历史 `manuallyPlaced=true` 坐标不删除、不重置、不迁移,但当前没有用户手动布局入口。手动拖动持久化、拖动性能和冲突后的重新拖动提示不再是当前验收条件。
- 聚焦边界:点击资源后只把中央主视窗切换为 `resources.focused.document / art / audio / version`,不遮盖或替换右侧 Supervisor 与底部 Agent 状态栏;通用容器不提供工具栏、工具侧边栏、底部画板工具栏或可拖动标题栏。退出恢复当前会话内搜索、dependency / type、画布滚动位置和选中资源,不把这些状态写入 sidecar。
- 后续边界:本阶段不新增项目文件读取、美术编辑、音频播放 / 编辑、版本替换或运行模块;若重新开放手动拖动,必须先更新 PRD、技术方案和 AppSurface 验收合同。
- 验证方式:纯投影测试覆盖四类映射、未知产物拒绝和显示名称改动下身份稳定;AppSurface 覆盖 Pointer Down / Move / Up / Cancel 后卡片坐标、SVG path 与布局更新调用均不变,并覆盖中央聚焦、上下文恢复、dependency / type 切换、搜索、依赖图和直接上下游高亮;追加 shell typecheck、编码检查与 `git diff --check`。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-03 依赖布局等待 Rust 图终态且拖动热路径脱离 React state
> 状态:其中依赖图初始化屏障继续生效;手动拖动热路径与性能验收已由上一条 mentor 最新决定暂缓。
- 背景:资源图异步返回前,dependency 布局会先以 `dependencyDepth=0` 创建并持久化自动坐标;图返回后的 reconcile 保留既有位置,导致首次布局永久停留在错误层级。4096 张真实资源卡拖动时,逐帧父组件 state 还会重渲染全部卡片,即使 SVG 已只更新局部 path 也无法满足帧预算。
- 决策:Rust 关系图 read model 负责在完整任务图 SCC 压缩后返回确定性 resource dependency depthdependency 模式等待当前 scope 图进入 `ready / failed` 后才启动布局读取与协调。手动位置永久保留,自动位置允许按最终图重新派生。拖动 preview 留在前端 ref/DOM 热路径,命令式更新卡片 CSS 与局部 SVG path,不逐帧跨 Tauri IPC,也不写 layout sidecar。
- 边界:不修改 `game-creator-resource-layout.v1`、布局 Rust 持久层、manifest、api-server 或 SpacetimeDBtype 模式不等待资源图且继续保留全部已有坐标。图失败只降级初始化一次,项目或 mode 切换后旧图结果必须丢弃。
- 验证:延迟图 Promise 证明终态前零布局读取/写入,手动位置保持且自动位置按最终深度协调;4096 张真实卡片连续拖动证明非拖动卡片零重渲染、静态 SVG 不重建、Observer 单实例,并以 Chromium p95 `<16.7ms` 和零 `>50ms` long task 验收。
## 2026-08-06 AGC 根长任务启动与终态失败采用 Runtime 公开消息硬门
- 背景:AGC 已有模型 final-reply、Runtime `eventId/publicText` 和进度卡,但根任务“已入队”没有后端持久公开回执;失败消息又散落在 main loop 多个 `let _ = append conversation` 分支。Provider 或 Runtime 在首条公开事件前失败时可能零消息,同一次失败也可能被 `turn.failed` 和 conversation 重复播报。
- 恢复补充(以本条为准):`preparing` 只要同 run 的用户消息或 accepted 任一已完整持久即属可恢复;preflight 不改写业务文件,resume 才在 Agent 锁内补写 accepted 并入队。用户消息或 accepted conversation 已存在而审计补写失败不得把任务改判为启动失败。根终态首次公开写入遇到瞬时失败时,完成终态投影后必须用同 message ID 再幂等写一次。带 parent 的 Supervisor receipt / isolated-join 续跑只保留一份 Runtime 公开终态,不再追加 Session 重复消息;`runtime-task-*` 与 `runtime-public-status-*` 共享同 run 的不透明关联摘要,多个同秒任务在 UI 中按实际 run 关联的 `user -> accepted -> terminal` 交错排序。
- 验证方式:覆盖 `preparing -> accepted -> pending` 顺序与崩溃恢复、启动确认幂等、启动确认无法持久化时任务零执行、公开状态不进入实际 Agent prompt、状态文件写入本身失败时仍先产生公开失败、根终态事件不重复进聊天、专业 Agent 启动事件仍可见、普通项目消息不被误标 Runtime-owned,并运行 Runtime 定向 Rust、AppSurface、模型测试、typecheck、编码和 diff 门禁。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`、`docs/project-memory/shared-memory/pitfalls.md`。
---
- 背景:ready scheduler 已为当前根 Run 持久化并启动 `code-prototype`,但并发 hydration 持有的旧 manifest 快照随后把该节点从 running 覆盖回 pending。父 Run 只读取 manifest 时会在 child 仍运行的情况下误判固定 Graph 已停滞并先行失败;child 完成门又因 pending 连续拒绝真实交付,最终耗尽 loop。
- 失败关闭:GUI/CLI、旧父 Run child、终态 child、错误/伪造绑定、非确定性 runId、WaitingForConfirmation、WaitingForUserInput 和 needs-reconciliation 都不得借用该容忍;创建更新的根 Run 后,旧 child 立即失去父 DAG 活性与 pending 完成资格。
- 玩法连续性:普通美术措辞不再触发历史玩法类型回溯;只有正式 failed continuation 继承原完成合同,纯“继续”沿用现有 continuation 识别边界。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/pitfalls.md`。
---
## 2026-08-05 增量接入既有美术时复用已验收 Graph 节点
- 禁止替代方案:不得增加 loop 次数掩盖冲突,不得递增虚构的产物版本号,不得覆盖 art manifest 扩展字段,也不得重放历史图片生成 action。
- 验证方式:回归必须证明明确复用时两项美术节点保持 completed、父完成门不再报告 art manifest baseline 未变化、俄罗斯方块场景保持 `tetris-v1`;删除任一切片后豁免立即失效。另以普通新目标和“全新美术”请求证明旧美术不能被认领。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/pitfalls.md`。
---
## 2026-08-05 专业 Agent ready-task 必须先确认 durable start 再异步执行
- 背景:AI 游戏创作首波 `design-director / art-director / code-director` 已全部写入 queued 与 scheduled,但 scheduler 把 per-Agent 执行锁直接移交 fire-and-forget Tokio future 后就返回成功;其中一个 child 未首次 poll 时会永久停在 `pending / queued`,而 recovery 又因执行锁仍被该 future 持有而无法接管。Runner heartbeat 与进程均正常,单靠“进程存活 / 锁已移交 / scheduled 已写”不能证明任务开始。
- 决策:在实际持有执行权的 Runner/进程内,ready scheduler 必须先释放项目写锁,再同步完成 child 的 `pending -> running`、`turn.started` 和 started journal,随后才把已经启动的 state 与执行锁交给已确认开始轮询的独立 execution worker。同步启动或 worker 接管失败时,必须在仍持有 per-Agent 执行锁期间依次把 child 落为 failed、把 manifest Graph 节点投影为 failed,再释放锁并向 parent 返回调度错误;`autonomous_ready_task.scheduled` 仅是诊断审计,写入失败不得阻断 durable child 启动。只负责投递的 external client 继续释放本地锁并 wake External Runner,不在客户端冒充执行。
- 可观测性:用户界面的“疑似停滞”只是 Runtime 活跃度投影,不改写正式业务状态。`startedAt` 由新 Run 的 durable queued/start 时间写入,旧 Run 从完整 task journal 的同一 `sessionId + runId` 最早记录恢复;从最新 task record 构造的 fallback state 必须保持 `startedAt=0`,不能把最近进度或终态时间冒充开始时间。运行态超过 5 分钟没有父 Run 或当前关联专业 Agent 的新事件时提示静默时长;等待用户、等待确认、Provider retry、视觉资产、进程会话、pausing 和 paused 均排除。父 Run terminal 后持续时间只冻结在父 Run 自身最后活动,关联 child 的晚到收口事件不能继续增加父 Run 时长。
- 对账取消续跑:ready-task 的未知工具结果仍禁止自动重放;人工核对并取消旧 child 后保留 cancel tombstone,旧 child 与旧父 Run 都按真实 cancelled/failed 终态收口。后续同 Session、同 source、同有效任务语义的 Supervisor continuation 建立新完成合同时,只把 manifest 中能由历史 `needs-reconciliation -> cancelled` child 与 tombstone 共同证明的对应 failed 节点恢复为 pending,并生成全新 child Runmanifest 读取、failed 节点筛选、每任务一次的 child journal 索引、证据重验和最终写回必须位于同一项目写锁域。较新的无 child 父 Run 只有在 durable root journal 明确记录为“固定 Graph 在调度前已无法推进”时才能跨过;普通失败、scheduler 持久化前失败、无 tombstone、无 reconciliation 历史或不同任务语义均不得借用更老凭证隐式重试,也不得把旧 action、observation 或 child 伪装为 completed。
- 完成门性能边界:Canvas 视觉验收在源码同时没有 `import` 与 `export` 关键字时不运行模块依赖分析,必须保留纯 `export ... from` / `export * from` 重导出依赖;当前脚本既不含目标资产文件名、也不含任一已绑定 DOM 图片元素 ID,且对 JavaScript `\\xNN`、`\\uNNNN`、`\\u{...}` 等转义做候选解码后仍不含二者时,不运行完整 Canvas 数据流与函数可达性分析。转义解码不确定时必须保守进入 parser,以保留 computed `src` 和转义 DOM 方法名;词法预检不能把命中当作通过,存在候选时仍执行原 parser、semantic binding、解码后的属性/StringLiteral 路径、可达性与目标 Canvas 检查。
- 影响范围:AI 游戏创作 `runtime_driver/task_start.rs`、`task_queue.rs`、自主构建 continuation 合同、Supervisor 进度卡与相应 Rust/AppSurface 回归;不改变 manifest DAG、Agent catalog、Provider 路由或项目产物合同。
- 验证方式:不预占 child locks,真实一次调度三项首波任务,并在有界时间内证明每个逻辑 Run 至少写入 running/`turn.started`;重复调度不得新增逻辑 Run。前端固定时钟覆盖正常运行、子 Agent 新活动、疑似停滞、各类合法等待与 terminal 冻结。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/pitfalls.md`。
## 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<RgbaImage>``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-31 AI 游戏创作资源依赖图采用 Rust 只读拓扑与前端派生 SVG
> 状态:其中资源卡 Pointer Move 拖动预览与局部更新验收已由 2026-08-03 mentor 最新决定暂缓;只读拓扑、SVG 派生展示、搜索与选择高亮合同继续生效。
- 背景:资源画布已有 dependency / type 双模式坐标与本地 CAS sidecar,但 dependency 模式尚未把当前 manifest 中可证明的资源引用和任务流转可视化;关系图不能反向污染布局持久化或建立第二套资源真相。
- 决策:dependency 模式由 Tauri Rust 只读命令从当前 manifest、资源卡身份和有界 `.agent/agent.db` 审计构建稳定 `ProjectResourceGraph` read model,前端只归一化 DTO、测量卡片坐标并用原生 SVG 渲染。资产 `source.referenceResourceIds` 只在唯一匹配另一资产 `source.resourceId` 后形成橙色实线;任务依赖按任务对聚合为灰色虚线主线与两端分支,禁止资源笛卡尔积。Rust 以迭代式强连通分量分析识别资源环和完整任务 DAG 环,并返回资源局部连接索引。
- 任务身份:External Editor 响应中的 `source.taskId` 是平台生成任务 ID,不等于本地 manifest task ID,禁止据此分配 producer。画布资产只接受 `agent.runtime.canvas.asset_generate` 审计中经当前 manifest task 校验的 `assetId -> agentId`;证据缺失、冲突或已超出有界读取窗口时不生成对应 task flow。任务产物与 Agent 回执继续使用自身已有的 manifest task 身份。
- 生命周期与边界:Pointer Move 先用 `requestAnimationFrame` 合帧;基础 positions 与拖动预览分离,SVG 静态拓扑保持复用,每帧只按局部索引更新拖动资源关联的 reference edge 和 task flow。`ResizeObserver` 在单个图层生命周期只创建一次。type 模式不挂载图层;切换 mode、项目或卸载工作台时销毁 SVG、Observer 和窗口监听。SVG 统一 `pointer-events: none`path、marker、图结构和 section 原点从不持久化。
- 数据边界:本切片只新增 Tauri Rust 只读 read model,不修改 layout sidecar、`resourceCanvasLayoutModel.ts`、manifest、api-server、SpacetimeDB schema 或生成绑定,也不引入第三方图表库。dependency section 只在显示层额外预留 `64px` 右侧视觉 gutter,卡片坐标和持久化布局不变。
- 影响范围:`apps/ai-game-creator-shell` 的 Tauri project read model/command、项目开发资源投影、依赖图 DTO、SVG overlay、样式与前后端测试,以及工作台 PRD 和客户端实施计划。
- 验证方式:Rust 定向测试覆盖真实 producer 映射、拒绝复用外部 `taskId`、证据缺失、去重、无效 ID、完整任务环、4096 任务链与聚合复杂度;前端模型和 SVG 测试覆盖 DTO 防御过滤、局部上下游、可见资源自引用闭环、搜索、高亮、单帧局部 path 更新和稳定 ObserverAppSurface 覆盖生产数据形状、两种边、type 模式卸载和项目切换销毁,并运行 shell typecheck、编码检查与 `git diff --check`。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
---
## 2026-08-04 画布生成输入 V2 原地收紧并统一回落当前默认值
- 背景:画布 `generationInputs.version=2` 从持久化 JSON 恢复时只校验通用结构,未知、非法、已下线或与当前模型能力不兼容的模型、比例、尺寸、清晰度、声音、时长及角色动作档位仍可通过 TypeScript 断言进入 UI 和再次提交。前端已隐藏但后端仍兼容的历史 Veo 也不再属于当前可选模型。
- 决策:不新增 V3,不迁移数据库,不保留旧值再次执行;V2 在读取时原地按 `action` 解码。所有已存在但未知、非法、已下线或与当前模型不兼容的参数统一回落到该 action 的当前默认值,历史 Veo 同样回落到当前默认视频模型。图片比例 / 尺寸按回落后的模型联动校验,视频参数按当前模型能力校验,角色动作 `frameCount / durationSeconds` 按完整档位成对校验。发生回落时必须向用户显示“部分原生成参数已使用当前默认值”告警;再次提交只使用规范结果并保存为仍是 `version: 2` 的新快照。缺失字段继续由当前 action 默认值补齐,不为此单独升级版本。
- 实现边界:单一 action 级 runtime decoder 是持久化 V2 的读取真相,恢复 UI、改造入口和再次提交链不得各自解释原始 JSON;canonical 选项从当前编辑器模型 / 参数注册表派生,不新增平行旧模型清单。通用结构不合法时整份配方不支持改造;必需 `source` 缺失时仍按 action capability 保留改造按钮,点击后在恢复路径拒绝改造,不用默认值伪造引用;运行期来源变化时同样必须复检并拒绝。该策略只改变画布配方的运行时恢复和后续重存,不修改 SpacetimeDB schema、BFF DTO 或已有资产原始 JSON。
- 影响范围:`ImageCanvasGenerationModel` 的 V2 decoder、`ImageCanvasGenerationDialogModel` 的恢复入口、`useImageCanvasGenerationWorkflow` 的回落告警、生成输入回归测试和编辑器 Lovart 统一方案。
- 验证方式:fixture 覆盖当前合法 V2、历史 Veo、未知模型、模型不兼容尺寸、非法视频参数、非法音效参数、角色动作错配档位和缺失必需来源;断言 UI、价格与提交使用规范值,回落显示告警,再次生成仍保存 V2。运行图片画布定向 Vitest、`npm run typecheck`、定向 ESLint、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`review.txt`。
---
## 2026-07-30 抠图实际后端作为 generationInputs 顶层内部元数据保存
- 背景:角色、图标图集和 UI 图集抠图派生资产需要保留最终实际执行的处理后端,供后台诊断 BgFilter、阿里云通用抠图和本地键色的降级结果;把抠图模型写成 `generationInputs.fields` 的“处理模型”会进入图片信息,与用户可见输入快照语义冲突,而覆盖正式资产 `model` 又会丢失源生图模型。
- 决策:继续使用现有 `generation_inputs_json` JSON 包络,不修改 SpacetimeDB schema。`fields` / `references` 只保存用户可见生成输入;仿照顶层 `screenColorHex`,抠图派生资产在顶层写入 `mattingProvider` / `mattingModel`。BgFilter 记录本次实际 `seg_model`;阿里云记录 `Aliyun Matting / segment-common-image`;本地键色记录 `Genarrative Local / screen-color-keying`。三条链路的正式资产 `model` 继续继承源生图模型,图片信息不读取顶层内部字段。`screenColorHex`、`mattingProvider`、`mattingModel` 和素材顶层 `provider` 属于内部执行信息:普通用户(包括素材 owner)、精选提交 / 点赞响应与匿名公开读取统一省略,只有后台管理和服务端 raw 审计读取原始值;历史数据不迁移,在 User/Public mapper 边界清理。角色动作逐帧可能混用多个 fallback,本次不把单帧结果提升为整组动画模型。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs` 的抠图结果和角色 / 图标 / UI 派生资产持久化、图片信息兼容测试、后端数据契约与图片画布 MVP 文档;不修改前端生产展示逻辑、请求 DTO、SpacetimeDB 表、迁移或生成绑定。
- 验证方式:后端单测覆盖三种实际结果映射、顶层元数据不改写 `fields`,结构断言覆盖三条派生资产持久化链路仍保留源生图模型;User/Public mapper 测试同时证明素材 owner、精选提交 / 点赞与匿名响应均省略 `provider` 和三个内部键,Admin raw payload 保留原始值;前端测试覆盖顶层字段不在图片信息或搜索索引出现。运行 `cargo test -p api-server editor_project::tests --manifest-path server-rs/Cargo.toml`、图片信息定向前端测试、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
---
## 2026-07-31 修正抠图内部元数据的普通用户读取边界
- 背景:2026-07-30 的记录误把素材 owner 与后台审计并列为原始抠图执行信息的读取方。owner 是普通用户,前端不展示字段不能阻止其从项目资源、素材库、精选提交 / 点赞回包、画布布局或任务完成响应的网络 payload 读取 BgFilter、阿里云、本地键色、具体分割模型或背景色。
- 决策:本条取代 2026-07-30 决策中“素材 owner、精选提交响应仍可读取原始值”的表述。普通用户(包括素材 owner)读取时必须过滤素材顶层 `provider`、内部处理 `model`,以及 `generationInputs` 顶层 `screenColorHex`、`mattingProvider`、`mattingModel`;正常用户可见生成 `model` 和其他合法功能性顶层字段(例如 `characterAnimation`)保持不变。匿名公开素材 payload 不包含整个 `generationInputs`。User/Owner mapper 完成普通用户清理,public mapper 在其基础上移除该 owner-only 配方字段;后台管理与服务端审计继续使用 raw mapper 和持久化原值。历史数据不迁移,统一在读取边界清理。
- 入站与持久化:客户端提交的 `generationInputs` 不得伪造上述内部键,服务端在实际处理完成后才写入可信值。手动去背景的正式素材 `model` 必须继承经服务端验证的正常源生图模型;若来源或祖先链不存在正常模型则为 `null`,不得写入 `BgFilter complex` 等内部处理模型。内部抠图 provider / model 可继续持久化供后台审计,普通用户完成响应和用户可见错误文本均不得暴露它们;手动去背景与角色动作透明化失败在 Owner HTTP / 任务状态边界统一替换为稳定业务文案,原始错误只留在任务记录、tracing 和后台审计。
- 影响范围:项目资源、素材库、精选提交 / 点赞、图片 / 图标 / 视频 / 音频 / 角色动画生成完成、Agent 紧凑结果和识别出的画布资源 / 图层快照的 User/Public mapper;手动去背景持久化和完成响应;External Editor API 创建素材 / 资源时的保留键入站清理;相应响应 / OpenAPI 契约、前端搜索 / 详情 / ZIP 过滤测试与后台 raw 审计测试。同源画布 BFF 的角色、图标和 UI 请求继续由前端自动提交默认 `segModel=birefnet`,后端继续校验并在缺失时回落默认值;该请求控制字段不进入 `generationInputs`、普通用户响应、搜索、详情、导出或错误详情。角色动作的 `seg_model` 继续由后端固定。不修改 SpacetimeDB schema、迁移或 bindings。
- 验证方式:Owner 响应覆盖无 `provider`、无内部 `model`、无三个内部 `generationInputs` 键,同时保留正常 `model` 与 `characterAnimation`;匿名公开素材响应完全不含 `generationInputs`Admin raw payload 保持完整。覆盖历史 `BgFilter complex`、三类入站伪造键、手动去背景源模型回溯和用户错误文本过滤;运行 api-server 定向测试、前端定向测试、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding` 与 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
---
## 2026-07-31 接受外部 OpenAPI v1 的未版本化 breaking change
- 背景:2026-07-31「修正抠图内部元数据的普通用户读取边界」把 OpenAPI 更新列为影响范围内的机械同步,但实际动作是从已发布的 `/api/external/v1` 契约中删除四个生成响应里原本 `required` 的 `provider`,另从 `EditorProjectResource` / `EditorAsset` 删除可选 `provider`,而 `info.version` 仍为 `1.0.0`、路径前缀未变。严格反序列化或由 OpenAPI 生成的调用方会在服务端上线瞬间直接失败,且不需要调用方做任何动作。脱敏目标本身成立,但契约处理方式当时没有单独定性。
- 决策:确认这是 breaking change 而非文档同步,并接受本次不升版本、不提供兼容字段、不设弃用期。唯一依据是截至 2026-07-31 `external_api_key` 无属于外部第三方的存量调用方。该豁免不具一般性:API Key 由用户在个人中心自助发放,`/api/external/v1/openapi.json` 又是该批路由中唯一免鉴权端点,因此「无外部调用方」不是受控状态,出现非内部账号活跃密钥、对外公布契约或与外部团队联调后立即失效。今后删除响应字段、把字段移出 `required`、收窄类型或取值、改变字段语义、新增请求必填字段均视为 breaking,存量调用方出现后必须按兼容值、弃用期或 `/api/external/v2` 三选一处理,只更新 JSON 不构成合规变更流程。本次不追加代码改动。
- 影响范围:`docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md` 新增「版本与兼容策略」一节;`docs/openapi/genarrative-external-v1.openapi.json` 的 `info.description` 补充面向集成方的兼容性说明;`pitfalls.md` 记录「无调用方」不可当长期前提。不修改响应结构、请求 DTO、路由或 `external_editor_api.rs` 断言。
- 验证方式:`info.version` 保持 `1.0.0` 且 JSON 仍可被 `serde_json` / `json.load` 解析;`external_editor_api.rs` 既有 openapi 断言继续通过。该断言只校验 schema 形状、不校验兼容性,因此通过不等于契约安全,判定仍以上述 breaking 清单为准。正式对外发放第一个外部密钥前需复核本条是否仍成立。
- 关联文档:`docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md`、`docs/openapi/genarrative-external-v1.openapi.json`、`docs/project-memory/shared-memory/pitfalls.md`。
---
## 2026-07-31 历史素材的内部处理模型不做回溯清理
- 背景:用户可见性过滤 `isEditorUserVisibleGenerationInputField` 的实现是标题精确匹配 `处理模型`,既不识别语义也不探测取值;服务端 User/Public mapper 只清理 `generationInputs` 顶层的 `screenColorHex` / `mattingProvider` / `mattingModel`,不遍历 `fields` 数组。历史资产中存在标题为「抠图模型」、取值形如 `动漫风格 anime-seg` 的字段,两侧都拦不住,因此仍会出现在图片信息弹窗和画布 ZIP 导出元数据中。MVP 接入方案原文「前端同时过滤历史项目中已持久化的处理模型字段」读起来是全覆盖保证,与实际实现和历史数据存在显式冲突。
- 决策:维持产品决策——历史素材不迁移、不回溯清理,本次不扩大过滤范围,不修改 `isEditorUserVisibleGenerationInputField`。改为收敛文档口径:明确过滤是标题精确匹配而非语义识别,明确「抠图模型」为已知例外且属于接受状态,不得据此判定为缺陷。约束只对新写入生效,新产生的 `generationInputs.fields` 不得再写入任何内部处理模型字段,无论标题为何。若将来决定扩大过滤范围,必须先对生产 `generation_inputs_json` 做标题去重查询枚举真实存在的历史标题,不得仅凭测试夹具推断清单。
- 影响范围:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md` 收敛该句表述并补充已知例外;`pitfalls.md` 记录过滤口径与排查方式。不修改前端过滤实现、服务端 mapper、素材数据或导出结构。
- 验证方式:图片信息弹窗与画布 ZIP 共用同一过滤口径,任何一侧改动必须同时覆盖另一侧;既有 `ImageCanvasMetadataModalView` 与 `ImageCanvasExportModel` 测试保持通过,不新增针对历史「抠图模型」的过滤断言,以免与本决策冲突。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/project-memory/shared-memory/pitfalls.md`。
---
## 2026-07-30 图片画布搜索不得索引内部模型和 Provider
- 背景:素材和图层详情虽已把内部处理模型显示为 `-`,搜索仍索引原始 `model` 与 `provider`,导致 `BgFilter complex`、`segment-common-image`、`BgFilter` 或 `Aliyun Matting` 等隐藏信息可被查询命中,并出现“命中但无可见匹配字段”的异常体验。
- 决策:素材与图层搜索只索引用户可见生成模型;统一复用 `isEditorInternalProcessingModel(...)` 排除内部处理模型,并完全排除 `provider`。该规则只约束前端临时搜索值,不删除或改写素材、图层及后端保存的原始审计字段;`gpt-image-2`、`audio1.0` 等正常模型继续支持搜索。
- 影响范围:`ImageCanvasAssetLibraryModel.ts` 的素材与图层搜索值、图片画布素材 / 图层侧栏搜索和对应前端架构文档;不改变持久化、详情展示、删除、移动或画布保存行为。
- 验证方式:模型单测覆盖内部模型和 Provider 不命中、正常模型继续命中且原对象元数据不变;侧栏交互测试覆盖素材与图层两类入口。运行对应 Vitest、`npm run typecheck`、`npm run lint:eslint`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
---
## 2026-07-31 每日免费泥点基础额度纳入后台钱包配置
- 背景:每日免费泥点已是独立余额桶,但基础发放量仍在运行时固定为 `20`,后台“账号配置”只能维护注册初始泥点,运营调整需要改代码。
- 决策:在 `profile_wallet_config` 尾部追加带默认值 `20` 的 `daily_free_points_per_day`,与 `initial_mud_points` 共用 `/admin/api/profile/wallet-config` 和后台账号配置页一次读写。尚未初始化当日额度的用户立即使用最新值;已初始化用户的当日余额不追补、不回收,下一北京时间业务日首次触达时按最新配置重置。跨日退款可继续使当日 `granted_points` 高于基础额度,因此充值中心 `dailyFreeResetPoints` 必须显式投影配置值,不用当日已发放总额反推。
- 迁移与边界:旧 SpacetimeDB 表行和旧迁移 JSON 均缺少新字段,自动迁移与 `migration.rs` 导入归一统一补 `20`;新字段只允许正整数。每日任务奖励、扣费桶顺序、退款归因和北京时间日切边界不变。
- 影响范围:`module-runtime`、`spacetime-module`、`spacetime-client`、`shared-contracts`、`api-server`、`apps/admin-web`、SpacetimeDB 迁移与生成绑定。
- 验证方式:后台页面与 API 定向测试、每日免费日切与迁移定向 Rust 测试、`npm run spacetime:generate -- --rust-only`、`npm run check:spacetime-schema`、`npm run admin-web:typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
---
## 2026-07-31 发布前延期冷备份由独立 systemd 上传并补偿扫描
- 背景:Jenkins Stdb Publish 的 async 备份先生成 `uploadStatus=deferred` 的本地 tar.gz,再从 EXIT trap 用 `nohup` 启动上传。后台进程仍继承 Jenkins Cookie,作业结束时可被清理;旧 deferred manifest 也没有后续补偿扫描,导致 dev 的本地冷备份持续占满根盘。
- 决策:`production-stdb-publish.sh` 只能用具名、`Type=exec`、`--collect` 的 `systemd-run` transient service 启动异步上传,禁止回退 `nohup`。独立服务执行 `database-backup-to-oss.mjs --upload-deferred-dir <backup-dir>`,在同一备份锁内按文件名串行补传同库 `deferred/pending` 归档;目录外路径或 manifest/归档不匹配时失败关闭,缺失归档的历史 manifest 只报告不删除。
- 清理边界:只有 archive 上传与 HEAD 验真、manifest sidecar 上传验真、baseline state 写入全部成功后,才按 `GENARRATIVE_DATABASE_BACKUP_KEEP_LOCAL` 删除精确的 archive 与 manifest。transient unit 未启动或上传失败时保留归档,由后续 publish 继续补偿;`files-history` timer 仍不负责清理这些 tar.gz。
- 影响范围:`scripts/deploy/production-stdb-publish.sh`、`scripts/database-backup-to-oss.mjs`、生产运维门禁和本文档。
- 验证方式:`npm run check:database-backup`、`npm run check:production-ops`、`npm run check:encoding`、`git diff --check`dev 现场还必须确认 transient unit 不在 Jenkins session scope,旧 deferred 归档逐份变为 OSS 已验真对象后被删除,备份锁清空,核心服务与公开接口健康。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
---
## 2026-07-31 画布 Agent 图片结果单击直接定位
- 背景:生成图片在对话中是静态缩略图,用户需要用更直接的方式回到对应画布图层;视频和音频仍有播放、拖动和音量等原生点击交互,不能共用该行为。
- 决策:携带有效 `resourceId` 的 Agent 生成图片在普通单击时直接调用实例级 `ImageCanvasActionsContext.focusResource(resourceId)`;无 `resourceId` 的旧图片保持无动作。视频和音频的普通点击仍只操作播放器,三类媒体均保留右键菜单的“在画布中定位”。图片卡片本次不新增按钮语义或键盘 Tab 停靠,Enter / Space 不触发定位。
- 影响范围:`ToolCallView`、消息气泡交互测试和画布 Agent 前端专题文档;不修改共享 DTO、后端 API、SpacetimeDB 或 viewport 动画语义。
- 验证方式:覆盖有效图片单击、旧图片无动作、视频 / 音频单击无定位及三类媒体右键定位;运行前端定向测试、`npm run typecheck`、`npm run lint:eslint`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【编辑器】画布Agent对话面板-2026-07-03.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
---
## 2026-07-30 画布 Agent 结果通过实例级 Action Context 刷新并从右键菜单定位资源
- 背景:画布 Agent 图片、视频和音频结果需要提供画布定位和结果完成后的工程刷新;若继续从舞台向面板、消息和工具结果逐层传 callback,会扩大现有 prop drilling,而把 callback 或瞬时命令放入全局 Zustand 又会引入多实例和卸载残留问题。直接把点击和定位语义附到生成媒体上还会让视频 / 音频的播放、暂停、拖动与音量操作误触发画布聚焦,并给原生媒体控件附加错误的定位标签。
- 决策:`ImageCanvasEditorView` 提供实例级 `ImageCanvasActionsContext`,暴露 `focusResource(resourceId)` 与 `refreshCanvas()`Agent 工具结果刷新、生成结果右键菜单和任务侧栏直接消费对应动作,不新增中间 props,也不扩展现有只保存 `projectId` 的 Zustand store。`refreshCanvas()` 统一重新读取当前工程快照并刷新素材库,任务列表入队后的立即失效继续保持独立。带有效 `resourceId` 的图片、视频和音频生成结果只在素材右键菜单显示“在画布中定位”,普通媒体卡片不声明按钮语义或 `tabIndex`,点击、Enter 和 Space 均不触发定位;视频和音频原生播放器只使用描述媒体自身的标签,不承载定位标签或点击处理。`focusResource(resourceId)` 命中当前图层后只按完整画布 viewport 播放固定 `420ms` ease-out fit 动画,不改变图层选择、工具、侧栏或 Agent 面板;普通 viewport 写入和用户交互可取消动画,reduced-motion 直接完成,缺失 ID 或图层时无动作。
- 影响范围:图片画布 Action Context、viewport controls、Agent 结果媒体交互、前端测试和编辑器专题文档;不修改共享 DTO、后端 API 或 SpacetimeDB。
- 验证方式:覆盖 Context 作用域、三类媒体普通点击无动作与右键菜单分发、原生媒体控件无定位标签、动画中间帧与终态、手动取消、reduced-motion,以及编辑器集成中选择态和面板保持不变;运行前端定向测试、`npm run typecheck`、`npm run lint:eslint`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【编辑器】画布Agent对话面板-2026-07-03.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md`。
---
## 2026-07-29 图集切片必须受前置容量和有界 CPU 保护
- 背景:图标与 UI 图集的 alpha 连通域识别会在 async handler 上同步执行;原始连通域合并采用全量两两比较,`64` 个输出限制又晚于排序、裁剪和 PNG 编码。碎块或噪点图会放大 CPU 与内存成本,手动拆分、图标自动拆分和 UI 提取都受影响。另一方面,图标与 UI 的 Alpha 尺寸恢复、provider 原图回读或透明图解码失败此前只记日志,仍会把不可信透明图持久化并拆分。
- 决策:`platform-image` 在每次 flood-fill 后累计所有原始连通域(包括随后过滤的噪点)并以 `4096` 为硬上限;合并只通过 `64px` 空间网格查询 `48px` 最大邻域内且满足辅助部件尺寸条件的候选,单网格最多登记 `256` 个组件、单 source 最多保留 `512` 个候选,拥挤时明确返回资源限制错误;调用方把固定 `maxOutputSlices=64` 传入 platform slicer,并在排序、裁剪和 PNG 编码前拒绝超限。三条入口统一走 2 路 semaphore、30 秒本地上界与请求绝对 deadline 共同保护的 `spawn_blocking`permit 必须由 blocking 闭包持有。自动图标 / UI 超限以空切片和稳定 `sliceWarning` 完成,手动拆分返回 `422`,两者都不得产生任何切片 PUT、资源或画布切片;自动路径已成功的整张图集仍按既有契约保留。
- source-only 收口:角色、图标和 UI 共用同一个 provider 原图收口 helper。BgFilter 最终失败、Alpha 比例漂移超过 `5%`、provider 原图修复性回读失败、Alpha 回贴失败或透明图完整解码失败时,只用已保存 provider 原图完成占位,返回 `completed + warning`;图标 / UI 固定 `iconImageSrcs=[]`、`sliceWarning=null`,不写透明图、不拆分。provider 原图本身无法解码时在首次持久化前失败,不再伪造 `512×512` 元数据。
- 影响范围:`server-rs/crates/platform-image/src/generated_asset_sheets/`、`server-rs/crates/api-server/src/editor_project.rs`、图片画布图标与 UI 素材生成 / 手动拆分链路;不修改请求 DTO、扣费退款、SpacetimeDB schema 或成功路径多产物布局。
- 验证方式:platform-image 覆盖大量独立 `4×4` 块、单像素噪点和 65 个有效输出;api-server 覆盖比例漂移、原图回读失败、截断透明 PNG、共享 source-only helper 无持久化副作用,以及三入口统一 bounded slicer。运行 `cargo test -p platform-image generated_asset_sheets --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_project::tests --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
---
## 2026-07-29 像素规整降级必须复用交付尺寸守卫
- 背景:像素模式接入「角色带背景原图与透明图统一交付尺寸」后,删除了原先像素路径末尾的后置尺寸恢复。但像素规整的 best-effort 降级分支(预算耗尽、回读 provider 原图失败或超时、CPU permit 获取失败、worker 内 deadline、join 异常、worker 超时)都直接返回 BgFilter 原始输出并把尺寸错误置为 `None`,跳过了非像素路径已有的尺寸比对与 alpha 回贴。BgFilter 回图尺寸漂移是已知现象,叠加并发上限 2 导致的 permit 超时后,角色会绕过「改用已保存的同尺寸原图完成画布」的安全降级,角色和图标都可能持久化尺寸漂移的低分辨率透明图。
- 决策:像素路径的每一条降级都必须经 `degrade_editor_pixel_art_to_postprocessed_with_dimension_guard` 收口,该守卫复用非像素路径的 `apply_editor_postprocessed_alpha_from_persisted_provider_source_or_original`:先做纯内存尺寸比对,与交付尺寸一致就原样返回且不产生额外 OSS GET;漂移才回读原图重贴 alpha;修复失败如实返回尺寸错误,由调用方按各自既有语义处理。由 provider 原图逐像素合成的 `rgba_source` fallback 尺寸天然正确,不再经守卫。像素路径函数因此需要显式接收交付宽高。
- 生效范围(由同日后续决策补齐):像素路径继续保证不把 BgFilter 原始输出连同 `None` 尺寸错误交回调用方;角色、图标和 UI 拿到尺寸 / Alpha 错误后现已统一走 provider 原图 source-only 收口,不再持久化或拆分尺寸异常、比例异常或不可解码的透明图。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs` 的角色与图标像素规整降级路径;不改变成功路径、OSS PUT 次数、资源类型、画布项或前端契约,OSS GET 仍只在尺寸漂移时发生。
- 验证方式:`pixel_art_degrade_paths_guard_postprocessed_delivery_dimensions` 结构断言固定"降级分支不得返回 `(postprocessed, None, …)`"与守卫的委托实现;运行 `cargo test -p api-server editor_project --manifest-path server-rs/Cargo.toml`、`npm run check:rustfmt`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:本文件「2026-07-29 角色带背景原图与透明图统一交付尺寸」与「2026-07-28 图片生成风格使用可扩展字段并以纯内存像素规整首发」。
- 补充(同日):守卫的回读必须分两类处理。已取得 provider 原图的四条降级分支(permit 获取失败、worker 内 deadline、join 异常、worker 超时)改走纯内存守卫 `degrade_editor_pixel_art_with_provider_source`,零额外 GET;尚未取得原图的三条分支(进函数即预算耗尽、第一次回读失败、第一次回读超时)才走会回读的守卫。计数断言固定「回读守卫 3 处、内存守卫 4 处」,防止后续新增分支时误用回读版本。
- OSS 回读口径(修正此前「最多增加一次 OSS GET」的措辞):约束是**不重复读取已经成功取得的对象**,而不是"整个请求至多一次 GET"。仅在尺寸漂移且尚未持有原图时才发起最多一次修复性回读,失败后不再重试;因此第一次回读失败或被像素预算掐断时,允许存在第二次、也是最后一次尝试——第一次超时往往并非 OSS 异常,而是被 30 秒像素预算切断,此时对象通常可正常读取,放弃修复反而会让角色更频繁地退化为原图单产物。
- 回读上界:修复性回读必须始终有绝对 deadline。优先取外层 `request_deadline`,但它只在队列 worker 路径上有值——inline HTTP 请求的 `RequestContext` 默认 `external_call_deadline = None`,此时守卫自行以 `Instant::now() + EDITOR_PIXEL_ART_MAX_PROCESSING_DURATION` 重新计时派生上界,不得退化为无界 `download.await`。`apply_editor_postprocessed_alpha_from_persisted_provider_source_or_original` 的可选 `download_deadline` 只对像素守卫传值,非像素路径继续传 `None` 保持既有语义不变。结构断言固定守卫内必须同时出现 `request_deadline.unwrap_or_else(` 与 `EDITOR_PIXEL_ART_MAX_PROCESSING_DURATION`,防止兜底上界被移除后静默退回无界。
---
## 2026-07-29 角色带背景原图与透明图统一交付尺寸
- 背景:图片画布已将模型原生回图归一到统一业务像素矩阵,但角色分支为了保留 provider 原生分辨率,先持久化带背景原图,只在扣背后归一透明主图。因此同一个 1K 角色任务会同时给出模型原生大图和长边 `1024` 的透明图。
- 决策:角色分支必须在持久化带纯色背景原图和调用 BgFilter 之前,先按统一业务像素矩阵执行一次尺寸归一;该原图和透明派生图始终使用同一实际像素尺寸,1K 的长边为 `1024`。若 provider 回图任意一边小于业务目标或比例偏差过大,仍禁止放大或大幅裁切;此时两张图一同保留 provider 实际尺寸并返回通用 `warning`,不允许只改透明图。BgFilter 回图尺寸漂移时只允许在宽高比偏差不超过 `5%` 时重采样 alpha 蒙版并回贴到该原图;蒙版比例超限、回贴失败或尺寸验证失败时必须改用原图单产物降级,不持久化尺寸或比例不一致的透明图。若尺寸降级和后处理降级同时发生,同一条 `warning.reason` 必须同时保留两个原因。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs` 的角色生成、原图持久化、BgFilter 输入、项目资源尺寸与画布图层 Resolution;不改变前端请求 DTO、扣费、素材类型或多产物布局。
- 验证方式:后端定向测试覆盖角色全尺寸矩阵:`nanobanana2` 的 `0.5K / 1K / 2K` 和 `gpt-image-2` 的 `1K / 2K`,每档均覆盖 `1:1 / 4:3 / 3:2 / 2:3 / 9:16 / 16:9`,30 个组合全部构造大于目标尺寸的真实 PNG provider 回图并执行像素恢复,不只校验字符串映射;另覆盖欠尺寸禁止放大、比例超限、BgFilter 错比例 alpha 蒙版拒绝和组合告警。同时从函数调用顺序上固定“尺寸归一 → 持久化带背景原图 → BgFilter”。运行 `cargo test -p api-server editor_project --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
---
## 2026-07-29 图标图集 BgFilter 开启 cross-check
- 背景:图标 spritesheet 的透明化需要提高主体内部孔洞、轮廓和相邻小图标边缘的交叉校验质量。
- 决策:生成图标素材的 BgFilter `background_mode=flat` 请求固定显式传 `cross_check=on`,与角色形象和角色动作逐帧去背一致;UI 设计图素材提取及手动 complex 去背景继续传 `off`。该参数仍属于后端内部供应商策略,不进入前端 DTO 或外部 OpenAPI。
- 边界:不修改 BgFilter fallback、Alpha 回贴、默认关闭 despill、图标切片、OSS / 资源 / 画布持久化和任务告警语义。
- 验证方式:运行 `cargo test -p api-server editor_canvas_screen_background_generation_uses_bgfilter_postprocess --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:rustfmt`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
---
## 2026-07-23 画布 Agent 工具生命周期统一经 object-safe trait 分派
- 背景:画布 Agent 八类工具的参数规范化、确认展示、计价与 worker payload、完成结果格式化和媒体投影分别在 `tool_args.rs`、`display_args.rs`、`api.rs`、`reconcile.rs` 重复按工具名分派;新增或调整工具时容易漏改其中一处。
- 决策:api-server 以 object-safe `EditorAgentTool: ToolDyn` 取代仅承载计价的 `EditorAgentPricedTool`。trait 的所有动态方法统一接收 `serde_json::Value`;每个具体工具实现自行反序列化为真实 Args / 结果,`validate_args` 与 `format_execute_message` 显式转发到 `platform-editor-agent` 已有强类型实现,再把规范 Args、展示投影、job payload、完成文本或媒体引用擦除回公共类型。`editor_agent_tool(toolName, context)` 绑定当前 `EditorToolContext` 并作为唯一八分支工具名分派;规划、确认和回填不得再维护平行 switch。LLM builder 的工具注册列表保持独立显式维护。
- 边界:不改变工具名、LLM schema、OSS 消息文档、`displayArgs`、模型定价、job kind / payload、dedupe key、worker、计费、完成消息或图片 / 视频 / 音频引用契约,不涉及前端、SpacetimeDB schema 或迁移。
- 影响范围:`server-rs/crates/api-server/src/editor_agent` 的工具 trait、参数规范化、确认入队与终态回填,以及画布 Agent 专题文档。
- 验证方式:覆盖八类 factory 与 dyn validation / pricing / display / job / formatter / media projection 的 api-server 定向测试,运行 `cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_agent`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:rustfmt`、`npm run check:encoding` 和 `git diff --check`。
---
## 2026-07-28 AI 游戏创作资源画布布局使用本地双模式 CAS sidecar
- 背景:项目开发工作台当前只在 React 会话内保存同分类资源的一维拖拽顺序,项目切换或客户端重启后重建默认排列;工作台 PRD 虽已给出二维位置字段,但缺少落盘路径、坐标系、Tauri API、CAS、异常与安全边界,仍不足以直接编码。
- 决策:dependency 与 type 两套布局分别保存为项目内 `.agent/workbench/resource-layouts/dependency.json` 和 `type.json`,统一使用 `game-creator-resource-layout.v1`。`x / y` 是 section 内容 CSS 像素,revision 从缺文件时的 `0` 单调递增;新资源首次默认放置,任何已有坐标不因排序、筛选、模式切换或 resize 被自动覆盖。type 默认布局固定按 `subtype -> mediaType -> label -> id` 排序,manifest 资产使用 `asset.kind`,任务产物、附件与 Agent 文本成果使用稳定 fallback,subtype 同时进入资源协调签名。
- 并发与失败:Tauri 用 `read_local_project_resource_canvas_layout` 和 `update_local_project_resource_canvas_layout` 暴露读写,以 `projectId + mode + expectedRevision` 在专用跨窗口布局锁内做 CAS。更新额外携带只读结果中的 `expectedProjectId` 身份栅栏,路径被重建为新项目时旧窗口在锁副作用前失败;Rust 内部 revision 保留 `u64`,但共享 serde、Tauri 输入和前端 IPC 统一限制为 `0..=Number.MAX_SAFE_INTEGER`,达到上限时保持原文件。锁入口文件持久存在,Unix 以 `flock` 文件描述符、Windows 以不共享句柄持有互斥;应用不按 mtime / PID 猜测 stale、不删除锁文件,进程退出由操作系统释放。更新在创建锁目录前只读验证 manifest,锁内复核 projectId;无效根保持零 workbench 副作用。前端以 project/path/mode epoch 丢弃旧 scope 迟到响应,资源变化不得取消首读或同 scope 在途写;同 scope 的手动拖动与资源协调进入单写者 FIFO,后一笔只使用前一笔权威响应的 revision。切换 scope 会释放旧活动槽,旧请求即使卡死也不能阻塞新 scope;同资源尚未发送的连续拖动折叠为最后坐标,已经在途的 CAS 不取消。冲突返回最新完整布局且零写入,前端载入最新值、丢弃基于旧快照排队的手动拖动并要求重新操作;资源协调最多追加两次冲突重试,普通失败恢复最近可信布局。写入复用项目安全路径、链接校验、容量上限、恢复副本与原子替换,损坏或身份冲突不能被空布局覆盖。
- 业务边界:布局是本地工作台 UI sidecar,不进入 manifest,不推进游戏项目 mutation revision,不使 Runtime verification 失效,不触发 Agent 权限,也不属于资产、Agent 产物、Git 或云端事实。本切片不包含关系线、资源替换、浮层位置、缩放 / 平移、搜索 / 筛选条件和当前 mode。
- 影响范围:`packages/shared` 与 Rust `shared-contracts` 的跨边界 DTO、AI 游戏创作 Tauri 项目持久层与命令、项目开发资源画布、定向 Rust / React 测试、工作台 PRD 和客户端实施计划。
- 验证方式:序列化与字段上限测试、缺文件 / 损坏 / 原子恢复 / 链接安全测试、同 revision 双写最多一个成功、两种 mode 跨重启独立恢复、新增资源不移动旧坐标、`1280×800` 横屏无页面级溢出,以及 `npm run agc:typecheck`、定向测试、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-31 generic-v1 使用稳定观察与当前场景指纹关闭试玩假阳性
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
- 背景:External Runner 和 Tauri 客户端各自拥有进程内 `PreviewRegistry`。Runner 完成 `preview.validate` 后,其 server 与 running 状态不会出现在 Tauri registry,导致已有可玩版本时用户预览不自动出现;后续 revision 即使验证成功,既有 iframe 也可能继续显示 WebView 缓存中的旧资源。顶部只写“未启动”还会让用户无法判断是 Runner、Runtime 还是预览未启动。
- 验证方式:以当前 run 成功 `preview.validate` revision N 后断言 iframe 自动出现且 server 归 Tauri registry;再完成 revision N+1,断言 server 进程和 loopback origin 不变、iframe 重新加载新内容且所有响应为 `no-store`。Runner registry 单独 running 不得让页面显示预览;相同 / 更低 revision 不得刷新;停止预览后顶部必须显示“预览未启动”;构建产物和安装信息必须为 `0.1.1`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-08-03 开放 Issue 115、118、127、128 的修复边界
- AGC 的 MCP 目录以 server 为隔离单元:可选 server 的连接、tools/list、工具归一化或聚合容量失败只关闭该 serverrequired server 仍失败关闭;MCP schema 包入原生 action 后只沿 subschema 关键词重定位当前 document 根的 JSON Pointer fragment`default / const / examples / enum` 等数据值、命名 anchor 与 `$id` resource 内 fragment 保持不变。Anthropic strict 不由 `apiKind` 单独推断:AGC 只对官方 HTTPS endpoint 与 Claude 4.5+ 版本化 model id 显式开启,旧模型、未知别名和兼容网关默认关闭。开启后使用官方支持关键词白名单生成专用传输 schema,剔除不受支持的约束但不修改调用方原 schema;未知关键词、不可解析 / 递归 `$ref` 或请求复杂度超限时保持 non-strict。最后一个工具设置 ephemeral prompt-cache breakpointusage 统计把 cache creation / read token 一并计入 prompt 和 total。
- Windows 私有 ACL 检查复用 `Get-Item` 对象的 `GetAccessControl()`,避免从 PowerShell 7 启动时继承的模块路径让 Windows PowerShell 5.1 的 `Get-Acl` 加载不兼容模块;静态配置门禁禁止重新引入该命令。
- 编辑器持久化的 `prompt` 统一表示规范化用户意图;provider `actual_prompt` 只保留在 resource / asset 审计字段,系统 prompt 不进入跨资源检索字段。角色和图标的透明图、切片继承源用户 prompt;本次只修新写入,不迁移历史记录,不修改 SpacetimeDB schema。
- 画布收到生成完成等较新权威快照时,必须把同项目待保存或在途的本地布局重放到新 revision:后端资源与生成终态优先,本地布局编辑优先;后端新增项合入,后端删除项和用户本地删除项均不得复活,合并后立即进入既有串行 CAS 保存队列。
- 生成器合并必须把 `status / composerOpen / generatedLayerId / errorMessage / generation timestamps / characterAnimationResult` 视为后端生命周期事实;生成完成快照继续保持 `composerOpen=false`,不得被本地在途快照重新展开。提示词、参数和占位位置等本地布局编辑继续保留。
- 同项目权威快照刷新不得无条件选择第一张图层:当前仍有效的单选、多选和生成占位选择保持,已删除的选择过滤,本来未选择时保持空选;只有首次载入或切换到另一项目时才默认选择第一张可用图层。生成完成结果需要用户显式点击后才进入选中态,后台完成回包不能偷走用户当前焦点。
## 2026-07-30 Provider 503 等待与耗尽状态使用严格字段派生的安全摘要
- 决策:重试资格继续按稳定错误类别判断,durable retry record 额外保留精确且不含正文的 `upstream-<status>`;等待态从 record 派生 HTTP 状态、真实 attempt 上限和退避剩余秒数。耗尽态只在 `kind/httpStatus/fingerprint/chars/retryAttempt/maxRetries/retryState` 全部严格匹配时派生安全中文摘要;Runtime 私有机器字段可供确定性投影,但状态卡和持久 conversation 不显示 fingerprint、字符数、绝对路径、脱敏占位符或 Provider 正文。所有写入“后台任务失败”conversation 的生产分支统一经过同一安全 formatter,其它错误只显示固定失败文案。
- 验证方式:Rust mock 503 覆盖等待、恢复与耗尽,断言 exact HTTP status、attempt、sidecar 清理和正文零泄漏;前端模型覆盖状态卡优先级、严格字段解析、字段不一致与尾随正文失败关闭;conversation 测试覆盖 Provider URL/query、API Key、绝对路径、fingerprint、chars 和 redaction marker 均不可见。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
- 背景:独立包原先在 Tauri 最终退出时仅调用一次 `runner.shutdown_if_idle`;若 Runner 正忙便返回 busy 且没有稍后关闭闩锁,Runner 和后台命令会永久残留。`command.exec / project.verify` 只有进程组 flagSTDIO MCP、Git 和清理命令也缺少 `CREATE_NO_WINDOW`,因此 Windows release 会连续弹出多个控制台窗口。
- 验证方式:定向测试覆盖专用 RPC 的 draining / shutdown 与普通 idle 语义不变;正式安装包在活跃任务期间确认无后台控制台窗口,关闭主窗口后核对 Runner、MCP、command、ConPTY 和孙进程全部退出,再启动确认 durable 状态正确 reconciliation。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-07-30 Windows Runner 新建私有控制文件在原子安装前初始化 TokenUser owner
- 决策:既有 durable 文件的读取继续严格拒绝 foreign owner。只有本进程以 `create_new` 创建且仍持有 Windows `share_mode(0)` 独占句柄、并通过普通文件 / 非 reparse / 单链接检查的临时文件,才在写入和原子安装前初始化 `TokenUser` owner 与 protected 私有 DACL,失败时关闭句柄并清理刚创建的文件;安装后继续严格复核。固定 `agent-runner.lock` 的 stale 恢复还要求父目录是已验证的私有 AppData;活锁不可接管或截断,只有 sharing / lock violation `32/33` 表示占用。父进程观察到 Runner 子进程退出后立即返回真实错误,不再空等启动 deadline。Tauri `.setup()` 内的致命失败在记录日志后直接显示诊断路径。
- 验证方式:Windows 定向测试覆盖 endpoint 原子写入与 TokenUser 复核、new/stale lock owner 修复、活锁不截断、hardlink / reparse 不触碰目标、project-owner 诊断 TokenUser 复核、错误码精确分类,以及子进程退出在 2 秒内返回;正式包在现场 TokenOwner 为 Administrators 的机器上必须依次出现 `startup.runner.start.complete`,创建项目任务时 execution-owner 诊断也必须成功。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
- 背景:Windows 安装包首次创建 AppData 时若沿用继承 owner,目录 owner 可能是 Administrators 而非当前登录用户;后续严格 owner 校验会让客户端在 `.setup()` 阶段退出,且无控制台 release 缺少可见诊断。直接修改 foreign-owner 旧目录的 ACL 还可能覆盖其它主体持有的数据或跟随 reparse 路径。
- 决策:新建客户端 AppData 时以进程 `TokenUser` SID 显式设置 owner 和当前用户私有 DACL,不使用 `TokenOwner` 代表用户。历史 foreign-owner 真实目录先原子重命名为同级唯一 `.owner-mismatch-backup-*`,再重建并回读验证安全目录;任何 reparse / junction / symlink、备份冲突或迁移失败都失败关闭,不在旧目录上放宽权限。独立 release 的 `startup.log` 和记录 Runner stdout / stderr 摘要的 `agent-runner.log` 均采用 256 KiB 上限、仅一份 previous 和脱敏写入;AppData 日志不可写时 `startup.log` 回退系统 TEMPTauri URL、AppData、Runner、`.setup()` 或 `.build()` 初始化失败时在 Windows 显示包含诊断日志位置的错误对话框。
- 验证方式:Windows 定向测试覆盖 TokenUser owner、私有 DACL、foreign-owner 同级备份不覆盖和 reparse 拒绝;诊断测试覆盖 256 KiB 单 previous 轮转、凭据与绝对路径脱敏、AppData 不可写时 TEMP 回退和初始化失败对话框。安装包 smoke 后保留旧备份证据并确认新 AppData 可写、Runner 可启动。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-28 AI 游戏临时页面启动消息采用 URL 消费加页面闩锁
- 决策:首屏只读取一次 `initialMessage`,随即使用同源 history 替换从 URL 删除该参数;App 同时以启动项目绑定页面级闩锁。StrictMode、组件重挂载、HMR、整页重载、Runtime 终态和项目切换均不得重投,旧消息也不得投给另一个项目。
- 验证方式:AppSurface 覆盖 StrictMode、组件重挂载、终态变化、项目切换和 URL 二次消费;真实长运行中再次触发前端热更新后,Supervisor 队列不得新增相同任务。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-28 AI 游戏创作视觉产物使用规范图 DAG 和玩法无关 UI 合同
- 背景:游戏图集曾被普通生图代替,且 UI 生成与视觉验收硬编码单位卡槽、波次和敌人入口,导致贪吃蛇等非塔防项目即使调用正确接口也生成错误内容。
- 决策:复用现有 16 任务图,固定 `art-director -> design-foundation -> art-asset-plan` 三 owner DAG。规范图走 images generations `kind=spec`UI 精确引用当前规范图 resourceId,走同一路由 `kind=ui-design`;透明图集使用同一 resourceId 和具体 `iconDescriptions`,只走 icon-spritesheets generations。UI extraction 仅用于已有带标注 UI 图,不参与该 DAG。
- 内容与验收:UI prompt、art spec、图集分类和 `ui-prototype.v2` 必须从当前任务与 `game/game_design.md` 提取真实玩法,不得预设塔防或补入不存在的单位、卡牌、波次、敌人入口。v2 检查信息 HUD、可玩区域、关键实体、主要操作、失败/重开、移动布局意图、实现清晰度和原创主题;`ui-prototype.v1` 只供历史审计安全读取,不能放行新建或恢复 run。
- 替换与透明度:旧正式图只能由 Supervisor 认领原合同后签发的唯一 repair 原位替换,禁止先删图。`postprocess-failed-source-preserved` 源图不得登记为透明交付物;正式图集必须有真实 `alpha < 255`。
- 影响范围:AI 游戏创作 Runtime 生图路由、策划/美术 Agent prompt、manifest 溯源、UI 视觉审计、External Editor API skill 和技术方案;不新增后端接口或平台玩法入口。
- 验证方式:定向 Rust 测试锁定专用路由、精确规范图引用、告警分类、真实 alpha、玩法无关 prompt 和 v1/v2 审计边界;完整生成后用当前 revision 的桌面/移动真实试玩和 PNG 证据验收。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`.codex/skills/genarrative-external-editor-api/SKILL.md`。
## 2026-07-21 AI 游戏创作客户端采用薄入口与领域模块
- 背景:`apps/ai-game-creator-shell` 的前端入口、Tauri 项目能力、Rust 测试、界面测试和真实 Runtime E2E 随能力增长形成超大文件,单文件所有权已经影响并行开发、审查和定向验证。
- 决策:入口文件只负责依赖组合、模块注册和稳定导出;生产实现按认证、配置、Agent Runtime、项目摘要、项目持久化等功能域拆分,测试与 E2E 按 suite / 场景域拆分并保留稳定注册顺序。不同 Agent 并行重构时必须使用互斥写入目录,由主线程统一审查和执行完整门禁。
- 拆分边界:重构不得改变 Tauri command、前端公开导出、测试名称、测试数量、E2E CLI 参数或持久化格式;不得用 `include!`、运行时读取源码、整文件字符串拼接或把原文件整体搬到另一个超大文件来规避体量问题。共享 helper 只在确有多模块复用时上提。
- 影响范围:`apps/ai-game-creator-shell/src`、`src-tauri/src/project`、`src-tauri/src/tests`、`tests/appSurface`、`scripts/agent-runtime-real-e2e` 和客户端实施计划。
- 验证方式:对比拆分前后测试名集合,运行 shell ESLint / Prettier / typecheck、完整界面测试、Tauri Rust 测试、真实 E2E self-test、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-18 AI 游戏创作正式项目页升级为 GameAgent 工作台
- 背景:新的《陶泥儿GameAgent-V1.0 项目开发界面需求》要求正式项目开发页同时承载资源管理、运行表现层、陶泥儿对话和子 Agent 状态,旧的“正式用户页只有主聊天与只读专业 Agent 列表”已不足以支撑目标交互。
- 决策:在现有 `apps/ai-game-creator-shell` 项目开发入口内扩展单一工作台,不新建平行客户端。首版从当前 manifest、导入附件和 Agent 状态派生界面,提供资源 / 运行切换、资源排列与聚焦、审批弹层和底部状态栏;真实游戏通过现有 localhost 预览 server 直接载入客户端内受限运行容器,不再调用系统外部浏览器。未具备正式写回契约的拖拽布局、版本资源替换、数值微调、泥点累计、Agent.md 和 Skill 管理不得在前端伪造成功。
- 横屏窗口:当前独立 App 只交付横屏桌面工作台,`client` 默认窗口固定为 `1280×800`,最小窗口固定为 `1280×720`。工作台按壳内剩余视口排布并收紧四周留白;消息区与 Runtime 区各自承担内部滚动,专业状态增长不得把输入区或底部 Agent 栏推到视口外。窄屏纵向布局不作为当前客户端验收目标。
- 影响范围:`apps/ai-game-creator-shell` 正式项目开发页、项目工作台前端测试、AI 游戏创作智能体 App 实施计划和原生壳预览门禁。
- 验证方式:运行 AI game creator shell 定向测试与 typecheck、`npm run ai-game-creator-shell:check`、`npm run check:encoding`、`git diff --check`,并用真实浏览器检查 `1280×720` 最小横屏、`1280×800` 默认窗口与目标桌面视口布局。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-17 AI 游戏创作 V1.31 使用同一父 run 收束静态与隔离协作
- 背景:V1.30 已证明 Supervisor 能在无编排配方的真实终端任务中自主选择多个 static 专业 Agent,但尚未证明同一父 run 同时存在 static delivery/claim 与 isolated all-join 时,等待、唤醒、恢复和唯一 finalization 可以组合。两类协议分别通过不能替代组合证据。
- 决策:继续复用 `project-supervisor`、`--swarm-chat`、External Runner 和既有 durable 事实源,不新建第二套调度器或结果协议。用户任务只描述业务范围;仓库规则可声明验证要求和安全禁用边界,但不写 Agent 编排工具、调用顺序或 Runner 配方。Supervisor 在同一目标同时包含长期专业交付与临时隔离检查时,首个协作批次不得遗漏任一类。
- 完成边界:static delivery/claim 与 isolated group/result/join delivery 保持各自状态机,但全部绑定同一个 parent Agent/Session/runwaiting phase 只投影当前首个 blockerRunner 恢复和 finalization 必须在项目锁内重新枚举两类事实源。两类结果都已认领且其它 blocker 清零后,原 Supervisor run 才能写唯一用户 assistant。
- 验证:确定性基线统一运行 `project_supervisor_mixed_`,真实行为运行 `npm run agc:mixed-swarm-e2e -- --config-dir <AppData>`。失败尝试不得和后续轮次拼接;详细拓扑、一次性计数与当前 PASS 报告只维护在 Runtime V1.31 技术方案,不复制进长期共享决策。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-17 AI 游戏创作 V1.30 使用自主 Supervisor 终端门禁和语义消息收敛
- 背景:V1.28 已证明预置双专业方向下的合同委派、repair、Runner 恢复和唯一回复,V1.29 已证明受控瞬态重试;但二者都没有证明 Supervisor 在用户不提供 Agent ID、数量、并行或 repair 配方时会自主编排,也没有把重复 `agent.message` 的持久幂等与后台 loop 有界收敛串成完整证据。
- 自主编排:保留 `project-supervisor` 作为正式用户唯一对话与最终回复 Agent。`supervisor-swarm-autonomous-chat` 必须通过真实发布二进制的 `--swarm-chat` 接收纯业务任务,由 Supervisor 在同一 native planning 批次自主选择至少两个不同规范专业 Agent;真实 child Provider 生命周期必须重叠。弱交付只能由 Supervisor 按 acceptance criteria 做语义裁决,并在同一父 Session/run 创建唯一、完整继承原合同的单层 repair。终局以 durable delivery/claim/receipt/finalization、唯一 Supervisor assistant 和单行脱敏 `turn.report` 为事实源。
- 消息收敛:`agent.message` 的语义身份固定为来源 Agent/run、目标 Agent/已解析 Session 与清洗截断后正文 SHA-256。相同语义重放必须复用唯一 conversation message 与 `agent.runtime.agent.message` 审计,冲突失败关闭;不同来源、run、目标、Session 或正文仍是新消息。重复调用返回 `messageAppended=false`,不算上下文窗口的新进展,也不能替代专业 Agent 自身最终回执。持续重复时最多在当前 6 轮停滞窗口结束后进入 `failed / budget-exhausted / loop-budget-exhausted`,原 `in_progress` 计划保持原样,不能写 completed 或成功回复;每次 Runtime action/observation/receipt 仍完整留痕且公共 receipt 不保存正文。
- E2E 隔离:自主 suite 的 sentinel AppData 必须创建在正式 AppData 同级,不能嵌套在源目录;正式目录只读,配置副本、source-dir guard、endpoint 身份、CLI 调用计数和自动清理均进入硬门禁。父 Supervisor 在 repair 前执行的 `project.verify` 属于合法宿主验证,harness 只能拒绝其它意外父 pending action,不能把父验证和专业 Agent 修改确认一刀切。
- 验证:最终正式 `openai_chat / gpt-5.5` 诊断轮为 PASS:无编排配方任务下完成双专业 Provider 真重叠、2 个初始 delivery、1 个 repair、2 个 Observed claim、pidfd Runner 强杀/boot 恢复、同一父 Session/run、严格宿主验证、唯一正式 assistant 和 3 条内部专业 assistant。51 个 Provider request identity 全部 `started -> completed`28/28 成功计划和 19/19 格式修复全为 `native_runtime_tools`;重复、残留 sidecar、Provider payload、私有正文、API Key、诱饵、项目/配置路径和报告泄漏均为 0。确定性完整 loop 回归另证明 6 次重复消息 action 全部落账、目标消息/两类消息审计各 1 条、第 6 轮预算失败且第 7 次 Provider 请求、compaction 和 completed 均为 0。
- 范围:V1.30 证明自主 static 专业编排与真实终端聊天可组合;同一父 run 的 static delivery + isolated all-join 真实组合,以及 Tauri/WebView 宿主级 Supervisor E2E 仍是独立后续门禁。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-17 AI 游戏创作 Swarm 显式重试必须使用受控真实故障门禁
- 背景:V1.28 `supervisor-swarm` 正式报告的 46 个 Provider request 全部 completed;确定性测试和条件式 E2E validator 虽覆盖 retry 契约,但 `failed=0 / retry=0` 仍可 PASS,不能证明真实 Provider Swarm 进入过显式重试链。
- 决策:保留正常 `supervisor-swarm` 作为合同委派/repair/恢复协议门禁,另设 `supervisor-swarm-transient-retry`。新 suite 用 sentinel 管理的隔离 AppData 只覆盖一个专业 Agent 的 `baseUrl / maxRetries / retryBackoffMs`;本地 loopback 代理在首个 POST 转发正文前断线,后继请求先暂停。暂停期间必须证明唯一 `started -> failed`、唯一 retry audit、不同 request identity、稳定 `-transient-1` slot和相同逻辑身份,同时 action、receipt、目标 Agent 子委派、claim、assistant、pending、project revision、目标产物和 upstream forwarding 全为 0,之后才允许真实 Provider 请求继续。
- 安全:代理不记录或返回 upstream URL、headers、Authorization、请求/响应正文或凭据,只公开计数与布尔状态;只接受 loopback origin-form POST 和原 base pathredirect 原样返回而不跟随。E2E 启动 CLI/Runner 时必须合并并同时覆盖 `NO_PROXY / no_proxy`,显式加入 `127.0.0.1 / localhost / ::1`,避免继承的系统 HTTP 代理先接触发往故障代理的凭据和正文。端口 0 耗尽时使用有界 loopback fallbackstop 必须幂等关闭全部上下游连接。隔离 AppData 创建在正式目录同级,source-dir guard 禁止本 suite 前缀进入源目录或留下残留项;源配置私有副本逐字校验,正式 Runner endpoint 身份保持不变,临时合并配置与 overlay 为 `0600` 并由 sentinel 删除。并发正式 Runner 的 heartbeat 可改变目录 mtime/ctime,不得据此把外部写入误归因给 suite。完整链后续失败时,partial report 仍必须回填已经取得的 retry checkpoint 和零副作用证据。
- 验证:最终加强版正式 `openai_chat / gpt-5.5` 报告为 46 个 request identity、46 started/terminal、45 completed、1 failed、1 retry;受控重试前所有副作用计数为 0。代理观察到的 10 个目标 Agent 请求与该 Agent lifecycle 数量一致,其中 1 个注入失败、1 个暂停、9 个转发。放行后双专业 Agent 真重叠、2 初始 + 1 repair delivery、2 个 Observed claim、targeted contract read、pidfd Runner 强杀恢复、唯一 Supervisor assistant 和 3 条内部专业 assistant 全部成立;27/27 成功计划与 14/14 repair 全为原生工具协议,源 AppData 未被写入,重复、残留和敏感泄漏均为 0,代理、隔离 Runner/AppData/项目全部清理。
- 范围:该门禁证明“显式重试可与既有 Swarm 完整链组合”,不证明 Supervisor 已在无 Agent ID、同轮或 repair 次数提示时自主选择编排。自主 suite、真实 `--swarm-chat`、static+isolated all-join 组合和 Tauri 宿主 E2E 保留为后续完成项。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-16 AI 游戏创作 Agent Runtime V1.28 Supervisor 合同委派与单回复收束
- 背景:V1.16 已建立 Supervisor 的 durable static delivery/claim 和同一父 run 唯一回复,但旧 `agent.delegate` 只描述目标与任务,Runtime 只能确认子任务终态,不能持久证明预期产物、验证证据或返工关系;实施计划中也仍有普通用户进入单 Agent 对话的旧表述。
- 决策:`project-supervisor` 固定为正式用户唯一默认对话与最终回复 Agent。专业 Agent 和 isolated child 只向父 run 提交内部回执、摘要与证据;开发窗口单 Agent 直调和 `agc:swarm` 调试不获得正式用户回复所有权。
- 正式 GUI:登录后的单窗口客户端从首页创建项目或从项目组打开已有项目后,项目开发页只挂载 Supervisor 用户面。首条需求直接投递 active `project-supervisor` Session;全新项目还没有该 Session 时先通过既有 Session 命令创建并设为 active;已有非终态 run 的后续输入使用 same-run steer。普通用户只看到 Supervisor 对话、紧凑 Runtime 状态、确认/Needs input 和专业 Agent 协作只读状态;专业 Agent picker、Session 管理、完整计划和工具台继续只属于开发入口。
- 对话与配置边界:legacy `.agent/conversations/project.jsonl` 只作有界兼容读取,正式 Runtime user/流式草稿/final assistant 不再由 React 双写到 legacy 项目对话,规范消息只归属 Supervisor Session。正式项目页缺少 LLM/AppData 配置时只显示 Runtime 错误,由单窗口壳全局“配置”入口处理,不自动弹开发配置框。
- 合同:新 native `agent.delegate` 的 strict schema 固定携带 `agentId / task / acceptanceCriteria / expectedArtifacts / repairOfDelegationId / runId`,六个字段均必填,后两者可为 `null`。`acceptanceCriteria` 为 1-8 项;`expectedArtifacts` 为 0-16 个精确项目内非私有相对文件,不接受 glob。旧持久 action 缺字段按空合同恢复,不迁移已有 pending/delivery/claim sidecar。
- 交付与门禁:durable delivery、ready receipt 和 claim 快照原样保存合同及 `structuredResult`;结构化结果包含 `contractStatus=evidence-ready|needs-repair`、artifact path/SHA-256、`missingExpectedArtifacts`、`verificationRequired`、`verifiedRevision`、安全 `evidence/error`。Runtime 只在 child completed、预期产物齐全、必要 verification passed 时判 evidence-ready;语义是否满足仍由 Supervisor 按 acceptance criteria、摘要和证据裁决。
- 返工:Supervisor 只有在同一父 run 已认领原 delivery 后,才能为 needs-repair 或语义未通过发出 `repairOfDelegationId=<原 delegationId>` 的新委派。repair 必须完整继承原合同并交回原专业 Agent,深度固定为 1,同一原 delivery 同时最多一个非 suppressed repair;相同 durable action 重放幂等复用,不同重复或并发竞争拒绝。`suppressed` repair 不算完成,同一 action 可在无终态字段时原地恢复;若该 action 已持久失败,新 action 只可在所有既有 repair 均 suppressed 时重做基础设施投递。repair 继续在原父 Session/run 收束,不产生第二条用户回复。首次 repair 被合同继承门禁拒绝时,失败 observation 返回同一 durable delivery 的完整权威合同,Supervisor 可据此直接逐项修正;字段缺失或身份不确定时再按 `delegationId` 定向重读。
- Prompt 与完成:专业 Agent task prompt 必须携带完整合同并明确只交内部回执;Supervisor prompt 明确不得把 evidence-ready 自动当作语义通过,也不得忽略 needs-repair。无法自行裁决的问题统一通过既有 `user.input_request` 汇总询问用户。所有必要 delivery/claim/repair、结构化计划、verification、确认、用户输入及其它既有 blocker 清零后,才允许原 Supervisor finalization 写唯一 assistant。
- 计划推进:单独 `update_agent_plan` 只用于步骤或状态真实变化;当前 `in_progress` 步骤已具备事实、权限和合同后必须在同一响应附带具体 action,格式修复不能把可执行动作退化为 explanation-only checkpoint。未完成计划 blocker 和 prompt 必须明确该规则,避免 Supervisor 理解了下一步却持续空转。
- 多 action 原批次:Provider 同轮返回 2-3 个 action 时,Runtime 先持久化绑定完整 planning 身份与稳定 actionId 的私有批次,再对整批完成策略/MCP preflight。任一拒绝保证零工具执行;所有确认收齐后才从 action 0 按原顺序 dispatch。cursor 只在 observation、投影、receipt、Agent DB 与 context 全部落盘后推进;Runner 重启按原 cursor 补投影,steer、仓库漂移或非 ok observation 会持久作废剩余后缀。批次 sidecar 清理前,空 action 收束与 finalization 都必须阻断。
- Provider 瞬态失败显式重试:`agentLlm.<agent>.maxRetries / retryBackoffMs` 由 Runtime 解释为独立物理尝试及其有界指数退避,不得在单 lifecycle 内恢复 `LlmClient` 隐式 HTTP 重放。每次尝试都重建禁用自动重试的 client,并形成自己唯一的单次 lifecycle;首次 request slot 不变,第 `N` 次重试稳定使用 `-transient-N` 后缀。只有 `timeout / connectivity / transport` 可进入重试;工具协议无效继续使用独立 `repair-N` 格式修复,其它错误与重试耗尽按原失败路径收束。退避后必须重新检查 Goal、steer、cancel、task/run 和 orphan lifecycle,控制请求可阻止下一次尝试;Runner 强杀后无可信终态的 `started` 仍进入 reconciliation,不能自动补发。重试发生在解析与副作用之前,不创建 action、pending、receipt、delivery、assistant 或 revision;既有控制、Runner、orphan、finalization 和隐私边界不放宽,公共审计不得保存 Provider 正文、arguments、凭据或绝对路径。
- 影响范围:AI 游戏创作 Agent Runtime 的 native tool schema、静态委派 delivery/claim/receipt、恢复与 finalization、Supervisor/专业 Agent prompt、正式用户对话入口、确定性测试和真实 Provider E2E;编码级细节以 Runtime V1.28 章节为准。
- 验证方式:确定性回归覆盖 strict schema、旧 action 空合同恢复且 sidecar 不迁移、合同跨 Runner 重启、artifact/verification 客观门禁、语义验收边界、单层唯一 repair、并发幂等和 final barrier。真实 `gpt-5.5` swarm 必须证明同一 Supervisor run 下两个专业 Agent 真并行、一份弱交付恰好触发一次 repair、唯一 Supervisor assistant、重复 action/receipt/message 为 0、敏感信息泄漏为 0。
- 当前状态:PASS。2026-07-17 正式 `openai_chat / gpt-5.5` `supervisor-swarm` 已证明同一 native 批次双专业委派、真实 Provider 重叠、2 份初始 delivery、1 次 targeted contract read、唯一 repair、pidfd Runner 强杀/boot 恢复、同一父 Session/run、唯一 Supervisor assistant 和专业回复仅内部可见。报告为 46/46 Provider lifecycle 闭合且 completed,成功计划 24/24、格式修复 20/20 全为原生工具协议;重复、残留 sidecar、正文/凭据/绝对路径/报告泄漏均为 0。真实门禁同时发现并修复 `agent.message` 公共审计保存绝对 conversation path 的缺陷,现统一保存 `.agent/conversations/...` 项目相对路径并有定向回归。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-16 AI 游戏创作 Agent Runtime 只并行持久只读批次
- 背景:V1.26 已允许 Provider 一轮返回最多三个原生工具 action,但同一 Agent 仍逐个执行;直接把 action future `join` 会因同步文件 I/O、单 pending sidecar 和项目一致性锁而形成假并行,并破坏 steer、崩溃恢复与 exactly-once。
- 决策:同一 Agent 只把连续 2-3 个自动批准的严格只读工具组成 durable parallel-read batch。首版白名单为 `memory.read`、`conversation.read`、`asset.list`、`project.search`、`project.diff`、`git.inspect`、`file.list`、`file.read`、`task.list`;所有写入、命令/进程、确认、Provider/MCP、生成、Git commit、委派和回执认领工具继续串行。批次在项目一致性锁内完成 preflight、executing、线程并行读取和 observed 落盘,控制请求只在批次边界前或后线性化;终态观察仍按 Provider 顺序投影。
- 恢复与安全:批次成员使用稳定 actionId/指纹。Runner 在 `executing` 中退出只允许同身份重放严格只读物理读取,`observed` 只补齐幂等投影;不能新建 Provider lifecycle、action 或 receipt。公共审计只记录身份、工具名、计时与重叠结论,不记录参数、观察正文、绝对路径或凭据。任何分类、策略、Goal、steer、repository context 或持久身份不确定都失败关闭或退回既有串行路径。
- 影响范围:AI 游戏创作客户端 Rust Agent Runtime、Runner 恢复、定向测试、真实 Provider E2E 和 Runtime 技术文档。
- 验证方式:`parallel_read_batch_` 的 8 项确定性用例覆盖真实重叠、稳定顺序、串行屏障、控制竞态、取消/Goal/repository drift、`executing` 重放和 `observed`/部分投影恢复去重;正式 `openai_chat / gpt-5.5` 独立 suite 必须证明模型自主发出至少两个同轮读取、时间区间真实重叠、同 run 唯一回复、原生协议、零重放/重复/泄漏和隔离清理。
- 真实结论:2026-07-16 隔离 `parallel-read` suite PASS。真实模型同轮提交 2 个独立 `project.search`,单一持久批次重叠 `10,969,247ns` 且按 Provider 顺序投影;4/4 个成功工具计划与 2/2 个 repair 全为 `native_runtime_tools`6 个 tool-plan 与 1 个 final-reply lifecycle 唯一闭合。最终 assistant/completed 各 1,重复 action/receipt/Provider lifecycle、遗留 finalization/批次 sidecar、私有正文、API Key、诱饵、项目/配置路径和报告泄漏均为 0,隔离 Runner/AppData/项目完整清理。V1.27 当前门禁为 PASS。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`。
## 2026-07-16 AI 游戏创作 Goal 真实验收强制原生工具协议
- 背景:V1.18 的 `goal-runtime` 已证明 edit/pause/Runner 强杀/resume/finalization,但该 PASS 早于 V1.26 原生工具目录;旧验收只接受协议兼容集合,无法证明长任务没有静默退回 wrapper/text JSON。阶段等待在 Runtime 已因外部 transport 失败时还可能继续等 30 分钟。
- 决策:Goal PASS 必须要求同一主 run 的全部成功 tool-plan 和 repair audit 都是 `native_runtime_tools`wrapper/text fallback 为 0function call 数量、call id、函数名数组完整且协议审计不含 arguments/response/toolArguments。PASS、部分失败与空报告统一输出协议计数。旧动作 blocked、revision 2 失败验证和写动作 settle 等长等待必须同步读取 task/runtimefailed、cancelled、budget-exhausted 或 needs-reconciliation 立即结构化失败。
- 影响范围:`apps/ai-game-creator-shell/scripts/agent-runtime-real-e2e.mjs`、AI 游戏创作 Runtime 与 App 实施计划;生产 Goal/Runner 状态机不改变。
- 验证方式:正式 `openai_chat / gpt-5.5` 加强版 `goal-runtime` 最终成功计划 21/21、repair 17/17 全原生,fallback/协议 payload 为 0Goal revision 1 -> 2、真实失败后 patchset 修复、pause、pidfd Runner 强杀、paused 零推进、显式同 run resume、verification、四阶段 finalization、唯一 assistant、零重复/重放/泄漏全部 PASS。独立 transport failure 报告的成功 6/6、repair 5/5 仍全原生,并促成 terminal fail-fast。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-16 AI 游戏创作 Agent Runtime 使用 Provider 原生工具目录
> 后续更正:本条把 Anthropic 与「历史 fixture 和旧响应」并列为 wrapper/text JSON 兼容对象的描述,已由 2026-07-27「Anthropic 与流式统一使用 Provider 原生工具」取代;Anthropic 现在与 Chat / Responses 一样发送原生工具目录,text JSON 只剩历史响应与 fixture 兼容。下文保留作历史记录。
- 背景:OpenAI-compatible planning 虽已使用 function calling,但只向 Provider 提供 `submit_agent_tool_plan` 包装函数,真实工具藏在 `actions[].tool + input` 中,具体工具名和参数主要依赖长提示词,Provider 不能按工具 schema 约束选择与输入。
- 决策:OpenAI Chat / Responses 直接获得稳定的 `update_agent_plan`、`respond_to_user`、每个内置 Runtime action 和动态 MCP function。内置名称从规范 tool id 映射,MCP 名称从 server/tool 身份派生;真实 MCP binding 与 fingerprint 由 Runtime 注入。每个 action 携带非空 reason 与独立 input schema,一轮最多 1 次计划更新和 3 个动作,或计划更新加最终回复;动作与回复不得共存。plan-only 是合法持久 checkpoint,应用后继续同一 run planning,未完成计划和项目验证门禁继续阻止最终化。
- 兼容与安全:新请求和 repair 不广告旧 wrapperparser 只为 Anthropic、历史 fixture 和旧响应保留 wrapper/text JSON 兼容。未知函数、重复 call id、重复计划/回复、四个动作、正文与 function calls 共存、MCP binding 冲突和非法参数均在副作用前失败。公共审计只保存协议、call 数量、函数名和 call id,不保存 arguments、正文或 MCP 参数。
- 影响范围:`agent_native_tools.rs`、后台 planning 请求/解析/repair、Runtime 协议审计、真实 E2E harness、AI 游戏创作 Runtime 与 App 实施计划。
- 验证方式:确定性目录/parser/Runtime 回归覆盖 plan-only、计划加多动作、计划加回复、顺序与负向边界;正式 `openai_chat / gpt-5.5` 的 `project-skill` suite 最终 9/9 个成功计划和 6/6 个 repair 全为 `native_runtime_tools`wrapper/text fallback 为 0,只读 Skill、单文件修改、Agent/宿主验证、唯一 lifecycle/assistant/completed、零泄漏和隔离清理全部通过。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-16 AI 游戏创作 Agent Runtime 使用项目 Skill 渐进加载
- 背景:单 Agent 已能按目录 scope 应用 `AGENTS.md`,但领域工作流如果全部预加载进每轮 prompt,会长期占用上下文并让无关说明干扰规划;只保存文件哈希又无法证明模型真正读取并遵循了匹配工作流。
- 决策:项目 Skill 只从 `.codex/skills/<name>/SKILL.md` 和兼容的 `.agents/skills/<name>/SKILL.md` 直接入口发现,同名时 `.codex` 优先。`repository-startup-context-v3` 首轮只向 Provider 提供清洗后的名称、描述、入口路径与正文哈希;Agent 判断任务命中后必须通过现有 `file.read` 渐进读取正文和必要引用。Skill 不新增工具或权限,不替用户确认,不放宽沙箱、隐私、验证、finalization、仓库 scope 或副作用重放门禁;适用路径的 `AGENTS.md` 始终优先。active Skill 内容或 metadata 变化必须推进 repository fingerprint,使旧 pending 动作先 blocked 后同 run 重规划。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/repository_context.rs`、Agent planning prompt、pending repository-context drift 门禁、真实 Runtime E2E harness 和 AI 游戏创作 Runtime 文档。
- 验证方式:确定性测试覆盖发现根、优先级、YAML/路径/符号链接/预算、metadata 清洗、正文按需可见和 fingerprint 漂移;正式 `openai_chat / gpt-5.5` 的 `project-skill` suite 必须证明 hash-only fixture 先失败、匹配 Skill 在首个变更前读取、无关 Skill 不读取、唯一目标文件修改、Agent 与宿主验证通过,以及 Provider lifecycle、唯一回复、配置隔离和零泄漏全部闭合。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-23 BgFilter 失败审计使用硬上限与独立 tracking outbox
- 背景:BgFilter worker 每个已发出的失败 provider attempt 都会启动 detached 审计任务;专用 worker 又关闭了 tracking outbox,使任务逐条等待 SpacetimeDB。`Q` 只约束内部 HTTP 请求生命周期,响应结束后无法限制仍在等待数据库的审计任务,部分失败、预算截短 timeout、重试恢复和熔断重置场景下可能持续堆积。
- 决策:BgFilter worker 的失败审计在 `tokio::spawn` 前统一获取进程级 `1024` 个硬上限 permit,满载时直接丢弃并记录低基数指标,不创建等待任务。获准任务优先写入 worker 独立 tracking outbox,目录固定派生为共享 `GENARRATIVE_TRACKING_OUTBOX_DIR` 下的 `bgfilter-worker/` 子目录;worker 启动 outbox flush worker,退出时先排空已获准审计 enqueue,再封存并尽力 flush。BgFilter 专用策略在 outbox 缺失、容量拒绝或写盘失败时丢弃并观测,不回退同步直写 SpacetimeDB;其它外部 API 审计保持原有 fallback 语义。
- 影响范围:`api-server` BgFilter worker、外部 API 失败审计策略、tracking outbox 进程接线、指标与测试、BgFilter 架构和开发运维文档;不修改 SpacetimeDB schema、procedure、bindings、前端或公开 DTO。
- 验证方式:覆盖 spawn 前容量拒绝、flat / complex 共享总上限、permit 生命周期、独立 outbox 目录、outbox 满载 / 写盘失败不直写、SpacetimeDB 不可用时任务与磁盘保持有界,以及退出时 tracker drain 后再 flush;运行 api-server 定向测试、BgFilter fault smoke、Rust check、编码和 diff 检查。
- 关联文档:`docs/technical/【后端架构】BgFilter受限资源调度方案-2026-07-21.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
---
## 2026-07-22 BgFilter flat 与 complex 使用独立熔断状态
- 背景:complex 请求在 provider 持续快速失败时仍会不断发起真实 provider attempt,并为每次已发出的失败生成异步审计;现有 flat 熔断不能约束 complex,且五分钟冷却会让短暂故障恢复后的等待过长。
- 决策:把现有 flat 熔断行为按原语义复用到 complex。flat / complex 共享 `GENARRATIVE_EDITOR_BGFILTER_CIRCUIT_FAILURE_THRESHOLD=3` 与 `GENARRATIVE_EDITOR_BGFILTER_CIRCUIT_COOLDOWN_SECONDS=120`,但在唯一 `bgfilter-worker` 内分别维护独立的连续失败数和打开截止时间;真实 provider attempt 的失败或成功只更新当前模式。两种模式都在排队前及取得 provider permit 后、第一次真实 HTTP 前检查自身熔断;已经通过第二次检查的逻辑调用仍可完成自己的第二次顺序 attempt。complex 熔断仍直接使父流程失败,不获得 flat 的阿里云 / 本地 fallback;本次不修改失败审计的异步处理流程。
- 部署边界:deploy / Provision 将 worker env 中历史模板默认 cooldown `300` 定向迁移到 `120`,其它显式自定义值保持不变;`bgfilter_circuit_state` 分别上报 `mode=flat` 与 `mode=complex`。
- 影响范围:`api-server` BgFilter worker、熔断指标与测试、worker 环境模板、生产部署迁移门禁、BgFilter 架构和运维文档;不修改 SpacetimeDB schema、父业务 fallback、计费或失败审计流程。
- 验证方式:覆盖两种模式状态隔离、阈值、成功重置、cooldown 到期、permit 前二次检查和部署默认值迁移;运行 api-server BgFilter 定向测试、生产部署脚本门禁、编码检查与 diff 检查。
- 关联文档:`docs/technical/【后端架构】BgFilter受限资源调度方案-2026-07-21.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-07-21 BgFilter 首版采用单实例同步内部 HTTP 与父流程原地等待
- 背景:角色动画在单个 `external_generation_job` 内通过 `buffer_unordered(frame_count)` 可并发发射最多 `48` 次 BgFilter 请求;限制父 worker 并发不能限制单个父 job 内的实际 BgFilter 并发。父 job checkpoint / continuation 和 SpacetimeDB 持久子任务都会扩大父状态机、attempt、计费、恢复和清理改动,而当前 BgFilter 成功结果本来就是 HTTP 图片二进制。
- 决策:父 future 保持原调用栈、lease 和 attempt,等待期间继续占用通用 worker 槽并由现有 heartbeat 续租;所有调用统一同步请求唯一 `bgfilter-worker` 的内部 loopback HTTP。输入只传 OSS object key、参数、`maxQueueWaitMs / callBudgetMs` 和有界审计关联,成功直接返回经过校验的图片二进制。子 worker 使用有界 admission `Q` 和进程内 `Semaphore(N)`,负责最多两次顺序 provider attempt、flat 进程级熔断和失败审计;父流程继续负责 flat 降级、complex 失败、Alpha / 尺寸恢复、动画 finalizer、最终 OSS、业务写回、计费和父终态。
- 超时边界:父 job 总预算仍为普通 `900s` / 长任务 `1800s`,并保留现有 `60s` 终态写回窗口。内部协议拆成互不挪用的 `maxQueueWaitMs` 与 `callBudgetMs`:前者由父剩余绝对预算扣除调用预算和父侧预留后派生,只限制等待 provider permitflat 的 `39s` 父侧预留由 `37s` fallback(阿里云 `30s` + 本地 `7s`)与 `2s` 传输窗组成,complex 只留 `2s` 传输窗。后者从取得 permit 后起算,覆盖签名、最多两次 attempt、结果校验和响应构造。provider attempt 上限按 `N × est × 2` 派生,调用预算按 `2 × attempt + 1s` 派生;额外 `1s` 吸收 attempt 间开销,只要剩余时间仍能容纳完整 attempt 就不得先扣响应预留。父内部 client timeout 精确取 `maxQueueWaitMs + callBudgetMs + 2s`,不在发送阶段重新裁剪两笔相对预算。冻结 `N=16`、`est=5000ms` 时 attempt / callBudget 分别为 `160s / 321s`。动画删除旧的按帧数 timeout 增量,父侧不得在内部 timeout / 断连后重试整次 RPC。
- 故障边界:首版不新增 `bgfilter_task_group`、`bgfilter_request_task`、raw OSS、checkpoint、continuation、数据库 capacity slot、共享熔断或 QPS token bucket。父或子进程崩溃、RPC 丢失时不查询、不恢复结果;父 job 沿用现有 lease / `max_attempts=1` 失败退款语义。动画首版保持所有已提交帧 collect / drain,不增加跨帧取消组。
- 部署边界:专用进程首版仍复用完整 `AppState`,因此 systemd unit 先加载共享 `/etc/genarrative/api-server.env`,再加载 `/etc/genarrative/bgfilter-worker.env` 覆盖 worker 独占参数;父子共同依赖的 `N=16` 与 `est=5000ms` 必须来自共享基础环境,worker 专属环境只管理 flat 熔断参数和默认 `Q=2048` 保险丝。发布切换前校验父子使用同一个非空、非符号链接、`root:genarrative 0440` 的内部 Token 文件,并拒绝父子内部 URL、Token、连接参数、`N / est`、OSS bucket 或 endpoint 漂移(同 bucket 的独立 AK 允许)。worker 停机时立即拒绝仍在排队的请求,只排空已取得 provider permit 的调用;unit 使用 `TimeoutStopSec=900` 覆盖默认 `321s` 调用预算及响应收口。运行期巡检同时检查唯一 worker unit active 与 loopback readiness;本地 `npm run dev` 同样启动独立子进程并解析第五个 dev 端口,`ProcessRole::All` 不内嵌 listener。
- 影响范围:已实施范围包括 `api-server` 内部 HTTP client、专用 `bgfilter-worker` listener / process role、并发与超时配置、部署和运维观测;未修改 SpacetimeDB schema、父 job schema、用户任务 DTO、任务列表或收费归属,当前待生产压测后启用。
- 验证方式:`48` 帧并发进入父 future 时,健康唯一子 worker 进程持有的 BgFilter HTTP future 峰值不得超过生产显式配置的 `N`,且 `queued + running + egress` 不超过 `Q`admission permit 持有到 response body 发送完成或 drop);覆盖 flat / complex 降级矩阵、内部 deadline、断连后已启动请求排空、动画全帧 drain、External v1 / inline 旁路扫描、二进制大小 / MIME / 尺寸门禁、单实例部署、DDD、编码和 diff 门禁。
- 关联文档:`docs/technical/【后端架构】BgFilter受限资源调度方案-2026-07-21.md`、`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
---
## 2026-07-20 角色动作抠图前禁止透明 padding
- 背景:图片画布角色动作此前在 BgFilter 前复用最终帧 finalizer,把 FFmpeg 抽帧先转成目标尺寸 RGBA 画布并用透明黑像素补边;透明区域进入 BgFilter、阿里云和本地键色共同读取的 OSS 源帧后,会干扰主体边缘判断并降低抠图质量。
- 决策:仅图片画布角色动作链路在抠图前把 FFmpeg 帧转为 RGB8,按最终帧宽高的 contain 比例使用 `Triangle` 缩放到内容尺寸,不创建最终目标画布、不引入 Alpha、不插入 padding;该 RGB8 PNG owned 上传 OSS 后由三段抠图链共享。抠图返回后继续复用原最终帧 finalizer,转为 RGBA8、居中放入最终目标尺寸,并以 `RGBA(0,0,0,0)` 补边。`560×752 → 323×480` 的固定验收结果为 `323×434 RGB8` 抠图输入和上下各 `23px` 透明补边的 `323×480 RGBA8` 最终帧。
- 补充(2026-07-21 实现收口):转 RGB8 时若解码帧携带 Alpha 通道(共享 FFmpeg 抽帧命令不固定 `-pix_fmt`,源视频为 alpha 格式时 PNG 可能是 RGBA),必须先把像素按白底合成为不透明再转 RGB8`flatten_alpha_onto_white_rgb`),禁止直接丢弃 Alpha——全透明像素下未定义的 RGB 值会以杂色进入抠图输入,重新引入本决策要消除的杂色边缘。该白底合成职责只属于图片画布角色动作的 BgFilter 输入准备阶段,不得为此在共享抽帧命令里固定像素格式。
- 边界:不改变最终帧的 RGBA/padding 语义与透明帧格式、BgFilter 请求、OSS 上传与签名、抽帧数量和采样时间,也不改变旧 `/api/assets/character-animation/*` 动作发布链路;因为降级链复用同一个 object key,阿里云和本地键色同样读取新的无补边 RGB8 源帧。
- 影响范围:`server-rs/crates/api-server/src/character_animation_assets.rs`、后端融合架构、角色动作专题和图片画布当前接入方案;不涉及 DTO、前端接口、SpacetimeDB schema 或运维配置。
- 验证方式:像素测试断言 `560×752 RGB8 → 323×434 RGB8` 且无 Alpha/补边,并断言抠图结果最终成为上下各 `23px` 透明补边的 `323×480 RGBA8`;运行 `cargo test -p api-server character_animation --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
---
## 2026-07-20 角色动画帧 OSS 请求使用专用连接池、并发保护与结构化重试
- 背景:角色动作逐帧流水线会同时发起源帧 PUT、透明帧 PUT 和最终帧 HEAD;原路径每次请求新建 `reqwest::Client`,且 OSS 请求错误丢失 HTTP 状态和 timeout/connect/transport 分类,多个动画任务叠加时无法在进程级限制 OSS 在途请求,也无法安全区分 PUT 与 HEAD 的失败。
- 决策:`AppState` 仅为角色动画帧初始化一次 OSS HTTP Client 和 8 路 `Semaphore`。全帧 Future 仍保持 `buffer_unordered(frame_count.max(1))`BgFilter、阿里云抠图和本地处理不占 OSS permit;每次 PUT/HEAD 网络 attempt 单独获取 permit,退避期间释放。`platform-oss` 保留 `OssErrorKind::Request`,但在 `OssError::Request` 中保留 operation、status、timeout、connect、transport、OSS code、OSS request-id 和原脱敏 message,并为动画帧提供 3 次 attempt、250ms/500ms 退避的 PUT/HEAD 独立重试。仅无响应传输错误、timeout、OSS PutObject 的 `400 + RequestTimeout`、PUT 400 错误体读取失败(未解析出 `Code`,按 timeout/transport 归类)、408、429 和 5xx 可重试;动作帧 PUT 对 400 错误体最多读取 16 KiB,只提取 `Code` 和响应头优先的 `x-oss-request-id`,不记录完整 XML;错误体读取超时/断流时保留已读字节,已解析出的 `Code` 优先生效。除 `RequestTimeout` 与该错误体读取失败情形外的确定性 4xx、配置、签名、URL、空请求体、抠图和素材登记错误不重试。重试体在 platform-oss 内一次转为可复用 `Bytes`,每次重新签名和构造 Request,不复制整帧字节。
- 失败语义:最终帧 PUT 成功后才执行 HEAD;HEAD 失败只重试 HEAD,不重复 PUT。任一帧最终失败仍排空已启动的 Future、整段动作退款并禁止发布缺帧动画,帧结果继续按原始序号排序。
- 影响范围:`state.rs`、`platform-oss/lib.rs`、`character_animation_assets.rs`、对应 Cargo 依赖和架构 / 运维文档;不改变其他 OSS 调用方、BgFilter/阿里云降级、worker、计费退款、SpacetimeDB schema/DTO 或前端接口。
- 验证方式:`cargo test -p platform-oss --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server character_animation --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
---
## 2026-07-19 角色动作视频使用单进程批量抽帧
- 背景:角色动作生成在拿到预览视频后,原实现会为 `32 / 40 / 48` 个采样点分别启动一次 FFmpeg、重复解码同一视频。release 的 2 vCPU 主机在一次 32 帧任务中因此出现约 10 秒的 CPU 尖刺,且进程启动和重复解码都不是业务必需开销。
- 决策:角色动作抽帧必须先沿用 `compute_sample_time_seconds()` 计算全部采样点,再通过一个 FFmpeg filter graph 对输入统一 `setpts`、`split`,各分支按精确 `select=gte(t\,<target>)` 输出一帧。不得改用会漂移现有采样时刻的粗粒度 `fps` 抽帧。单次命令完成后逐一确认全部目标文件存在,任一缺帧继续使用原有用户错误文案,并在 details 中保留首个缺帧的 `targetSeconds / outputPath`、整批 `missingFrames` 和 stdout/stderr。视频封面等单帧调用保留兼容 helper,但内部复用同一批量实现。
- 影响范围:`server-rs/crates/api-server/src/character_animation_assets.rs` 的角色动作视频本地抽帧和单帧视频封面抽取;不改变尾帧安全步长、BgFilter 并发、OSS 路径、帧编号、透明化后处理或前后端结果契约。
- 验证方式:运行 `cargo test -p api-server editor_character_animation --manifest-path server-rs/Cargo.toml`,真实短视频回归必须由一次 FFmpeg 命令产出整批帧,并继续断言 `32帧·4秒` 最后一帧为 `3.875s`;追加 `cargo check -p api-server --manifest-path server-rs/Cargo.toml`、Rust 格式、编码和 diff 门禁。
- 关联文档:`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/project-memory/shared-memory/pitfalls.md`。
---
## 2026-07-18 图片生成 K 档由 provider 直接生成
- 背景:旧 gpt-image-2 尺寸表会把 2K 竖版回落到 `1024x1536`,图标入口又使用固定 `360x360 / 512x512` 占位;角色去背景结果变小时还会直接放大整张透明成品,导致 UI 显示的 2K 与模型实际生成清晰度不一致。
- 决策:用户选择的模型、比例和 K 档先映射为 provider 可直接接受的真实像素,前端占位、api-server 请求和 VectorEngine request body 保持一致。带显式尺寸选项的用户生成不再用回图后缩放恢复 K 档;宣发素材固定交付尺寸与旧无尺寸请求保留原有兼容恢复。角色、图标和 UI 去背景降采样时只重采样 alpha 蒙版并应用回 provider 原始 RGB,不放大低分辨率后处理 RGB。
- 影响范围:普通图片、角色形象、图标图集、UI 设计图的占位与生成请求,gpt-image-2 尺寸矩阵,以及角色透明后处理。
- 验证方式:前端尺寸矩阵和入口占位测试、api-server 生成参数与 alpha 合成测试、platform-image 最终 request body 测试、类型检查、Rust check、编码和 diff 门禁。
- 关联文档:`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-07-18 阿里云 URL 抠图链路按外部调用阶段审计
- 背景:阿里云 URL 抠图先从源 OSS GET,再解码、校验尺寸、归一化并上传临时 OSS;此前解码和尺寸失败仍使用普通 `InvalidRequest`,被错误标记为 `externalCallAttempted=false`,无法满足阿里云失败统一审计约定。
- 决策:真正开始外部调用前的本地预检不写 `external_api_call_failure`;源 OSS GET 成功后发生的解码、尺寸、归一化、临时上传、阿里云请求和结果处理失败均进入审计。`platform-matting` 使用结构化 `LocalProcessing` 分类和 `failureStage`,由 api-server 映射为 `source_decode`、`source_validate`、`source_normalize`、`temp_upload`、`result_decode` 等阶段,不再把这些错误统称为“发请求前本地预检”。
- 影响范围:`server-rs/crates/platform-matting/src/lib.rs`、`server-rs/crates/api-server/src/aliyun_matting.rs`、`server-rs/crates/api-server/src/external_api_audit.rs`、`server-rs/crates/api-server/src/editor_project.rs`、后端架构文档。
- 验证方式:运行 `cargo test -p platform-matting --manifest-path server-rs/Cargo.toml`、阿里云抠图与外部审计定向测试、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
## 2026-07-18 手动去背景稳定媒体引用校验收口
- 背景:当前分支与 `master` 分别增加手动去背景专用 Data URL 校验和编辑器通用稳定媒体引用校验,直接叠加会让 API 与 worker 重复执行语义相同的 helper,并造成 `data:` / `blob:` 覆盖范围和错误文案漂移。
- 决策:删除手动去背景专用校验。HTTP API 在入队前统一调用 `ensure_editor_reference_image_source_is_stable`,立即拒绝 `data:` / `blob:`worker 不重复调用该入口校验,只通过 `resolve_editor_reference_object_key_for_owner` 完成稳定引用解析和 owner 归属校验。底层 `resolve_editor_reference_object_key` 在尝试 object key、项目资源 ID 或素材 ID 解析前统一拒绝内联媒体,作为历史任务和内部直接调用的最终边界。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、图片画布手动去背景测试和图片画布技术方案;不改变队列 DTO、BgFilter `image_url` 协议或 SpacetimeDB 的编辑器任务 payload 门禁。
- 验证方式:覆盖 API 入队前拒绝 `data:` / `blob:`、解析器拒绝内联媒体、worker 只调用稳定引用解析与归属校验;运行 api-server 编辑器定向测试、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-07-17 画布 Agent 普通消息不提供客户端停止
- 背景:普通消息进入 LLM 前,后端已经把用户消息写入 OSS;前端中断 fetch 只能停止本地等待,不能保证后端停止规划,且会保留无法与后端消息对齐的 optimistic message。
- 决策:移除画布 Agent 普通消息的“停止”按钮和 `stopCurrentTurn`,发送期间保持按钮禁用并等待后端响应。待确认工具调用的“取消”仍保留,不受本决策影响。
- 影响范围:画布 Agent 对话 hook、发送区交互、前端测试和专题文档。
- 验证方式:运行画布 Agent hook / 面板定向测试、`npm run typecheck`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【编辑器】画布Agent对话面板-2026-07-03.md`。
## 2026-07-10 画布 Agent 工具确认分离执行参数与展示投影
- 背景:画布 Agent 已在实际生成前进入 `pending_confirmation`,但 `EditorAgentToolCall.args` 只保存工具私有的规范参数 JSON,其中图片参数是保护真实 data key 的 SHA-256 opaque ID。前端直接解析 `args` 只能显示内部哈希或图片数量,无法向用户准确展示即将使用的目标图、参考图和完整参数;若直接把图片 URL 或对象塞回 `args`,又会破坏确认执行反序列化和 LLM 不可见真实 data key 的安全边界。
- 决策:LLM 返回的原始工具参数只作为 api-server 本次处理的瞬时输入;后端按已注册 ToolArgs 反序列化、补齐默认值、删除未知 / 退役字段、完成工具参数校验并重新序列化后,才把结果写入 `EditorAgentToolCall.args`。校验失败的调用不得持久化为待确认消息。该规范 `args` 是确认执行唯一真相,不允许前端改写或回传替代参数;新增必填 `displayArgs` 只读展示投影,内含 `stringArgs`、`imageArgs` 和 `extras.priceMudPoints`。`stringArgs` 承载提示词与规格等用户可见字段,`imageArgs.refs` 承载规范 `args` 中的 `imageId` 及后端解析出的 `objectKey`、`imageSrc`、可选缩略图、标签和尺寸;`extras.priceMudPoints` 由 api-server 在创建待确认消息时使用后端运行时模型定价快照计算,前端只显示“预计消耗 N泥点”,不自行计算或回传价格。api-server 必须按已注册 tool 白名单,从规范 `args` 与 OSS 会话文档的附件 / 历史生成结果构建该投影;前端只渲染投影,以 `ResolvedAssetImage` 换签显示图片,不解析 tool 私有 schema、不展示 SHA-256 ID。展示价格不参与确认执行或实际扣费,确认后仍由既有生成 BFF 按后端运行时定价预扣费。删除只重复 `args` 且没有稳定语义的 `EditorAgentToolCall.summary`。模块尚未上线,不保留缺少 `displayArgs` 时读取 `args` 的旧消息降级路径。
- 影响范围:`shared-contracts` / `packages/shared` 的 `editorAgent` DTO、`api-server/src/editor_agent/api.rs` 的待确认消息构建、画布 Agent 待确认卡、OSS 会话消息文档与相关测试。
- 验证方式:`cargo test -p shared-contracts --manifest-path server-rs/Cargo.toml editor_agent`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_agent`、`npm run test -- src/components/image-editor/EditorAgentConversation/EditorAgentConversationPanelView.test.tsx src/components/image-editor/EditorAgentConversation/useEditorAgentConversation.test.tsx src/services/image-editor/editorAgentClient.test.ts`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】画布Agent对话面板-2026-07-03.md`、`docs/adr/【ADR】画布Agent会话消息存OSS-2026-07-03.md`。
## 2026-07-18 图片多产物任务的原图进入正常完成画布
- 背景:角色形象、图标 spritesheet 和 UI 素材提取会同时持久化带纯色背景的 provider 原图与透明后处理结果;这些原图都需要在画布中可直接查看和复用。任务与扣费实际仍只有一次。
- 决策:provider 原图继续写入 OSS、`asset_object`、项目资源和账号素材库。三类任务透明处理正常成功时,透明结果作为主图并保持生成器 `generatedLayerId` 锚点,provider 原图作为第二个图层放在透明主结果右侧;图标和 UI 的业务拆分素材从原图右侧继续排列。只有透明背景处理最终失败时,才把 provider 原图作为唯一主图完成占位并返回 warning。
- 影响范围:角色形象、图标 spritesheet、UI 素材提取的画布完成快照,以及多产物持久化与画布展示边界。
- 验证方式:后端定向测试断言三类任务正常成功都落透明主图与右侧 provider 原图、`generatedLayerId` 仍指向透明主图,图标 / UI 拆分素材继续排列在原图右侧,同时保留 source-only 失败降级测试;并运行 `cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-07-17 生成后抠图原图以 OSS 作为内存生命周期边界
- 背景:角色形象、图标图集、UI 素材图集和角色动作抽取帧的带背景原图虽然已先落私有 OSS,但 api-server 仍可能把原图字节保留到 BgFilter / 阿里云 / 本地 fallback 结束,造成并发任务下的内存峰值叠加。
- 决策:目标链路的带背景原图上传 OSS 时消费 `DownloadedImage` 或动作帧字节所有权,不为上传克隆整张字节缓冲;上传完成后不再跨 BgFilter 调用常驻。手动去背景直接复用已有 OSS object key,不下载原图。BgFilter 只读取 600 秒签名 URL;进入阿里云 fallback 时由 `platform-matting` 新 URL 接口下载原图、上传 `AuthorizeFileUpload` 临时对象,并在开始阿里云推理前结束下载缓冲作用域;阿里云继续失败时,api-server 再从私有 OSS 独立下载原图供本地键色,产出后释放本次原图下载缓冲。
- 边界:不改变接口 DTO、资源记录、画布原图展示、图集切分行为和降级顺序;“释放”指 Rust 所有权和 `Vec<u8>` 析构,RSS 不保证同步下降。
- 验证方式:`platform-matting` 测试覆盖 URL 下载缓冲在临时上传后结束、降尺寸 Alpha 回贴;`api-server` 结构测试覆盖带背景原图 owned 上传、URL 阿里云 fallback、本地重新下载与原图释放;随后运行两个 crate 的测试与编译检查。
## 2026-07-17 图片改造保持源图与所选清晰度
- 背景:图片画布从已生成的 2K 角色图重新打开生成器时,面板恢复逻辑会优先采用新建面板的 1K 默认值;即使用户重新选择 2K,角色透明化链路也可能接受 BgFilter / 阿里云返回的 1K 后处理图,并因 `nanobanana2` 使用标量清晰度档位而跳过几何尺寸恢复,最终把 2K provider 原图降为 1K 透明图。
- 决策:从既有图片重新打开普通图片、角色、UI 或宣发生成器时,在没有仍存活的生成对话框快照时按当前图层真实 `originalWidth / originalHeight` 恢复比例与清晰度,并按目标模型支持范围归一;恢复或切换比例 / 清晰度后,普通图片、角色、图标图集和 UI 设计图的待生成及生成中占位框必须同步使用目标像素尺寸,不能保留新建 draft 的默认 1K 框。UI 素材提取的占位按框选数量对应的 1K / 2K 计划生成,旧图片修改入口按源图真实尺寸占位。角色形象去背景完成后必须保持去背景前 provider 原图的像素尺寸;若去背景供应商返回较小结果,只把 alpha 蒙版重采样回原图并保留原始 RGB,不放大低分辨率透明成品。
- 影响范围:图片画布生成对话框恢复、生成中占位尺寸、UI 素材提取与旧图片修改的 `canvasCompletion`、角色形象 BgFilter / 阿里云 / 本地去背后处理、项目资源与账号素材尺寸元数据。
- 验证方式:覆盖“持久化 2K 角色图重开仍为 2K”“普通图片 / 角色 / 图标 / UI 改造的 2K 占位与目标一致”“普通生图、规范图和角色图生成中占位不回退 1K”“UI 提取和旧修改入口的完成占位使用业务目标尺寸”以及“较小去背结果只提供 alpha、最终 RGB 仍来自 2K provider 原图”的前后端定向测试,并运行前端类型检查、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
## 2026-07-15 BgFilter 输入改用私有 OSS 短期签名 URL
> 后续更正(2026-07-21):复用 object key、通过 `image_url` 提交且不传 `file` 的协议语义保留,但 600 秒 OSS GET URL 的签发和 BgFilter provider multipart 调用已迁入唯一 `bgfilter-worker`。父流程只向内部 worker 发送一次 object key、参数和剩余预算,不签发 BgFilter URL,也不重试已被 worker 接收的内部 RPC2026-07-23 起:连接从未建立的失败按调度方案 §5.1 有界重连,见当日决策条目)。下文保留作历史记录。
- 背景:角色形象、图标图集、UI 素材图集、角色动作抽取帧和手动去背景在调用 BgFilter 前都已有私有 OSS object key;继续由 api-server 下载或保留图片并作为 multipart `file` 再上传,会重复传输图片字节并占用 API 进程网络与内存。
- 决策:上述抠图链路统一复用 object key,签发 600 秒 OSS GET URL,并通过 BgFilter multipart 的 `image_url` 字段提交;请求中不再携带 `file`。签名 URL 只交给 BgFilter,不写日志或持久化。2026-07-17 起,生成原图和动作帧上传后不再保留图片字节;进入“阿里云通用抠图 → 本地键色”兜底链时按阶段从私有 OSS 重新下载。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/api-server/src/character_animation_assets.rs`、相关测试与文档;不改变 BgFilter endpoint、鉴权、`screen_color`、`seg_model`、输出校验、熔断规则、阿里云上传协议或降级顺序。
- 验证方式:定向测试必须断言 BgFilter 请求函数包含 `image_url` 与 600 秒 OSS 换签,不包含 multipart `file` 或源图字节读取;随后运行 `cargo check -p api-server --manifest-path server-rs/Cargo.toml`。
## 2026-07-15 阿里云通用抠图上传切换到 AuthorizeFileUpload 正式链路
- 背景:`platform-matting` 原先通过 `viapiutils/GetOssStsToken` 获取临时 AK/SK,再向固定 `viapi-customer-temp` 共享桶执行 OSS V1 PUT。阿里云官方文档将该显式生成 URL 的共享临时桶通道标记为不保证 SLA、仅便于调试且不推荐生产使用;动作视频逐帧抠图会把这条风险放大到每任务 32 至 48 次。
- 决策:非上海地域图片字节统一按新版官方 SDK `AdvanceRequest` 的实际协议处理:调用 `openplatform.aliyuncs.com` 的 `AuthorizeFileUpload` 获取单对象 `Bucket`、`Endpoint`、`AccessKeyId`、`EncodedPolicy`、`Signature` 与 `ObjectKey`;再以 multipart Policy POST 上传到动态返回的上海临时 OSS,表单字段为 `OSSAccessKeyId`= AccessKeyId)、`policy`= EncodedPolicy)、`Signature`、`key`= ObjectKey)、`success_action_status=201` 与 `file`,最后把临时对象 URL 交给 `SegmentCommonImage`。移除 `GetOssStsToken`、固定 `viapi-customer-temp`、AccessKeySecret/SecurityToken 临时凭证组合和 OSS V1 SHA-1 签名;Policy POST 仍需要授权响应中的 `AccessKeyId`,不再下发可独立签名的完整临时密钥。图片归一化、结果下载、原尺寸 Alpha 回贴和上层降级顺序保持不变。该链路仍会让图片字节经过执行任务的 api-server / worker 并上传临时 OSS,不把它描述成阿里云服务端直接抓取任意公网 URL。
- 影响范围:`server-rs/crates/platform-matting`、阿里云抠图冒烟示例、后端架构与开发运维文档;不改变 api-server DTO、动作拆帧、BgFilter 或业务降级契约。
- 验证方式:`cargo test -p platform-matting --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`,并用真实图片运行 `segment_smoke`,确认授权上传 host 来自动态上海 OSS 且 `SegmentCommonImage` 成功返回。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、阿里云“通用图像分割”与“文件 URL 处理”官方文档。
## 2026-07-15 角色动作 BgFilter 请求超时按帧数扩展
> 后续更正(2026-07-21):本条按帧数增加 timeout 的决策已被 2026-07-21「BgFilter 首版采用单实例同步内部 HTTP 与父流程原地等待」的公式化双预算取代。当前每帧分别携带 `maxQueueWaitMs` 与 `callBudgetMs`:排队预算只约束等待 provider permit,取得 permit 后才启动调用预算;provider attempt 按 `N × est × 2`、调用预算按 `2 × attempt + 1s` 运行时派生,冻结 `N=16`、`est=5000ms` 时为 `160s / 321s`,不再按 `32 / 40 / 48` 帧扩展。下文保留作历史记录。
- 背景:角色动作全部序列帧会并发进入 BgFilter,而服务端可能在自身进程内排队;固定 `180000ms` 会把排队时间和单帧推理共用同一预算,靠后的请求可能在服务仍正常处理时被 api-server 提前取消。
- 决策:保留 `GENARRATIVE_EDITOR_BGFILTER_REQUEST_TIMEOUT_MS` 作为统一基准值。只有角色动作逐帧 BgFilter 在共享 Client 的 RequestBuilder 上把每一次 HTTP attempt 覆盖为“基准值 + `2000ms × 本次实际帧数`”,默认 `32 / 40 / 48` 帧为 `244000 / 260000 / 276000ms`;角色形象单图、图标、UI 和手动去背景不增加帧预算。该 timeout 覆盖请求发起到响应体读取完成;首次失败后的重试重新获得同样的 request deadline,整批并发策略、失败排空语义和 worker long-job 总预算不变。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/api-server/src/character_animation_assets.rs`、后端架构、开发运维和图片画布专题文档。
- 验证方式:运行角色动作超时公式、BgFilter request override 与逐帧流水线定向测试,执行 `cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-07-15 手动复杂去背景复用 BgFilter 单次重试
> 后续更正(2026-07-21):首次失败后再尝试一次、即同一次 complex 逻辑调用最多两次顺序 provider attempt 的语义保留,但重试所有权已迁入唯一 `bgfilter-worker`。父 `external-generation-worker` 至多让 worker 接收一次内部 HTTP RPC,不重试已被接收的 RPC2026-07-23 起:连接从未建立的失败按调度方案 §5.1 有界重连,见当日决策条目);两次 provider attempt 都失败时,子 worker 把最终类型化错误返回父流程,complex 仍不接入 flat 的阿里云 / 本地 fallback。下文所称“worker 重试”按此边界理解。
- 背景:图片画布手动去背景已经改用 BgFilter `background_mode=complex`,但 worker 仍只发送一次上游请求,短暂网络抖动会直接让任务失败。
- 决策:手动去背景的 complex 请求复用现有 `EDITOR_BGFILTER_RETRY_COUNT=1`,首次请求失败后立即重试一次,两次都失败仍返回最终错误;本次不把手动 complex 接入标准纯色背景链路的阿里云 / 本地兜底,也不改变 flat 路径的熔断状态。
- 影响范围:图片画布手动去背景 worker、BgFilter complex 请求日志和 api-server 定向测试。
- 验证方式:运行 `cargo test -p api-server editor_manual_background_removal_retries_once --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/project-memory/shared-memory/decision-log.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-07-14 手动去背景迁移到 BgFilter complex 模式
> 后续更正:本条关于 multipart 图片文件输入的描述已由 2026-07-15「BgFilter 输入改用私有 OSS 短期签名 URL」和 2026-07-17「生成后抠图原图以 OSS 作为内存生命周期边界」取代;当前手动路径不下载原图,只提交 `image_url`。下文保留作历史记录。
- 背景:图片画布手动“去除背景”此前单独代理 BiRefNet 服务;BgFilter 已增加 `background_mode=complex`,可直接处理非纯色背景,继续保留独立服务会形成重复的上游、配置和错误处理链路。
- 决策:`POST /api/editor/images/background-removals` 保持前端与 BFF 契约不变,worker 改用现有 BgFilter 地址、token、超时和共享 HTTP client。multipart 提交图片文件、`background_mode=complex`、`seg_model=birefnet` 与 `cross_check=off`,不提交 `screen_color`。标准纯色背景的角色形象、图标 spritesheet、UI 素材提取和角色动作逐帧抠图继续使用 `background_mode=flat`。删除独立 BiRefNet base URL / timeout 配置;旧 `GENARRATIVE_EDITOR_BACKGROUND_REMOVAL_TOKEN` 仅作为 `GENARRATIVE_EDITOR_BGFILTER_TOKEN` 的兼容回退别名。
- 影响范围:图片画布手动去背景 worker、BgFilter HTTP 协议、api-server 配置、资源元数据、前端 provider 展示和相关文档。
- 验证方式:运行 api-server BGFilter / 手动去背景定向测试、前端 editorProjectClient / 画布 workflow 定向测试、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-07-13 外部生成任务持久化真实执行阶段
- 背景:图片画布任务列表此前把所有 `running` 任务固定映射为“正在生成”,角色生图、图标/UI spritesheet、角色动作和手动去背景进入抠图后仍无法展示“正在处理”;前端按耗时推断阶段会产生新的非正式业务真相。
- 决策:不新增 DB 表,在既有 `external_generation_job` 与 `external_generation_job_summary` 末尾追加带默认值的可选 `phase`。worker claim 时写 `generating`;角色生图、图标 spritesheet、UI 素材提取在调用 BgFilter 前,角色动作在视频生成返回并开始抽帧/逐帧抠图前,手动去背景在执行开始时,通过 `job_id + worker_id + lease_token` 保护的 procedure 写 `processing`。phase procedure 用结构化结果区分 `LeaseFencingRejected` 与 `OtherRejected`api-server 对 `LeaseFencingRejected` 立即终止,对 `OtherRejected` 以及 SDK 的 `Procedure` / `Runtime` 错误不重试,仅对 `Build` / `ConnectDropped` / `Timeout` 在同一 job attempt 内重试 `1` 次。编辑器 job 固定 `max_attempts=1`,第二次传输失败后进入 `failed`,不回 `pending`、不重新调用 provider,也不按错误文案猜测拒绝类型。BFF 将 `running + processing` 映射为“正在处理”,其它 `running`(含旧数据 `phase=None`)映射为“正在生成”;前端只展示后端投影。
- 影响范围:`external_generation_job`、`external_generation_job_summary`、SpacetimeDB procedure / typed client / bindings、图片画布生成 worker、任务列表 BFF 与相关文档。
- 验证方式:运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、外部生成 module/client/api-server 定向测试、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-07-13 角色动作逐帧开启 BgFilter cross-check
- 背景:角色动作逐帧抠图此前为减少额外推理开销固定传 `cross_check=off`,但动作帧同样需要保留发丝、镂空和运动边缘质量。
- 决策:角色动作逐帧 BgFilter 请求固定显式传 `cross_check=on`,与角色形象保持一致;图标 spritesheet 和 UI 设计图素材提取继续固定传 `off`。该策略仍属于后端内部供应商参数,不进入前端或外部 OpenAPI。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/api-server/src/character_animation_assets.rs`、后端架构文档和图片画布技术文档。
- 验证方式:运行 `cargo test -p api-server editor_bgfilter_cross_check --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_character_animation_frames_use_three_stage_matting_fallback --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-07-13 角色动作 BgFilter 全帧流水线与单次重试
> 后续更正(2026-07-23):本条「每次 BgFilter 调用失败后立即重试 1 次」与「不新增供应商进程锁或全局 Semaphore」已被 2026-07-21 起的唯一 `bgfilter-worker` 架构取代。重试所有权迁入子 worker:对一次逻辑调用最多两次顺序 provider attemptprovider 并发由 worker 进程内 `Semaphore(N)`(生产 `N=16`)约束;父侧不重试已被 worker 接收的内部 RPC,仅 TCP 连接从未建立的失败按调度方案 §5.1 有界重连(见 2026-07-23「BgFilter 父侧连接失败有界重连与冷启动宽限」条目)。全帧独立流水化、失败排空与整任务失败退款的语义保留。下文保留作历史记录。
- 背景:角色动作抽帧后原先固定 `buffered(3)`,并在整批绿幕源帧串行落 OSS 后才开始抠图;每帧还单独创建 HTTP Client。公网 BgFilter 的网络等待会让服务端推理队列出现空档,且首个最终错误会通过 `try_collect` 提前取消 api-server 中其余已发 Future。
- 决策:BgFilter HTTP Client 在 `AppState` 中统一创建并复用 keep-alive 连接池;每次 BgFilter 调用失败后立即重试 `1` 次,两次都失败才进入既有“阿里云通用抠图 → 本地键色”降级链,每次已发失败调用都保留审计。角色动作全部 `32 / 40 / 48` 帧按“单帧绿幕源图落 OSS → BgFilter/降级 → 透明帧落 OSS”独立流水化,使用覆盖本次全部帧的 `buffer_unordered` 连续发射并携带原始帧序,完成后排序;不在 api-server 新增供应商进程锁或全局 Semaphore。任一帧最终失败时先排空全部已启动 Future,再让整个动作任务失败退款,不发布缺帧动画。
- 影响范围:`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/api-server/src/character_animation_assets.rs`、后端架构文档和图片画布技术文档。
- 验证方式:运行 `cargo test -p api-server editor_bgfilter_retries_once_before_fallback --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_character_animation_frames_use_three_stage_matting_fallback --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_canvas_screen_background_generation_uses_bgfilter_postprocess --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-07-16 SpacetimeDB 备份采用逐文件基线、CAS 增量与安全历史清理
- 背景:SpacetimeDB standalone 2.6.0 不自动删除已被 snapshot 覆盖的历史 commitlog 与旧 snapshot;反复压缩整个 `/stdb` 会重复占用磁盘、停机和 OSS 带宽。上游 issue #5542 的 contributor 明确说明,不触碰最新 snapshot 与重启所需 commitlog suffix 时,可在运行中移动或删除这些历史文件。
- 决策:统一脚本新增 `--storage-format files`,完整基线递归保留目录、文件和 data-dir 内部相对符号链接,普通文件按 SHA-256 上传为不可变 CAS 对象,catalog 记录目录、路径、长度、SHA、对象 key 与相对链接目标;绝对或越界链接拒绝备份。相同内容不重复 PUT,后续 full 扫描只上传新增或变化内容,不再生成 tar.gz。旧 `archive` 路径保留兼容。full 必须从停库目录或已验证的冻结副本生成,不能把在线跨文件扫描称为一致时点备份。
- history 继续按 replica 计算安全边界:只接受完整、未锁定且含同 offset `.snapshot_bsatn` 的 snapshot,保留跨越最新 snapshot 的边界 segment 及全部后缀。旧 segment 对和旧 snapshot 被递归映射为单文件 CAS 对象;对象、history catalog、full baseline catalog、候选 fingerprint 与当前边界全部验真后才删除源文件。同库执行用 work-dir PID lock 互斥。
- OSS 固定恢复入口为 `<prefix>/<database>/latest.json`。CAS 文件和 full/history catalog 保持不可变;latest pointer 只保存最新 full catalog 与已发布 history catalog 的 object key、长度和 SHA,不包含主机绝对路径或文件内容。每次 state 变化先验真全部引用 catalog,再覆盖上传并 HEAD 验真 latest pointer,成功后才落本地 statehistory 还必须在 pointer 成功后才允许删除源文件。全新机器可仅凭 bucket、database、prefix 与 OSS 凭据自动下载 pointer 和 full catalog。
- dev 带宽不足时,允许把已冻结的 dev 基线经 `10.2.0.10 -> 10.2.4.16` 内网 rsync 到 release 独立 staging,再用 release 出口上传 dev bucketstaging 不得指向 release `/stdb`,不得停止或修改 release 服务,传输凭据必须临时创建并在演练后移除。catalog 不记录 staging 绝对路径,files state 可回传 dev 继续 history。
- 恢复边界:恢复时默认从 OSS `latest.json` 自动定位 full catalog,创建目录并按相对路径下载每个对象、逐文件校验长度与 SHA;本地 state 只用于备份续跑,不再是异机恢复前置条件。远程 dev 已完成真实 OSS、清理、重启和异机隔离恢复演练;release timer 与 publish 前备份继续保持原行为。
- systemd 接线:主 service 保持 `archive-full`。Server-Provision 新增默认值为 `archive-full` 的 `DATABASE_BACKUP_PROFILE`development 可显式选择 `files-history`,必须指定独立 work-dir 并先用 current release 脚本执行 history dry-run,确认已有 full state 后才安装仓库托管 drop-inrelease 拒绝 `files-history`,直到流式 catalog 改造完成,以免大目录扫描再次触发 Node 内存峰值。切回默认 profile 必须删除所有 history 覆盖;备份 unit 同时设置 Node heap 与 systemd memory 上限,避免备份异常拖垮业务主机。
- 影响范围:`scripts/database-backup-to-oss.mjs`、备份门禁、生产 env 示例、systemd 模板、Server-Provision、SpacetimeDB 运维与恢复流程;release timer 固定使用 archive-fullpublish 前备份是否切换仍需单独决策。
- 验证方式:`npm run check:database-backup`、`npm run check:production-ops`、`npm run check:encoding`、`git diff --check`dev 现场必须完成逐文件 full catalog、重复 full 零 PUT、history dry-run、上传后清理、STDB 重启和按 catalog 隔离恢复 roundtrip。
- 关联:<https://github.com/clockworklabs/SpacetimeDB/issues/5542#issuecomment-4981566448>。
## 2026-07-27 逐文件备份本地元数据采用去重 state 与 gzip 保留
- 背景:files v1 本地 state 同时在 `baselineCatalog`、`latestCatalog` 和每个 `historyCatalogs[]` 中嵌入完整文件清单,且每次成功 history 的本地 catalog 与 `--result-file` 再复制同一清单;release 独立 work-dir 已由此累积约 840 MiB JSON,但 OSS-only 恢复实际只依赖 `latest.json`、catalog 引用和 CAS 对象。
- 决策:OSS catalog/latest schema、对象 key、序列化字节与恢复链保持不变。本地 state 升级为 gzip v2,只保存去重后的 catalog 引用;旧 v1 JSON 可读,并且只在非 dry-run 成功发布、验真 latest、原子写入 v2 后删除。full 增量复用从本地 latest full catalog gzip 缓存读取,缓存缺失时退化为逐对象 OSS HEAD,不影响正确性;缓存长度或 SHA 与 state 不一致时失败关闭。
- 清理边界:成功运行后只压缩保留 latest full catalog;已验真的本地 history catalog、旧 full catalog和严格文件名匹配的失败/dry-run 遗留 catalog 清理。`--result-file` 只写紧凑引用和计数。任一 state 压缩或本地 metadata 清理失败都发生在 `/stdb` history 源文件删除之前;OSS catalog、latest 和 CAS 对象永不由本地 metadata 清理删除。
- 兼容与验证:`--restore-files-state` 同时接受 v1 JSON 与 v2 gzip`--restore-files-latest` 不受本地格式影响。门禁覆盖 v1 迁移、gzip/state/catalog 损坏拒绝、full 增量复用、history catalog 本地清理、紧凑 result、pointer 失败不清源和 OSS-only 恢复。
- 关联:`scripts/database-backup-to-oss.mjs`、`scripts/check-database-backup-to-oss.mjs`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-07-14 后台账号采用 owner 引导账号与一级 Tab 实时授权
- 背景:后台此前只支持一组环境变量管理员,所有 `/admin/api/*` 共用统一 admin 门禁,无法给运营、审核等人员分配独立账号和页面范围。
- 决策:现有 `GENARRATIVE_ADMIN_USERNAME/PASSWORD` 账号固定作为不可编辑 owner;新增 member 独立保存到私有 `admin_account` 表,密码使用 Argon2id 摘要。登录凭据快照与普通账号快照在类型层分离,普通列表、按 ID 查询和写入响应不包含 `password_hash`。Argon2id 在 blocking 任务中运行并由 api-server 有界限流;未知、停用和 owner 错密账号使用 dummy hash 抹平耗时。member 常规权限粒度固定为后台 15 个一级 Tab,“账号管理”只允许 owner 且不可授予 member2026-07-24 起,历史花费手动对账作为独立高风险操作权限 `profile-wallet-consumption-reconcile`,不随任意 Tab 自动授予。member 每次请求重新读取当前账号并校验启停、`token_version`、Tab 权限和独立操作权限;任一权限、密码或启停变化递增版本并立即淘汰旧 JWT。账号不存在返回 `401`SpacetimeDB 故障保留 `502/503` 而不清理有效 token。前端导航和操作按钮过滤只负责体验,正式授权由 api-server 路由权限矩阵执行,未登记的新后台路由对 member 默认拒绝。后台面向运营展示管理员身份时统一使用 `displayName`;持久审计仍保存稳定 subject,由 api-server 解析显示名称,前端不得暴露账号 ID 或用登录用户名代替。写接口必须在主事务前加载显示名目录,或在主事务后降级解析,不能把已提交写入伪装为失败。
- 影响范围:`admin_account`、SpacetimeDB typed procedures / client facade、后台 JWT 与 session DTO、`/admin/api/accounts*`、后台路由权限中间件、admin-web 导航和账号管理页。
- 验证方式:SpacetimeDB schema / client / API 定向测试、`npm run check:admin-account-procedures` 隔离 procedure smoke、完整路由矩阵测试、admin-web 权限路由与账号 API 测试、owner/member 浏览器 smoke、`npm run check:spacetime-schema`、编码与 diff 门禁。
- 关联文档:`docs/technical/【后台管理】多账号与Tab访问权限方案-2026-07-14.md`。
## 2026-07-13 图片画布生成资源统一命名
- 背景:图片画布的普通图片、规范、角色、图标图集、UI 设计、宣发素材、视频和音频默认使用“类型 + 数字”命名,用户只能在生成后单独重命名素材,画布图层、项目资源和素材库名称容易不一致。
- 决策:主生成状态继续使用可选 `assetLabel`,名称最多 80 个字符并在提交时去除首尾空格;当前生成面板不展示“资源名称”标签和输入框,默认沿用现有自动编号名称,历史状态或内部调用若携带非空名称,仍必须让同一个名称贯穿 `assetLabel`、`canvasCompletion.title`、本地结果图层标题、项目资源和账号素材库,不允许各链路自行生成不同名称。移除名称输入后,角色、图标图集、UI 设计和角色动作等提示词输入恢复统一可见边框。
- 派生产物:图标和角色动作后端契约补齐 `assetLabel`。带背景原图、角色动作绿幕预览等具有独立复用价值的中间产物基于主名称追加“(原图)”等后缀;普通图片和图片修改的纯尺寸变换在内存完成后只上传一次,不生成“原始输出”副本。2026-07-29 起,图标切片不再按用户提示词命名,统一按全连通域视觉顺序命名为 `素材 N`。
- 影响范围:图片画布生成状态与面板、提交模型、图标和角色动作请求契约、项目资源 / 素材库持久化和相关编辑器文档。
- 验证方式:覆盖生成面板不渲染资源名称输入、提示词边框、空白回退、内部自定义名与长度限制,以及图片 / 图标 / 视频 / 音频 / 角色动作的请求名称、完成快照标题和素材名称一致性;运行前端定向测试、Rust 契约与 API 定向测试、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-07-13 图片画布多产物生成任务必须保存全部可恢复产物
- 背景:角色形象、图标 spritesheet 和 UI 素材提取会先得到带纯色背景的原图,再执行抠图或拆分;图片修改会先得到模型对齐尺寸的原始输出,角色动作会先得到绿幕预览视频,再抽帧和抠图。此前部分原始产物只登记到 OSS,或者要等后处理成功后才进入项目资源,用户无法在失败后找回已经生成成功的内容。
- 决策:凡一次资产生成任务产生多个具有独立复用价值的产物,后端必须把上游已返回的中间产物写入 OSS、`asset_object`、项目资源和账号素材库,再执行抠图、抽帧或拆分;未指定素材文件夹时进入默认“项目”文件夹。角色形象、图标 spritesheet 和 UI 素材提取在透明背景处理正常成功时同时保留纯色背景原图与透明后处理结果;透明背景处理最终失败时只保留已经持久化的 provider 原图,并按下一条降级规则收口。普通图片和图片修改的纯尺寸变换不属于独立产物:provider 回图保留在内存,变换成功只上传变换结果,变换失败只上传 provider 原图,整个流程只写一次 OSS 并只创建一个素材,不能制造重复“原始输出”。`nanobanana2` 使用标量清晰度档位和独立比例,保留 provider 输出尺寸,不按 `WIDTHxHEIGHT` 解析。角色动作把绿幕预览视频作为一个可复用素材保存,逐帧源图继续留在同一任务 OSS 路径,不把 32 至 48 帧逐张灌入素材库。去背景、音频等没有独立上游中间产物的任务不制造重复副本。
- 画布、成本与降级:有项目上下文的图片多产物继续由同一次 `canvasCompletion` 写入权威画布快照,正常成功时生成器 `generatedLayerId` 锚定主后处理结果,角色形象、图标 spritesheet 和 UI 素材提取都把 provider 原图作为第二个图层放在透明主结果右侧;图标和 UI 的业务拆分素材从原图右侧继续排列。三类任务已经保存 provider 原图、但透明背景处理最终失败时,任务以 `completed + warning` 收口,原图作为唯一主图完成画布占位;不写入不存在的透明处理图,图标和 UI 也不继续拆分。透明处理成功后的图标和 UI 图集自动拆分仍是 best-effort;识别或切片持久化失败继续完成整张透明图集,并在 inline、队列轮询和刷新后任务列表中提示非阻断 warning,不得借用失败错误字段。provider 原图或角色动作预览视频承载该任务的模型生成成本,抠图、逐帧处理、透明图集和切片等后处理派生产物的 `generation_cost_mud_points = 0`,避免把生图成本误显示成抠图成本;所有中间产物沿用所属任务的真实 `asset_kind`,角色原图仍为 `character`、图标和 UI 图集原图仍为 `icon-spritesheet`、角色动作预览仍为 `character-animation`,不得再写新的“原图类型”。后台素材查询按任务分页,最终产物作为父行并显示任务总成本,每个中间产物作为可展开的独立子行显示阶段生成器和阶段成本。扣费确认边界保持为 provider 成功,OSS、尺寸恢复和画布回填不延长退款保护。
- 2026-07-16 告警契约补充:inline / external v1 继续返回结构化原始诊断;queue 有意把通用 `warning` 或 `sliceWarning` 归一为展示就绪字符串,通用 `warning.reason` 原样保留,`sliceWarning.reason` 由 worker 添加“图集已生成,但自动拆分未完成:”前缀,摘要与 BFF 原样投影,Web 直接展示。历史值保留写入时快照,不按新格式回填或推断;该内部字符串契约通过 API/worker 与 Web 同一维护窗口、同版本发布收口,不增加混部兼容层。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`character_animation_assets.rs`、外部生成任务摘要、图片画布完成快照、账号素材库和前端生成提示。
- 验证方式:覆盖中间产物登记先于后处理、默认素材文件夹、图集拆分降级、inline / queue warning 和主结果锚定的定向测试,并运行 `cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`、前端定向测试、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-07-12 泥点充值收敛为四档并统一资产入口
- 背景:主站与图片画板的泥点余额入口、余额明细和充值弹窗存在不同实现,旧充值口径仍展示六档泥点、首充双倍和会员购买 / 升级入口,容易让展示、商品资格与后端余额真相发生漂移。
- 决策:主站与图片画板统一复用公共泥点资产入口,收起态展示总额与充值,展开态只展示不限时泥点、每日免费泥点和使用详情;充值中心 BFF 继续统一下发总额、三桶余额、限时到期时间、每日免费基础重置额及下次重置时间,前端不得自行相减推算,但会员周期限时泥点仅用于存量兼容和后端结算,当前版本不在前台展示。钱包明细每次展开都重新读取充值中心 BFF,打开期间实时总额变化时继续补读;图片画板的生成扣费或退款完成后同时刷新总额与充值中心拆分。充值中心读请求必须使用 revision 门禁,支付创建、到账确认等权威响应写入时使旧读失效,避免旧响应覆盖新的每日免费 / 不限时明细。默认泥点商品收敛为 `60 / ¥6`、`180 + 90 / ¥18`、`300 + 150 / ¥30`、`680 + 340 / ¥68` 四档,`60` 档无赠送,后三档按现有 `user_id + product_id` 独立资格规则首次购买加赠 `50%`。当前版本关闭会员购买页签、会员商品和购买 / 升级入口。
- 2026-07-17 追加:主站、图片画板与 AI 游戏创作独立 App 的泥点账单统一复用 `packages/shared/src/components/PlatformProfileWalletLedgerModal`。共享组件只依赖 `ProfileWalletLedgerResponse`,承接来源 label、金额正负号、UTC 日期、余额兜底和 loading / empty / error 展示;`/api/profile/wallet-ledger` 请求、鉴权、打开状态与重试生命周期继续由各宿主持有,不把账户事实或后端副作用下沉到共享 UI。
- 2026-09-07 追加:资产扣费在既有钱包流水 metadata 中记录服务端确定的 `assetKind``GET /api/profile/wallet-ledger` 只把白名单类型映射为可选用户文案 `reason`,不暴露原始 metadata、资源 ID、任务 ID 或未知内部枚举。共享账单组件优先展示非空 `reason`;历史、未知和空 metadata 继续按 `sourceType` 回退为“资产操作消耗”,不得由客户端猜测业务类型。
- 影响范围:`profile_recharge_product_config` 默认商品、充值中心 read model、共享前后端契约、主站与图片画板泥点资产入口、充值弹窗、后台充值商品默认值。
- 验证方式:充值与统一入口定向前端测试、`npm run typecheck`、充值商品定向 Rust 测试、`cargo check -p spacetime-module -p spacetime-client -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-07-12 每日免费泥点独立于任务并按北京时间日切
- 背景:现有“每日免费泥点”实际只是每日登录任务领取奖励,领取后进入普通永久余额,既不是独立余额桶,也不会在次日失效;同时主站仍展示每日任务卡片和任务中心入口,与新的产品口径不一致。
- 决策:新增 `profile_daily_free_points` 作为每日免费泥点事实源,基础额度固定为 `20`,以北京时间 `day_key` 为业务日;跨日后的首次余额读取或扣费原子清除昨日剩余及退款叠加量,并把当日额度重置为 `20`,对外语义始终视为北京时间 `00:00` 已重置。扣费按“每日免费 -> 会员周期限时 -> 永久”顺序;资产退款在同一业务日恢复原每日免费额度,跨业务日时把原每日免费消费部分叠加到退款当日每日免费桶,当日允许超过 `20`,下一业务日仍统一重置为 `20`。每日任务系统和 `daily_task_reward` 保留为普通永久奖励,但主站隐藏每日任务卡片及任务中心入口。
- 影响范围:`profile_daily_free_points`、`profile_wallet_ledger`、个人资金 read model、钱包扣费和退款 metadata、主站“我的”页、SpacetimeDB 迁移与生成绑定。
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、钱包定向 Rust 测试、个人中心定向前端测试、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-07-11 BgFilter 交叉模型否决用于角色形象与角色动作
- 背景:新版 BgFilter 的 `cross_check` 默认开启,会额外运行 HR-matting 第二意见模型;角色形象与角色动作序列帧需要保留发丝、镂空和运动边缘质量;图标 spritesheet 和 UI 素材提取不需要承担这部分额外推理开销。
- 决策:api-server 调用 BgFilter 时必须显式发送 multipart 字段 `cross_check`,不依赖服务端默认值。角色形象生成和角色动作逐帧去背固定传 `on`;图标 spritesheet 生成和 UI 设计图素材提取固定传 `off`。角色动作逐帧去背与三条静态生图路线复用同一条 `BgFilter → 阿里云通用抠图 → 本地键色` 降级链和同一 BgFilter 熔断器。该字段是后端内部供应商策略,不进入前端请求或外部 OpenAPI。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/api-server/src/character_animation_assets.rs`、图片画布 BgFilter 调用文档。
- 验证方式:运行 `cargo test -p api-server editor_bgfilter_cross_check --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_canvas_screen_background_generation_uses_bgfilter_postprocess --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_character_animation_frames_use_three_stage_matting_fallback --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-07-11 SpacetimeDB 工具链统一升级到 2.6.0
- 背景:生产数据副本验证已使用 2.6.0 standalone,而仓库 Rust crate、本地 CLI、生成 bindings、容器与 server provision 仍锁定 2.5.0 或更早版本,继续混用会增加 BSATN / procedure 返回值与发布产物错配风险。
- 决策:`server-rs/Cargo.toml` 的 `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 精确锁定 2.6.0;本地 CLI / standalone、Rust bindings、worker smoke、容器压测镜像和生产 provision 下载根同步对齐 2.6.0。其它 crate 恰好出现的 2.4.1 / 2.5.0 不随本决策机械替换。
- 影响范围:Rust workspace lockfile、SpacetimeDB bindings、本地 dev 版本门禁、容器 smoke / loadtest、server provision Jenkins 与项目 SpacetimeDB skills / 文档。
- 验证方式:核对 `spacetime --version`,运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo check` / 定向测试、`npm run test -- scripts/dev.test.ts`、server provision 工具测试、production ops / encoding / diff 门禁。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-07-17 SpacetimeDB 工具链统一升级到 2.6.1
- 背景:SpacetimeDB 2.6.1 修复 procedure context 中调用者 `Identity` / `ConnectionId` 丢失问题,并修正 TypeScript 生成代码中 `Option<T>` 字段的可选键语义;继续运行 2.6.0 会保留已知 procedure 身份回归。
- 决策:`server-rs/Cargo.toml` 的 `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 精确锁定 2.6.1;本地 CLI / standalone、Rust bindings、worker smoke、容器压测镜像和生产 provision 下载根同步对齐 2.6.1。
- 影响范围:Rust workspace lockfile、SpacetimeDB bindings、本地 dev 版本门禁、容器 smoke / loadtest、server provision Jenkins 与项目 SpacetimeDB skills / 文档。
- 验证方式:核对 `spacetime --version`,运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo check` / 定向测试、server provision 工具测试、encoding / diff 门禁。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-07-23 SpacetimeDB 工具链统一升级到 2.7.0
- 背景:SpacetimeDB 2.7.0 增加满足数据约束时的 unique / primary-key 非破坏迁移、Rust SDK capability traits、standalone MCP endpoint、SQL JSON 输出和更多连接/视图/内存指标,并修复旧 procedural-view backing table 的自动迁移。官方当前发行资产位于 `v2.7.0-hotfix3` 标签,二进制和 Rust crates 版本仍为 2.7.0。
- 决策:`server-rs/Cargo.toml` 的 `spacetimedb`、`spacetimedb-sdk`、`spacetimedb-lib` 精确锁定 2.7.0;本地 CLI / standalone 与 Rust bindings 使用官方 2.7.0 hotfix3 构建,worker smoke 本地镜像按运行版本标记 2.7.0,官方容器和生产 provision 下载根固定到 `v2.7.0-hotfix3`。provision 从 hotfix 资产标签解析运行版本时必须得到 2.7.0,并同时核对 CLI commit 为 `d220349a...`;裸 tag `a08663c7...` 不得因版本号相同而被复用,下载 / 安装结果也必须通过同一 commit 门禁。
- 影响范围:Rust workspace lockfile、SpacetimeDB bindings、本地 dev 版本门禁、容器 smoke / loadtest、server provision Jenkins 与项目 SpacetimeDB skills / 文档;现役 module 没有 procedural view,本次不修改 schema 或 migration。
- 验证方式:核对 CLI 版本和 commit,重新生成 Rust bindings,运行 `npm run check:spacetime-schema`、相关 Cargo check、server provision 工具测试、容器配置、Rust 1.93 兼容检查、standalone `/v1/ping`、encoding 和 diff 门禁。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-07-10 外部生成任务只持久化轻量媒体引用并独立维护摘要投影
- 背景:编辑器 worker 化后直接把同步接口 payload 序列化进 `external_generation_job.request_payload_json`;前端又把已有 OSS `objectKey` 下载成 Data URL 再提交,导致单个任务 JSON 膨胀到数 MB,正式任务列表读取 20 条任务时同时搬运约 65 MB payload,并放大为 SpacetimeDB 与 api-server 的瞬时内存峰值。此前“禁止 Data URL 持久化”只覆盖工程、素材、图层和元数据,遗漏了正式生成任务表。
- 决策:`external_generation_job.request_payload_json` / `result_payload_json` 同样属于正式持久化边界。对于本次事故涉及的 `source_module = editor-canvas` 任务,只允许普通业务参数和 `objectKey` / `resourceId` / `assetId` 等已登记轻量引用;任意层级 `data:` / `blob:` 与超限 JSON 必须由 api-server 和 SpacetimeDB 双重拒绝。编辑器已有媒体直接传正式引用,本地红框标记图先上传 OSS 后再入队,上传目录与文件名使用同一个强唯一 ID。其它玩法现存 Data URL 请求契约不在本次事故修复中被静默禁用,后续必须先完成各自资源化再扩大 DB 门禁。用户任务列表、单任务状态和 acknowledge 只读取不含 request/result payload 的 `external_generation_job_summary` 投影;acknowledge 只更新摘要小表并保留审计事件,不为确认通知加载 / 重写主任务 payload。提示词在入队时提前提取;错误摘要统一去除内联媒体并限制为 2048 字符;列表在单次 owner 扫描中只保留固定大小 top-N,不再收集全量历史后截断。历史终态 payload 仅允许迁移操作员通过默认 dry-run、`editor-canvas + job_id` B-tree cursor 显式分批压缩,pending / running 永不压缩;cursor 选择最多读取 `limit + 1` 行,apply 再逐条主键读取。首次发布默认 fail-closed 暂停在 Stdb 与 API 之间,保持维护模式并停止旧 API/controller/worker,完成压缩和摘要回填后才由指定审批人放行 API。
- 影响范围:编辑器生成提交 workflow、`external_generation_job`、`external_generation_job_summary`、外部生成 procedure / typed client / BFF、SpacetimeDB bindings、历史数据维护流程和图片画布文档。
- 验证方式:覆盖编辑器嵌套内联媒体与 payload 上限拒绝、非编辑器既有任务不被本轮门禁误伤、正式任务接口类型不含 payload、终态分批压缩不修改活动任务、已有 objectKey 不转 Data URL、本地标记图先上传再提交;运行外部生成定向 Rust / Vitest、`npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run typecheck`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/project-memory/shared-memory/pitfalls.md`。
## 2026-07-10 BgFilter segModel 保留内部字段,不进入外部 OpenAPI
- 背景:`api-server` 的图片生成、图标 spritesheet 与 UI 素材提取请求仍可反序列化 `segModel`,并识别 `birefnet` / `anime-seg`,以兼容内部调用和既有任务;但 BgFilter 当前受服务进程内存与并发容量约束,不同分割模型的内存占用并非可由外部调用方自由选择的稳定契约。
- 决策:`segModel` 是有效的**内部**字段,不是用户可配置字段。产品 UI 不提供抠图模型选择,应用内调用固定使用 `birefnet`;外部编辑器 OpenAPI 刻意不声明 `segModel`,并通过请求 schema 的 `additionalProperties: false` 拒绝该字段。外部调用方应省略它并使用服务端默认值;只有维护 BgFilter 容量与模型策略的后端代码可在经过内存 / 并发验证后调整内部固定值或兼容策略。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`src/services/image-editor/editorProjectClient.ts`、图片画布提交模型、`docs/openapi/genarrative-external-v1.openapi.json`。
- 验证方式:确认外部 OpenAPI 三个生成请求 schema 均未公开 `segModel` 且保持 `additionalProperties: false`;运行 `npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/project-memory/shared-memory/pitfalls.md`BgFilter 模型字段的对外暴露边界)。
## 2026-07-10 背景色决策统一走 gpt-5-mini 并挪到预扣泥点之后
- 背景:背景色决策此前无源图路径继承 `state.llm_client()`Ark/豆包,选色能力弱、线上从未真正调用 VectorEngine);且四条生成链路(角色生图 / 角色动作生视频 / 图标 spritesheet / UI 设计图提取)都在 `execute_billable_asset_operation_with_cost` 预扣泥点之前发起决策,导致用户余额不足或生成注定失败时仍白发一次 gpt-5-mini 决策、平台白付 token,也与定价文档「预扣失败不得继续调用上游」的原则相悖。
- 决策:(1)无源图决策改走独立常量 `EDITOR_SCREEN_BACKGROUND_TEXT_LLM_MODEL = gpt-5-mini`VectorEngineResponses 协议 + `reasoning_effort=low`),与有源图视觉档 `EDITOR_SCREEN_BACKGROUND_VISION_LLM_MODEL` 分离、便于各自调参;gpt5 客户端未配置时才降级回默认文本客户端。(2)**所有用到背景色决策的生成链路,决策必须在余额校验 + 预扣泥点之后发起**:预扣前只做颜色无关的算价 / 校验 / settings(动画用默认色占位算价,与队列路径一致),决策及颜色相关的 prompt / 合成 / `generationInputs` 搬进 billable 闭包,闭包把决策结果带出供后续抠图与落库使用。语义:余额不足则决策不跑(平台零成本);决策失败则闭包返 `Err` 走失败退款(用户不损失泥点)。后续新增任何用背景色的生成链路都必须遵循此顺序。
- 影响范围:`server-rs/crates/api-server/src/llm_model_routing.rs`、`editor_screen_background_decision.rs`、`editor_project.rs`(生图 / 图标 / UI 提取三条 `_for_owner`)、`character_animation_assets.rs`(动画 `_for_owner`);四条链路的 worker / inline / agent / external-API 入口同时覆盖。
- 验证方式:`cargo test -p api-server editor_project --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server character_animation --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml``editor_screen_background_decision::tests::live_*``-- --ignored`,需真实 `VECTOR_ENGINE_*`)验证有图 / 无图两条路径真实调用 VectorEngine。
- 关联文档:`docs/【编辑器】模型定价配置管理方案-2026-06-22.md`(预扣泥点原则)、`docs/project-memory/shared-memory/pitfalls.md`gpt-5-mini 图片输入上限实测)。
## 2026-07-10 图片画布模型定价以 SpacetimeDB 为运行时事实源
- 背景:后台模型定价原先保存到运行时 override JSON 文件,生产部署需要额外保证目录可写,且不符合当前 `server-rs + SpacetimeDB` 的配置事实源边界。
- 决策:模型定价默认 JSON 继续保留在 `server-rs/crates/api-server/config/editor-generation-pricing.default.json` 作为空表和数据库不可达时的兜底;运行时事实源改为 SpacetimeDB `editor_generation_pricing_config` 全局表,固定 `config_id = global`,以强类型 `models` 保存模型、单位和档位,不在 procedure / client 边界传递不透明 JSON。首次初始化通过 `initialize_editor_generation_pricing_config_if_missing_and_return` 在单事务内仅缺失时写入,并把 `ctx.sender()` 记为 `writer_identity`;表存在后 bootstrap secret 不得接管 writer,迁移操作员修复价格也必须保留 writer。runtime queue / 钱包 guard 只接受精确 writeridentity 轮换只能由迁移操作员调用独立 procedure,并写入 `editor_generation_runtime_identity_rotation` 审计表;migration operator 与 runtime writer 必须身份互斥,任何 operator 不能成为 writer,当前 writer 也不能授权为 operator,已有任一 operator 后 bootstrap secret 不得新增或接管 operator;生产统一由 `scripts/deploy/production-runtime-writer-identity-rotate.mjs` 双录新 identity 后执行并核对审计。后台 `/admin/api/editor-generation-pricing` 保存时必须入库,不再写 override 文件;主站 `/api/editor/generation-pricing` 和后端扣费入口优先读取 SpacetimeDB。旧 override 文件只作为启动本地缓存和首次空表种子的兼容来源。外部生成队列必须保存入队时价格,worker 的扣费、退款、响应和资产成本统一使用该冻结价格,配置更新不得改变已入队任务金额。队列 attempt 结算通过 `asset_operation_wallet_settlement` 持久化 consume/refund 配对或取消 intent;退款先到时,后续迟到 consume 必须失败,重复 ledger 只有用户、金额和来源一致才算幂等。lease 过期仅在 `attempt < max_attempts` 时允许重领;最终 attempt 耗尽后由 claim transaction 直接收口为 failed 并结算当前 attempt,不能再次进入 provider executor。运行时服务首次授权必须使用固定 64 位十六进制原始 bootstrap secretWASM 只嵌入其 SHA-256,发布 artifact 不保存原文:本地 dev 把专用 API token 与按 server/database 作用域的 secret 分别持久化为 gitignored `0600` 文件,只注入 api-server;人工 production release 自动生成的原文只写 `server-rs/.spacetimedb/build-secrets/<version>.txt`,目录 `0700`、文件 `0600`。生产 Jenkins 构建和发布阶段分别挂载同一个受保护 Secret FileBuild / Publish 的 credential ID 必须相同;构建阶段只计算并注入 SHA-256Stdb release manifest 以 `migration_bootstrap_secret_sha256` 记录摘要,发布阶段使用同一 Secret File 重算摘要并与 manifest 强制匹配后,才交给 `production-stdb-publish.sh` 安装为 root 持有、`genarrative` 组只读的固定运行时文件,归档和 `copyArtifacts` 都不含原文。Full Build 先发 Stdb、后发 API,所以 Stdb publish 必须同步补齐旧 API / worker env 的 FILE 配置并重启 active API / controller / worker,日志和前端子进程不得接触明文。重启前的 systemd 状态查询、active worker 枚举、重启后 active 复核以及原 active API 的本机 `/healthz` readiness 都是退出维护模式前的硬门禁,任一步查询或验证失败都必须保留维护模式。
- 追加约束(2026-07-10):`writer_identity` 只保留在 private 表和轮换审计内,不进入定价 procedure 返回快照;只有 HTTP 角色负责空表 seedworker / controller 启动改用受 writer 鉴权的 queue-stats procedure 做只读预检并继续 fail-fast。后台保存携带 `AppConfig` 已读取的受保护 bootstrap secret,只在配置行意外缺失时用于原子首写,已有配置仍按 writer / operator 鉴权且不能隐式轮换身份。
- 影响范围:`editor_generation_pricing_config`、`editor_generation_runtime_identity_rotation`、`asset_operation_wallet_settlement`、`editor_generation_config`、`spacetime-client` editor project facade、后台模型定价页、编辑器生成扣费链路、Stdb Build / Publish、Server-Provision、API deploy、生产 env 示例和模型定价文档。
- 验证方式:运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo check -p spacetime-module -p spacetime-client -p api-server --manifest-path server-rs/Cargo.toml`、模型定价路由定向测试、部署脚本 `bash -n`、`node --check scripts/dev.mjs scripts/check-production-ops-guardrails.mjs`、`npm run check:production-ops`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【编辑器】模型定价配置管理方案-2026-06-22.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-07-10 资产签名读取按入口和对象事实授权
- 背景:后台资源预览需要跨账号读取图片、视频和音频,但主站或 External API 若只按 generated 前缀签名任意 `objectKey`,知道私有对象键的调用方即可越权读取;`read-bytes` 若不复用换签授权也会形成旁路。
- 决策:主站 `/api/assets/read-url` 与 `/api/assets/read-bytes` 共用同一授权函数,并优先按配置 bucket / 精确 key 查询 `asset_object`。一旦存在 metadata,即使 key 命中 legacy 前缀,也必须按 `PublicRead` 或 owner ACL;只有同 bucket / key 未登记 metadata 的历史对象,才允许显式 `legacyPublicPath` 命中 `platform_oss::LEGACY_PUBLIC_PREFIXES` curated 白名单后匿名兼容。任意 `objectKey` 必须命中已登记 metadata。External `/api/external/v1/assets/read-url` 还必须有 `editor:asset` scope,并以 API Key 绑定 owner 执行同一检查;主站和 External 的 object confirm owner 均来自认证主体,不能信任请求体 owner,同 bucket / key 已登记后不得改变 owner。后台跨 owner 换签仅用于管理员资源预览,不放宽主站和 External 边界;成功换签后以 `admin_asset_read_url` 写入 `tracking_event`,持久化管理员 subject、请求对象和有效期,不保存 signed URL。未登记、跨 owner 和匿名私有对象统一按不存在处理。
- 影响范围:`api-server` assets / external assets / admin 路由、后台资源查询图片放大和音视频预览、OSS 读取契约与安全测试。
- 验证方式:定向测试覆盖 curated `legacyPublicPath` 可匿名签名、任意未登记 `objectKey` 拒绝、`PublicRead` 可读、owner 私有对象仅本人可读、External 跨 owner 拒绝、Admin endpoint 仅管理员可用,以及 `read-bytes` 与 `read-url` 同授权。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-07-09 角色动作视频生成背景色统一为多色自动决策 + 阿里云抠帧
- 背景:角色动作视频抽帧过去固定 legacy `#00FF00` 绿幕 + 本地 `editor_green_screen`,与生图链路的多色自动决策不一致;实测出现背景色与前景 / 皮肤撞色(蓝撞蓝、桃 / 黄撞肤色)以及图生视频背景变白的问题。
- 决策:角色动作视频背景色与生图统一。`screenColor=auto` 时由视觉 LLM`gpt-5-mini`Responses 协议、`reasoning_effort=low``max_output_tokens=1024`)读源角色图自动决策,并经硬过滤器(Lab 危险质量 + 皮肤专属三判据:ΔE 距离 / 色调投影 / RGB 分离)剔除与前景及皮肤撞色的候选,手动 hex 仍尊重用户选择;透明源角色图在提交 Ark 图生视频前先合成到选定背景色实色,使视频背景确定性等于抠图键色。抽帧后逐帧优先走 BgFilter(固定 `seg_model=birefnet`、`cross_check=on`),失败依次降级阿里云通用抠图和本地 `editor_green_screen` 键色兜底(按生成时选定的背景色,而非固定 `#00FF00`)。BgFilter 与阿里云抠图失败均写入 `external_api_call_failure` 失败审计。调色板新增中明度低饱和「灰竹绿 `#A0BBA0`」补齐冷区绿色段。
- 影响范围:`server-rs/crates/api-server/src/character_animation_assets.rs`、`editor_screen_background_decision.rs`、`editor_screen_background_filter.rs`(新增硬过滤模块)、`editor_green_screen.rs`(调色板)、`external_api_audit.rs`、`llm_model_routing.rs`、图片画布 MVP 与后端数据契约文档。
- 验证方式:`cargo test -p api-server editor_screen_background character_animation --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、真机对源角色图跑视觉决策与候选危险度表、抽帧后采样序列帧背景色确认落在冷区安全集。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-07-09 充值订单过期改为 SpacetimeDB scheduled 表触发
- 背景:旧充值过期处理使用 api-server 后台轮询 worker claim 普通 schedule 表,非 HTTP 的 external-generation-worker / controller 进程也可能启动同一过期任务;扩外部生成 worker 会意外放大微信查单 / 关单流量,并且本地过期后若微信仍可支付,容易出现“微信扣款但本地拒绝入账”的风险。
- 决策:新建原生 scheduled 表 `profile_recharge_order_expiration_timer`,创建真实微信 pending 充值订单时写入 5 分钟 timerscheduled reducer 到点只把仍为 `pending` 的订单改为 `expired` 并写 `expired_at`。HTTP `api-server` 只订阅活跃 timer 表的删除事件,按 `order_id` 重新读取订单并仅对 `expired` 执行微信查单补偿;支付或主动关闭导致的 timer 删除会被状态判断忽略,断线窗口由未检查过期订单 catch-up 补齐,不订阅完整 `profile_recharge_order` 历史表。`SUCCESS` 允许 `Expired -> Paid` 入账,未支付或远端已终态只记录检查结果,本地保持 `expired`。`external-generation-worker` 和 controller 不处理充值过期。
- 影响范围:`profile_recharge_order`、`profile_recharge_order_expiration_timer`、充值订单状态契约、`spacetime-client` bindings/facade、`api-server` 充值过期监听器、微信支付查单 / 关单、个人中心充值前端、后台表查询和运维文档。
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、充值过期 listener / 微信支付 / shared contracts / 前端充值定向测试、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-07-09 会员有效期与周期泥点重置分离
- 背景:账户会员制度新增 Starter / Basic / Pro / Ultimate 四档后,会员有效期、周期限时泥点和普通永久泥点容易被混成同一条时间线;升级场景尤其容易误把“补差额”实现成延长会员或重算 reset time。
- 决策:`profile_membership.expires_at` 只表示会员是否生效,`cycle_resets_at/cycle_period_days` 只表示会员周期限时泥点重置时间。同级会员购买只延长 `expires_at`,不发当前周期额外泥点,不移动 reset time;升级只补齐当前周期应发泥点差额并更新档位,不延长 `expires_at`,不移动 reset time,但后续周期天数切换为新商品配置。旧月 / 季 / 年卡购买同等级新会员档位时按同级迁移购买处理,延长有效期并切到 Starter / Basic / Pro,避免存量会员无法迁移;购买更高等级新档位仍按升级处理。周期刷新由后端在个人中心、充值中心、任务中心、账单读取和钱包扣费入口执行,先清上周期剩余限时泥点,再发当前档位周期额度;资产操作退款按原消费流水恢复同一周期限时泥点,避免把限时泥点退成永久泥点。
- 影响范围:`profile_membership`、`profile_recharge_product_config`、`profile_wallet_ledger`、充值中心、后台充值商品配置、个人资金 ViewModel、钱包扣费入口。
- 验证方式:`npm run spacetime:generate`、`cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml wechat_virtual_pay_params`、`npm run typecheck`、充值弹窗和资金 ViewModel 定向测试。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-07-09 AI 游戏创作 App Runtime V1 增加单 Agent 后台任务
- 2026-07-12 安全边界:`project.verify` 的 script 最多 160 个字符,固定使用系统 script shell,并在解析和执行前拒绝项目级 `.npmrc`。Runtime context bundle 必须绑定 `projectId / agentId / taskId / sessionId / runId / source / task`,结尾换行计入 64 KiB 上限;恢复时还要校验 `nextLoopIndex`、context window、当前窗口已完成轮数、观察指纹、计划和 observation 数量。bundle 写入必须拒绝父目录符号链接,读取必须基于同一文件句柄限制到 64 KiB,并清洗项目路径及常见平台凭据;已观察动作只有在 observation 写入 context checkpoint 后才能删除 ledger,下一轮 planning 和跨重启恢复不得再被旧 ledger 抢占。
- 背景:开发用单 Agent 聊天已经能真实调用各 Agent 的 LLM 路由并持久化对话,但 Agent 仍主要表现为同步问答,用户无法明确投递一个任务让某个 Agent 独立运行,也无法同时启动多个 Agent 的工作。
- 决策:在现有 `.agent/runtime` 和 `.agent/conversations` 基础上新增单 Agent 后台任务入口。Tauri 命令 `start_game_creator_agent_runtime_task` 立即写入该 Agent 的 runtime state/event/task history,追加用户任务到 `.agent/conversations/agents/<agentId>.jsonl`,随后在 App 进程内启动 tokio task 执行最小 Agent loopAgent 按轮输出 `thinkingSummary / plan / actions / response`Runtime 按白名单和项目权限策略执行工具并记录 `action / observation` 事件,再把已有 observation 放回下一轮 prompt,让 Agent 修正计划、继续行动或用空 actions + response 收束;单 Agent Runtime 每 6 轮形成一个上下文压缩窗口,窗口有新的独立 observation 时压缩上下文并在同一 run 继续,最近 6 轮没有独立进展或相邻窗口重复时以 `failed / budget-exhausted` 和 `loop-budget-exhausted` 终止,不生成总结伪装完成。完成或失败后把 assistant 回复或错误追加回对话,并写入 `.agent/agent.db` 审计记录。工具箱包含只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`project.diff`、`file.list`、`file.read`、`agent.run_status`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`command.run_limited`、`blackboard.write`、`agent.message` 和 `agent.delegate``memory.write` 可追加或覆盖本 Agent 私有记忆、项目长期/短期记忆或黑板,`file.write` 只能写项目内相对路径,`command.run_limited` 只接受 `game.static_smoke` 并复用本地静态自检安全边界,`blackboard.write` 追加共享黑板,`agent.message` 写目标 Agent 对话,`agent.delegate` 把任务投递到目标 Agent 的独立后台队列;策略拒绝时不执行工具并把 `blocked` observation 回给 Agent;策略要求确认时不执行工具,而是持久化精确待确认动作并暂停该 Agent 队列,待开发者确认或拒绝后在同一 run 续跑。每个 Agent 的任务历史落在 `.agent/runtime/tasks/<agentId>.jsonl`,读 runtime 时按 `runId` 去重返回最近任务,任务视角状态使用 `pending / running / completed / failed`Runtime state 增加 `nextStep`UI 在 Runtime 面板和主 Agent 状态卡展示当前任务、动作、下一步与最近任务。不同 Agent 使用独立 `.agent/runtime/locks/<agentId>.lock`,允许并行运行;同一 Agent 已有运行任务时,新任务会先进入该 Agent 的 pending 队列,当前 drain 持锁完成后串行继续下一条 pending。该能力仍不是独立 OS 进程或跨重启离线常驻 worker。
- 2026-07-10 补充:后台 Runtime 每次追加 `.agent/runtime/events/<agentId>.jsonl` 后会通过 Tauri `game-creator-agent-runtime-update` 事件广播当前 `AgentRuntimeResult`;开发单 Agent 聊天页、项目内 Agent 对话弹窗和主窗口 Agent 状态列表都只把该事件作为实时 UI 通知并复用前端 runtime 归一化合并,事实源仍是 `.agent/runtime/agents`、`events` 和 `tasks` 文件。
- 2026-07-11 补充:开发单 Agent 聊天页保留整页纵向滚动,聊天消息区固定响应式高度并在内部滚动;Runtime 恢复确认区使用独立布局行,避免与 Runtime 详情或聊天内容重叠。Runtime 面板详情可折叠且折叠时不渲染详情 DOM,但状态标题与任务控制按钮继续保留;等待 LLM 时在消息区持续显示动态状态和进行中提示,连续流式 delta 合并到动画帧更新并跳过重复 Runtime state。OpenAI Chat SSE 会跳过空 `choices` 心跳 / 元数据事件,收集 usage-only 尾包、保留 finish reason 与上游 error message,收到 `[DONE]` 后立即结束;正文与 finish reason 已接收后出现尾包异常时保存已完成正文,不把整轮改写成失败。持久事件订阅失败时显示非致命错误,聊天事件监听不可用或首个文本片段前流式失败时降级普通回复并继续落盘。
- 2026-07-11 补充:为缩小单 Agent 与 Codex CLI 在代码任务上的差距,Runtime 工具箱新增 `project.search` 和 `file.patch`,并扩展 `file.read` 的按行分页。`project.search` 在项目内执行有界字面量检索,默认忽略大小写,返回相对路径、行号和匹配行,跳过 `.agent`、敏感配置、依赖和构建目录;权限继承 `file.read`。`file.read` 接受 `startLine / maxLines`,返回带行号的最多 240 行、8,000 字符上下文,允许 Agent 继续分页而不是只看到文件开头约 900 字符。`file.patch` 只做 `oldText -> newText` 精确替换,必须声明预期匹配数,匹配数不符时不写入;它继承 `file.write` 权限,复用项目写锁和 Runtime 动作账本,并追加不含代码正文的 `agent.runtime.file.patch` 审计记录。三者组成“搜索定位 -> 分段读取 -> 局部修改 -> 再次读取验证”的最小代码工作闭环,不开放任意 shell。
- 2026-07-12 补充:单 Agent Runtime 工具箱新增受策略保护的 `file.delete`,补齐项目文件的完整生命周期。该工具只接受项目内相对 `path`,使用独立且默认 `confirm` 的 `file.delete` 权限,不继承 `file.write`;绝对路径、`..`、反斜杠、有效或悬空符号链接、目录和整个 `.agent/**` 控制面都失败关闭。Runtime 在项目写锁内、删除前先推进 project revision 并锁存当前 run 的 verification gate,成功后写 `agent.runtime.file.delete` 审计而不记录文件正文;目标已不存在时返回幂等 observation,但不撤销保守推进的 revision。自动与确认删除都经过 durable action ledgerpending action v3 额外绑定创建时的全局 project revision,等待确认期间任一 Agent 推进 revision 后旧动作必须进入 `needs-reconciliation`,不得删除漂移后的目标。崩溃停在 `executing` 时同样进入 `needs-reconciliation`,不得自动重放。删除后必须通过当前 revision 的 `project.verify` 或 `game.static_smoke` 才能收束;普通用户聊天和正式用户窗口不新增删除入口,也不因此开放任意 shell。
- 2026-07-12 加固:`file.delete` 取得项目写锁后必须重新读取当前项目和 Agent 权限策略;锁竞争期间从 allow 改为 deny 时立即阻断,从 allow 改为 confirm 时自动动作退回待确认,只有已确认动作可继续。Agent 私有记忆以及客户端开发面板的文件、记忆、资产、草案、导出和 checkpoint 恢复写入都在同一项目锁内保守推进全局 revision,确保等待中的旧删除动作不会作用于客户端刚改写的内容。manifest 持久化使用同目录临时文件;平台不能覆盖既有文件时先移动到 `.manifest.json.previous` 恢复副本,主文件缺失时从副本读取,安装新文件失败时恢复旧文件。
- 2026-07-11 补充,2026-07-12 更新:单 Agent 代码闭环新增开发专用 `project.verify`,用于在修改后执行项目根 `package.json` 已定义的固定脚本 `check / typecheck / test / lint / build`,或以 `check: / test: / lint: / typecheck: / build: / verify: / validate:` 开头、后缀由安全非空段组成的命名脚本;当前只支持 npm,不接受自由命令、参数或工作目录。Agent 必须先读取普通文件 `package.json`,再把真实存在的脚本名、完整原始脚本文本 `expectedCommand` 和 1-300 秒超时一起提交;Runtime 在真正执行前重新解析 JSON,要求脚本仍存在且正文精确一致,脚本漂移时拒绝执行。非 npm `packageManager` 或 pnpm / yarn / bun 锁文件必须失败关闭;`pre* / post*` 生命周期脚本名不在允许范围,npm 执行再附加 `--ignore-scripts`,阻止所选脚本关联的 pre/post lifecycle。该工具使用独立、默认 `confirm` 的 `project.verify` 权限,不再与 `command.run_limited` / `game.static_smoke` 共用授权;确认指纹覆盖脚本正文和超时。执行时不经过 App 自行拼接的 `bash -c`;继承环境被清理到 PATH 与必要平台变量,HOME/TMP/npm cache 隔离,stdin 关闭,输出保留有界头尾。Unix 下验证根进程正常结束或超时都会清理同进程组残留后代;项目写锁会按持有 PID 回收崩溃遗留锁,并拒绝 `.agent` 符号链接逃逸。进入进程执行后的成功、非零退出、启动失败和超时会写 `.agent/logs/command.log`、manifest command run 与 `agent.runtime.project.verify` 审计;输入预检拒绝则只进入 Runtime observation / error 事件。输出先过滤敏感内容再进入 observation。只要最新 `project.verify` 未通过,或通过后又发生 `file.write / file.patch / file.delete / project.restore`Runtime 就拒绝模型用空 actions 假完成,继续要求修复和重新验证;多窗口重复无进展而以 `loop-budget-exhausted` 终止时,仍未形成新通过结果则保持失败。该能力会执行用户项目自身脚本,环境隔离不等同于 OS 沙箱,不能把不可信项目脚本视为安全代码;它不是自由 shell 代理,也不进入普通用户命令入口。
- 2026-07-11 补充:开发侧新增 headless 单 Agent Runtime 入口 `npm run ai-game-creator-shell:agent-task -- [--init] <projectPath> <agentId> <task>`。该入口不实现第二套 Agent,只复用 Tauri App 的持久任务队列、per-agent 锁、LLM 路由、权限策略、工具 action / observation loop、对话和审计文件,并轮询到 `completed / failed / waiting-for-confirmation` 后用稳定键值行退出;`--init` 只在显式传入且 manifest 不存在时初始化项目。遇到待确认动作时 CLI 返回非零并打印 actionId、tool 和脱敏摘要,后续仍由开发窗口完成确认,不提供静默 `--yes` 绕过。
- 2026-07-15 收口:废止后台 Agent 工具规划和最终回复对 `EmptyResponse / Timeout / Connectivity / Transport / 408 / 429 / 5xx` 的原样自动重试。每个 Provider request lifecycle 只允许一次物理请求,专用客户端强制 `max_retries=0`,不继承全局或 per-Agent 的 `maxRetries`;可观察错误写唯一 `failed` 终态,`started` 后没有可信终态则进入 `needs-reconciliation` orphan barrier。只有显式 steer、Goal resume 或人工 reconciliation 后的新 request slot 才能建立新 lifecycle;工具协议格式修复使用新的 repair slot/lifecycle,不属于传输重试。
- 2026-07-11 调整,2026-07-12 更新:后台单 Agent 的 planning loop 每 6 轮形成一个上下文压缩窗口,每轮工具动作上限仍为 3;6 轮不再是整个 run 的固定上限。`loopIteration` 在同一 run 内连续递增,`maxLoopIterations` 指向当前窗口结束轮次,跨重启待确认动作按 context bundle 的 `nextLoopIndex` 继续。每个窗口结束时压缩已有 observation;窗口产生新的独立观察时继续同一 run,最近 6 轮没有独立进展或相邻窗口指纹重复时才进入 `failed / budget-exhausted`,并记录 `loop-budget-exhausted`,不会伪装完成。该调整只作用于后台单 Agent Runtime,不改变游戏草案 Generator/Evaluator 的 3 轮上限;旧实施摘要中“后台 3 轮后整理最终回复”或“整个 run 最多 6 轮”的描述由本条取代。
- 2026-07-12 补充:后台 Agent 每个 run 的可恢复 planning 上下文使用 `.agent/runtime/context-bundles/<agentId>/<runId>.json`。Runtime 通过临时文件替换原子写入,绑定 Agent、Task、Session、Run 和任务正文,保存 `nextLoopIndex`、当前窗口、计划、fallback response、压缩后的 observation 与上一窗口指纹;单文件最多 64 KiB、最多 12 条 observation。写入前统一截断并过滤敏感内容和项目绝对路径,安全校验失败时拒绝落盘;读取时要求普通文件,并校验 schema、Agent、Session、Run、任务正文和 observation 数量,身份不一致时拒绝续跑。该路径属于 Runtime 私有控制面,与根级 `.agent/context.bundle.json` 的旧 run-control 辅助文件不是同一契约,通用文件工具不得暴露。
- 2026-07-11 调整,2026-07-15 收口:开发者投递的后台任务从队列记录、Runtime `currentTask/currentGoal` 到待确认动作私有账本统一保留最多 4,000 字符,不再在入队时截成 180 字符。必要的 180 字符可见摘要只用于私有执行界面;公共 event、Agent DB、receipt、activity、output 和报告不再保存任务预览或正文,只保存 `taskSha256 / taskChars` 等身份、哈希和计数。LLM planning、显式恢复、确认续跑和重启恢复继续使用私有完整任务字段,避免丢失长需求末尾的验收条件、禁止项或输出格式。
- 2026-07-11 调整,2026-07-15 收口,2026-08-06 修正 token 契约:后台结构化 planning 使用独立的 4,000 生成 token 预算,最终回复使用 2,400;两者显式请求 low reasoning effort 和 low text verbosity。生成预算包含可见输出与隐藏 reasoning token,不是可见输出余量,也不是输入加输出总量。`platform-llm` 会把 reasoning effort 同时映射到 OpenAI Responses 的 `reasoning.effort` 与 Chat Completions 的 `reasoning_effort`,未设置时不新增字段;协议中立预算按 endpoint 能力映射为 Chat `max_completion_tokens` 或 legacy `max_tokens`、Responses `max_output_tokens`、Anthropic `max_tokens`。AGC 配置、Provider 请求序列化和重试 / handoff 指纹中的既有 `maxOutputTokens` 键冻结不变,避免升级后进入 reconciliation。低推理强度和较大的生成预算只用于降低空响应概率;`EmptyResponse` 仍按单次 lifecycle 的歧义失败处理,不再自动原样重放。
- 2026-07-11 补充:后台单 Agent 的工具计划响应只接受可反序列化为计划 schema 的 JSON object。解析器提取模型输出中的首个完整对象并允许对象后带普通说明;未找到完整 JSON 对象,或提取对象无法反序列化为工具计划时,Runtime 最多追加 2 次自动格式修复请求。每次修复只携带限长、脱敏后的上一次无效输出,并写入 `agent.runtime.tool_plan.repair` 审计。两次修复后仍无有效对象则按工具规划失败处理;工具规划阶段的普通文本不得转换为默认的空 actions + response,也不得据此把任务标记为完成。
- 2026-07-11 调整:工具计划顶层 `thinkingSummary / plan / actions / response` 四个字段必须同时存在,未知顶层字段、空 thinkingSummary 和空 tool 均属于协议错误并进入同一格式修复预算,`{}` 或前置无关 JSON 对象不能再触发空计划收束。空 actions 表示 planning 收束;response 非空时直接采用,response 为空时进入独立的最终回复生成。`agent.runtime.project.verify` 审计同时保存 `runId / actionId / actionFingerprint`,使并行 Agent 的失败与通过记录能够精确归属到发起动作。
- 2026-07-12 补充,2026-07-27 更正:OpenAI Chat / Responses 的后台 Agent 工具 planning 改用唯一 `submit_agent_tool_plan` 原生 function tool,字符串 `tool_choice=required` 和 strict schema;只接受恰好一次同名调用,arguments 继续经过本地计划 schema、工具白名单和权限策略校验,错误函数、多调用或非法 arguments 进入原有两次格式修复预算且不产生副作用。本条原写「Anthropic 保留文本 JSON 回退;planning 非流式」,已由 2026-07-27「Anthropic 与流式统一使用 Provider 原生工具」取代——Anthropic 同样发送原生工具目录,planning 不再因协议强制非流式。每轮成功协议写 `agent.runtime.tool_plan.protocol`,修复审计记录 protocol、callId 和 functionName。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `preview.start`,让 Agent 在完成写盘或静态自检后能按策略自行启动当前项目的 `127.0.0.1` 本地 HTTP 预览。该工具复用 `preview.start` 权限策略、项目写锁、共享 `PreviewRegistry`、manifest 预览状态、`.agent/logs/preview.log` 和 run trace 追加逻辑;写入 `.agent/agent.db` 的审计类型为 `agent.runtime.preview.start`。发给 LLM 的 observation 只包含 localhost URL 和端口,不包含用户项目绝对路径。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `canvas.asset_generate`,让美术类 Agent 可在 loop 中自行请求生成首版美术素材。该工具读取 AppData / Tauri 配置中的 `editorApi`,复用 `canvas.asset_generate` 权限策略、项目写锁、External Editor API 生成和下载链路、manifest 资产登记以及 `canvas.asset_generate` 本地索引记录;另写 `agent.runtime.canvas.asset_generate` 记录到 `.agent/agent.db`,标明触发的 agent 与本地素材路径。API Key 不进入 prompt observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。
- 补充:规范 Agent ID 统一使用 manifest taskId,例如 `art-asset-plan` 和 `code-prototype`;历史前端曾使用的 `group-role` 别名只在 Tauri command 层兼容并映射到规范 taskId。主窗口 Agent 状态列表通过 `read_game_creator_agent_runtimes` 批量读取 `.agent/runtime/agents/<taskId>.json` 和最近任务,把每个 Agent 的 Runtime 状态、当前动作和最近 task 直接显示在状态卡片和 `/agents` 汇总里。
- 2026-07-10 补充:单 Agent 聊天和后台 planning prompt 统一注入本 Agent 的 Runtime 连续上下文,包括最近状态、runId、当前任务、计划、观察、最近回复、最近工具动作、最近事件、最近任务和工具策略摘要;上下文只按规范 taskId 读取本 Agent runtime,进入 prompt 前过滤密钥和本机绝对路径。新后台 run 启动时继承同 Agent 上次 `recentToolCalls` 和 `lastResponse`,让下一轮任务能基于前一轮真实行动证据继续推理,同时不串入其他 Agent 的 runtime。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `file.list`,让 Agent 可先列出项目文件摘要或某个相对目录下的条目,再决定是否读取具体文件或继续行动。该工具复用 `file.list` 项目权限策略,策略要求确认或拒绝时不会枚举项目文件;observation 只包含项目相对路径、类型和大小,不读取文件内容、不返回项目绝对路径。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `project.diff`,让 Agent 可基于已存在 checkpoint 观察本地项目新增、修改和删除摘要。该工具复用 `project.diff` 项目权限策略,策略要求确认或拒绝时不会执行 diff;observation 只包含 checkpoint id、三类计数和项目相对路径,不返回本机绝对路径或文件正文。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `agent.run_status`,让 Agent 可在 loop 中读取自己、目标 Agent 或一组 Agent 的 Runtime 状态摘要,用于判断同伴是否正在运行、最近任务和最近工具动作。该工具复用 `agent.run_status` 项目权限策略,策略要求确认或拒绝时不会读取状态;observation 只包含 agentId、status、phase、runId、当前任务、当前动作、下一步、计划摘要、最近任务、最近工具和错误摘要,不返回 `.agent/runtime/*` 文件绝对路径。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `agent.delegate`,让 Agent 可把明确子任务投递到另一个 Agent 的独立后台队列。该工具复用目标 Agent 既有锁和 pending drain 语义,不创建平行 runtime;同一目标 Agent 串行,不同 Agent 可并行。该工具使用独立 `agent.delegate` 权限策略,策略要求确认或拒绝时不会写目标对话、不会启动目标后台任务,也不会写 `agent.runtime.agent.delegate` 审计记录。
- 2026-07-10 补充:主窗口 Agent 状态栏在开发模式新增“调度 Ready”控制,用来显式调用 `schedule_game_creator_agent_ready_tasks`。该控制先读取项目策略,命中 `agent.schedule_ready` confirm 时走现有确认弹窗,确认后才把 manifest ready task 投递到对应 Agent Runtime;普通用户窗口不展示这个开发控制。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `agent.schedule_ready`,让 Agent 在完成或更新 manifest task 后可按项目权限策略自行调度新 ready task。该工具复用同一个 scheduler:扫描依赖已完成且仍为 pending 的 manifest task,先标为 running,再按 taskId 投递到对应 Agent 的既有后台队列;策略要求确认时当前 Agent 停在 `waiting-for-confirmation`,不会静默启动下游 Agent。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `project.checkpoint`,让 Agent 在 `file.write`、`task.update` 或批量修改前自行创建本地 checkpoint。该工具复用 `project.checkpoint` 策略和项目写锁,observation 只返回 checkpoint id、文件数和总字节数,不返回本机绝对路径;策略要求确认时不创建 checkpoint。
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `project.restore`,让 Agent 在 diff 或自检发现走偏后可请求恢复到指定 checkpoint。该工具复用 `project.restore` 确认策略和项目写锁,observation 只返回 checkpoint id、恢复文件数和删除文件数;默认确认策略下不会静默回滚用户项目。
- 影响范围:`apps/ai-game-creator-shell` 的 Tauri command、Agent Runtime state/event、开发窗口单 Agent 聊天、项目内 Agent 对话弹窗、`appSurface.test.ts` 和 AI 游戏创作 App 实施计划。
- 验证方式:运行 Tauri Rust 后台 Agent 并行测试、壳前端 appSurface 测试、壳 typecheck、编码检查和 `git diff --check`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-08 AI 游戏创作 App v1 使用单窗口首页作为普通用户入口
- 背景:GameAgent V1 首页需求把登录后的入口定义为单窗口客户端首页,旧“先选项目再打开主窗口”的启动器概念会让普通用户流程割裂,也不符合首页先输入需求再选择目录创建项目的交互。
- 决策:普通用户启动 App 后先检查平台登录态,未登录只展示登录页,登录后进入同一个客户端壳。客户端左侧栏和顶部栏常驻,中部在首页、项目组、指南 / 反馈和项目开发占位之间切换;旧 Tauri 窗口 command 只保留兼容,不进入用户主流程。首页支持 `做游戏` / `做素材` / `做方案` 三种模式,发送需求时弹原生目录选择,非空目录必须二次确认;确认后只初始化本地项目、导入附件、追加首条用户需求和接收回执、写最近项目,并切到项目开发占位,不启动真实生成、不调用平台美术生成或 LLM 聊天。
- 补充:项目组页在同一窗口管理最近项目、打开项目、新建项目和显示目录。打开项目只进入已初始化且 `.agent/manifest.json` 可读的本地项目并切到项目开发占位;无效项目禁用,不自动重建历史路径。首页最近项目最多展示 3 个,空时隐藏。
- 补充:项目开发占位展示项目名、路径、创建模式、首条需求、附件导入结果、最近 run 状态和后续“项目开发画布”占位;“项目开发画布”是 GameAgent 项目的工作区概念,不等同于 `/editor` 图片画布工程。
- 补充:首页账户 / 泥点 / 精选素材只读取平台真实接口 `/api/profile/dashboard`、`/api/profile/wallet-ledger` 和 `/api/editor/showcase/resources`;失败时显示轻量空态,不伪造后端未下发字段。
- 补充:2026-07-18 修复首页“开启创作”打开目录选择器时的客户端冻结。目录和文件 picker 统一使用非阻塞 callback,并绑定到当前 `client` 窗口;不得把 `blocking_pick_folder` / `blocking_pick_file` 放回同步 Tauri command。首页直接创建项目仍是普通用户主路径,不要求先进入项目组。
- 影响范围:`apps/ai-game-creator-shell` 登录后渲染入口、首页 / 项目组 / 项目开发占位 UI、本地项目初始化与附件导入流程、AI 游戏创作 App 实施计划和 `CONTEXT.md`。
- 验证方式:运行 `npm run test -- apps/ai-game-creator-shell/tests/appSurface.test.ts`、`npm --prefix apps/ai-game-creator-shell run typecheck`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`CONTEXT.md`。
## 2026-07-03 AI 游戏创作 App 本地试玩包导出只打包运行白名单
- 背景:AI 游戏创作 App 需要给普通用户提供首版本地试玩包,但不能把项目记忆、trace、日志、运行时配置或密钥类文件混入可分发 ZIP。
- 决策:v1 新增 `/export` 聊天入口和 `project.export_package` 确认命令。导出前重新校验 `game/index.html` 是可试玩自包含 HTMLZIP 只包含 `game/**`、`assets/**` 和 `exports/README.md`,输出到 `exports/playtest-package-*.zip`;导出拒绝符号链接和不安全条目路径,并写入 manifest `commandRuns`、`.agent/logs/command.log` 和 `.agent/agent.db`。
- 补充:新增 `/exports` 只读聊天入口和 `project.export_list` 自动命令,用于列出当前项目 `exports/playtest-package-*.zip` 历史试玩包;该入口只读、不删除旧包、不做系统分享,给用户继续 `/export` 或显示目录的草稿。
- 影响范围:`apps/ai-game-creator-shell` 的聊天命令、Tauri 本地项目能力、共享命令契约和 AI 游戏创作 App 实施计划。
- 验证方式:运行 AI 游戏创作壳主窗口 smoke、Tauri `export` 定向测试、共享契约测试、类型检查、编码检查和 `git diff --check`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-01 AI 游戏创作 App v1 使用本地 JSONL 对话和派生 Agent 状态
- 背景:AI 游戏创作 App 已有 Godcoder 式本地工程护栏、项目黑板、角色私有记忆、manifest 和 run trace;新增结构化对话记录、agent 状态列表和单 agent 对话入口时,需要避免引入平行状态源或提前承诺后台 runner 能力。
- 决策:v1 结构化对话记录统一使用本地 `.agent/conversations/` append-only JSONL。普通聊天写 `.agent/conversations/project.jsonl`;从 agent 状态列表进入单个 agent 后,用户消息、agent 回复、工具建议和错误只写对应 `.agent/conversations/agents/<agentId>.jsonl`。Agent 状态列表从 `.agent/manifest.json` 的任务 / 角色清单和 `.agent/run.latest.json` / `.agent/runs/<runId>.json` 的 step、taskGraph、passPlans、lifecycleStatus 派生,并把 `taskGraph.tasks` 的任务状态与 active / carry-over / ready 编排标记显示在主窗口和单 agent 对话入口中;单 agent 最近证据里的安全相对输入 / 输出路径只填入 `/read <path>` 草稿,仍由用户发送并走既有 `file.read` / `agent.trace_read` 权限流。不新增独立状态数据库。项目黑板和角色私有记忆继续只保存稳定摘要,不承载原始对话流水。
- 补充:2026-07-08 起普通用户入口改为单窗口客户端首页;旧独立启动器 / 主窗口切换口径废止。首页发送需求或项目组新建项目时,先选择目录并在非空目录时二次确认,初始化成功后写最近项目并切到项目开发占位;取消或初始化失败则不切换视图、不写最近项目。最近工作区只保存在本机 WebView storage,可单项移除或清空,不进入项目文件或共享记忆;已初始化项目优先显示 manifest 项目名并保留路径副信息,`.agent/run.latest.json` 可读时显示最近 run 状态。最近项目路径缺失、不是目录、缺少可读 `.agent/manifest.json` 或检查失败时禁用打开,刷新只重新执行只读检查;“显示”只用系统文件管理器打开已确认存在的本地目录,未初始化但存在的目录也可显示,避免把历史路径误当新项目重建。
- 补充:项目开发占位“显示目录”复用同一只读目录打开能力,只打开当前本地项目目录,不初始化项目、不写项目文件、不切换工作区;顶部只读显示 manifest 项目名、项目路径、最近 `.agent/run.latest.json` 的 run 状态摘要和当前预览状态,并通过“刷新状态”重新读取同一 trace,不新增状态数据库。最近项目资产入口只读展示 localPath、kind、mediaType 和 source.kind,点击仍走原 `file.read` 权限流;旁边的“读取命令”只填入 `/read <path>` 草稿,不直接读取文件或绕过权限。项目开发占位里的项目黑板和 Agent 状态快捷入口仍只填入聊天草稿,不直接读取 run 辅助文件、不写 `.agent/policy.json`、不调用 LLM。
- 补充:首页、项目组和项目开发占位共用同一个运行时配置弹窗,配置只读写 Tauri 应用配置目录中的 `game-creator.config.json`,不写入项目文件或对话历史。
- 补充:项目组“打开”只进入已初始化且 `.agent/manifest.json` 可读的 AI 游戏项目;路径不存在、不是文件夹或只是普通文件夹时不切换到项目开发占位、不创建目录,用户需要创建或初始化时走“新建项目”。
- 影响范围:`apps/ai-game-creator-shell` 的主窗口 agent 状态列表、单 agent 对话入口、本地项目文件结构、共享契约和 AI 游戏创作 App 实施计划。
- 验证方式:文档更新先运行 `npm run check:encoding` 和 `git diff --check`;后续工程落地时补充壳 typecheck、Tauri Rust 测试和对话 JSONL / 状态派生的定向测试。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-06-30 AI 游戏创作 App 使用客户端配置文件
- 背景:`apps/ai-game-creator-shell` 是客户端 App,不应通过 `.env` 或进程环境变量承载 LLM / 画板同步配置;旧口径会让本地 secrets、CLI wrapper 和桌面 App 启动逻辑混在一起。
- 决策:仓库内 `apps/ai-game-creator-shell/game-creator.config.json` 只作为默认模板;发布 App 启动时在 Tauri 应用配置目录写入默认 `game-creator.config.json`,真实密钥和本机覆盖项都保存在该运行时配置文件中。主窗口提供“配置”面板读写该运行时 JSON;开发 CLI 无 AppHandle 时才回退读取仓库旁边的模板和 gitignored 本机覆盖文件。`llm.apiKey/baseUrl/model/apiKind/stream/requestTimeoutMs/maxRetries/retryBackoffMs` 驱动全局 LLM 路径,`agentLlm.<agentId>` 可为 Planner、Generator 和角色 agent 单独覆盖 API Key、base URL、模型、API 类型和流式请求,空项继承全局配置;`editorApi.baseUrl/apiKey` 驱动画板项目同步;`/llm-status` 只展示全局和各 agent resolved 后的 baseUrl、model、apiKind、stream 和 API Key 是否存在,不显示密钥;`/llm-routes` 复用同一只读检查结果,按 agent 展示 resolved provider 路由、单独路由数量和缺口数量,不请求上游、不显示密钥、不写项目。生成游戏或平台美术遇到 LLM / editorApi 缺配置错误时,主窗口自动打开运行时配置弹窗,但错误消息仍只显示缺失项,不回显密钥值。
- 影响范围:AI 游戏创作 App 的 Tauri Rust 配置加载、主窗口配置面板、CLI wrapper、agent-run smoke、`check-config` 门禁、`.gitignore` 和实施计划文档。
- 验证方式:运行 `npm run ai-game-creator-shell:typecheck`、`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-02 图片画布生成抠图背景色使用 screenColor 传递
- 背景:画布角色、图标和 UI 素材生成过去固定要求 `#00FF00` 绿幕,后续 BGfilter 服务需要按生成时背景色做去背景,不能继续把背景色写死在 prompt 或后处理里。
- 决策:角色形象、图标 spritesheet 和 UI 设计图素材提取不再向用户提供手动抠图背景色选择;前端用户路径统一提交 `screenColor=auto`,但用户可见生成输入快照不再写入 `抠图背景色` 或 `抠图模型`。api-server 在 12 个候选色中自动决策具体 hex,失败后兜底 `#CFEFFF`;最终 prompt 和 BgFilter 去背景只接收解析后的具体 hex 作为 `screen_color`。后端仍保留手动 hex 解析能力供内部兼容。角色动作背景色和抠帧口径已由 2026-07-09 决策取代。
- 影响范围:`/editor/canvas` 角色形象生成、图标素材生成、UI 设计图素材提取、BgFilter 服务入参、图片画布 MVP 和角色形象生成设计文档。
- 验证方式:运行画布生成模型 / workflow / API client 定向前端测试、`cargo test -p api-server editor_green_screen --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-image generated_asset_sheet_light_blue_key_color_removes_selected_background --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
## 2026-07-05 图片画布抠图背景色自动决策
- 背景:手动背景色选择对用户负担较高,且不同角色、图标和 UI 素材主题需要避开不同主体色;但 BgFilter 和生成 prompt 仍必须拿到明确的纯色 hex。
- 决策:前端用户路径直接固定通过 `screenColor=auto` 提交,不再展示背景色选项;api-server 新增 `editor_screen_background_decision` 模块,在角色形象、图标 spritesheet 和 UI 设计图素材提取组装 prompt 前解析 `screenColor`。手动 hex 直接校验并使用;`auto` 通过服务端 LLM 在 12 个候选色中选择具体 hex,最多重试 3 次,LLM 未配置、请求失败或返回非法颜色时 fallback 到 `浅雾蓝 #CFEFFF`。自动解析结果不写入用户可见生成输入快照;最终生图 prompt 和 BgFilter `screen_color` 永远只接收具体 hex,不透传 `auto`。
- 影响范围:`/editor/canvas` 角色形象生成、图标素材生成、UI 设计图素材提取、api-server LLM 调用、BgFilter 参数、图片画布文档。
- 验证方式:运行背景决策模块单测、画布生成模型 / workflow / API client 定向前端测试、`cargo test -p api-server editor_screen_background_decision editor_green_screen --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/【编辑器】画板UI设计图生成入口设计-2026-06-17.md`、`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`。
## 2026-07-05 BgFilter 失败时本地纯色去背兜底
> 后续更正:本条关于手动去背景仍使用独立 BiRefNet BFF 的描述已由 2026-07-14「手动去背景迁移到 BgFilter complex 模式」、2026-07-15 OSS 签名 URL 决策和 2026-07-17 内存生命周期决策取代。下文保留作历史记录。
- 背景:曾用一次性 BgFilter live probe 稳定复现 BgFilter 对 2K 输入返回 `HTTP 500 {"detail":"inference failed"}`,浏览器生成链路会因此收到“BgFilter 服务返回非成功状态”。
- 决策:角色形象生成、图标 spritesheet 生成和 UI 设计图素材提取仍优先调用独立 BgFilter;若 BgFilter 请求失败、返回非成功状态、返回空图片或非法图片,api-server 记录 warning 后使用本地 `editor_green_screen` 按解析后的纯色背景执行确定性去背兜底,不中断生成。手动任意图片去背景仍只走独立 BiRefNet BFF,不使用该兜底。
- 更正(截至 2026-07-10 实现):BgFilter 失败/熔断后不再直接本地兜底,而是先走阿里云通用抠图,仅阿里云也失败才本地 `editor_green_screen` 键色兜底;口径统一见 2026-07-09「角色动作视频…阿里云抠帧」决策,并已扩展到本条的角色形象/图标/UI 三条静态生图链路。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、BgFilter 运维排障、图片画布生成后处理。
- 验证方式:运行 `cargo test -p api-server editor_canvas_screen_background_generation_uses_bgfilter_postprocess editor_green_screen --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/【编辑器】画板UI设计图生成入口设计-2026-06-17.md`、`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`。
## 2026-07-03 图片画布生成纯色背景资产接入 BgFilter
> 后续更正:本条关于 multipart `file`、手动去背景独立 BiRefNet 配置以及 BgFilter 失败后直接本地兜底的描述,已分别由 2026-07-14、2026-07-15 OSS 签名 URL 决策和 2026-07-17 内存生命周期决策取代;其中固定 provider timeout 配置也已被 2026-07-21 的公式化双预算取代,当前请求分别携带 `maxQueueWaitMs / callBudgetMs`attempt 按 `N × est × 2` 派生,冻结 `N=16 / est=5000ms` 时为 `160s`,调用预算为 `321s`。下文保留作历史记录。
- 背景:独立 BgFilter 服务已部署在 image host,并提供 `POST /bgfilter/remove-background`,支持显式 `screen_color` 和 `seg_model`。手动去背景已有独立 BiRefNet BFF,不能把两个服务的配置或语义混在一起。
- 决策:角色形象生成、图标 spritesheet 生成和 UI 设计图素材提取在保存带纯色背景源图后,统一调用 BgFilter 生成透明 PNG;请求 multipart 字段为 `file`、`screen_color=<screenColor>` 和内部固定的 `seg_model=birefnet`。`segModel` 虽是后端可识别的内部兼容字段(另保留 `anime-seg`),但不向用户或外部 OpenAPI 暴露:当前 BgFilter 的内存与并发容量不适合由调用方自由切换模型。BgFilter 使用独立配置 `GENARRATIVE_EDITOR_BGFILTER_BASE_URL`、`GENARRATIVE_EDITOR_BGFILTER_TOKEN`、`GENARRATIVE_EDITOR_BGFILTER_REQUEST_TIMEOUT_MS`,默认 base URL 为 `http://58.87.105.82/bgfilter`,默认请求超时 `180000ms`token 未配置时复用 `GENARRATIVE_EDITOR_BACKGROUND_REMOVAL_TOKEN`。手动 `POST /api/editor/images/background-removals` 继续使用独立 BiRefNet 配置 `GENARRATIVE_EDITOR_BACKGROUND_REMOVAL_BASE_URL`,不受 BgFilter 影响。BgFilter 参数里的 `seg_model=birefnet` 只表示 BgFilter 内部分割后端,不等于手动去背景的独立 BiRefNet 服务。若 BgFilter 失败,api-server 对这些标准纯色背景生成图使用本地 `editor_green_screen` 兜底;连续失败达到 `GENARRATIVE_EDITOR_BGFILTER_CIRCUIT_FAILURE_THRESHOLD`(默认 `3`)后,`GENARRATIVE_EDITOR_BGFILTER_CIRCUIT_COOLDOWN_SECONDS`(默认 `300`)内直接本地兜底。角色动作背景色和抠帧口径已由 2026-07-09 决策取代:角色动作同样使用多色自动决策,抽帧后优先阿里云通用抠图,失败再按选定背景色本地兜底。更正(截至 2026-07-10 实现):BgFilter 失败与熔断期本条描述的「直接本地兜底」已过时——角色形象/图标/UI 三条静态生图链路同样先走阿里云通用抠图,仅阿里云也失败才本地 `editor_green_screen` 兜底。
- 影响范围:`server-rs/crates/api-server/src/config.rs`、`server-rs/crates/api-server/src/editor_project.rs`、图片画布 MVP 文档和角色形象生成设计文档。
- 验证方式:运行 `cargo test -p api-server config::tests::from_env_reads_editor_bgfilter_settings_and_reuses_background_token editor_project::tests::editor_canvas_screen_background_generation_uses_bgfilter_postprocess --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
## 2026-07-03 作品公开默认关闭
- 背景:作品发布完成不应默认进入公开广场 / 公开详情 / 公开互动消费路径,需要先由后台可见性开关明确开启。
- 决策:各玩法源表的 `visible` 新作品默认值改为 `false`;从草稿首次发布时仍保持 `false`,只有已发布作品再次发布 / 更新时才保留既有 `visible`。公开列表、详情、点赞、Remix 和正式公开 runtime 继续按 `Published + visible=true` 判断。旧迁移数据缺少 `visible` 时仍补 `true`,避免历史已公开作品被批量隐藏。
- 影响范围:`spacetime-module` 各玩法作品表、发布 / 编译 / Remix 写入路径、统一公开作品 read model、后台作品可见性管理。
- 验证方式:运行 `cargo fmt --manifest-path server-rs/Cargo.toml --all`、`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md`。
## 2026-07-04 陶泥儿精选改为素材提交审核后公开
- 背景:`/creation` 的 `陶泥儿精选` 过去依赖 `editor_project_resource.public_showcase_enabled`,生成画布资源默认可公开,和“作品公开默认关闭、由用户主动投稿精选”的运营要求冲突,也无法在后台审核、返还泥点和配置固定活动卡。
- 决策:`陶泥儿精选` 的公开事实改为独立 `editor_showcase_asset` 审核表。生成素材默认不公开;用户在账号级素材库对 `sourceType="generated"` 且有媒体内容的素材提交审核,后端快照素材信息并写入 `pending`。后台审核通过后写入 `approved`,但默认 `display_enabled=false` 且 `showcase_category=null`,运营可按前台具体 Tab 手动设置分类并开启展示;未设置分类的素材展示开启后进入前台“全部”,但不进入角色 / UI / 音乐 / 美宣具体分类。审核通过时按 `generation_cost_mud_points` 返还 50% 泥点;拒绝后写入 `rejected`。公开接口 `GET /api/editor/showcase/resources` 返回已通过、展示开启且媒体非空的快照,按通过时间和 `showcaseId` 倒序分页,并可携带后台配置的固定活动卡。旧 `editor_project_resource.public_showcase_enabled` 和旧 PATCH 接口只保留兼容,不再驱动精选公开。
- 影响范围:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`spacetime-client` 绑定与 mapper、`api-server` 编辑器和后台路由、admin-web 精选审核页、素材库右键菜单、`/creation` 精选瀑布流、图片画布文档和后端表目录。
- 验证方式:运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo check --manifest-path server-rs/Cargo.toml -p spacetime-module -p spacetime-client -p api-server`、前端 / 后台 typecheck 与精选相关组件测试,确认默认不公开、提交后 pending、审核通过后展示和返还、展示开关与点赞生效。
- 后续修正:精选批准、确定性返还流水和返还完成标记必须由同一个 SpacetimeDB procedure 在单事务内落地,失败时不得先留下 `approved`;已公开精选私有对象通过同 owner 的精确 `assetObjectId` / `objectKey` 派生匿名读取授权,不把 `generated-*` 前缀整体公开。
- 关联文档:`docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-07-03 外部编辑器 API 生成默认写入画布与素材库
- 背景:外部 API 面向美术 Agent 使用时,需要从自然语言自动选路,并保证生成结果不会只停留在接口回包里;同时后续素材生成需要复用已抽象出的美术规范,避免每次重新追问风格要求。
- 决策:外部编辑器 API skill 在新对话首个生成前先确认画布名称,并创建 / 复用同名画布项目和素材库文件夹。所有外部生成请求默认携带 `projectId`、`assetFolderId`、素材展示名和 `canvasCompletion`,使结果进入画布和素材库;角色动画端点当前不直接返回 `asset`,由 helper 在动画成功后用首帧补建素材库记录。skill 先把用户需求抽象为可复用美术规范,缺少目标素材必需信息时再追问;已有规范且用户未提出新规范时自动复用。
- 影响范围:`.codex/skills/genarrative-external-editor-api`、外部 OpenAPI 使用说明、外部画布生成集成方。
- 验证方式:运行 skill 校验、helper 自测、Python 编译检查、编码检查和 `git diff --check`;真实线上生成 smoke 需要本机 `~/.config/genarrative/external-editor-api.json` 中有有效 API Key。
- 关联文档:`docs/openapi/genarrative-external-v1.openapi.json`、`.codex/skills/genarrative-external-editor-api/SKILL.md`。
## 2026-07-03 新建项目与 AI 任务 ID 使用短前缀
- 背景:新建外部画布项目和 AI 任务 ID 需要统一以 `proj`、`task` 开头,同时保留旧 ID 兼容读取和路由。
- 决策:新建 editor project ID 前缀改为 `proj-`,新建 AI task / 生成任务 / 草稿任务 ID 前缀改为 `task-`。路由和读写仍按字符串处理,不新增拒绝 `editor-project-*`、`aitask_*` 或 `extgen-*` 的校验,历史数据继续兼容。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/module-ai/src/domain/ids.rs`、`server-rs/crates/api-server/src/editor_generation_queue.rs`、玩法外部生成入队路径、外部编辑器 API 新建项目返回值、AI 任务创建链路。
- 验证方式:运行 `cargo test -p module-ai --manifest-path server-rs/Cargo.toml`、定向 api-server editor project 测试、编码检查和 `git diff --check`。
- 关联文档:`docs/openapi/genarrative-external-v1.openapi.json`。
## 2026-07-03 画布Agent会话元数据入 SpacetimeDB、消息正文存 OSS
- 背景:图片画布工程需要对话式编辑历史,但消息正文随对话和工具结果增长,不适合放入表行或画布布局快照;同时画布 Agent 只属于编辑器画布域,不能复用拼图 `creative-agent` 内存会话。
- 决策:`module-editor-agent` 只承载可供 SpacetimeDB WASM 使用的纯领域规则;Agent runner、工具实现和资产 DTO 迁入原生 `platform-editor-agent`,仅由 `api-server` 依赖。`editor_agent_conversation` 只保存会话元数据,完整消息以 `editor-agent/{conversationId}.json` 会话粒度存 OSS`api-server` 负责编排 LLM、普通 JSON 消息、OSS 读写和既有生成工具调用。用户消息以独立 `clientMessageId` 在会话锁内幂等,数字 `message.id` 只作后端定位;旧 OSS 消息允许缺失幂等键,早期用户消息字符串 `id` 在读取时迁入 `clientMessageId`。画布 Agent 只与任务侧栏互斥,不与左侧素材 / 图层栏互斥。
- 影响范围:图片画布右侧 Agent 面板、`shared-contracts` / `packages/shared` 的 `editorAgent` 契约、`spacetime-module` / `spacetime-client`、`platform-oss` 内部读签名边界、画布生成落板规则。
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo test -p module-editor-agent --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_agent`、前端 Agent 面板与 JSON client 定向测试、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】画布Agent对话面板-2026-07-03.md`、`docs/adr/【ADR】画布Agent会话消息存OSS-2026-07-03.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-07-01 认证工作集只经 typed projection 同步正式表
- 背景:同手机号重复账号、兑换码白名单错配和微信资料不回写暴露出 `module-auth` 内存工作集、`auth_store_snapshot` 和正式认证表之间仍有历史互刷路径;旧 JSON 快照会把过期手机号索引或用户资料重新带回运行态。
- 决策:删除 `auth_store_snapshot` 表和旧 `import_auth_store_snapshot_json` / `export_auth_store_snapshot_from_tables` procedure`module-auth` 只保留内存工作集和 typed `AuthStoreProjectionView` 导入 / 导出。运行中认证写操作通过 `sync_auth_store_projection` 同步 `user_account` / `auth_identity` / `refresh_session`,启动恢复通过 `export_auth_store_projection_from_tables` 从正式表恢复内存。账号资料真相只在 `user_account``auth_identity` 只保存登录入口身份键。
- 影响范围:`module-auth` projection API、`spacetime-module` auth schema/procedure、`spacetime-client` bindings/facade、`api-server` 启动恢复和认证同步、后端架构文档与认证排障记忆。
- 验证方式:`npm run spacetime:generate`、`SPACETIME_SCHEMA_GUARD_ALLOW_BREAKING=1 npm run check:spacetime-schema`、`cargo test -p module-auth --manifest-path server-rs/Cargo.toml -- --nocapture`、`cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-29 图片画布手动抠图走远端 BiRefNet BFF
> 后续更正:本条独立 BiRefNet 服务、专用 base URL 以及 api-server 下载并解析原图的实现,已由 2026-07-14「手动去背景迁移到 BgFilter complex 模式」、2026-07-15 OSS 签名 URL 决策和 2026-07-17 内存生命周期决策取代。下文保留作历史记录。
- 背景:用户手动“去除背景”面对任意图片,前端 `chromaKey` 和标准绿幕后处理不适合复杂人物、自然背景或非纯色背景;远端 image host 已部署 BiRefNet 服务,需要让手动抠图走高质量模型,同时避免把服务令牌暴露到浏览器。
- 决策:画布手动“去除背景”默认调用登录态同源 BFF `POST /api/editor/images/background-removals`。api-server 解析当前图片后代理到 `GENARRATIVE_EDITOR_BACKGROUND_REMOVAL_BASE_URL/remove-background`,默认指向 `http://58.87.105.82/remove-background`,可选 `GENARRATIVE_EDITOR_BACKGROUND_REMOVAL_TOKEN` 只在服务端注入。api-server 对上游结果做字节和尺寸上限保护,并先落 OSS / asset object 再返回给前端。编辑器自己生成的标准绿幕资产不属于该决策,见 2026-06-30 绿幕契约收口。
- 影响范围:api-server 编辑器图片接口、图片画布手动去背景、画布右上角任务侧栏、图片画布 MVP 技术文档。
- 验证方式:运行 `cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server config::tests::from_env_reads_editor_background_removal_settings --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、定向画布 workflow 测试、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-26 React 组件测试按用户行为与稳定契约收敛
- 背景:部分 React 测试把组件内部状态、测试专用 DOM 探针、图标 class、完整按钮顺序或精确长文案当成契约,正常 UI 重构时容易误报,增加维护成本。
- 决策:新增和重写 React 测试时,默认分成用户流程测试、稳定契约测试、hook / model 逻辑测试三层。用户流程测试优先断言 role / label / URL / 弹窗 / callback 等可感知结果;演化中的 DTO 和 callback payload 使用关键字段或 `expect.objectContaining(...)`hook 测试使用 `renderHook` 验证公开返回契约,不再为读取内部状态制造 `data-testid` 仪表盘。
- 影响范围:前端 React 组件测试、图片画布测试、平台入口测试、后续共享组件和 hook 测试新增 / 重写方式。
- 验证方式:运行定向 React 测试、`npm run typecheck`、`npm run check:encoding` 和 `git diff --check`;出现正常重构引发测试破碎时,优先把测试改到用户行为或稳定契约层。
- 关联文档:`docs/technical/【前端测试】React组件测试准则-2026-06-26.md`、`src/components/image-editor/useCanvasGenerationDialogs.test.tsx`、`src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx`。
## 2026-06-26 AI 游戏创作 App 生成过程必须在聊天可见
- 背景:普通用户窗口只保留聊天入口,但如果生成确认后只显示“已生成草案”和本地产物路径,真实 LLM / Agent loop 会被误解成固定模板落盘。
- 决策:`game.generate_draft` 保持正式用户窗口不展示开发面板,但必须通过聊天实时显示 Planner LLM、Orchestrator、6 组角色 brief、Generator LLM、Evaluator、ArtifactWriter 和自检进度;生成完成后普通聊天消息直接展示 `.agent/run.latest.json` 的 Run、LLM 对话、loop 轮次、active / carry-over 任务、编排轮次、最近步骤、建议命令和本地产物快照;没有同步建议命令时,首个安全产物只提供 `/read` 草稿,`/trace` 继续读取同一份完整证据。
- 影响范围:`apps/ai-game-creator-shell/src/App.tsx`、`apps/ai-game-creator-shell/src-tauri/src/main.rs`、AI 游戏创作 App 聊天体验和实施计划文档。
- 验证方式:运行 `npm run ai-game-creator-shell:check`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-06-26 AI 游戏创作 App 增加显式质量评审 Gate
- 背景:AI 游戏创作 App 已有 Evaluator loop 和静态 smoke,但任务图、能力清单和 trace 中没有单独的质检 / 评审任务,用户无法从 `/tasks`、`/trace` 或 `/audit` 看出质量评审是明确环节。
- 决策:保持策划、美术、程序、数值、音乐、运营 6 个专业组不变,在程序组内新增 `quality-review` / `Review` 角色任务;Evaluator 的评审 step 绑定到该任务,依赖顺序为 `code-prototype -> quality-review -> preview-readiness -> preview-playtest -> publish-strategy -> publish-package`。`game.static_smoke` 只完成 `preview-readiness`,不代替质量评审。
- 影响范围:AI 游戏创作 App 任务图、共享契约、Tauri trace / manifest 状态推导、聊天 `/capabilities` `/tasks` `/trace` `/audit` 摘要和实施计划文档。
- 验证方式:运行 `npm run ai-game-creator-shell:check`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-06-25 AI 游戏创作 App 真实 LLM 联调用流式请求
- 背景:AI 游戏创作 App 的真实 OpenAI-compatible provider 验收中,小请求可返回,但 Planner 等稍长非流式请求会在上游响应前被网关空闲连接切断,表现为 TLS record 解密失败;本地无密钥 provider smoke 不能覆盖该真实网关行为。
- 决策:`platform-llm` 文本 client 使用系统 TLS backend,并保留底层错误链用于排障;AI 游戏创作 App 通过客户端配置项 `llm.stream=true` 开关打开流式请求,打开后 Planner、组内角色和 Generator 走流式请求。
- 影响范围:`server-rs/crates/platform-llm`、`apps/ai-game-creator-shell/src-tauri/src/main.rs` 和 AI 游戏创作智能体 App 实施计划。
- 验证方式:运行 `cargo test -p platform-llm --manifest-path server-rs/Cargo.toml request_text_parses_non_stream_response`,并用真实 OpenAI-compatible 本机配置执行 `npm run ai-game-creator-shell:agent-run -- --no-wait /tmp/genarrative-ai-game-real-loop-test-6 "做一个像素风反弹弹幕厨房小游戏..."`,确认 36 个 trace step、36 次 tool call、`game.static_smoke`、`preview.start` 和 `preview.stop` 完成。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
2026-06-27 追加,2026-06-30 更新:`platform-llm` 旧 `LlmTextRequest` / `LlmTextResponse` 已直接替换为 provider-neutral 的 `LlmRunRequest` / `LlmRunResponse`API kind 先固定为 `openai_chat`、`openai_responses`、`anthropic` 三类。AI 游戏创作 App 改用客户端运行时配置(Tauri 应用配置目录的 `game-creator.config.json`),LLM 维度由 `llm.apiKind` 控制,默认 `openai_responses`,可设为 `openai_chat` 接旧 Chat Completions 兼容网关,或 `anthropic` 接 Anthropic Messages。当前 run 响应只保留通用文本、finish reason、response id 和 usage,高级能力后续按 capability 扩展,不把业务层绑死到 Responses 字段。
## 2026-06-24 AI 游戏创作 App 生成编排使用文件驱动 loop
- 背景:AI 游戏创作 App 的 `game.generate_draft` 已接入 LLM,但单次请求仍不能体现 Planner / Generator / Evaluator 的协作闭环,也无法把评估反馈作为下一轮生成输入。
- 决策:v1 使用最小文件驱动 loop,不引入 LangChain、AutoGen、Microsoft Agent Framework 或 OpenAI Agents SDK sidecar。Planner 写 `.agent/spec.md`;每轮先调用策划、数值、美术、音乐、程序、运营 6 组下的 15 个角色 agent,角色 brief 写到 `.agent/passes/pass-N/groups/<group>/*.md`,再由 `GroupCoordinator` 汇总到 `.agent/passes/pass-N/groups/*.md`Generator 读取 spec、`.agent/findings.md` 和 6 组汇总 brief 生成结构化游戏草案。LLM JSON 必须带 `handoffs` 数组并覆盖 `design`、`balance`、`art`、`audio`、`code`、`publishing` 6 个专业组;每轮再把这些结构化交接快照写到 `.agent/passes/pass-N/`。Evaluator 做本地静态验收并写 `.agent/findings.md`,最多 3 轮;返工轮必须把 findings 转成结构化 `repairRoutes`,记录每条问题命中的 taskIds 和 reason,再据此选择 activeTaskIds。每次运行另写 `.agent/run.latest.json` 和 `.agent/runs/<runId>.json`,记录 step、角色级 `toolCalls`、组汇总、专业组交接、输入输出路径、artifact 字节数与 `fnv1a64:` checksum;每个 step 带 phase、taskId、group 和 roletrace 顶层 `taskGraph` 记录 goal、readyTaskIds、activeTaskIds、carriedTaskIds、repairFocus、repairRoutes 和当前任务状态,`passPlans` 逐轮记录 mode、summary、activeTaskIds、carriedTaskIds、dependencyWaves、repairFocus 和 repairRouteslatest 是当前指针,runs 目录保留历史 trace,作为开发窗口和后续工具调用 trace 的事实源,schema 由共享 TS/Rust 契约 `game-creator-agent-run.v1` 固定。最终产物写盘时追加 `ArtifactWriter / file.write.local_artifacts` step,随后自动跑白名单 `game.static_smoke`,检查 `game/index.html` 具备 canvas、canvas 渲染上下文、绘制调用、主循环、输入监听、明确目标、失败或胜利状态和重开路径,且不使用远程资源、`eval`、`new Function`、`localStorage`、`fetch`、`WebSocket` 或 `ServiceWorker`,再把 Playtest 工具调用写回 trace;通过后把 runId、状态、轮次、下一步、active / carry-over 任务和最终本地产物摘要追加到 `memory/session.md` 与 `memory/project.md`,让下一次 Planner / 角色 agent / Generator 从记忆输入直接看到上一轮稳定原型;后续 `preview.start` 会在已有 trace 上追加 Preview 工具调用和本地预览 URL。
- 决策补充:普通用户聊天 `/trace` 读取同一份 `.agent/run.latest.json`,但摘要必须把 activeTaskIds、carriedTaskIds、repairRoutes 和 dependencyWaves 从内部 taskId 映射成专业组 / 角色 / 任务名,确保不打开开发窗口也能看出 6 组 agent、组内角色、返工路线和 carry-over 真实发生。
- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/main.rs`、`packages/shared/src/contracts/gameCreationApp.ts`、`server-rs/crates/shared-contracts/src/game_creation_app.rs` 和 AI 游戏创作智能体 App 实施计划。
- 验证方式:运行 AI 游戏创作壳 Rust 测试、共享契约 TS/Rust 测试、壳 typecheck、编码检查和 `git diff --check`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-06-24 AI 游戏创作 App 编排 v1 使用 ready-task 选择器
- 背景:AI 游戏创作 App 已有专业组任务拆分和依赖字段,但如果没有当前可执行任务选择器,“任务编排”只停留在静态清单,普通用户在聊天里也看不到下一步由哪组 agent 接手。
- 决策:v1 编排先使用最小 ready-task 规则:只选择 `pending` 且所有依赖任务均为 `completed` 的任务;共享 TS/Rust 契约和 `platform-agent` 都提供同一语义的选择器,聊天 `/tasks` 只展示下一步可执行专业组,不新增独立编排面板或外部 agent 框架。
- 影响范围:`packages/shared/src/contracts/gameCreationApp.ts`、`server-rs/crates/shared-contracts/src/game_creation_app.rs`、`server-rs/crates/platform-agent/src/game_creation.rs`、`apps/ai-game-creator-shell/src/App.tsx` 和 AI 游戏创作智能体 App 实施计划。
- 验证方式:运行共享契约测试、`platform-agent` 与 `shared-contracts` 的 Rust 测试、AI 游戏创作壳 typecheck、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-06-24 外部生成队列升级为正式生成任务列表
- 背景:外部生成队列已经承载画板和玩法的付费生成,但前端只展示排队概览,缺少可追溯任务列表、后端确认状态、完成提示补弹和退款记录到任务的追踪关系。
- 决策:`external_generation_job` 同时作为正式生成任务列表事实源,保存 `price_mud_points`、`refund_ledger_id` 和 `notification_acknowledged_at`;新增 `external_generation_job_event` 追加状态转换审计。BFF 新增当前账号任务列表和 acknowledge 接口;前端只展示后端任务状态,完成 / 失败提示关闭时由后端写确认时间,未确认终态任务在下次登录后按列表集中弹出。任务触发的钱包扣费 / 退款流水 metadata 必须写 `externalGenerationJobId`,本机退款 outbox 重放也保留该任务 ID。
- 2026-06-25 追加:平台壳的当前账号任务列表只在登录、网络恢复、页面回到前台或已有 queued/running/未确认终态任务时刷新;空队列刷新一次后不保持 4 秒轮询,避免 `/api/runtime/external-generation/jobs` 在无任务时持续请求。
- 影响范围:`spacetime-module` 外部生成 schema / procedure、`spacetime-client` bindings/facade、`api-server` 外部生成 BFF、worker 失败回写和资产计费退款链路、平台入口“我的”页任务卡和完成提示弹窗。
- 验证方式:运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo test -p spacetime-module external_generation --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server wallet_refund_outbox --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-23 编辑器宣发素材固定 gpt-image-2
- 背景:画板宣发素材的游戏首图、详情五图和运营海报只应使用稳定的宣发图生成链路,不能被图片模型上次选择或旧请求切到 `nanobanana2`。
- 决策:三个宣发素材工作流前端面板只显示禁用态 `gpt-image-2` 模型胶囊,生成提交固定携带 `gpt-image-2` 且不写入图片模型记忆;后端 `/api/editor/images/generations` 对 `kind = "publication-material"` 强制归一为 `gpt-image-2` 后再生成和按运行时模型定价扣费。`生成角色形象` 的默认图片模型继续使用 `nanobanana2`。
- 影响范围:图片画布宣发素材面板、图片生成提交模型、编辑器图片 BFF、宣发素材设计文档和 Lovart 生成面板方案。
- 验证方式:运行宣发素材提交模型 / 面板测试、`api-server` 宣发素材模型锁定测试、前端类型检查、编码检查和 `git diff --check`。
- 关联文档:`docs/【编辑器】宣发素材工具演示入口设计-2026-06-17.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-23 陶泥儿产品 IP 形象统一为陶罐探出橙色耳朵形象
- 背景:平台左上角“陶泥儿”品牌旁产品形象曾分散使用创作主页旧小陶偶、运行态 logo 或局部欢迎图,后续提到产品 IP / 产品形象容易产生歧义。
- 决策:产品 IP / 产品形象统一定义为 `public/branding/taonier-product-ip.png`,即陶罐中探出的橙色耳朵形象。公共品牌组件 `RpgEntryBrandLogo` 默认展示该图;后续左上角品牌区和产品形象说明默认引用该资产,专题玩法若有独立运行态素材需在对应文档单独说明。
- 影响范围:平台左上角品牌区、绑定手机号页品牌块、创作主页工作台 chrome、公共品牌资产常量和产品基线文档。
- 验证方式:运行品牌标识、绑定手机号页和平台首页相关前端测试,确认品牌图 `src` 为 `/branding/taonier-product-ip.png`;执行 `npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md`。
## 2026-06-22 创作主页精选展示全站公开画布生成资源
- 背景:`/creation` 的 `陶泥儿精选` 曾从账号级素材库读取,并在素材为空时用公开作品图片补充,导致新创作页出现不属于任何当前图片画布项目的素材。
- 决策:该历史决策已被 2026-07-04 的“素材提交审核后公开”取代。历史背景仍有效:公开作品图片和假数据不应回填精选;但精选事实源不再是 `editor_project_resource.public_showcase_enabled`,而是 `editor_showcase_asset` 审核快照。
- 影响范围:`/creation` 创作主页、`creationShowcaseModel`、公开精选 BFF、图片画布素材列表右键菜单、账号素材库快照、创作主页改版计划和精选素材相关测试。
- 验证方式:运行 `src/components/creation-home/creationShowcaseModel.test.ts`、`CreationLandingView.test.tsx`、`ImageCanvasAssetRowView.test.tsx`、`useImageCanvasAssetLibrary.test.tsx` 与 `src/services/image-editor/editorProjectClient.test.ts`,确认公开生成资源展示、上传素材过滤、公开开关隐藏资源、删除入口在右键菜单中、公开作品不再 fallback。
- 关联文档:`docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-22 编辑器生成模型默认定价调整
- 背景:图片画布生成按钮、后端模型定价扣费和后台定价页需要统一使用新的模型默认泥点。
- 决策:`audio1.0` 默认按次 `5` 泥点,`chirp-v5` 默认按次 `12` 泥点,`gpt-image-2` 默认 `1K=3`、`2K=5` 泥点;运行态仍允许后台 override 覆盖,前端兜底必须与后端默认 JSON 保持一致。
- 影响范围:`editor-generation-pricing.default.json`、`ImageCanvasGenerationModel.ts`、后台定价页 fixture、后端价格计算和编辑器定价文档。
- 验证方式:运行 `editor_generation_config`、公开定价路由、图标素材价格校验、图片画布定价模型和后台定价页相关测试。
- 关联文档:`docs/【编辑器】模型定价配置管理方案-2026-06-22.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 2026-06-22 编辑器生成扣费与新用户赠送收口
- 背景:画板多个生成按钮已经展示泥点消耗,但部分图片、图标、UI 提取、视频、角色动作或音频链路只校验 / 展示价格,没有统一进入钱包预扣;新用户注册送泥点也需要与当前生成价格匹配。
- 决策:编辑器所有外部生成入口不再从前端请求接收 `priceMudPoints`,后端按运行时模型定价配置计算价格后统一进入 `execute_billable_asset_operation_with_cost` 或等价音频发布扣费链路;角色动作和视频使用真实登录用户作为扣费 owner。新用户注册赠送固定为 `100` 泥点。
- 影响范围:编辑器图片 / 图片修改 / 图标 spritesheet / UI 提取 / 视频 / 角色动作 / 音频生成 BFF,前端画板生成提交模型,外部 OpenAPI`module-runtime` 钱包注册奖励。
- 验证方式:运行编辑器图片、图标、UI 提取、视频、角色动作、音频扣费结构性测试,前端生成提交和 API client 测试,`module-runtime` 注册奖励测试。
- 关联文档:`docs/【编辑器】模型定价配置管理方案-2026-06-22.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-22 AGENTS.md 收敛为入口导航
- 背景:`AGENTS.md` 同时承载项目记忆、RAG、Issue、UI、Git、后端、SpacetimeDB 和文档图谱等细则,入口过重,复杂任务启动成本高。
- 决策:`AGENTS.md` 只保留最高优先级规则、任务路由、后端红线、验证提交要求和文档图谱;新增 `docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md` 承接完整执行细则。复杂任务阅读顺序固定为 `AGENTS.md` -> Agent 执行准则 -> `docs/project-memory/` -> `docs/README.md` 和专题文档。
- 影响范围:`AGENTS.md`、`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`、`docs/README.md`、`docs/project-memory/README.md` 和共享记忆索引。
- 验证方式:执行 `npm run check:encoding`、`git diff --check`,并检查入口文档不再重复承载专题细则。
- 关联文档:`AGENTS.md`、`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`。
## 2026-06-22 图片画布角色动作主媒体改为透明序列帧
- 背景:角色动作生成后端已经在视频生成后抽取透明 PNG 帧并完成绿幕去背;画板继续把 `previewVideoPath` 当主媒体会让用户看到未扣绿幕视频,下载也拿不到可直接用于游戏素材的帧序列。
- 决策:`/api/editor/character-animations/generations` 的上游预览视频继续保留为来源信息,但画板落层主类型固定为 `mediaType="image-sequence"`、`assetKind="character-animation"`;图层 `src` / `thumbnailSrc` 使用首帧,完整 `frames` 保存到 `imageSequenceFrames`,画布展示使用序列帧播放器循环播放。单图层下载生成序列帧 ZIP,画布素材 ZIP 中角色动作写入 `sequences/<编号-标题>/frames/`,不再把预览视频作为角色动作下载产物。
- 影响范围:图片画布角色动作生成、画布图层快照、序列帧播放器、素材导出、角色动作设计文档和排障记忆。
- 验证方式:运行角色动作图层工厂、画布展示、画布持久化、生成提交和素材导出相关前端测试,执行 `npm run typecheck`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
## 2026-06-24 图片画布项目封面使用静态快照资源
- 背景:项目页和创作主页最近项目曾在卡片中根据项目 `layers + viewport + resources` 临时重建一份迷你画布,视觉上像封面,但它不是持久快照,也会把列表页变成画布布局解释器。
- 决策:项目封面图改为画布当前视口栅格化后的静态资源。前端在项目加载后和防抖保存 layout 时生成 320x240 WebP,走私有 OSS / asset object 上传,再创建 `editor_project_resource`,其中 `assetKind="project-cover-snapshot"`、`sourceType="uploaded"`;项目列表和创作主页最近项目只读取最新封面快照资源渲染,没有快照时显示项目占位,不再回退为实时画布组合。
- 2026-07-24 补充:封面取景以当前画布工作区的实际尺寸和渲染态 viewport 为准,先绘制工作区背景色,再从视口中心等比放大并裁成 4:3;持久化显示倍率不得直接用于封面渲染。
- 2026-07-29 补充:常规编辑仍沿用防抖保存;用户从画布返回项目页时必须取消待执行 timer,以最新权威 revision 立即保存 layout,并等待同一视口封面写入本地缓存和正式项目资源后再导航。当前视口存在图层但全部位于取景外时仍生成纯背景封面,不沿用旧缩略图。
- 影响范围:`src/components/image-editor/useImageCanvasProjectPersistence.ts`、`src/components/image-editor/ImageCanvasProjectCoverSnapshotModel.ts`、`src/components/project/ProjectCanvasCover.tsx`、`src/components/project/ProjectGalleryView.tsx`、`src/components/creation-home/CreationLandingView.tsx` 和图片画布数据契约文档。
- 验证方式:运行项目页、封面快照模型、图片画布项目持久化和媒体上传相关前端测试,执行 `npm run typecheck`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-21 图片画布生成完成态由后端写入画布布局
- 背景:图片画布角色形象等长耗时生成在服务端完成后,如果浏览器已刷新或原 HTTP 回调丢失,前端无法再把生成结果图层和 `generation-dialog` 完成态写回 `editor_canvas.layers_json`,用户会继续看到“生成中”卡片。
- 决策:图片生成请求在有项目上下文时携带 `canvasCompletion`(生成器 `dialogId`、结果标题和占位框);`api-server` 在生成成功并创建 `editor_project_resource` / `editor_asset` 后,直接读取当前项目布局,只有当前布局仍存在对应生成器时才插入轻量结果图层,把生成器标记为 `idle` 并写入 `generatedLayerId`,沿用后端当前 viewport 保存 layout 后返回最新项目快照。前端只应用后端快照刷新显示,不再把生成完成态作为正式业务真相,也不在项目加载时根据资源行推断完成态。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、图片画布生成提交工作流、项目快照 hydrate / persistence、图片画布技术方案和排障记录。
- 验证方式:`cargo test -p api-server editor_canvas_generation_completion --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/image-editor/ImageCanvasEditorModel.test.ts src/components/image-editor/useImageCanvasProjectPersistence.test.tsx src/services/image-editor/editorProjectClient.test.ts src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx -- --runInBand`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-21 图片画布参考图元数据只保存项目内行引用
- 背景:参考图如果把 Data URL、signed URL 或 `objectKey` 写入 `generationInputs` 或生成器布局快照,会撑大资源 / 素材 / 画布 JSON,也无法稳定索引到项目内用户可见行数据。
- 决策:`generationInputs.references` 只保存稳定行指针,不保存媒体本身。2026-08-03 起 V2 新写入结构为 `{ id, title, label?, refType, refId }``refType="project-resource"` 和 `refType="asset"` 只用于匹配当前画布中已 hydrate 图层的 `resourceId/sourceAssetId`,媒体类型取匹配图层的运行时数据,不新增 owner-only 工程资源 / 素材库 resolver。生成器 `itemType="generation-dialog"` 布局快照中的参考图也只保存 `resourceId/sourceAssetId` 和展示 label,不保存图片 Data URL、signed URL 或 `objectKey`;提交生成请求前的内存态可以临时持有 `src/objectKey`。面板直接上传引用不是画布图层,不承诺刷新或复用恢复;已移出画布的引用同样不恢复。不兼容旧 `src` 型参考图元数据。
- 影响范围:图片画布生成输入快照、生成器布局保存 / 恢复、参考图上传工作流、元数据弹窗和图片画布技术文档。
- 验证方式:运行图片画布生成模型、生成提交、上传工作流、项目持久化、元数据弹窗相关前端测试,执行 `npm run typecheck`、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-19 外部 OpenAPI 与 API Key 管理走 server-rs 正式链路
- 背景:外部调用方需要稳定调用图片画布项目创建、画布布局保存和编辑器美术生图能力,同时需要可撤销的开发者凭据,不能依赖前端临时状态或人工分发密钥。
- 决策:外部 API 固定放在 `/api/external/v1` 命名空间,v1 暴露素材直传凭证 / asset object 确认 / 签名读取、项目列表 / 最近 / 创建 / 读取 / 重命名 / 删除、默认画布保存、账号级素材库、项目资源记录、编辑器图片 / 视频 / 音频生成和 `/api/external/v1/openapi.json`。API Key 管理走登录态 `/api/profile/api-keys`,外部调用使用 `Authorization: Bearer tnr_sk_xxx`;后端只保存 `key_hash` 和 `key_prefix`,明文只在创建响应返回一次。外部 API 鉴权、项目 / 画布 / 素材写回全部经 `api-server -> spacetime-client -> spacetime-module`,生成素材成功后按请求写入账号级 `editor_asset`,带 `projectId` 时写入 `editor_project_resource`;外部确认 asset object 时 owner 固定为 API Key 所属账号。API Key 管理接口不进入外部 OpenAPI JSON。
- 影响范围:`server-rs/crates/api-server/src/external_*`、`server-rs/crates/api-server/src/modules/external_api.rs`、`server-rs/crates/spacetime-module/src/external_api_key_storage.rs`、`server-rs/crates/spacetime-client/src/external_api_key.rs`、`docs/openapi/genarrative-external-v1.openapi.json` 和后端数据契约文档。
- 验证方式:`cargo test -p api-server external_api --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server external_editor_api --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md`。
## 2026-06-19 图片画布素材生成元数据上移到资源和素材
- 背景:角色、图标、UI 设计图、视频和音频等生成结果会在图片信息页展示用户可见输入快照;此前这些 `assetKind/generationInputs` 主要保存在画布 layer JSON 中,素材进入账号级素材库后跨项目复用和刷新恢复都依赖画布布局,不符合素材库作为账号级事实源的边界。
- 决策:普通图层的新保存不再把 `assetKind/generationInputs` 写入 `editor_canvas.layers_json`。`editor_project_resource` 保存项目画布资源快照的 `asset_kind/generation_inputs_json``editor_asset` 保存账号级素材的同名元数据和可选封面 `thumbnail_src`;图片 / 图标 / UI 提取等生成 BFF 在请求携带 `projectId` / `assetFolderId` 时由后端创建新 resource / asset 并把快照回传前端,前端只用回包更新画布图层和素材栏,不再把同一生成结果二次调用保存接口。生成视频由后端单独抽取首帧封面写入 `editor_asset.thumbnail_src` 和画布图层 `thumbnailSrc`,刷新素材库或从素材库拖回画布时继续作为视频 poster 使用。前端加载时优先从 resource / asset 恢复素材类别和生成输入快照,旧 layout 中的同名字段只作为历史兼容兜底。生成器对象本身仍作为 `itemType="generation-dialog"` 保存在画布布局中。
- 影响范围:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`server-rs/crates/spacetime-client/src/mapper/editor_project.rs`、`server-rs/crates/api-server/src/editor_project.rs`、`src/services/image-editor/editorProjectClient.ts`、图片画布 hydrate / serialize / project persistence / asset library 代码和后端数据契约文档。
- 验证方式:运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run test -- src/components/image-editor/ImageCanvasEditorModel.test.ts src/components/image-editor/useImageCanvasProjectPersistence.test.tsx src/services/image-editor/editorProjectClient.test.ts`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`,并按需补充 `cargo check -p spacetime-client -p api-server --manifest-path server-rs/Cargo.toml`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-19 图片画布生成按钮价格统一绑定模型定价配置
- 背景:图片画布的生成图片、生成视频、生成规范、生成角色、生成素材、生成 UI、宣发素材、快速编辑、重绘和音频生成入口都在按钮内显示泥点;如果按钮文案、前端请求和后端扣费各自写固定数值,后续调整模型价格会出现展示价和扣费价不一致。
- 决策:所有画板生成按钮展示价格必须从 `src/components/image-editor/ImageCanvasGenerationModel.ts` 的模型定价配置函数计算,但生成请求不提交 `priceMudPoints`;后端默认配置独立放在 `server-rs/crates/api-server/config/editor-generation-pricing.default.json`,运行时事实源为 SpacetimeDB `editor_generation_pricing_config` 全局表;后台“模型定价”通过 `/admin/api/editor-generation-pricing` 读取和保存完整 `models` 配置,主站通过 `/api/editor/generation-pricing` 动态下发。后端扣费以 `AppState` 当前运行时配置为准,前端内置定价只作为接口失败兜底展示。模型定价不再按图片 / 规范、视频 / 动作用途拆分,只按模型区分:图片模型按尺寸单次计价,`gemini-3.1-flash-image-preview` 必须配置 `0.5K / 1K / 2K``gpt-image-2` 必须配置 `1K / 2K`,规范固定读取 `gpt-image-2` 的 `2K`;视频和角色动作共用视频模型分辨率每秒价格,角色动作仍固定 `seedance2.0-fast`。后台管理页必须显示定价单位“按次 / 按秒”。画板 UI 统一显示 `nanobanana2`,历史输入或旧布局中的 `nano-banana` 必须归一到真实模型 ID 后再提交和计费。
- 影响范围:图片画布生成类面板、生成提交模型、编辑器图片 / 视频 / 音频 BFF、`editor_generation_config`、后台管理端和 Lovart 生成类面板文档。
- 验证方式:运行 `cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_generation_config::tests editor_generation_pricing_route -- --nocapture`、`npx vitest run src/components/image-editor/ImageCanvasGenerationModel.test.ts src/services/image-editor/editorProjectClient.test.ts apps/admin-web/src/pages/AdminEditorGenerationPricingPage.test.tsx apps/admin-web/src/app/adminRoutes.test.ts --reporter verbose`、`npm run admin-web:typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/【编辑器】模型定价配置管理方案-2026-06-22.md`。
## 2026-06-18 图片画布 UI 设计图提取素材保留图集
- 背景:UI 设计图需要从成图中继续抽取可复用独立素材;原图标素材生成只把拆分后的图标放入画布,spritesheet 原图没有保留,后续追溯和二次切图不方便。
- 决策:`assetKind="ui-design"` 图层浮动工具栏新增 `提取素材`,点击后先进入红框素材框选编辑态,默认矩形框选,并支持椭圆框选和画笔自由框选。至少存在一个框选区域后才能提交;前端把红色轮廓绘入原 UI 设计图并将合成图作为 `/api/editor/ui-designs/assets/extractions` 的参考图。后端固定 `gpt-image-2` 和纯色背景素材提取提示词,返回结构复用图标 spritesheet 响应。透明背景处理正常成功时,UI 提取把透明 spritesheet 图集作为 `assetKind="icon-spritesheet"` 图层放到画布,再放 provider 原图和拆分成功的 `assetKind="icon"` 素材。2026-07-03 起,UI 提取的纯色背景由 `screenColor` 选择并经 BgFilter 透明化。2026-07-13 起,图标素材生成在透明背景处理正常成功时把带背景原图和透明 spritesheet 同时写入项目资源、账号素材库和画布,未指定文件夹时落默认“项目”文件夹,再 best-effort 按 alpha 连通域拆分独立图标;拆分素材从 provider 原图右侧继续排列。拆分失败不改变生成成功状态,响应以空 `iconImageSrcs` 和结构化 `sliceWarning` 返回原因,用户可从图集工具栏手动重试。2026-07-16 起,透明背景处理最终失败时只把已经持久化的 provider 原图作为唯一主图放入画布,以 `completed + warning` 收口,不创建透明图集,也不继续拆分。2026-07-29 起,图标生成的自动拆分与手动拆分共同识别全图集有效连通域,限制单边 `4096`、总像素 `2048×2048`、最多 `64` 个切片,不再以提示词条目数决定切片数量;手动拆分仍保留且不计费。所有切片用 `sourceResourceId` 指向透明图集。`icon-spritesheet` 图集继续显示并允许快速编辑,只有拆分后的 `assetKind="icon"` 单图标隐藏并拒绝快速编辑;工具栏、右键菜单、打开流程和提交兜底必须共用同一判定。本条新决策取代“图标素材生成只保留图集”的旧口径。
- 影响范围:图片画布浮动工具栏、编辑器图片生成 BFF、`platform-image` 图集连通域拆分、画布图层类型和编辑器文档。
- 验证方式:运行图片画布工具栏 / 图集落层 / 生成提交相关前端测试,`cargo test -p platform-image generated_asset_sheets --manifest-path server-rs/Cargo.toml`,以及 `cargo test -p api-server editor_ui_design_asset_extraction_prompt_is_fixed --manifest-path server-rs/Cargo.toml`。
- 关联文档:`docs/【编辑器】画板UI设计图生成入口设计-2026-06-17.md`、`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`。
## 2026-06-30 图片画布标准绿幕契约收口
> 后续更正:本条关于生成资产只使用本地透明化、手动路径继续使用独立 BiRefNet 的描述,已由 2026-07-14、2026-07-15 OSS 签名 URL 决策和 2026-07-17 内存生命周期决策取代;当前 flat 链路为 `BgFilter → 阿里云 → 本地键色`。下文保留作历史记录。
- 背景:角色形象、图标 spritesheet、UI 提取 spritesheet 和角色动作帧都要求模型生成标准绿幕,但提示词片段和后处理入口分散在多个模块中,容易把标准绿幕资产误接到远端 BiRefNet。
- 决策:编辑器标准绿幕提示词和本地确定性绿幕透明化统一收口到 `server-rs/crates/api-server/src/editor_green_screen.rs`。手动 `POST /api/editor/images/background-removals` 继续面向用户任意图片并走 BiRefNet;编辑器自己生成的标准绿幕资产统一复用 `platform-image::generated_asset_sheets` 的本地透明化能力,不再依赖 BiRefNet。角色图、图标 spritesheet、UI 提取 spritesheet 和角色动作抽帧源图必须在绿幕透明化前先保存一份带绿幕原图到 OSS,便于追溯和重处理。
- 影响范围:`editor_project.rs` 的角色图 / 图标图集 / UI 提取图集、`character_animation_assets.rs` 的编辑器角色动作帧、编辑器绿幕相关文档。
- 验证方式:运行 `cargo test -p api-server --manifest-path server-rs/Cargo.toml prompt`,并单独按需过滤 `editor_canvas_green_screen_generation_uses_local_postprocess`、`editor_character_animation_frames_use_local_green_screen_postprocess`;同时运行 `npm run check:encoding` 和 `git diff --check`。
## 2026-06-18 `/creation` 独立为陶泥儿创作工具主页
- 背景:图片画布项目已经成为独立项目资产,旧“创作”站内 Tab 和一级“草稿”入口不能清晰表达桌面端创作工具主页与项目管理入口。
- 决策:桌面端新增独立 `/creation` 创作工具主页,桌面端直接打开站点首页 `/` 时也默认展示新版创作主页;顶级“创作”入口跳转 `/creation`,原“草稿”入口替换为“项目”并跳转 `/project`。移动端首页 `/` 继续保持原推荐首页,移动端隐藏“创作”和“项目”入口;移动端直达 `/creation` 时不加载创作主页,显示桌面端打开引导,`/creation/<play>` 玩法工作台直达仍按原链路进入。页面文案统一使用“陶泥儿”,外部品牌只作为设计参考,不进入用户可见 UI、测试名、产品文案或验收口径。`陶泥儿精选` 是用户素材瀑布流,不展示玩法入口列表;创作入口事实源继续来自 `/api/creation-entry/config`,项目入口通过 `createEditorProject` 和 `/editor/canvas?projectid=xxx` 链路进入画布。
- 影响范围:平台入口导航、`SelectionStage` 路由、`/creation` 首页、`/project` 项目入口、`/editor/canvas` 项目打开链路、“我的”页快捷入口和创作入口相关测试。
- 验证方式:桌面 `/` 初始阶段和 `/creation` 解析为创作主页,移动端 `/` 仍是推荐首页,`/creation/<play>` 仍进入对应玩法工作台;桌面导航显示“创作 / 项目”且不显示“草稿”;移动端底部不显示“创作 / 项目”;页面不出现外站品牌或 `Discord` 字样;新建项目进入 `/editor/canvas?projectid=xxx`。
- 关联文档:`docs/【玩法创作】创作主页与项目入口改版计划-2026-06-18.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-18 图片画布 Seedance 2.0 参考媒体提交边界
- 背景:`/editor/canvas` 生成视频需要严格对齐火山 Seedance 2.0 多模态参考输入;参考视频若继续走 Base64 / `data:video` 会超过请求体并被上游拒绝,参考音频单独输入和非 Seedance 模型携带参考字段也会违反文档契约。
- 决策:仅 `seedance2.0-fast` / `seedance2.0` 可提交参考图片、参考视频、参考音频;图片 `0~9`、视频 `0~3`、音频 `0~3`,音频必须搭配图片或视频。参考视频只能提交公网 URL、`asset://` 或画板资源 `objectKey`,禁止 `data:video/*`;视频 / 音频上传先走 OSS 直传和 asset*object confirm,前端保存 signed URL 预览但提交优先 `objectKey`,后端统一重新签名给 Ark。Ark body 按 `image_url` / `video_url` / `audio_url` + `reference*\*`role 构造,并显式发送`generate_audio:false`。
- 影响范围:图片画布生成视频面板、参考媒体上传工作流、`editorReferenceUploadClient`、`ImageCanvasGenerationSubmissionModel`、`shared-contracts`、`api-server` 编辑器视频 BFF、Lovart 生成类面板文档。
- 验证方式:运行 `npx vitest run src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/services/image-editor/editorReferenceUploadClient.test.ts --reporter verbose`、`cargo test -p api-server editor_video --manifest-path server-rs/Cargo.toml`、`cargo test -p shared-contracts editor_video_request_supports_seedance_multimodal_references --manifest-path server-rs/Cargo.toml`,并执行 `npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、火山 Seedance 2.0 任务创建文档。
## 2026-06-21 图片画布生成视频参数扩展
- 背景:编辑器画布生成视频需要开放更多 Lovart 式参数,同时保留模型能力边界;`seedance2.0-fast` 不支持 `1080p`,联网搜索暂没有可确认的 Ark 视频生成 body 字段。
- 决策:生成视频参数面板支持比例 `16:9 / 9:16 / 1:1 / 4:3 / 3:4 / 21:9`,时长为 4 到 15 秒整数 slider,清晰度支持 `480p / 720p / 1080p``seedance2.0-fast` 不展示可用 `1080p`,从其它模型的 `1080p` 切回 Fast 时自动降到 `720p`,后端也拒绝 `seedance2.0-fast + 1080p`。静音只作为一个 toggle 展示,默认有声并映射 `sound=on` / Ark `generate_audio=true`;关闭静音时传 `sound=off` / `generate_audio=false`。`webSearchEnabled` 默认随请求提交为 `true`,但前端不展示联网搜索开关,后端当前只接收契约字段,不向 Ark 透传未知参数。
- 影响范围:图片画布生成视频面板、生成提交模型、画布项目快照恢复、`editorProjectClient`、`shared-contracts`、`api-server` 编辑器视频 BFF、编辑器技术文档。
- 验证方式:运行 `npm run test -- src/components/image-editor/ImageCanvasGenerationModel.test.ts src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx src/services/image-editor/editorProjectClient.test.ts src/components/image-editor/ImageCanvasGenerationDialogModel.test.ts`、`cargo test -p shared-contracts editor_video --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_video --manifest-path server-rs/Cargo.toml`,并执行 `npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 2026-06-18 图片画布生成音乐入口作为音频图层接入
- 背景:图片画布底部生成工具需要补齐游戏音效和游戏背景音乐生成,既要复用现有 Lovart 式画布生成器快照、占位避让和持久化,又不能把音频能力并入图片素材库或视觉小说专用音频开关。
- 决策:`/editor/canvas` 新增底部 `生成音乐` 入口,点击后先弹出“生成游戏音效 / 生成游戏背景音乐”选项框,再分别创建 `audio-sound-effect` 或 `audio-background-music` 生成器;生成结果作为 `mediaType="audio"` 的画布音频卡保存,`assetKind` 分别为 `sound-effect` / `background-music`。音效请求字段固定映射 Vidu `prompt/model/duration`,模型固定 `audio1.0`、时长严格 `2-10` 秒;背景音乐请求字段固定映射 `gpt_description_prompt` 且 `make_instrumental=true`。
- 影响范围:图片画布生成工作流、前端 editorProjectClient、`shared-contracts`、`platform-audio`、`api-server` 编辑器音频 BFF、图片画布技术方案和音乐生成入口设计文档。
- 验证方式:运行编辑器生成入口 / 提交 / 音频图层相关前端测试,`platform-audio` 请求体测试,`shared-contracts` editor audio 序列化测试,`api-server` editor audio 归一化测试,并执行 `npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 2026-06-17 图片画布生成占位统一避让落点
- 背景:图片画布的普通图片、规范、角色、图标、视频和 UI 设计图生成入口都会先在画布中新建“即将生成”的占位图;若各入口直接使用当前视口中心,容易压住已有图片或已有生成占位,Lovart 式连续创作体验不稳定。
- 决策:所有会新建画布生成占位的入口统一经过 `ImageCanvasGenerationPlacementModel` 计算落点。模型以当前视口世界中心为目标,避让所有未隐藏画布图层和 active / inactive generation dialog placeholder,按 32px 画布世界坐标间距外扩阻挡矩形,选择距离当前屏幕中心对应画板位置最近且不重叠的位置。选定后立即调用 `centerViewportOnPlacement(...)`,保持当前缩放比例不变,只平移画布 viewport,让屏幕中心移动到新占位中心。
- 影响范围:`/editor/canvas` 图片画布生成入口、`useImageCanvasGenerationWorkflow`、`ImageCanvasGenerationPlacementModel`、图片画布技术方案和 Lovart 生成类面板文档。
- 验证方式:运行 `npm run test -- src/components/image-editor/ImageCanvasGenerationPlacementModel.test.ts src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx`,并执行 `npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 2026-06-13 Pingora 低端口直连只通过显式 systemd drop-in 启用
- 背景:`genarrative-pingora-gateway.service` 默认以 `genarrative` 非 root 用户运行,shadow 阶段只监听本机高端口;如果正式评估让 Pingora 直接绑定公网 `80/443`,需要低端口绑定能力,但不能让 Server-Provision 或默认 service 自动改变接流边界。
- 决策:主 systemd service 保持 shadow 口径,不携带 `CAP_NET_BIND_SERVICE`。仓库提供 `deploy/systemd/genarrative-pingora-gateway-direct-entry.conf` 作为人工启用 drop-in 模板,Server-Provision 只安装到 `/etc/genarrative/pingora/genarrative-pingora-gateway-direct-entry.conf` 备查和手动覆盖;正式切换窗口从 `/opt/genarrative/current/scripts/deploy/pingora-direct-enable.sh` 执行时默认读取 current release 随包的 `/opt/genarrative/current/deploy/systemd/genarrative-pingora-gateway-direct-entry.conf`,不依赖 `/etc` 参考模板、Jenkins 工作区或源码 checkout。直连切换前先用 `npm run plan:pingora-direct-cutover -- --require-direct ...` 生成 JSON runbook,逐条审阅 Host 与回退巡检入口确认、current release preflight、启用前基础门禁、direct enable dry-run、direct enable apply(通过命令证据脚本归档 stdout / stderr / 退出码)、切换后 health patrol 切到 `pingora-direct`、启用后 health patrol env 直连复核、启用后 `--require-direct` 复核、rollback dry-run、rollback apply(通过命令证据脚本归档 stdout / stderr / 退出码)、回退后 health patrol 切回 `nginx` 并恢复切换前 public base URL / Host、回退后 health patrol env Nginx 模式复核;启用前基础门禁不带 `--require-direct`,因为 systemd drop-in 尚未生效,启用后复核必须带 `--require-direct`。正式切换 runbook 中 `--direct-redirect-host`、`--rollback-nginx-smoke-host` 和 `--direct-host` 必须使用同一 hostname,只允许端口不同,避免 redirect 和回退 smoke 分别验证到不同入口;还必须显式传 `--rollback-health-patrol-public-base-url <切换前Nginx巡检入口>`,切换前 Nginx 巡检需要 Host 覆盖时再传 `--rollback-health-patrol-public-host <切换前Host>`,确认步骤会展示回退后要恢复的 public base URL / Host,避免回退 runbook 覆盖现场原有巡检入口。若需把回退后 Pingora shadow 探针复核纳入 runbook,追加 `--rollback-pingora-shadow-probe-url` / `--rollback-pingora-shadow-probe-token`JSON 输出会隐藏 token 原文并把参数传给 rollback dry-run / apply。只有切换窗口通过 `pingora-direct-enable.sh --apply --preflight-env-file /etc/genarrative/pingora-gateway.env --preflight-check-cert-readable --preflight-check-service-env-file --preflight-check-service-user-cert-readable --preflight-check-service-binary-executable --preflight-check-ports-free --direct-https-base-url https://127.0.0.1 --direct-http-base-url http://127.0.0.1 --direct-host <域名> --direct-redirect-host <域名或host:port> --direct-spacetime-database <库名>` 先跑 current release 自审,确认发布包自包含、`pingora-gateway` 可执行且 systemd `ExecStart` 指向随包网关,失败时不安装 drop-in;随后跑 direct preflight,确认当前执行用户和 `genarrative-pingora-gateway.service` 的 `User=` 服务用户都可读取证书链 / 私钥,确认 service 模板与 `systemctl cat` 最终配置读取的 `EnvironmentFile=` 都包含本次 `/etc/genarrative/pingora-gateway.env`,并确认 service `ExecStart=` 指向的 current release `pingora-gateway` 存在且可执行,再安装到 `/etc/systemd/system/genarrative-pingora-gateway.service.d/direct-entry.conf`、执行 `systemctl daemon-reload`、重启 Pingora,并用 `systemctl cat` 核验 `AmbientCapabilities=CAP_NET_BIND_SERVICE`、`CapabilityBoundingSet=CAP_NET_BIND_SERVICE` 和 `EnvironmentFile=/etc/genarrative/pingora-gateway.env` 已生效、用 `systemctl is-active` 确认服务 active、用 direct live smoke 验证 HTTPS / HTTP redirect / ACME / WSS 101 后,才视为授予低端口能力成功。启用前还必须显式配置 TLS / redirect env 和真实证书,并确认 current release 已落盘可执行 `pingora-gateway`、Nginx 或其它进程已释放 `80/443`;启用后仍必须跑 release readiness 门禁;验证失败时统一执行 `pingora-direct-rollback.sh --apply --reload-nginx --nginx-smoke-url https://<域名>/ --nginx-smoke-expect-body '<!doctype html>'` 回到 shadow / Nginx 入口,回退脚本会先运行 `nginx -t`,通过后移除 direct-entry drop-in、重启 Pingora,再用 `systemctl cat` 确认两条低端口 capability 均已从最终 unit 配置中移除,用 `systemctl show ... ExecStart` 确认最终 service 仍指向随包主 service 模板中的 current release `pingora-gateway`,并 reload Nginx、确认 Nginx service 仍为 `active`,最后用 curl smoke URL 证明 Nginx 入口真实可访问;回退 smoke URL/body 必须来自切换前真实 Nginx 入口,不要继续用固定 `http://127.0.0.1/healthz` 与 `"ok":true`。回退脚本 `--apply` 必须同时带 `--reload-nginx` 和 `--nginx-smoke-url`,避免只撤掉 Pingora 低端口能力却没有证明 Nginx 已重新接流;本机打 `127.0.0.1`、`localhost` 或 `::1` 时,`--apply` 必须带 `--nginx-smoke-host <域名>`,且该值只能是 host 或 `host:port`,避免命中默认 vhost。回退后必须复核 health patrol env 已切回 `nginx` 且 public base URL / Host 恢复为切换前记录值;如果 env 已预先修正,rollback 脚本可追加 `--health-patrol-env-file /etc/genarrative/health-patrol.env --health-patrol-expected-public-base-url <切换前Nginx巡检入口> --health-patrol-require-empty-public-host` 自动执行这项复核,切换前 Nginx 巡检需要 Host 覆盖时把最后一项换成 `--health-patrol-expected-public-host <切换前Host>`。若需证明 Pingora 仍以 shadow 高端口存活,可追加 `--pingora-shadow-probe-url http://127.0.0.1:18081/__genarrative_pingora/healthz --pingora-shadow-probe-token <token>`,脚本会隐藏 token 并要求响应包含 `gateway=pingora-shadow`。
- 决策补充:health patrol env 的直连/回退切换不再靠人工编辑三行变量;正式 runbook 使用 current release 随包 `node -- /opt/genarrative/current/scripts/deploy/pingora-health-patrol-env-switch.mjs --apply`,只更新 `GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE`、`GENARRATIVE_HEALTH_PATROL_PUBLIC_BASE_URL`、`GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST` 并立即调用随包 `check-production-health-patrol-env.mjs` 复核。Pingora direct 使用本机 public base URL 时脚本必须带 `--public-host <域名>`;回退到 Nginx 时根据切换前记录传 `--clear-public-host` 或 `--public-host <切换前Host>`。生产巡检、health patrol env 复核和 env 切换脚本读取的布尔 env 必须严格解析,非法值直接失败,不得静默按 false 继续;env 复核脚本的 `--env-file` 与 env 切换脚本的 `--env-file` / `--check-script` 必须是绝对路径且不能是文件系统根目录,也不能包含换行或 NUL;env 切换脚本写入的 public base URL / Host 同样不能包含换行或 NUL。Node 22 已内置 `--env-file` 启动参数,凡是用 Node 启动项目脚本且要把业务 `--env-file` 传给脚本时,必须写成 `node -- <script> --env-file ...`systemd / shell / release readiness runbook 中的这类 Node 调用都必须保留 `--` 分隔符。
- 写入语义补充:`pingora-health-patrol-env-switch.mjs --apply` 必须先对权限固定为 `0600` 的临时目标 env 运行随包 env 复核脚本,复核通过后才按真实 `/etc/genarrative/health-patrol.env` 原权限和 owner/group 原子替换;复核失败时不得写入真实 env,避免切换窗口留下半坏巡检配置。`--apply` 时 `--env-file` 必须直接指向真实普通文件,不能是符号链接;若现场 env 是链接,先确认真实目标路径后再传给脚本,避免替换链接本身或写入非预期目标。
- 顺序补充:正式 runbook 中 health patrol 的 Nginx 回退 env 必须在 `rollback apply` 前预置,随后 `pingora-direct-rollback.sh --apply` 会用 `--health-patrol-expected-public-base-url` 和 `--health-patrol-expected-public-host` / `--health-patrol-require-empty-public-host` 在 Nginx smoke 后复核该 env;最后仍保留独立的回退后 health patrol env 复核步骤。
- 顺序补充:正式 runbook 中 Pingora 自身 env 的 shadow 回退也必须在 `rollback apply` 前预置。真实直连会把 `/etc/genarrative/pingora-gateway.env` 提升为 `0.0.0.0:80/443` direct 配置;回退脚本移除 `CAP_NET_BIND_SERVICE` 后会重启 Pingora,如果 env 仍保留低端口监听,服务可能按预期失败而不是回到 shadow。因此 rollback apply 前必须用 current release 随包 `node -- /opt/genarrative/current/scripts/deploy/pingora-gateway-env-shadow-switch.mjs --apply --env-file /etc/genarrative/pingora-gateway.env` 先确认 `GENARRATIVE_PINGORA_GATEWAY_LISTEN=127.0.0.1:18081`,并清空 `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN`、`GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN`、`GENARRATIVE_PINGORA_GATEWAY_TLS_CERT_FILE` 和 `GENARRATIVE_PINGORA_GATEWAY_TLS_KEY_FILE`,避免保留证书路径但无 TLS listener 的半直连 env。
- 安全补充:直连公网地址时 `GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR` 必须保持 `false`;只有 Pingora 前方仍有会清洗 `X-Forwarded-For` 的受控代理且监听为 loopback / 受控入口时,才允许配合 `GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED=true` 使用转发 IP 作为接流保护 client key。目标机 direct preflight 必须在公网监听加 `TRUST_X_FORWARDED_FOR=true` 时失败,且这两个 gateway env 布尔值也必须严格解析,非法值直接失败,避免公网用户伪造限流 key 或拼写错误被当成 false。
- 发布补充:`npm run check:production-api-release` 继续验证 API release 自包含和显式 include 的假二进制布局;`npm run check:pingora-production-release-build` 必须额外走真实 `cargo build -p pingora-gateway --release --target x86_64-unknown-linux-gnu`,并用假 `api-server` 验证 `--include-pingora-gateway` 发布包包含可执行 `pingora-gateway`、`pingora-gateway.sha256` 和 manifest 登记。`check:pingora-release-readiness` 默认纳入该真实构建 smoke,避免正式切换前只验证发布包布局而没有验证 Pingora release 二进制可构建。
- 发布安全补充:API release 和 deploy 动态烟测必须复核 `deploy/pingora/pingora-gateway.env.example` 随包后的生产安全默认值,至少包括 `COMPRESSION_ALGORITHMS=gzip`、`TRUST_X_FORWARDED_FOR=false`、`TRUSTED_FRONT_PROXY_CONFIRMED=false`、`PROTECTION_ENABLED=true` 和空 `PROBE_TOKEN`;这些值漂移时应在构建 / 部署门禁中失败,而不是等切换窗口人工审查。
- 自审补充:正式直连 runbook 的 current release 自审不只看文件存在和可执行;还必须复核 `api-server.sha256` / `pingora-gateway.sha256` 与当前文件匹配,并读取 `release-manifest.api-server.json` 或 `release-manifest.json` 确认 `component_type=api-server` 且 manifest 已登记 `pingora-gateway` 与 `pingora-gateway.sha256`。checksum 或 manifest 漂移属于 `CRITICAL`,应先修发布包或 deploy 复制链路,再继续切换。
- 证据补充:current release 自审、状态快照和证据包脚本的显式 `--timeout-ms`,以及 `GENARRATIVE_PINGORA_CURRENT_RELEASE_TIMEOUT_MS` / `GENARRATIVE_PINGORA_CUTOVER_SNAPSHOT_TIMEOUT_MS` 必须是正整数;这些脚本读取的布尔 env 只接受 `true/false`、`1/0`、`yes/no`、`on/off` 或空值。非法值直接失败,不得静默回退默认超时或 false。自审、状态快照和证据包的 `--release-root` 都不能是文件系统根目录,状态快照的 `--health-patrol-env-file` / `--pingora-env-file` 以及证据包所有显式路径参数也不能是文件系统根目录;状态快照自身还必须拒绝带换行或 NUL 的 release/env 路径,并在执行 systemctl、current release 自审、health patrol env 复核或生产巡检子命令前复核子命令参数;证据包在执行状态快照或 direct live 子命令前还必须拒绝任何带换行或 NUL 字符的子命令参数,避免污染后的结构化 `args[]` 先进入 manifest / command 证据再等最终总审计兜底;`--output-root` 及其已存在上级路径不能是符号链接,已存在的 `--output-root` 必须是真实目录;路径异常时必须在执行状态快照前失败,避免把切换证据写入非预期软链目标。
- current release 自审安全补充:`pingora-current-release-audit.mjs` 的 `--release-root` 和 `--systemd-service` 不能包含换行或 NUL 字符;启用 `--systemd-show` 时,脚本必须在执行 `systemctl show` 前复核子命令可执行文件和所有参数不含换行或 NUL,避免污染参数进入只读自审命令。
- direct preflight 安全补充:`check-pingora-direct-preflight.mjs` 的 `--env-file`、`--systemd-service`、服务用户和 env 中的 listen / cert / key 值不能包含换行或 NUL 字符;执行 `systemctl cat` 或 `sudo -u <serviceUser> test -r <file>` 前必须复核子命令可执行文件和所有参数不含换行或 NUL,避免污染参数进入目标机直连预检命令。
- API 代理头补充:Pingora 直连接管前必须证明上游 `api-server` 收到的代理头仍对齐 Nginx。网关透传 `Host`,写入 `X-Forwarded-Host`、配置化的 `X-Forwarded-Proto`、TCP 对端 IP 作为 `X-Real-IP`,并把 TCP 对端 IP 追加到 `X-Forwarded-For``GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR` 只影响接流保护 client key,不改变上游归因头。`npm run check:pingora-gateway-smoke` 必须用 mock 上游回显并断言这些头,避免直连后回调 URL、鉴权来源或日志归因漂移。
- 静态缓存补充:Pingora 直连静态响应必须同时保留 `Cache-Control` 分档和浏览器协商缓存能力。HTML / SPA fallback 默认 `no-cache`Vite 指纹资源默认 `public, max-age=31536000, immutable`,其它静态资源和 ACME 默认 `no-cache`;所有静态文件响应写入弱 `ETag` 与 `Last-Modified`,并对 `GET` / `HEAD` 的 `If-None-Match`、`If-Modified-Since` 返回 `304`。`npm run check:pingora-gateway-smoke` 必须覆盖静态 `HEAD`、ETag 304 和 Last-Modified 304,避免直连后旧浏览器缓存体验或 HTML 入口刷新语义漂移。
- 静态 Range 补充:Pingora 直连静态响应必须声明 `Accept-Ranges: bytes`,支持单段 `Range: bytes=` 返回 `206 + Content-Range`,越界范围返回 `416 + Content-Range: bytes */<len>``HEAD + Range` 只返回头且保留正确 `Content-Length`。多段 range 暂按完整文件返回,不引入 multipart 响应;条件请求优先于 Range,命中时仍返回 `304``If-Range` 日期匹配时继续返回 `206`,日期旧于文件或弱 ETag 校验器时回完整 `200``206` / `304` / `416` 不做 gzip 压缩,避免局部内容语义漂移。`npm run check:pingora-gateway-smoke` 必须覆盖 206、suffix range、416、HEAD range、If-Range 匹配和 If-Range 回完整文件。
- 静态方法补充:Pingora 静态路由只允许 `GET` / `HEAD` 读取;非读取方法命中静态候选时返回 `405` 并写入 `Allow: GET, HEAD`,缺失文件仍返回 `404`。`npm run check:pingora-gateway-smoke` 必须覆盖该行为,避免直连后错误客户端把静态入口当作可写接口。
- direct live 静态资产补充:`check-pingora-direct-live.mjs` 在 HTTPS 根路径返回 `200` 且 HTML 中发现 `/assets/` 或 `/admin/assets/` 引用时,必须额外请求该静态资源,校验 `Cache-Control`、`ETag`、`Last-Modified`、`Accept-Ranges: bytes`,再用 `HEAD` 验证头响应,用 `If-None-Match` / `If-Modified-Since` 验证 `304` 协商缓存,用 `Range: bytes=0-0` 验证 `206 + Content-Range` 且不压缩,并把这些 request_id 都纳入 `direct-access-log` method/path/status 对账;如果首页引用 Vite 指纹资源,还必须额外验证 `Cache-Control: public, max-age=31536000, immutable`,并把指纹资源 GET / HEAD / 304 / Range request_id 纳入同一 access log method/path/status 对账。静态 GET / HEAD / 304 / Range 的 `direct-live.json` 结果必须写入白名单 `headers`,只保留 `cache-control`、`etag`、`last-modified`、`accept-ranges`、`content-range`、`content-length` 和 `content-encoding`,让证据包复盘时能直接确认缓存分档、校验器和 Range 语义;API / WSS 检查不落原始响应头。证据包 `manifest.summary.directLiveStaticHeaders` 必须把普通静态和 Vite 指纹静态的缓存头、校验头、Range `Content-Range` 与 304 状态提升出来;如果 direct live 已输出静态资产结果但摘要缺少缓存头、校验头、Range `206 + Content-Range`、ETag 304 或 Last-Modified 304 证据,整包记为 `CRITICAL`。维护模式、非 HTML 或首页没有构建资产引用时该项允许标记为 skipped。这样直连切换证据不只覆盖 API / WSS / redirect,也覆盖当前发布包前端静态资源可读、协商缓存和旧 tab chunk 长缓存口径。
- 快照补充:状态快照必须把 `--pingora-env-file` 与 `systemctl cat genarrative-pingora-gateway.service` 的 `EnvironmentFile=` 精确匹配,支持 `EnvironmentFile=-/path` 和一行多个文件,但不能用路径前缀误判;未包含本次 pingora env 时 `systemd.pingoraUnit.environmentFileMatchesPingoraEnvFile=false` 并标记 `CRITICAL`,避免证据包读取一份 env 而真实 Pingora 服务读取另一份 env。
- 快照补充:状态快照支持 `--expected-pingora-env-mode shadow|direct`。正式 cutover runbook 的 `post-enable` 证据包必须传 `--expected-pingora-env-mode direct``post-rollback` 证据包必须传 `--expected-pingora-env-mode shadow`;若 active env 姿态与期望不一致,证据包应标记 `CRITICAL`,不能只依赖 health patrol gateway mode 或 systemd drop-in 判断切流状态。
- 证据验真补充:`scripts/ops/pingora-cutover-evidence-verify.mjs` 只接受 `schemaVersion=1` 的 manifest,并把 `manifest.files` 视为闭集,证据目录中除 `manifest.json` 和 manifest 已登记文件外,任何未登记普通文件、目录或符号链接都默认失败;`--allow-extra-files` 只用于人工排障显式放行,正式切换归档不使用。正式 runbook 的五个即时验真步骤都必须追加 `--require-summary-ok`,让 `pre-cutover`、`enable-apply`、`post-enable`、`rollback-apply`、`post-rollback` 证据在生成后立即要求 `manifest.summary.status=OK`;缺少 summary 或状态非 OK 时先修现场状态或重采证据,不等最终总审计才发现。
- 正式 runbook 安全补充:`--warn-only`、`--allow-extra-files` 和 `--allow-extra-root-entries` 只允许在 runbook 外作为人工排障命令使用;`plan:pingora-direct-cutover` 生成的正式切换计划不得携带这些放行参数。需要使用放行参数时,先修现场状态、证据目录或重新归档,不能把人工排障口径带入正式切换计划。
- 证据根目录补充:`scripts/ops/pingora-cutover-evidence-audit.mjs` 默认把证据根目录也视为闭集,只允许带 `manifest.json` 的证据目录;根目录普通文件、无 manifest 子目录和符号链接都会失败。`--allow-extra-root-entries` 只用于人工排障显式放行,正式切换归档不使用。
- 证据总审计补充:三阶段 `pre-cutover` / `post-enable` / `post-rollback` 证据包分别验真,且 `enable-apply` / `rollback-apply` 命令证据生成后,正式 runbook 还必须执行 current release 随包 `scripts/ops/pingora-cutover-evidence-audit.mjs --evidence-root <证据根目录> --require-phase pre-cutover --require-phase post-enable --require-phase post-rollback --require-phase-direct-live-access-log post-enable --require-phase-direct-live-static-headers post-enable --require-command enable-apply:pingora-direct-enable-apply --require-command post-enable:pingora-health-patrol-direct-env-switch --require-command rollback-prep:pingora-gateway-shadow-env-switch --require-command rollback-prep:pingora-health-patrol-nginx-env-switch --require-command rollback-apply:pingora-direct-rollback-apply --require-command-executable enable-apply:pingora-direct-enable-apply:/opt/genarrative/current/scripts/deploy/pingora-direct-enable.sh --require-command-executable post-enable:pingora-health-patrol-direct-env-switch:/opt/genarrative/current/scripts/deploy/pingora-health-patrol-env-switch.mjs --require-command-executable rollback-prep:pingora-gateway-shadow-env-switch:/opt/genarrative/current/scripts/deploy/pingora-gateway-env-shadow-switch.mjs --require-command-executable rollback-prep:pingora-health-patrol-nginx-env-switch:/opt/genarrative/current/scripts/deploy/pingora-health-patrol-env-switch.mjs --require-command-executable rollback-apply:pingora-direct-rollback-apply:/opt/genarrative/current/scripts/deploy/pingora-direct-rollback.sh --require-command-arg enable-apply:pingora-direct-enable-apply:--apply --require-command-arg post-enable:pingora-health-patrol-direct-env-switch:--apply --require-command-arg post-enable:pingora-health-patrol-direct-env-switch:pingora-direct --require-command-arg rollback-prep:pingora-gateway-shadow-env-switch:--apply --require-command-arg rollback-prep:pingora-health-patrol-nginx-env-switch:--apply --require-command-arg rollback-prep:pingora-health-patrol-nginx-env-switch:nginx --require-command-arg rollback-apply:pingora-direct-rollback-apply:--apply --require-cutover-run-id <本次cutoverRunId> --timeline-max-span-ms 86400000`。runbook 会自动生成或接受显式 `--cutover-run-id <id>`,并把同一 `manifest.cutoverRunId` 写入三阶段证据包、五条真实切换命令证据和最终总审计;该脚本只读扫描证据根目录,按 `manifest.phase` 选择每个阶段最新证据目录、按 `manifest.phase + manifest.commandName` 选择真实切换命令证据,并复用随包 verifier 验真;所有候选 manifest 必须是 `schemaVersion=1`,最新证据选择和时间线证明只接受合法且规范的 UTC 毫秒 `manifest.generatedAt`,命令记录 `startedAt` / `finishedAt` 也必须使用 `new Date().toISOString()` 形式;缺失、非法或省略毫秒 / 本地时区格式时直接失败,不能用目录 mtime 兜底;`post-enable` 阶段还必须带可判定的 `manifest.summary.directLiveAccessLog` 与 `manifest.summary.directLiveStaticHeaders`,否则总审计失败,避免旧启用后证据包缺少 request_id 对账、静态缓存、校验器、Range 或 304 复盘入口;缺阶段、缺命令证据、最新证据损坏、阶段 `manifest.summary.status` 非 `OK`、命令 `manifest.summary.status` 非 `OK`、命令 `manifest.summary.exitCode` 非 `0`、命令证据缺少或漂移 `manifest.expectedExecutable` / `manifest.command.executable` / 独立 `command-record.json` executable、命令证据缺少必需 `--apply` 参数、`manifest.command` 与 `command-record.json` 关键字段不一致、命令 stdout / stderr 引用与 `manifest.files` 不一致、命令 args / command 漂移、命令记录时间线不合法、标准八段证据任一条目审计状态非 `OK`、标准八段证据 `manifest.generatedAt` 顺序不满足 `pre-cutover -> enable-apply -> post-enable:pingora-health-patrol-direct-env-switch -> post-enable -> rollback-prep:pingora-gateway-shadow-env-switch -> rollback-prep:pingora-health-patrol-nginx-env-switch -> rollback-apply -> post-rollback`、标准八段证据跨度超过默认 24 小时、要求 `--require-cutover-run-id` 时任一阶段或命令缺少同一 `manifest.cutoverRunId`、证据根目录或 verifier 路径不安全、证据 verifier / 总审计入口路径或 manifest 登记文件名含换行 / NUL、证据根目录内出现坏 manifest / 符号链接时都应失败。确需更长维护窗口时,通过 release readiness runbook 参数 `--cutover-evidence-timeline-max-span-ms <ms>` 显式放宽,并让最终总审计 JSON 留下 `requiredCutoverRunId`、`requiredCommandExecutables`、`requiredCommandArgs`、`timeline.maxSpanMs` / `timeline.spanMs`。
- 证据总审计补充:最终审计 JSON 的 `summary` 是值班人员优先入口;`summary.status` 给出 `OK` / `CRITICAL``summary.failedItems[]` 聚合根目录、阶段、命令和时间线失败,`summary.directLiveEvidence[]` 聚合 `post-enable` 的 access log 与静态头要求、ok 状态、短 reason 和摘要。现场先看 summary 定位,再展开 `phases[]`、`commands[]`、`timeline` 深挖。
- 证据总审计补充:证据包会把状态快照里的 Pingora env 监听摘要提升到 `manifest.summary.pingoraEnvShadow`,包含 `listen`、`tlsListen`、`httpRedirectListen`、`tlsCertFile`、`tlsKeyFile`、`mode`、`shadowReady` 和 `ok`。正式 runbook 的最终总审计必须追加 `--require-phase-pingora-env-shadow post-rollback`,要求 `post-rollback` 证明 `listen=127.0.0.1:18081`、`mode=shadow`、`shadowReady=true`,且 TLS / HTTP redirect 低端口监听和证书路径均为空;最终审计 JSON 的 `summary.pingoraEnvShadowEvidence[]` 会聚合该要求、ok 状态、短 reason 和摘要,避免回退后 active env 仍残留 direct 低端口配置或 cert/key 半直连配置。
- 文档门禁补充:Pingora 技术文档里的最终证据根目录总审计命令示例也必须包含 enable apply、health patrol direct env switch、Pingora gateway shadow env switch、health patrol nginx env switch 和 rollback apply 五条 `--require-command-executable``npm run check:pingora-release-readiness-plan` 会读取文档并阻断示例落后于真实 runbook 的情况;`npm run check:production-api-release` 还必须动态生成 API release,检查发布包 README 和随包 `scripts/check-pingora-release-readiness.mjs --dry-run-cutover` 输出的最终总审计步骤也保留这五条 current release 脚本身份要求。
- 证据总审计补充:同一阶段或同一命令的最新 `manifest.generatedAt` 必须唯一。若多个候选共享最新时间戳,`pingora-cutover-evidence-audit.mjs` 必须输出 `AMBIGUOUS_LATEST` 并列出重复目录,不能按目录名排序打平;处理方式是重新归档该阶段 / 命令证据,或把旧证据移出正式证据根目录后再审计。
- 证据总审计补充:标准八段时间线只要任一阶段或命令证据声明了 `manifest.cutoverRunId`,八段就必须全部声明同一个值;顺序固定为 `pre-cutover -> enable-apply -> post-enable:pingora-health-patrol-direct-env-switch -> post-enable -> rollback-prep:pingora-gateway-shadow-env-switch -> rollback-prep:pingora-health-patrol-nginx-env-switch -> rollback-apply -> post-rollback`。缺字段或混入其它批次时,即使没有显式传 `--require-cutover-run-id`,总审计也必须失败,避免人工临时审计把不同切换批次拼成一条时间线。任何证据 manifest 只要显式写入 `cutoverRunId` 字段,就必须是安全非空 ID,不能用空字符串伪装成缺省字段。
- 证据总审计输出补充:标准八段时间线失败时,`timeline.failedCount` 按具体失败项累计,`timeline.failureBreakdown` 分别记录 `nonOkItems`、`missingGeneratedAt`、`cutoverRunIdMismatch`、`outOfOrder` 和 `spanExceeded`,用于把多个失败证据或多个时间线问题拆成可操作排障项。
- 命令证据身份补充:正式 runbook 的 `enable-apply` / `rollback-apply` 命令证据必须传 `--expected-executable <current release 随包脚本绝对路径>` 和 `--require-arg --apply`,分别绑定 `/opt/genarrative/current/scripts/deploy/pingora-direct-enable.sh` 和 `/opt/genarrative/current/scripts/deploy/pingora-direct-rollback.sh`,并在执行前要求真实命令参数包含 `--apply`。命令证据脚本会在创建正式命令证据前拒绝真实命令与预期脚本不一致、缺少必需 apply 参数,或任一真实命令参数包含换行 / NUL 字符,并把 `expectedExecutable` 写入 manifest / command-record;最终证据根目录总审计再用 `--require-command-executable` 复核 `manifest.expectedExecutable`、`manifest.command.executable` 与独立 `command-record.json` executable,并用 `--require-command-arg ...:--apply` 复核 `manifest.command.args` 与独立 `command-record.json.args` 都包含 `--apply`,同时拒绝 args 数组中任何带换行或 NUL 字符的结构化参数。总审计传入的 `--require-command-executable` executable 段也必须是安全绝对路径,不能是文件系统根目录,也不能包含换行或 NUL 字符,且会写入 `requiredCommandExecutables` 供复盘。总审计还会要求顶层 `manifest.commandName` 与内嵌 `manifest.command.name` 只要存在就各自是安全非空命令名,且两者同时存在时必须一致,并比较 `manifest.command` 与 `command-record.json` 的 schemaVersion / phase / name / cutoverRunId / exitCode / signal / startedAt / finishedAt / durationMs / stdoutPath / stderrPath / args / command / cwd / error 等关键字段;两份命令记录的 `schemaVersion` 都必须是 `1`stdoutPath / stderrPath 还必须分别与 `manifest.files.stdout.path` / `manifest.files.stderr.path` 指向同一份归档文件,args 与 command 用于证明真实 apply 参数未被替换成 dry-run 或其它动作;命令记录时间必须满足 `finishedAt >= startedAt`、`durationMs == finishedAt - startedAt`,且 `manifest.generatedAt` 不能早于命令 `finishedAt`;这些参数会隐式要求对应命令证据存在;`commandName` 只用于审计分类,不能替代真实脚本身份和真实 apply 参数校验。
- 命令证据字段补充:`manifest.expectedExecutable` 和 `manifest.command.expectedExecutable` 只要出现,就必须是安全绝对路径;空字符串、相对路径、文件系统根目录或包含换行 / NUL 的值必须按坏 manifest 失败,不能用 `||` 等兜底逻辑把坏字段吞掉。命令证据生成端的 `--expected-executable` 同样必须在执行真实命令前拒绝相对路径、文件系统根目录和带换行 / NUL 的路径。
- 命令证据身份补充:只要命令证据声明了 `expectedExecutable``manifest.command.executable` 和独立 `command-record.json.executable` 就必须同时是同一个安全绝对路径;即使人工总审计漏传 `--require-command-executable`,真实 executable 与 expectedExecutable 漂移也必须失败。
- 命令证据字段补充:正式命令证据必须在 `manifest.command.executable` 和 `command-record.json.executable` 中同时记录真实可执行文件绝对路径;只保留可读 `command` 字符串、缺少结构化 executable 或 executable 不是绝对路径,都必须失败。
- 命令证据生成补充:`pingora-cutover-command-evidence.mjs` 的 `-- <command>` 必须直接传真实命令绝对路径,不能传 PATH 裸命令名;生成端会在执行前拒绝非绝对路径,避免写出最终总审计天然会拒绝的 command-record。
- 路径安全补充:切换窗口覆盖 `pingora-direct-enable.sh` 的 `--preflight-script`、`--direct-live-script`、`--current-release-audit-script`、`--template-path`、`--service-unit-path`、`--dropin-path` 或 env 文件路径时必须使用绝对路径,且不能指向文件系统根目录,避免 current release、Jenkins 工作区和现场 cwd 混用;`--apply` 必须在安装 direct-entry drop-in 前确认 current release 自审、direct preflight 和 direct live smoke 脚本都真实存在,任一脚本缺失时直接失败且不改 systemd。启用脚本还必须在 current release 自审、preflight、drop-in 写入和 systemctl 前拒绝 service、路径、URL、Host、probe token、access log、数据库名、tail 行数和 timeout 参数中的换行或 NUL 字符,并在安装前拒绝符号链接形式的 drop-in 目录或 drop-in 目标文件、拒绝已存在但不是普通文件的目标,避免把低端口 capability 写入非预期 systemd 位置。`pingora-direct-rollback.sh --apply` 也必须在 `nginx -t`、删除 drop-in、reload Nginx 或 health patrol / shadow probe 复核前拒绝 service、路径、Nginx smoke URL / Host / 响应片段、health patrol 复核参数、shadow probe URL / token 和二进制 override 中的换行或 NUL 字符,并拒绝 service unit、drop-in、health patrol env、复核脚本和路径形式二进制 override 指向文件系统根目录,再拒绝符号链接形式的 drop-in 目录或目标文件、拒绝已存在但不是普通文件的目标,避免回退窗口误删或误判非预期 systemd 位置。`pingora-direct-rollback.sh` 覆盖 `--nginx-binary` 或 `--curl-binary` 时允许裸命令名走 `PATH`,但只要值包含路径分隔符就必须是绝对路径,避免回退窗口从 cwd 运行相对二进制;`--nginx-smoke-url` 必须是 `http(s)` URL,非法值在移除 drop-in 前失败。
- 直连日志证据补充:Pingora 直连接管不能只看 HTTPS / HTTP redirect / ACME / WSS 响应成功;`pingora-direct-enable.sh --apply` 和 release readiness `--require-direct` 必须显式提供 `--direct-pingora-access-log /var/log/genarrative/pingora-gateway.access.log`。direct live smoke 会给每个请求生成 `X-Request-Id`,再反查 Pingora access log 尾部记录,缺少对应 `request_id`、方法、路径或状态漂移都应阻断启用后复核。启用脚本 apply 后必须以 `--json` 运行 direct live 并解析 stdout 中的 `direct-access-log` 结构化结果;缺少该结果、`matchedCount != checked`、`missingCount != 0` 或 `mismatchCount != 0` 都让启用失败,避免 direct live 子进程退出 0 但 access log 证据缺失时误判切换成功。正式 runbook 的启用后证据包必须额外传 `--run-direct-live`,把 `direct-live.json`、stdout / stderr、命令记录、direct-access-log 检查结果和静态响应头白名单证据写入同一证据目录,不能只依赖启用脚本或 release readiness 的终端输出;`direct-live.json` 中的 `direct-access-log` 必须保留 `scannedLineCount`、`matchedCount`、`missing[]`、`mismatches[]` 以及每个 `request_id` 的预期 / 实际 method、path 与 status,静态资源检查还必须保留 `cache-control`、`etag`、`last-modified`、`accept-ranges`、`content-range`、`content-length` 和 `content-encoding` 白名单头,避免复盘时只看到 count 或终端 stderr。证据包还必须把 `directLiveAccessLog` 和 `directLiveStaticHeaders` 摘要提升到 `manifest.summary`,让值班人员先从 manifest 快速看到普通静态 / 指纹静态的 `Cache-Control`、`ETag`、`Last-Modified`、`Content-Length`、Range `Content-Range` 与 304 状态证据;缺少 `direct-access-log` 结构化结果、缺少可判定的静态头摘要,或静态头摘要缺少缓存头、校验头、Range `206 + Content-Range`、ETag 304 / Last-Modified 304 证据时整包记为 `CRITICAL`。若 snapshot 或 direct live stdout 无法解析,证据包必须保留 `snapshot-parse-error.txt` 或 `direct-live-parse-error.txt`,并在 manifest / 最终 stdout 中索引错误文件。`deploy/env/pingora-direct-live.env.example` 必须保留 `GENARRATIVE_PINGORA_DIRECT_PINGORA_ACCESS_LOG` 和 `GENARRATIVE_PINGORA_DIRECT_ACCESS_LOG_SINCE_LINES`,让目标机和 CI 使用同一口径。
- 影响范围:`deploy/systemd/genarrative-pingora-gateway.service`、`deploy/systemd/genarrative-pingora-gateway-direct-entry.conf`、`deploy/env/`、`scripts/deploy/pingora-direct-enable.sh`、`scripts/deploy/pingora-direct-rollback.sh`、`scripts/deploy/pingora-health-patrol-env-switch.mjs`、`scripts/jenkins-server-provision.sh`、API release / Jenkins 归档链路、生产运维护栏、Pingora 试点文档、Nginx README 和生产护栏。
- 验证方式:`npm run check:production-ops` 确认主 service 仍是 shadow、drop-in 具备最小 capability、Provision 只安装人工启用模板且 release 携带启用 / 回退脚本,并确认 enable 脚本默认模板路径指向 `deploy/systemd/genarrative-pingora-gateway-direct-entry.conf`、`plan:pingora-direct-cutover` 入口存在;`npm run check:pingora-direct-enable` 和 `npm run check:pingora-direct-rollback` 确认脚本默认 dry-run 不修改 drop-in、会打印命令、release layout 默认读取随包 direct-entry 模板、拒绝相对路径、非法 Nginx smoke URL、路径形式的二进制 override、enable / rollback 控制字符参数、符号链接 drop-in 目录 / 目标以及非普通 drop-in 目标,enable current release 自审失败、direct preflight 脚本缺失或 direct live smoke 脚本缺失时不会安装 drop-indirect live 退出 0 但缺少 `direct-access-log` JSON 证据时启用失败,rollback drop-in 路径异常或控制字符参数时不会继续执行 `nginx -t` 或删除真实目标,enable `--apply` 缺少 `--preflight-env-file`、`--preflight-check-cert-readable`、`--preflight-check-service-env-file`、`--preflight-check-service-user-cert-readable`、`--preflight-check-service-binary-executable`、`--preflight-check-ports-free`、`--direct-https-base-url`、`--direct-http-base-url`、`--direct-host`、`--direct-redirect-host`、`--direct-pingora-access-log` 或 `--direct-spacetime-database` 会失败,rollback `--apply` 缺少 `--reload-nginx` 或 `--nginx-smoke-url` 会失败,且启用 / 回退后都会核验 systemd 最终配置和 `ExecStart` 指向 current release`npm run check:pingora-cutover-status-snapshot` 必须覆盖 `systemctl cat` 读取另一份 Pingora env 时快照标记 `CRITICAL``npm run check:pingora-release-readiness-plan` 必须覆盖 `--dry-run-cutover` runbook、Host 一致性确认、redirect Host / rollback smoke Host 漂移负例、缺少 `--require-direct` 的负例、缺少 `--rollback-health-patrol-public-base-url` 的负例、enable / rollback apply 步骤和回退后 health patrol env Nginx 模式复核;enable 必须确认 Pingora active 并执行 direct live smokedirect live 失败或 access log 结构化证据缺失时整次启用失败;rollback 必须先跑 `nginx -t`,失败时不能先删除 drop-in,重启后必须确认 `ExecStart` 没有漂到旧 releasereload 后必须确认 Nginx active 并执行 smoke URLcurl 失败时整次回退失败;可选 health patrol env 复核必须拒绝 `pingora-direct` 残留或 public base URL / Host 漂移;可选 shadow probe 复核必须拒绝 URL / token 缺任一项、非法 URL 或非 `pingora-shadow` 响应;`npm run check:pingora-release-readiness` 默认会串起 direct-entry 静态预检、启用 / 回退 dry-run 与生产护栏;切换窗口先运行 `npm run check:pingora-direct-preflight -- --env-file /etc/genarrative/pingora-gateway.env --require-live-env --systemd-cat --check-cert-readable --check-service-env-file --check-service-user-cert-readable --check-service-binary-executable --check-ports-free`,再运行 release readiness `--require-direct`;验证失败时先 dry-run 回退脚本,再用 `--apply --reload-nginx --nginx-smoke-url https://<域名>/ --nginx-smoke-expect-body '<!doctype html>'` 执行,并确认回退脚本未报告 capability 残留、ExecStart 漂移、Nginx 非 active 或 smoke 失败,最后用 `node -- /opt/genarrative/current/scripts/check-production-health-patrol-env.mjs ...` 运行 health patrol env nginx 模式复核。
- 关联文档:`docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`deploy/nginx/README.md`。
## 2026-06-13 Pingora direct live 必须覆盖 WSS subscribe
- 背景:Pingora 已具备显式 TLS / HTTP redirect 直连入口,但 SpacetimeDB 前端订阅依赖 `/v1/database/<db>/subscribe` 的 WebSocket 长连接;只检查 HTTPS 普通请求无法证明直连入口能替代 Nginx 的 WebSocket 透传。
- 决策:`check-pingora-direct-live.mjs` 默认先通过 TLS ALPN 确认 HTTPS 可协商 `h2`,再对 `wss://<direct>/v1/database/<database>/subscribe` 发起握手,并使用 SpacetimeDB SDK 2.4.1 默认子协议 `v2.bsatn.spacetimedb`;成功 101 时必须保留该子协议和 `X-Genarrative-Gateway: pingora-shadow`。release readiness `--require-direct` 会自动要求 WSS subscribe 返回 101,并强制要求 `--direct-http-base-url` / `GENARRATIVE_PINGORA_DIRECT_HTTP_BASE_URL`、`--direct-host` / `GENARRATIVE_PINGORA_DIRECT_HOST`、`--direct-redirect-host` / `GENARRATIVE_PINGORA_DIRECT_REDIRECT_HOST`、`--direct-pingora-access-log` / `GENARRATIVE_PINGORA_DIRECT_PINGORA_ACCESS_LOG`、`--direct-preflight-systemd` / `GENARRATIVE_PINGORA_DIRECT_PREFLIGHT_SYSTEMD_CAT=true`、`--direct-preflight-check-cert-readable` / `GENARRATIVE_PINGORA_DIRECT_PREFLIGHT_CHECK_CERT_READABLE=true`、`--direct-preflight-check-service-env-file` / `GENARRATIVE_PINGORA_DIRECT_PREFLIGHT_CHECK_SERVICE_ENV_FILE=true`、`--direct-preflight-check-service-user-cert-readable` / `GENARRATIVE_PINGORA_DIRECT_PREFLIGHT_CHECK_SERVICE_USER_CERT_READABLE=true`、`--direct-preflight-check-service-binary-executable` / `GENARRATIVE_PINGORA_DIRECT_PREFLIGHT_CHECK_SERVICE_BINARY_EXECUTABLE=true` 和 `--direct-spacetime-database` / `GENARRATIVE_PINGORA_DIRECT_SPACETIME_DATABASE` 和 `--direct-health-patrol-env-file` / `GENARRATIVE_PINGORA_DIRECT_HEALTH_PATROL_ENV_FILE`,确保 HTTP/2 ALPN、HTTP 301、ACME challenge、正式域名 Host/SNI、redirect Location host、Pingora access log 的 request_id 落盘、systemd drop-in 生效、service EnvironmentFile 一致性、当前用户证书可读性、服务用户证书可读性、current release 二进制可执行性、health patrol direct 模式和目标库 WSS subscribe 都进入直连硬门禁;单独运行 direct live smoke 时可用 `--require-wss-upgrade` 强制同一口径。本机 `--host <域名>` 验证正式证书时,该 host 同时用于 HTTP Host 和 TLS SNI`--redirect-host <域名或host:port>` 则用于校验 HTTP redirect `Location`。`--skip-wss` 只允许单独 direct live 临时排障;release readiness `--require-direct` 会直接拒绝。
- 影响范围:`scripts/check-pingora-direct-live.mjs`、`scripts/check-pingora-gateway-smoke.mjs`、`scripts/check-pingora-release-readiness.mjs`、`deploy/env/pingora-direct-live.env.example`、生产运维护栏、Pingora 试点文档和 Nginx README。
- 验证方式:`node --check scripts/check-pingora-direct-live.mjs scripts/check-pingora-release-readiness.mjs scripts/check-pingora-release-readiness-plan.mjs`、`npm run check:pingora-gateway-smoke`、`npm run check:pingora-release-readiness-plan`、`npm run check:production-ops`、`npm run check:pingora-release-readiness`。
- 关联文档:`docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`deploy/nginx/README.md`。
## 2026-06-13 Pingora 直连入口先做显式配置能力
- 背景:Pingora shadow 已覆盖 Nginx handoff、gzip、timeout 和接流保护;继续接近正式版时需要验证是否具备不经 Nginx 的 HTTPS 入口能力,但不能让默认 shadow 部署误绑定公网 `80/443`。
- 决策:`pingora-gateway` 默认仍只监听 `GENARRATIVE_PINGORA_GATEWAY_LISTEN`;只有显式配置 `GENARRATIVE_PINGORA_GATEWAY_TLS_LISTEN`、`TLS_CERT_FILE`、`TLS_KEY_FILE` 时才额外挂载 Rustls HTTPS listener。只有在 TLS 入口已配置时才允许配置 `GENARRATIVE_PINGORA_GATEWAY_HTTP_REDIRECT_LISTEN`,该 HTTP listener 除 ACME challenge 外统一 301 到 HTTPS。证书申请和续期仍由 Certbot / 外部自动化承担,Pingora 只读取现有证书文件。目标机直连入口验收使用 `npm run check:pingora-direct-live` 或 release readiness 的 `--require-direct`,默认 shadow / Nginx canary 门禁不强制直连。
- 影响范围:`server-rs/crates/pingora-gateway`、`deploy/pingora/pingora-gateway.env.example`、`scripts/check-pingora-gateway-smoke.mjs`、`scripts/check-pingora-direct-live.mjs`、生产运维护栏、Pingora 试点文档和 Nginx README。
- 验证方式:`cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml`、`npm run check:pingora-gateway-smoke`、`npm run check:production-ops`、`npm run check:pingora-release-readiness`。
- 关联文档:`docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`deploy/nginx/README.md`。
## 2026-06-13 Pingora shadow 网关显式承接上游 timeout
- 背景:Pingora shadow 已覆盖路由、接流保护、gzip 和 canary handoff,但上游连接 / 读 / 写 timeout 若继续依赖框架默认值,正式 canary 时可能和 Nginx 的 `proxy_read_timeout` / `proxy_send_timeout` 口径漂移。
- 决策:`pingora-gateway` 在 `HttpPeer` 上显式设置 timeout:连接默认 `3000ms`,没有 Nginx 显式长超时的代理路由读取默认 `60s`,通用 `/api`、公开列表 / 详情和 SpacetimeDB subscribe 读取默认 `3600s`,写上游默认 `3600s`。读 / 写 / 连接超时统一映射为 JSON `504 GATEWAY_UPSTREAM_TIMEOUT`。所有 `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_*TIMEOUT*` 配置必须大于 `0`。
- 影响范围:`server-rs/crates/pingora-gateway`、`deploy/pingora/pingora-gateway.env.example`、`scripts/check-pingora-gateway-smoke.mjs`、生产运维护栏、Pingora 试点文档和 Nginx README。
- 验证方式:`cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml`、`npm run check:pingora-gateway-smoke`、`npm run check:production-ops`、`npm run check:pingora-release-readiness`。
- 关联文档:`docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`deploy/nginx/README.md`。
## 2026-06-13 Pingora shadow 网关先补齐 gzip parity
- 背景:Pingora 影子网关已经覆盖核心路由、接流保护和 Nginx handoff smoke,但旧口径仍把 gzip 和 Brotli 一起列为未承接能力,正式切换前需要先补齐 Nginx 当前已启用的 gzip 行为,同时避免未验收算法被 Pingora 默认行为隐式打开。
- 决策:`pingora-gateway` 默认启用 Pingora response compression`GENARRATIVE_PINGORA_GATEWAY_COMPRESSION_ALGORITHMS=gzip`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_ENABLED=true`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_LEVEL=5`、`GENARRATIVE_PINGORA_GATEWAY_GZIP_MIN_LENGTH_BYTES=1024` 对齐 Nginx `gzip_comp_level 5` / `gzip_min_length 1024`;进入 compression 模块前把 `Accept-Encoding` 收敛成 gzip allowlist,避免未验收的 `br` / `zstd` 被隐式打开,并避免小响应被额外压缩。smoke 还必须覆盖图片资源不压缩,保持 Nginx `gzip_types` 边界。`GZIP_LEVEL` 必须在 `0..=9``GZIP_MIN_LENGTH_BYTES` 必须大于 `0`,越界启动失败;`COMPRESSION_ALGORITHMS` 当前只允许 `gzip`。Pingora 正式化口径固定为 gzip-onlyBrotli 不进入当前 Pingora 直连门禁,仍由 Nginx / 前置代理能力探测承担。
- 影响范围:`server-rs/crates/pingora-gateway`、`deploy/pingora/pingora-gateway.env.example`、`scripts/check-pingora-gateway-smoke.mjs`、生产运维护栏、Pingora 试点文档和 Nginx 压缩文档。
- 验证方式:`cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml`、`npm run check:pingora-gateway-smoke`、`npm run check:production-ops`、`npm run check:pingora-release-readiness`。
- 关联文档:`docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`deploy/nginx/README.md`。
## 2026-06-13 Pingora canary handoff 先本机容器复现再目标机 live 验证
- 背景:Pingora 影子网关已覆盖核心 Nginx 路由 parity,但只靠静态 snippet 检查和目标机手工 live canary,无法在本机 / CI 中复现真实 Nginx -> Pingora handoff 链路。
- 决策:新增 `npm run check:pingora-canary-docker` 作为正式切换前的本机容器验收:脚本启动 Docker Nginx、真实 `pingora-gateway`、mock `api-server` 和 mock SpacetimeDB,渲染同一份 `deploy/nginx/snippets/genarrative-pingora-canary.conf` 后复用 live canary 断言。Docker Nginx 必须写入生产同口径 access log,并在 live smoke 后复用 `scripts/check-pingora-canary-access-log-parity.mjs` 按同一 `request_id` 对账 canary healthz、代表性 API、SpacetimeDB identity 和静态资源路径,避免本机 / CI 只验证 handoff 响应头。默认 Docker 或镜像缺失时跳过,CI / 目标 agent 用 `node scripts/check-pingora-canary-docker.mjs --require-docker --pull` 强制执行;目标机人工 include 后仍必须跑 `npm run check:pingora-canary-live`。同时新增 `npm run check:pingora-release-readiness` 作为正式切换聚合门禁,默认适合本机提交前检查;切换窗口必须用 `--require-docker --pull-docker --require-nginx --require-live` 强制 Docker handoff、目标机 `nginx -t` 和 live canary 全部通过,且 `--require-live` 必须显式提供 `--live-host`,避免只打到 Nginx 默认 vhost。
- 真实路径补充:前缀 canary 通过后、Pingora direct 直连前,使用 `deploy/nginx/snippets/genarrative-pingora-realpath-canary.conf` 做真实路径 canary。该 snippet 必须作为独立本机 `server` include 到 Nginx `http` 上下文,默认监听 `127.0.0.1:18083` 并写独立 `genarrative-pingora-realpath-canary.access.log`,不能 include 到生产 `443` server 内覆盖正式 location。`check-pingora-canary-live.mjs --realpath` 和 `check-pingora-canary-access-log-parity.mjs --realpath` 负责验证真实 `/api`、`/v1`、`/assets` 路径;release readiness 默认执行 `check:pingora-realpath-canary-toggle`,验证 realpath canary 启停脚本 dry-run、apply、失败回滚和 disable 恢复逻辑,目标机 runtime-only 用 `--require-realpath-live` 把已启用真实路径 canary 纳入门禁。
- 影响范围:`scripts/check-pingora-canary-docker.mjs`、`scripts/check-pingora-release-readiness.mjs`、`package.json`、生产运维护栏、Nginx README、Pingora 试点文档和生产运维文档。
- 验证方式:`node --check scripts/check-pingora-canary-docker.mjs scripts/check-pingora-release-readiness.mjs scripts/check-pingora-release-readiness-plan.mjs scripts/check-pingora-realpath-canary-toggle.mjs`、`npm run check:pingora-realpath-canary-toggle`、`npm run check:pingora-canary-docker`、`npm run check:pingora-release-readiness`、`npm run check:nginx-pingora-canary`、`npm run check:production-ops`;有 Docker 镜像或允许拉取时追加 `node scripts/check-pingora-canary-docker.mjs --require-docker --pull`,目标机切换窗口追加 `node scripts/check-pingora-release-readiness.mjs --require-docker --pull-docker --require-nginx --require-live --live-base-url http://127.0.0.1 --live-host <域名>`。
- 关联文档:`docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`deploy/nginx/README.md`。
## 2026-06-21 移动原生包产物必须显式验收
- 背景:`npm run check:native-shells` 会跑 Expo production bundle 和 EAS profile smoke,但不在普通开发机上强制执行 Android APK 或 iOS simulator 包构建;真实构建完成后仍需要一个固定命令证明输出不是空文件、错误压缩包或非原生包。
- 决策:移动壳新增 `npm run mobile-shell:build-artifacts`,读取 `build/native/mobile/genarrative-mobile-android.apk` 和 `build/native/mobile/genarrative-mobile-ios-simulator.tar.gz`。APK 必须是 ZIP 格式并包含 `AndroidManifest.xml`、`classes.dex`、`assets/index.android.bundle`iOS simulator 包必须是 gzip tar 并包含 `.app/Info.plist`、`.app/Genarrative`、`.app/main.jsbundle`。该命令只在真实移动构建后运行,不替代 `check:native-shells` 的普通门禁。
- 影响范围:`apps/mobile-shell/scripts/check-build-artifacts.mjs`、`apps/mobile-shell/package.json`、根 `package.json`、移动端分发构建流程。
- 验证方式:无移动构建产物时运行 `npm run mobile-shell:build-artifacts` 应明确失败;真实构建后运行同一命令必须通过。普通改动继续运行 `npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`。
## 2026-06-21 原生壳生成物必须被 gitignore 覆盖
- 背景:移动壳和桌面壳验收会生成 Expo `.expo/`、Expo export smoke、Tauri `target/`、Tauri schema、自动生成权限目录和根目录 `build/native/` 分发产物;只检查这些路径未被 Git 追踪,不能防止后续误删 `.gitignore` 条目后把生成物暴露给开发者手动误加。
- 决策:`npm run check:native-shells` 必须同时用 `git ls-files` 确认原生壳生成物未被追踪,并用 `git check-ignore -v` 确认这些生成物路径仍被 `.gitignore` 覆盖。手写 capability、权限配置和壳源码仍在生产扫描范围内,不得借生成目录排除规则绕开检查。
- 影响范围:`.gitignore`、`scripts/check-native-shells.mjs`、Expo / Tauri 构建和分发烟测。
- 验证方式:`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`。
## 2026-06-21 原生壳依赖版本门禁不可移除
- 背景:Expo / React Native / Tauri 的依赖版本会影响 WebView、权限、capability、构建产物和宿主桥接行为;两端单端配置检查已经锁定 package、lockfile 和 Cargo 解析版本,但根级总验收也需要防止未来重构时把这些锁版本检查从单端脚本中移除。
- 决策:`npm run check:native-shells` 必须反查移动壳 `check-config.mjs` 继续校验 Expo SDK、React Native、WebView、EAS CLI 和 `package-lock.json` 解析版本;桌面壳 `check-config.mjs` 必须继续校验 Tauri CLI、Cargo manifest、`Cargo.lock` 解析版本和直接依赖关系。升级原生壳底层依赖必须同步更新单端配置检查、锁文件和宿主壳方案文档。
- 影响范围:`scripts/check-native-shells.mjs`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、原生壳依赖升级流程。
- 验证方式:`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`。
## 2026-06-20 移动 HostBridge 消息注入失败边界
- 背景:Expo 移动壳通过 WebView `injectJavaScript` 把 HostBridge response 以及 `app.lifecycle`、`network.statusChanged`、`navigation.canGoBack` 等宿主事件回放给 H5;如果 WebView 进程切换、页面卸载或注入同步失败,壳层不能因为响应或事件回灌异常而崩溃。
- 决策:移动壳所有 HostBridge message 注入必须统一经过 `injectHostBridgeMessage`,该函数捕获同步注入异常,且 shell 卸载后直接丢弃迟到 response;事件注入用 `logMobileHostEventFailure(event, error)` 记录,response 注入用 `logMobileHostBridgeMessageFailure(error)` 记录。移动壳声明的请求 capability 不允许只由 `unsupported(request.method)` case 支撑;配置检查反查运行时 mounted guard、try/catch、ShellApp 注入失败测试、卸载后迟到响应测试和 capability 真实实现边界。
- 影响范围:`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/src/shell/ShellApp.test.tsx`、`apps/mobile-shell/scripts/check-config.mjs`。
- 验证方式:`npm run mobile-shell:test -- src/shell/ShellApp.test.tsx`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`。
## 2026-06-20 桌面图片拖拽坐标非负边界
- 背景:Tauri 桌面壳通过系统拖拽事件向 H5 发送 `file.imageDropped`,拖拽坐标来自窗口事件;窗口边缘或平台差异可能产生负数或小数坐标,H5 只负责校验有限 number,不负责裁剪桌面系统坐标。
- 决策:桌面壳在发送图片拖拽 HostBridge 事件前,必须把拖拽坐标 round 成整数并裁剪到非负值,再组装 import image payload;配置检查反查坐标归一 helper 和对应 Rust 单测。
- 影响范围:`apps/desktop-shell/src-tauri/src/shell/file_drop.rs`、`apps/desktop-shell/scripts/check-config.mjs`。
- 验证方式:`npm run desktop-shell:test -- file_drop`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`。
## 2026-06-20 桌面图片拖拽候选读取失败不可静默
- 背景:桌面壳拖入图片时会按路径列表寻找第一个真实可导入图片;扩展名合法但内容损坏、超限或读取失败的文件如果被静默跳过,用户只会看到拖拽无反应,开发侧也缺少排障线索。
- 决策:`first_valid_desktop_image_drop_payload(...)` 对扩展名符合图片候选但 payload 组装失败的路径必须记录 `desktop host event failed for file.imageDropped.payload`,然后继续尝试后续候选;目录、非图片扩展名或没有任何有效图片仍保持不派发 `file.imageDropped` payload。
- 2026-06-21 调整:拖拽图片 payload 组装失败日志只记录固定 `file.imageDropped.payload` 标签,不把本机读取错误、文件内容校验错误或其它 payload 细节写入可分发桌面壳 stderr;配置检查拒绝重新输出 `: {error}`。
- 影响范围:`apps/desktop-shell/src-tauri/src/shell/file_drop.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳方案文档。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::file_drop`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`。
## 2026-06-20 移动扫码权限异步取消边界
- 背景:Expo 移动壳 `scanner.scanQrCode` 会打开真实相机权限请求和扫码 overlay;如果用户在系统权限 Promise 返回前关闭扫码,旧权限结果不能重新激活 CameraView,也不能完成已经取消的 HostBridge 请求。
- 决策:`QrScannerOverlay` 必须在 active/requestKey 变化和组件清理时忽略迟到的权限结果;移动壳配置检查必须反查“取消后迟到权限不重新打开 CameraView”的测试用例。
- 影响范围:`apps/mobile-shell/src/shell/QrScannerOverlay.tsx`、`apps/mobile-shell/src/shell/QrScannerOverlay.test.tsx`、`apps/mobile-shell/scripts/check-config.mjs`。
- 验证方式:`npm run mobile-shell:test -- src/shell/QrScannerOverlay.test.tsx`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`。
## 2026-06-20 桌面壳系统能力调用顺序门禁
- 背景:Tauri 桌面壳文件导出和本地通知已经在运行时代码中先校验 HostBridge payload,再打开系统保存对话框、读取通知权限或请求通知权限;如果后续重构把系统能力调用提前,非法请求会触达原生系统边界。
- 决策:桌面壳单端配置检查必须逐函数反查 `file.exportText`、`file.exportImage`、`file.exportAudio` 先调用对应 payload helper 再进入 `.dialog()`,并反查 `notification.showLocal` 先调用 `local_notification_payload(request)` 再进入 `app.notification()`。文件导入仍以用户主动选择文件后的本地 payload 读取和大小 / MIME 校验为准。
- 影响范围:`apps/desktop-shell/scripts/check-config.mjs`、`apps/desktop-shell/src-tauri/src/host_bridge/files.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`。
- 验证方式:`node apps/desktop-shell/scripts/check-config.mjs`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`。
## 2026-06-20 原生壳替身词扫描排除构建产物
- 背景:桌面壳单端配置检查会递归扫描生产源码和配置中的替身词;本地或 CI 运行 Tauri / Cargo 后,`apps/desktop-shell/src-tauri/target/` 会包含依赖 `.d` 等生成文件,若纳入扫描会让门禁被缓存内容污染。
- 决策:生产替身词扫描只覆盖壳源码、分发配置、共享 HostBridge 契约和已接入真实宿主能力的 H5 调用链;Expo export、Tauri `target/`、Tauri schema `gen/`、Tauri 自动生成权限目录、Cargo / Metro 缓存和 release 构建产物不进入扫描范围。桌面单端配置检查显式跳过 `target/`、`gen/` 和 `permissions/autogenerated/`,根级 `check:native-shells` 继续排除生成目录。
- 影响范围:`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档、宿主壳能力统一协议文档和共享开发流程记忆。
- 验证方式:`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-17 原生移动与桌面壳统一作为 HostBridge Adapter
- 背景:后续需要移动端 App 和桌面端 App,但现有主站、固定玩法 runtime、小程序壳和未来 AI H5 sandbox 已经以 H5 为主线;如果移动端重写 React Native UI、桌面端重写 Rust/Tauri UI,会形成玩法、登录、支付、分享和运行态的多套实现。
- 决策:移动端原生壳采用 `Expo + React Native`,桌面端壳采用 `Tauri`。两者都只作为 `native_app` 宿主壳和 HostBridge adapter,不重写现有 React H5 主站,不把固定内置玩法迁到 React Native / Rust UI,也不让 AI 生成 H5 游戏直接访问完整 HostBridge。Expo 壳通过 `react-native-webview` 承接 H5 与 native 通信,Tauri 壳通过受控 command 和 capabilities 承接桌面能力;新增能力必须先进入 HostBridge 契约和测试。
- 2026-06-17 首轮落地:新增 `packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/nativeAppHostBridge.ts`、`apps/mobile-shell/` 和 `apps/desktop-shell/`。壳只声明并实现真实可用能力;移动壳使用真实品牌图标资产并支持 `genarrative://`、iOS associated domain、Android app link 到同源 H5 路径,`navigation.openNativePage` 只接受同源 H5 route 并切换 WebView URL,不伪造尚未存在的原生页面,且通过 `host.events` 注入 `navigation.canGoBack` 返回栈状态事件,`share.setTarget` / `share.open` 解析统一分享目标并调用 React Native 系统分享面板,发布分享弹窗在 Expo 移动壳中通过 `share.open` 提供“系统分享”动作,失败时保留复制链接回退路径;`file.exportText` 写入 Expo 缓存文本文件后交给系统分享 / 保存面板,成功只返回文件名和字节数,`haptics.impact` 通过 Expo Haptics 承接 H5 运行时点击反馈;`app.openExternalUrl` 在 Expo 与 Tauri 两端都只允许 `http:`、`https:`、`mailto:`、`tel:` 外链协议;H5 复制服务在 native_app 中优先通过 `clipboard.writeText` 写入 Expo / Tauri 系统剪贴板,失败后再回退浏览器复制路径;H5 运行时反馈在 native_app 中优先通过 `haptics.impact` 请求真实移动端触觉,宿主不可用或 unsupported 时回退浏览器 `navigator.vibrate`H5 主站按当前平台阶段同步 `document.title` 并通过 `app.setTitle` 请求宿主窗口标题,Tauri 壳通过主窗口 API 同步非空窗口标题,Expo 移动壳不声明该能力时静默忽略;桌面壳已通过 Tauri clipboard-manager 接入 `clipboard.writeText`,将 `navigation.openNativePage` 实现为 `https://www.genarrative.world` 同源 H5 route 的主窗口受控跳转,并将 `share.setTarget` / `share.open` 实现为复制非空分享文本到系统剪贴板,H5 发布分享弹窗在 Tauri 桌面壳中展示“复制分享文案 / 已复制 / 复制失败”;桌面 `file.exportText` 通过 Tauri dialog 插件打开系统保存对话框并由 Rust 写入文本文件,但不把 dialog / fs 插件 command 直接暴露给 H5,成功只返回文件名和字节数,用户取消返回 `cancelled`;登录、支付、原生系统分享面板等未接入真实 SDK / 插件前必须返回 unsupported 并让 H5 fallback,生产代码禁止 mock 成功。
- 2026-06-18 外链接入:H5 新增 `openHostExternalUrl()` facade`native_app` 下会把外链归一化为允许协议的绝对 URL 后请求 `app.openExternalUrl`ICP备案号和 RPG 资产调试原图入口已优先走宿主系统浏览器,普通浏览器和小程序保留原 `<a>` 行为,宿主不可用或拒绝时回退浏览器外链。
- 2026-06-18 外链协议白名单门禁:`packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_EXTERNAL_URL_PROTOCOLS` 是 `app.openExternalUrl` 唯一协议来源,当前只允许 `http:`、`https:`、`mailto:`、`tel:`Expo 直接复用共享归一化逻辑,Tauri Rust 侧必须用 URL parser 镜像同一清单,根级 `npm run check:native-shells` 会拒绝共享契约与桌面壳协议清单漂移。
- 2026-06-18 移动壳 WebView 导航收紧:Expo WebView 自身拦截外域导航时复用 HostBridge 外链协议白名单,只把 `http:`、`https:`、`mailto:`、`tel:` 交给 `Linking.openURL``javascript:`、`file:`、相对异常路径等危险目标直接阻断,避免离开同源主站后仍保留完整 HostBridge。
- 2026-06-19 移动壳 WebView 外链协议共源:`apps/mobile-shell/src/shell/navigation.ts` 的 WebView 外链离壳判断必须调用共享 `normalizeHostBridgeExternalUrl`,不得在 shell 层另写协议判断;`apps/mobile-shell/scripts/check-config.mjs` 会拒绝重新硬编码 `mailto:` / `tel:` / `javascript:` 等协议分支,`navigation.test.ts` 用 `HOST_BRIDGE_EXTERNAL_URL_PROTOCOLS` 反查当前允许协议。
- 2026-06-19 移动壳 WebView 外链打开收口:Expo WebView 外链拦截统一调用 `openMobileShellExternalNavigation(Linking, request.url)`,该 helper 先复用共享外链协议 normalizer,再调用 `canOpenURL` 确认系统可处理,最后才 `openURL`;系统不能打开或 URL 被拒绝时只阻断留壳,不伪造成功也不把危险协议交给系统。`ShellApp` 不再内联 `Linking.canOpenURL` / `Linking.openURL` Promise 链,移动壳配置检查和 `navigation.test.ts` 会覆盖该顺序。
- 2026-06-19 移动壳外链打开 helper 共用:Expo WebView 外域拦截和 HostBridge `app.openExternalUrl` 都必须复用 `openMobileShellExternalNavigation` 执行系统外链打开动作;HostBridge 分支仍先调用 `normalizeHostBridgeExternalUrlPayload` 保留 payload 错误语义,且 `apps/mobile-shell/src/host-bridge/navigation.ts` 自己承接 `expo-linking` 系统 API 调用,不再让 `dispatch.ts` 直接导入 `Linking` 或维护 `Linking.canOpenURL` / `Linking.openURL` 顺序。移动壳配置检查会拒绝 `app.openExternalUrl` 绕开该 helper 或分发层重新导入 `expo-linking`,避免两条离壳路径漂移。
- 2026-06-19 移动壳系统分享 URL 边界:Expo `share.open` 调用 React Native 系统分享面板前,只允许把 `url`、`href`、`path`、`targetPath` 和 `work` 归一为 `https://www.genarrative.world` 同源公开 URL;外域、协议相对 URL、`javascript:` 等危险目标必须返回 `invalid_request`,且显式非法 payload 不得回退到之前缓存的 `share.setTarget` 目标。分享实现复用移动壳入口 URL 的生产主站 origin,配置检查会拒绝重新声明同值 origin 或移除协议相对 URL 拦截。
- 2026-06-19 桌面壳系统分享 URL 边界:Tauri `share.open` 写入系统剪贴板前同样只允许把 `url`、`href`、`path`、`targetPath` 和 `work` 归一为 `https://www.genarrative.world` 同源公开 URL;外域、协议相对 URL、`javascript:` 等危险目标必须返回 `invalid_request`,且显式非法 payload 不得回退到之前缓存的 `share.setTarget` 目标。桌面壳配置检查会拒绝移除同源分享 URL 归一和协议相对 URL 拦截。
- 2026-06-19 原生壳分享桥接边界:Expo `share.setTarget` / `share.open` 的缓存目标、分享 payload 归一、系统分享调用和 HostBridge 响应统一收口在 `apps/mobile-shell/src/host-bridge/share.ts`Tauri `share.setTarget` / `share.open` 的缓存目标、分享文本生成、剪贴板 fallback 写入和 HostBridge 响应统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/share.rs`。两端 `dispatch` 只负责委托对应 share 模块,配置检查会拒绝分发层直接持有分享状态、生成分享文本、写入分享剪贴板结果或包装分享成功响应。
- 2026-06-19 桌面壳窗口标题桥接边界:Tauri `app.setTitle` 的 payload 校验、非空 / 控制字符拒绝、80 字符截断和主窗口 `set_title` 调用统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/title.rs``dispatch.rs` 只负责委托 `set_desktop_host_bridge_window_title(...)`。桌面壳配置检查和根级结构门禁会覆盖 `title.rs` 文件清单、共享标题长度镜像和 dispatch 委托关系。
- 2026-06-19 桌面壳文件桥接执行边界:Tauri `file.exportText` / `file.importText` / `file.importDocument` / `file.exportImage` / `file.importImage` / `file.importAudio` / `file.exportAudio` 的系统文件对话框过滤器、用户取消语义、路径转换、异步读写编排和 HostBridge 响应统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/files.rs`MIME、大小、base64、文件名清洗、本地副本读写和 HostBridge payload 组装统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/file_payloads.rs``dispatch.rs` 只负责按 method 委托 `export_desktop_host_bridge_*_file(...)` / `import_desktop_host_bridge_*_file(...)`。桌面壳配置检查会拒绝分发层直接调用 `.dialog()`、`blocking_save_file` / `blocking_pick_file`、文件 payload helper 或落盘 helper,避免文件访问边界重新散落。
- 2026-06-20 移动壳文件桥接载荷边界:Expo `file.exportText` / `file.importText` / `file.importDocument` / `file.exportImage` / `file.importImage` / `file.captureImage` / `file.importAudio` / `file.exportAudio` 的 DocumentPicker、ImagePicker、File、Sharing 系统交互、用户取消语义、缓存读写编排和 HostBridge 响应包装统一收口在 `apps/mobile-shell/src/host-bridge/files.ts`MIME、大小、base64、文件名清洗、图片 / 音频 bytes 匹配和 picker 结果到 HostBridge payload 的组装统一收口在 `apps/mobile-shell/src/host-bridge/filePayloads.ts`。移动壳单端配置检查和根级 `npm run check:native-shells` 会把 `filePayloads.ts` 纳入结构清单与 HostBridge 源码扫描,避免文件载荷边界重新散落到分发层或 shell 层。
- 2026-06-20 移动文件动作单测边界:`apps/mobile-shell/src/host-bridge/files.test.ts` 直接覆盖 Expo 文件动作 helper 的文本导出、系统分享不可用、文本 / 文档 / 音频导入、用户取消、图片相册导入、相机权限拒绝和音频二进制导出;单端配置检查会反查这些动作测试存在,根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,避免系统文件交互只靠完整 HostBridge bridge 流程间接覆盖。
- 2026-06-20 移动文件导出分享不可用边界:Expo `file.exportText` / `file.exportImage` / `file.exportAudio` 在 `Sharing.isAvailableAsync()` 返回 false 时必须直接返回 `unsupported_capability`,不得写入 Expo cache,也不得调用 `Sharing.shareAsync` 或伪造 saved 成功;`apps/mobile-shell/src/host-bridge/files.test.ts` 用三类导出参数化覆盖该顺序,配置检查反查“不写缓存”断言。
- 2026-06-20 移动文件载荷单测边界:`apps/mobile-shell/src/host-bridge/filePayloads.test.ts` 直接覆盖移动壳文件载荷 helper 的 base64、UTF-8 byte、MIME / 扩展名归一、图片 / 音频 bytes 匹配、导出文件名补扩展、导入大小门禁和 ImagePicker payload 转换;根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,防止后续只靠完整 HostBridge bridge 流程间接覆盖文件安全边界。
- 2026-06-20 移动本地通知单测边界:`apps/mobile-shell/src/host-bridge/notifications.test.ts` 直接覆盖 Expo `notification.showLocal` 的已授权 / iOS provisional 权限复用、alert-only 权限请求、权限拒绝失败、iOS 即时调度、Android 固定 channel、共享 payload 归一和结构化 `delivered_to_system` 成功响应;根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,避免移动通知边界只靠完整 HostBridge bridge 流程间接覆盖。
- 2026-06-20 桌面能力清单单测边界:Tauri `capabilities.rs` 必须用 Rust 单测同时覆盖桌面 runtime capability 清单顺序、无重复、真实桌面能力完整包含,并显式排除 `auth.requestLogin`、`payment.request`、`file.captureImage`、`scanner.scanQrCode` 和 `haptics.impact` 等未接入能力;桌面单端配置检查会反查该测试边界,避免只靠方案文档或共享 profile 发现桌面壳能力伪声明。
- 2026-06-20 桌面本地通知契约镜像:Tauri `notification.showLocal` 的 title / body 归一化、长度上限和成功结果 action 必须镜像共享 HostBridge 契约;Rust 侧常量使用 `HOST_BRIDGE_LOCAL_NOTIFICATION_TITLE_MAX_LENGTH`、`HOST_BRIDGE_LOCAL_NOTIFICATION_BODY_MAX_LENGTH` 和 `HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_ACTION` 命名,桌面单端配置检查会与 `packages/shared/src/contracts/hostBridge.ts` 比对数值并反查成功结果由该 action 常量组装,避免通知 payload 边界变成桌面壳本地规则。
- 2026-06-19 桌面壳外链打开 helper 共用:Tauri WebView 外域拦截和 HostBridge `app.openExternalUrl` 都必须复用 `open_normalized_desktop_external_url` 执行系统外链打开动作;HostBridge 分支仍先用 `normalize_external_url` 保留 payload 错误语义并把 opener 错误回传给 H5WebView 拦截保持 best-effort 静默处理。桌面壳配置检查会拒绝 `dispatch.rs` 直接调用 `app.opener().open_url` 绕过该 helper,避免两条离壳路径漂移。
> 2026-07-18 覆盖说明:本段后续关于微信 `navigation.openNativePage`、生成结果订阅页、`[subscribe-message]` 日志和订阅页路由门禁的 2026-06 决策均已由旧创作模板退役决策废止,只作为历史记录。Expo / Tauri 的同源 H5 受控导航及微信登录、支付、分享能力继续有效。
- 2026-06-20 H5 原生导航预校验:`navigateHostNativePage()` 在 `native_app` 下发送 `navigation.openNativePage` 前必须先拒绝空值、控制字符、协议相对 URL、外域绝对 URL 和非 `http:` / `https:` 协议目标;同源绝对 URL、`/path` 和保留给桌面壳兼容的相对 route 继续交给 Expo / Tauri 壳二次归一并补写宿主上下文。微信小程序分支仍按小程序页面 URL 语义走 `wx.miniProgram.navigateTo`,不套原生 App 同源 H5 预校验。根级 `npm run check:native-shells` 会反查 H5 facade 仍使用 `normalizeNativeAppPageUrl(...)` 且发送归一后的 URL,避免明显不安全目标触达原生壳。
- 2026-06-20 微信受控原生页能力声明:微信小程序壳真实 capability profile 声明 `navigation.openNativePage`,用于承接已经登记并测试的小程序原生页 flow;当前订阅生成结果通知页通过 H5 `requestGenerationResultSubscribePermission()` 调用 `navigateHostNativePage()` 打开 `/pages/subscribe-message/index`,小程序页再调用真实 `wx.requestSubscribeMessage` 并按既有结果协议回灌。根级 `npm run check:native-shells` 必须把该能力反查到共享 profile、微信 `WECHAT_HOST_CAPABILITIES` 镜像、订阅页协议常量、H5 入口、小程序 host-bridge / shell / page 文件和相关测试;该能力不代表开放任意小程序页面跳转。
- 2026-06-18 能力声明收紧:`packages/shared/src/contracts/hostBridge.ts` 提供 HostBridge method / capability 白名单,H5 的 `getHostRuntime()` 会解析并过滤 `hostCapabilities``openHostShare`、`writeHostClipboardText`、`requestHostHapticsImpact`、`setHostAppTitle`、`exportHostTextFile` 等 native 能力只在宿主声明对应 capability 后调用。发布分享弹窗只有声明 `share.open` 时才显示受控分享动作,并按 `hostShell` 区分 Expo 系统分享面板和 Tauri 剪贴板复制表达,避免旧壳或裁剪壳露出不可用入口。
- 2026-06-20 H5 原生能力门控收口:除 `host.getRuntime` 为了支持旧入口 URL 缺少 capability 时回读真实 runtime 可保留特殊判断外,H5 facade 中所有 native_app request 能力都必须通过 `canUseNativeHostCapability(...)` 统一门控,不得在业务能力函数内直接读取 `runtime.hostCapabilities.includes(...)`,避免各能力复制门控规则;根级 `npm run check:native-shells` 会从共享 `HOST_BRIDGE_METHODS` 自动派生需门控的 request capability 清单,新增 method 时必须同步补齐 H5 facade 门控。
- 2026-06-20 移动壳未声明 method 覆盖:Expo 移动壳对未进入 `HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES` 的共享 request method 必须由测试从 `HOST_BRIDGE_METHODS` 自动派生覆盖,并在请求到达时返回明确 `unsupported_method`;平台差异能力如 Android 不声明的 `app.setBadgeCount` 保持独立 `unsupported_capability` 语义,不混入未声明 method 清单,移动壳配置检查必须反查 Android 角标请求失败测试仍存在。
- 2026-06-20 移动 dispatch 单测边界:`apps/mobile-shell/src/host-bridge/dispatch.test.ts` 直接从 `HOST_BRIDGE_METHODS` 与 `HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES` 派生未声明 method 清单,覆盖 `dispatchMobileHostBridgeRequest(...)` 返回 `unsupported_method`,避免只靠完整 `bridge.test.ts` 间接证明 `auth.requestLogin`、`payment.request` 等等待真实 SDK 的能力不会伪成功。
- 2026-06-20 移动壳能力清单单测边界:`apps/mobile-shell/src/host-bridge/capabilities.test.ts` 直接覆盖 Expo 移动壳 `MOBILE_HOST_CAPABILITIES` / `IOS_MOBILE_HOST_CAPABILITIES` 必须引用共享 `HOST_BRIDGE_EXPO_MOBILE_*` profileAndroid 只使用 base profile 且不声明 `app.setBadgeCount`iOS 只额外声明真实角标能力,并且两端在真实 SDK / 渠道流程落地前不得声明 `auth.requestLogin` 或 `payment.request`。根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,避免移动壳 capability profile 本地复制或伪声明。
- 2026-06-20 壳文档能力清单反查:`npm run check:native-shells` 会同时反查移动壳 / 桌面壳主状态段落能力清单和后续完整能力清单;主状态段落按集合检查,允许按叙述需要调整顺序,但不得漏写或多写 capability,完整能力清单继续按共享 profile 顺序检查。
- 2026-06-18 宿主 runtime 回读:主 App 启动时会通过真实 `host.getRuntime` 回读 Expo / Tauri runtime 并缓存过滤后的能力清单,能力来源为 URL `hostCapabilities` 与宿主真实回包的并集;裁剪壳或旧入口 URL 缺少 `hostCapabilities` 时也能启用真实声明能力,但仍不会仅凭 `native_app` 或 transport 存在推断能力可用。该回读请求的短超时由共享契约 `HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS` 声明,H5 facade 不得本地重声明。
- 2026-06-18 壳能力防漂移:`npm run mobile-shell:typecheck` 与 `npm run desktop-shell:typecheck` 会校验 Expo / Tauri 壳声明的 capability 均来自共享 HostBridge 白名单,并校验壳 runtime 回包、H5 URL `hostCapabilities` 和实现分支保持一致;微信小程序 `WECHAT_HOST_CAPABILITIES` 由 `miniprogram/host-bridge/protocol.test.js` 和根级 `npm run check:native-shells` 反查共享 `HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES`。新增能力必须先更新契约和真实壳实现,再通过这些检查。
- 2026-06-19 宿主上下文 query 契约收口:`packages/shared/src/contracts/hostBridge.ts` 是宿主上下文 query 字段和值的唯一 TypeScript 来源;`HOST_BRIDGE_RUNTIME_CONTEXT_QUERY_KEY` 覆盖 H5 runtime parser 字段,`HOST_BRIDGE_NATIVE_APP_QUERY_KEY` / `HOST_BRIDGE_NATIVE_APP_QUERY_KEYS` / `HOST_BRIDGE_NATIVE_APP_QUERY` 固定 Expo / Tauri 原生壳入口 query`HOST_BRIDGE_WECHAT_MINI_PROGRAM_SOURCE_QUERY` 固定微信 WebView 来源标记,`HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS` 固定 H5 页面内导航需要保留的宿主字段。Expo 壳直接引用共享常量,Tauri Rust 和微信 CommonJS 镜像由 `npm run check:native-shells` / 单壳配置检查反查;微信请求头必须从 `WEB_VIEW_SOURCE_QUERY` 读取 `clientType` / `clientRuntime`,不得另起常量。
- 2026-06-19 H5 HostBridge 载荷边界收口:`src/services/host-bridge/hostBridge.ts` 作为 H5 facade 也必须直接导入 `HOST_BRIDGE_TEXT_MIME_TYPES`、`HOST_BRIDGE_DOCUMENT_MIME_TYPES`、`HOST_BRIDGE_IMAGE_MIME_TYPES` 和 `HOST_BRIDGE_AUDIO_MIME_TYPES`,只能从共享契约派生本地 Set 用于归一化,不得重新写 MIME 字面量清单;`npm run check:native-shells` 会拒绝 H5 facade 重新复制文本、图片或音频 MIME 边界。
- 2026-06-18 原生壳统一验收门禁:根级 `npm run check:native-shells` 统一执行 H5 HostBridge 关键测试、Expo 壳 typecheck / test / config smoke / Metro export smoke、Tauri 壳 typecheck / cargo test、桌面 release `--no-bundle` 构建烟测,以及可分发壳与 H5 HostBridge 真实调用链的临时替身词扫描;根级 `npm run check` 会在 lint、主站测试、构建和内容检查后继续执行该门禁,避免 HostBridge、三端壳、Expo managed config、移动端 production bundle、桌面 release 入口和 H5 HostBridge 真实调用链禁替身验收散落成容易漏跑的单项命令。
- 2026-06-19 原生壳临时替身扫描范围:`npm run check:native-shells` 的生产替身词扫描必须覆盖微信小程序壳生产 `.js`、Expo / Tauri 壳源码与配置、共享 HostBridge 契约、H5 native transport,并自动覆盖已接入真实宿主能力 facade 的 H5 生产调用链文件;新增 H5 调用点接入 HostBridge 时,必须让自动扫描覆盖对应文件或在同等门禁中证明生产代码没有 mock / fake / stub / TODO / FIXME / 模拟 / 伪造。
- 2026-06-18 微信壳桥接层纳入统一验收:`npm run check:native-shells` 还会运行 `miniprogram/host-bridge/`、`miniprogram/shell/`、`pages/web-view` 样式和 `scripts/miniprogram-web-view-auth.test.ts` 的微信壳测试,覆盖 WebView 入口、登录触发、分享目标、支付结果、订阅消息结果和九宫切图行为;三端桥接层文件结构检查只证明目录边界,行为回归必须由同一门禁中的微信壳测试证明。
- 2026-06-21 微信 WebView env 诊断日志收口:微信 `web-view` 壳读取小程序 envVersion 失败时只记录 `[web-view] read mini program env failed` 固定标签,不输出 `wx.getAccountInfoSync()` 原生异常对象;根级原生壳门禁拒绝恢复 `console.warn(..., error)`。
- 2026-06-21 微信九宫切图诊断日志收口:微信 `share-grid` 壳下载封面、读取图片、导出切图、保存相册或最终保存流程失败时,只记录 `[share-grid] <stage>` 固定标签,不输出 `wx.downloadFile`、`wx.getImageInfo`、`wx.canvasToTempFilePath`、`wx.saveImageToPhotosAlbum` 或其它原生错误对象;页面仍只展示 `九宫切图保存失败。` 稳定文案。
- 2026-06-21 微信支付与订阅诊断日志收口:微信支付参数解析、虚拟支付失败和订阅消息请求失败只记录 `[wechat-pay] <stage>` / `[subscribe-message] <stage>` 固定标签,不输出微信原生错误对象;H5 回灌继续只使用稳定 `wechat payment unavailable` / `wechat subscribe unavailable` 语义。
- 2026-06-21 微信 WebView 认证诊断日志收口:微信 `web-view` 壳解析认证结果、`wx.login`、小程序登录请求、手机号绑定请求、认证流程和手机号授权拒绝失败时,只记录 `[web-view] <stage>` 固定标签,不输出微信原生错误对象、HTTP response 或授权 detail;页面仍只展示稳定登录 / 绑手机号错误文案。
- 2026-06-21 微信 WebView 页面事件诊断日志收口:微信 `web-view` 壳加载成功、加载失败和 H5 message 事件只记录 `[web-view] <stage>` 固定标签,不输出 WebView `event.detail`;分享目标解析继续走结构化 message payload,但生产日志不得回吐原生事件体。
- 2026-06-21 原生壳生产替身词门禁同步:根级 `check:native-shells`、Expo 移动壳单端 `check-config` 和 Tauri 桌面壳单端 `check-config` 都必须拒绝生产壳源码出现 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造 / 未实现 / 临时 / 后续 等替身或未来式占位词;真实未接能力只能通过明确 unsupported 语义表达。
- 2026-06-21 HostBridge 导航与移动 WebView 进程诊断收口:H5 微信小程序 `navigateTo.fail` 只记录 `[host-bridge] wechat mini program navigation failed` 固定标签,不输出微信原生失败对象;Expo 移动壳 WebView content / render process failure 只记录 `mobile WebView process failed for <stage>` 固定标签,不输出计数、URL、native event 或 detail 对象。
- 2026-06-19 微信壳路由一致性门禁:`npm run check:native-shells` 必须反查 `miniprogram/app.json.pages`、`miniprogram/host-bridge/protocol.js`、H5 `src/services/host-bridge/hostBridge.ts` 小程序页面常量、H5 `src/services/wechatMiniProgramSubscribe.ts` 订阅授权页面常量、`miniprogram/host-bridge/webView.js` 分享入口 / 分享消息类型、`miniprogram/config.js` source query / 域名格式、`miniprogram/shell/webView.js` 请求头来源标记、H5 runtime parser 和 H5 路由保留字段。新增或调整小程序页面、登录派生 URL、支付页、九宫切图页、订阅页、WebView 来源标记、H5 入口域名、API base URL 或宿主上下文 query 字段时,必须同步这几处常量并保持生产 / 开发域名都显式配置为纯 HTTPS domain;运行时开发域名回退生产域名只作为异常兜底。
- 2026-06-18 登录 / 支付能力禁伪声明:`auth.requestLogin` 和 `payment.request` 保留在共享 HostBridge 契约中供未来真实接入,但 Expo / Tauri 壳在真实 SDK、渠道流程和后端契约落地前不得声明这些 capability,也不得把它们写入入口 URL `hostCapabilities`;两端检查脚本会拒绝伪声明,请求实际到达壳层时必须返回明确 `unsupported_method` 并让 H5 fallback,两端壳测试直接覆盖这两个 method。
- 2026-06-20 桌面壳未声明 method 禁伪成功:Tauri 桌面壳只声明真实可用 capability;共享 HostBridge method 白名单中未进入桌面 capability profile 的 method,例如 `auth.requestLogin`、`payment.request`、`file.captureImage`、`scanner.scanQrCode` 和 `haptics.impact`,请求实际到达桌面壳时必须统一返回明确 `unsupported_method`,不得伪造成功或半接入。桌面 Rust 测试必须从 `HOST_BRIDGE_METHODS` 与 `capabilities()` 差集派生 unsupported 覆盖清单,桌面单端配置检查会反查该派生路径,后续共享契约新增 method 时必须同步声明真实桌面能力或补进 unsupported 语义。
- 2026-06-18 移动壳触觉反馈边界:`haptics.impact` 只接受 `light`、`medium`、`heavy` 三档 impact style,缺省为 `light`;未知值必须返回 `invalid_request`,不得静默降级成真实设备触觉反馈。桌面壳不声明该 capabilityH5 继续按 HostBridge fallback 处理。
- 2026-06-19 移动壳触觉反馈模块边界:Expo `haptics.impact` 的 HostBridge payload 解析、共享 style 归一、`light` / `medium` / `heavy` 到 `Haptics.ImpactFeedbackStyle` 的映射、真实 `Haptics.impactAsync(...)` 调用和成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/haptics.ts``dispatch.ts` 只负责把完整 request 委托给 `runMobileHostBridgeHapticsImpact(...)`,不得直接导入 `expo-haptics`、读取 `HapticsImpactPayload`、调用 `Haptics.impactAsync` 或包装触觉反馈成功响应。移动壳配置检查会覆盖该模块结构、共享 style 边界和 dispatch 委托关系,避免触觉反馈能力散落到分发层。
- 2026-06-20 移动触觉反馈单测边界:`apps/mobile-shell/src/host-bridge/haptics.test.ts` 直接覆盖 `haptics.impact` 的 `light` / `medium` / `heavy` 到 Expo Haptics style 映射、缺省 `light`、未知 style 不触发设备反馈、HostBridge 成功响应和 `invalid_request` 失败包装;根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,避免触觉反馈边界只靠完整 HostBridge bridge 流程间接覆盖。
- 2026-06-18 分享卡图片导出:新增 `file.exportImage` HostBridge capabilityH5 分享卡下载在 native app 中优先把 canvas 生成的 base64 图片交给宿主导出;Expo 壳写缓存图片后交给系统分享 / 保存面板,Tauri 壳通过系统保存对话框写入图片字节。该能力只接受 `image/png` / `image/jpeg` / `image/webp`、单次 5 MiB 内图片数据,成功只返回文件名和字节数,不暴露本机绝对路径;宿主未声明时保留浏览器下载。Expo 图片导出的 payload 校验、缓存写入、系统分享 / 保存面板和 HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/files.ts``dispatch.ts` 只委托文件模块。
- 2026-06-18 应用角标能力:新增 `app.setBadgeCount` HostBridge capabilityH5 只传 `0` 到共享契约 `HOST_BRIDGE_BADGE_COUNT_MAX` 之间的整数并在宿主未声明时静默 fallback;Expo 壳只在 iOS 声明,并通过 Expo Notifications 查询 / 请求 `allowBadge` 权限后调用 `setBadgeCountAsync(count)`,只有系统返回 `true` 才报告成功,Android 不声明、不返回成功;Tauri 壳通过主窗口 `set_badge_count` 设置任务栏角标,底层平台不支持时返回真实错误。
- 2026-06-18 草稿生成未读角标:平台壳层把“可见作品架里未读的草稿生成完成更新”同步到 `app.setBadgeCount`;同一草稿的 work/profile/session 等多个恢复 ID 只计 1,已读、失败、生成中和不可见草稿不计入。该角标只消费已有 HostBridge 能力,宿主不支持或设置失败不影响 H5 红点、作品架或后端状态。
- 2026-06-19 原生壳角标边界:Expo `app.setBadgeCount` 的 iOS 平台判定、共享上限校验、badge 权限确认、`Notifications.setBadgeCountAsync(count)` 返回值校验和 HostBridge 成功 / 失败响应映射统一收口在 `apps/mobile-shell/src/host-bridge/badge.ts`Tauri `app.setBadgeCount` 的 payload 校验、清除语义、主窗口 `set_badge_count` 调用和 HostBridge 响应映射统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/badge.rs`。两端 `dispatch` 只负责委托对应 badge 模块,配置检查会拒绝分发层直接导入角标底层 API、重声明数量边界或包装角标成功响应。
- 2026-06-18 宿主外观只读查询:新增 `appearance.getColorScheme` HostBridge capabilityExpo 壳通过 React Native `Appearance.getColorScheme()` 读取系统配色,Tauri 壳通过主窗口 `theme()` 读取窗口主题;该能力只返回 `light` / `dark` / `unknown`,不设置 H5 主题、不覆盖系统主题,也不作为强制 UI 样式入口。
- 2026-06-19 原生壳外观查询边界:Expo `appearance.getColorScheme` 的系统配色读取、HostBridge 配色归一和成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/appearance.ts`Tauri `appearance.getColorScheme` 的主窗口 `theme()` 读取、`light / dark / unknown` 映射和 HostBridge 响应包装统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs`。两端 `dispatch` 只负责委托对应 appearance 模块,配置检查会拒绝分发层直接读取系统配色、窗口主题或包装外观查询成功响应。
- 2026-06-18 原生壳生命周期事件:新增 `app.lifecycle` HostBridge capabilityExpo 壳通过 React Native `AppState` 派发 `active` / `inactive` / `background`Tauri 壳通过主窗口 focus / blur、托盘隐藏 / 恢复和页面加载重放派发统一状态;桌面隐藏到托盘或最小化都归一为 `background``hidden`、`minimized`、`focused`、`blurred` 只进入 `nativeState` 便于排障,不扩展共享 `state`。两端都声明 `host.events` 表示事件通过 HostBridge message 注入,但不把它作为 request method,也不开放 Tauri event 插件或 React Native 私有事件 API。H5 只通过 `subscribeHostAppLifecycle()` 订阅统一状态,后续游戏循环、音频和轮询暂停 / 恢复不得直接依赖 Expo / Tauri 平台细节。
- 2026-06-18 原生壳网络状态:新增 `network.status` 与 `network.statusChanged` HostBridge capabilityExpo 壳通过 `expo-network` 查询和订阅真实系统网络状态;Tauri 壳只声明 `network.status`,从 `WEB_APP_ORIGIN` 解析主站 host / port 后做短超时 TCP 可达性查询,暂不声明 `network.statusChanged`,避免把 WebView `online` / `offline` 当作桌面 Rust 网络事实。H5 统一使用 `getHostNetworkStatus()` / `subscribeHostNetworkStatusChange()`,不得直接读取 Expo / Tauri 私有网络 API。
- 2026-06-19 移动壳本地通知边界:Expo `notification.showLocal` 的 payload 归一、权限确认、iOS 仅 alert 且不请求 badge/sound、Android 固定 channel、即时调度、通知 handler 和成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/notifications.ts``dispatch.ts` 只负责把 HostBridge request 委托给 `showMobileHostBridgeLocalNotification(...)`,不得直接调用 `expo-notifications` 调度或权限 API,不得重新做通知 payload 归一,也不得包装本地通知成功响应。移动壳配置检查会覆盖该模块结构、固定 channel、即时调度形态和 dispatch 委托关系,避免后续混入远程推送、后台推送或散落的本地通知实现。
- 2026-06-18 移动壳 WebView 状态重放:Expo WebView 每次同源主站页面成功加载后都会补发当前 `app.lifecycle` 与 `network.statusChanged` 状态,覆盖首载、受控刷新、H5 刷新和系统回收 WebView 进程后的新 JS 上下文;补发不新增 HostBridge capability,也不向 `about:blank`、外域或错误页注入宿主状态。
- 2026-06-20 移动壳加载失败边界:Expo 壳 `onError` / `onHttpError` 只对同源 H5 主页面展示原生失败兜底层,外域、`about:blank`、危险协议、favicon 和非当前主页面资源失败不得触发兜底。兜底层可以用完整 URL 判断是否属于当前主文档,但返回给 UI 的 `url` 只保留 `origin + pathname``detail` 只使用稳定文案,不展示 query、hash 或系统原生 description。移动壳配置检查会反查 `loadFailure.test.ts` 的同源、favicon、当前主页面、URL 脱敏和稳定文案测试,避免错误页策略漂移或泄露 H5 运行态上下文。
- 2026-06-18 桌面壳 WebView 状态重放:Tauri 主 WebView 每次页面加载完成后都会回放当前 `app.lifecycle`,覆盖托盘刷新、`app.reloadWebView` 和 H5 自刷新后的新 JS 上下文;桌面 runtime 同步声明 `host.events` 表示生命周期、返回栈和拖拽图片事件通道可用,但桌面暂不声明 `network.statusChanged`,仍不开放 Tauri event 插件或额外 command。
- 2026-06-18 桌面壳 H5 返回栈事件:Tauri 壳开始声明 `navigation.canGoBack`,但只通过固定注入脚本追踪当前 H5 文档内的 `pushState` / `replaceState` / `popstate` 路由栈并派发 HostBridge event;不把该能力实现为 request method,不开放 H5 到 Tauri 的 event 写入通道,也不声明跨文档 native back-forward list 真相。
- 2026-06-18 移动壳 H5 返回栈事件:Expo 壳开始用固定 WebView 注入脚本追踪当前 H5 文档内的 `pushState` / `replaceState` / `popstate` 路由栈,并通过内部 `genarrative.mobile.historyState` 消息回传给壳层;壳层把该状态与 `react-native-webview` 原生 `canGoBack` 合成为 HostBridge `navigation.canGoBack` 事件。Android 返回键优先回退 H5 当前文档路由栈,H5 不可回退时才走 WebView 原生 `goBack()`;该内部消息不是 HostBridge request method,不开放通用 H5 -> 原生事件通道,外域 / 危险页面消息仍在进入 HostBridge 前丢弃。
- 2026-06-18 外部生成队列轮询接入宿主网络状态:H5 新增 `useHostNetworkOnline()`,宿主未声明网络能力时按在线处理以保持浏览器和旧壳行为;宿主明确 `isConnected=false` 或 `isInternetReachable=false` 时,平台外部生成队列概览暂停 HTTP 轮询,恢复在线后重新刷新。该能力只减少离线请求,不改变外部生成队列、作品架、弹窗或后端任务状态事实。
- 2026-06-18 桌面图片导入:新增 `file.importImage` 与 `file.imageDropped` HostBridge capabilityTauri 壳通过系统文件选择框和主窗口拖拽事件读取用户选择 / 拖入的真实图片,只允许 `image/png`、`image/jpeg`、`image/webp` 且单次不超过 10 MiBH5 统一使用 `importHostImageFile()` / `subscribeHostImageDrop()`,宿主只回传文件名、MIME、base64 内容、字节数和可选坐标,不暴露本地绝对路径,也不开放通用文件系统。拖入目录、文本、损坏图片或没有任何有效图片时不向 H5 派发 `file.imageDropped` payload,桌面壳配置门禁会反查该单测边界。
- 2026-06-18 移动图片导入:Expo 壳开始声明并实现 `file.importImage`,通过 `expo-image-picker` 请求相册权限并打开系统相册选择器,只允许 `image/png`、`image/jpeg`、`image/webp` 且单次不超过 10 MiB;picker 调用必须固定为单选、禁用编辑、禁用 EXIF、请求 base64 且 `mediaTypes` 只允许 `images`,不得扩大到视频或任意媒体。成功只回传清洗后的文件名、MIME、base64 内容和字节数,不暴露设备本地 URI,用户取消返回 `cancelled` 并由 H5 facade 归为 `false`。Expo 图片导入的相册权限、ImagePicker 调用、MIME / 体积 / 图片字节校验和 HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/files.ts``dispatch.ts` 只委托文件模块。
- 2026-06-18 移动图片拍摄导入:Expo 壳新增 `file.captureImage` HostBridge capability,通过 `expo-image-picker` 请求相机权限并打开系统相机拍摄图片,沿用 `file.importImage` 的 MIME、体积、base64 和文件名清洗规则;相机 picker 必须禁用编辑、禁用 EXIF、请求 base64 且 `mediaTypes` 只允许 `images`,成功回传 `action=captured`,不暴露设备本地 URI;该拍摄能力不使用麦克风权限,移动壳麦克风权限只服务同源 H5 实时玩法。Tauri 壳不声明该能力,不伪造桌面拍摄。Expo 图片拍摄的相机权限、ImagePicker 调用、MIME / 体积 / 图片字节校验和 HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/files.ts``dispatch.ts` 只委托文件模块。
- 2026-06-19 移动二维码扫描:Expo 壳新增 `scanner.scanQrCode` HostBridge capability,通过 `expo-camera` 请求相机权限并打开真实扫码 overlay,成功只返回共享契约清洗后的二维码文本和 `qr_code` 格式,空值、控制字符和超长文本按 `normalizeHostBridgeQrCodeValue` 处理;HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/scanner.ts``dispatch.ts` 只委托扫码请求。用户关闭扫码返回 `cancelled`,H5 个人中心扫码入口不再连带弹出浏览器摄像头权限。Tauri 桌面壳只把 `scanner.scanQrCode` 保留在 method 白名单中返回 `unsupported_method`,不声明 capability、不伪造桌面扫码;宿主缺能力或非法结果时 H5 继续走原浏览器扫码 fallback。
- 2026-06-20 移动壳扫码单测边界:`apps/mobile-shell/src/host-bridge/scanner.test.ts` 直接覆盖扫码 helper 的订阅状态初始值、进行中 requestKey、并发扫码拒绝、非法完成不清 pending、成功完成的二维码值清洗、用户取消 `cancelled`、宿主失败 `host_error`、无 pending 时的空操作和 HostBridge 成功响应包装;根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,避免扫码状态机只靠完整 bridge 流程或 overlay 测试间接覆盖。
- 2026-06-18 H5 图片上传接入宿主导入:`CreativeImageInputPanel` 在 `native_app` 且声明 `file.importImage` / `file.captureImage` 时,主图上传和描述参考图上传可分别调用 `importHostImageFile()` / `captureHostImageFile()`,并把宿主返回的 base64 图片转换为现有 `File` 回调;浏览器、小程序和未声明能力的裁剪壳继续走原生 `<input type="file">` 路径,不新增玩法侧上传分叉。
- 2026-06-18 移动壳安全区:Expo 壳根布局使用 `react-native-safe-area-context` 的 `SafeAreaProvider` 与四边 `SafeAreaView` 保护 WebView,避免 H5 主站内容贴进 iOS 刘海、底部 Home Indicator、Android 状态栏或横屏边缘;该能力属于宿主壳布局保护,不新增 H5 占位 UI,不改变玩法 runtime 或 HostBridge capability。`safeArea.test.ts` 必须证明 top / right / bottom / left 四边固定覆盖,移动壳配置检查会反查该测试边界。
- 2026-06-18 移动壳方向策略:Expo 壳 `orientation` 固定为 `default`,不锁竖屏或横屏;后续固定玩法和 AI H5 sandbox 的方向需求由设备方向、H5 响应式布局和玩法自身画布适配承接,壳层只负责安全区、WebView 容器和 HostBridge。移动壳配置检查和 Expo public config smoke 会拒绝重新锁定 portrait / landscape。
- 2026-06-18 移动壳键盘布局:Expo Android 壳 `softwareKeyboardLayoutMode` 固定为 `resize`,让系统键盘打开时真实调整 WebView 可视高度;H5 继续使用已有 viewport / 输入法聚焦适配承接创作表单、聊天输入和玩法输入框,壳层不新增键盘遮挡补偿 UI、不伪造键盘状态。移动壳配置检查和 Expo public config smoke 会拒绝该字段缺失或漂移。
- 2026-06-18 移动壳媒体策略:Expo WebView 允许内联媒体播放和用户触发的全屏视频,但保留 `mediaPlaybackRequiresUserAction`,不允许无手势自动播放;固定玩法和 AI H5 sandbox 的音频仍由 H5 用户开关、运行态状态和宿主生命周期控制,壳层不注入额外播放器或假播放状态。移动壳配置检查会拒绝 WebView 媒体策略漂移。
- 2026-06-18 移动壳启动 URL 归一:Expo 壳的 `EXPO_PUBLIC_GENARRATIVE_WEB_URL` 和 deep link 基准地址只接受生产主站 `https://www.genarrative.world`,以及本机开发联调 `http://127.0.0.1`、`http://localhost`、`http://[::1]`;空值、相对路径、外域、`file:`、`javascript:` 等非法配置回退到默认 H5 地址后再附加 `native_app` 宿主上下文;deep link 仍只映射归一后基准 origin 的 H5 路径,禁止把外域或危险协议页面装进带完整 HostBridge 的 WebView。
- 2026-06-18 移动壳主动导航上下文:Expo 壳的 `navigation.openNativePage` 与 deep link 都必须复用 `buildMobileShellUrl(...)` 补写 `native_app`、`expo_mobile`、真实平台、版本和 capability 清单;受控导航只接受当前允许 origin 的同源 H5 URL。移动壳配置检查会拒绝主动导航或 deep link 绕过该宿主上下文构造入口。
- 2026-06-20 移动壳导航单测边界:`apps/mobile-shell/src/host-bridge/navigation.test.ts` 直接覆盖 `app.openExternalUrl` 的共享外链 helper 调用、危险 URL 拒绝、系统不能打开时的 `host_error`,以及 `navigation.openNativePage` 的同源 H5 跳转、宿主上下文补写、缺失 navigation adapter 的 unsupported 语义和 `app.reloadWebView` adapter 调用;根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,避免宿主导航边界只靠 WebView shell 测试或完整 bridge 流程间接覆盖。
- 2026-06-18 移动壳协议常量来源:Expo 壳的 HostBridge 事件注入、入口 URL `bridgeVersion`、`host.getRuntime` 回包和 Expo public config smoke 必须使用 `packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_PROTOCOL` / `HOST_BRIDGE_VERSION`,不得在壳层重新写死协议名或版本字面量;配置检查会拒绝这些常量漂移。
- 2026-06-19 公开 Web origin 单一来源:原生壳允许加载 / 分享 / 跳转的公开 H5 主站 origin 以 `packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_PUBLIC_WEB_ORIGIN` / `HOST_BRIDGE_PUBLIC_WEB_URL` 为源;Expo 移动壳只能通过 `DEFAULT_MOBILE_SHELL_WEB_URL` / `ALLOWED_PRODUCTION_WEB_ORIGIN` 语义别名引用共享常量,Tauri 桌面壳 `WEB_APP_ORIGIN` 作为 Rust 运行时镜像常量必须由 `apps/desktop-shell/scripts/check-config.mjs` 反查同一共享值。两端不得在分享、WebView policy、启动 URL 或桌面导航里另行复刻 `https://www.genarrative.world` 作为独立真相。
- 2026-06-18 桌面壳协议常量来源:Tauri Rust 侧 `host_bridge/protocol.rs` 的 `HOST_BRIDGE_PROTOCOL` / `HOST_BRIDGE_VERSION`、桌面入口 URL `bridgeVersion`、HostBridge event 注入和 runtime 回包必须与 `packages/shared/src/contracts/hostBridge.ts` 保持一致;桌面配置检查会反查共享契约并拒绝协议名或协议版本漂移。`tauri.conf.json` 只保留基础入口,`shell/url.rs` 统一补写桌面宿主上下文和真实 capability 清单,配置检查会拒绝把 `hostCapabilities` 等宿主 query 长串重新写回 Tauri 配置。
- 2026-06-18 移动壳默认入口:Expo 壳默认 H5 地址固定为 `https://www.genarrative.world/`,开发联调本机 Vite 必须显式设置 `EXPO_PUBLIC_GENARRATIVE_WEB_URL=http://127.0.0.1:3000/`、`http://localhost:3000/` 或 `http://[::1]:3000/`;生产包不得在未配置环境变量时加载设备本机 localhost,也不得通过环境变量把第三方外域 H5 放入带完整 HostBridge 的 WebView。
- 2026-06-18 移动壳安装包身份:Expo 移动壳的 iOS bundle identifier 与 Android package 统一固定为 `world.genarrative.mobile`,应用版本固定为 `0.1.0`iOS `buildNumber` 从字符串 `"1"` 起步,Android `versionCode` 从整数 `1` 起步;后续分发安装包时递增构建号 / versionCode,产品版本号按发布节奏调整。移动壳配置检查会校验 `app.json` 与 `package.json` 版本一致,并拒绝缺失或漂移的包标识,当前不写入假商店元数据、假更新端点或占位渠道 SDK 配置。
- 2026-06-19 移动壳 HostBridge 版本运行时来源:Expo 移动壳的 H5 入口 query 和 `host.getRuntime` 回包都读取 `MOBILE_SHELL_HOST_VERSION`,该值必须从移动壳 `app.json` 的 Expo `version` 配置解析,异常配置只回退到与 `app.json` / `package.json` 一致的受检 fallback;配置检查会拒绝 `App.tsx`、`bridge.ts` 或 `runtime.ts` 重新散落硬编码版本,避免安装包版本升级时 H5 首屏上下文与 runtime 回读分叉。
- 2026-06-19 移动壳 runtime 桥接边界:Expo `host.getRuntime` 的平台归一、hostVersion、bridgeVersion、capability 清单组装和 HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/runtime.ts``dispatch.ts` 只负责把 `host.getRuntime` 委托给 `getMobileHostBridgeRuntimeResponse(...)`。移动壳配置检查会拒绝分发层重新读取 `MOBILE_SHELL_HOST_VERSION`、`HOST_BRIDGE_VERSION`、`resolveMobileHostCapabilities(...)`、`Platform.OS` 或包装 runtime 成功响应,避免入口 URL、runtime 回包和能力清单继续分叉。
- 2026-06-19 桌面壳 runtime 桥接边界:Tauri `host.getRuntime` 的平台归一、hostVersion、bridgeVersion、capability 清单组装和 HostBridge 成功响应包装统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/runtime.rs``dispatch.rs` 只负责把 `host.getRuntime` 委托给 `desktop_host_bridge_runtime_response(&request)`。桌面壳配置检查会拒绝分发层重新读取 `env!("CARGO_PKG_VERSION")`、`HOST_BRIDGE_VERSION`、`capabilities()`、`desktop_platform()` 或包装 runtime 成功响应,避免入口 URL、runtime 回包和能力清单继续分叉。
- 2026-06-18 移动壳发布通道边界:Expo 移动壳默认显式关闭 OTA 更新,只允许 `updates.enabled=false`;在真实发布通道、更新端点、签名 / 回滚策略和团队发布流程落地前,不得配置 `runtimeVersion`、release channel、EAS channel、`expo-updates` 插件或移动端 crash / analytics / CodePush 依赖。移动壳配置检查和 Expo public config smoke 会拒绝这些发布通道能力被提前打开,根 `package-lock.json` 也不得解析 `expo-updates`、Sentry、Firebase Analytics、PostHog、Amplitude、Segment、CodePush 等真实发布 / 观测 SDK。
- 2026-06-18 移动壳观测与渠道 SDK 初始化边界:移动壳生产入口、HostBridge、启动 URL 和 runtime 配置不得提前初始化 Sentry、Firebase Analytics、PostHog、Amplitude、Segment、CodePush 或 Expo Updates;这些 SDK 必须等真实发布通道、采集字段、用户授权、隐私披露、签名 / 回滚策略和团队发布流程落地后逐项接入。配置检查会同时拒绝相关依赖、锁文件解析、Expo 配置和源码初始化片段;`expo-application` 可能由 Expo 自身传递解析,但项目不得主动 direct 依赖它实现渠道逻辑。
- 2026-06-18 桌面图片拖入接入主图槽位:`CreativeImageInputPanel` 在桌面壳声明 `file.imageDropped` 时订阅宿主拖入事件,只在拖入坐标命中当前主图卡片且未被上层元素遮挡时消费事件,避免窗口级拖入被多个创作面板同时接收;成功后仍转换为现有 `File` 上传回调。
- 2026-06-18 H5 背景音乐接入宿主生命周期:`useBackgroundMusic` 通过 `useHostLifecycleActive()` 消费 `subscribeHostAppLifecycle()` 的归一结果,宿主进入后台、inactive 或桌面窗口失焦时降低音量并暂停音频循环,同时 `suspend` WebAudio context;回到 `active + focused` 且用户原本开启音乐时再恢复播放,不改变用户音量设置。
- 2026-06-18 固定玩法音频接入宿主生命周期:前端新增 `useHostLifecycleActive()` 统一消费 `subscribeHostAppLifecycle()``useBackgroundMusic`、拼图运行态和抓大鹅运行态都只依赖该归一状态判断音频可播放性;宿主 inactive、background 或窗口失焦时暂停 `<audio>` / WebAudio,回到 `active + focused` 后仅在运行态仍在播放、音源存在且用户音乐音量大于 0 时恢复,不改变用户音量设置。
- 2026-06-18 本地通知能力:新增 `notification.showLocal` HostBridge capabilityH5 只能传必填 `title` 和可选 `body`,共享契约负责修剪、折叠普通空白、限制长度并拒绝控制字符;Expo 壳通过 `expo-notifications` 请求系统通知权限、创建 Android 本地 channel 并发送即时本地通知,Android channel id 由共享契约 `HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID` 固定,Tauri 壳通过 Rust 侧 `tauri-plugin-notification` 发送系统通知且不开放插件 JS guest API。该能力不包含远程推送、token 注册、定时提醒或后台远程通知,权限拒绝、系统失败或宿主未声明时由 H5 视作失败并继续主流程。
- 2026-06-18 移动壳通知权限边界:Expo 移动壳的 Android 包配置必须显式声明 `POST_NOTIFICATIONS`,并阻断 `RECEIVE_BOOT_COMPLETED`、`SCHEDULE_EXACT_ALARM` 和 `USE_EXACT_ALARM`,只保留即时本地通知所需权限和前台展示 handler;移动壳源码不得调用 Expo push token、设备 push token、push token listener、通知响应跳转 listener、定时 / 周期通知 API 或 `seconds` / `repeats` / `timeInterval` / `date` / `calendar` / `daily` / `weekly` / `monthly` / `yearly` 触发字段。`Notifications.scheduleNotificationAsync` 只能保留 iOS / 默认 `trigger: null` 和 Android 使用共享 channel id 的即时通知结构;配置检查和 Expo public config smoke 会拒绝这些权限或远程 / 后台 / 定时通知流程被重新打开。
- 2026-06-18 草稿生成完成 / 失败通知:平台壳层的 `markDraftReady` / `markDraftFailed` 统一收口会在原生壳声明 `notification.showLocal` 时请求即时本地通知;通知 payload 只包含生成完成 / 失败标题和草稿来源正文,按草稿来源去重,同一草稿重新进入生成中后才允许再次通知。该能力不替代现有完成 / 错误弹窗、作品架红点、队列概览或后端状态回读,通知失败不阻断主流程。根级原生壳门禁必须覆盖平台壳同步层通过真实 HostBridge transport 发出 `notification.showLocal`,避免只测模型文案。
- 2026-06-19 草稿生成 HostBridge 消费门禁:`PlatformEntryFlowShellImpl` 只负责派生草稿通知和未读数量,实际宿主同步经 `platformHostBridgeSync.ts` 调用 `showHostLocalNotification` / `setHostAppBadgeCount``npm run check:native-shells` 必须运行该同步层的真实 Tauri transport 测试,并继续覆盖通知模型、未读计数模型、音频导入和文档导入等 H5 HostBridge 消费测试。
- 2026-06-18 剪贴板读取能力:新增 `clipboard.readText` HostBridge capabilityH5 只能读取纯文本结果,契约限制返回文本最多 100000 字符;Expo 壳通过 `expo-clipboard` 读取系统剪贴板文本,Tauri 壳通过 Rust 侧 `tauri-plugin-clipboard-manager` 读取文本且不开放插件 JS guest API。该能力不读取图片、HTML、文件列表或剪贴板监听事件,宿主未声明或读取失败时由 H5 视作失败并保留原流程。
- 2026-06-19 移动壳剪贴板边界:Expo `clipboard.writeText` / `clipboard.readText` 的系统剪贴板读写、共享 100000 字符归一、payload 校验和 HostBridge 成功 / 失败响应边界统一收口在 `apps/mobile-shell/src/host-bridge/clipboard.ts``dispatch.ts` 只负责把 HostBridge request 委托给 `writeMobileHostBridgeClipboardText(request)` / `readMobileHostBridgeClipboardText(request)`,不得直接导入 `expo-clipboard`、调用 `Clipboard.setStringAsync` / `Clipboard.getStringAsync` 或包装剪贴板成功响应。移动壳配置检查会覆盖该模块结构、共享文本边界和 dispatch 委托关系,避免剪贴板能力散落到分发层。
- 2026-06-20 移动剪贴板单测边界:`apps/mobile-shell/src/host-bridge/clipboard.test.ts` 直接覆盖 Expo `clipboard.writeText` / `clipboard.readText` 的共享文本归一、100000 字符截断、非字符串写入拒绝、空字符串纯文本读写、不可用读取返回 `host_error`、系统剪贴板读写调用和 HostBridge 成功 / 失败响应包装;根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,移动壳配置检查会反查不可用读取失败语义,避免移动剪贴板边界只靠完整 HostBridge bridge 流程间接覆盖或把底层不可用值伪装成成功空文本。
- 2026-06-19 桌面壳剪贴板边界:Tauri `clipboard.writeText` / `clipboard.readText` 的系统剪贴板读写、共享 100000 字符归一、payload 校验和响应边界统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs``dispatch.rs` 只负责把 HostBridge request 委托给 `write_desktop_host_bridge_clipboard_text(...)` / `read_desktop_host_bridge_clipboard_text(...)`,不得直接承接剪贴板文本截断、payload 解析或插件读写细节;桌面 share fallback 仍可调用底层写剪贴板函数复制分享文本。桌面壳配置检查会覆盖该模块结构、共享文本边界和 dispatch 委托关系,避免剪贴板能力散落到分发层。
- 2026-06-19 桌面壳本地通知边界:Tauri `notification.showLocal` 的 payload 清洗、权限状态检查、prompt 权限请求和系统通知发送统一收口在 `apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs``dispatch.rs` 只负责把 HostBridge request 委托给 `show_desktop_local_notification(...)` 并映射响应,不得直接调用 `app.notification()`、`NotificationExt` 或 `PermissionState`。桌面壳配置检查会覆盖该模块结构、权限语义和 dispatch 委托关系,避免本地通知能力散落到分发层。
- 2026-06-18 文本文件导入能力:新增 `file.importText` HostBridge capabilityH5 统一通过 `importHostTextFile()` 读取宿主返回的纯文本内容;Expo 壳通过 `expo-document-picker` 打开系统文档选择器,Tauri 壳通过系统文件选择框读取真实文本文件。两端只接受 `text/plain`、`text/markdown`、`text/csv`、`application/json` 或对应扩展名,单次不超过 5 MiB,成功只返回清洗后的文件名、MIME、UTF-8 文本内容和字节数,不暴露设备 URI / 本机绝对路径,也不开放通用文件系统。Expo 文本导入的 DocumentPicker 调用、大小校验、文本读取和 HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/files.ts``dispatch.ts` 只委托文件模块。
- 2026-06-19 文档文件导入能力:新增 `file.importDocument` HostBridge capability,作为创作 Agent 工作台优先导入路径;Expo 壳通过 DocumentPicker、Tauri 壳通过系统文件选择框读取文本类文档或 DOCX 副本。两端只接受文本 MIME / DOCX MIME 或对应扩展名,单次不超过 5 MiB,成功只返回清洗后的文件名、MIME、base64 内容和字节数,不暴露设备 URI、本机绝对路径,也不开放通用文件系统;H5 把返回内容转换成浏览器 `File` 后继续走后端 `/api/runtime/creation-agent/document-inputs/parse`,不在前端解析 DOCX。旧壳只声明 `file.importText` 时继续使用文本导入兜底。Expo 文档导入的 DocumentPicker 调用、大小校验、base64 读取和 HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/files.ts``dispatch.ts` 只委托文件模块。
- 2026-06-18 Tauri 系统托盘:桌面壳启用真实 OS 托盘并复用品牌图标,托盘菜单只执行显示主窗口、刷新主窗口和退出应用,左键点击托盘图标恢复并聚焦主窗口;该能力归桌面壳自身,不进入 HostBridge capability,不向 H5 暴露托盘、菜单、shell 或任意窗口控制 API。托盘注册成功时主窗口关闭按钮只隐藏到托盘,必须通过托盘“退出”结束应用;托盘注册失败不得阻断主窗口启动,也不得拦截关闭,避免窗口消失后无法恢复。`check:native-shells` 和 Tauri cargo test 覆盖托盘配置、菜单动作映射和关闭策略。
- 2026-06-18 Tauri 单实例:桌面壳启用 `tauri-plugin-single-instance` 并要求该插件最先注册;重复启动 App 时第二实例退出,只唤醒、取消最小化并聚焦已有主窗口,不把第二实例 argv / cwd 作为事件透传给 H5。Windows / Linux 的二次实例深链只通过 single-instance 的 `deep-link` feature 交给 Tauri deep-link 插件,再由 `shell/deep_link.rs` 做受控 URL 归一。
- 2026-06-18 Tauri 桌面深链:桌面壳启用 `tauri-plugin-deep-link`,但不安装 JS guest 包、不把 deep-link command 加入主窗口 capability,也不新增 HostBridge capability。Tauri 配置只注册 `genarrative` schemeRust 层只接受 `genarrative://open/...`、`genarrative://app/...`、`genarrative://<path>` 和 `https://www.genarrative.world/...`,统一跳转到同源 H5 并补写 `native_app`、`tauri_desktop`、当前平台、版本和真实 capability 清单;外域、明文协议和危险协议不进入主 WebView。
- 2026-06-18 桌面壳安装包身份:Tauri 桌面壳的产品名固定为 `Genarrative`,应用 identifier 固定为 `world.genarrative.desktop`Tauri 配置、`apps/desktop-shell/package.json` 与 Cargo package 版本统一为 `0.1.0`Release 主窗口只加载共享公开主站 `https://www.genarrative.world/`,不得配置 `frontendDist` 打包根 H5 资产;dev URL 只指向本机 Vite 调试入口。桌面壳 CSP 保持 `script-src 'self'`,不得加入 `unsafe-eval`、`tauri:` 或 `file:`,也不得在没有真实端点、签名密钥和发布流程前配置 updater;检查脚本会拒绝包身份、版本、CSP 或 updater 约束漂移。
- 2026-06-18 桌面壳观测与渠道 SDK 边界:Tauri 桌面壳默认不接入崩溃上报、analytics、遥测日志、自动更新或渠道分发 SDKSentry、Datadog、PostHog、Segment、Amplitude、Bugsnag、OpenTelemetry、Tauri log / updater 等 Node / Cargo 依赖、`package-lock.json` / `Cargo.lock` 解析包和 Rust 初始化片段都会被配置检查拒绝。后续只有在真实端点、采集字段、用户授权、隐私披露、签名和发布流程确定后,才能按单项能力更新方案并接入。
- 2026-06-18 桌面壳 HostBridge 版本边界:Tauri release / dev 入口 URL 的 `hostVersion` 由 Rust `shell/url.rs` 从 Cargo package 版本统一补写;`host.getRuntime` 回包继续使用 `env!("CARGO_PKG_VERSION")`,配置检查会确认 `tauri.conf.json`、`apps/desktop-shell/package.json` 和 Cargo package 版本一致,并拒绝在 Tauri 配置里手写入口 query 版本。
- 2026-06-18 桌面壳运行时平台 queryTauri 静态配置不再写入 `hostPlatform` 或其它宿主上下文 queryRust `setup` 手动创建主窗口前必须把基础入口改写为当前 `macos` / `windows` / `linux` 平台和完整宿主上下文,保证 H5 首屏 query 与 `host.getRuntime` 回读的平台一致。第二实例参数、外部 deep link 或 H5 自报值不得覆盖该字段;桌面壳测试和配置检查会拒绝绕过该归一流程。
- 2026-06-20 桌面入口 URL 宿主上下文清洗:`desktop_entry_url_with_host_context(...)` 对 dev URL 和打包入口补写宿主上下文前必须先移除旧 `clientRuntime`、`hostShell`、`hostCapabilities` 等宿主 query,再追加当前 Tauri 壳真实上下文;Rust 单测和桌面配置检查反查旧 query 不会在首屏入口中重复或覆盖当前壳身份。
- 2026-06-18 桌面壳顶层导航边界:Tauri 主 WebView 只允许打包资产 URL 和 `https://www.genarrative.world` 同源 H5 route 留在主窗口;外域 `http:` / `https:`、`mailto:`、`tel:` 导航与 `window.open` 请求交给系统 opener 后拒绝 WebView 留壳;`javascript:`、`file:` 等危险协议直接拒绝。该规则不进入 HostBridge capability,不开放 opener JS guest API,配置检查和 cargo test 覆盖导航策略。
- 2026-06-18 桌面壳默认下载边界:Tauri 主 WebView 的下载事件默认拒绝网页自动下载和 `<a download>` 落盘,桌面文件保存只能通过 `file.exportText`、`file.exportImage`、`file.exportAudio` 等已声明 HostBridge method 进入 Rust 侧系统保存对话框,并继续执行 MIME、大小、文件名清洗和用户确认。该规则不进入 HostBridge capability,配置检查和 cargo test 覆盖下载拒绝策略。
- 2026-06-18 桌面壳文件 bytes 校验:Tauri 图片 / 音频导入导出不得只信扩展名或 H5 声明 MIME;Rust 侧必须识别 PNG / JPEG / WebP、MP3 / MP4-M4A / WAV / OGG / WebM bytes 头部,要求导入文件扩展名对应 MIME 与真实 bytes 匹配,导出 payload 的 `mimeType` 与 `base64Data` 解码 bytes 匹配。不匹配返回 `invalid_request`,继续不暴露本机绝对路径或通用文件系统能力。配置检查和 cargo test 覆盖该边界。
- 2026-06-18 移动壳文件 bytes 校验:Expo 图片 / 音频导入导出不得只信系统 picker 返回 MIME、文件扩展名或 H5 声明 MIME;移动壳必须识别 PNG / JPEG / WebP、MP3 / MP4-M4A / WAV / OGG / WebM base64 bytes 头部,要求导入 MIME 归一结果与真实 bytes 匹配,导出 payload 的 `mimeType` 与 `base64Data` 解码 bytes 匹配。不匹配返回 `invalid_request`,不会写入缓存文件、调起系统分享或把内容回传给 H5。移动图片导出还必须按 MIME 给系统分享 / 保存面板补齐 `.png` / `.jpg` / `.webp` 文件名扩展,避免缓存文件名与真实图片类型漂移。配置检查和移动壳测试覆盖该边界。
- 2026-06-18 桌面壳 DevTools 边界:Tauri 主 WebView 配置必须显式 `devtools=false`Cargo 依赖不得启用 Tauri `devtools` feature;桌面壳本地调试走普通浏览器和 Vite,不把 debug / release 桌面包变成可打开浏览器检查器的调试容器。配置检查会拒绝主窗口 DevTools 或 release feature 被重新打开。
- 2026-06-18 桌面壳 Tauri 命令白名单:桌面壳源码、Tauri build manifest、主窗口 capability 和本地自动生成权限目录都只能暴露 `host_bridge_request` 一个受控 command;所有桌面能力继续在 Rust 内部按 HostBridge method 白名单分发,不新增可被 H5 直接 `invoke` 的 Tauri command,也不授予插件 JS guest API。检查脚本会拒绝自动生成权限目录缺失、权限文件集合漂移、多余 command、权限列表顺序漂移和残留的自动生成权限文件。
- 2026-06-18 桌面壳 capability 最小化:Tauri 主窗口 capability 只授予 `allow-host-bridge-request`,不得授予 `core:default`、`core:*:default`、任意 core 子权限或 dialog / fs / notification / opener / clipboard / deep-link / window-state 等插件权限。窗口、菜单、托盘、剪贴板、文件、通知和外链能力只能由 Rust 壳内部调用,再经 `host_bridge_request` 分发。
- 2026-06-18 HostBridge request id replayExpo 和 Tauri 壳都必须按 request id 回放首次完成结果;同 id 进行中的请求共享同一执行结果,已完成请求直接回放缓存响应,避免系统分享、外链、剪贴板、文件选择 / 保存、本地通知、窗口导航等宿主副作用被重复触发。两端配置检查和测试会锁住 replay 结构。
- 2026-06-18 HostBridge request envelope 校验:共享契约提供 `isHostBridgeMethod` 与 `normalizeHostBridgeRequestId`Expo 壳直接复用,Tauri 壳镜像同一白名单和 id 规则;空 id、控制字符 id、超长 id 和未知 method 都必须在 replay / 能力分发前返回 `invalid_request`,已知但当前壳未实现的登录 / 支付等 method 才返回 `unsupported_method`。Expo 壳捕获原生异常时只透传共享 `HostBridgeError.code` 白名单内且 `message` 为字符串的协议错误,Tauri 壳的 `failed(...)` 出口也必须先校验同一错误码白名单;未知原生错误对象或非法错误码统一归一为 `host_error` 和固定失败文案,不把 native 私有字段、任意错误码或非字符串 message 回传给 H5。
- 2026-06-20 桌面 HostBridge command facade 单测边界:Tauri 唯一 `host_bridge_request` command 必须先通过 `prepare_host_bridge_request(...)` 做 envelope、method 和 request id 校验,再进入 `HostBridgeReplayState` reserve / wait / execute`apps/desktop-shell/src-tauri/src/host_bridge/mod.rs` 的单测必须覆盖非法 envelope 在 replay 前返回 `invalid_request` 且不会占用对应 request id 的 replay slot,桌面配置检查会反查该测试存在。
- 2026-06-20 桌面 HostBridge replay 内部失败边界:Tauri `HostBridgeReplayState` 的 cache lock、slot lock 和 condvar wait 异常不得 panic,也不得把 Rust 内部错误细节回传给 H5;桌面壳只写 `desktop host bridge replay failed for ...` 固定阶段标签,不把 mutex / condvar 错误文本写入 stderr,并统一返回 `host_error: desktop host bridge request failed`。桌面配置检查反查 `reserve(...)` 的 `Result` 出口、稳定错误响应、label-only 诊断和 poison lock 单测。
- 2026-06-20 移动 HostBridge runtime 能力回包边界:Expo `host.getRuntime` 回包里的 `capabilities` 必须直接等于共享契约 `HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES` 或 `HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES`,并使用与 `platform` 字段一致的归一平台值选择 profile;移动壳 runtime 单测和配置检查反查精确 profile 断言,避免 H5 实际消费的能力回包与入口 URL 能力 query 或共享 profile 分叉。
- 2026-06-20 移动 production bundle 宿主上下文边界:`apps/mobile-shell/scripts/check-expo-export.mjs` 必须读取 iOS / Android Metro export bundle,确认可分发 bundle metadata 是 Metro version 0、只包含当前平台 `fileMetadata`、指向 Hermes `AppEntry-*.hbc`,且 bundle 包含共享生产 H5 URL、`native_app`、`expo_mobile`、`hostCapabilities`、`hostVersion` 和 `bridgeVersion`,并不包含本机开发 H5 URL;移动壳配置检查反查 export smoke 的这些 token 和 metadata 结构门禁,避免 production bundle 丢失宿主上下文、混入本机入口或导出形态漂移。
- 2026-06-21 移动壳本机 H5 入口边界:`EXPO_PUBLIC_GENARRATIVE_WEB_URL` 只允许生产主站或开发态显式本机 H5 联调地址;`ShellApp` 必须用 `__DEV__` 控制 `allowLocalDevelopment`production runtime 遇到 `127.0.0.1`、`localhost` 或 `::1` 时回退到共享生产主站。`buildMobileShellUrl(...)` 默认不得隐式放行本机入口,Deep Link 和 `navigation.openNativePage` 必须显式传递同一基准 URL 归一选项,避免可分发移动壳被环境变量、deep link 或 HostBridge 导航带到本机调试页面。
- 2026-06-20 桌面 release 主窗口宿主上下文边界:Tauri release 配置只保留基础 `index.html`Rust app 装配层必须在主窗口启动配置单测里断言补齐 `clientRuntime`、`clientType`、`hostShell`、`hostPlatform`、`hostVersion`、`bridgeVersion` 和 `hostCapabilities`;桌面单端配置检查反查这些断言存在,避免首屏 H5 丢失桌面壳运行态 query 后只靠 runtime 回读补救。
- 2026-06-20 移动壳协议 helper 单测边界:`apps/mobile-shell/src/host-bridge/protocol.test.ts` 直接覆盖 Expo 移动壳 HostBridge JSON 解析、envelope 和 request id 校验、未知 method 拒绝、ok / failure 响应包装、unsupported / invalid_request 错误构造,以及 native helper 错误归一时只透传共享错误码与字符串 message,不泄露非法错误码、nativeStack 或其它私有字段;根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,避免协议边界只靠完整 bridge 流程间接覆盖。
- 2026-06-20 移动扫码 overlay 单测边界:`apps/mobile-shell/src/shell/QrScannerOverlay.test.tsx` 直接覆盖移动扫码 overlay 的相机权限请求、二维码扫码成功、权限拒绝失败和关闭取消;单端配置检查会反查该组件测试存在,根级 `npm run check:native-shells` 会把该测试文件列入移动 shell 层结构清单,避免扫码 UI 容器只靠 `ShellApp.test.tsx` 的完整 HostBridge 流程间接覆盖。
- 2026-06-18 HostBridge method 白名单跨壳门禁:`packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_METHODS` 是唯一协议来源;Expo 壳 HostBridge 分发不得处理共享契约外 methodTauri 壳 Rust `HOST_BRIDGE_METHODS` 必须与共享契约逐项一致。新增宿主 method 必须先更新共享契约,再落两端壳实现或明确 unsupported。
- 2026-06-18 HostBridge capability / handler 关系门禁:两端壳声明 request method capability 时必须有对应 HostBridge handler;壳 handler 处理的 method 必须已被该壳声明,登录 / 支付等 SDK-backed method 只能保留明确 `unsupported_method` 路径。事件类 capability 不要求 request handler。
- 2026-06-18 桌面壳 CSP 分层:Tauri release `csp` 不得包含 `http://127.0.0.1:*`、`ws://127.0.0.1:*` 或其它本机调试源,本机 Vite、HMR WebSocket 和开发 frame 只允许出现在 `devCsp`。桌面壳配置检查会同时拒绝 release CSP 混入本机调试源、dev CSP 缺失本机开发源,拒绝 release / dev CSP 加入 `unsafe-eval`、`tauri:` 或 `file:`,并要求两者 `script-src` 精确保持为 `'self'`。
- 2026-06-19 桌面壳 macOS 媒体权限说明:Tauri 桌面壳不新增摄像头 / 麦克风 HostBridge method,但同源 H5 可以继续通过浏览器标准 `getUserMedia` 承接儿童动作热身 Demo 的实时摄像头输入和汪汪声浪正式 runtime 的实时麦克风输入;macOS 分发包必须通过 `bundle.macOS.infoPlist="Info.plist"` 合并 `NSCameraUsageDescription` 与 `NSMicrophoneUsageDescription`,文案只描述同源 H5 实时动作 / 声音玩法。桌面壳配置检查会校验 plist 路径与文案,防止缺少系统授权说明或把媒体权限扩成通用宿主采集能力。
- 2026-06-18 壳生产代码禁用临时替身:微信 / Expo / Tauri 三端壳的生产源码和配置不得出现 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造 / 未实现 / 临时 等脚手架或替身词;测试文件仍可使用 mock。两端原生壳配置检查会扫描生产入口、配置和壳实现,根级 `npm run check:native-shells` 会统一扫描 `miniprogram`、`apps/mobile-shell`、`apps/desktop-shell`、H5 HostBridge transport 和共享 HostBridge 契约生产源码,防止把临时替身、占位文案或伪实现带进可分发壳或真实调用链。
- 2026-06-19 H5 HostBridge 调用链自动扫描:根级 `npm run check:native-shells` 从 `src/` 生产文件自动收集真实宿主能力 facade 的直接消费者,以及 `useHostLifecycleActive`、`useHostNetworkOnline`、`platformProfileHostClipboard` 等薄 wrapper 的消费者。H5 业务文件允许正常表单 `placeholder` 属性、业务占位图文案和真实兼容 / 故障语义中的“未实现”“临时”表述,但不得出现 mock / fake / stub / TODO / FIXME / 模拟 / 伪造等替身痕迹;壳源码和配置仍继续禁用 placeholder / 占位 / 未实现 / 临时。
- 2026-06-18 原生壳本地生成物边界:Expo `.expo/`、Expo export smoke 临时目录、Tauri `target/`、Tauri schema `gen/` 和 Tauri 自动生成权限目录都必须保持 gitignored,不作为生产源码敏感词扫描输入;手写 capability / 权限配置仍在扫描范围内。
- 2026-06-18 移动壳启动页与 adaptive iconExpo 移动壳启动页和 Android adaptive icon 复用现有真实品牌图标 `apps/mobile-shell/assets/icon.png`,背景色固定为 H5 壳根背景 `#fffdf9`。该 PNG 是 1024x1024 RGBA 透明前景品牌资产,不新增占位图;配置检查会校验图标尺寸、透明像素、splash 和 adaptive icon 指向,避免后续换成非品牌或占位素材。
- 2026-06-18 桌面壳 bundle 图标集:Tauri 桌面壳从现有真实品牌 PNG `apps/desktop-shell/src-tauri/icons/icon.png` 派生 `32x32.png`、`128x128.png`、`128x128@2x.png`、`icon.ico` 和 `icon.icns`,并在 `bundle.icon` 中同时声明这些平台图标。检查脚本会校验 PNG 尺寸、ICO 多尺寸头部、ICNS 容器长度和 bundle 图标列表,避免后续退回单图标或替换为非品牌 / 占位素材。
- 2026-06-18 移动壳网络安全元数据:Expo 移动壳默认包配置显式禁用 Android 明文流量 `usesCleartextTraffic=false`iOS ATS 禁用任意加载 `NSAllowsArbitraryLoads=false`,并设置 `ITSAppUsesNonExemptEncryption=false` 作为当前未接入自定义加密能力的出口合规声明;本地 Vite 联调只通过 development build 显式环境变量进入,不把任意明文流量开关带进默认包配置。
- 2026-06-19 移动壳同源 H5 麦克风权限:Expo 移动壳允许 `RECORD_AUDIO` 和 iOS 麦克风用途文案,仅用于同源主站 H5 中需要实时声音输入的正式玩法,例如汪汪声浪 `published` runtime 的 `getUserMedia({ audio: true })` 音量采样;WebView 必须保持 `mediaCapturePermissionGrantType="grantIfSameHostElsePrompt"`,外域页面仍不能留在带 HostBridge 的 WebView 内。该权限不新增 HostBridge method,不代表后台录音、远程语音 SDK 或 AI H5 sandbox 能直接访问宿主能力;`expo-camera` 与 `expo-image-picker` 的麦克风用途文案、Android `RECORD_AUDIO`、Expo public config 和 WebView 媒体捕获策略由移动壳配置检查统一约束。
- 2026-06-18 移动壳 Android 自动备份关闭:Expo 移动壳必须保持 `android.allowBackup=false`,避免 WebView cookie、localStorage、缓存文件和宿主文件导入导出中间态进入 Google Drive 自动备份 / 恢复链路;正式业务事实仍以后端账号、作品、钱包和草稿状态为准。配置检查会拒绝恢复 Android 默认允许备份的包配置。
- 2026-06-18 移动壳 WebView 安全开关:Expo 移动壳 WebView 必须显式禁用 JS 自动开窗、多窗口、文件访问、file URL 跨源访问、HTTPS 混合内容、第三方 Cookie、共享 Cookie 和 WebView 远程调试;同源主站页面才能留在带 HostBridge 的 WebView 内,外链只通过受控协议离开容器交给系统。配置检查和移动壳导航测试会拒绝这些边界被放宽。
- 2026-06-18 移动壳 WebView 默认下载边界:Expo WebView 内网页自动下载和 `<a download>` 直接落盘默认关闭;壳层注入脚本阻断 download 链接,iOS `onFileDownload` 只丢弃不落盘,Android 包配置通过 `blockedPermissions` 移除外部存储读写、管理外部存储和请求安装包权限。移动端文本、图片、音频保存只能通过 `file.exportText`、`file.exportImage`、`file.exportAudio` 等 HostBridge 受控导出能力进入系统分享 / 保存面板。
- 2026-06-18 移动壳 HostBridge 消息来源校验:Expo 移动壳 `onMessage` 必须根据 `event.nativeEvent.url` 校验消息来源,只有同源主站页面能进入 `handleMobileHostBridgeMessage``about:blank`、外域、协议降级和危险协议页面消息直接丢弃,不返回宿主能力错误细节。该规则与 WebView 导航留壳规则共用同源判断,配置检查和移动壳导航测试会拒绝移除。
- 2026-06-18 三端桥接层目录同构:微信小程序、Expo 移动壳和 Tauri 桌面壳都按 `host-bridge / shell` 两层管理宿主桥接代码。微信 `miniprogram/host-bridge/webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js` 只放协议归一、支付 / 订阅 / 分享结果编解码和可测试桥接函数,`miniprogram/shell/` 下同名职责文件承接 Page 生命周期、`wx.*` 容器调用、WebView 容器行为和页面工厂;页面目录只保留 `Page(createWechat...Page())` 装配。Expo `protocol.ts`、`capabilities.ts`、`dispatch.ts`、`files.ts`、`scanner.ts`、`share.ts` 和 facade `bridge.ts` 分别对齐 Tauri `host_bridge/protocol.rs`、`capabilities.rs`、`dispatch.rs`、`files.rs`、`share.rs`、`mod.rs`,根 `App.tsx` 只装配 `src/shell/ShellApp.tsx`,不直接进口 HostBridge。Tauri `shell/runtime.rs`、`url.rs`、`navigation.rs`、`network.rs`、`lifecycle.rs`、`file_drop.rs`、`events.rs`、`deep_link.rs`、`tray.rs`、`window_state.rs` 和 `webview.rs` 分别承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、窗口状态持久化和 WebView 门面。`npm run check:native-shells` 会校验微信、移动和桌面三端目录清单,新增宿主能力必须按同一边界落文件和测试。
- 2026-06-19 桌面壳单端结构门禁:`apps/desktop-shell/scripts/check-config.mjs` 与根级 `npm run check:native-shells` 同步校验 `src-tauri/src` 根目录、`host_bridge/` 和 `shell/` 的生产模块清单,并要求 `main.rs` 保持薄入口、`app.rs` 承接 Tauri builder / plugin / window 装配。后续新增桌面宿主能力必须先按 HostBridge / shell 职责边界登记文件和测试,不能只靠根门禁或把能力逻辑塞回 `main.rs`。
- 影响范围:`src/services/host-bridge/`、未来 `apps/mobile-shell/`、未来 `apps/desktop-shell/`、移动端支付 / 分享 / 深链 / 推送、桌面端系统能力、AI H5 sandbox 的 GameBridge 边界。
- 验证方式:普通浏览器、小程序、Expo 壳、Tauri 壳都能返回正确 `getHostRuntime()`;未支持能力能回退 H5;固定玩法在各宿主中读取同一作品数据和运行态 snapshot;AI sandbox 无法直接调用 HostBridgeTauri release 不允许任意远端页面调用桌面命令。
- 关联文档:`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`。
## 2026-06-17 H5 宿主壳能力统一走 HostBridge
> 2026-07-18 覆盖说明:以下订阅授权、订阅页和旧玩法导航部分已退役;登录、支付、分享、九宫切图与通用 HostBridge 分层仍有效。
- 背景:主站同时运行在普通浏览器、微信小程序 `web-view` 和未来可能出现的原生 App WebView 中;登录、支付、分享、订阅授权和运行态分享目标同步曾散落在业务组件与服务文件里,后续新增宿主壳会导致同一业务重复分叉。
- 决策:前端宿主运行态识别、微信小程序 JS SDK 加载、原生页跳转、支付跳转、登录跳转、九宫切图和 `postMessage` 统一收口到 `src/services/host-bridge/hostBridge.ts`,业务层优先调用 `getHostRuntime`、`requestHostLogin`、`requestHostPayment`、`navigateHostNativePage`、`setHostShareTarget` 和 `openHostShareGrid`。`authService`、分享服务、订阅授权和个人中心充值可保留兼容导出或业务编排,但不再自行加载微信 JS SDK 或直接判断 `wx.miniProgram`。固定内置玩法不走代码包下载流程;AI 生成 H5 沙箱后续单独定义受限 `GameBridge`,不得直接暴露完整 `HostBridge`。
- 影响范围:`src/services/host-bridge/`、`src/services/authService.ts`、`src/services/payment/paymentPlatform.ts`、`src/services/wechatMiniProgramShareGrid.ts`、`src/services/wechatMiniProgramShareTarget.ts`、`src/services/wechatMiniProgramSubscribe.ts`、`src/components/platform-entry/usePlatformProfileCenterController.ts`、微信小程序壳和未来原生 App 壳接入。
- 验证方式:微信小程序首点登录仍打开原生登录页;小程序支付仍跳转 `/pages/wechat-pay/index` 并保留 hash 回灌确认;订阅授权仍跳转 `/pages/subscribe-message/index` 且返回不阻断生成;普通浏览器分享、H5 支付和 Native 二维码支付不受影响。前端验证运行 HostBridge、auth、payment、分享、订阅和个人中心充值相关定向测试,并执行 `npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`。
## 2026-06-15 SpacetimeDB 本地 skills 范围(已由 2026-08-27 决策覆盖)
> 2026-08-27 覆盖说明:本节记录的“三个本地 skill”方案已收敛为单一项目适配层;当前口径见下方“SpacetimeDB 项目 skill 与官方插件职责收敛”。
- 背景:本仓库的 SpacetimeDB 接入已固定为 `server-rs + Axum + SpacetimeDB`,本地 skill 需要从上游 SpacetimeDB `skills/` 更新到 2.5 口径,同时避免继续维护当前项目不使用的 TypeScript server/client、C# 和 Unity 专用 skill。
- 决策:当时仅在仓库内维护与当前后端路线相关的 SpacetimeDB skill,通用 SDK/CLI 内容按上游资料核对;该历史范围已由 2026-08-27 的项目适配层方案替代。
- 影响范围:当时的 `AGENTS.md` SpacetimeDB skill 清单和本地 skill 维护范围;当前范围以新的项目适配层及官方插件路由为准。
- 验证方式:保留当时的上游 skill 对照、本地 skill 校验、删除引用扫描、diff 和编码检查记录。
- 关联文档:`AGENTS.md`、`.codex/skills/genarrative-spacetimedb/SKILL.md`、`docs/【协作规范】Agent工作入口与执行准则-2026-06-22.md`。
## 2026-06-13 图片大图预览统一为黑底全屏查看器
- 背景:`CreativeImageInputPanel` 的参考图 / 主图预览曾使用白底 `UnifiedModal` 工具弹窗,移动端会透出原页面背景,且不能全屏查看、缩放或拖拽细节。
- 决策:纯图片大图预览统一使用 `src/components/common/PlatformImagePreviewModal.tsx`。该组件底层复用 `UnifiedModal` 的 dialog / portal / Escape 语义,但视觉上固定为黑底全屏查看器;图片按视口 contain 初始完整展示,缩放范围固定 `1x-4x`,拖拽位移按缩放后的图片边界夹取,避免露出背景。裁剪、选择、编辑等工具语义仍继续使用白底工具弹窗,不并入图片查看器。
- 影响范围:`CreativeImageInputPanel` 的参考图预览、主图预览,以及后续 common 级图片查看场景。
- 验证方式:`npm run test -- src/components/common/PlatformImagePreviewModal.test.tsx src/components/common/CreativeImageInputPanel.test.tsx`、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/README.md`、`docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md`。
## 2026-06-13 外部生成队列概览归属“我的”页签
- 背景:外部生成 worker 队列从单个生成页等待信息扩展为当前账号级别的后台排队 / 生成概览;继续放在生成页 / 进度页会把账号级队列与当前玩法业务进度混在一起。
- 决策:移动端用户可见的外部生成队列概览统一放在一级 `我的` 页签;生成页 / 进度页只展示当前玩法的阶段、步骤、总进度、错误和重试动作。队列概览只读取 BFF `GET /api/runtime/external-generation/queue-overview` 与当前前端已知单 job 状态作为等待补充,不替代玩法 session/detail 的 ready / failed 回读。
- 影响范围:平台入口壳层轮询条件、`RpgEntryHomeView` 我的页卡片、共用生成页 `CustomWorldGenerationView` / `UnifiedGenerationPage`、外部生成 worker 技术文档和本地开发验证文档。
- 验证方式:生成页不出现“生成队列”区域;登录用户进入“我的”页且队列有 pending/running 或当前 job 为 queued/running/failed 时显示队列卡;退出登录或切换账号时不保留旧账号队列概览。前端验证运行 `npm run test -- src/components/platform-entry/PlatformEntryFlowShellImpl.test.ts src/components/unified-creation/UnifiedGenerationPage.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`。
## 2026-06-13 `/editor/agent` AI Web 工程编辑器采用静态沙箱预览 MVP
- 背景:`/editor/agent` 需要承载浏览器内类似 IDE 的 AI Web 工程编辑和实时预览能力,但 AI 生成工程的构建和运行不能进入 Genarrative 主站 JS 上下文、当前仓库源码目录或 api-server 进程。
- 决策:第一版采用“平台编辑器壳 `/editor/agent` + api-server 控制面 + 独立 `web-project-runner` worker + 独立 preview origin”的四层结构。MVP 只支持固定 React / Vite / TypeScript 静态模板、虚拟文件系统、结构化 AI patch、平台固定构建命令、独立 runner 静态构建和独立域 iframe 预览;明确不做 HMR、终端 shell、后端服务、任意端口代理、任意 npm 安装、AI 自定义 shell script 或主站同源预览。
- 影响范围:`/editor/agent` 前端入口、api-server Web project 控制面、Web project runtime job、runner 部署、preview gateway、artifact store、安全验收和后续作品化发布链路。
- 验证方式:Phase 0 必须先完成技术方案、威胁模型和验收清单;Phase 1 只能在路径校验、runner 资源限制、网络隔离、preview token、iframe/CSP、失败保留上一版预览和刷新恢复验收口径明确后进入编码。
- 关联文档:`docs/technical/【技术方案】浏览器内AIWeb工程沙箱预览方案-2026-06-13.md`、`docs/technical/【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md`、`docs/technical/【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md`。
## 2026-06-12 外部生成 worker 扩展到跳一跳、拼消消和敲木鱼
- 背景:外部图片生成已从 HTTP 长请求迁到 `external_generation_job` 队列;跳一跳、拼消消和敲木鱼继续扩展时需要统一 job 粒度、前端等待展示和本地 / 生产验证口径。
- 决策:队列 BFF 暴露用户可见队列概览 `GET /api/runtime/external-generation/queue-overview` 和单 job 状态 `GET /api/runtime/external-generation/jobs/{jobId}`;首版固定“单动作单 job”,不拆提示词 / 生图 / 切图 / 持久化等阶段 job。进入队列的范围为跳一跳 `compile-draft` / `regenerate-tiles`、拼消消 `compile-draft` / `regenerate-atlas`、敲木鱼 `compile-draft` / `regenerate-hit-object` 图片资产动作;非外部图片生成动作继续 inline。
- 影响范围:外部生成 worker Module、api-server BFF、生成页等待展示、跳一跳 / 拼消消 / 敲木鱼创作与结果页生成动作、本地和生产验证文档。
- 验证方式:本地 `npm run dev` 与 `npm run dev:api-server` 默认注入 `GENARRATIVE_PROCESS_ROLE=all`,同一 Rust 进程监听 HTTP 并消费外部生成队列;验证生产式拆分角色、lease 或扩缩容时分别启动 `api`、`external-generation-worker` 和 `external-generation-controller`,或运行 `npm run container:worker-smoke -- smoke`。部署后确认 `/healthz`、`/readyz`、队列概览 BFF、单 job 状态和对应玩法 session/detail 状态都能收敛。
- 关联文档:`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-06-11 本地服务器管理入口采用 SSH alias + egui 桌面面板
- 背景:release / dev 等服务器的日常巡检已有 systemd、健康巡检 timer 和 HTTP 探测口径,但开发者本地仍需要在多个 SSH alias 间手工切换命令并重复执行启停操作。
- 决策:新增 `server-rs/crates/server-manager-panel` 作为本地 egui 桌面工具;服务器来源只读取本机 `~/.ssh/config` 的具体 `Host` alias,不保存服务器密钥或凭据;巡检通过 `ssh <alias> sh -s` 执行只读脚本,服务操作只允许 `start`、`stop`、`restart` 并限制 systemd unit 名字符集。
- 影响范围:本地运维工具入口、`package.json` 的 `server-manager:panel`、开发运维文档和团队共享工作流。
- 验证方式:`cargo check -p server-manager-panel --manifest-path server-rs/Cargo.toml`、`cargo test -p server-manager-panel --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`。
- 关联文档:`docs/technical/【开发运维】本地SSH服务器管理面板技术方案-2026-06-11.md`。
## 2026-06-10 公开作品互动能力进入后台全局配置
- 背景:作品详情页的点赞和改造能力原本由前端和各玩法 handler 的硬编码能力矩阵决定,后台无法临时关闭某类公开作品的互动入口,直接关闭创作入口又会误伤已有作品读取和游玩。
- 决策:公开作品点赞 / 改造能力作为 `creation_entry_config.public_work_interactions_json` 的全局矩阵保存,不进入单个 `creation_entry_type_config`。`GET /api/creation-entry/config` 下发 `publicWorkInteractions`;后台通过 `/admin/api/creation-entry/config/interactions` 按 `sourceType` 保存点赞、改造开关和关闭提示;api-server 只对已经接入后端动作的 RPG / custom-world、大鱼吃小鱼和拼图 like / remix 路由做同源熔断,公开列表、详情读取、已发布作品启动和运行态请求不受影响。
- 影响范围:`CreationEntryConfigResponse`、`AdminCreationEntryConfigResponse`、`module-runtime` 默认矩阵、`spacetime-module` 表字段和 procedure、`spacetime-client` 绑定、后台入口开关页、平台作品详情点赞 / 改造意图解析。
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo test -p module-runtime public_work_interaction_config_defaults_and_overrides --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server public_work_interactions --manifest-path server-rs/Cargo.toml`、后台和前台作品详情互动相关前端测试。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-19 Jenkins Git 源统一为内网 SSH
- 背景:本地和 Jenkins 流水线改用 Gitea 的 `/git` 前缀内网入口后,继续在 Jenkinsfile 内保留 `http://127.0.0.1:3000/...` 主地址和 `https://git.genarrative.world/...` fallback 会让构建节点误走 localhost 或公网链路。
- 决策:常规生产构建、数据库导入导出和 `Genarrative-Full-Build-And-Deploy` 的 Jenkinsfile 内部 checkout 统一使用 `ssh://git@192.168.35.82:2222/GenarrativeAI/Genarrative.git`,并显式传入 Jenkins SSH 凭据 `genarrative-local-gitea-ssh`;不再把 `https://git.genarrative.world/git/GenarrativeAI/Genarrative.git` 作为默认主源或 fallback,也不再回退公网域名。`Genarrative-Server-Provision` 不再暴露 `SOURCE_GIT_REMOTE_URL` 参数,由 Jenkins 构建节点使用同一 SSH 源准备 provision 脚本和配置,再上传给目标部署 agent 执行。
- 影响范围:`jenkins/Jenkinsfile.production-api-build`、`jenkins/Jenkinsfile.production-web-build`、`jenkins/Jenkinsfile.production-stdb-module-build`、`jenkins/Jenkinsfile.production-full-build-and-deploy`、`jenkins/Jenkinsfile.production-database-export`、`jenkins/Jenkinsfile.production-database-import`、`jenkins/Jenkinsfile.production-server-provision`、生产 Jenkins live job SCM 配置和运维文档。
- 验证方式:Jenkins 内部 `GitSCM checkout` 日志应显示使用 `genarrative-local-gitea-ssh`,不应出现 `No credentials specified``rg "git.genarrative.world|127.0.0.1:3000/GenarrativeAI/Genarrative.git|10.2.0.10/GenarrativeAI/Genarrative.git|genarrative-station/git/GenarrativeAI/Genarrative.git" jenkins scripts` 不应命中流水线源码;所有相关 Jenkinsfile 仍保留单分支 refspec、浅克隆、`noTags` 和 `honorRefspec`。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/project-memory/shared-memory/pitfalls.md`。
## 2026-06-10 dev Gitea 提供内网 HTTP 入口
- 背景:release / dev 目标 agent 需要从 dev 自托管 Gitea 拉取仓库;继续走 `https://git.genarrative.world/...` 会绕公网链路,`10.2.0.10:3000` 又受云侧端口策略影响不能作为稳定入口。
- 决策:dev 上 Gitea 进程保持 `HTTP_ADDR = 127.0.0.1`、`HTTP_PORT = 3000`,公网 `ROOT_URL = https://git.genarrative.world/` 不变;新增 Nginx 内网 vhost `/etc/nginx/conf.d/gitea-internal.conf`,只允许 `10.2.0.0/16` 与本机访问,并把 `http://10.2.0.10/` 反代到本机 Gitea。内网 agent 统一使用 `http://10.2.0.10/GenarrativeAI/Genarrative.git` 作为可直连 Git 源。
- 影响范围:dev Gitea / Nginx 运维配置、历史 `SOURCE_GIT_REMOTE_URL` 参数和旧 release / dev 目标 agent checkout 口径;`Genarrative-Server-Provision` 已于 2026-06-22 改为 Jenkins 上传脚本执行,目标 agent 不再直接 checkout Git。
- 验证方式:从 release 执行 `git ls-remote http://10.2.0.10/GenarrativeAI/Genarrative.git HEAD` 应返回 HEAD;公网来源伪造 `Host: 10.2.0.10` 访问 dev 公网 80 应返回 `403``https://git.genarrative.world/` 原入口应保持 `200`。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-06-08 通用分享统一为作品分享卡片
- 背景:已发布作品的分享入口需要同时支持网页复制链接、下载可传播的分享卡,以及微信小程序内的九宫切图;推荐页在小程序内直接使用系统“分享到聊天”时,宿主快照只截页面中部,容易裁掉游戏主体,且原生分享默认只能拿到小程序页面启动参数。
- 决策:统一分享入口继续收口到 `PublishShareModal`,分享卡展示作品封面、作品类型、作品名称和公开作品号,底部提供“复制链接”和“下载卡片”。普通 H5 复制公开作品 H5 URL;微信小程序 WebView 内复制小程序 `pages/web-view/index` 路径,缺少直达参数时补 `targetPath=/works/detail` 与 `work=<公开作品号>`,由小程序原生 WebView 页转成 H5 作品详情 URL。当 H5 运行在微信小程序 WebView 内且存在封面图时,额外显示“九宫切图”,跳转小程序原生 `pages/share-grid/index`,由原生页按 3x3 从左到右、从上到下裁切并保存。推荐页当前作品会通过 `wx.miniProgram.postMessage` 同步给小程序原生 `web-view` 页,右上角系统分享优先使用该目标生成带作品参数的小程序路径。小程序运行态通过根节点标记启用推荐页 runtime 快照安全区,把游戏画面等比缩放到分享快照中部。
- 影响范围:`src/components/common/PublishShareModal.tsx`、`src/components/common/publishShareModalModel.ts`、`src/components/common/publishShareCardImage.ts`、`src/services/wechatMiniProgramShareGrid.ts`、`src/services/wechatMiniProgramShareTarget.ts`、`miniprogram/pages/web-view/`、`miniprogram/pages/share-grid/`、推荐页 runtime CSS 和平台玩法链路文档。
- 验证方式:`npm run test -- src/components/common/PublishShareModal.test.tsx miniprogram/pages/web-view/index.test.js src/services/wechatMiniProgramShareTarget.test.ts`、`npm run test -- miniprogram/pages/share-grid/index.test.js`、`npm run test -- src/index.test.ts -t "mini program recommend runtime"`、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-08 微信能力按领域收口
- 背景:微信登录、订阅消息、普通微信支付和小程序虚拟支付能力曾分散在 `api-server` 根模块、`platform-auth` 与 `platform-wechat`,支付协议细节和业务 handler 边界不够清晰。
- 决策:`api-server` 内微信相关 HTTP/BFF 适配统一收在 `server-rs/crates/api-server/src/wechat.rs` 与 `wechat/*``platform-wechat` 负责微信订阅消息、微信支付 V3、虚拟支付消息推送的协议 client、header、签名、验签、解密、mock 和 payload 解析;`api-server::wechat` 只负责 AppConfig 映射、Axum handler、用户 / 订单 / 钱包 / SSE / 错误 envelope 编排。微信 OAuth / 小程序登录 provider 暂继续在 `platform-auth`,通过 `api-server::wechat::provider` 作为组合根 adapter 接入。
- 影响范围:`server-rs/crates/api-server/src/wechat.rs`、`server-rs/crates/api-server/src/wechat/*`、`server-rs/crates/platform-wechat/src/*`、微信支付 / 订阅消息 / 小程序消息推送文档。
- 验证方式:执行 `cargo check --manifest-path server-rs/Cargo.toml -p platform-wechat`、`cargo check --manifest-path server-rs/Cargo.toml -p api-server`、微信相关定向测试和编码检查;新增微信协议细节优先落到 `platform-wechat`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【技术方案】微信虚拟支付接入-2026-05-26.md`。
## 2026-06-08 后端创作 / 游玩流程先统一主干再领域分发
- 背景:前端平台入口、作品架、公开详情和推荐运行态已经持续收口,但 `api-server` 仍在 `app.rs` 逐玩法合并创作 / 运行态路由,入口开关路径判断也独立维护,新增玩法容易复制出平行链路。
- 决策:后端所有创作 / 游玩相关 HTTP 路由先进入 `server-rs/crates/api-server/src/modules/play_flow.rs` 统一主干;主干注册 `playId`、领域模块 key、创作路由前缀、运行态路由前缀和新建创作入口开关匹配规则,并在进入领域 handler 前统一挂载 `PlayFlowRequestContext`,再在最后一步分发到各玩法领域 HTTP Adapter。创作入口配置、AI task、runtime chat、运行态设置 / 存档、运行态库存、游玩历史、存档归档、游玩统计、历史素材、角色资产工坊、角色图像 / 动画生成和 Hyper3D 代理也作为创作 / 游玩支撑能力从 `play_flow` 进入;`modules/platform.rs` 只保留通用 LLM / 语音代理。`app.rs` 只合并 `modules::play_flow::router(state)`,不再逐玩法 merge`creation_entry_config.rs` 复用 `play_flow` 的入口开关解析,不维护第二份路径表。
- 影响范围:`api-server` 路由组织、入口开关、玩法接入 SOP、后端契约文档、后续新增 / 迁移玩法。
- 验证方式:`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`,并确认旧 `/api/creation/<play>/*`、历史 `/api/runtime/<play>/agent/*` 与公开 runtime 路由外部契约不变。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-08 PlatformUiKit 弹窗与复制反馈收口
- 背景:前端已有 `UnifiedModal` 统一遮罩和无障碍外壳,但业务页面仍反复手写“知道了”“确认 / 取消”“危险确认”的 footer 按钮和关闭禁用逻辑。
- 决策:简单提示、确认 / 取消和危险确认统一使用 `src/components/common/UnifiedConfirmDialog.tsx`;剪贴板复制反馈统一使用 `src/components/common/useCopyFeedback.ts`,可点击复制按钮统一使用 `src/components/common/CopyFeedbackButton.tsx` 承载图标、三态文案、可访问名称、纯图标模式和动作按钮外观入口,作品号 / 用户号等短代码 chip 统一使用 `src/components/common/CopyCodeButton.tsx` 承载代码、三态后缀和默认可访问名称,非按钮复制提示统一使用 `src/components/common/CopyFeedbackMessage.tsx`,白底平台状态提示统一使用 `src/components/common/PlatformStatusMessage.tsx`,无操作空态 / 轻量读取态统一使用 `src/components/common/PlatformEmptyState.tsx`,平台动作按钮统一使用 `src/components/common/PlatformActionButton.tsx` 承载 platform / profile 两类样式族、尺寸、圆角、对齐、宽度和禁用态;认证表单的提交、验证码、第三方登录和邀请码提交按钮使用 `size="lg"` 复用 48px 高度,统一创作工作台、统一创作页壳层、玩法创作工作台、结果页返回按钮和反馈页 header 返回使用 `tone="ghost"`,生成 / 提交 / 发布按钮使用主动作,自定义世界实体目录、RPG 首页作品卡删除、创作中心错误重试和素材槽的小动作使用 `size="xs"` 或 `shape="pill"` 收口,推荐回复和列表内动作使用 `align="start"` 承接左对齐,上传控件等需要 label 语义时使用 `PlatformActionButton asChild="label"`,不把文件输入伪装成普通 button。普通平台图标动作按钮和图标上传 label 统一使用 `src/components/common/PlatformIconButton.tsx` 承载 `platform-icon-button` 外观、可访问名称、默认 `type="button"`、`asChild="label"` 和可选 title;历史图片选择弹窗、RPG 发布检查弹窗、RPG 首页搜索结果清空、creative-agent 侧边栏关闭 / 外观 / 设置入口、creation-agent 参考图移除、敲木鱼结果页新增主题标签入口、拼图结果页标签生成 / 标签新增 / 关卡详情关闭 / 发布弹窗关闭 / 删除关卡入口、视觉小说结果页素材选择 / 音频生成 / 保存草稿 / 运行配置入口,以及抓大鹅结果页标签生成 / 标签新增 / 物品素材删除 / 参考图上传入口已先迁移;图标上传控件必须保留 label + file input 语义。平台 / 个人中心弹窗关闭按钮统一使用 `src/components/common/PlatformModalCloseButton.tsx` 承载 profile / profileCompact / floating / floatingPlain / platformIcon 五类圆形关闭按钮、默认图标和可访问名称;认证入口、邀请码弹窗、抓大鹅结果页弹窗关闭等平台头部关闭按钮使用 `variant="platformIcon"`,不在业务 JSX 中手写 `platform-icon-button` + X 图标。RPG / 拼图 / 抓大鹅 / 跳一跳 / 敲木鱼 / 拼消消 / 宝贝识物 / 方洞 / 汪汪声浪结果页,拼消消 / 宝贝识物 / 视觉小说 / 汪汪声浪创作工作台,发布检查、素材生成面板和自定义世界实体目录中的错误 / 成功 / 信息 / 警告 / 中性提示使用 `PlatformStatusMessage surface="platform"` 复用平台 banner token;个人中心弹窗、账号安全弹窗、认证入口、验证码提示、统一创作工作台和通用创作输入区的错误 / 成功 / 信息 / 警告提示使用 `PlatformStatusMessage surface="profile"` 复用 profile token,不再把 `platform-profile-error` / `platform-profile-success` 或 `platform-banner--danger / success / info / warning / neutral` 作为业务 JSX 接口。`UnifiedModal` 继续作为底层模态窗口 Module。已有弹窗栈内的二级确认使用 `UnifiedConfirmDialog portal={false}` 内嵌到当前层级。特殊确认按钮外观通过 `confirmClassName` 适配,不让业务页重新手写 footer`UnifiedConfirmDialog` 自身的 footer 按钮也复用 `PlatformActionButton`。带复制状态、渠道按钮、媒体预览或复杂网格的弹窗可以保留专用 Module,但普通确认按钮、普通动作按钮、普通图标动作按钮、复制按钮动作外观、复制状态机、copied / failed 按钮 / toast 分支、基础错误 / 成功提示条、无操作空态和普通弹窗关闭按钮不再直接写进业务页面。运行态 HUD、输入 Composer 发送 / 上传按钮、复制三态图标按钮或需要专用交互禁用语义的图标按钮先保留专用布局,等对应场景验证时再迁移。业务代码中的阻断提示、删除确认和公开作品失效恢复不得继续调用浏览器原生 `window.alert` / `window.confirm`,应由页面壳层或编辑器壳层用 `UnifiedConfirmDialog` 承接。简单确认需要像素风时使用 `UnifiedConfirmDialog variant="pixel"`,不再为同类确认单独维护壳层和按钮。
- 2026-06-10 追加:推荐页运行态卡片底部的点赞 / 分享 / 改造入口,以及创作中心公开作品卡右上角分享入口统一迁移到 `PlatformIconButton`;这类和 swipe / drag 手势耦合的图标动作必须继续保留业务局部 class 与 `onPointerDown` / `onClick` 里的 `stopPropagation`,只把按钮语义、可访问名称和默认 `type="button"` 收口到共享组件,避免图标动作误触推荐卡切换、整卡打开或残留左滑状态。
- 2026-06-10 追加:标准泥点消耗确认弹窗统一收口到 `src/components/common/PlatformMudPointConfirmDialog.tsx`;该 Module 专门承接“确认消耗泥点 + 消耗 N 泥点”的同形态确认骨架,当前已覆盖 `PuzzleCreationWorkspace.tsx`、`Match3DCreationWorkspace.tsx`、`PuzzleResultView.tsx` 与 `Match3DResultView.tsx`。后续遇到同形态泥点确认时,业务页只传点数、补充说明和确认回调,不再重复拼接 `UnifiedConfirmDialog` 正文;`RpgCreationRoleAssetStudioModalImpl` 这类节奏和内容结构不同的泥点弹层继续单独评估,留作后续轮次处理。
- 2026-06-10 追加:`RpgCreationRoleAssetStudioModalImpl.tsx` 的角色形象生成 / 动作草稿生成确认也并入 `PlatformMudPointConfirmDialog`;共享组件通过自定义 title 与补充说明承接工坊语义,工坊页不再单独维护 `UnifiedConfirmDialog` 的标准泥点文案骨架。后续同类“确认消耗泥点 + 补充说明”场景继续优先复用该 Module。
- 2026-06-10 追加:平台危险确认统一收口到 `src/components/common/PlatformDangerConfirmDialog.tsx`;该 Module 专门承接“确认 / 取消 + 危险主动作”的标准骨架,当前已覆盖 `PlatformEntryFlowShellImpl.tsx` 的删除作品确认、`RpgCreationResultViewImpl.tsx` 的重新生成确认和 `CustomWorldEntityCatalog.tsx` 的删除角色 / 批量删除确认。后续删除、覆盖、清空等危险动作优先复用该 Module,不再在业务页重复拼接 `UnifiedConfirmDialog` 的 `showCancel + confirmTone=\"danger\"` 组合。
- 2026-06-10 追加:平台未保存离开确认统一收口到 `src/components/common/PlatformUnsavedLeaveConfirmDialog.tsx`;该 Module 专门承接“继续编辑 + 确认离开”的标准骨架,当前已覆盖 `RpgCreationEntityEditorShared.tsx` 里的关闭未保存修改、生成结果未保存退出和普通结果未保存退出确认。后续同类未保存离开场景优先复用该 Module,不再在业务页重复拼接 `UnifiedConfirmDialog` 的 `showCancel + cancelLabel=\"继续编辑\"` 组合和重复壳层 class。
- 2026-06-10 追加:平台单按钮已读状态统一收口到 `src/components/common/PlatformAcknowledgeStatusDialog.tsx`;该 Module 专门承接“状态提示 + 知道了”的单按钮确认已读语义,当前已覆盖 `BigFishResultView.tsx` 的发布失败提示、`RpgEntryHomeView.tsx` 的支付结果提示、`RpgCreationEntityEditorShared.tsx` 的编辑器 notice、`PlatformEntryFlowShellImpl.tsx` 的泥点提示 / 作品不可用 / 搜索未命中提示,以及 `CustomWorldEntityCatalog.tsx` 的“无法删除”阻断提示。后续同类 status-dialog 场景优先复用该 Module,不再在业务页重复拼装 `action={{ label: '知道了', onClick: onClose }}`。
- 2026-06-10 追加:RPG 首页个人中心里的统计卡、统计骨架、常用功能入口、设置行和法律信息入口统一抽到 `src/components/platform-entry/PlatformProfilePrimitives.tsx`;这组纯展示原子以后优先通过 props 接收图片资源、点击回调和展示文案,不再继续塞回 `RpgEntryHomeView` 的账户控制逻辑里。新建 `PlatformProfilePrimitives.test.tsx` 作为组件级护栏,页面级布局与法律入口继续由 `RpgEntryHomeView.recharge.test.tsx` 兜底。
- 2026-06-10 追加:RPG 首页个人中心的充值 / 钱包 / 每日任务 / 邀请 / 兑换码等商业与账户控制逻辑统一收口到 `src/components/platform-entry/usePlatformProfileCenterController.ts`controller 负责账户动作分流、商业状态派生与相关面板控制,`RpgEntryHomeView` 只保留展示、昵称头像编辑、扫码入口和页面级交互编排,不在页面组件里继续堆叠账户控制分支。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`。
- 2026-06-10 追加:RPG 首页个人中心的“玩过 / 可继续”历史弹层统一抽到 `src/components/platform-entry/PlatformProfilePlayedWorksModal.tsx``RpgEntryHomeView` 不再内联 `SaveArchiveCard`、`ProfilePlayedWorksModal` 和未连通的 `ProfileSaveArchivesModal`。当前产品语义已经把存档恢复并入“玩过”弹层的“可继续”分区,因此 controller 里的 `ProfilePopupPanel` 也去掉了没有真实入口的 `saveArchives` 分支。验证命令:`npm run test -- src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`npm run typecheck`。
- 2026-06-10 追加:个人中心标准头部弹窗与白底副弹层的共享壳层统一抽到 `src/components/platform-entry/PlatformProfileModalShell.tsx`;标准头部弹窗优先复用 `PlatformProfileModalShell`,白底副弹层优先复用 `PlatformProfileSecondaryModalShell`,不再在业务页重复手写 profile overlay、header、title、description、floating close 和关闭策略。昵称修改、账户充值、每日任务、兑换码、泥点账单、“玩过 / 可继续”以及邀请相关弹层已接入这套壳层。
- 2026-06-10 追加:RPG 首页个人中心的邀请好友 / 填邀请码 / 玩家社区三态弹层统一抽到 `src/components/platform-entry/PlatformProfileReferralModal.tsx`;首页不再内联邀请码规范化、社区二维码卡片和邀请用户头像行,后续 profile 侧同类二级弹层优先按“独立组件 + `PlatformProfileSecondaryModalShell`”继续收口。
- 2026-06-10 追加:RPG 首页个人中心的账户充值弹层统一抽到 `src/components/platform-entry/PlatformProfileRechargeModal.tsx`;充值 tab、套餐卡片、Native 二维码生成和确认支付入口不再内联在 `RpgEntryHomeView`,后续 profile 侧充值入口优先复用同一个组件。
- 2026-06-10 追加:RPG 首页个人中心的泥点账单、每日任务和兑换码弹层统一抽到 `src/components/platform-entry/PlatformProfileWalletLedgerModal.tsx`、`src/components/platform-entry/PlatformProfileTaskCenterModal.tsx` 与 `src/components/platform-entry/PlatformProfileRewardCodeRedeemModal.tsx``RpgEntryHomeView` 只保留打开条件和数据流,标准 profile 弹层内容以后优先沉到 `platform-entry` 独立组件,不在首页继续堆叠。
- 2026-06-10 追加:个人中心支付结果提示与支付确认遮罩统一抽到 `src/components/common/PlatformStatusDialog.tsx`,扫码面板统一抽到 `src/components/platform-entry/PlatformProfileQrScannerModal.tsx``RpgEntryHomeView` 只保留支付结果 kind 到 `success / loading / cancel / error` 的映射、确认遮罩开关和扫码结果写回,不再内联 profile 状态弹层壳层、二维码摄像头启动或 `BarcodeDetector` 轮询。后续 profile 侧同类“状态图标 + 标题正文 + 可选主动作”弹层优先复用 `PlatformStatusDialog`,扫码类弹层优先复用 `PlatformProfileQrScannerModal`。
- 2026-06-10 追加:`PlatformStatusDialog` 支持自定义图标、图标可访问标签以及动作按钮 surface / size / className 透传,用来承接玩法结果页里保留品牌视觉但语义仍是“状态结果弹层”的场景;大鱼吃小鱼结果页的发布失败弹层已迁移到这套组件,业务页不再保留 `UnifiedConfirmDialog + PlatformIconBadge` 的专用组合。
- 2026-06-10 追加:`PlatformStatusDialog` 继续支持 header notice 布局、body content、close button、backdrop / Escape 关闭路径,用来承接“提示 / 规则阻断 / 作品不可用 / 泥点不足”这类带标题栏的状态 notice;平台入口的 `draftGenerationPointNotice`、`workNotFoundRecoveryDialog` 和 RPG 大编辑器里的 `EditorNoticeDialog` 已迁移到这套共享组件,不再各自维护 `UnifiedConfirmDialog` 壳层和关闭策略。
- 2026-06-10 追加:`CustomWorldEntityCatalog` 的 `minimum-playable` 规则阻断提示也统一迁到 `PlatformStatusDialog`,不再和删除角色 / 批量删除共用 `UnifiedConfirmDialog` 配置;同日平台入口公开编号搜索把 error 分支从用户摘要 modal 中拆出,未命中结果单独走 `PlatformStatusDialog`,命中用户继续保留 `UnifiedModal + PlatformSubpanel` 信息布局。
- 2026-06-11 追加:`PlatformAsyncStatePanel` 继续从 profile modal 与作品架扩展到 RPG 首页公开分区;`RpgEntryHomeView.tsx` 的移动端排行、发现页寓教于乐 / 默认公开 feed、桌面首页“今日游戏 / 推荐”、桌面发现页寓教于乐 / 默认公开 feed,以及“我的创作”分区已统一改成 `loadingState / emptyState / children` 三态 slot。页面级 `platformError` 继续留在状态壳外层,保证错误提示可以和内容并存;`recommend runtime`、分类筛选等含运行态或二级筛选语义的分支暂不硬并入这一轮。
- 2026-06-11 追加:暗色 / 像素 modal 的标准 footer 布局统一抽到 `src/components/common/PlatformDarkModalFooter.tsx`;该组件只负责 dark footer 的分隔线、padding 和常见动作区排布,不持有“取消 / 确认”业务语义。`NpcModals.tsx` 的交易 / 赠礼 / 招募 footer、`SelectionCustomizationModals.tsx` 的 `SelectionModal` footer、`RpgAdventurePanelOverlays.tsx` 的 goal panel footer,以及 `InventoryItemViews.tsx` 的详情 footer wrapper 已接入;sticky 工作台 footer、正文内单 CTA 收尾和 runtime HUD 工具条暂不并入这一抽象。
- 2026-06-11 追加:桌面首页里的轻量可点击扁平行开始统一收口到 `src/components/common/PlatformNavigableListItem.tsx`;目前已覆盖 `RpgEntryHomeView.tsx` 的搜索结果行、桌面“最近作品”、桌面“最近浏览”以及桌面“今日游戏”趋势行。组件只承接 `button + left content + right affordance` 结构、默认 `type="button"` 与 `leading / trailing` 插槽,暂不扩成覆盖教培 promo card、分类卡片、世界卡或 runtime 列表项的万能 row primitive。
- 2026-06-11 追加:`PlatformNavigableListItem` 继续扩展到 profile 设置行;`src/components/platform-entry/PlatformProfilePrimitives.tsx` 的 `ProfileSettingsRow` 已改成委托共享 `button + leading + trailing` 骨架,继续保留本地 `platform-profile-settings-row` class 承接分隔线、icon 胶囊和字号微调。后续 profile / 账户中心里的同类轻量导航行优先直接复用共享行骨架,不再回退成原生 `<button>` 手写布局。
- 2026-06-11 追加:`PlatformNavigableListItem` 继续扩展到 RPG 首页公开列表里的排行行与分类行;`RpgEntryHomeView.tsx` 的 `PlatformRankingItem`、`PlatformCategoryGameItem` 已改成委托共享 `button + leading + body + trailing` 骨架,同时保留 `platform-ranking-item__*` 与 `platform-category-game-item__*` 局部 class 承接封面、metric、badge、摘要和右侧 `试玩 / 进入` affordance。后续首页 / 发现页里同类浅色导航行优先沿“共享骨架 + 本地皮肤 class”推进,不再为了这类 row 回退成原生 `<button>` 手写布局。
- 2026-06-11 追加:`PlatformAsyncStatePanel` 继续补齐 RPG 首页分类分支;移动端“发现 -> 分类”、桌面发现页“分类”和桌面首页“作品分类”模块现在都统一委托共享状态壳切换外层 `loading / empty / content`,分类控制条与排序按钮继续留在内容 slot 中。筛选后无结果的“当前筛选下没有作品。”也统一改成内层 `PlatformAsyncStatePanel` 切换,不再在三处 JSX 中各自维护嵌套 ternary。
- 2026-06-11 追加:`PlatformDarkModalFooter` 不只收动作按钮区,也继续覆盖纯内容 footer`CompanionCampModal.tsx` 底部“营地气氛”区域已改成 `layout="content"` + `padding="roomy"` 的共享 footer frame,保留原有文案和卡片布局,不再单独手写 `border-t border-white/10 px-5 py-4`。
- 2026-06-11 追加:`PlatformDarkModalFooter` 继续从标准双按钮 footer 扩到 detail / confirm 收尾;`NpcModals.tsx` 的交易详情 footer 和 `MapModal.tsx` 的场景切换确认 footer 已改成复用同一个 dark footer frame,即使只有单个“关闭”按钮也不再手写 `flex justify-end`。这条抽象继续只覆盖 dark / pixel modal 里的底部分隔线与常规动作区排布,不向白底 profile 弹窗 footer、sticky 工作台 footer 或运行态 HUD 工具条扩张。
- 2026-06-11 追加:`PlatformFilterToolbar.tsx` 作为薄结构组件收口 RPG 首页分类工具条;组件只承接“筛选按钮 + tabs + 排序按钮”的排布与 `mobile / desktop` 两种布局差异,不持有筛选状态、空态或排序逻辑。后续只有在同构壳层真的复现时才继续往 `common` 扩覆盖面;如果只是单页内局部重复、接口会越抽越胖,就优先退回文件内 helper。
- 2026-06-11 追加:`SquareImageCropModal.tsx` 的白底弹窗壳层改为复用 `UnifiedModal.tsx`,同时给 `UnifiedModal` 薄补 `titleId` 与 `closeIcon` 透传,让裁剪弹窗继续保留自定义 close icon、无 backdrop / Escape 关闭和两列 footer,而不把 `PlatformProfileModalShell` 这类带页面语义的壳层倒灌回 `common/`。这条规则适用于 `common` 级工具弹窗:先看 `UnifiedModal` 能不能承接,再决定是否需要新的薄壳。
- 2026-06-11 追加:`CreativeImageInputPanel.tsx` 里参考图预览、主图预览和移除图片确认都继续并回 `UnifiedModal` 体系:两个预览弹窗直接复用 `UnifiedModal`,删除确认直接复用 `UnifiedConfirmDialog`,不再在图片面板里手写三段 `platform-modal-backdrop + platform-modal-shell`。当前没有新增 `PlatformImagePreviewModal`,因为这批差异还只在尺寸与文案层,继续组合已有 modal 原语的 leverage 更高。
- 2026-06-11 追加:`src/components/common/PlatformUtilityInfoModal.tsx` 作为 `UnifiedModal` 之上的薄壳,统一承接 `PlatformReportDialog.tsx` 与 `PublishShareModal.tsx` 共同的工具信息弹窗骨架:平台主题 overlay、白底 panel,以及 body / footer 间距与标准 footer frame。该壳层不继续向上吸收报告字段列表、分享正文、复制逻辑、渠道按钮或品牌 icon;后续 `common` 级工具信息弹窗若只是重复这套白底信息壳,优先复用 `PlatformUtilityInfoModal`,业务正文和 footer 交互继续留在调用方。验证命令:`npx vitest run src/components/common/PlatformUtilityInfoModal.test.tsx src/components/common/PlatformReportDialog.test.tsx src/components/common/PublishShareModal.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 2026-06-11 追加:profile 白底副弹层里的摘要头、列表骨架和内容行继续沉到 `PlatformProfileSummaryHeader.tsx`、`PlatformProfileSkeletonList.tsx` 与 `PlatformProfileContentRow.tsx`;这组组件只承接 `kicker + title + badge` 摘要层次、重复 skeleton 行以及 `PlatformSubpanel` 上的 `div / button` 内容行语义,不持有账单金额、任务进度、邀请用户信息、充值商品结构或状态切换逻辑。后续 profile modal 若只是重复这三类白底内容骨架,优先复用这组薄组件,不再把 skeleton、摘要头和 row chrome 写回各自 modal。验证命令:`npx vitest run src/components/common/PlatformProfileModalContent.shared.test.tsx src/components/platform-entry/PlatformProfileTaskCenterModal.test.tsx src/components/platform-entry/PlatformProfileWalletLedgerModal.test.tsx src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx src/components/platform-entry/PlatformProfileReferralModal.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 2026-06-11 追加:`PlatformProfileModalShell` 继续补齐标准 footer 插槽,直接透传 `UnifiedModal.footer` 与 `footerClassName``RpgEntryHomeView.tsx` 的昵称修改弹窗已改成标准 profile footer,不再把双按钮动作区手写在 body 末尾。后续个人中心里同类“表单内容 + 底部双按钮”弹窗优先走壳层 footer 接法。
- 2026-06-11 追加:`PlatformProfileModalShell` 的标准 footer 接法继续扩展到单 CTA 表单收尾;`PlatformProfileRewardCodeRedeemModal.tsx` 的兑换按钮已迁到壳层 footer,body 只保留输入和反馈消息。`PlatformAsyncStatePanel` 同日继续扩展到 `PlatformAssetPickerGrid`、`VisualNovelSavePanel.tsx` 与 `AccountModal.tsx` 的账号安全三个子区块;其中公共素材网格继续把 `error` banner 放在状态壳外层,保持错误提示可与加载态或内容并存的原语义。
- 2026-06-11 追加:按钮层继续补齐轻量漏网项。`PlatformTagEditor.tsx` 的标签 chip 删除入口已改成紧凑 `PlatformIconButton`,保留透明背景和原 chip 高度;`RpgEntryCharacterSelectView.tsx` 的两处“返回”按钮统一沉到局部 `CharacterSelectBackButton`,底层委托 `PlatformActionButton surface="editorDark"`。同日 `GenerationProgressHero.tsx` 新增 `GenerationHeaderBackButton``CustomWorldGenerationView.tsx` 与 `BarkBattleGeneratingView.tsx` 已开始复用这套暖色生成页返回入口骨架;后续同类轻量返回按钮与 chip 删除按钮优先继续沿共享按钮 + 薄包装的方向推进。
- 2026-06-09 追加:通用输入 Composer 的上传参考图、发送和移除参考图已迁移到 `PlatformIconButton`;图标上传仍使用 `asChild="label"` 保留 label + file input 语义,公共组件会自动写入隐藏文本,确保内嵌 file input 继承可访问名称。
- 2026-06-10 追加:creation-agent composer 的上传文档 / 上传参考图入口使用 `PlatformIconButton` 默认 `platformIcon`;工作台只保留动态 label、title、busy 状态和 picker 回调,发送按钮继续保留主题色动作布局。验证命令:`npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx src/components/common/PlatformIconButton.test.tsx`。
- 2026-06-10 追加:作品详情顶部返回 / 分享和封面轮播上一张 / 下一张入口使用 `PlatformIconButton variant="platformIcon"`;详情页保留原 `platform-work-detail__*` 局部 class 控制位置和尺寸,点赞、复制三态等专用动作暂不迁移。验证命令:`npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformIconButton.test.tsx`。
- 2026-06-09 追加:通用输入 Composer 普通 panel 外壳迁移到 `PlatformSubpanel`,文本域迁移到 `PlatformTextField variant="textarea"`,读图错误迁移到 `PlatformStatusMessage surface="profile"`;浮动胶囊 Composer 保留专用外壳和 CSS 覆盖。
- 2026-06-10 追加:`PlatformStatusMessage` 根节点固定带 `platform-status-message` 类名,供业务测试断言公共状态条接入;RPG 大编辑器中的场景背景生成、作品封面生成和封面上传错误 / 成功提示先使用 `surface="tinted"` 加局部暗色 class 保留编辑器视觉,后续普通暗色编辑 / 运行面板状态提示统一迁入 `surface="editorDark"`。
- 2026-06-10 追加:`PlatformStatusMessage surface="editorDark"` 承接 RPG 暗色面板里的普通错误 / 成功 / 信息 / 警告 / 中性提示;背包故事档案 QA 提示、角色聊天错误提示、营地编组战斗中提示和自定义选择弹窗错误 / 生成中提示已迁移,业务 JSX 不再手写暗色 `border-*-300/15 bg-*-500/10 text-*-50/90` 状态条 chrome。
- 2026-06-10 追加:NPC 交易 / 赠礼 / 招募弹窗里的叙事提示使用 `PlatformStatusMessage surface="editorDark"`;弹窗只保留 introText 数据和业务 tone 选择,不再手写暗色提示条边框、底色、圆角、字号和换行 class。
- 2026-06-10 追加:creation-agent composer 错误条使用 `PlatformStatusMessage surface="platform"`;工作台只保留错误来源合并和局部外边距 / 圆角,不再手写红色边框、底色和文字 class。验证命令:`npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-10 追加:creative-agent 首页错误提示使用 `PlatformStatusMessage tone="error" surface="platform" size="md"`;首页只保留宽度对齐局部 class 和错误文案,不再手写 danger panel chrome。验证命令:`npm run test -- src/components/creative-agent/CreativeAgentHome.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-10 追加:大鱼吃小鱼结果页发布校验阻断项使用 `PlatformStatusMessage tone="warning" surface="platform" size="xs"`;结果页只保留阻断项裁剪和文案,不再手写 amber 文本列表。验证命令:`npm run test -- src/components/big-fish-result/BigFishResultView.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-09 追加:通用创作图片面板中覆盖在图片或输入区上的更换主图、移除主图、历史入口短标签按钮和提示词参考图上传入口,以及抓大鹅封面编辑中覆盖在封面图上的移除入口,使用 `PlatformIconButton variant="surfaceFloating"`;白底圆形 / 短标签浮动图标动作的 `border-white/80`、`bg-white/94`、`backdrop-blur`、hover 和禁用态不再在业务 JSX 中重复拼。
- 2026-06-10 追加:`PlatformIconButton variant="darkMini"` 承接覆盖在缩略图上的暗色小型图标动作;`PlatformUploadPreviewCard` 的 square 右上移除按钮已迁移到该 variant,上传预览卡不再手写黑底圆形移除按钮 chrome。
- 2026-06-09 追加:图片编辑面板中的白底胶囊开关统一使用 `src/components/common/PlatformPillSwitch.tsx` 承载 label + `role="switch"` 输入语义、轨道、圆点、白底浮层和禁用态;通用创作图片面板和抓大鹅封面编辑的 `AI重绘` 已先迁移,业务页只保留受控布尔值和状态变更回调。
- 2026-06-09 追加:设置面板、结果页配置和工作台白底配置项里的整行开关统一使用 `src/components/common/PlatformToggleRow.tsx` 承载 label、checkbox、只读状态 pill、可选 icon、可选点击状态行、禁用态和 soft / plain 两类白底 surface;视觉小说结果页运行配置 / 玩家可见开关、视觉小说 runtime 设置面板和拼消消创作工作台 AI 生成底图开关已先迁移,业务页只保留字段写回和点击动作。
- 2026-06-09 追加:公开编号搜索结果弹窗关闭按钮使用 `PlatformModalCloseButton variant="platformIcon"`,平台壳不再手写 `platform-icon-button` + 关闭文本。
- 2026-06-10 追加:RPG 大编辑器主壳层和紧凑对话壳层的右上角关闭入口使用 `PlatformModalCloseButton variant="platformIcon"`,暗色编辑器保留 `platform-icon-button` 视觉 token,但业务 JSX 不再手写关闭按钮 aria、默认 X 图标和禁用态拼接。
- 2026-06-10 追加:`PlatformModalCloseButton variant="editorDark"` 承接 RPG 暗色弹窗中非像素风的圆形 X 关闭入口,根节点固定带 `platform-modal-close-button--editor-dark` 稳定类名;自定义选择弹窗头部关闭按钮已迁移,并补齐 `aria-label`,业务 JSX 不再手写暗色关闭按钮边框、底色、hover 和默认 X 图标。验证命令:`npm run test -- src/components/common/PlatformModalCloseButton.test.tsx src/components/SelectionCustomizationModals.test.tsx`。
- 2026-06-10 追加:`PlatformModalCloseButton variant="pixel"` 承接 `UnifiedModal variant="pixel"` 头部圆形关闭入口;`UnifiedModal` 只选择 `platformIcon / pixel` 变体并保留 closeDisabled、Backdrop、Escape 和 portal 语义,不再手写 X 图标、aria 和关闭按钮 class。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/common/PlatformModalCloseButton.test.tsx src/components/common/UnifiedConfirmDialog.test.tsx`。
- 2026-06-10 追加:`UnifiedModal` 新增 `closeVariant`、`closeOnEscape`、`titleClassName` 和 `descriptionClassName`,用于在收口标准平台弹窗壳层时保留个人中心 `profile / profileCompact` 关闭按钮、原有标题层级和“不响应 Escape / backdrop”的交互语义;RPG 首页个人中心里的昵称修改、账户充值、每日任务和兑换码弹窗已迁移到 `UnifiedModal`,支付结果 / 支付确认遮罩 / 泥点账单这类头部结构不同的弹窗继续保留专用实现。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
- 2026-06-10 追加:`UnifiedModal` 新增 `showHeader`,用于收口不需要标准头部但仍要保留 dialog 无障碍语义、遮罩和层级控制的轻量弹窗;RPG 首页个人中心的支付结果提示与支付确认遮罩已迁移到 `showHeader={false}` 模式,业务页只保留 icon badge、文案与按钮,不再手写 backdrop、aria 和白底壳层。个人中心移动端顶栏“扫码”“打开设置”入口统一使用 `PlatformIconButton`,并继续保留 `.platform-profile-header__icon-button` 局部 class 控制位置与主题色。验证命令:`npm run test -- src/components/common/UnifiedModal.test.tsx src/components/common/PlatformIconButton.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
- 2026-06-10 追加:RPG 首页发现页分类筛选弹窗和个人中心扫码面板改用 `UnifiedModal` 承接 backdrop、dialog 语义和层级;分类筛选保留本地选项 / 动作布局,扫码面板继续使用 `showHeader={false}` 保留深色自定义头部与摄像头 viewport,并显式维持 `closeOnBackdrop={false}`、`closeOnEscape={false}`。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/common/UnifiedModal.test.tsx`。
- 2026-06-10 追加:RPG 首页个人中心泥点账单改用 `UnifiedModal showHeader={false}` 承接 `dialog` 语义和遮罩层级,同时保留渐变面板、`PlatformModalCloseButton variant="floating"`、余额 badge 与账单列表布局;账单继续显式维持 `closeOnBackdrop={false}`、`closeOnEscape={false}`,测试改为直接断言具名 dialog 和关闭后卸载。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "opens wallet ledger modal from narrative coin card|wallet ledger modal shows empty and error states" src/components/common/UnifiedModal.test.tsx`。
- 2026-06-10 追加:RPG 首页个人中心“玩过作品”面板改用 `UnifiedModal showHeader={false}` 承接 `dialog` 语义和遮罩层级,同时保留 `PLAYED` kicker、总时长 badge、`PlatformModalCloseButton variant="floating"`、`可继续 / 玩过` 双分区与作品卡布局;存档入口继续留在同一个“玩过”面板内,不再回退成独立 `SAVE ARCHIVE` / `ARCHIVE` 壳层。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile played modal summary and work type use platform pill badges|profile played modal empty state uses platform empty state" src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "authenticated users can open save archives from the profile played panel|profile page keeps save archives inside played stats panel" src/components/common/UnifiedModal.test.tsx`。
- 2026-06-10 追加:RPG 首页个人中心邀请相关弹层里的 live `community / redeem` 分支改用 `UnifiedModal showHeader={false}` 承接 `dialog` 语义和遮罩层级,同时保留 `PlatformModalCloseButton variant="floatingPlain"`、居中标题、社区二维码卡片、邀请码输入 / 已填写空态和成功 / 失败提示;历史 `invite` 分支没有新的入口,当前只随同一壳层维持现状。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile community shortcut shows reward subtitle and invited users|invite query opens redeem modal directly for logged in users|profile redeem invite query modal submits code after login" src/components/common/UnifiedModal.test.tsx`。
- 2026-06-10 追加:RPG 首页个人中心昵称旁的铅笔入口改用 `PlatformIconButton`,继续保留 `.platform-profile-edit-button` 局部尺寸、边框和浅色底样式;昵称编辑入口不再手写原生 `<button>` 的 `type`、`aria-label` 和图标壳。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile nickname modal uses platform text field and submits with Enter" src/components/common/PlatformIconButton.test.tsx`。
- 2026-06-09 追加:RPG 大编辑器暗色面板内的保存和角色槽动作继续走本地 `ActionButton`,不再混用白底平台 `platform-button` class;平台白底动作收口和编辑器暗色动作收口保持两套视觉边界。
- 2026-06-10 追加:`PlatformActionButton surface="editorDark"` 承接 RPG 暗色弹窗 / 运行面板里的普通取消、确认、刷新和编组动作,支持 `size="xxs"` 与 `tone="success" | "warning"``tone="accent"` 承接暗色壳层内的琥珀实心 CTA`tone="accentSoft"` 承接依赖局部 accent 变量的柔和强调按钮。角色自定义 footer、自定义世界生成 footer、地图切换确认、营地编组普通动作和角色聊天刷新动作已迁移。暗色可选项卡仍使用 `PlatformDarkOptionCard`,像素风发送 / 强品牌动作继续保留专用布局。验证命令:`npm run test -- src/components/common/platformActionButtonModel.test.ts src/components/common/PlatformActionButton.test.tsx src/components/SelectionCustomizationModals.test.tsx src/components/CompanionCampModal.test.tsx src/components/MapModal.test.tsx src/components/CharacterChatModal.test.tsx`。
- 2026-06-10 追加:RPG 首页创作 / 草稿顶栏的钱包快捷入口通过同文件 `TopbarWalletShortcutButton` 复用 `PlatformActionButton tone="accentSoft" shape="pill" size="xs"` 与 `PlatformIconBadge`;移动端 / 桌面端继续保留 `.platform-mobile-create-wallet-chip`、`.platform-desktop-create-wallet-chip` 和 `.platform-desktop-search` 兼容 class,承接余额截断、桌面顶栏胶囊壳和既有测试锚点,点击语义仍统一走 `openRechargeOrRewardCodeModal`。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
- 2026-06-10 追加:RPG 大编辑器里的当前角色、可选角色、预设背景和场景连接关系等暗色信息面板通过本地 `EditorInfoPanel` 复用 `PlatformSubpanel surface="dark"`;有右侧动作的面板也只向适配器传 actions,不再在业务 JSX 中重复手写暗色面板边框、底色、圆角、标题行和内容间距。验证命令:`npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx -t "场景编辑器会在场景内展示槽位化多幕配置并保存"`。
- 2026-06-10 追加:作品详情底部“作品改造 / 作品编辑”和“启动”使用 `PlatformActionButton surface="platform" shape="pill" size="lg" fullWidth`;详情页保留 `platform-work-detail__remix / start` 局部 class 控制 sticky 底部栏位置、比例和品牌背景。验证命令:`npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformActionButton.test.tsx`。
- 2026-06-10 追加:作品详情点赞按钮使用 `PlatformActionButton tone="accentSoft"`;详情页只保留纵向排布、尺寸和 `--platform-action-accent` 局部变量,不再手写点赞按钮边框、底色、文字和阴影 chrome。验证命令:`npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/common/platformActionButtonModel.test.ts`。
- 2026-06-09 追加:大鱼吃小鱼结果页白底平台动作迁移到 `PlatformActionButton shape="pill" size="xs"`;资产工坊关闭 / 生成正式图、关卡主图 / 待机 / 移动入口和场地背景生成只保留业务回调,深色 hero 返回 / 测试 / 发布按钮继续保留玩法品牌布局。
- 2026-06-10 追加:大鱼吃小鱼结果页 hero 顶部的玩法摘要 chip 使用 `PlatformPillBadge tone="lightOverlay"`,并只保留局部 `bg-white/10` 覆盖;hero 只保留 `coreFun / ecologyTheme / levelCount` 文案,不再手写三段白色静态标签。验证命令:`npm run test -- src/components/big-fish-result/BigFishResultView.test.tsx -t "renders generated formal previews with accurate status copy"`。
- 2026-06-10 追加:反馈页“查看反馈与投诉记录”这类页面内次级文本动作使用 `PlatformActionButton tone="ghost" shape="pill" size="xs"`;反馈页只保留提示回调,不再手写居中、字号、内边距和冷色文本按钮 class。验证命令:`npm run test -- src/components/platform-entry/PlatformFeedbackView.test.tsx src/components/common/PlatformActionButton.test.tsx`。
- 2026-06-10 追加:创作中心作品卡积分激励的“领取积分 / 领取中”按钮使用 `PlatformActionButton tone="secondary" size="xxs"`;作品卡保留 `creation-work-card-incentive__button` 局部 class 承接三列布局、移动端跨列、紧凑高度和玻璃底,同时保留点击 / 键盘冒泡拦截,避免触发整卡打开。验证命令:`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/common/PlatformActionButton.test.tsx src/index.test.ts`。
- 2026-06-09 追加:敲木鱼 fallback 返回、跳一跳结算、拼消消 runtime header / 结算弹窗等白底 HUD 动作使用 `PlatformActionButton`,拼消消 runtime 白底错误条使用 `PlatformStatusMessage surface="platform"`;深色半透明游戏提示和强品牌按钮仍可保留 runtime 专用布局。
- 2026-06-10 追加:运行态短错误 / 成功 / 命中反馈 chip 使用 `PlatformRuntimeStatusToast` 承接圆角、字号、阴影、色值和 `role="alert/status"` 语义;跳一跳、拼图、敲木鱼、方洞和宝贝爱画运行态短 toast 已迁移。玩法专属返回按钮、计分牌、蓄力提示和强品牌主按钮仍留在 runtime 壳层,不把位置和玩法资产耦合进公共 Module。验证命令:`npm run test -- src/components/common/PlatformRuntimeStatusToast.test.tsx src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx src/components/wooden-fish-runtime/WoodenFishRuntimeShell.test.tsx src/components/square-hole-runtime/SquareHoleRuntimeShell.test.tsx src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.test.tsx`。
- 2026-06-09 追加:历史图片 / 历史素材 / 可引用素材选择统一使用 `src/components/common/PlatformAssetPickerCard.tsx` 中的 `PlatformAssetPickerCard` 与 `PlatformAssetPickerGrid`,由该 Module 承载缩略图、禁用态、选中态、边框、hover、主副文案、`ResolvedAssetImage` 壳层、错误态、读取态、空态和网格布局;拼图历史图片弹窗、方洞历史生成、视觉小说历史素材选择器、RPG 大编辑器历史素材弹窗和抓大鹅封面编辑可引用素材网格已先迁移,业务页只传素材数组、素材地址、文案、可访问名称、surface、选中判断和选择回调。RPG 大编辑器等暗色弹窗使用 `surface="editorDark"`,不混用白底平台卡片视觉;场景横图通过 `imageShellClassName` 保留 16:9。
- 2026-06-09 追加:平台白底圆角输入框和文本域统一使用 `src/components/common/PlatformTextField.tsx` 承载 input / textarea 语义、基础边框、背景、内边距、字号 / 行高、密度和禁用态;同组下拉框使用 `PlatformSelectField` 复用同一输入 chrome。抓大鹅结果页作品名称 / 描述、封面描述、素材名称、批量新增 / 批量重生成物品名称,方洞结果页主信息表单和形状 / 洞口选项字段,拼图结果页作品信息 / 关卡名称 / 智能修订输入,敲木鱼结果页作品标题 / 简介,敲木鱼创作工作台功德词条输入,creative-agent 模板确认调整弹层关卡数输入,拼消消创作工作台作品标题 / 简介 / 主题词、跳一跳创作工作台主题,以及视觉小说结果页音乐生成、作品信息、开场、运行配置、角色、场景、阶段和世界观普通文本 / 下拉字段已先迁移,业务页只保留受控值、事件、可访问名称、占位符、选项和局部布局 class。同一面板内的主图上传和提示词参考图上传必须使用不同可访问名称,避免多个同名“上传参考图”入口让测试和读屏语义混淆;拼图关卡编辑中的描述参考图入口使用“上传描述参考图”。
- 2026-06-09 追加:通用创作图片输入面板的提示词文本域也使用 `PlatformTextField variant="textarea" density="roomy"`;图片面板只通过局部 class 保留高度、`pb-14` 和浮动参考图上传按钮避让,不再自己维护白底 textarea 边框、背景、字号和禁用态。
- 2026-06-09 追加:`PlatformTextField` / `PlatformSelectField` 的 `tone="warm" | "rose" | "emerald"` 统一承接平台表单焦点色;视觉小说创作工作台、统一抓大鹅创作工作台、汪汪声浪轻配置编辑器和宝贝识物工作台普通输入 / 文本域 / 下拉框已先迁移,玩法调性焦点色通过 tone 表达,不在业务 JSX 中重复拼 `focus:border-* focus:ring-*`。
- 2026-06-10 追加:`PlatformTextField` / `PlatformSelectField` 支持 `surface="editorDark"` 和 `tone="sky"`,承接 RPG 暗色弹窗 / 运行面板里的普通输入框、文本域、下拉框、禁用态、密度、字号和焦点色;自定义选择弹窗角色名字 / 背景补充 / 生成模式 / 世界描述和角色聊天草稿已迁移,业务 JSX 不再手写暗色 `border-white/10 bg-black/30 px-4 py-3` 或 `focus:border-*` 输入 chrome。验证命令:`npm run test -- src/components/common/PlatformTextField.test.tsx src/components/SelectionCustomizationModals.test.tsx src/components/CharacterChatModal.test.tsx`。
- 2026-06-10 追加:`PlatformTagEditor` 内部新增标签输入框也使用 `PlatformTextField density="compact" size="xs"`;标签编辑器只保留新增状态、解析、Enter / Escape 行为和按钮组合,不再手写白底 input chrome。
- 2026-06-10 追加:认证图形验证码答案输入使用 `PlatformTextField density="compact"`;验证码组件只保留 challenge 展示、答案受控值和变更回调,不再手写 `platform-input` 输入框 chrome。
- 2026-06-10 追加:认证入口的短信 / 密码登录、重置密码、绑定手机号、邀请码和账号安全表单字段使用 `PlatformTextField surface="platform"` 与 `PlatformFieldLabel variant="form"`;认证业务组件只保留受控值、登录 / 绑定流程、原生 input 属性和校验提示,字段可访问名称继续由外层原生 `label` 承接,不再手写 `platform-input` 或表单标题 class。
- 2026-06-10 追加:个人中心兑换码和邀请兑换输入使用 `PlatformTextField surface="platform"`;业务组件只保留兑换 / 邀请码提交、归一化、大写展示、Enter 提交和原生可访问名称,不再手写 `platform-profile-input` 或白底 input chrome。
- 2026-06-10 追加:个人中心昵称弹窗输入框使用 `PlatformTextField surface="editorDark" size="lg" density="roomy"`;业务组件保留原生 `label` / sr-only “新昵称”、`autoFocus`、`maxLength`、Enter 提交、昵称校验和保存流程,不再手写暗色 input chrome。
- 2026-06-10 追加:平台反馈页问题描述和联系电话字段使用 `PlatformTextField surface="platform"`,标题使用 `PlatformFieldLabel variant="form"`;反馈页保留外层原生 label、受控值、长度限制、透明嵌入式局部 class 和提交校验,不再手写 textarea / input / 字段标题 chrome。验证命令:`npm run test -- src/components/platform-entry/PlatformFeedbackView.test.tsx src/components/common/PlatformTextField.test.tsx src/components/common/PlatformFieldLabel.test.tsx`。
- 2026-06-09 追加:平台字段标签统一使用 `src/components/common/PlatformFieldLabel.tsx` 承载 `field`、`section`、`form`、`pill` 与 `accentPill` 五类字段标题视觉;视觉小说结果页、汪汪声浪轻配置编辑器和宝贝识物工作台已先迁移,业务页只保留字段文案和必要局部布局 class,不再重复拼普通字段名、分区标题、表单标题、普通胶囊和强调胶囊 class。
- 2026-06-10 追加:通用创作图片输入面板的主图标题和提示词标题使用 `PlatformFieldLabel variant="form"`;提示词字段保留外层原生 `label htmlFor`,业务组件只保留字段文案、布局和上传 / 生成交互,不再手写 `mb-2 block text-sm font-black` 标题 class。
- 2026-06-10 追加:个人中心存档 / 玩过弹窗里的简单空态使用 `PlatformEmptyState surface="subpanel" size="inline"`,玩过弹窗的“可继续 / 玩过”分区标题使用 `PlatformFieldLabel variant="section"`,已玩作品白底按钮卡使用 `PlatformSubpanel as="button" surface="flat" radius="sm" padding="md" interactive``SaveArchiveCard` 因含图片遮罩和加载态暂不并入本轮。
- 2026-06-10 追加:creative-agent 首页抽屉无创作记录使用 `PlatformEmptyState surface="subpanel" size="inline"`;抽屉只保留历史记录分组和点击行为,不再手写 bordered empty chrome。验证命令:`npm run test -- src/components/creative-agent/CreativeAgentHome.test.tsx src/components/common/PlatformEmptyState.test.tsx`。
- 2026-06-10 追加:平台入口壳纯 Suspense fallback 使用 `PlatformSubpanel radius="sm" padding="none"` 承接原 `platform-subpanel` 外壳;带恢复动作、错误语义或运行态遮罩的提示面板不和纯加载 fallback 同批迁移。
- 2026-06-10 追加:平台入口作品详情读取 / 错误提示、Agent 工作区恢复提示和生成结果恢复面板也迁移到 `PlatformSubpanel`;普通提示使用 `radius="sm" padding="none"`,带恢复动作的 `CreationResultRecoveryPanel` 使用 `radius="xl" padding="none"`,玩法 runtime overlay 继续保留专用层级语义。验证命令:`npm run test -- src/components/common/PlatformSubpanel.test.tsx src/components/platform-entry/PlatformEntryFlowShellImpl.test.ts`。
- 2026-06-10 追加:RPG runtime 主阶段路由里的平台首页、角色选择和冒险面板懒加载提示使用 `PlatformSubpanel radius="sm" padding="none"`;路由器只保留 Suspense 分流和提示文案,运行态 HUD / overlay 不并入该普通提示面板规则。验证命令:`npm run test -- src/components/rpg-runtime-shell/RpgRuntimeStageRouter.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 追加:个人中心钱包账单弹窗的“暂无账单记录”使用 `PlatformEmptyState surface="subpanel" size="inline"`,账单行使用 `PlatformSubpanel as="div" surface="flat" radius="xs" padding="none"`;业务 JSX 只保留来源、时间、收支色值、余额右对齐和局部间距 / 阴影。
- 2026-06-10 追加:个人中心邀请弹窗里的社区二维码卡、邀请码展示卡、成功邀请容器和邀请用户行使用 `PlatformSubpanel`,简单空态使用 `PlatformEmptyState`,小标题使用 `PlatformFieldLabel variant="section"`;外层弹窗、query 自动打开、复制邀请和提交邀请码状态机不随 UI chrome 收口改动。
- 2026-06-10 追加:个人中心邀请弹窗里的邀请奖励说明使用 `PlatformStatusMessage tone="warning" surface="profile" size="md"`;弹窗只保留奖励文案和两行排版,不再手写 amber 提示块。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile community shortcut shows reward subtitle and invited users"`。
- 2026-06-10 追加:个人中心任务中心任务条目使用 `PlatformSubpanel radius="sm" padding="md"` 承接原 `platform-subpanel` 外壳;业务组件只保留任务标题、进度、奖励、状态和领取按钮逻辑。
- 2026-06-10 追加:个人中心充值弹窗微信 Native 支付二维码确认面板使用 `PlatformSubpanel radius="sm" padding="md"`;业务组件只保留二维码生成、扫码展示和确认支付按钮流程。
- 2026-06-10 追加:个人中心充值弹窗商品整卡按钮使用 `PlatformSubpanel as="button" surface="platform" radius="sm" padding="none" interactive`;商品标题、金额、角标、购买中态和购买回调留在业务组件,按钮壳、hover、focus、默认 type 与 disabled chrome 归公共组件。验证命令:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile recharge modal trusts per-product first bonus display after points recharge"`、`npm run test -- src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 追加:个人中心充值商品卡里的“购买 / 处理中”胶囊暂不抽共享组件;该胶囊位于 `PlatformSubpanel as="button"` 内部,直接复用 `PlatformActionButton` 会形成嵌套交互,当前也还没有第二个同形态的非交互 action chip 证明需要单独沉淀共享展示基元。
- 2026-06-09 追加:抓大鹅结果页作品信息、发布封面和物品素材详情中的 section 字段标题迁移到 `PlatformFieldLabel variant="section"`;业务页不再重复拼 `text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]`。
- 2026-06-09 追加:方洞结果页主信息、形状选项、洞口选项和历史生成标题迁移到 `PlatformFieldLabel variant="section"`;业务页只保留字段文案、图标和按钮布局,不再重复拼 section 标题 class。
- 2026-06-09 追加:拼图结果页关卡详情的“关卡名称”和发布弹窗的“发布检查 / 封面关卡”标题迁移到 `PlatformFieldLabel variant="section"`;业务页保留 label 关联和弹窗布局,不再重复拼 section 标题 class。
- 2026-06-09 追加:拼消消创作工作台作品标题 / 简介 / 主题词、跳一跳创作工作台主题、大鱼素材弹窗 prompt 和 RPG 发布弹窗发布检查 / 封面设置迁移到 `PlatformFieldLabel variant="section"`;业务组件内不再直接出现 `text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]` section 标题 class,后续同类标题只从公共 Module 扩展。
- 2026-06-09 追加:平台白底分段 Tab / 二选一统一使用 `src/components/common/PlatformSegmentedTabs.tsx` 承载选项、当前 id、变更回调、响应式列数、尺寸、圆角、surface、截断标签、禁用态和 `aria-pressed`;拼图结果页、抓大鹅结果页、抓大鹅素材配置、视觉小说结果页和 creative-agent 模板确认弹窗已先迁移,业务页不再重复拼 `grid + border + bg-white/62 + button aria-pressed`。
- 2026-06-09 追加:`PlatformSegmentedTabs` 支持 `columns="four"`、`size="choice"`、`tone="warm" | "rose"`、`surface="transparent"` 和 `frame="bare"`,用于承接创作 / 结果页里的四选一配置项;抓大鹅创作工作台和结果页难度选择已迁移,业务页只保留难度选项、当前值和派生回调。
- 2026-06-09 追加:`PlatformSegmentedTabs` 支持 `columns="one"`、`size="tab"`、`tone="underline"` 和 `semantics="tabs"`,用于承接认证入口短信 / 密码登录切换的真实 Tab 语义;认证页不再维护本地 `LoginTabButton`、`role="tab"`、`aria-selected` 和下划线选中态。登录入口不可用的白底提示也迁移到 `PlatformSubpanel`。
- 2026-06-09 追加:平台结果页统计小卡和轻量状态 chip 统一使用 `src/components/common/PlatformStatGrid.tsx` 承载 `items`、响应式列数、密度、surface、对齐和 label/value 顺序;拼消消结果页素材摘要、方洞结果页封面状态 chip 和抓大鹅结果页难度摘要已迁移,业务页不再重复拼统计卡 `grid + rounded + bg-white/* + text-xl/text-xs`。
- 2026-06-09 追加:平台单个胶囊状态 / 标签 chip 统一使用 `src/components/common/PlatformPillBadge.tsx` 承载 tone、尺寸、图标、圆角、边框、底色和字号;宝贝识物结果页发布状态、主题标签与占位资源 overlay,宝贝识物 / 拼图 / 抓大鹅 / 视觉小说工作台 BETA chip、汪汪声浪轻配置 chip、汪汪声浪结果页草稿 chip、汪汪声浪预览 VS chip、敲木鱼结果页飘字 chip、creative-agent 过程计数 / 条目 meta chip、通用音频输入面板限制标签、抓大鹅 / RPG / 拼图 / 方洞结果页自动保存状态、抓大鹅结果页当前难度 badge、拼图结果页关卡生成中 overlay / 列表 badge、大鱼吃小鱼结果页终局 / 关卡元信息 / 发布校验成功 badge、汪汪声浪生成页和通用生成页右上状态 badge、RPG 开发资产诊断数量 / 加载状态 badge、RPG 发布弹窗封面来源 badge、账号弹窗主题状态 / 会话数量 / 设备状态 badge、创作类型弹层锁定 badge、拼图图库详情页题材标签、自定义世界作品卡二级 badge 和生成失败 chip 已先迁移,业务页不再重复拼 `rounded-full border bg-* text-* px-* py-*`。多项数值 / 标签摘要仍归 `PlatformStatGrid`,可交互标签编辑仍归 `PlatformTagEditor`。
- 2026-06-09 追加:`PlatformPillBadge` 支持 `profile` / `profileAccent` 个人中心玫瑰色 chip tone;泥点账单余额、玩过总时长和玩过作品类型 chip 已迁移,个人中心后续轻量状态 / 分类胶囊不再在业务 JSX 中重复拼 rose / zinc 胶囊 class。
- 2026-06-10 追加:`PlatformPillBadge` 支持 `neutralSolid` 实心中性 tone,承接无强调的只读状态胶囊;`PlatformToggleRow mode="status"` 的开启 / 关闭状态已迁移到 `platformPillBadgeModel`,整行开关不再手写中性 pill class。
- 2026-06-10 追加:`PlatformPillBadge` 支持 `lightOverlay` 浅色叠层 tone,承接主动作按钮内部的泥点消耗等小胶囊;通用创作图片面板和抓大鹅创作工作台提交按钮内的消耗标签已迁移,业务 JSX 不再手写 `rounded-full bg-white/24 px-2 py-0.5`。
- 2026-06-10 追加:`PlatformPillBadge` 支持 `size="xxs"` 承接密集目录元信息 chip;自定义世界实体目录的新生成、生成中进度、开局 CG 消耗 / 时长 / 已生成、批量删除已选数量和可扮演角色元信息 chip 已迁移,实体目录不再手写 `platform-pill platform-pill--* px-2.5 py-1 text-[10px]`。
- 2026-06-10 追加:creative-agent 工作台顶部阶段状态 chip 迁移到 `PlatformPillBadge tone="cool" size="xs"`;工作台只保留阶段枚举到文案的映射,不再手写 `platform-pill platform-pill--cool` 外观。
- 2026-06-10 追加:RPG 首页公开作品卡标签、趋势卡标签、公开作品搜索结果类型、充值商品角标、移动端创建入口、桌面发现 hero / 今日 / 最近作品 / 最近浏览 chip 迁移到 `PlatformPillBadge`,首页不再手写 `platform-pill platform-pill--neutral / warm / cool`。
- 2026-06-10 追加:RPG 世界详情页的发布状态、主题、作者、发布时间 / 可见性和展示标签等静态元信息 chip 迁移到 `PlatformPillBadge`;作品号复制和分享入口仍保留 `CopyCodeButton` / `CopyFeedbackButton` 管复制状态。
- 2026-06-10 追加:`CopyFeedbackButton` 支持 `actionAppearance="pill"``CopyCodeButton` 透传同一入口,并复用 `platformPillBadgeModel.ts` 的 `getPlatformPillBadgeClassName` 视觉 chrome;可点击复制 / 分享胶囊 chip 不再在业务 JSX 中手写 `platform-pill`RPG 世界详情作品号复制 / 分享入口和抓大鹅批量新增 / 重生成物品名称预览已迁移。
- 2026-06-10 追加:平台作品详情页主题标签使用 `PlatformPillBadge tone="neutralSolid" size="sm"`,作品号复制按钮使用 `CopyCodeButton actionAppearance="pill" actionPillTone="neutralSolid" actionPillSize="sm"`;详情页只保留标签映射、作品号复制状态和顶部外边距,不再手写 `platform-work-detail__chip / code` 基础 chrome。验证命令:`npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformPillBadge.test.tsx src/components/common/CopyCodeButton.test.tsx`。
- 2026-06-10 追加:平台作品详情页分享复制反馈使用 `PlatformStatusMessage surface="platform"`,按 `shareState` 映射 `success / error`;详情页保留 `useCopyFeedback` 状态机和文案,不再让失败态复用成功 toast chrome。验证命令:`npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-10 追加:平台错误弹窗和生成完成弹窗的“字段展示 + 复制整段报告”能力统一收口到 `src/components/common/PlatformReportDialog.tsx``PlatformErrorDialog` 与 `PlatformTaskCompletionDialog` 只保留标题、字段语义和错误黑名单过滤,不再各自组合 `UnifiedModal`、`PlatformInfoBlock`、`CopyFeedbackButton` 与 `useCopyFeedback`。验证命令:`npm run test -- src/components/common/PlatformReportDialog.test.tsx src/components/platform-entry/PlatformErrorDialog.test.tsx src/components/platform-entry/PlatformTaskCompletionDialog.test.tsx`。
- 2026-06-10 追加:`CopyFeedbackButton` 支持 `actionShape`,用于共享复制状态按钮直接对齐 `PlatformActionButton` 的圆角外观;拼图广场详情页 hero 的分享按钮已使用 `actionSurface="editorDark" actionShape="pill"`,修改作品 / 进入第 1 关动作使用 `PlatformActionButton`,返回和封面轮播前后按钮使用 `PlatformIconButton darkMini`。验证命令:`npm run test -- src/components/common/CopyFeedbackButton.test.tsx src/components/puzzle-gallery/PuzzleGalleryDetailView.test.tsx`。
- 2026-06-10 追加:creative-agent 首页的侧边栏菜单、账号入口、开启新对话、我的创作、首页激励 CTA 和 prompt suggestion 按钮迁移到 `PlatformIconButton` / `PlatformActionButton`,但继续保留 `creative-agent-home__*` 本地 class 承接透明顶栏和抽屉品牌视觉;收口按钮语义时不强行同时抹平定制视觉。验证命令:`npm run test -- src/components/creative-agent/CreativeAgentHome.test.tsx`。
- 2026-06-10 追加:像 `creative-agent-drawer__history-item` 这种纯文本轻量列表行,当前不为了单点场景单独新建共享组件;现阶段优先沿用 `PlatformActionButton` 承接动作行、`PlatformSubpanel as="button" interactive` 承接有壳列表行,等出现更多同构透明列表行再评估独立 row primitive。
- 2026-06-10 追加:绑定手机号页左侧“当前登录身份”提示块迁移到 `PlatformSubpanel radius="sm" padding="md"`;认证页只保留身份文案和绑定流程,不再手写 `platform-subpanel` 信息块壳。验证命令:`npm run test -- src/components/auth/BindPhoneScreen.test.tsx`。
- 2026-06-10 追加:大鱼吃小鱼结果页 hero 的返回入口迁移到 `PlatformIconButton darkMini`,测试 / 发布动作迁移到 `PlatformActionButton surface="editorDark"`;结果页只保留测试运行、发布状态和提交语义,不再手写 hero 顶栏按钮壳。验证命令:`npm run test -- src/components/big-fish-result/BigFishResultView.test.tsx`。
- 2026-06-10 追加:`PlatformPillBadge` 支持 `darkSoft` / `darkNeutral` / `darkSky` / `darkEmerald` / `darkAmber` / `darkRose` 暗色 tone,用于 RPG 暗色弹窗和角色详情里的纯展示 chip;角色身份 / 等级、技能列表出手方式、技能详情方式 / 风格 / 状态标签、地图节点方向标签、地图场景切换方向标签和营地编组状态数值已迁移。暗色动作按钮、runtime HUD、属性加成动态 pill 和按钮内部消耗 chip 暂不直接套静态 badge。
- 2026-06-10 追加:背景故事已解锁 / 需好感状态和好感等级 badge 也使用 `PlatformPillBadge` 的 `dark*` tone;好感进度时间轴刻度、runtime HUD 和带点击卡片视觉的标签仍保留专用布局。
- 2026-06-10 追加:RPG 角色资产工作室动作列表的生成中 / 已生成 / 待生成状态 chip 直接使用 `PlatformPillBadge` 的 `darkAmber` / `darkEmerald` / `darkNeutral` tone;父弹窗不再维护本地 `StatusBadge` 浅封装,动作生成按钮仍保留工作室专用暗色按钮布局。
- 2026-06-10 追加:NPC 交易物品数量、赠礼好感增量和背包工坊材料需求状态使用 `PlatformPillBadge` 的 `dark*` tone;这些只是纯展示 chip,交易 / 赠礼列表按钮和工坊锻造 / 合成动作按钮继续保留各自交互布局。
- 2026-06-10 追加:RPG 角色编辑器技能列表里的动作已生成 / 待生成动作状态直接使用 `PlatformPillBadge` 的 `darkEmerald` / `darkNeutral` tone;本地 `StatusBadge` 浅封装删除,技能编辑按钮卡片仍保留原有点击布局。
- 2026-06-10 追加:RPG 角色编辑器两处重复的已应用主图 / 已应用动作 chip 合并为局部 `RoleAssetAppliedBadges`,内部复用 `PlatformPillBadge darkEmerald / darkAmber`;场景角色选择列表的选择 / 已选中和地标连接列表的当前连接也使用 `PlatformPillBadge dark*`,但外层按钮卡片仍保留原交互语义。
- 2026-06-10 追加:RPG 作品封面来源状态使用 `PlatformPillBadge darkNeutral`,角色开局物品标签合并为局部 `RoleInitialItemTagBadges` 并复用 `PlatformPillBadge darkNeutral`;物品编辑弹窗和开局物品列表不再重复维护标签 chip class。
- 2026-06-10 追加:RPG 世界地图节点中的当前状态使用 `PlatformPillBadge tone="muted"` 复用平台白底柔和 badge chrome;地图节点位置、连线和整体卡片仍保留地图专用布局。
- 2026-06-10 追加:媒体 / 舞台预览上的非交互悬浮短标签使用 `src/components/common/PlatformOverlayBadge.tsx`,复合控件内部的紧凑槽位编号使用 `src/components/common/PlatformSlotBadge.tsx`RPG 场景幕预览左上幕标签和每幕角色槽位“主 / 2 / 3”已迁移。普通状态 chip 继续使用 `PlatformPillBadge`,外层按钮卡片、人物舞台位置和运行态 HUD 不迁入这两个小 Module。
- 2026-06-10 追加:拼图结果页智能修订条的白底图标圆槽使用 `PlatformIconBadge tone="soft" size="sm"`,外层编辑条使用 `PlatformSubpanel radius="lg"`;结果页只保留提交、禁用和错误提示语义,不再手写 `platform-subpanel rounded-[1.35rem] p-3 sm:p-4` 或 `hidden h-9 w-9 rounded-full bg-white/72`。
- 2026-06-10 追加:拼图结果页关卡卡片外壳使用 `PlatformSubpanel radius="lg" padding="none"`,关卡列表只保留图片、生成中状态、标题打开和删除动作,不再手写 `platform-subpanel overflow-hidden rounded-[1.35rem] p-0`。
- 2026-06-10 追加:`PlatformOverlayBadge` 支持 `tone="muted"`、`size="compact"` 和 `offset="tight"`,用于素材缩略图右上角“占位图”等紧凑非交互浮层;宝贝识物结果页占位资源标记已从绝对定位的 `PlatformPillBadge` 迁移到 overlay badge。
- 2026-06-10 追加:`PlatformSlotBadge` 支持 `tone="soft"` 和 `size="md"`,用于 creative-agent 阶段时间线的白底柔和步骤圆点;阶段卡片本体与 active / done / idle 语义仍保留在 `CreativeAgentStageTimeline`。
- 2026-06-10 追加:物品格、奖励格等缩略图右下角数量使用 `src/components/common/PlatformQuantityBadge.tsx`;背包物品格和 RPG 冒险面板 / 覆盖层奖励物品数量已迁移。该 Module 只承接数量角标 chrome,物品按钮、稀有度边框、选中态和详情弹窗仍归业务 Module。
- 2026-06-10 追加:RPG 冒险面板和覆盖层里的任务目标状态、任务日志状态、当前幕、剩余交谈等暗色纯展示 chip 使用 `PlatformPillBadge dark*`;任务 presentation / 日志状态只返回语义 tone,不再直接返回整段 `border / bg / text` class。运行态动作按钮、任务面板打开按钮和带 hover / click 语义的胶囊仍保留专用布局。任务日志状态补充验证命令:`npm run test -- src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx src/components/common/PlatformPillBadge.test.tsx -t "quest offer accept button|supports dark RPG badge tones"`。
- 2026-06-10 追加:RPG 角色面板里的标签数、适配倍数、性别和装备稀有度等暗色纯展示 chip 使用 `PlatformPillBadge darkNeutral / darkEmerald / darkAmber`;角色面板只保留标签数和 multiplier 计算,不再手写这些胶囊 chrome。
- 2026-06-10 追加:RPG 首页作品卡里的发布状态、元信息、主标签,以及存档卡右上恢复 / 最近游玩时间等暗色静态 chip 使用 `PlatformPillBadge dark*`;作品卡 / 存档卡只保留可点击卡片、删除动作、进入 / 继续创作箭头和业务文案。
- 2026-06-10 追加:自定义世界实体目录里的基础设定词条标签使用 `PlatformPillBadge darkSoft`;目录页只保留词条解析和空值展示逻辑,不再手写白字暗底 tag chrome。
- 2026-06-10 追加:RPG 实体编辑器基本设定里的拆分标签也使用 `PlatformPillBadge darkSoft`;编辑器只保留字段草稿、文本解析和保存逻辑,不再手写暗色静态 tag chrome。
- 2026-06-10 追加:`PlatformSubpanel` 支持 `surface="dark"`、`radius="xs"` 和 `padding="xs"`,用于 RPG 暗色编辑器 / 运行态里的非交互小信息卡;任务目标、区域、进度、描述、角色维度和角色形象状态已先迁移。暗色 HUD、动作按钮、可点击卡片和强玩法品牌面板继续保留业务布局。
- 2026-06-10 追加:`PlatformSubpanel` 支持 `surface="darkSky" | "darkEmerald" | "darkAmber" | "darkRose"`,用于 RPG 暗色编辑器 / 运行态里带业务色强调的结构化信息面板;实体详情私聊提示、队友收束、玩家等级进度、角色面板等级 / 收束状态、任务奖励好感度 / 货币 / 经验数值卡、RPG 大编辑器上传封面中提示、地图场景切换目标场景面板和 `CharacterInfoShared.MultiplierContributionList` 状态标签外壳已迁移。地图场景切换当前 / 前往摘要、营地编组分区、同行者卡和营地气氛小卡走 `surface="dark"` 非强调信息卡。后续同类 sky / emerald / amber / rose 暗色信息壳不再手写 `border-*-400/18 bg-*-500/8`,普通暗色信息卡不再手写 `border-white/* bg-black/*`。
- 2026-06-10 追加:自定义选择弹窗当前角色信息块使用 `PlatformSubpanel surface="dark"`;弹窗只保留角色标签文案,不再手写 `rounded-2xl border border-white/10 bg-black/20 px-4 py-3` 暗色纯展示块。验证命令:`npm run test -- src/components/SelectionCustomizationModals.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 追加:RPG 队伍面板和实体详情弹窗里的构筑标签效果详情统一由 `CharacterInfoShared.BuildContributionDetailPanel` 承接;标签概览、属性加成明细和无明细提示组合 `PlatformSubpanel surface="dark"`,业务弹窗只保留选中状态和属性 rows,不再复制同一段标签效果暗色面板 JSX。
- 2026-06-10 追加:`CharacterInfoShared.CharacterSkillsList` 的空态使用 `PlatformEmptyState surface="editorDark"`,可点击和只读技能卡使用 `PlatformSubpanel surface="dark"`;角色信息共享模块只保留技能 render id、选择回调、数值字段和标签展示语义,不再手写技能空态 / 技能卡暗色外壳。验证命令:`npm run test -- src/components/CharacterInfoShared.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformEmptyState.test.tsx -t "CharacterSkillsList|supports dark compact subpanel cards"`。
- 2026-06-10 验证补充:共享构筑状态标签外壳收口到 `PlatformSubpanel surface="darkSky"` 后,补跑 `npm run test -- src/components/CharacterInfoShared.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 追加:RPG 实体详情弹窗的物品空态使用 `PlatformEmptyState surface="editorDark"`,技能预览 fallback、技能数值卡、技能说明和附带状态标签区使用 `PlatformSubpanel surface="dark"`;实体详情只保留技能 / 物品数据和业务文案,不再手写这些暗色小卡 chrome。
- 2026-06-10 追加:RPG 实体详情弹窗最近回响中的后果、编年、载体和场景残留纯展示卡使用 `PlatformSubpanel surface="dark"`;实体详情只保留 story memory / 场景 residue 数据映射,队友收束等强调态继续保留业务语义样式。验证命令:`npm run test -- src/components/AdventureEntityModal.test.tsx src/components/common/PlatformSubpanel.test.tsx -t "最近回响|supports dark compact subpanel cards"`。
- 2026-06-10 追加:RPG 实体详情弹窗本地 `Section` 适配到 `PlatformSubpanel surface="dark"`;立绘、关系、私聊、最近回响、属性、技能和物品等主分区只保留标题与内容插槽,不再由业务组件维护 `rounded-2xl border border-white/8 bg-black/20 p-4` 外壳。验证命令:`npm run test -- src/components/AdventureEntityModal.test.tsx src/components/common/PlatformSubpanel.test.tsx -t "主分区|supports dark compact subpanel cards"`。
- 2026-06-10 追加:RPG 冒险统计弹窗的总览和统计卡使用 `PlatformSubpanel surface="dark"`;统计弹窗只保留统计字段、图标和总览文案,设置弹窗里的 range input、保存退出按钮和入口按钮继续保留运行态专用交互布局。验证命令:`npm run test -- src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx src/components/common/PlatformSubpanel.test.tsx -t "adventure statistics panel|supports dark compact subpanel cards"`。
- 2026-06-10 追加:RPG 覆盖层里的任务完成领奖提示、任务奖励缓存、战斗结束提示、战利品缓存和奖励物品详情描述 / 效果 / 标签使用 `PlatformSubpanel surface="dark"`,战斗结算敌人名使用 `PlatformPillBadge darkEmerald`;覆盖层只保留奖励数据、物品选择和弹窗层级语义,不再手写奖励缓存暗色面板和敌人名胶囊 chrome。验证命令:`npm run test -- src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformPillBadge.test.tsx -t "quest offer accept button|quest completion notice|battle reward modal|supports dark compact subpanel cards|supports dark RPG badge tones"`。
- 2026-06-10 追加:RPG 覆盖层里的任务摘要卡和任务奖励条使用 `PlatformSubpanel surface="dark"`,奖励条内物品数量使用 `PlatformQuantityBadge`;覆盖层只保留任务文案、奖励数据和物品选择语义,不再手写任务摘要 / 奖励条暗色外壳或数量角标 chrome。验证命令:`npm run test -- src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformQuantityBadge.test.tsx -t "quest reward strip|supports dark compact subpanel cards|renders a dark bottom-right quantity badge"`。
- 2026-06-10 追加:RPG 覆盖层里的任务奖励好感度、货币和经验数值卡使用 `PlatformSubpanel surface="darkRose" | "darkAmber" | "darkSky"`;覆盖层不再手写三套 `rounded-xl border bg-* px-3 py-2.5` 数值卡 chrome,也不再通过局部 class 覆盖 tint 调性。验证命令:`npm run test -- src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 追加:RPG 角色详情弹窗的装备格、背包格、旅程原因 / 目标、背景和性格小卡使用 `PlatformSubpanel surface="dark"`,候选人和性别静态 badge 使用 `PlatformPillBadge dark*` tone;角色详情只保留资料、属性、技能和动画展示语义,立绘框与属性网格暂保留原布局。验证命令:`npm run test -- src/components/CharacterDetailModal.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformPillBadge.test.tsx`。
- 2026-06-10 追加:RPG 角色面板详情里的个人线阶段、背景故事、性格纯展示块和装备行使用 `PlatformSubpanel surface="dark"`;角色面板只保留选中成员、个人线状态、展示文本和装备字段映射,像素外层面板与动作入口继续保留业务布局。验证命令:`npm run test -- src/components/CharacterPanel.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformPillBadge.test.tsx`。
- 2026-06-10 追加:好感状态卡的等级摘要和好感进度外壳使用 `PlatformSubpanel surface="dark"`;好感卡只保留等级推导、进度刻度和文案,不再手写 `rounded-xl border border-white/8 bg-black/20 px-* py-*` 暗色面板 chrome。验证命令:`npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/AffinityStatusCard.test.tsx`。
- 2026-06-10 追加:背景故事公开印象、已解锁章节和锁定章节外壳使用 `PlatformSubpanel surface="dark"`,无背景线索空档案使用 `PlatformEmptyState surface="editorDark"`;背景档案只保留章节状态、好感阈值和故事文案,不再手写这些暗色小卡 / 空态 chrome。验证命令:`npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/BackstoryArchive.test.tsx`。
- 2026-06-10 追加:NPC 交易弹窗的数量 stepper 外壳、库存计数条、详情容器和总价卡使用 `PlatformSubpanel surface="dark"`;交易弹窗只保留交易数量、库存、价格和禁用原因语义,交易物品 / 礼物 / 招募可选列表按钮改由 `PlatformDarkOptionCard` 承接暗色 selected / idle / hover chrome。验证命令:`npm run test -- src/components/NpcModals.test.tsx src/components/common/PlatformSubpanel.test.tsx -t "NPC 交易静态信息卡|supports dark compact subpanel cards"`。
- 2026-06-10 追加:背包文书、故事档案和工坊分区外壳,以及文书按钮、故事档案条目和工坊配方卡使用 `PlatformSubpanel surface="dark"`;工坊材料需求状态使用 `PlatformPillBadge dark*` tone,故事档案 QA 提示使用 `PlatformStatusMessage surface="editorDark"`。锻造 / 合成动作按钮继续保留业务交互布局。验证命令:`npm run test -- src/components/InventoryPanel.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformStatusMessage.test.tsx -t "背包文书|背包工坊|supports dark compact subpanel cards|supports editor dark surface"`。
- 2026-06-10 追加:NPC 交易详情里的装备位、即时使用和标签属性格使用 `PlatformSubpanel surface="dark" padding="row"`,使用效果提示使用 `PlatformStatusMessage surface="editorDark"`;物品详情弹窗只保留物品属性、效果和标签计算,不再手写 `rounded-lg border border-white/8 bg-black/20 px-3 py-2` 或 emerald 提示条 chrome。
- 2026-06-10 追加:新增 `PlatformDarkOptionCard` 承接 RPG 暗色弹窗 / 面板中的可选项按钮卡 selected / idle / hover / disabled chromeNPC 交易模式、交易物品行、赠礼候选、招募替换候选、角色素材工作室动作预览格和营地编组替换位按钮已迁移。业务组件只保留选中判断、tone、点击回调和卡片内容,不再手写 `rounded-* border px-3 py-*`、`border-*-400/* bg-*-500/10` 或 `border-white/* bg-black/20 hover:border-white/15`。
- 2026-06-10 追加:角色聊天弹窗的状态 / 总结卡使用 `PlatformSubpanel surface="dark"`,空聊天记录使用 `PlatformEmptyState surface="editorDark"`,建议回复按钮使用 `PlatformDarkOptionCard tone="sky"`;弹窗只保留角色状态、聊天记录和建议语义,不再手写这些暗色信息卡、空态或建议按钮 chrome。验证命令:`npm run test -- src/components/CharacterChatModal.test.tsx src/components/common/PlatformStatusMessage.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformDarkOptionCard.test.tsx`。
- 2026-06-10 追加:拼图首访 onboarding 提示词文本域使用 `PlatformTextField surface="editorDark"`,输入错误和登录保存错误使用 `PlatformStatusMessage surface="editorDark"`,生成 / 登录 CTA 使用 `PlatformActionButton surface="editorDark" tone="accent"`,跳过按钮使用 `PlatformActionButton surface="editorDark" tone="ghost" shape="pill"`onboarding 保留全屏沉浸壳层、登录 / 生成状态机和跳过行为,不再手写 textarea / 错误条 / 按钮 chrome。验证命令:`npm run test -- src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleOnboardingView.test.tsx src/components/common/PlatformStatusMessage.test.tsx src/components/common/PlatformTextField.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/common/platformActionButtonModel.test.ts`。
- 2026-06-10 追加:RPG 大编辑器本地 `SectionPanel` 适配到 `PlatformSubpanel surface="dark"`;可扮演角色背景故事 / 关系 / 技能 / 物品、世界基础设定等编辑分区只保留标题、subtitle、右侧动作和内容插槽,不再由本地适配器手写外层暗色面板 chrome。验证命令:`npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx src/components/common/PlatformSubpanel.test.tsx -t "可扮演角色技能动作状态|supports dark compact subpanel cards"`。
- 2026-06-10 验证补充:RPG 大编辑器上传封面中提示收口到 `PlatformSubpanel surface="darkSky"` 后,补跑 `npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx src/components/common/PlatformSubpanel.test.tsx -t "作品封面上传|tinted dark information panels"`。
- 2026-06-10 追加:RPG 角色形象参考图缩略框使用 `PlatformMediaFrame surface="editorDark"`;角色形象面板只保留参考图数组、上传 / 清空回调和状态文案,不再手写 `img + overflow-hidden + border` 缩略图 chrome。
- 2026-06-10 追加:营地编组同行者头像框使用 `PlatformMediaFrame surface="editorDark"` 和固定尺寸 class,保留角色图片 `object-contain`、放大比例与 pixelated 渲染;编组卡只保留角色数据和操作语义,不再手写头像框 `border-white/10 bg-black/25` 外壳。验证命令:`npm run test -- src/components/CompanionCampModal.test.tsx src/components/common/PlatformMediaFrame.test.tsx`。
- 2026-06-09 追加:平台普通进度条统一使用 `src/components/common/PlatformProgressBar.tsx` 承载 `progressbar` 语义、`platform-progress-track` 壳、填充宽度、最小可见宽度、尺寸、条内覆盖层、未知进度语义和局部主题色;creation-agent 主进度 / operation banner、RPG 结果页生成提示、RPG 实体目录生成中提示、开场 CG 生成占位、拼图关卡画面生成进度、生成页当前步骤线性进度、抓大鹅批量物品素材生成进度和自定义世界生成选择弹窗进度提示已先迁移,业务页只保留进度值、显示文案、状态配色和必要覆盖内容。没有准确百分比的脉冲占位条使用 `indeterminate`,不暴露假的 `aria-valuenow`;生成页环形总进度继续保留 `GenerationProgressHero` 专用 SVG。
- 2026-06-09 追加:creation-agent operation banner 的状态外壳迁移到 `PlatformStatusMessage surface="platform" remapSurface`,进度条继续使用 `PlatformProgressBar`;局部 platform token 作用域需要重映射时由 `remapSurface` 承接,不在业务 JSX 中继续手写 `platform-remap-surface platform-banner` 和 `platform-banner--*`。
- 2026-06-09 追加:平台只读信息块统一使用 `src/components/common/PlatformInfoBlock.tsx` 承载短标签、无标签纯正文、白底圆角边框、单行 / 多行正文排版和横向只读信息行的标签 / 值局部排版;错误弹窗和生成完成弹窗的来源、错误、状态展示、分享弹窗正文,以及汪汪声浪预览卡场景 / 形象 / 难度 / 声浪信息行已迁移,业务页不再重复拼 `rounded-[1rem] border ... bg-white/72 px-3 py-2`、`rounded-[1.25rem] border ... bg-white/72 p-4` 或 `rounded-[0.85rem] bg-white/74 px-* py-*`。
- 2026-06-10 追加:`PlatformInfoBlock` 支持 `variant="compactRow"` 承接预览卡密集横向 label / value 行;汪汪声浪预览卡四个信息行只保留 label 和内容,不再维护本地 `PREVIEW_INFO_*` class 常量。
- 2026-06-09 追加:平台白底子面板统一使用 `src/components/common/PlatformSubpanel.tsx` 承载 `platform-subpanel` 外壳、标题行、右侧动作区、强标题、圆角和响应式内边距;静态 element 透传 `aria-*` / `data-*` 等原生属性,便于结果页预览卡保留可访问名称。拼图结果页作品信息 / 标签编辑 / 智能修订条 / 关卡卡片、拼图图库详情页封面轮播壳 / 题材标签 / 关卡摘要、拼图图片生成模式选择器菜单外壳、敲木鱼结果页元信息 / 标签 / 飘字 / 音效、汪汪声浪结果页草稿摘要 / 素材槽 / 预览卡、通用音频输入面板和 RPG 个人中心未登录提示已先迁移。`surface="soft" padding="tight"` 用于标签编辑新增输入行等白底柔和紧凑行,不再手写 `rounded-[1rem] border ... bg-white/68 p-2``surface="soft" padding="row"` 用于上传预览横向已选素材条等白底柔和横向行,不再手写 `rounded-[1rem] border ... bg-white/68 px-3 py-2`;静态封面轮播壳使用 `radius="xl" padding="none"` 保留内部固定比例和轮播按钮;抓大鹅物品详情五视角面板使用 `radius="xl" padding="sm"` 加局部 `sm:p-5` 保留响应式间距。后续仅表达“白底子面板 + 标题 / 右侧动作 + 内容”或小型浮层菜单的片段优先使用该 Module;暗色运行态 HUD、媒体预览和强玩法品牌面板继续保留专用布局。
- 2026-06-10 追加:发布分享弹窗渠道 tile 按钮使用 `PlatformSubpanel as="button" surface="flat" radius="sm" padding="tight" interactive`;弹窗只保留渠道枚举、品牌图标和复制分享文本回调,不再手写白底 tile 圆角、边框、底色、hover 或 focus chrome。验证命令:`npm run test -- src/components/common/PublishShareModal.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 追加:平台入口创作类型弹层玩法卡片使用 `PlatformSubpanel as="button" surface="platform" radius="xl" padding="none"`;弹层只保留玩法图片、蒙版、锁定 badge、标题副标题和分流回调,外层按钮语义、标准圆角和已开放卡 hover / focus chrome 归公共子面板。验证命令:`npm run test -- src/components/platform-entry/PlatformEntryCreationTypeModal.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 追加:creation-agent 工作台聊天区外壳使用 `PlatformSubpanel radius="xl" padding="none"`;工作台只保留消息列表、引用图预览、错误提示和输入区语义,不再手写聊天面板外层圆角、边框和底色。验证命令:`npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 追加:creation-agent 无 session / 加载提示块迁移到 `PlatformSubpanel radius="sm" padding="lg"`;工作台只保留提示文案,不再手写 `platform-subpanel rounded-2xl px-5 py-4` 普通居中提示面板。
- 2026-06-10 追加:拼图结果页空草稿提示块迁移到 `PlatformSubpanel radius="sm" padding="lg"`;结果页只保留提示文案,不再手写 `platform-subpanel rounded-2xl px-5 py-4` 普通居中提示面板。
- 2026-06-09 追加:敲木鱼结果页主预览面板也迁移到 `PlatformSubpanel`,页面只保留标题、简介和资源叠放语义,不再手写 `platform-subpanel rounded-[1.25rem] p-4`。
- 2026-06-09 追加:拼消消创作工作台左侧结构化表单面板迁移到 `PlatformSubpanel`,工作台只保留字段、开关、错误和提交语义,不再手写 `platform-subpanel rounded-[1.25rem] p-4`。
- 2026-06-09 追加:抓大鹅创作工作台难度选择小面板迁移到 `PlatformSubpanel surface="flat"`,工作台只保留难度选项和 payload 派生,不再手写小白底面板边框、圆角、内边距和 inset 高光。
- 2026-06-09 追加:视觉小说创作工作台画风选择小面板迁移到 `PlatformSubpanel surface="flat"`,横向滚动、选中态和移动端 touch 行为仍由业务滚动区与样式按钮承接,不再手写外层白底面板 chrome。
- 2026-06-09 追加:创作中心作品架整块无作品 / 无筛选结果空态迁移到 `PlatformEmptyState surface="soft" size="panel"`,加载骨架卡迁移到 `PlatformSubpanel as="div"`Hub 只保留筛选、列表和打开 / 删除 / 分享语义,不再直接拼空态 `platform-subpanel` 或 skeleton 卡片外壳。
- 2026-06-09 追加:视觉小说上传资产弹窗的无历史素材本地上传占位迁移到 `PlatformEmptyState surface="dashed"`;弹窗只保留上传、AI 生成、历史素材和选择回调语义,不再手写 dashed 空态面板 chrome。
- 2026-06-09 追加:creative-agent 工作台目录、目标就绪、空消息、过程、关卡计划和模板确认理由等标准白底面板迁移到 `PlatformSubpanel`;模板确认的“关卡模式 / 计划关卡”摘要迁移到 `PlatformStatGrid`creative-agent 内不再直接拼 `platform-subpanel rounded-[1.35rem] p-4` / `rounded-[1.25rem] p-4` / `rounded-[1.15rem] p-4`。
- 2026-06-09 追加:拼消消结果页预览、统计和操作三个标准白底面板迁移到 `PlatformSubpanel`;页面只保留图片预览、统计项和动作回调,不再直接拼 `platform-subpanel rounded-[1.25rem] p-4` 或 `platform-subpanel mt-auto rounded-[1.25rem] p-4`。
- 2026-06-09 追加:跳一跳结果页预览和结果操作两个标准白底面板迁移到 `PlatformSubpanel`,公开排行榜小卡迁移到 `PlatformSubpanel surface="flat"`;操作面板标题走 `PlatformFieldLabel variant="section"`,页面只保留资源预览、排行榜数据、状态提示和动作回调,不再直接拼 `platform-subpanel rounded-[1.25rem] p-4` 或 `rounded-[1rem] border ... bg-white/70 p-3`。
- 2026-06-09 追加:跳一跳结果页角色 / 图集 / 路径预览框和拼消消结果页场地底图 / 素材图集预览框使用 `PlatformSubpanel surface="flat" padding="none"`;白底媒体框只保留内部图片、占位和尺寸,不再重复拼 `rounded-[1rem] border ... bg-white/80`。
- 2026-06-09 追加:`PlatformSubpanel` 支持 `radius="xl"`,用于承接方洞结果页等 `rounded-[1.5rem]` 的标准大面板;方洞结果页封面、主信息、形状选项和洞口选项面板已迁移到 `PlatformSubpanel radius="xl" padding="lg"`,页面只保留图片、字段、选项和动作逻辑。
- 2026-06-09 追加:方洞结果页形状 / 洞口选项卡迁移到 `PlatformSubpanel surface="flat"`,贴图缩略图按钮迁移到 `PlatformSubpanel as="button" interactive surface="flat"`;选项卡只保留字段写回、目标洞口选择、删除和图片槽位打开逻辑,不再重复小卡边框、白底、圆角、缩略图 hover / disabled chrome。
- 2026-06-09 追加:敲木鱼创作工作台的“功德有什么”词条面板迁移到 `PlatformSubpanel`,词条输入迁移到 `PlatformTextField`,删除词条圆形浮动入口迁移到 `PlatformIconButton variant="surfaceFloating"`;工作台只保留词条输入、新增和删除交互,不再直接拼 `platform-subpanel rounded-[1.25rem] p-4`、本地标题 class、白底输入框 chrome 或白底圆形图标按钮 chrome。
- 2026-06-09 追加:视觉小说结果页作品、开场、运行配置和世界观标准编辑面板迁移到 `PlatformSubpanel radius="lg"`;页面只保留表单字段、资产预览和运行配置写回,不再直接拼 `platform-subpanel rounded-[1.35rem] p-4`。
- 2026-06-09 追加:抓大鹅结果页作品信息、难度配置、难度统计、UI 素材预览和物品图集预览标准面板迁移到 `PlatformSubpanel radius="lg" padding="lg"`;页面只保留表单、滑杆、统计项和素材预览逻辑,不再直接拼 `platform-subpanel rounded-[1.35rem] p-4 sm:p-5`。
- 2026-06-09 追加:`PlatformSubpanel` 支持 `surface="flat"`、`padding="sm"` 和 `radius="sm"`,用于承接素材 / 音频等小型白底卡片的圆角、边框、`bg-white/72`、标题行和右侧图标动作;视觉小说结果页素材选择 / 音频生成小面板已迁移,业务页不再重复手写 `rounded-[1rem] border ... bg-white/72 p-3`。
- 2026-06-09 追加:抓大鹅结果页难度配置里的当前难度摘要小卡迁移到 `PlatformSubpanel surface="flat" radius="sm" padding="sm"`;结果页只保留当前难度标题、消除次数、物品种类和难度 badge,不再手写 `rounded-[1rem] border ... bg-white/62 px-3 py-3` 小卡 chrome。
- 2026-06-09 追加:RPG 结果页开发资产诊断面板里的摘要卡、资产条目和空态迁移到 `PlatformSubpanel`;开发开关判定拆到 `rpgCreationAssetDebugPanelModel.ts`,组件文件只保留诊断面板渲染和图片加载状态。
- 2026-06-09 追加:RPG 发布弹窗封面预览壳迁移到 `PlatformSubpanel padding="none"`;发布弹窗只保留封面 presentation、设置封面和发布动作语义,不再直接手写 `platform-subpanel rounded-[1.25rem] p-2`。
- 2026-06-09 追加:creative-agent 关卡计划小卡和抓大鹅结果页物品 spritesheet 分组卡迁移到 `PlatformSubpanel surface="flat" radius="sm"`;普通信息 / 图集分组小卡不再直接拼 `rounded-[1rem] border ... bg-white/58 p-3` 或 `px-3 py-3`。
- 2026-06-09 追加:抓大鹅批量物品素材生成状态卡迁移到 `PlatformSubpanel surface="flat" radius="sm"`,内部进度条迁移到 `PlatformProgressBar`;局部进度状态不再手写白底边框和 track / fill div。
- 2026-06-09 追加:平台反馈页问题描述、上传凭证和联系方式三个普通白底区块迁移到 `PlatformSubpanel radius="md"`;平台表单页只表达字段、上传和提交语义,不再直接拼 `platform-subpanel rounded-[1.2rem] px-4 py-4`。
- 2026-06-10 追加:`PlatformSubpanel` 支持 `surface="dark"`、`radius="xs"` 和 `padding="xs"`,用于暗色编辑 / 运行面板里的小型信息卡;RPG 冒险面板 / 覆盖层任务目标、区域、进度和描述卡,以及自定义世界实体目录角色维度小卡已迁移。后续同类暗色小信息卡只保留标题、图标和值,不再手写 `rounded-xl border border-white/10 bg-black/* px-* py-*`。
- 2026-06-09 追加:`PlatformSubpanel` 支持 `as="button"` 与 `interactive`,用于承接普通白底整卡点击列表项的 hover、focus、disabled 和默认 `type="button"`;视觉小说 runtime 历史条目和存档列表已迁移,业务页不再重复手写 `rounded-[1rem] border ... bg-white/78 p-3 hover:bg-white disabled:cursor-not-allowed disabled:opacity-55`。
- 2026-06-09 追加:视觉小说结果页角色 / 场景 / 阶段列表项和空态迁移到 `PlatformSubpanel`;列表项使用 `as="button" interactive` 保留整卡点击、hover / focus / disabled chrome 和默认 button type,空态使用静态 `PlatformSubpanel`,结果页不再直接手写 `platform-subpanel min-h-32` 列表卡片。
- 2026-06-09 追加:账号设置入口卡、主题选择卡、当前主题状态、账号绑定卡、密码 / 安全 / 设备 / 操作记录区块,以及设备 / 操作记录内的白底列表行迁移到 `PlatformSubpanel`;账号弹窗只保留换绑、撤销会话、刷新和日志展示语义,不再直接拼 `platform-subpanel rounded-2xl` 或内层白底列表边框。
- 2026-06-09 追加:RPG 世界详情页的世界信息统计卡、关键角色 / 关键场景预览卡和操作区标题迁移到 `PlatformSubpanel` 与 `PlatformFieldLabel variant="section"`;详情页只保留作品展示、启动、编辑、发布、下架和删除动作语义,不再直接拼小型 `platform-subpanel` 卡片或本地 section 标题 class。
- 2026-06-10 追加:RPG 运行态任务覆盖层里的任务更新提示、地点 / 人物提示和任务日志条目迁移到 `PlatformSubpanel surface="dark"`;运行态只保留任务文案、任务选择和奖励条交互,暗色边框、底色、圆角和条目 hover 外壳不再在业务 JSX 中重复拼。验证命令:`npm run test -- src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx -t "quest offer accept button reuses the shared accepted-quest follow-up chain"`。
- 2026-06-09 追加:大鱼吃小鱼结果页的关卡卡片、场地背景卡、发布校验卡、空草稿提示和素材工坊 PROMPT 信息块迁移到 `PlatformSubpanel`;结果页只保留大鱼玩法的青色主题按钮、预览背景、素材生成动作和发布校验语义,不再直接拼大圆角白底边框卡片。
- 2026-06-09 追加:汪汪声浪结果页草稿编译小卡迁移到 `PlatformSubpanel surface="flat"`,跳一跳结果页排行榜行卡迁移到 `PlatformSubpanel surface="flat"`,排行榜无成绩空态迁移到 `PlatformEmptyState surface="subpanel"`;结果页只保留玩法文案、排行榜字段和错误 / 空态文案,不再手写白底小卡圆角、边框、底色和 padding。
- 2026-06-09 追加:自定义世界实体目录世界页的档案规模统计迁移到 `PlatformStatGrid`,世界基调、角色维度和基本设定条目迁移到 `PlatformSubpanel`;目录只保留世界资料读取、编辑入口和标签展示语义,不再直接拼统计卡 grid 或 `platform-subpanel rounded-2xl` 设定块。
- 2026-06-09 追加:自定义世界实体目录场景幕级缩略图迁移到 `PlatformSubpanel padding="none"`;目录只保留场景名、幕标题和图片来源语义,不再手写 `platform-subpanel h-12 w-[5.25rem]` 预览框 chrome。
- 2026-06-09 追加:自定义世界实体目录 `CatalogCard` 的角色 / 场景媒体框迁移到 `PlatformSubpanel padding="none"`;目录卡片只保留图片、角色动画或占位内容,不再手写媒体框 `platform-subpanel rounded-[1rem]` / `rounded-[1.1rem]` chrome。
- 2026-06-09 追加:`PlatformSubpanel` 支持 `surface="danger"` 承接整卡危险选中态,`PlatformPillBadge` 支持 `tone="muted"` 承接白底柔和选择 badge;自定义世界实体目录 `CatalogCard` 整卡壳迁移到 `PlatformSubpanel as="button"`,批量选择的“选择 / 已选”迁移到 `PlatformPillBadge`,目录只保留选择状态和点击回调,不再手写卡片 `role="button"` / 危险选中边框 / 选择 badge chrome。
- 2026-06-09 追加:平台媒体预览框统一使用 `src/components/common/PlatformMediaFrame.tsx` 承载图片源、fallback 图、fallback 文案、固定比例、refreshKey、warm / editorDark / plain / soft / bright / none / bare surface 和 overlay;自定义世界实体目录场景图片框、RPG 实体编辑器 `ImagePreview` 和拼图结果页关卡列表正式图框已先迁移,业务页只保留素材地址、可访问名称和业务覆盖层。`surface="soft"` 用于由媒体框自身承接 `border border-[var(--platform-subpanel-border)] bg-white/68` 的白底柔和预览,`surface="bright"` 用于由媒体框自身承接 `border border-[var(--platform-subpanel-border)] bg-white/82` 的亮白素材槽,`surface="none"` 用于嵌在已有按钮 / 卡片交互壳里的纯图片与 fallback 内容;`PlatformSubpanel` 继续负责白底面板 / 轻量媒体壳 / 整卡点击列表项,不承接需要 fallback 或 overlay 的图片预览状态。
- 2026-06-09 追加:`PlatformMediaFrame` 支持 `aspect="portrait"` 承接 9:16 竖版预览;拼消消结果页场地底图 / 素材图集预览已迁移到 `PlatformMediaFrame surface="none"`,外层仍用 `PlatformSubpanel surface="flat" padding="none"` 提供白底边框、圆角和 `bg-white/80` 媒体壳,页面不再手写 `ResolvedAssetImage` 与无图占位分支。
- 2026-06-09 追加:平台媒体缩略格网格统一使用 `src/components/common/PlatformMediaTileGrid.tsx` 承载列数、间距、白底容器、tile 圆角、边框、图片、refreshKey、可选 tile `testId` 和 fallback 格;跳一跳结果页地块池 / 无图集 fallback 地块池、拼消消结果页卡片预览网格和抓大鹅物品 spritesheet 解析预览分组已先迁移。结果页只保留素材数组切片、素材地址、fallback 内容和玩法色值,不再重复手写 `grid-cols-*`、`rounded-[0.45rem] border border-white/80 bg-white/78` 或直接依赖底层 `ResolvedAssetImage`;网格内部 tile chrome 由 `tileSurface` 承接,内层 `PlatformMediaFrame` 统一使用 `surface="none"`,不再重复加公共 subpanel fill。
- 2026-06-09 追加:`PlatformMediaFrame` 支持 `fallbackContent` 承接图标型无图占位;方洞结果页图片查看弹窗的 4:3 预览已迁移到 `PlatformMediaFrame aspect="standard" surface="plain"`,页面不再手写图片 / 图标占位分支。
- 2026-06-09 追加:宝贝识物结果页素材卡图片框迁移到 `PlatformMediaFrame aspect="square" surface="none"`,占位资源 badge 作为 `previewOverlay` 传入;素材卡只保留外层 `PlatformSubpanel`、素材名、渐变槽局部样式和业务状态,不再手写 `ResolvedAssetImage` 绝对铺满与 overlay 分支。
- 2026-06-09 追加:视觉小说结果页封面 4:3 预览和资产字段 16:9 图片预览迁移到 `PlatformMediaFrame`;封面使用 `surface="editorDark"` 和图标型 `fallbackContent`,资产字段使用 `aspect="landscape" surface="none"` 嵌入现有小型白底卡片,页面不再手写 `ResolvedAssetImage`、`aspect-[4/3]` / `aspect-[16/9]` 和无图占位分支。
- 2026-06-09 追加:跳一跳结果页地块图集整图 fallback 预览迁移到 `PlatformMediaFrame aspect="square" surface="none"`;单个地块网格和路径平台预览保留专用组合布局,只有纯图片源 + 正方形比例的 atlas 分支进入公共媒体框,图集底色作为局部 `bg-white/78` 保留在媒体框 class。
- 2026-06-09 追加:方洞结果页封面和背景两个点击预览按钮内部迁移到 `PlatformMediaFrame aspect="standard" / "landscape" surface="none"`;按钮继续负责打开图片槽位弹窗和承接渐变边框交互壳,公共媒体框只负责 4:3 / 16:9 比例、图片读取和图标型 fallback,占位和图片分支不再写在业务 JSX 中。
- 2026-06-09 追加:方洞结果页形状 / 洞口选项里的 80px 贴图缩略图迁移到 `PlatformMediaFrame aspect="square" surface="none"`;外层 `PlatformSubpanel as="button"` 继续负责打开素材弹窗和亮白交互壳,业务页不再直接依赖底层 `ResolvedAssetImage`,内层媒体框也不再重复承接背景。
- 2026-06-09 追加:`PlatformMediaFrame` 支持 `aspect="wide"` 承接 9:5 宽图预览;大鱼吃小鱼素材工坊候选预览迁移到 `PlatformMediaFrame aspect="wide" surface="none"`,工坊只保留 prompt、生成动作和 cyan 主题外观适配,虚线边框与浅青底作为局部 class 保留。
- 2026-06-09 追加:拼图发布弹窗封面关卡预览迁移到 `PlatformMediaFrame aspect="square" surface="soft"`;发布弹窗只保留发布检查、泥点提示和发布动作,不再手写封面图片框 `aspect-square`、`ResolvedAssetImage`、白底柔和边框和空图分支。
- 2026-06-09 追加:大鱼吃小鱼结果页场地背景竖版预览迁移到 `PlatformMediaFrame aspect="portrait" surface="none"`;结果页保留青色深海背景主题和生成背景动作,不再手写 9:16 图片框与 `ResolvedAssetImage` 分支。
- 2026-06-09 追加:大鱼吃小鱼结果页关卡主图缩略图迁移到 `PlatformMediaFrame aspect="square" surface="none"`;关卡卡片只保留关卡文案、状态和工坊入口,不再直接依赖底层 `ResolvedAssetImage`。
- 2026-06-10 追加:抓大鹅结果页物品素材列表缩略图和详情大图迁移到 `PlatformMediaFrame aspect="square" surface="bright"`,详情视角缩略图嵌在保留选中态的按钮壳内并使用 `surface="none"`;素材列表卡只保留打开详情、素材名和删除动作,详情预览只保留视角切换状态,不再手写正方形图片 / 图标 fallback / 亮白边框槽;需要测试 id / aria 时通过媒体框容器属性透传。
- 2026-06-10 追加:抓大鹅结果页 UI 素材子 Tab 的游戏背景、UI spritesheet 和物品 spritesheet 主图预览迁移到 `PlatformMediaFrame surface="none"`;外层按钮 / 白底预览壳继续负责交互、边框、底色和内边距,媒体框只承接图片读取、fallback 和固定比例。
- 2026-06-10 追加:`PlatformMediaFrame` 根节点固定带 `platform-media-frame` 类名,供业务测试断言公共媒体框接入;拼图图库详情页封面轮播的内层正方形图片 / 暂无封面 fallback / 轮播 overlay 迁移到 `PlatformMediaFrame aspect="square" surface="none"`,外层 `PlatformSubpanel radius="xl" padding="none"` 继续承接面板边框、圆角和裁切。
- 2026-06-10 追加:认证图形验证码图片使用 `PlatformMediaFrame aspect="auto" surface="soft"`;验证码组件只保留图片 data URL、可访问名称和固定尺寸 class,不再手写 `img + platform-subpanel` 图片框。
- 2026-06-09 追加:敲木鱼结果页主 9:16 背景 + 敲击物叠层预览迁移到 `PlatformMediaFrame aspect="portrait" surface="plain"`;页面保留背景图和敲击物的叠放顺序,不再手写固定比例外框、白底边框和无图占位。
- 2026-06-09 追加:`PlatformMediaFrame` 支持 `fallbackShellClassName` 承接无图 fallback 区域的局部背景 / 渐变;creative-agent 模板确认预览迁移到 `PlatformMediaFrame aspect="landscape" surface="soft"`,弹窗只保留模板标题、泥点、调整和确认语义,不再手写 16:9 图片 / 图标占位容器,也不再在业务 JSX 中重复拼基础边框和 `bg-white/68`。
- 2026-06-09 追加:creative-agent 模板目录卡迁移到 `PlatformSubpanel as="button" interactive surface="flat"`,卡内 16:9 预览迁移到 `PlatformMediaFrame aspect="landscape" surface="none"`;工作台只保留模板选择、标题、摘要、预览渐变局部样式和泥点范围,不再手写白底按钮卡、16:9 图片框或图标 fallback 容器。
- 2026-06-09 追加:非交互中性 / 柔和 / hero / 暗色琥珀 / 成功 / 危险图标槽统一使用 `src/components/common/PlatformIconBadge.tsx` 承载图标、尺寸、圆角、neutral / soft / softBright / hero / heroMuted / darkAmber / success / danger 底色和可访问隐藏语义;视觉小说 runtime 面板标题、存档列表项,creative-agent 模板卡 / 模板确认 / 顶部 hero / 目标就绪 / 过程条目图标圆槽,创作类型弹层锁定卡小圆锁图标、大鱼吃小鱼发布失败弹窗图标槽、通用创作图片面板空主图上传占位图标槽,以及 GameCanvas 宝箱遭遇图标槽已先迁移,业务页不再重复拼 `grid h-* w-* place-items-center bg-[var(--platform-neutral-bg)] text-[var(--platform-neutral-text)]`、白底柔和小圆槽、目标完成图标槽、暗色琥珀图标槽或危险提示红色圆槽。
- 2026-06-10 追加:宝贝识物工作台静态玩法预览卡迁移到 `PlatformSubpanel surface="soft"`,卡内礼物图标槽迁移到 `PlatformIconBadge tone="softBright"`;工作台只保留玩法渐变、装饰层和文案,不再手写白底柔和面板边框 / 圆角 / 内边距或图标槽 chrome。
- 2026-06-09 追加:平台标签编辑统一使用 `src/components/common/PlatformTagEditor.tsx` 承载标签 chip、删除按钮、新增输入、Enter 提交、Escape 取消、空态、可选 AI 生成动作和错误提示;拼图结果页作品标签、敲木鱼结果页主题标签和抓大鹅结果页作品标签已先迁移。业务页只保留标签 parse / normalize 规则、最大数量和最终写回,不再重复维护标签编辑 JSX 与本地新增状态机。
- 2026-06-10 追加:标签编辑 Module 内部的新增输入行由 `PlatformSubpanel surface="soft" padding="tight"` 承接外壳,输入框由 `PlatformTextField` 承接;公共标签编辑不再把子面板和输入框 chrome 混写在同一段本地 JSX class 中。
- 2026-06-09 追加:方形上传入口和紧凑虚线新增入口统一使用 `src/components/common/PlatformUploadTile.tsx` 承载虚线方块、图标、主副文案、button / label 语义和禁用态;`size="compact" showLabel={false}` 用于工作台里的纯图标虚线新增入口,仍保留隐藏可访问名称。上传后的图片预览统一使用 `src/components/common/PlatformUploadPreviewCard.tsx` 承载缩略图壳、预览图片、可选标题行、可选预览点击、横向已选素材条和移除按钮。默认 `layout="square"` 用于方形缩略图,`layout="inline"` 用于“缩略图 + 文件名 / 素材名 + 移除”的已选参考图条,内部横向行复用 `PlatformSubpanel surface="soft" padding="row"`;反馈页上传凭证入口 / 预览、敲木鱼工作台新增功德词条入口、通用创作图片面板的提示词参考图缩略图、抓大鹅封面编辑参考图缩略图、通用输入 Composer 已选参考图条和 creation-agent 已选参考图条已先迁移,业务页只保留文件选择、预览数组、预览回调、删除回调、新增回调和校验逻辑。工具栏小图标上传仍使用 `PlatformIconButton asChild="label"`,带大面积缩略图选择的历史素材仍使用 `PlatformAssetPickerGrid`。
- 2026-06-09 追加:拼图结果页关卡详情中的只读引用图横条也使用 `PlatformUploadPreviewCard layout="inline"`,由公共组件承载缩略图、`ResolvedAssetImage` 换签、素材名截断和横向白底条 chrome;只读场景不传 `onRemove`,避免结果页额外出现删除按钮。历史素材弹窗仍使用 `PlatformAssetPickerGrid`,结果页只展示选择后的引用关系。
- 2026-06-09 追加:白底平台子面板内的无操作空态使用 `PlatformEmptyState surface="subpanel" size="inline"`,由 Module 承载圆角、边框、`bg-white/74`、居中、字号和 soft 文本色;视觉小说 runtime 历史、属性、存档读取 / 空态已先迁移,业务页不再重复拼白底空态 class。
- 2026-06-10 追加:个人中心充值弹窗的“暂无可购买套餐”和每日任务弹窗的“暂无任务”使用 `PlatformEmptyState surface="subpanel" size="inline"`;业务组件只保留数据分支,不再手写 `platform-subpanel rounded-2xl px-4 py-8` 空态 chrome。
- 2026-06-10 追加:`PlatformEmptyState` 根节点固定带 `platform-empty-state` 类名,并支持 `surface="editorDark"` 承接 RPG 大编辑器和运行态弹窗 / 面板里的暗色虚线纯展示空态;角色槽位、可选角色、关系、技能、物品、交易空列表、赠礼空列表、招募替换空列表、奖励物品空态、任务日志空态、运行态设置保存禁用提示和营地编组空队列只保留业务文案,不再重复拼 `rounded-2xl border border-dashed border-white/12 bg-black/20 px-4 py-4 text-sm text-zinc-500`、`rounded-xl border border-dashed border-white/10 bg-black/20 px-4 py-6 text-sm text-zinc-500` 或 `rounded-xl border border-dashed border-white/10 bg-black/20 px-3 py-4 text-center text-xs text-zinc-500`。
- 2026-06-09 追加:自定义世界实体目录搜索框迁移到 `PlatformTextField density="compact"`,搜索无结果空态迁移到 `PlatformEmptyState surface="dashed"`;目录只保留搜索值、占位符和过滤语义,不再直接拼 `platform-subpanel rounded-2xl` 输入壳或虚线空态。
- 2026-06-10 追加:creation-agent 聊天区“暂无消息”迁移到 `PlatformEmptyState surface="subpanel" size="compact"`composer 文本域迁移到 `PlatformTextField variant="textarea" size="md" density="compact"`;工作台保留消息列表滚动、受控输入、禁用条件、Enter 提交和 Shift+Enter 换行语义,不再手写空态和 textarea chrome。
- 2026-06-10 追加:大鱼吃小鱼结果页缺少可编辑草稿提示迁移到 `PlatformEmptyState surface="subpanel" size="compact"`;结果页只保留草稿分支和文案,不再为白底无操作提示手写 `PlatformSubpanel` 空面板。验证命令:`npm run test -- src/components/big-fish-result/BigFishResultView.test.tsx src/components/common/PlatformEmptyState.test.tsx`。
- 2026-06-09 追加:视觉小说 runtime 普通白底面板里的保存主按钮和历史重生成行内动作使用 `PlatformActionButton surface="platform"`;保存使用默认主动作,行内重生成使用 `tone="secondary" size="xs" shape="pill"`,业务页只保留图标、禁用条件和回调。
- 影响范围:`src/components/common/UnifiedConfirmDialog.tsx`、`src/components/common/useCopyFeedback.ts`、`src/components/common/CopyFeedbackButton.tsx`、`src/components/common/CopyCodeButton.tsx`、`src/components/common/CopyFeedbackMessage.tsx`、`src/components/common/PlatformStatusMessage.tsx`、`src/components/common/PlatformEmptyState.tsx`、`src/components/common/PlatformActionButton.tsx`、`src/components/common/platformActionButtonModel.ts`、`src/components/common/PlatformIconButton.tsx`、`src/components/common/PlatformUploadTile.tsx`、`src/components/common/PlatformUploadPreviewCard.tsx`、`src/components/common/PlatformMediaFrame.tsx`、`src/components/common/PlatformModalCloseButton.tsx`、平台入口壳、公共错误 / 完成 / 分享弹窗、公开详情页、大鱼 runtime / result、账号个人资料区、自定义世界实体目录、RPG 结果页重新生成确认、RPG / 拼图 / 抓大鹅 / 跳一跳 / 敲木鱼 / 拼消消 / 宝贝识物 / 方洞 / 汪汪声浪 / 视觉小说结果页普通按钮和状态提示、历史图片选择弹窗 / RPG 发布检查弹窗 / creative-agent 侧边栏 / creation-agent 参考图 / 敲木鱼结果页 / 拼图结果页普通图标按钮、方洞结果页图片素材弹窗关闭按钮、视觉小说结果页资产 / 音频 / 编辑器弹窗和 runtime 普通面板关闭按钮、统一创作页壳层、拼图创作工作台、拼消消创作工作台、宝贝识物创作工作台、视觉小说创作工作台、汪汪声浪创作工作台、creation-agent 推荐回复、creative-agent 工作台、creative-agent 模板确认弹窗、自定义世界实体目录小动作和状态提示、创作中心错误重试、反馈页 header 返回、认证入口 / 邀请码弹窗关闭按钮、通用生成页重试 / 中断动作、RPG 详情页删除确认、RPG 角色素材工作室泥点确认、RPG 场景编辑器阻断提示、RPG 角色背景章节阻断提示、RPG 编辑器未保存关闭确认、RPG 场景背景 / 作品封面生成退出确认、公开作品深链失效恢复、账户充值 / 泥点账单 / 每日任务 / 兑换码 / 扫码 / 存档 / 玩过作品等个人中心弹窗、RPG 首页 / 公开广场 / 作品架和历史素材选择弹窗空态、个人中心充值 / 任务 / 兑换 / 邀请 / 支付结果弹窗主动作按钮、RPG 作品详情和生成结果恢复面板平台动作按钮、法律信息弹窗 footer、通用创作图片 / 音频输入面板动作按钮和上传 label、统一创作工作台返回 / 生成按钮和错误提示、短信登录 / 密码登录 / 绑定手机号认证表单动作按钮和状态提示、账号安全弹窗动作按钮和状态提示、验证码提示、邀请码弹窗提交按钮和错误提示、错误 / 完成 / 分享弹窗复制按钮外观、结果页 / 工作台后续简单弹窗迁移。
- 验证方式:`npm run test -- src/components/common/UnifiedConfirmDialog.test.tsx src/components/common/useCopyFeedback.test.tsx src/components/common/CopyFeedbackButton.test.tsx src/components/common/CopyCodeButton.test.tsx src/components/common/CopyFeedbackMessage.test.tsx src/components/common/PlatformStatusMessage.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/common/platformActionButtonModel.test.ts src/components/common/PlatformIconButton.test.tsx src/components/common/PlatformUploadTile.test.tsx src/components/common/PlatformUploadPreviewCard.test.tsx src/components/common/PlatformModalCloseButton.test.tsx`,迁移页面时补跑对应页面交互测试;实体目录删除确认、角色背景章节阻断与场景编辑器提示补跑 `npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx`;公开作品深链失效恢复补跑 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct missing public work detail"`RPG 结果页重新生成确认补跑 `npm run test -- src/components/CustomWorldResultView.test.tsx`RPG 详情页删除 hook 补跑 `npm run test -- src/components/rpg-entry/useRpgEntryAgentDraftRestore.test.tsx`;角色素材工作室泥点确认补跑 `npm run test -- src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx`;个人中心弹窗关闭按钮迁移补跑 `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "wallet ledger|reward code|task center|recharge|save archive|played works"`;认证入口 / 邀请码弹窗关闭按钮迁移补跑 `npm run test -- src/components/auth/AuthGate.test.tsx src/components/common/PlatformModalCloseButton.test.tsx`RPG 首页 / 公开广场 / 作品架空态迁移补跑 `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "mobile discover|desktop logged in home|profile played works|logged in draft bottom tab|ranking"`;历史素材选择弹窗空态迁移补跑 `npm run test -- src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.test.tsx`;结果页普通动作和状态提示迁移补跑 `npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`、`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run test -- src/components/jump-hop-result/JumpHopResultView.test.tsx src/components/wooden-fish-result/WoodenFishResultView.test.tsx`、`npm run test -- src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx`、`npm run test -- src/components/square-hole-result/SquareHoleResultView.test.tsx src/components/common/PlatformModalCloseButton.test.tsx`、`npm run test -- src/components/visual-novel-result/VisualNovelResultView.test.tsx`;玩法创作工作台普通动作和错误提示迁移补跑 `npm run test -- src/components/puzzle-clear-creation/PuzzleClearWorkspace.test.tsx src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx src/components/bark-battle-creation/BarkBattleResultView.test.tsx src/components/creative-agent/CreativeAgentWorkspace.test.tsx src/components/creative-agent/CreativeAgentTemplateConfirmPanel.test.tsx`creation-agent 推荐回复动作迁移补跑 `npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/common/platformActionButtonModel.test.ts`;创作中心重试和反馈页返回按钮迁移补跑 `npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/platform-entry/PlatformFeedbackView.test.tsx src/components/common/PlatformActionButton.test.tsx`;通用生成页动作迁移补跑 `npm run test -- src/components/CustomWorldGenerationView.test.tsx src/components/common/PlatformActionButton.test.tsx`;统一创作页壳层补跑 `npm run test -- src/components/unified-creation/UnifiedCreationPage.test.tsx`;拼图创作工作台返回按钮补跑 `npm run test -- src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx`;个人中心主动作按钮迁移补跑 `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recharge|wallet ledger|task center|reward code|invite|community"`;复制弹窗外观迁移补跑 `npm run test -- src/components/platform-entry/PlatformErrorDialog.test.tsx src/components/common/PublishShareModal.test.tsx`;阶段完成前复扫 `rg -n "window\\.confirm|window\\.alert" src/components src/services src/hooks -g '*.tsx' -g '*.ts'`。
- 2026-06-09 验证补充:通用输入 Composer 图标按钮迁移补跑 `npm run test -- src/components/creative-agent/CreativeAgentInputComposer.test.tsx src/components/creative-agent/CreativeAgentWorkspace.test.tsx src/components/common/PlatformIconButton.test.tsx`。
- 2026-06-10 验证补充:creative-agent 首页抽屉空态和首页错误提示收口后,补跑 `npm run test -- src/components/creative-agent/CreativeAgentHome.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-10 验证补充:creative-agent 过程面板空态收口到 `PlatformEmptyState surface="subpanel" size="compact"` 后,补跑 `npm run test -- src/components/creative-agent/CreativeAgentWorkspace.test.tsx src/components/common/PlatformEmptyState.test.tsx`。
- 2026-06-10 验证补充:creative-agent 工作台消息空态收口到 `PlatformEmptyState surface="subpanel" size="compact"` 后,补跑 `npm run test -- src/components/creative-agent/CreativeAgentWorkspace.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 验证补充:作品详情顶部和封面轮播图标按钮收口补跑 `npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformIconButton.test.tsx`。
- 2026-06-10 验证补充:作品详情底部启动 / 改造动作收口补跑 `npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformActionButton.test.tsx`。
- 2026-06-10 验证补充:作品详情点赞按钮收口补跑 `npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/common/platformActionButtonModel.test.ts`。
- 2026-06-10 验证补充:creative-agent 模板确认弹层“关卡数”行内标题收口到 `PlatformFieldLabel variant="inlineForm"` 后,补跑 `npm run test -- src/components/creative-agent/CreativeAgentTemplateConfirmPanel.test.tsx src/components/common/PlatformFieldLabel.test.tsx`。
- 2026-06-10 验证补充:平台入口公开编号搜索结果弹层收口到 `UnifiedModal`、`PlatformStatusMessage` 和 `PlatformSubpanel` 后,补跑 `npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "public code search"`。
- 2026-06-10 验证补充:平台作品详情主题标签和作品号复制 chip 收口后,补跑 `npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformPillBadge.test.tsx src/components/common/CopyCodeButton.test.tsx`。
- 2026-06-10 验证补充:平台作品详情分享复制反馈按状态映射到 `PlatformStatusMessage surface="platform"` 后,补跑 `npm run test -- src/components/platform-entry/PlatformWorkDetailView.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-10 验证补充:大鱼吃小鱼结果页缺草稿空态收口补跑 `npm run test -- src/components/big-fish-result/BigFishResultView.test.tsx src/components/common/PlatformEmptyState.test.tsx`。
- 2026-06-10 验证补充:大鱼吃小鱼结果页发布校验阻断项收口补跑 `npm run test -- src/components/big-fish-result/BigFishResultView.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-09 验证补充:通用输入 Composer 面板、文本域和读图错误状态收口补跑 `npm run test -- src/components/creative-agent/CreativeAgentInputComposer.test.tsx src/components/common/PlatformTextField.test.tsx src/components/common/PlatformStatusMessage.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 验证补充:creation-agent composer 错误条收口补跑 `npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-09 验证补充:通用创作图片面板历史入口和抓大鹅封面编辑浮动图标按钮收口补跑 `npm run test -- src/components/common/PlatformIconButton.test.tsx src/components/common/CreativeImageInputPanel.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-09 验证补充:AI 重绘胶囊开关收口补跑 `npm run test -- src/components/common/PlatformPillSwitch.test.tsx src/components/common/CreativeImageInputPanel.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-09 验证补充:白底整行开关收口补跑 `npm run test -- src/components/common/PlatformToggleRow.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx`。
- 2026-06-09 验证补充:RPG 大编辑器动作按钮收口补跑 `npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx -t "保存修改|保存角色"`。
- 2026-06-09 验证补充:runtime 白底 HUD 收口补跑 `npm run test -- src/components/wooden-fish-runtime/WoodenFishRuntimeShell.test.tsx src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`。
- 2026-06-09 验证补充:历史素材选择卡片收口补跑 `npm run test -- src/components/common/PlatformAssetPickerCard.test.tsx src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx`。
- 2026-06-09 验证补充:RPG 大编辑器历史素材弹窗收口补跑 `npm run test -- src/components/common/PlatformAssetPickerCard.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx`。
- 2026-06-09 验证补充:抓大鹅封面编辑可引用素材网格收口补跑 `npm run test -- src/components/common/PlatformAssetPickerCard.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-09 验证补充:抓大鹅结果页白底输入框和文本域收口补跑 `npm run test -- src/components/common/PlatformTextField.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-09 验证补充:方洞结果页主信息表单白底输入框和文本域收口补跑 `npm run test -- src/components/common/PlatformTextField.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx`。
- 2026-06-09 验证补充:方洞结果页形状 / 洞口选项紧凑输入、文本域和下拉框收口补跑 `npm run test -- src/components/common/PlatformTextField.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx`。
- 2026-06-09 验证补充:拼图 / 敲木鱼结果页作品信息输入、拼图关卡名称和智能修订输入收口补跑 `npm run test -- src/components/common/PlatformTextField.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx src/components/wooden-fish-result/WoodenFishResultView.test.tsx`。
- 2026-06-09 验证补充:通用创作图片输入面板提示词文本域收口补跑 `npm run test -- src/components/common/PlatformTextField.test.tsx src/components/common/CreativeImageInputPanel.test.tsx`。
- 2026-06-09 验证补充:创作工作台白底字段输入和焦点色 tone 收口补跑 `npm run test -- src/components/common/PlatformTextField.test.tsx src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx` 与 `npm run test -- src/components/common/PlatformTextField.test.tsx src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx`。
- 2026-06-09 验证补充:白底分段 Tab / 二选一收口补跑 `npm run test -- src/components/common/PlatformSegmentedTabs.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx src/components/match3d-result/Match3DResultView.test.tsx src/components/creative-agent/CreativeAgentTemplateConfirmPanel.test.tsx`。
- 2026-06-09 验证补充:抓大鹅难度四选一收口补跑 `npm run test -- src/components/common/PlatformSegmentedTabs.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-09 验证补充:平台统计小卡收口补跑 `npm run test -- src/components/common/PlatformStatGrid.test.tsx src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-09 验证补充:自定义世界实体目录搜索框和空态收口补跑 `npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx src/components/common/PlatformTextField.test.tsx src/components/common/PlatformEmptyState.test.tsx`。
- 2026-06-10 验证补充:RPG 大编辑器暗色纯展示空态迁移到 `PlatformEmptyState surface="editorDark"` 后,补跑 `npm run test -- src/components/common/PlatformEmptyState.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx -t "可扮演角色空态复用暗色平台空态"`。
- 2026-06-10 验证补充:角色聊天错误提示收口到 `PlatformStatusMessage surface="editorDark"` 后,补跑 `npm run test -- src/components/CharacterChatModal.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-10 验证补充:营地编组战斗中提示、状态数值、分区 / 同行者卡、空队列和替换位按钮分别收口到 `PlatformStatusMessage surface="editorDark"`、`PlatformPillBadge darkNeutral`、`PlatformSubpanel surface="dark" / "darkSky"`、`PlatformEmptyState surface="editorDark"` 和 `PlatformDarkOptionCard` 后,补跑 `npm run test -- src/components/CompanionCampModal.test.tsx src/components/common/PlatformStatusMessage.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformPillBadge.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformDarkOptionCard.test.tsx`。
- 2026-06-10 验证补充:自定义选择弹窗错误 / 生成中提示收口到 `PlatformStatusMessage surface="editorDark"` 和 `PlatformProgressBar` 后,补跑 `npm run test -- src/components/SelectionCustomizationModals.test.tsx src/components/common/PlatformStatusMessage.test.tsx src/components/common/PlatformProgressBar.test.tsx`。
- 2026-06-10 验证补充:地图场景切换目标场景面板、当前 / 前往摘要和方向标签收口到 `PlatformSubpanel surface="darkAmber" / "dark"` 与 `PlatformPillBadge dark*` 后,补跑 `npm run test -- src/components/MapModal.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformPillBadge.test.tsx`。
- 2026-06-10 验证补充:RPG 构筑标签效果详情收口到 `CharacterInfoShared.BuildContributionDetailPanel` 和 `PlatformSubpanel surface="dark"` 后,补跑 `npm run test -- src/components/CharacterInfoShared.test.tsx src/components/AdventureEntityModal.test.tsx -t "BuildContributionDetailPanel|技能详情静态标签"`。
- 2026-06-10 验证补充:RPG 实体详情弹窗物品空态和技能详情暗色小卡收口后,补跑 `npm run test -- src/components/AdventureEntityModal.test.tsx -t "物品空态|技能详情静态标签"`。
- 2026-06-09 验证补充:创作中心作品架空态和加载骨架卡收口补跑 `npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:平台胶囊状态标签和宝贝识物结果页白底卡片收口补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx`。
- 2026-06-09 验证补充:平台胶囊状态标签扩展到宝贝识物 / 拼图 / 汪汪声浪工作台和结果页 chip 后,补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/bark-battle-creation/BarkBattleConfigEditor.test.tsx src/components/bark-battle-creation/BarkBattleResultView.test.tsx src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx`。
- 2026-06-09 验证补充:平台胶囊状态标签扩展到视觉小说 / 抓大鹅工作台 BETA chip 后,补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx`。
- 2026-06-09 验证补充:平台胶囊状态标签扩展到敲木鱼结果页飘字 chip 后,补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/wooden-fish-result/WoodenFishResultView.test.tsx`。
- 2026-06-09 验证补充:平台胶囊状态标签扩展到 creative-agent 过程计数 / 条目 meta chip 后,补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/creative-agent/CreativeAgentWorkspace.test.tsx`。
- 2026-06-10 验证补充:实心中性状态胶囊和整行状态开关收口补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/common/PlatformToggleRow.test.tsx`。
- 2026-06-10 验证补充:媒体紧凑占位浮层收口补跑 `npm run test -- src/components/common/PlatformOverlayBadge.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx`。
- 2026-06-10 验证补充:creative-agent 阶段时间线柔和步骤圆点收口补跑 `npm run test -- src/components/common/PlatformSlotBadge.test.tsx src/components/creative-agent/CreativeAgentWorkspace.test.tsx`。
- 2026-06-10 验证补充:creative-agent 过程条目柔和图标圆槽收口补跑 `npm run test -- src/components/common/PlatformIconBadge.test.tsx src/components/creative-agent/CreativeAgentWorkspace.test.tsx`。
- 2026-06-10 验证补充:creative-agent 模板 / hero / 目标就绪图标圆槽收口补跑 `npm run test -- src/components/common/PlatformIconBadge.test.tsx src/components/creative-agent/CreativeAgentWorkspace.test.tsx src/components/creative-agent/CreativeAgentTemplateConfirmPanel.test.tsx`。
- 2026-06-10 验证补充:创作类型弹层锁定卡小圆锁图标收口补跑 `npm run test -- src/components/common/PlatformIconBadge.test.tsx src/components/platform-entry/PlatformEntryCreationTypeModal.test.tsx`。
- 2026-06-10 验证补充:大鱼吃小鱼发布失败弹窗危险图标槽收口补跑 `npm run test -- src/components/common/PlatformIconBadge.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx -t "shows publish failures in a dismissible modal"`。
- 2026-06-10 追加:`PlatformIconBadge` 根节点固定带 `platform-icon-badge` 稳定类名;个人中心充值结果弹窗和支付确认遮罩里的 56px 圆形图标槽使用 `PlatformIconBadge size="xl"` 并保留局部 `bg-white/10` 与状态文字色覆盖,支付弹窗不再手写圆形图标容器。验证命令:`npm run test -- src/components/common/PlatformIconBadge.test.tsx`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "confirms virtual payment after returning without hash result|releases submitting state after cancelled wechat pay result"`。
- 2026-06-10 验证补充:宝贝识物工作台静态玩法预览卡和图标槽收口补跑 `npm run test -- src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformIconBadge.test.tsx src/components/edutainment-creation/BabyObjectMatchWorkspace.test.tsx`。
- 2026-06-10 验证补充:通用创作图片面板空主图上传占位图标槽收口补跑 `npm run test -- src/components/common/PlatformIconBadge.test.tsx src/components/common/CreativeImageInputPanel.test.tsx`。
- 2026-06-10 验证补充:GameCanvas 宝箱遭遇图标槽收口到 `PlatformIconBadge size="xxl" shape="xl" tone="darkAmber"` 后,补跑 `npm run test -- src/components/common/PlatformIconBadge.test.tsx src/components/game-canvas/GameCanvasEntityLayer.test.tsx`。
- 2026-06-10 验证补充:通用创作图片面板按钮内泥点消耗胶囊收口补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/common/CreativeImageInputPanel.test.tsx`。
- 2026-06-10 验证补充:抓大鹅创作工作台按钮内泥点消耗胶囊收口补跑 `npm run test -- src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/common/PlatformPillBadge.test.tsx`。
- 2026-06-10 验证补充:标签编辑新增输入行 soft 子面板收口补跑 `npm run test -- src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformTagEditor.test.tsx`。
- 2026-06-10 验证补充:标签编辑新增输入框收口到 `PlatformTextField` 后,补跑 `npm run test -- src/components/common/PlatformTextField.test.tsx src/components/common/PlatformTagEditor.test.tsx`。
- 2026-06-10 验证补充:个人中心昵称弹窗输入框收口后,补跑 `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile nickname modal"` 与 `npm run test -- src/components/common/PlatformTextField.test.tsx`。
- 2026-06-10 验证补充:认证图形验证码图片和答案输入分别收口到 `PlatformMediaFrame` 与 `PlatformTextField` 后,补跑 `npm run test -- src/components/auth/CaptchaChallengeField.test.tsx src/components/common/PlatformTextField.test.tsx src/components/common/PlatformMediaFrame.test.tsx`。
- 2026-06-10 验证补充:认证登录、重置密码、绑定手机号、邀请码和账号安全表单字段收口到 `PlatformTextField` 与 `PlatformFieldLabel` 后,补跑 `npm run test -- src/components/auth/AuthGate.test.tsx src/components/auth/AccountModal.test.tsx src/components/auth/BindPhoneScreen.test.tsx src/components/auth/CaptchaChallengeField.test.tsx src/components/common/PlatformTextField.test.tsx src/components/common/PlatformFieldLabel.test.tsx`。
- 2026-06-10 验证补充:通用创作图片输入面板主图 / 提示词字段标题收口到 `PlatformFieldLabel` 后,补跑 `npm run test -- src/components/common/CreativeImageInputPanel.test.tsx src/components/common/PlatformFieldLabel.test.tsx`。
- 2026-06-10 验证补充:个人中心存档 / 玩过弹窗简单空态、分区标题和已玩作品按钮卡收口后,补跑 `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "profile played modal|profile page keeps save archives inside played stats panel"`。
- 2026-06-10 验证补充:平台入口壳纯 Suspense fallback 收口到 `PlatformSubpanel` 后,补跑 `npm run test -- src/components/common/PlatformSubpanel.test.tsx src/components/platform-entry/PlatformEntryFlowShellImpl.test.ts`。
- 2026-06-10 验证补充:个人中心钱包账单空态和账单行收口后,补跑 `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "wallet ledger"` 与 `npm run test -- src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 验证补充:个人中心邀请弹窗内部卡片、标题和空态收口后,补跑 `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile community shortcut|profile redeem invite"` 与 `npm run test -- src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformFieldLabel.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 验证补充:个人中心任务中心任务条目收口后,补跑 `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile daily task"` 与 `npm run test -- src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 验证补充:个人中心充值弹窗 Native 支付二维码确认面板收口后,补跑 `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile recharge modal shows native qr code"` 与 `npm run test -- src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 验证补充:个人中心兑换码 / 邀请码输入和充值 / 任务空态收口后,补跑 `npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/common/PlatformTextField.test.tsx src/components/common/PlatformEmptyState.test.tsx -t "reward code|invite query|profile redeem invite|daily task"`。
- 2026-06-10 验证补充:背包文书按钮收口到暗色 `PlatformSubpanel`、故事档案 QA 提示收口到 `PlatformStatusMessage surface="editorDark"` 后,补跑 `npm run test -- src/components/InventoryPanel.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-10 验证补充:NPC 叙事提示和交易详情属性格收口后,补跑 `npm run test -- src/components/NpcModals.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformStatusMessage.test.tsx`。
- 2026-06-10 验证补充:NPC 暗色可选项按钮卡收口到 `PlatformDarkOptionCard` 后,补跑 `npm run test -- src/components/NpcModals.test.tsx src/components/common/PlatformDarkOptionCard.test.tsx`。
- 2026-06-10 验证补充:角色素材工作室动作预览格收口到 `PlatformDarkOptionCard` 后,补跑 `npm run test -- src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx src/components/common/PlatformDarkOptionCard.test.tsx`。
- 2026-06-10 验证补充:上传预览横向已选素材条 soft row 子面板收口补跑 `npm run test -- src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformUploadPreviewCard.test.tsx`。
- 2026-06-10 验证补充:creation-agent 无 session / 加载提示块收口补跑 `npm run test -- src/components/common/PlatformSubpanel.test.tsx src/components/creation-agent/CreationAgentWorkspace.test.tsx`。
- 2026-06-10 验证补充:creation-agent 聊天空态和 composer 文本域收口后,补跑 `npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/common/PlatformTextField.test.tsx`。
- 2026-06-10 验证补充:拼图首访 onboarding 提示词文本域、输入错误和登录保存错误收口后,补跑 `npm run test -- src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleOnboardingView.test.tsx src/components/common/PlatformStatusMessage.test.tsx src/components/common/PlatformTextField.test.tsx`。
- 2026-06-10 验证补充:拼图首访 onboarding 生成 / 登录 / 跳过按钮收口到 `PlatformActionButton` 后,补跑 `npm run test -- src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleOnboardingView.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/common/platformActionButtonModel.test.ts`。
- 2026-06-10 验证补充:拼图结果页空草稿提示块收口补跑 `npm run test -- src/components/common/PlatformSubpanel.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`。
- 2026-06-10 验证补充:RPG 个人中心未登录提示子面板收口补跑 `npm run test -- src/components/common/PlatformSubpanel.test.tsx`,并对 `src/components/rpg-entry/RpgEntryHomeView.tsx` 执行 ESLint / typecheck;游客态当前不暴露“我的”Tab,不新增不可达业务断言。
- 2026-06-10 验证补充:拼图图库详情页封面轮播壳收口到 `PlatformSubpanel radius="xl" padding="none"` 后,补跑 `npm run test -- src/components/common/PlatformSubpanel.test.tsx src/components/puzzle-gallery/PuzzleGalleryDetailView.test.tsx`。
- 2026-06-10 验证补充:抓大鹅物品详情五视角面板收口到 `PlatformSubpanel radius="xl" padding="sm"` 后,补跑 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:拼图 / 方洞结果页自动保存 badge 收口补跑 `npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx src/components/common/PlatformPillBadge.test.tsx`。
- 2026-06-09 验证补充:抓大鹅结果页自动保存 / 当前难度 badge 收口补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-09 验证补充:拼图结果页关卡生成中 badge 收口补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`。
- 2026-06-09 验证补充:大鱼吃小鱼结果页终局 / 发布校验成功 badge 收口补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx`。
- 2026-06-10 验证补充:大鱼吃小鱼结果页关卡元信息标签收口补跑 `npm run test -- src/components/big-fish-result/BigFishResultView.test.tsx src/components/common/PlatformPillBadge.test.tsx`。
- 2026-06-09 验证补充:宝贝识物占位资源 overlay 和方洞选项删除图标按钮收口补跑 `npm run test -- src/components/edutainment-result/BabyObjectMatchResultView.test.tsx src/components/common/PlatformPillBadge.test.tsx` 与 `npm run test -- src/components/square-hole-result/SquareHoleResultView.test.tsx src/components/common/PlatformIconButton.test.tsx`。
- 2026-06-09 验证补充:平台普通进度条收口补跑 `npm run test -- src/components/common/PlatformProgressBar.test.tsx src/components/creation-agent/CreationAgentWorkspace.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx src/components/CustomWorldResultView.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx src/components/CustomWorldGenerationView.test.tsx src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx`。
- 2026-06-09 验证补充:汪汪声浪结果页草稿摘要 / 素材槽 / 预览卡收口到 `PlatformSubpanel` 后,补跑 `npm run test -- src/components/common/PlatformSubpanel.test.tsx src/components/bark-battle-creation/BarkBattleResultView.test.tsx`。
- 2026-06-09 验证补充:跳一跳结果页公开排行榜小卡收口到 `PlatformSubpanel surface="flat"` 后,补跑 `npm run test -- src/components/jump-hop-result/JumpHopResultView.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:汪汪声浪草稿编译小卡、跳一跳排行榜行卡和排行榜空态收口后,补跑 `npm run test -- src/components/bark-battle-creation/BarkBattleResultView.test.tsx src/components/jump-hop-result/JumpHopResultView.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformEmptyState.test.tsx`。
- 2026-06-09 验证补充:跳一跳 / 拼消消结果页媒体预览框收口到 `PlatformSubpanel surface="flat" padding="none"` 后,补跑 `npm run test -- src/components/jump-hop-result/JumpHopResultView.test.tsx src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:方洞结果页标准大面板收口到 `PlatformSubpanel radius="xl"` 后,补跑 `npm run test -- src/components/square-hole-result/SquareHoleResultView.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:方洞结果页形状 / 洞口选项卡和缩略图按钮收口后,补跑 `npm run test -- src/components/square-hole-result/SquareHoleResultView.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-10 验证补充:RPG 大编辑器场景背景 / 作品封面生成和封面上传状态提示收口到 `PlatformStatusMessage surface="tinted"` 后,补跑 `npm run test -- src/components/common/PlatformStatusMessage.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx -t "场景图片保存后会同步更新编辑页和场景列表"`。
- 2026-06-09 验证补充:creation-agent operation banner 状态外壳收口补跑 `npm run test -- src/components/common/PlatformStatusMessage.test.tsx src/components/creation-agent/CreationAgentWorkspace.test.tsx`。
- 2026-06-09 验证补充:平台只读信息块收口补跑 `npm run test -- src/components/common/PlatformInfoBlock.test.tsx src/components/platform-entry/PlatformErrorDialog.test.tsx`。
- 2026-06-09 验证补充:汪汪声浪预览卡横向只读信息行收口补跑 `npm run test -- src/components/common/PlatformInfoBlock.test.tsx src/components/bark-battle-creation/BarkBattleResultView.test.tsx`。
- 2026-06-09 验证补充:平台白底子面板收口补跑 `npm run test -- src/components/common/PlatformSubpanel.test.tsx src/components/common/CreativeAudioInputPanel.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx src/components/wooden-fish-result/WoodenFishResultView.test.tsx`。
- 2026-06-09 验证补充:拼消消创作工作台左侧表单面板收口补跑 `npm run test -- src/components/puzzle-clear-creation/PuzzleClearWorkspace.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:抓大鹅创作工作台难度小面板收口补跑 `npm run test -- src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:视觉小说创作工作台画风选择小面板收口补跑 `npm run test -- src/components/visual-novel-creation/VisualNovelAgentWorkspace.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:拼消消结果页白底面板收口补跑 `npm run test -- src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformStatGrid.test.tsx`。
- 2026-06-09 验证补充:creative-agent 标准白底面板收口补跑 `npm run test -- src/components/creative-agent/CreativeAgentWorkspace.test.tsx src/components/creative-agent/CreativeAgentTemplateConfirmPanel.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformStatGrid.test.tsx`。
- 2026-06-09 验证补充:creative-agent 模板目录卡和 16:9 预览收口补跑 `npm run test -- src/components/creative-agent/CreativeAgentWorkspace.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformMediaFrame.test.tsx`。
- 2026-06-10 验证补充:creative-agent 模板确认预览使用 `PlatformMediaFrame surface="soft"` 后,补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/creative-agent/CreativeAgentTemplateConfirmPanel.test.tsx`。
- 2026-06-09 验证补充:通用音频输入面板限制标签收口补跑 `npm run test -- src/components/common/CreativeAudioInputPanel.test.tsx src/components/common/PlatformPillBadge.test.tsx`。
- 2026-06-09 验证补充:RPG 世界详情页白底信息卡与 section 标题收口补跑 `npm run test -- src/components/rpg-entry/RpgEntryWorldDetailView.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformFieldLabel.test.tsx`。
- 2026-06-09 验证补充:大鱼吃小鱼结果页白底卡片收口补跑 `npm run test -- src/components/big-fish-result/BigFishResultView.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformFieldLabel.test.tsx`。
- 2026-06-09 验证补充:大鱼吃小鱼结果页白底动作按钮收口补跑 `npm run test -- src/components/big-fish-result/BigFishResultView.test.tsx src/components/common/PlatformActionButton.test.tsx`。
- 2026-06-09 验证补充:RPG 结果页开发资产诊断面板收口补跑 `npm run test -- src/components/rpg-creation-result/RpgCreationAssetDebugPanel.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:自定义世界实体目录世界页统计和基本设定收口补跑 `npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformStatGrid.test.tsx`。
- 2026-06-09 验证补充:自定义世界实体目录场景幕级缩略图收口补跑 `npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:自定义世界实体目录卡片媒体框收口补跑 `npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx src/components/common/PlatformSubpanel.test.tsx`。
- 2026-06-09 验证补充:自定义世界实体目录卡片整卡壳和批量选择 badge 收口补跑 `npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformPillBadge.test.tsx`。
- 2026-06-10 验证补充:RPG 实体编辑器基本设定 tag 和角色形象参考图 / 状态小卡收口补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/common/PlatformMediaFrame.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx`。
- 2026-06-09 验证补充:平台媒体预览框收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`。
- 2026-06-09 验证补充:方洞图片查看弹窗媒体框收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx`。
- 2026-06-09 验证补充:拼消消结果页卡片预览网格收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx`。
- 2026-06-09 验证补充:宝贝识物结果页素材卡媒体框收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx`。
- 2026-06-09 验证补充:视觉小说结果页封面和资产字段媒体框收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx`。
- 2026-06-09 验证补充:跳一跳结果页地块图集整图媒体框收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/jump-hop-result/JumpHopResultView.test.tsx`。
- 2026-06-09 验证补充:平台媒体缩略格网格收口补跑 `npm run test -- src/components/common/PlatformMediaTileGrid.test.tsx src/components/jump-hop-result/JumpHopResultView.test.tsx src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx`。
- 2026-06-09 验证补充:方洞结果页封面 / 背景点击预览媒体框收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx`。
- 2026-06-09 验证补充:方洞结果页形状 / 洞口贴图缩略图媒体框收口到 `PlatformMediaFrame surface="none"` 后,补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx`。
- 2026-06-10 验证补充:方洞封面 / 背景、拼消消场地底图 / 素材图集、宝贝识物素材卡、跳一跳图集整图和大鱼媒体槽统一收口到 `PlatformMediaFrame surface="none"` 后,补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/square-hole-result/SquareHoleResultView.test.tsx src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx src/components/edutainment-result/BabyObjectMatchResultView.test.tsx src/components/jump-hop-result/JumpHopResultView.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx`。
- 2026-06-10 验证补充:拼图发布封面收口到 `surface="soft"`,拼图关卡列表、视觉小说资产字段和 creative-agent 模板目录卡收口到 `surface="none"` 后,补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx src/components/creative-agent/CreativeAgentWorkspace.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx`;业务页面不再直接使用 `PlatformMediaFrame surface="bare"`。
- 2026-06-10 验证补充:`PlatformMediaTileGrid` 内部媒体框改用 `surface="none"` 并支持 item `testId`,抓大鹅物品 spritesheet 解析分组迁移后,补跑 `npm run test -- src/components/common/PlatformMediaTileGrid.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-10 验证补充:抓大鹅 UI 素材子 Tab 的背景、UI spritesheet 和物品 spritesheet 主图迁移到 `PlatformMediaFrame surface="none"` 后,补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/common/PlatformMediaTileGrid.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-10 验证补充:拼图图库详情页封面轮播内层媒体框收口到 `PlatformMediaFrame surface="none"` 后,补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/puzzle-gallery/PuzzleGalleryDetailView.test.tsx`。
- 2026-06-10 验证补充:`PlatformMediaFrame` 增加 `aspect="auto"`、容器 `ref` 和 `imageProps` 后,RPG 封面上传裁剪操作区 / 裁剪结果、角色素材工作室形象预览和动作静态预览迁移到公共媒体框,补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx -t "作品封面上传会先进入 16:9 裁剪面板再提交到后端"` 与 `npm run test -- src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx`。
- 2026-06-10 验证补充:RPG 编辑器场景幕背景预设、技能编辑 fallback 预览、技能列表缩略图和角色编辑顶部形象预览继续收口到 `PlatformMediaFrame` 后,补跑 `npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx -t "可扮演角色技能动作状态复用暗色平台胶囊标签|场景编辑器会在场景内展示槽位化多幕配置并保存"`。
- 2026-06-10 验证补充:RPG 大编辑器场景幕角色槽位当前角色 / 可选角色面板,以及幕背景预览 / 预设背景面板收口到本地 `EditorInfoPanel` + `PlatformSubpanel surface="dark"` 后,补跑 `npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx -t "场景编辑器会在场景内展示槽位化多幕配置并保存"`。
- 2026-06-09 验证补充:大鱼吃小鱼素材工坊宽图候选预览收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx`。
- 2026-06-09 验证补充:拼图发布弹窗封面关卡预览收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`。
- 2026-06-09 验证补充:大鱼吃小鱼场地背景竖版预览收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx`。
- 2026-06-09 验证补充:大鱼吃小鱼关卡主图缩略图收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/big-fish-result/BigFishResultView.test.tsx`。
- 2026-06-10 验证补充:抓大鹅结果页物品素材列表缩略图和详情大图收口补跑 `npm run test -- src/components/common/PlatformMediaFrame.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-09 验证补充:敲木鱼结果页主预览面板和 9:16 叠层预览收口补跑 `npm run test -- src/components/wooden-fish-result/WoodenFishResultView.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/common/PlatformMediaFrame.test.tsx`。
- 2026-06-09 验证补充:平台标签编辑器收口补跑 `npm run test -- src/components/common/PlatformTagEditor.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx src/components/wooden-fish-result/WoodenFishResultView.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-09 验证补充:反馈页上传方块和上传预览收口补跑 `npm run test -- src/components/common/PlatformUploadPreviewCard.test.tsx src/components/common/PlatformUploadTile.test.tsx src/components/platform-entry/PlatformFeedbackView.test.tsx`。
- 2026-06-10 验证补充:反馈页查看记录次级动作收口补跑 `npm run test -- src/components/platform-entry/PlatformFeedbackView.test.tsx src/components/common/PlatformActionButton.test.tsx`。
- 2026-06-10 验证补充:创作中心作品卡积分激励领取按钮收口补跑 `npm run test -- src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/common/PlatformActionButton.test.tsx src/index.test.ts`。
- 2026-06-10 验证补充:UnifiedModal 头部关闭按钮收口到 `PlatformModalCloseButton platformIcon / pixel` 后,补跑 `npm run test -- src/components/common/UnifiedModal.test.tsx src/components/common/PlatformModalCloseButton.test.tsx src/components/common/UnifiedConfirmDialog.test.tsx`。
- 2026-06-10 验证补充:上传预览卡右上移除按钮收口到 `PlatformIconButton darkMini` 后,补跑 `npm run test -- src/components/common/PlatformIconButton.test.tsx src/components/common/PlatformUploadPreviewCard.test.tsx`。
- 2026-06-10 验证补充:RPG 大编辑器参考图和封面上传入口收口到 `PlatformUploadTile surface="editorDark"`、参考图预览条收口到 `PlatformUploadPreviewCard surface="editorDark"` 后,补跑 `npm run test -- src/components/common/PlatformUploadTile.test.tsx src/components/common/PlatformUploadPreviewCard.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx -t "场景图片保存后会同步更新编辑页和场景列表"`。
- 2026-06-10 验证补充:角色素材工作室参考图入口收口到 `PlatformUploadTile surface="editorDark"` 后,补跑 `npm run test -- src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx`。
- 2026-06-09 验证补充:敲木鱼工作台新增功德词条虚线入口收口补跑 `npm run test -- src/components/common/PlatformUploadTile.test.tsx src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx`。
- 2026-06-09 验证补充:通用创作图片面板参考图缩略图收口补跑 `npm run test -- src/components/common/PlatformUploadPreviewCard.test.tsx src/components/common/CreativeImageInputPanel.test.tsx`。
- 2026-06-09 验证补充:抓大鹅封面编辑参考图缩略图收口补跑 `npm run test -- src/components/common/PlatformUploadPreviewCard.test.tsx src/components/match3d-result/Match3DResultView.test.tsx`。
- 2026-06-09 验证补充:横向已选参考图条收口补跑 `npm run test -- src/components/common/PlatformUploadPreviewCard.test.tsx src/components/creative-agent/CreativeAgentInputComposer.test.tsx src/components/creation-agent/CreationAgentWorkspace.test.tsx src/components/common/PlatformIconButton.test.tsx`。
- 2026-06-09 验证补充:拼图结果页关卡引用图横条收口补跑 `npm run test -- src/components/common/PlatformUploadPreviewCard.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`。
- 2026-06-10 验证补充:汪汪声浪预览 VS chip 收口到 `PlatformPillBadge` 后,补跑 `npm run test -- src/components/common/PlatformPillBadge.test.tsx src/components/bark-battle-creation/BarkBattleResultView.test.tsx`。
- 2026-06-10 验证补充:拼图结果页智能修订条 / 关卡卡片收口到 `PlatformSubpanel` / `PlatformIconBadge` 后,补跑 `npm run test -- src/components/common/PlatformIconBadge.test.tsx src/components/common/PlatformSubpanel.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`。
- 关联文档:`docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md`。
## 2026-06-07 推荐页运行态先封面预载再 ready 渐隐
- 背景:移动端推荐页上下切换公开作品时,如果运行态和封面资源没有明确准备边界,用户会看到未加载完成的 runtime、黑底闪动,或切卡后反向回弹。
- 决策:推荐页拿到推荐作品列表后预加载每个作品的卡片封面、主封面和玩法兜底封面;嵌入 runtime 的启动遮罩必须复用带玩法标签和标题的作品卡面视觉,不能再切到一层单独的纯封面图。作品切换后遮罩接手当前卡面时必须瞬时显示,不允许从旧预览卡面再淡入到同一张卡面;runtime 统一通过 ready 门控等待 run / profile、lazy 组件和 runtime DOM 内图片资源准备完成,ready 返回 true 后再由外层露出游戏画面并只让卡面遮罩渐隐。遮罩层级必须隔离下层 runtime,防止高 z-index HUD、canvas 或子运行态穿透到封面上;ready 前保留无说明文案的加载条 / 动效,不展示“加载中”文案。推荐 rail 切换完成后归零不能走反向过渡动画。
- 影响范围:`src/components/rpg-entry/RpgEntryHomeView.tsx`、推荐页 runtime 生命周期、平台玩法链路文档。
- 验证方式:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-07 登录态身份边界变更后刷新当前页
- 背景:推荐页运行态、作品架、个人数据和私有 query 都可能在页面内缓存当前身份;如果登录或退出只改 React 上下文,当前页可能继续拿旧身份的局部状态渲染。
- 决策:H5 登录态从未登录变为已登录,或从已登录变为未登录后,前端必须刷新当前页面一次,让平台壳和运行态按新身份重新初始化。普通 access token refresh、账号资料更新、主题或音量设置变化不触发整页刷新。
- 影响范围:`src/components/auth/AuthGate.tsx`、平台入口身份初始化、项目基线文档。
- 验证方式:`npm run test -- src/components/auth/AuthGate.test.tsx`。
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
## 2026-06-07 多端登录以 refresh session 为粒度互不顶号
- 背景:同一账号在多端登录后,若单设备退出或请求被打到尚未见过该 session 的 api-server 进程,旧设备会被误判为登录态失效。
- 决策:普通登录只新增当前设备 refresh session,不撤销其它 active session`POST /api/auth/logout` 只撤销当前 refresh session,不再提升账号级 `token_version``POST /api/auth/logout-all`、改密和重置密码继续吊销全端 session 并提升 `token_version`。api-server 鉴权和 refresh cookie 轮换在本进程工作集未命中 session 时,先从 SpacetimeDB 正式认证表按需刷新一次工作集再复查,支持多实例和滚动重启下的新会话被所有进程识别。
- 影响范围:`module-auth` refresh session 语义、`api-server` Bearer 鉴权和 `/api/auth/refresh`、账号安全页多端会话。
- 验证方式:`cargo test -p module-auth logout_current_session --manifest-path server-rs/Cargo.toml`、`cargo test -p module-auth refresh_from_snapshot_json_merges_session_created_by_another_process --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server logout_current_device_keeps_other_device_session_alive --manifest-path server-rs/Cargo.toml`。
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-07 跳一跳排行榜展示名禁止泄露内部身份键
- 背景:跳一跳排行榜曾在结果页和运行态失败弹窗里直接展示 `playerId` / `user_id`,用户可见内容暴露了内部身份键。
- 决策:`jump_hop_leaderboard_entry.player_id` 只作为 SpacetimeDB read model 的去重和 `viewerBest` 匹配字段,HTTP 契约新增并强制使用 `displayName` 作为排行榜展示字段。api-server 出口按账号 `displayName` 补齐展示名;匿名 runtime guest 固定展示“游客玩家”;账号失效或不可解析时展示“失效玩家”;前端排行榜 UI 禁止兜底展示 `playerId` / `user_id`。
- 影响范围:`packages/shared/src/contracts/jumpHop.ts`、`server-rs/crates/shared-contracts/src/jump_hop.rs`、`server-rs/crates/api-server/src/jump_hop.rs`、跳一跳结果页和运行态排行榜组件、跳一跳 PRD 与后端契约文档。
- 验证方式:`npm run test -- src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx -t "排行榜"`、`npm run test -- src/components/jump-hop-result/JumpHopResultView.test.tsx -t "排行榜"`、`cargo test -p api-server jump_hop_leaderboard_display_name_never_falls_back_to_player_id --manifest-path server-rs/Cargo.toml`。
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-07 generated 图片读取坚持 OSS 源站与签名缓存链路
- 背景:生成图片如果以完整 OSS 私有 bucket URL 进入前端,浏览器会裸连 OSS 并遇到 403 或绕过现有 `/api/assets/read-url` 签名缓存;同时旧对象缺少 `Cache-Control` 时只能走 `ETag` / `Last-Modified` 协商缓存,容易被误解为需要 api-server 本地磁盘缓存。
- 决策:OSS 继续作为 generated 私有资产源站,api-server 只签发短期读 URL,不做本地磁盘静态资源兜底。前端收到同 bucket 的 `https://*.oss-*.aliyuncs.com/generated-*` 地址时,必须先归一为 legacy public path,再复用 `/api/assets/read-url` 和本地 signed URL 缓存。新上传 generated 私有对象默认写入 `Cache-Control: public, max-age=31536000, immutable`,缓存职责交给 OSS 对象头、浏览器 / WebView HTTP 缓存和后续 CDN。
- 2026-06-25 追加:前端需要读取 generated/private 资源字节时,也应先通过 `/api/assets/read-url` 获取 signed OSS URL 并由浏览器直接下载字节;`/api/assets/read-bytes` 只作为换签或 OSS 读取失败后的 fallback,不作为默认文件代理路径。
- 2026-07-10 追加:`legacyPublicPath` 仅是 curated 历史公开前缀兼容口;任意 `objectKey` 必须查询已登记 `asset_object` 并满足 `PublicRead` 或当前 owner。External read-url 绑定 API Key owner;后台跨 owner 预览只走 admin-only endpoint`read-bytes` 与主站 read-url 共用同一授权,不得形成 fallback 越权旁路。
- 影响范围:`src/services/assetReadUrlService.ts`、`server-rs/crates/platform-oss`、`shared-contracts` direct upload form fields、`api-server` assets DTO 映射、后端契约文档和开发运维排障口径。
- 验证方式:完整 OSS generated URL 应触发 `/api/assets/read-url?legacyPublicPath=...`,同一路径、同一 `refreshKey` 版本且未临近过期时复用本地 signed URL`platform-oss` 的 `PostObject` policy / form fields 和 `PutObject` 请求头都应包含 immutable `Cache-Control`,且 `PutObject` V4 签名的 `AdditionalHeaders` 包含该普通请求头。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`server-rs/crates/platform-oss/README.md`。
## 2026-06-06 小程序微信绑定展示使用原生昵称组件
- 背景:账号信息面板需要显示“绑定的是哪个微信号”。微信小程序登录 `jscode2session` 不返回昵称或个人微信号,但小程序提供 `input type="nickname"` 原生昵称填写 / 选择能力,可在登录前收集微信昵称用于展示。
- 决策:小程序登录页先展示原生 `input type="nickname"`,将昵称作为 `displayName` 随 `/api/auth/wechat/miniprogram-login` 提交;若还需要绑定手机号,再随 `/api/auth/wechat/bind-phone` 一并提交。`wechatDisplayName` 只能来自微信平台 profile、历史已保存的微信身份资料或小程序原生昵称组件,不能用系统账号显示名或“微信旅人”兜底。小程序侧拿不到昵称时,前端使用后端下发的 `wechatAccount`openid / provider_uid)尾号展示,避免只显示裸“已绑定”。
- 影响范围:`platform-auth` 小程序登录 profile、`module-auth` 微信身份持久化、`api-server` 小程序登录 / 绑定响应、账号信息面板、项目基线和后端契约文档。
- 验证方式:`npm run test -- src/components/auth/AccountModal.test.tsx`、`cargo test -p platform-auth --manifest-path server-rs/Cargo.toml`、`cargo test -p module-auth --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml wechat_miniprogram`、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-03 拼消消收敛为单关 6x6 与 4-sheet 素材策略
- 背景:最初 4 关 / 135 次消除 / 单张大 atlas 方案生图数量和空间一致性成本过高,真实 image2 结果容易被布局提示词诱导成带文字、边框或编号的说明图,不适合运行态 1x1 切片。
- 决策:拼消消运行态收敛为单关 `6x6 / 35 次消除 / 600 秒`,直接解锁 `1x2`、`1x3`、`2x2`、`2x3`;素材生成改为 4 张 `1024x1536` 竖版 sheet,每张按 `4x6`、每格 `256x256` 切片,再由服务端合成 `10x10 / 2560x2560` 最终 atlas。形状配比固定为 `1x2=23`、`1x3=5`、`2x2=4`、`2x3=3`,总计 35 个复合图案组和 95 个 1x1 卡牌切片。
- 影响范围:`module-puzzle-clear` 关卡与图案组规划、api-server 拼消消素材生成编排、前端草稿试玩本地 runtime、结果页 atlas 预览、拼消消 PRD / 技术方案 / 平台链路文档。
- 验证方式:`cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle_clear --manifest-path server-rs/Cargo.toml -- --nocapture`、`npm run test -- src/services/puzzle-clear/puzzleClearLocalRuntime.test.ts`、`npm run test -- src/components/puzzle-clear-result/PuzzleClearResultView.test.tsx src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`。
- 关联文档:`docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md`、`docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-30 拼消消按独立玩法公开闭环接入
- 背景:拼消消以拼图交换手感为基础,但核心规则从“拼完整单图过关”变为“拼成多个复合图案组后逐个消除”,同时需要顶部补牌、防死局、半锁定局部拼接组和正式统计,不能继续复用拼图运行态规则本体。
- 决策:`puzzle-clear` 作为独立玩法域接入,公开作品码前缀固定为 `PC-`;创作链路采用表单 / 图片输入工作台 -> 独立生成页 -> 结果页 -> 试玩 -> 发布 -> 统一作品详情 -> 正式 runtime。领域规则落在 `module-puzzle-clear`SpacetimeDB 新增 `puzzle_clear_*` 表 / procedure / view,并接入统一 `public_work_gallery_entry` / `public_work_detail_entry`;前端只表现后端 snapshot/action 结果,不把胜负、补牌或消除裁决做成前端事实源。
- 补充约束:草稿编译和发布都必须拒绝缺失或 `placeholder` atlas / card assets,不允许后端 facade 或 SpacetimeDB 合成临时素材;当前单关正式 runtime 终态事件使用 `run-finished`、`level-failed`,并写入包含 `status`、`level`、`clears`、`clearDelta`、`elapsedMs` 的结果 JSON。
- 补充约束:拼消消结果页草稿试玩使用前端本地 `runtimeMode=draft` snapshot,不调用 `/api/runtime/puzzle-clear/runs`,不写正式 run 统计;公开详情和推荐流正式运行继续走后端 `/api/runtime/puzzle-clear/*`,客户端需要区分创作详情 `/api/creation/puzzle-clear/works/{profileId}` 与公开运行态详情 `/api/runtime/puzzle-clear/works/{profileId}`。
- 影响范围:`CONTEXT.md`、拼消消 PRD / 技术方案、平台玩法链路文档、`shared-contracts` / `packages/shared`、`api-server`、`spacetime-module`、`spacetime-client`、作品架 / 广场 / 统一作品详情 / runtime 前端分流。
- 验证方式:PRD 和技术方案必须覆盖资产槽位、素材工作表风险、切片验证、恢复语义、API 命名空间和验证命令;实现侧至少运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run check:spacetime-runtime-access`、`npm run check:server-rs-ddd`、`npm run typecheck`、`npm run check:encoding`、相关前端测试和 `cargo test -p module-puzzle-clear --manifest-path server-rs/Cargo.toml`。
- 关联文档:`docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md`、`docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-05 Server-Provision 全程在目标部署 agent 执行且不安装构建链
- 背景:`Genarrative-Server-Provision` 的 `DEPLOY_TARGET=development` 语义是部署到 dev 服务器,不是构建机 dry-run。旧流水线把 development 映射到 `linux && genarrative-build`,还先在 build 节点准备 `provision-tools/` 再 stash 给后续阶段,导致真实 dev 初始化可能跑到 Jenkins controller / build 节点;脚本还安装 clang / lld / pkg-config / OpenSSL headers / sccache 等构建链依赖,超出了服务器初始化职责。
- 决策:Server-Provision 只做服务器初始化,真实初始化阶段运行在目标部署 agentdevelopment 使用 `linux && genarrative-dev-deploy`release 使用 `linux && genarrative-release-deploy`。`Prepare Provision Tools` 与 `Provision Server` 在同一个目标 agent workspace 顺序执行,不再把 SpacetimeDB / otelcol 工具包放在 `linux && genarrative-build` 中转。`scripts/jenkins-server-provision.sh` 不再安装 clang / lld / pkg-config / libssl-dev / sccache;当前 OpenSSL 3.2 独立运行时自举会安装 `build-essential` 等最小工具,这是满足 api-server/libcurl 运行时符号的受控例外,不代表 provision 承担 api-server 构建职责。非 dry-run 仍要求目标 dev / release agent 具备 root 权限,因为 provision 会写 systemd、Nginx、`/etc` 和系统用户。Job 的 `Pipeline script from SCM` 必须使用 Jenkins controller 可访问的本机路径或内网 Git 源,不允许公网 Git fallback。
- 追加决策(2026-06-10):`Prepare Provision Tools` 必须先读取目标机现状,再准备需要的文件。目标机 `/usr/local/bin/otelcol-contrib` 版本匹配 `OTELCOL_VERSION` 时直接复用;`${SPACETIME_ROOT}/bin/current/spacetimedb-cli` 和 `spacetimedb-standalone` 存在且 CLI 版本匹配 `SPACETIME_EXPECTED_VERSION` 或 `SPACETIME_DOWNLOAD_ROOT` 中的版本时,直接复用当前安装生成 `provision-tools/`。只有目标机缺失、不可执行或版本不匹配时,才消费 `PROVISION_DOWNLOADS_DIR` 中的本地包或进入下载分支。
- 追加决策(2026-06-22):Server-Provision 不再要求目标 agent 自己 checkout 仓库,也不再保留 `SOURCE_GIT_REMOTE_URL` 参数。流水线先在 `linux && genarrative-build` 节点使用固定内网 SSH 源 checkout 并通过 `scripts/jenkins-checkout-source.sh` 校验 `SOURCE_BRANCH` / `COMMIT_HASH`,随后只把 provision 脚本、`scripts/deploy/**`、`deploy/**` 和 `.jenkins-source-commit` stash / unstash 到目标 agent。目标 agent 只接收 Jenkins 上传的脚本和配置后执行 `Prepare Provision Tools` / `Provision Server`,不需要访问源码 Git remote。
- 影响范围:`jenkins/Jenkinsfile.production-server-provision`、`scripts/jenkins-server-provision.sh`、生产运维文档、Server-Provision 排障口径。
- 验证方式:Jenkins 日志中 Server-Provision 的 `Prepare Provision Files` 在 `linux && genarrative-build` 上执行并使用 `genarrative-local-gitea-ssh``Provision Target` 下的 `Receive Provision Files`、`Prepare Provision Tools` 和 `Provision Server` 都在目标 dev / release agent 上执行;目标阶段日志不出现 Git checkout、`SOURCE_GIT_REMOTE_URL`、`Git 主地址拉取失败...改用备用地址`、`https://git.genarrative.world/GenarrativeAI/Genarrative.git` 或构建依赖 / sccache 安装步骤;`bash -n scripts/jenkins-server-provision.sh` 和编码检查通过。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-06-05 api-server 重启先摘流再排空并持久化 outbox
- 背景:生产部署重启 api-server 时,如果只用 `/healthz` 判断存活并直接停止进程,运行中的 HTTP 请求和本地 tracking outbox active 文件都可能被中断,容易造成用户请求失败或内存/本地缓冲数据延迟丢失。
- 决策:`/healthz` 只表示进程存活,发布和生产接流检查统一使用 `/readyz`。api-server 收到 `SIGINT` / `SIGTERM` 后先把 readiness 标记为不可用,再交给 Axum graceful shutdown 排空已有 HTTP 请求;退出前在 `GENARRATIVE_API_SHUTDOWN_OUTBOX_FLUSH_TIMEOUT_MS` 窗口内封存 active tracking outbox 并尽力 flush sealed 文件,失败或超时则保留本地文件给下次启动重试。systemd 停机窗口统一放到 `TimeoutStopSec=90`。
- 影响范围:`server-rs/crates/api-server`、`deploy/systemd/genarrative-api.service`、生产 API deploy 脚本、Jenkins API deploy 参数、Nginx 公网健康检查暴露策略、开发运维文档。
- 验证方式:`cargo test -p api-server --manifest-path server-rs/Cargo.toml readyz_reports_readiness_and_draining_state`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml shutdown_flush_seals_active_file_for_later_retry`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、部署脚本 `bash -n` 与 `/readyz` 本机 smoke。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-06-05 OSS 平台适配器输出结构化日志
- 背景:AI 生成资产、浏览器直传签名、私有读签名和对象确认都依赖 OSS;如果 OSS 侧只有错误字符串,排查资产写入 / 确认失败时很难按操作、对象、状态码和耗时下钻。
- 决策:`server-rs/crates/platform-oss` 统一为 `sign_post_object`、`sign_get_object_url`、`head_object` 和 `put_object` 输出结构化日志。日志固定携带 `provider=aliyun-oss`、`operation`、`bucket`、`endpoint`、`object_key` / `key_prefix`、`access`、`content_type`、`content_length`、`status`、`status_class`、`error_kind` 和 `elapsed_ms` 等排障字段;禁止输出 AccessKey、policy、signature、Authorization header 或完整 signed URL。
- 影响范围:`server-rs/crates/platform-oss`、`api-server` 资产签名 / 上传 / 确认链路、OTLP logs、本地 `logs/api-server/` 与运维排障文档。
- 验证方式:`cargo test -p platform-oss --manifest-path server-rs/Cargo.toml`;真实联调时按 `provider=aliyun-oss` 与 `operation` 过滤日志,确认只出现对象定位和状态字段,不出现签名材料。
- 关联文档:`server-rs/crates/platform-oss/README.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-06-05 跳一跳返回按钮改为独立主题资产
- 背景:跳一跳运行态曾把左上角返回按钮视觉锚点写进背景 image2 prompt,导致返回按钮像静态背景元素,不能替代真实可点击按钮。
- 决策:跳一跳背景 prompt 禁止生成任何 UI 或左上角图标;返回按钮由 `backButtonAsset` 单独生成 1:1 纯绿 key 图,后端去绿后作为透明 PNG 持久化到作品 profile,运行态左上角真实按钮优先渲染该资产。顶部得分 HUD 复用拼图模板结构,包含陶泥儿 IP logo、标题牌和下挂数字卡。
- 影响范围:`packages/shared/src/contracts/jumpHop.ts`、`shared-contracts`、`spacetime-module` / `spacetime-client` bindings、`api-server` 跳一跳生成链路、`JumpHopRuntimeShell`、玩法链路文档和后端数据契约文档。
- 验证方式:`npm run spacetime:generate`、`cargo test -p api-server jump_hop --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx`、`npm run check:spacetime-schema`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-03 创作入口关闭不下架已发布作品
- 背景:`creation_entry_disabled` 曾由 api-server 按 runtime 路由前缀统一熔断,导致用户进入平台首页或启动已发布作品时也可能看到“创作入口已关闭”错误。
- 决策:入口配置的 `open=false` 只表示关闭新建创作入口,不表示下架已有草稿、私有作品或公开作品。后端熔断只拦新建创作、新建草稿、首次生成入口和 Remix 成草稿等会产生新创作的请求;公开广场、公开详情、点赞、已发布作品启动、运行态过程请求、存档 / 浏览记录和已有作品回读不因创作入口关闭而失败。前端平台首页遇到旧服务端返回的 `creation_entry_disabled` 只降级,不弹平台级错误弹窗;关闭态模板卡必须明显禁用并展示 `暂未开放`,不得继续显示泥点消耗。
- 影响范围:`server-rs/crates/api-server/src/creation_entry_config.rs`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、创作入口相关测试与玩法链路文档。
- 验证方式:关闭任一创作入口后,新建创作请求返回 `creation_entry_disabled`;公开作品列表 / 详情 / 启动 / 运行态动作不返回该错误;进入平台首页不弹“平台首页:creation_entry_disabled”;关闭态入口卡显示锁定状态且不显示 `10-20泥点数`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-03 外部内容生成改为持久队列加 worker 角色
- 背景:拼图首图、图集、音频等外部生成链路长期占用 `api-server` HTTP handler,导致扩容只能放大 API 进程,且 HTTP 超时和外部 provider 波动会直接影响创作入口。
- 决策:外部生成任务统一进入 SpacetimeDB `external_generation_job` 持久队列,由 `api-server` 的 `external-generation-worker` 进程角色 claim lease 后执行;HTTP 角色只做鉴权、表单/状态初始化、入队和返回 `queued/running/completed/failed` 操作状态。生产通过 systemd worker 模板增加实例数或提高 `GENARRATIVE_EXTERNAL_GENERATION_WORKER_CONCURRENCY` 动态扩缩容,`GENARRATIVE_PROCESS_ROLE=all` 仅用于本地 smoke。拼图 `compile_puzzle_draft`、结果页 `generate_puzzle_images` 与 `generate_puzzle_ui_background` 已接入 worker;业务写回必须在 SpacetimeDB transaction 内校验 `external_generation_job` 的 `job_id + worker_id + lease_token`、job kind、owner 和 source entity,其中首图 worker 的前置 `compile_puzzle_agent_draft` 也必须带 guard。worker 核心业务写回失败不能返回内存快照并把 job 标成 completed;失败态业务写回成功后才能把 job 标成 failed,失败态未写回则保留租约等待后续重领。拼图业务失败不自动重试,只保留 lease 过期后的崩溃重领,避免钱包扣退费幂等漂移。生产发布会启用默认 `genarrative-external-generation-worker@1.service` 并等待 worker activeworker 停机时停止 claim 新任务并 drain 当前任务。
- 2026-06-07 追加:`GENARRATIVE_EXTERNAL_GENERATION_MODE` 使用 `queue|inline` 显式策略;生产和容器扩缩容验证保持 `queue`。本地开发若需要同步等待结果,应通过 `.env.local` 或本机环境显式配置为 `inline`,由 HTTP handler 复用同一 worker executor 直接返回 `completed`,不创建 `external_generation_job`,不支持 worker 动态扩缩容;脚本不得硬编码该策略。拼图写回 guard 字段改为可选,queue 路径仍必须完整校验 `job_id + worker_id + lease_token`inline 路径只允许三项同时为空,半空 guard 仍拒绝。
- 2026-06-11 追加:生产新增固定 `external-generation-controller` 进程角色和 `genarrative-external-generation-controller.service`。controller 只读取 `get_external_generation_queue_stats_and_return` 队列统计并管理 `genarrative-external-generation-worker@N.service`,不监听 HTTP、不执行外部生成任务;默认保留 `@1`,按 `claimable_pending + running_active + expired_running` 计算目标实例数,上限由 `GENARRATIVE_EXTERNAL_GENERATION_CONTROLLER_MAX_WORKERS` 控制,缩容需要连续空闲轮数且每轮只停最高编号一个实例。
- 2026-07-08 追加:生产 worker/controller 作为轻量 SpacetimeDB 客户端运行,专属 env 示例默认 `GENARRATIVE_SPACETIME_POOL_SIZE=1`;非 HTTP 角色只保留 `external_generation_job` 队列窄订阅作为响应式唤醒信号,实际抢占和扩缩容判断仍走 SpacetimeDB procedure,且不再订阅 API 读模型连接池。worker/controller poll interval 只作为订阅失效、漏事件和 lease 过期这类时间条件的兜底,不作为正常领取任务的主路径。
- 2026-07-08 追加:worker lease 默认从 `3600s` 收短到 `600s`,普通 job 执行预算默认 `900s`,视频 / 角色动作等长 job 默认 `1800s`;预算到期后当前 worker 停止续租并释放 worker 槽位,但不取消已启动的业务 future,也不主动写失败 / 重试状态。在途写回由 lease fencing 仲裁:有效租约内照常完成,租约过期后任务可被重新认领,attempt 耗尽时由认领事务原子标记失败并结算退款,避免客户端取消与服务端写回发生竞态。
- 影响范围:`server-rs/crates/spacetime-module/src/external_generation.rs`、`server-rs/crates/spacetime-client/src/external_generation.rs`、`server-rs/crates/api-server/src/external_generation_worker.rs`、`server-rs/crates/api-server/src/external_generation_worker_controller.rs`、`deploy/systemd/genarrative-external-generation-worker@.service`、`deploy/systemd/genarrative-external-generation-controller.service`、`deploy/env/external-generation-controller.env.example`、`scripts/deploy/production-api-deploy.sh`、`scripts/jenkins-server-provision.sh`、拼图 `compile_puzzle_draft`、拼图 `generate_puzzle_images`、拼图 `generate_puzzle_ui_background`、生产 env 模板和运维文档。
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run check:server-rs-ddd`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`,并在 queue 模式下用 `GENARRATIVE_PROCESS_ROLE=all npm run dev` smoke 至少一次 queued -> worker 完成链路;本地 inline 排查只确认不创建 `external_generation_job`。
- 关联文档:`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-03 外部生成 worker lease 使用 SpacetimeDB 时间和 token 栅栏
- 背景:外部生成 worker 支持多进程动态缩扩容后,长任务超过单次 lease、worker 本机时钟漂移或复用 worker id 都可能导致同一任务被重复领取并被过期执行者回写。
- 决策:`external_generation_job` 新增末尾字段 `lease_token``claim` 使用 SpacetimeDB `ctx.timestamp` 计算 lease,生成本次 claim tokenworker 执行期间调用 `renew_external_generation_job_lease_and_return` 续租;`complete/fail` 必须带 `worker_id + lease_token` 才能回写。拼图 `compile_puzzle_draft` 的 dedupe key 包含本次 `extgen-` job id,避免同一 session 的失败或完成 job 吞掉后续重新生成。拼图首图前置 `compile_puzzle_agent_draft`、图片保存、UI 背景与失败态业务写回同样必须携带 lease guard,并在 `compile_puzzle_agent_draft`、`save_puzzle_generated_images`、`save_puzzle_ui_background`、`mark_puzzle_draft_generation_failed`、`mark_puzzle_level_generation_failed` 的 SpacetimeDB 事务内校验。
- 影响范围:`server-rs/crates/spacetime-module/src/external_generation.rs`、`server-rs/crates/spacetime-module/src/puzzle.rs`、`server-rs/crates/module-puzzle/src/commands.rs`、`server-rs/crates/spacetime-client/src/external_generation.rs`、`server-rs/crates/spacetime-client/src/puzzle.rs`、`server-rs/crates/api-server/src/external_generation_worker.rs`、`server-rs/crates/api-server/src/puzzle/handlers.rs`、`server-rs/crates/api-server/src/puzzle/draft.rs`、`server-rs/crates/api-server/src/puzzle/generation.rs`。
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo test -p spacetime-module external_generation --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server external_generation_worker --manifest-path server-rs/Cargo.toml`、`GENARRATIVE_PROCESS_ROLE=all npm run dev` 后检查 `/healthz`。
- 关联文档:`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-06-04 Draft Generation Shelf 剩余草稿打开 intent 收口
- 背景:拼图 / 抓大鹅草稿打开 intent 已归入 `platformDraftGenerationShelfModel.ts`,但方洞挑战、大鱼吃小鱼和视觉小说仍在平台壳层内联判断已发布详情、缺 session、active generating、当前结果页和普通草稿恢复。
- 决策:继续扩展 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,新增 `resolveSquareHoleDraftOpenIntent(...)`、`resolveBigFishDraftOpenIntent(...)` 与 `resolveVisualNovelDraftOpenIntent(...)`;平台壳只按 intent 执行 notice seen、详情打开、恢复 session、读取 work detail、清生成态和切 stage 副作用。
- 追加决策:跳一跳与敲木鱼草稿打开也归入同一 Draft Generation Shelf Model,新增 `resolveJumpHopDraftOpenIntent(...)` 与 `resolveWoodenFishDraftOpenIntent(...)`;壳层只按 intent 执行已发布详情、失败生成页恢复、持久化 generating 恢复、读取 detail 和敲木鱼失败 fallback stage 副作用。
- 影响范围:创作中心作品架打开方洞挑战 / 大鱼吃小鱼 / 视觉小说 / 跳一跳 / 敲木鱼草稿、创作 URL 恢复时强制打开草稿、生成中回到生成页和视觉小说结果页恢复。
- 验证方式:`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`、针对 Draft Shelf Module 与平台壳执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md`。
## 2026-06-04 Platform Public Code Search matcher / DTO 收口
- 背景:`resolvePlatformPublicCodeSearchPlan(...)` 已收口公开搜索顺序,但 `PlatformEntryFlowShellImpl.tsx` 仍内联 RPG by-code DTO 构造,以及拼图、大鱼吃小鱼、跳一跳、敲木鱼、宝贝识物、抓大鹅、方洞挑战、视觉小说和汪汪声浪的 `isSame*PublicWorkCode` 匹配、公开可见性过滤与详情卡映射。
- 决策:扩展 `src/components/platform-entry/platformPublicCodeSearchModel.ts`,以 `mapRpgPublicCodeSearchDetailToGalleryCard(...)` 和各 `resolve*PublicCodeSearchMatch(...)` 收口 per-play 公开码匹配与 DTO 映射;壳层只保留 gallery 刷新、详情打开、Bark Battle runtime 特例、用户查询和错误归航副作用。`M3D-*` 旧抓大鹅前缀在 `isSameMatch3DPublicWorkCode(...)` 中继续匹配。
- 影响范围:平台首页搜索框、初始 `publicWorkCode` 恢复、各玩法公开作品号命中、RPG 公开作品 by-code 详情映射、Bark Battle runtime 内搜索启动。
- 验证方式:`npm run test -- src/components/platform-entry/platformPublicCodeSearchModel.test.ts src/services/publicWorkCode.test.ts`、针对搜索 Module / 壳层 / publicWorkCode 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformPublicCodeSearchModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Draft Generation Shelf 草稿打开 intent 收口
- 背景:`openPuzzleDraft` / `openMatch3DDraft` 在平台壳内重复判断已发布作品、缺 session、ready 未读、失败 notice、active / background 生成中、持久化 generating 和普通草稿恢复,导致壳层继续理解拼图稳定 ID、抓大鹅 notice key 与生成状态优先级。
- 决策:扩展 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,以 `resolvePuzzleDraftOpenIntent(...)` 与 `resolveMatch3DDraftOpenIntent(...)` 返回纯打开计划和 notice keys;壳层只按 intent 执行网络读取、生成态 rebase、试玩启动、错误写入、路由 / stage 和 notice seen 副作用。
- 影响范围:创作中心作品架打开拼图 / 抓大鹅草稿、公开码搜索强制打开抓大鹅草稿、生成完成后 ready 未读试玩、失败草稿恢复和后续 pending / persisted generating 判定。
- 验证方式:`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`、针对 Draft Shelf Module 与平台壳执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md`。
## 2026-06-04 Bark Battle Work Cache 草稿状态收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 仍内联维护 Bark Battle 草稿三图完整性、生成状态归一、作品架摘要恢复草稿配置,以及草稿 / 已发布作品进入 runtime 前的 `BarkBattlePublishedConfig` 字段映射,导致结果页试玩、作品架启动、草稿恢复和公开详情启动都要理解同一份资产字段清单。
- 决策:扩展 `src/components/platform-entry/barkBattleWorkCache.ts`,以 `hasBarkBattleDraftRequiredImages`、`resolveBarkBattleDraftGenerationStatus`、`buildBarkBattleDraftConfigFromWorkSummary`、`buildBarkBattlePublishedConfigFromDraft`、`buildBarkBattlePublishedConfigFromWork`、`buildBarkBattlePublishSnapshot` 和 `mergeBarkBattlePublishedConfigAssets` 收口 Bark Battle 纯规则。平台壳只保留 API、缓存刷新、React state、URL 和 stage 副作用。
- 影响范围:Bark Battle 草稿生成完成、结果页保存、作品架摘要恢复草稿、草稿试玩、作品架 / 公开详情启动正式 runtime,以及后续 Bark Battle 资产字段或 ruleset 默认值调整。
- 验证方式:`npm run test -- src/components/platform-entry/barkBattleWorkCache.test.ts`、针对 Bark Battle Work Cache Module 与平台壳执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md`。
## 2026-06-04 Platform Recommend Runtime Auth Model 收口
- 背景:平台推荐 runtime 的 embedded 启动需要在匿名 Runtime Guest Token、已登录 background auth 和非 embedded 默认鉴权之间分流,拼图还额外维护 `isolated` / `default` runtime auth mode;旧规则散在顶层 helper 与多个启动 callback。
- 决策:新增 `src/components/platform-entry/platformRecommendRuntimeAuthModel.ts`,以 `resolvePlatformRecommendRuntimeAuthPlan(input)` 和 `shouldUsePlatformRecommendRuntimeGuestAuth(input)` 收口纯鉴权计划。壳层仍负责读取 `getStoredAccessToken()`、申请 `ensureRuntimeGuestToken()`、拼装 request options 和写入拼图 runtime auth mode。
- 影响范围:推荐 Tab 内嵌 runtime 启动、拼图公开详情 isolated 入口、推荐运行态后续 action 的局部鉴权口径,以及后续新增可嵌入推荐 runtime 的玩法。
- 验证方式:`npm run test -- src/components/platform-entry/platformRecommendRuntimeAuthModel.test.ts`、针对新 Module 与平台壳执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformRecommendRuntimeAuthModel收口计划-2026-06-04.md`。
## 2026-06-04 Platform Recommend Runtime Auto Start 收口
- 背景:推荐 runtime 自动启动 effect 同时判断桌面断点、stage、Tab、loading、推荐列表、active entry、ready 状态和启动中状态,导致壳层 effect 依赖过长且混合推荐流状态机知识。
- 决策:扩展 `src/components/platform-entry/platformPublicGalleryFlow.ts`,新增 `resolvePlatformRecommendRuntimeAutoStartDecision(input)`,只返回 `noop`、`clear` 或 `start(entry)`。平台壳只执行清空 active runtime state 或调用 `selectRecommendRuntimeEntry(entry)`。
- 影响范围:移动端首页推荐 runtime 自动启动、推荐列表为空时清空状态、active entry ready 判定,以及后续新增推荐 runtime 玩法的启动时机。
- 验证方式:`npm run test -- src/components/platform-entry/platformPublicGalleryFlow.test.ts`、针对 Flow Module 与平台壳执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformRecommendRuntimeAutoStart收口计划-2026-06-04.md`。
## 2026-06-04 Platform Creation Launch Model 收口
- 背景:平台创作入口点击回调曾在 `PlatformEntryFlowShellImpl.tsx` 内联判断 `airp` 占位、隐藏的 `baby-object-match`、未知入口和各玩法工作台启动目标,壳层同时承接入口 ID 规则、启动前准备顺序和副作用。
- 决策:新增 `src/components/platform-entry/platformCreationLaunchModel.ts`,以 `resolvePlatformCreationLaunchIntent({ type, isBabyObjectMatchVisible })` 收口创作入口启动意图。`airp` 返回 `noop` 且不触发 `prepareCreationLaunch()`;隐藏 `baby-object-match` 返回 blocked intent 且仍在 prepare 后显示 `EDUTAINMENT_HIDDEN_MESSAGE`;未知入口保持旧语义,先 prepare 后 no-op;已知入口返回稳定 launch target。壳层只执行 prepare、错误提示和 `runProtectedAction(...)`。
- 影响范围:底部加号创作入口模板卡点击、入口可见性拦截、后续新增可启动模板的 launch target 接入。
- 验证方式:`npm run test -- src/components/platform-entry/platformCreationLaunchModel.test.ts`、针对新 Module 与壳层执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformCreationLaunchModel收口计划-2026-06-04.md`。
## 2026-06-04 Platform Selection Stage Model 收口
- 背景:平台入口在受保护数据失效后会清空当前用户私有作品、草稿、运行态和生成状态,但哪些 `SelectionStage` 可保留、哪些必须回首页曾以内联长否定串散在 `PlatformEntryFlowShellImpl.tsx`。
- 决策:新增 `src/components/platform-entry/platformSelectionStageModel.ts`,以 `resolveSelectionStageAfterProtectedDataLoss(stage)` 收口受保护数据失效后的 stage 去留判定。模型内部使用 `satisfies Record<SelectionStage, boolean>` 全量分类,新增 stage 时必须明确保留或回首页。壳层仍负责检测权限变化、清 state 和调用 `setSelectionStage`。
- 追加决策:缺失草稿 / 作品 / run 时的阶段回退也归入 `platformSelectionStageModel.ts`,由 `resolveSelectionStageAfterMissingCreationState(params)` 统一判断 big-fish、match3d、square-hole、visual-novel 和 baby-object-match 的 result / runtime / gallery-detail 是否还能被当前状态支撑。壳层只汇总布尔事实并按输出 stage 跳转;big-fish、match3d、square-hole 的草稿事实固定来自 `Boolean(session?.draft)`visual-novel 的 session draft 与 work draft 可独立支撑结果页,baby-object-match runtime 缺 draft 时直接回首页。
- 影响范围:退出登录、鉴权上下文收回、平台入口公开页 / 工作台 / 结果页 / 生成页 / 运行态的阶段恢复规则,以及后续新增 `SelectionStage`。
- 验证方式:`npm run test -- src/components/platform-entry/platformSelectionStageModel.test.ts`、针对新 Module 与壳层执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md`。
## 2026-06-04 Creation Work Delete Flow 收口
- 背景:平台入口作品架删除入口在 RPG、拼图、抓大鹅、方洞挑战、大鱼吃小鱼、视觉小说和宝贝识物 handler 内重复计算确认标题、删除说明、草稿 notice key 与拼图派生稳定 ID,导致删除确认规则散在巨型壳层。
- 决策:新增 `src/components/platform-entry/platformCreationWorkDeleteFlow.ts`,以 `resolvePlatformCreationWorkDeleteConfirmationModel(input)` 收口作品架删除确认纯模型;输出 `id/title/detail/noticeKeys`。`PlatformEntryFlowShellImpl.tsx` 仍作为副作用 Adapter,保留删除 API、刷新作品架 / 公开广场、错误状态、`markDraftNoticeSeen` 和页面跳转。
- 影响范围:创作中心作品架删除确认弹窗、删除后生成 notice 清理、拼图稳定 result ID 清理、宝贝识物已发布删除说明,以及后续新增玩法作品架删除接入。
- 验证方式:`npm run test -- src/components/platform-entry/platformCreationWorkDeleteFlow.test.ts`、`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`、针对新 Module 与平台壳执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】CreationWorkDeleteFlow收口计划-2026-06-04.md`。
## 2026-06-03 平台入口公开作品详情 Strategy 收口
- 背景:平台壳层直接判断公开作品详情入口的玩法类型、是否需要补读完整详情,以及自有作品按钮显示“编辑”还是“改造”,导致统一作品详情的纯决策散落在巨型 Implementation 内。
- 决策:新增 `src/components/platform-entry/platformPublicWorkDetailFlow.ts`,以 `getPlatformPublicWorkDetailKind`、`resolvePlatformPublicWorkDetailOpenStrategy`、`resolvePlatformPublicWorkActionMode`、`resolvePlatformPublicWorkDetailOpenDecision` 和 `resolveActivePlatformPublicWorkAuthorEntry` 收口公开作品详情 Strategy。`PlatformEntryFlowShellImpl.tsx` 只按 Strategy 调用现有详情读取 / 直接展示 Adapter,并保留作者请求竞态控制;启动、点赞、remix 和编辑副作用不搬入 Module。
- 追加决策:公开详情 entry 映射与公开详情反推玩法 work 摘要也归入 `platformPublicWorkDetailFlow.ts`,包括 RPG、拼图、大鱼吃小鱼、方洞挑战、视觉小说、跳一跳、敲木鱼和汪汪声浪的通用映射。抓大鹅 `mapMatch3DWorkToPublicWorkDetail` 归入 `platformMatch3DRuntimeProfile.ts`,继续委托 `normalizeMatch3DWorkForRuntimeUi` 做素材归一和背景资产提升,避免把 Match3D 运行态规则复制到公开详情 Flow Module。
- 追加决策:拼图公开详情封面解锁数由 `resolveVisiblePuzzleDetailCoverCount(entry, run)` 收口;非拼图、无当前 run 或 run 不匹配当前公开详情时只展示首图,匹配当前公开详情时按 `clearedLevelCount + 1` 解锁且至少为 1。`PlatformWorkDetailView` 只接收 `visibleCoverCount` 展示,不读取 run。
- 追加决策:公开详情点赞能力矩阵由 `resolvePlatformPublicWorkLikeIntent(entry)` 收口;Module 只返回大鱼吃小鱼、拼图、旧 RPG gallery fallback 或不可用文案,壳层仍执行鉴权、API 调用、缓存同步、错误展示和 busy 状态。
- 追加决策:公开详情改造能力矩阵由 `resolvePlatformPublicWorkRemixIntent(entry)` 收口;Module 只返回大鱼吃小鱼、拼图、旧 RPG gallery fallback 或不可用文案,壳层仍执行鉴权、remix API、session / 缓存写入、stage 切换、错误展示和 busy 状态。
- 追加决策:公开详情启动分流由 `resolvePlatformPublicWorkStartIntent(entry, deps)` 收口;Module 只返回大鱼吃小鱼、拼图、跳一跳、敲木鱼、抓大鹅、方洞挑战、视觉小说、汪汪声浪、宝贝识物或旧 RPG gallery 记录游玩的 intent。壳层仍执行登录保护、运行态启动、RPG 游玩记录、详情更新、busy 状态和错误展示;抓大鹅 public detail -> work mapper 作为 Adapter 注入,继续由 Match3D Runtime Profile Module 维护素材归一与背景资产提升。
- 追加决策:自有公开作品编辑分流由 `resolvePlatformPublicWorkEditIntent(entry, deps)` 收口;Module 只返回可编辑草稿目标、需解析宝贝识物本地草稿 intent、旧 RPG gallery 编辑 intent 或原阻断文案。壳层仍执行登录保护、草稿恢复、宝贝识物异步草稿解析、RPG 编辑导航和错误展示;抓大鹅 public detail -> work mapper 仍作为 Adapter 注入,不复制 Match3D 素材归一规则。
- 影响范围:统一作品详情入口、公开详情打开策略、自有公开作品编辑 / 改造动作模式,以及后续新增玩法公开详情接入。
- 验证方式:`npm run test -- src/components/platform-entry/platformPublicWorkDetailFlow.test.ts`、`npm run test -- src/components/platform-entry/platformMatch3DRuntimeProfile.test.ts`、公开详情壳层交互回归、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md`。
## 2026-06-03 平台入口弹窗状态规则收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 曾同时持有平台级错误 / 完成弹窗的文案归一、来源格式、候选择一、dismiss key、后台生成 still-running 识别和任务完成文案,导致壳层 Interface 偏浅,测试面不稳定。
- 决策:新增 `src/components/platform-entry/platformDialogStateModel.ts` 作为 Platform Dialog State Module,统一导出 `normalizePlatformDialogMessage`、`formatPlatformDialogSource`、`resolvePlatformErrorDialog`、dismiss key builder、`resolveActivePlatformDialog`、`isBackgroundGenerationStillRunningMessage` 和 `PLATFORM_TASK_COMPLETION_MESSAGE`。平台壳只汇总候选、持有 React state,并在关闭弹窗时作为 Adapter 清理对应副作用 setter。
- 影响范围:平台入口错误弹窗、任务完成弹窗、后台生成仍在处理识别、草稿生成完成 / 失败通知。
- 验证方式:`npm run test -- src/components/platform-entry/platformDialogStateModel.test.ts`、`npm run test -- src/components/platform-entry/PlatformErrorDialog.test.tsx`、相关壳层交互测试、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md`。
## 2026-06-03 前端 SSE 客户端传输层统一收口
- 背景:创作 Agent、创意互动 Agent、视觉小说运行态和微信充值订单状态等多个前端 client 曾各自手写 SSE 边界扫描、`TextDecoder` 解码、JSON 解析和流结束 flush,导致 CRLF / LF、UTF-8 尾部、多行 `data:` 和提前停止释放 reader 的处理容易漂移。
- 决策:前端 SSE 传输层统一使用 `src/services/sseStream.ts``readSseStream` 负责事件边界、解码 flush、多行 data 和提前停止取消 reader`readSseJsonStream` 负责 JSON object 事件解析与异常 JSON 静默跳过。业务 client 只保留领域事件归一化、结果聚合和中文错误文案,OpenAI 兼容文本流通过 `readSseStream` 处理 `[DONE]` 哨兵,后续不得复制 `findSseEventBoundary`、`parseSseEventBlock` 或手写 reader 循环。
- 影响范围:`src/services/sseStream.ts`、`src/services/aiService.ts`、`src/services/llmClient.ts`、`src/services/creation-agent/creationAgentSse.ts`、`src/services/creative-agent/creativeAgentSse.ts`、`src/services/visual-novel-runtime/visualNovelRuntimeSse.ts`、`src/services/rpg-entry/rpgProfileClient.ts`、前端 SSE 相关测试与架构文档。
- 验证方式:`npm run test -- src/services/sseStream.test.ts src/services/llmClient.test.ts src/services/creation-agent/creationAgentSse.test.ts src/services/creative-agent/creativeAgentSse.test.ts src/services/visual-novel-runtime/visualNovelRuntimeSse.test.ts src/services/rpg-entry/rpgProfileClient.test.ts src/services/ai.test.ts`、`npm run typecheck`、`npm run check:encoding`、相关文件 `npx eslint ... --max-warnings 0` 通过。
- 关联文档:`docs/technical/【前端架构】SSE客户端传输层收口约定-2026-06-03.md`。
## 2026-06-03 平台入口公开作品流身份规则收口
- 背景:平台入口公开作品推荐流需要同时处理 RPG、拼图、抓大鹅、跳一跳、敲木鱼、视觉小说、Bark Battle、宝贝识物等卡片,公开作品身份、跨玩法去重、排序和推荐运行态 kind 判定曾放在 `PlatformEntryFlowShellImpl.tsx` 巨型实现里。
- 决策:公开作品身份、排序规则、公开作品流聚合矩阵、推荐 runtime 启动意图和 ready 判定统一收口到 `src/components/platform-entry/platformPublicGalleryFlow.ts`;入口壳层只调用该 Module 的 `getPlatformPublicGalleryEntryKey`、`getPlatformRecommendRuntimeKind`、`buildPlatformPublicGalleryFeeds`、`resolvePlatformRecommendRuntimeStartIntent`、`isPlatformRecommendRuntimeReadyForEntry`、`isSamePlatformPublicGalleryEntry` 和 `mergePlatformPublicGalleryEntries`。`edutainment` key 必须带 `templateId`RPG 卡片回退为 `rpg`。公开作品流聚合负责 featured / latest、玩法可见性 gate、汪汪声浪 works fallback 和首屏 `slice(0, 6)`;推荐 runtime 启动 intent 只返回启动目标、`embedded` / `returnStage` 参数、阻断文案和错误落点;ready 判定只接布尔值与拼图 profile id,避免把各玩法 run snapshot 类型拖入 Module。壳层仍执行 request key、运行态 API、错误 setter 与 UI 状态。
- 影响范围:平台入口推荐流、最新公开作品流、公开作品详情、推荐 runtime 启动、跨玩法公开作品合并,以及后续新增玩法的入口接入。
- 验证方式:`npm run test -- src/components/platform-entry/platformPublicGalleryFlow.test.ts`、`npm run typecheck`、`npm run check:encoding`、相关文件 ESLint 通过。
- 关联文档:`docs/technical/【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md`。
## 2026-06-03 Work Shelf 打开动作交由 item Adapter
- 背景:`creationWorkShelf.ts` 已经为每个 `CreationWorkShelfItem` 生成 `actions.open`,但 `CustomWorldCreationHub.tsx` 点击卡片后仍按 `item.source.kind` 重复分发 RPG、拼图、抓大鹅、方洞、跳一跳、敲木鱼、视觉小说、Bark Battle 和宝贝识物的打开逻辑。
- 决策:`CreationWorkShelfItem.actions.open` 作为作品架打开动作的正式 InterfaceHub 只保留 `onOpenShelfItem` 通知和 `item.actions.open()` 调用,不再读取玩法 kind 做打开分支。`buildCreationWorkShelfItemsFromSources` 与 `CreationWorkShelfSourceAdapter` 作为 source registry Interface,统一执行 flatten、运行态覆盖、持久化生成态兜底和更新时间排序;旧 `buildCreationWorkShelfItems` 保留兼容,但内部改为组装 source adapters。
- 影响范围:创作中心作品架卡片点击、作品架动作 Adapter、source registry、后续新增玩法作品架接入。
- 验证方式:`npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、`npm run typecheck`、`npm run check:encoding`、相关文件 ESLint 通过。
- 关联文档:`docs/technical/【前端架构】WorkShelfModule收口计划-2026-06-03.md`。
## 2026-06-03 Runtime Client Family 请求骨架收口
- 背景:Match3D、SquareHole、Puzzle、Jump Hop 等 runtime client 重复手写 path segment 编码、JSON header / body、runtime guest token、auth options 和 retry options,新增玩法容易遗漏同一请求骨架。
- 决策:新增 `src/services/runtimeRequest.ts`,以 `buildRuntimeApiPath` 统一 runtime path 编码,以 `requestRuntimeJson` 统一 JSON 请求、runtime guest auth 和 retry 合并。Match3D 与 SquareHole runtime client 已先迁移,保留原导出函数名、错误文案、返回契约和重试常量。
- 追加决策:Big Fish 与 Bark Battle runtime client 也迁入 `runtimeRequest.ts`;玩法专属 payload 归一化(如 Bark Battle start / finish 自动补 `workId`、`runId`)仍留在各玩法 client,通用 Module 只承接请求骨架。
- 追加决策:Puzzle 的 start / get / swap / drag / next-level / leaderboard / pause / props 与 Jump Hop 的 start / jump / restart 也迁入 `runtimeRequest.ts`;只要调用方传入 Runtime Guest Token,所有正式 runtime 请求都统一带局部 Authorization、`skipAuth` 与 `skipRefresh`。
- 追加决策:Wooden Fish 的 start / checkpoint / finish 与 Visual Novel 的 gallery / run / history / regenerate JSON 请求也迁入 `runtimeRequest.ts`Wooden Fish 的 `clientEventId` 生成仍留在木鱼 clientVisual Novel start 因 `timeoutMs`、SSE 因流式 `fetchWithApiAuth` 仍暂留原实现。
- 影响范围:`src/services/runtimeRequest.ts`、Match3D / SquareHole / Big Fish / Bark Battle / Puzzle / Jump Hop / Wooden Fish / Visual Novel runtime client。
- 验证方式:`npm run test -- src/services/runtimeRequest.test.ts src/services/recommendedRuntimeGuestLaunch.test.ts src/services/match3d-runtime/match3dRuntimeAdapter.test.ts`、`npm run typecheck`、`npm run check:encoding`、相关文件 ESLint 通过。
- 关联文档:`docs/technical/【前端架构】RuntimeClientFamily收口计划-2026-06-03.md`。
## 2026-06-03 Public Gallery ViewModel 收口
- 背景:`RpgEntryHomeView.tsx` 巨型页面内混合了公开作品分类、跨来源去重、搜索归一化、作品号匹配、时间戳解析和排序规则,新增玩法时页面与 ViewModel 规则容易纠缠。
- 决策:新增 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,把 `buildPublicGalleryCardKey`、`buildPublicCategoryGroups`、`getPlatformPublicEntries`、`getAllPlatformPublicEntries`、`getPlatformSearchableWorkIds`、`filterPlatformWorkSearchResults`、`isExactPublicWorkCodeSearch`、`filterTodayPublishedEntries`、公开卡片指标 getter、`buildPlatformRankingEntries`、`getPlatformRankingMetricValue`、`getPlatformCategoryKindFilter`、`matchesPlatformCategoryKindFilter`、`sortPlatformCategoryEntries`、`getPlatformCategoryPrimaryMetric`、`parsePlatformEntryTimestamp` 和 `getPlatformWorldTimestamp` 收口为公开作品 ViewModel Interface。公开作品 key 复用平台入口身份规则,补齐 jump-hop / wooden-fish 等玩法区分。
- 影响范围:RPG 首页公开作品发现、分类、搜索、排行数据准备,以及后续新增玩法公开卡片接入。
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`、`npm run typecheck`、`npm run check:encoding`、相关文件 ESLint 通过。
- 关联文档:`docs/technical/【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md`。
## 2026-06-03 Profile Task ViewModel 收口
- 背景:`RpgEntryHomeView.tsx` 同时持有每日任务卡片和任务中心弹窗的任务选择、进度 clamp、奖励兜底、状态标签和按钮文案,导致任务展示规则和 JSX 缠在一起。
- 决策:新增 `src/components/rpg-entry/rpgEntryProfileTaskViewModel.ts`,把 `selectProfileTaskCenterTasks`、`selectProfileTaskCardTask`、`buildProfileTaskCardSummary`、`buildProfileTaskProgressLabel`、`getProfileTaskStatusLabel` 和 `getProfileTaskClaimButtonLabel` 收口为每日任务 ViewModel Interface。任务中心仍只展示一条 claimable / incomplete 优先任务,任务卡按可操作、claimed、非 disabled 的顺序兜底。
- 影响范围:RPG 首页“每日任务”卡片、任务中心弹窗、后续任务状态和任务展示文案调整。
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryProfileTaskViewModel.test.ts`、`npm run typecheck`、`npm run check:encoding`、相关文件 ESLint 通过。
- 关联文档:`docs/technical/【前端架构】ProfileTaskViewModel收口计划-2026-06-03.md`。
## 2026-06-03 最近创作只复用创作模板入口
- 背景:底部加号创作入口的“最近创作”最初由真实作品架摘要驱动,但页面曾按作品标题、摘要和生成状态渲染独立最近创作卡,和其它模板页签的卡片样式及点击语义不一致。
- 决策:“最近创作”仍只由真实后端作品架摘要决定是否展示,但只纳入 `updatedAt` 在最近 7 天内的摘要,且摘要只用于推导最近使用过的模板 ID;实际列表必须从后端入口配置的 `creationTypes` 中筛出对应模板,复用其它页签的模板卡结构、文案和 `onCreateType` 点击行为,不展示具体作品名称、作品摘要或草稿 / 生成状态,也不新增独立最近创作组件。最近创作页签激活时,页面必须显示“仅显示最近7天内使用过的模板”。
- 影响范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/platform-entry`、创作入口相关测试与玩法链路文档。
- 验证方式:`CustomWorldCreationHub` 测试应断言最近创作页签包含 `creation-template-card`、模板标题 / 副标题,并且不出现旧 `creation-recent-work-grid`、作品标题、作品摘要或“打开最近创作”按钮文案;RPG 入口交互测试应断言最近创作默认页签展示“文字冒险”模板卡。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-02 底部加号创作入口页 banner 与最近创作口径
- 背景:创作入口页 banner 曾固定为前端两张主题赛卡,且模板分类兜底会产生 `recent` / `最近创作` 页签,和后台配置及真实作品数据口径冲突。
- 决策:点击底部加号进入的创作入口页 banner 改由后端 `eventBanners` 数组配置,多条自动轮播;旧 `eventBanner` 只保留单条兼容。后台公告配置使用表单维护标题与 HTML 内容,保存时序列化为后端 `eventBannersJson` 传输字段;HTML 只允许经空权限 iframe 展示,不执行 JSX 或直接 DOM 注入。`最近创作` 不再作为模板分类,只由真实草稿 / 作品架后端数据决定是否展示,生成失败草稿也必须进入;模板分类缺失或历史 `recent` 统一归一到 `recommended` / `热门推荐`。移动端草稿页作品卡禁止长按选择文字,但输入框和可编辑区域保留选择能力。
- 影响范围:`server-rs/crates/module-runtime`、`server-rs/crates/spacetime-module`、`server-rs/crates/spacetime-client`、`server-rs/crates/api-server`、`shared-contracts`、`src/components/custom-world-home`、`src/components/platform-entry`、`apps/admin-web`、`src/index.css`。
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、相关 Rust / Vitest 入口配置测试和浏览器点击底部加号截图。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-26 微信小程序充值全面接入虚拟支付
- 背景:泥点和会员都属于小程序内由 Genarrative 控制的虚拟资产/权益,继续走普通小程序支付不符合微信虚拟支付接入口径。
- 决策:小程序 WebView 内充值商品全部使用渠道 `wechat_mp_virtual` 并由 `miniprogram/pages/wechat-pay` 调用 `wx.requestVirtualPayment`;泥点属于代币(coin),使用 `short_series_coin``buyQuantity` 必须取当前充值中心商品快照里的 `points_amount`;会员和后台新增道具类商品使用 `short_series_goods``signData` 必须带 `productId` 与 `goodsPrice`。后端保存微信小程序 `session_key`,仅用于生成 `signature`,不下发客户端。客户端 success 只作为支付页返回信号,最终到账仍由后端微信通知或查询确认后写订单。
- 影响范围:`src/services/payment/paymentPlatform.ts`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`miniprogram/pages/wechat-pay/`、`server-rs/crates/api-server/src/runtime_profile.rs`、`server-rs/crates/shared-contracts/src/runtime.rs`、`packages/shared/src/contracts/runtime.ts`、微信登录态存储。
- 验证方式:泥点和会员商品在小程序运行态都请求 `wechat_mp_virtual`;小程序页能按 payload 调用 `wx.requestVirtualPayment` / `wx.requestPayment``cargo check -p api-server --manifest-path server-rs/Cargo.toml` 与支付相关前端测试通过。
- 关联文档:`docs/【技术方案】微信虚拟支付接入-2026-05-26.md`。
## 2026-05-30 Linux 本地 dev 端口段按系统级注册表分配
- 背景:同一台 Linux 开发机上有多个用户同时跑 `npm run dev` 时,单纯靠各自 `GENARRATIVE_DEV_PORT_RANGE` 容易撞段,且同一用户并发起两个 dev 会话时也会把相同端口段重复拿走。
- 决策:Linux 上的本地 dev 端口段分配统一收口到系统级注册表 `/var/tmp/genarrative-dev-port-ranges/registry.json`,锁文件为 `/var/tmp/genarrative-dev-port-ranges/registry.lock`,可通过 `GENARRATIVE_DEV_PORT_RANGE_REGISTRY_DIR` 覆盖目录。未手动指定时自动从 `10000-10099` 开始按 100 端口块分配,后续块按 `10100-10199`、`10200-10299` 递增;端口段最初映射为 `web = start`、`api = start + 1`、`spacetime = start + 2`、`admin-web = start + 3`2026-07-21 按顶部 BgFilter 决策扩展 `bgfilter-worker = start + 4`;注册表会拒绝不同用户的相同或重叠段,并让同一用户后续启动继续复用自己已占用的固定段。`GENARRATIVE_DEV_PORT_RANGE` 与 `--port-range` 仍可手动指定端口段,但只在 Linux 生效,Windows 继续沿用统一端口探测与漂移逻辑,不读注册表。
- 影响范围:`scripts/dev-stack-port-utils.mjs`、`scripts/dev.mjs`、`scripts/dev-stack-port-utils.test.ts`、`scripts/dev.test.ts`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、本条决策记录、`development-workflow.md`。
- 验证方式:`node --check scripts/dev-stack-port-utils.mjs`、`node --check scripts/dev.mjs`、`node node_modules/vitest/vitest.mjs run scripts/dev-stack-port-utils.test.ts scripts/dev.test.ts` 通过;Linux 下能看到 `[dev] port-range:` 与 `registry.json` 路径日志,自动分配从 `10000-10099` 起步,Windows 不出现注册表分配日志。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-05-30 创作流程统一化门禁扩展为跨玩法矩阵
- 背景:统一创作 / 统一生成门禁已经足够覆盖 Phase 2 的入口与壳层,但当前总计划已经推进到 Phase 3-6,继续只保留单页门禁会让 Phase 4 的特殊工作台、Phase 5 的结果页 / 作品架 / 公开详情和 Phase 6 的冻结验收没有统一入口。
- 决策:`quality-gates/README.md` 继续保留单页门禁与 `dev-stack` 门禁,同时新增跨玩法回归 / 冒烟门禁,按 Phase 2 到 Phase 5 的最小验证集合分层执行;Phase 6 冻结前以这份矩阵为主,不再另外拆新波次。涉及入口配置、统一字段 spec、普通工作台、RPG / Bark Battle / 视觉小说特殊边界、发布 / 公开 / runtime 或本地 smoke 的变更,优先对照这份矩阵补齐验收命令。
- 影响范围:`quality-gates/README.md`、`quality-gates/【玩法创作】跨玩法回归与冒烟门禁-2026-05-30.md`、`docs/planning/【玩法创作】创作流程统一总计划-2026-05-30.md`、后续 Phase 2-6 玩法接入与冻结流程。
- 验证方式:按矩阵执行 `npm run check:encoding`、`npm run typecheck`、`npm run admin-web:typecheck`、对应分期 `npm run test`、`npm run check:visual-novel-vn11`,以及需要时的 `npm run dev:api-server` + `/healthz` smoke。
- 关联文档:`quality-gates/README.md`、`quality-gates/【玩法创作】跨玩法回归与冒烟门禁-2026-05-30.md`、`docs/planning/【玩法创作】创作流程统一总计划-2026-05-30.md`。
## 2026-05-30 跳一跳结果页直达必须优先恢复作品而不是白屏
- 背景:跳一跳结果页已经接入统一壳,但如果用户直接打开 `/creation/jump-hop/result`,旧路径容易因为缺少 `draft` 恢复信息而看起来像白屏,误导成结果页坏了。
- 决策:`PlatformEntryFlowShellImpl` 的跳一跳恢复顺序固定为 `profileId -> getWorkDetail`,再 `sessionId -> getSession`;两者都拿不到时必须展示 `跳一跳草稿未恢复` 恢复面板和 `返回创作`,不能继续留空白结果页。进入结果页的 smoke 允许恢复面板,但不允许纯空白。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`quality-gates/【玩法创作】跨玩法回归与冒烟门禁-2026-05-30.md`、`docs/planning/【玩法创作】创作流程统一总计划-2026-05-30.md`。
- 验证方式:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "direct jump hop result route"`;手测 `/creation/jump-hop/result` 和 `/creation/jump-hop/result?profileId=<id>`。
- 关联文档:`docs/planning/【玩法创作】创作流程统一总计划-2026-05-30.md`、`quality-gates/【玩法创作】跨玩法回归与冒烟门禁-2026-05-30.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-29 一期统一创作页必须提供可见统一外壳
- 背景:`UnifiedCreationPage` 首版只暴露隐藏 spec 元数据并包裹旧玩法工作台,用户打开拼图创作页时仍只能看到旧工作台外观,无法验收“统一创作页”。
- 决策:一期统一创作页(拼图、抓大鹅、敲木鱼)必须由 `UnifiedCreationPage` 提供统一标题栏、内容区、页面级纵向滚动和隐藏字段契约;字段元信息只留给测试和代码,不再额外作为可见 chip 占用首屏。玩法工作台只承载具体输入控件、上传、历史素材、校验和提交,不再各自渲染巨大入口标题。拼图、抓大鹅与敲木鱼的实现已经统一收口到 `src/components/unified-creation/workspaces/`,统一壳只依赖 `UnifiedCreationWorkspace`。敲木鱼右侧音效和功德面板不得再套内部滚动容器,移动端应自然跟随页面滚动。
- 追加决策:`UnifiedCreationPage` 自己负责页面级滚动;拼图、抓大鹅、跳一跳和敲木鱼四条统一创作入口必须在同一页面壳内从统一标题、表单控件一路滑到提交按钮,避免工作台内部或右侧面板形成套滚动。
- 影响范围:`src/components/unified-creation/UnifiedCreationPage.tsx`、`src/components/unified-creation/UnifiedCreationWorkspace.tsx`、`src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx`、`src/components/unified-creation/workspaces/Match3DCreationWorkspace.tsx`、`src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、玩法链路文档。
- 验证方式:`UnifiedCreationPage` 测试应断言隐藏契约仍在但 UI 不再出现字段 chip;拼图和抓大鹅工作台测试应断言 `unifiedChrome=true` 时不再渲染旧巨大标题且仍保留表单输入;木鱼工作台测试或手测应确认敲击音效和功德词条不再停留在独立滚动窗内。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-31 统一创作壳扩展到跳一跳并接管页面级滚动
- 背景:最初的统一创作页只收口拼图、抓大鹅和敲木鱼,跳一跳仍通过独立工作台壳与独立生成壳渲染,导致用户在 `/creation/jump-hop` 看到的可见外壳与其它统一入口不一致。
- 决策:`jump-hop` 也纳入统一创作壳与统一生成壳;`UnifiedCreationPage` 现在承担页面级滚动和统一标题栏,拼图、抓大鹅、跳一跳、敲木鱼四条入口都通过同一外壳承载各自工作台。`JumpHopCreationWorkspace`、`WoodenFishCreationWorkspace` 也补了 `unifiedChrome` / `showBackButton` 受控能力,避免双标题或双返回按钮。
- 追加决策:`UnifiedCreationPage` 的统一页头现在承载唯一返回入口,工作台内部的返回按钮全部关闭,避免同一页面出现双返回按钮;`UnifiedCreationWorkspace` 统一把 `onBack` 透传给页头。
- 追加决策:统一创作页内容区必须保持自然高度,页面级滚动只由 `UnifiedCreationPage` 外层承担,工作台内部只负责内容展开,不再额外包滚动壳。
- 影响范围:`src/components/unified-creation/UnifiedCreationPage.tsx`、`src/components/unified-creation/unifiedCreationSpecs.ts`、`src/components/unified-creation/unifiedGenerationCopy.ts`、`src/components/unified-creation/workspaces/JumpHopCreationWorkspace.tsx`、`src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`server-rs/crates/shared-contracts/src/creation_entry_config.rs`。
- 验证方式:`npm run test -- src/components/unified-creation/unifiedCreationSpecs.test.ts src/components/unified-creation/UnifiedCreationPage.test.tsx src/components/unified-creation/UnifiedGenerationPage.test.tsx src/components/unified-creation/workspaces/JumpHopCreationWorkspace.test.tsx src/components/unified-creation/workspaces/WoodenFishCreationWorkspace.test.tsx``npm run test -- src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx``npm run test -- src/routing/appPageRoutes.test.ts`。
## 2026-05-31 统一创作编排层必须由 UnifiedCreationWorkspace 统一收口
- 背景:`PlatformEntryFlowShellImpl` 仍直接 lazy import 并渲染四个旧工作台分支,虽然统一创作页已存在,但入口壳层仍然依赖旧工作台分支。
- 决策:新增 `UnifiedCreationWorkspace` 作为平台壳唯一依赖的统一创作编排层,由它内部按 `playId` 选择四条入口的真实工作台;平台壳层只再挂这一层,不再直接依赖旧工作台组件。旧工作台已移入 `src/components/unified-creation/workspaces/`,不再作为平台入口编排事实源。
- 影响范围:`src/components/unified-creation/UnifiedCreationWorkspace.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、统一创作页相关测试与后续入口接入。
- 验证方式:平台壳源码中不应再直接出现四个旧工作台的入口渲染分支;创作 Tab 与 `/creation/<play>` 仍可正常进入对应工作台。
- 关联文档:`docs/planning/【玩法创作】创作流程统一总计划-2026-05-30.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-27 生成页总进度圆弧锁定固定 SVG 坐标系
- 背景:多轮圆环角度微调后,`GenerationProgressHero` 的 SVG 圆弧仍会出现底部开口偏斜的问题;后来窄屏验收又发现固定 `400px` 外层宽度会让等待页右侧被裁切。
- 决策:共用 `GenerationProgressHero` 的 SVG 圆弧起始角固定为 `135deg`,轨道和橘黄色填充都从同一个对称起点 `rotate(135 200 200)` 出发;`270deg` 扫描角配合正下方 `90deg` 留空。SVG 内部坐标系固定为 `400x400`,圆弧使用 `r=166` 和 `strokeWidth=18`;外层显示宽度以 `400px` 为上限,窄屏按父容器 `min(400px, calc(100% - 0.75rem))` 等比收缩,避免嵌套页面 padding 或负 margin 下用 `100vw` 误判宽度。预计等待 / 已耗时信息卡在窄屏下落到圆环下方两列,`sm` 及以上再回到左右悬浮。
- 影响范围:`src/components/GenerationProgressHero.tsx`、共用 `CustomWorldGenerationView`、汪汪声浪 `BarkBattleGeneratingView` 以及生成页圆环布局文档。
- 验证方式:`CustomWorldGenerationView` 和 `BarkBattleGeneratingView` 测试断言 `data-ring-start-degrees=135`、`data-ring-fill-start-degrees=135`,且圆环容器包含 `w-[min(400px,calc(100%_-_0.75rem))]`、`max-w-full` 与 `aspect-square`track / fill transform 都是 `rotate(135 200 200)`;竖屏 smoke 至少覆盖 `280px / 320px / 360px / 390px` 宽度。
- 关联文档:`docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`。
## 2026-05-26 平台跨流程错误统一用可复制来源弹窗展示
- 背景:拼图等生成链路可能同时存在多个草稿或游玩实例,页面内裸错误 banner 容易让用户误以为当前正在看的拼图失败,也不方便复制完整错误给开发排查。
- 决策:平台入口、生成页、结果页、作品详情、作品架和运行态的跨流程错误统一收口到 `PlatformErrorDialog`;弹窗必须带错误来源,例如某个草稿、生成会话、作品详情或游玩实例,并提供复制按钮复制来源与错误内容。页面内旧的裸错误 banner、创作入口 modal 错误、生成页错误徽标等不再重复展示;表单校验和发布确认弹窗里的局部业务错误仍可保留在原弹窗内。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/platform-entry/PlatformErrorDialog.tsx`、`src/components/CustomWorldGenerationView.tsx`、`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/components/platform-entry/PlatformWorkDetailView.tsx`、`src/components/platform-entry/PlatformEntryCreationTypeModal.tsx`、`src/components/puzzle-result/PuzzleResultView.tsx`。
- 验证方式:`npm run test -- src/components/platform-entry/PlatformErrorDialog.test.tsx src/components/platform-entry/PlatformEntryCreationTypeModal.test.tsx`、`npm run typecheck`、`npm run check:encoding` 通过;手测时异步失败应弹出包含“错误来源”和“错误内容”的弹窗,复制按钮应复制完整诊断文本。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-26 生成任务完成在离开生成页后弹独立完成弹窗
- 背景:抓大鹅、拼图等生成任务完成时,用户如果已经离开生成页,草稿页的未读红点不足以表达“这次生成已完成”;但如果用户仍停留在生成页,结果页或试玩页本身就是完成反馈,不需要再叠一个成功提示。
- 决策:平台壳层在 `markDraftReady(..., viewedImmediately=false)` 时额外弹出 `PlatformTaskCompletionDialog`,完成弹窗必须带来源和复制按钮;如果 `viewedImmediately=true`,只保留结果页 / 试玩页本身的完成反馈和草稿未读态,不重复弹窗。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/platform-entry/PlatformTaskCompletionDialog.tsx`、`src/components/platform-entry/PlatformErrorDialog.test.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
- 验证方式:`npm run test -- src/components/platform-entry/PlatformErrorDialog.test.tsx`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "completed match3d draft"` 通过后,离开生成页再完成的草稿应出现“生成完成”弹窗,且复制内容包含来源与状态。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-26 “我的”页任务卡读后端任务摘要并移除常驻填邀请码入口
- 背景:移动端“我的”页每日任务卡曾硬编码 `0 / 1`,任务领取完成后只刷新弹窗内任务中心,卡片本身不更新;页面底部还保留旧的“填邀请码”次级按钮,和当前五项常用功能宫格口径重复。
- 决策:`RpgEntryHomeView` 的每日任务卡以 `/api/profile/tasks` 返回的任务中心为事实源,展示当前可操作任务的奖励、进度和状态;领取成功后同步使用 claim 响应里的 `center` 刷新卡片。移动端“我的”页不再渲染常驻“填邀请码”次级入口,邀请码填写仅保留邀请链接 query 自动打开弹窗和其它明确引导。
- 影响范围:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
- 验证方式:`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx` 应断言任务卡显示 `1 / 1`、领取后显示已完成,且新用户账号也没有 `次级入口` / `填邀请码` 常驻按钮;`npm run typecheck`、`npm run check:encoding` 通过。
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
## 2026-05-26 生成页总进度圆弧逆时针回调 5 度
- 背景:创作生成页的总进度圆弧在 `160deg` 位置仍需轻微向左微调,用户要求向左逆时针回调 `5deg`。
- 决策:共用 `GenerationProgressHero` 的 SVG 圆弧起始角从 `160deg` 调整为 `155deg`track 和 fill 都使用同一个 `rotate(155 200 200)` 变换;仍保持 `270deg` 扫描角和正下方 `90deg` 留空。
- 决策:总进度标题与百分比数字在 `GenerationProgressHero` 中显式提升到圆环之上,圆环 SVG 维持背景层级。
- 决策:总进度标题与百分比数字的内容区上边距从 `pt-[4%]` 收紧到 `pt-[2%]`,桌面端使用 `sm:pt-[1.5%]`,进一步拉开与圆环弧线的距离。
- 影响范围:`src/components/GenerationProgressHero.tsx`、共用 `CustomWorldGenerationView`、汪汪声浪 `BarkBattleGeneratingView` 以及生成页圆环布局文档。
- 验证方式:`CustomWorldGenerationView` 和 `BarkBattleGeneratingView` 测试断言 `data-ring-start-degrees=155` 且 track / fill transform 都是 `rotate(155 200 200)`。
- 关联文档:`docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`。
## 2026-05-25 抓大鹅发现页官方 demo 使用静态资源与本地运行态
- 背景:本轮抓大鹅资源管线曾生成一套官方静态 demo,用于验证生图、切图和运行态资源闭环,但该 demo 已被移除,不再作为发现页入口。
- 决策:发现页不再挂载前端固定官方抓大鹅 demo;公开卡片、作品号搜索、详情页和运行态启动全部来自后端真实 profile / gallery 投影,正式作品统一走 server runtime adapter。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、原前端 demo 数据文件(已删除)、Match3D 相关测试与原静态资源目录(已删除)。
- 验证方式:发现页不再出现原固定 demo;Match3D 公共详情只从真实后端作品数据读取。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-25 抓大鹅运行态 HUD 收敛为拼图同款低遮挡样式
- 背景:抓大鹅游玩阶段 UI 需要继续对齐拼图运行态的观感,同时移除右上角设置入口、灰白半透底板和显眼锅壳,让棋盘区域更专注。
- 决策:抓大鹅运行态只保留左上透明返回按钮,右上不再显示设置入口;顶部关卡名和倒计时直接复用拼图同款的铭牌 + 下挂计时牌结构、同色板、同造型和 `media/logo-runtime-hud.webp` 产品 logo 小图;底部备选栏和道具图标保持交互边界但不再显示灰白半透底;中央容器图层可以视觉隐藏,但棋盘命中边界和既有交互逻辑保留。
- 影响范围:`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`src/components/match3d-runtime/Match3DRuntimeShell.test.tsx`、`src/index.css`、抓大鹅玩法链路文档。
- 验证方式:运行态页面不再渲染“打开抓大鹅设置”,顶部仍显示关卡名和倒计时,底部槽位和道具按钮 class 中不含旧白底视觉;相关测试通过后保持该口径。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-25 平台首页推荐按桌面与移动断点分流
- 背景:平台首页的推荐页在桌面与移动端之间原先共用同一套推荐运行态逻辑,容易让桌面和移动两套内容同时启动,也让首页的推荐卡与桌面发现壳互相抢状态。
- 决策:`RpgEntryHomeView` 只接受同一个 `isDesktopLayout` 断点判断;桌面端首页渲染桌面发现壳(`今日游戏`、`推荐`、`作品分类` 等),不挂移动推荐嵌入运行态;移动端 `home` 才渲染推荐卡与嵌入运行态。平台壳和首页视图都必须共用 `usePlatformDesktopLayout()`,不能在不同文件里各自判断断点。推荐嵌入运行态不是登录门禁:未登录可直达匿名运行态;已登录或已有 access token 时继续使用账号 Bearer,但必须用 local auth impact 防止推荐卡 401 清空全局登录态。
- 影响范围:`src/components/platform-entry/platformEntryResponsive.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、首页推荐相关测试与 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
- 验证方式:桌面宽度下首页应只看到桌面发现壳,窄屏下首页应只看到移动推荐流;`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recommend"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation"`、`npm run typecheck`、`npm run check:encoding` 通过。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-25 新增玩法接入必须使用统一 SOP skill
- 背景:敲木鱼、跳一跳、汪汪声浪等玩法接入过程中,作品架曾经没有被作为强制闭环验收项,导致玩法可以先完成创作、发布、运行态或广场,但用户在草稿 / 已发布作品架中看不到自己的作品。
- 决策:凡是新增、补齐、迁移或重构玩法入口、玩法类型、创作工作台、生成页、结果页、发布、运行态、作品架、广场或公开 read model 的任务,开始前必须显式读取并按 `.codex/skills/genarrative-play-type-integration/SKILL.md` 执行。需要发布或试玩的玩法,作品架不是可选项,必须补齐私有 `/works` 列表、作品摘要、pending shelf 兜底、统一作品架 adapter、打开详情 / 草稿恢复、已发布分享入口和草稿 / 已发布可见性测试。
- 影响范围:`AGENTS.md`、`.codex/skills/genarrative-play-type-integration/SKILL.md`、玩法 PRD、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、新增玩法前后端接入流程。
- 验证方式:玩法接入 PRD 和实现验收必须列出作品架链路;若一个玩法具备发布或试玩能力,但缺少 `/api/creation/<play>/works`、前端 client `listWorks`、`CustomWorldCreationHub` props、`creationWorkShelf` adapter 或草稿 / 已发布作品架测试,则接入不算完成。
- 关联文档:`AGENTS.md`、`.codex/skills/genarrative-play-type-integration/SKILL.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-26 统一公开作品主读模型收口
- 背景:各玩法原有 `*_gallery_card_view` / `*_gallery_view` / `custom_world_gallery_entry` 已经足够承载各自 source 投影,但公开列表 / 详情在 `api-server` 侧分散拼装会继续放大重复逻辑和契约漂移。
- 决策:新增跨玩法统一公开主读模型 `public_work_gallery_entry` 与 `public_work_detail_entry`。各玩法旧公开 view 不删除,退为 source / 兼容路径;`api-server` 公开列表与详情主路径统一读 public view cache,再映射回现有 HTTP DTO。前端首期仍不直接订阅 SpacetimeDB,只走 BFF HTTP。
- 影响范围:`server-rs/crates/spacetime-module`、`server-rs/crates/spacetime-client`、`server-rs/crates/api-server`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md`。
- 验证方式:`SELECT * FROM public_work_gallery_entry` 与 `SELECT * FROM public_work_detail_entry` 可作为 `api-server` 长期订阅目标;`/api/public-works` 与 `/api/public-works/{publicWorkCode}` 走统一 cache;旧 `/api/runtime/<play>/gallery` 响应 shape 保持兼容。
- 关联文档:`docs/technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-26 推荐页拼图下一关 pending 时保留当前运行态
- 背景:推荐页嵌入拼图在点击“下一关”时,`advancePuzzleNextLevel` 的服务端请求会短暂处于 pending。旧逻辑把推荐卡的 `isStartingRecommendEntry` 和拼图局部 busy 混在一起,导致外层直接切回“加载中...”,把当前 `PuzzleRuntimeShell` 一起卸载,视觉上像是切关闪回。
- 决策:推荐页嵌入拼图切关 pending 期间必须保留当前运行态与棋盘,只让拼图壳内部 busy 表现承接同步;`isStartingRecommendEntry` 只表示推荐作品尚未真正启动出来,不再把已有嵌入拼图 run 的局部 busy 一并当成整卡加载态。若下一关落到相似作品,前端还必须把新作品写回推荐缓存并同步 `activeRecommendEntryKey`,避免运行态进入新作品但推荐卡元信息、分享 / 点赞 / 改造和后续“下一个”仍锚定旧作品。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、推荐页拼图切关测试与平台链路文档。
- 验证方式:点击推荐页拼图“下一关”后,在 `advancePuzzleNextLevel` 未返回前,页面仍应保留 `puzzle-board`,且不出现 `加载中...` 占位;返回相似作品后,当前推荐卡的 `作品信息` 应显示新作品标题。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-24 创作入口页 banner 曾固定主题赛
- 背景:点击底部加号进入的创作入口页 banner 曾经把后端入口配置里的默认活动横幅和两个主题赛一起轮播,导致出现 58000 奖池活动卡,和当时只强调拼图 / 抓大鹅主题赛的产品口径不一致。
- 决策:当时固定只展示 `拼图主题创作赛` 与 `抓大鹅主题创作赛` 两张主题卡;该口径已被 2026-06-02 的后台 `eventBanners` 配置决策替代。banner 底部顺序固定为开始 / 结束时间条在上、分页点在下,且二者都在封面内容底部。
- 影响范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/components/custom-world-home/CustomWorldCreationHub.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
- 验证方式:`CustomWorldCreationHub.test.tsx` 应断言默认活动标题不出现在 start-only 创作页,且 `creation-event-banner__timebar` 位于 `creation-event-banner__pager` 前。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-24 创作 Tab 首屏字号收敛到普通 UI 档位
- 背景:创作 Tab 的右上角泥点胶囊、赛事 banner、分类 Tab 和玩法卡标题 / 副标题 / 消耗说明曾经偏向展示级字号,和其它页面的常规 UI 字号不一致。
- 决策:创作首屏优先使用 `11px` 到 `14px` 的普通 UI 字号档位;仅在数字本体或强调值上做局部加粗,不使用 `text-lg`、`text-xl` 或更大的展示级字号来撑首屏。
- 影响范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、创作 Tab 相关测试、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
- 验证方式:`CustomWorldCreationHub.test.tsx` 的字号快照测试和本地浏览器检查都应确认右上组件、banner、分类 Tab、模板卡标题 / 副标题 / 消耗说明没有回到大字号。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-24 草稿页未读点统一使用暖棕色
- 背景:草稿页底部 Tab 和作品架的未读点之前仍用固定红色和红色 glow,和平台暖白/陶土橙体系不一致,也会让草稿未读态显得像危险告警。
- 决策:`platform-nav-unread-dot` 与 `creation-work-card__unread-dot` 统一改用平台暖棕色 token,并把 glow 也切到暖棕色,不再直接写红色 literal 或红色阴影。
- 影响范围:`src/index.css`、草稿页底部导航、草稿页作品架、相关 CSS 回归测试。
- 验证方式:`src/index.test.ts` 需要断言两个 unread dot block 都不再包含 `#b64a35` 或 `rgba(239, 68, 68, ...)`,并且仍引用 `--platform-unread-dot-fill` / `--platform-unread-dot-glow`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-24 创作 Tab 模板卡点击直达已有玩法入口表单
- 背景:创作 Tab 首屏需要对齐参考图,展示赛事 banner、玩法模板分类和两列模板卡;点击模板卡时,空白入口页会让用户多走一层,占位感也会让人误以为功能未接好。
- 决策:`/creation/<play>` 直达对应玩法已有的入口创作表单 stage,不再保留空白创作入口页。RPG、拼图、抓大鹅、汪汪声浪、敲木鱼、视觉小说、宝贝识物等都直接进入既有工作台,继续承接草稿恢复和后续编排。点击底部加号进入的创作入口页 banner 按参考图拆成右上泥点胶囊、主体宣传封面图文、底部开始/结束时间条和分页点;玩法模板卡使用独立 `creation-template-card` 白底信息区,不复用暗图蒙版 `platform-creation-reference-card`,确保标题、描述和“预计消耗 10-20 泥点”可见。
- 影响范围:`src/components/platform-entry/platformEntryTypes.ts`、`src/routing/appPageRoutes.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、创作大厅交互测试与平台入口文档。
- 验证方式:`npm test -- src/routing/appPageRoutes.test.ts`、`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t \"create tab opens match3d entry form from the template card|create tab opens puzzle entry form from the template card|create tab opens bark battle entry form from the template card\"`、`npm run typecheck`、`npm run check:encoding` 通过;创作卡片点击后应进入对应工作台,不再出现空白入口页。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-24 创作 Tab 顶栏余额与赛事奖池分离展示
- 背景:创作页顶部、banner 奖池和玩法卡消耗口径曾经混在一起,容易把活动奖池误认成账号余额,也让横向空间被外部边框和过大的卡片高度挤占。
- 决策:移动端创作 Tab 顶栏与 `陶泥儿` 品牌同一行只显示真实账户泥点数,数据直接取 `profileDashboard.walletBalance`banner 内只展示赛事奖池,新增拼图主题创作赛和抓大鹅主题创作赛,两个主题奖池各 `1000` 泥点数;玩法卡封面右下角固定展示 `10-20泥点数`,列表外框取消,卡片高度和横向间距一起收紧。
- 影响范围:`src/components/custom-world-home/CustomWorldCreationStartCard.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、创作页相关测试和玩法链路文档。
- 验证方式:移动端浏览器检查应看到创作顶栏余额、卡内分页点、内嵌横向 banner 和更紧凑的玩法卡;`CustomWorldCreationHub.test.tsx` 与 `RpgEntryHomeView.recharge.test.tsx` 的定向断言应保持通过。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-24 发现 / 创作 / 草稿三页去掉外层全局卡片壳
- 背景:发现 Tab、创作 Tab 和草稿 Tab 的页面根区原本都套着 `platform-page-stage`,导致全局内容卡片壳把横向空间吃掉,也让创作页和草稿页与发现页的频道标签 / 列表卡风格拉不开。
- 决策:这三页的根内容区不再使用 `platform-page-stage` 作为外层全局卡片壳,只保留 `platform-remap-surface` 作为主题与输入框样式钩子;草稿页顶部 `全部 / 草稿 / 已发布` 切换复用发现页的 `platform-mobile-home-channel` 频道标签样式。
- 影响范围:`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldWorkTabs.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/index.css`、相关创作 / 发现 / 草稿测试。
- 验证方式:创作 Hub 和发现页定向测试通过;浏览器里这三页的根区不再出现 `platform-page-stage`,但仍保留 `platform-remap-surface` 命中。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-23 拼图生成页按后端真实进度推进阶段
- 背景:拼图生成页原先会按本地耗时自动推进步骤,容易在后端真实生成尚未完成时跳到后续阶段,导致页面状态和会话进度脱节。
- 决策:拼图生成页的跨步骤推进只认后端会话 `progressPercent` 的真实里程碑,当前步骤内部再用本地耗时假进度平滑展示;`88/94/96` 只切换当前步骤,不直接作为总进度地板。总进度按已完成步骤权重加当前步骤内假进度推导,非完成态最多停在 `98%`。恢复持久化生成中草稿时,展示态 `startedAtMs` 使用后端 session `updatedAt` 或作品摘要 `updatedAt`,保证已耗时不因重新进入页面清零。只要当前步骤生成内容未完成,就必须停留在当前步骤。页面只展示当前步骤标题和进度,不展示步骤详细描述。`生成拼图首图` 单独按 4 分钟估算,完整 AI 重绘路径约 448 秒;上传图且关闭 AI 重绘路径跳过首图生成,仍约 208 秒。
- 影响范围:`src/services/miniGameDraftGenerationProgress.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/CustomWorldGenerationView.tsx`、拼图生成页相关测试与玩法链路文档。
- 验证方式:拼图生成页恢复、轮询和测试都应以 `puzzleProgressPercent` 驱动阶段推进;`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts src/components/CustomWorldGenerationView.test.tsx`、`npm run typecheck`、`npm run check:encoding` 通过。
- 关联文档:`docs/【玩法创作】拼图生成页进度口径-2026-05-23.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-02 生成失败草稿必须留在作品架并覆盖生成中摘要
- 背景:生成页收到失败回包后会进入重试态,但返回草稿 Tab 时,后端作品摘要可能仍短暂保持 `generationStatus=generating`,导致用户看到“生成中”;连续触发多个拼图生成时,失败后如果清掉 pending 条目,还会少显示新增草稿。后台失败如果只写局部生成页错误,用户离开生成页后也收不到通知。
- 决策:平台壳在生成失败时必须同时标记草稿 notice 和 pending 作品架条目为 `failed`,不得删除 pending 条目。失败 notice 要保存错误消息并在用户离开生成页后触发带来源的 `PlatformErrorDialog`;作品架本地失败 notice 要覆盖持久化生成中摘要,失败草稿仍显示为草稿卡但不显示“生成中”。点击失败草稿必须优先恢复失败 / 重试页,不能按持久化 `generating` 重新启动生成;拼图契约已允许 `generationStatus=failed`pending 拼图和后端失败回写都按 session 独立落失败态,跳一跳 / 木鱼 / 抓大鹅等也直接映射为 `failed` 或对应失败态。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/custom-world-home/creationWorkShelf.ts`、`src/components/custom-world-home/CustomWorldCreationHub.tsx`、玩法链路文档和失败态交互测试。
- 验证方式:`node node_modules/vitest/vitest.mjs run src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "failed parallel puzzle|background match3d"`;失败后返回草稿 Tab 应看到对应新增草稿,且没有“生成中”标记;后台失败应弹出错误来源,点击失败草稿应进入失败 / 重试页。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/project-memory/shared-memory/pitfalls.md`。
## 2026-05-23 所有玩法生成页统一圆环主视觉
- 背景:多个玩法生成页分别展示横向总进度条、步骤列表或三槽位列表,和最新参考图里的陶泥儿圆环等待态不一致,也让移动端信息密度偏高。
- 决策:`media/create_bg_video.mp4` 作为固定全屏背景层循环静音播放,主进度统一改为居中大圆弧,正下方保留 90 度留空;生成页顶部只保留返回入口和状态胶囊,圆弧左右悬浮半透明“预计等待 / 已耗时”时间卡,下方保留半透明当前步骤单卡和当前作品信息卡。生成页不再列表展示每个步骤块,只显示当前步骤名称和当前步骤进度;圆弧和当前步骤卡不再被独立大面板嵌套出双层卡片感。视频层需要显式触发播放,不能只依赖 `autoPlay/loop/muted`。顶部返回使用 `text-xs-sm`,右上状态使用 `11px-12px`,时间卡标签使用 `9px-10px`,时间值只展示纯时间,不重复拼“预计还需 / 已耗时”前缀;当前步骤标签使用 `10px-11px`,步骤名使用 `14px-15px`,步骤状态使用 `11px-12px`,底部玩法信息标题固定使用 `13px`,避免生成页 UI 字号大于其它页面。`CustomWorldGenerationView` 承接 RPG、拼图、抓大鹅、大鱼吃小鱼、方洞、跳一跳、敲木鱼、宝贝识物、视觉小说等共用生成页;汪汪声浪独立 `BarkBattleGeneratingView` 也对齐同一垂直布局。
- 影响范围:`src/components/GenerationProgressHero.tsx`、`src/components/CustomWorldGenerationView.tsx`、`src/components/bark-battle-creation/BarkBattleGeneratingView.tsx` 和玩法链路文档。
- 验证方式:执行 `npm run test -- src/components/CustomWorldGenerationView.test.tsx src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx`,并用桌面 / 移动端视口检查生成页只出现圆环和当前步骤卡。
- 关联文档:`docs/【玩法创作】生成页圆环布局口径-2026-05-23.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-23 寓教于乐玩法入口收敛为马路街区式横向延展
- 背景:参考图和视频表明,寓教于乐板块的图形化入口更接近 Toca Life World 式的“中央马路串联主题小建筑群街区”,而不是乐园分区、环形岛屿或世界球体结构。
- 决策:后续寓教于乐入口概念图统一采用“横屏 16:9、中央灰蓝色马路贯穿、建筑群沿路两侧聚集、左右边缘持续出画可接下一屏”的结构;马路必须带车道线、斑马线、路口和小汽车,区域通过水果店、画笔工坊、运动馆、音乐剧场、树屋温室等主题小建筑群暗示,不再使用乐园式分区组织。
- 影响范围:寓教于乐入口概念图、image2 prompt 生成脚本、设计文档、后续横向世界地图探索稿。
- 验证方式:新生成概念图必须满足“马路是主脊线、建筑群成街区聚合、左右边缘可延展、无品牌乐园元素”四项约束;若图面再跑回环形乐园或漂浮岛,需要重新收敛 prompt。
- 关联文档:`docs/design/【前端体验】寓教于乐Toca式横向世界地图入口概念图-2026-05-23.md`、`scripts/generate-edutainment-road-town-map-concepts.mjs`、`output/imagegen/edutainment-road-town-map-concepts-20260523/`。
## 2026-05-22 敲木鱼图片创作采用三图 image2 链路
- 背景:敲木鱼自定义题材只生成中央敲击物时,运行态缺少与新主题匹配的竖屏背景和主题化返回按钮;若直接让背景 prompt 自由发挥,又容易把敲击物或木槌画进背景里。
- 决策:敲木鱼 `compile-draft` / `regenerate-hit-object` 图片链路固定为三步 image2 edits。第一步调用 VectorEngine `/v1/images/edits` + `gpt-image-2`,以默认木鱼图作为结构和画风参考,用户上传参考图只作为同次请求的新主题参考,结合用户题材关键词或参考图主题生成 `1:1` 单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景主体图;`api-server` 先对这张绿幕图执行去绿背景处理并写回 `hitObjectAsset`。第二步必须以第一步抠图完成后的透明敲击物图作为参考,结合用户原始题材生成 `9:16` 背景环境图并写回 `backgroundAsset`,避免背景图继承绿幕或纯绿色画布。第三步必须以去绿后的敲击物主体图和背景环境图为参考,生成 `1:1` 单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景返回按钮图,服务端去绿后写回 `backButtonAsset`。三步 prompt 使用 PRD 中固定隐藏关键词,不追加额外 negative prompt;返回按钮只允许参考图约束圆形底色和箭头配色,不允许继承复杂造型、花纹、浮雕边、异形外框或装饰图案,主体视觉尺寸比当前模板再放大约 50%,并带主题色外描边;背景图不得包含敲击物本体或木槌互动物品,返回按钮图不得包含文字、数字、水印或额外 UI 面板。
- 影响范围:`api-server` 木鱼图片生成编排、`wooden_fish_work_profile.background_asset_json`、`wooden_fish_work_profile.back_button_asset_json`、shared contracts、前端结果页 / 运行态背景与返回按钮展示、敲木鱼 PRD 和平台链路文档。
- 验证方式:执行 `cargo test -p api-server wooden_fish --manifest-path server-rs/Cargo.toml`、`cargo test -p spacetime-client wooden_fish --manifest-path server-rs/Cargo.toml`、`npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run typecheck`。
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-25 通用系列素材图集实现下沉到 platform-image
- 背景:`generated_asset_sheets` 同时承载 sheet prompt、切图、绿幕去背、边缘 matte 清理和 OSS 持久化准备,长期放在 `api-server` 会把多个玩法的图片 seam 继续绑死在 HTTP crate 上。
- 决策:通用系列素材图集的实现真值源下沉到 `platform-image::generated_asset_sheets``api-server::generated_asset_sheets` 只保留 `AppState` / `AppError` 适配与调用方兼容导出,不再承载图像处理和 OSS 请求构造细节。
- 影响范围:`server-rs/crates/platform-image/src/generated_asset_sheets/`、`server-rs/crates/api-server/src/generated_asset_sheets.rs`、`server-rs/crates/api-server/src/match3d/item_assets.rs`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
- 验证方式:`cargo test -p platform-image --test generated_asset_sheets --manifest-path server-rs/Cargo.toml` 与 `cargo check -p api-server --manifest-path server-rs/Cargo.toml` 通过;调用方继续通过 `api-server` 的薄包装访问同一组能力。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-22 敲木鱼敲击物暂不做服务端抠图后处理
- 背景:gpt-image-2 偶尔会把木鱼图直接回成带黑底或其它实底背景的 PNG,但服务端抠图后处理在玉米等主题上误伤过主体像素。
- 决策:敲木鱼 hit object 落盘前暂不做服务端抠图后处理,当前只通过 prompt 强约束真实透明 alpha PNG、透明底、禁止黑底 / 白底 / 棋盘格 / 实底背景。后续若重启后处理,必须先有可验证的保守策略,只能清理画布边缘连通背景,不能抠掉主体内部深色结构或主题细节。
- 影响范围:`server-rs/crates/api-server/src/wooden_fish.rs`、敲木鱼 PRD、平台链路文档、后续同类 image2 单图资产落盘策略。
- 验证方式:`cargo test -p api-server wooden_fish --manifest-path server-rs/Cargo.toml`,并在试玩阶段确认主体像素未被后处理误删。
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-22 敲木鱼背景中央禁主体要写成硬约束
- 背景:苹果等主题在试玩时,背景图中央仍可能残留主题主体,说明“外围设计”这种软描述不够。
- 决策:敲木鱼背景 prompt 必须显式要求中央主体预留区保持干净,中央 40% 区域禁止出现主题主体、主体局部特写、轮廓影子、重复元素或主题主体碎片;主题元素只允许出现在外围氛围。
- 影响范围:`server-rs/crates/api-server/src/wooden_fish.rs`、敲木鱼 PRD、平台链路文档、后续 image2 背景类玩法 prompt。
- 验证方式:背景 prompt 单测应包含中央禁区硬约束,试玩图中央不再出现苹果或其它主题主体。
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-27 敲木鱼背景 prompt 不再写中央木鱼预设
- 背景:背景 prompt 曾写入“木鱼预设在屏幕中央位置”,与“背景图中不包含新木鱼物品”“中央 40% 禁止出现主题主体”直接冲突,导致 image2 偶发把静态木鱼画回背景中心。
- 决策:背景 prompt 只能写“中央主体预留区”“运行态叠放敲击物的留白区域”“只生成背景环境图”,不得再出现“木鱼预设在屏幕中央位置”或任何等价的中心主体正向描述。
- 影响范围:`server-rs/crates/api-server/src/wooden_fish.rs`、敲木鱼 PRD、平台链路文档、背景 prompt 单测。
- 验证方式:`wooden_fish_background_prompt_uses_hidden_image2_flow` 必须断言旧冲突句子不存在,并断言新的中央留白表述存在。
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-21 外部 API 失败必须 OTLP 上报并落库
- 背景:图片生成等外部供应商调用失败时,仅返回 502/504 或普通日志无法支持后续按 provider、阶段和重试属性聚合排障。
- 决策:外部 API 调用未成功时,`api-server` 必须同时发送 OTLP 失败观测并写入 `tracking_event`。当前通用 VectorEngine `gpt-image-2` 图片生成 / 编辑适配器记录 `external_api_call_failure``scope_kind = module`、`scope_id = provider`、`module_key = external-api`metadata 包含 endpoint、operation、failureStage、statusCode、statusClass、timeout、retryable、errorMessage、latencyMs、promptChars、referenceImageCount、imageModel 和 rawExcerpt。
- 落库方式:优先复用 tracking outbox 异步批量写入;outbox 不可写或因保护阈值拒绝时回退同步直写 SpacetimeDB。不新增 SpacetimeDB 表,不让 reducer 做外部 I/O。
- 影响范围:`server-rs/crates/api-server/src/external_api_audit.rs`、`server-rs/crates/api-server/src/openai_image_generation.rs`、`server-rs/crates/api-server/src/telemetry.rs`、tracking outbox、后端架构文档和开发运维文档。
- 验证方式:执行 `cargo test -p api-server external_api_audit --manifest-path server-rs/Cargo.toml -- --nocapture`、`cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml -- --nocapture`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-05-25 VectorEngine 图片 provider 收到 platform-image
- 背景:`api-server` 里原本同时混着 VectorEngine 创建 / 编辑协议、响应解析、远端图片下载、失败日志和审计落库逻辑,Puzzle / Match3D 还各自藏着一份近似实现,导致“provider 协议”和“业务编排”边界不清。
- 决策:把 VectorEngine `gpt-image-2` 图片 provider 协议、URL / base64 响应解析、远端图片下载和 provider 侧结构化日志统一收口到 `server-rs/crates/platform-image/src/vector_engine/`,并按 `client.rs`、`transport.rs`、`request.rs`、`payload.rs`、`response.rs`、`image_source.rs` 等小模块拆分,避免把大文件从 `api-server` 平移到平台 crate。`api-server` 只保留配置校验、玩法 prompt 编排、OSS / asset object / binding 持久化、计费和外部 API 失败审计桥接;旧 `openai_image_generation.rs` 只作为兼容转接层,不再承担 provider 实现。
- 影响范围:`server-rs/crates/platform-image`、`server-rs/crates/api-server/src/openai_image_generation.rs`、`server-rs/crates/api-server/src/puzzle/vector_engine.rs`、`server-rs/crates/api-server/src/external_api_audit.rs`、后端架构与运维文档。
- 验证方式:`cargo test -p platform-image --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-image --test vector_engine --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml -- --nocapture`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-05-26 音频 provider 协议收口到 platform-audioHyper3D 继续保持薄代理
- 背景:`api-server/src/vector_engine_audio_generation.rs` 和 `api-server/src/hyper3d_generation.rs` 仍然承担太多 provider 细节,容易把外部协议、下载、解析和 BFF 编排混在一起。
- 决策:VectorEngine Suno/Vidu 音频协议、任务提交/轮询、下载和 OSS 持久化请求准备收口到 `platform-audio`,并继续按 `client.rs`、`request.rs`、`response.rs`、`download.rs`、`persist.rs`、`error.rs` 拆小模块;`api-server` 只保留路由、配置、计费、asset_object confirm、entity binding 和错误映射。Hyper3D 维持后端安全代理和旧数据兼容,`platform-hyper3d` 承接 Rodin 的协议与解析,`api-server` 仅做薄 wrapper。
- 影响范围:`server-rs/crates/platform-audio/`、`server-rs/crates/platform-hyper3d/`、`server-rs/crates/api-server/src/vector_engine_audio_generation.rs`、`server-rs/crates/api-server/src/hyper3d_generation.rs`、相关后端架构文档。
- 验证方式:`cargo test -p platform-audio --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-hyper3d --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml` 通过;`api-server` 不再包含音频 provider 协议和 Hyper3D parser 主实现。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【后端架构】复杂媒体资产链路Adapter扩展计划-2026-05-14.md`。
## 2026-05-21 拼图参考图主链改为 OSS assetObjectId 与只读签名 URL
- 背景:release 上拼图图生图生成草稿时,旧链路把上传图转成 Data URL/base64 放进创作 action JSON body,容易先触发 Nginx `413 Request Entity Too Large`,也让外部模型调用前的 HTTP body 过大。
- 决策:浏览器参考图先通过资产直传票据上传 OSS,并确认 `asset_object`;拼图 action 主链只提交 `referenceImageAssetObjectId(s)`。`api-server` 按当前登录用户校验 asset owner、bucket、kind、图片 MIME 和大小后签发 OSS 只读 URL,传给 VectorEngine 的 generation fallback 使用;需要 edits multipart 时由后端用该签名 URL 拉取字节,不再让前端把图片塞进 JSON body。
- 兼容边界:旧 `referenceImageSrc(s)` Data URL 与历史 `/generated-*` 路径仅保留给旧草稿、旧入口和迁移期请求;调大 Nginx `client_max_body_size` 只作为兼容兜底,不是长期创作主链。
- 影响范围:拼图创作前端、`packages/shared` / `shared-contracts` action DTO、`api-server` 拼图 VectorEngine 编排、资产确认和 `spacetime-client` 资产读取 facade。
- 验证方式:前端 payload 中 AI 重绘优先出现 `referenceImageAssetObjectId(s)` 且 `referenceImageSrc(s)` 不再携带 Data URL;后端 `puzzle_vector_engine_generation_prefers_signed_reference_url`、`puzzle_reference_image_sources_prefer_asset_object_ids`、`puzzle_asset_object_reference_requires_matching_owner` 通过。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-05-21 Nginx 通用 API 入口放行创作参考图请求体
- 背景:release 上拼图结果页重绘动作携带参考图 Data URL 时,Nginx access log 出现 `413`、`request_time=0.000`、`upstream_status=-`,说明请求被反代层默认 1 MiB 上限拦截,未进入 `api-server`。
- 决策:发布、开发服和容器 Nginx 模板的通用 `location ~ ^/api(?:/|$)` 统一设置 `client_max_body_size 64m`。该值只作为反代放行和旧 Data URL 请求兼容兜底,具体业务请求体和图片字节上限继续由 `api-server` 路由 `DefaultBodyLimit`、OSS asset 确认和业务校验控制,不能替代接口级限制;拼图参考图长期主链见同日 `OSS assetObjectId` 决策。
- 影响范围:`deploy/nginx/genarrative.conf`、`deploy/nginx/genarrative-dev-http.conf`、`deploy/container/nginx.conf`、Nginx README、生产运维文档和 release 排障口径。
- 验证方式:目标机 `nginx -T 2>/dev/null | grep client_max_body_size` 应看到 `client_max_body_size 64m;`;大于 1 MiB 的参考图请求不再在 Nginx 层直接 413access log 应出现有效 `upstream_status`。
- 关联文档:`deploy/nginx/README.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-05-24 跳一跳推荐页允许未登录直达运行态并记录匿名游玩埋点
- 背景:推荐页的跳一跳作品在未登录时曾被前端登录门禁拦住,导致公开推荐流无法直接游玩;同时游玩埋点如果只接受登录态 userId,会让匿名启动和匿名重开被静默丢失。
- 决策:跳一跳推荐页的运行态启动、跳跃和重开路由统一使用可选鉴权;未登录时仍允许进入运行态,并把 `work_play_start` 以匿名语义记录下来,而不是伪造用户身份或直接跳过埋点。
- 影响范围:`api-server` 跳一跳 runtime 路由、`work_play_tracking`、推荐页进入运行态逻辑、匿名推荐试玩测试、平台入口 / 玩法链路文档。
- 验证:登录态和未登录态都能从推荐页进入运行态;`work_play_start` 事件在匿名时仍产生,metadata 带匿名标记。
- 关联:`server-rs/crates/api-server/src/jump_hop.rs`、`server-rs/crates/api-server/src/auth.rs`、`server-rs/crates/api-server/src/work_play_tracking.rs`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`。
## 2026-05-22 抓大鹅素材生成改为关卡整图派生三图
- 背景:旧抓大鹅素材链路按物品 5x5 sheet、纯背景和独立容器图分开生产,难以保证背景、UI、容器和物品风格一致,也让结果页继续暴露背景 / 容器重生成入口。
- 决策:抓大鹅草稿生成先用 `gpt-image-2` 无参考图生成竖屏 `9:16` 完整关卡画面;关卡画面完成后,以它作为参考并发生成三张可运行资产:`1K 1:1` UI spritesheet、`1K 9:16` 关卡背景图、`2K 1:1` 物品 spritesheet。UI 与物品 spritesheet 都固定要求单一纯绿色 `#00FF00 / RGB(0,255,0)` 绿幕背景,后端上传 OSS 前扣成真实透明 PNG。物品 spritesheet 固定 `10*10`,每行两种物品、每种五个形态。运行态和编辑器都按 alpha 连通域矩形检测解析 UI 和物品图集,不按固定像素坐标切图。
- 兼容:新增字段继续存入现有 `generatedItemAssets[].backgroundAsset` / `generatedBackgroundAsset` JSON,不新增 SpacetimeDB schema 字段。历史 `containerImage*` 字段只作兼容;如果它与 `uiSpritesheetImage*` 同源,不得再作为运行态中心容器图。
- 影响范围:`server-rs/crates/api-server/src/match3d/*`、`server-rs/crates/shared-contracts/src/match3d_*`、`packages/shared/src/contracts/match3dWorks.ts`、`src/components/match3d-result/Match3DResultView.tsx`、`src/components/match3d-runtime/Match3DRuntimeShell.tsx`、`src/services/match3dSpritesheetParser.ts`。
- 验证方式:执行 `cargo test -p api-server match3d --manifest-path server-rs\Cargo.toml`、`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx src/components/match3d-runtime/Match3DRuntimeShell.test.tsx src/services/match3dSpritesheetParser.test.ts src/services/match3dGeneratedModelCache.test.ts`、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-18 Rust 手写模块入口统一不用 mod.rs
- 背景:Rust 目录模块同时存在 `mod.rs` 与同名 `.rs` 两种入口形式,前次拆分已让 `spacetime-client/src/mapper.rs` 采用同名入口;继续新增 `mod.rs` 会让文件定位和评审口径不一致。
- 决策:手写 Rust 模块统一使用同名入口文件,例如 `puzzle.rs`、`match3d.rs`、`gameplay.rs`,子模块继续放在同名目录下;不要再为手写模块新增 `mod.rs`。SpacetimeDB CLI 生成的 bindings 也由生成脚本同步为 `module_bindings.rs` 加 `module_bindings/` 子目录,避免仓库里继续出现 `mod.rs`。
- 边界:本决策只规范文件布局,不改变 module path、HTTP route、DTO、SpacetimeDB schema、生成绑定内容或运行时行为。
- 影响范围:`server-rs/crates/api-server/src/`、`server-rs/crates/spacetime-module/src/`、`server-rs/crates/spacetime-client/src/module_bindings.rs`、`scripts/generate-spacetime-bindings.mjs`。
- 验证方式:执行 `Get-ChildItem server-rs -Recurse -Filter mod.rs` 应无结果;再执行对应 `cargo check` / 定向测试 / 编码检查。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-18 大文件拆分继续按聚合入口加领域子模块推进
- 背景:完成拼图 `api-server` 拆分后,`match3d.rs`、`spacetime-client/src/mapper.rs` 与 `PlatformEntryFlowShellImpl.tsx` 仍是后续迭代和评审的高噪音大文件。
- 决策:抓大鹅 Match3D 的 `api-server` 单文件改为同名入口 `src/match3d.rs` 加 `src/match3d/` 子模块目录,`handlers.rs`、`draft.rs`、`works.rs`、`item_assets.rs`、`runtime.rs`、`vector_engine_gemini.rs`、`mappers.rs`、`tags.rs`、`tests.rs` 分担原实现;`spacetime-client/src/mapper.rs` 改为聚合入口,具体 mapper 按领域落到 `src/mapper/*.rs`;平台入口继续以 `PlatformEntryFlowShellImpl.tsx` 为编排壳,独立 UI 片段优先拆到 `PlatformEntryFlowShellImpl/` 子目录,本次已抽出 `PuzzleOnboardingView.tsx`。
- 边界:这些拆分只改变文件组织,不改变 HTTP route、DTO、error envelope、SpacetimeDB schema、生成绑定、procedure result、入口配置事实源、前端行为、VectorEngine / OSS 副作用或计费语义。后续要下沉领域规则时另行讨论并更新设计。
- 影响范围:`server-rs/crates/api-server/src/match3d/`、`server-rs/crates/spacetime-client/src/mapper/`、`src/components/platform-entry/PlatformEntryFlowShellImpl/`、后端架构文档和玩法链路文档。
- 验证方式:执行 `cargo check -p api-server --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d --manifest-path server-rs\Cargo.toml --no-run`、`cargo check -p spacetime-client --manifest-path server-rs\Cargo.toml`、前端 typecheck 或定向 tsc、`git diff --check` 与 `npm run check:encoding`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-18 api-server 拼图能力按 HTTP/BFF 子模块拆分
- 背景:`server-rs/crates/api-server/src/puzzle.rs` 已膨胀为数千行大文件,混合 Axum handler、草稿编译、图片生成、VectorEngine / OSS 持久化、DTO mapper、标签生成和测试;继续在单文件内迭代会降低定位和评审效率。
- 决策:原超大 `puzzle.rs` 改为同名入口 `server-rs/crates/api-server/src/puzzle.rs` 加 `server-rs/crates/api-server/src/puzzle/` 子模块目录。`puzzle.rs` 只保留聚合入口和 handler re-export`handlers.rs` 放 HTTP handler`draft.rs` 放表单草稿 / 编译 / snapshot helper`generation.rs` 放图片与 UI 背景生成编排;`vector_engine.rs` 放 VectorEngine、下载、OSS、asset object / binding 和错误归一;`mappers.rs` / `tags.rs` 保留映射和标签 / 错误 helper`tests.rs` 承接原 puzzle 单测。
- 2026-05-21 追加决策:拼图 HTTP/BFF handler 不再直接提取完整 `AppState`,统一通过 `PuzzleApiState` 暴露拼图能力需要的 SpacetimeDB facade、gallery cache、OSS、作者查询、LLM 和少量配置快照。`modules/puzzle.rs` 仍接收全局 `AppState` 以挂接鉴权和回到全局路由树,但内部路由先 `.with_state(PuzzleApiState::from_ref(&state))`handler 使用 `State<PuzzleApiState>`。确需复用计费、外部失败审计等仍要求 `AppState` 的横切 helper 时,先经 `PuzzleApiState::root_state()` 显式过渡,后续再继续收窄。
- 边界:本次只改变 `api-server` 内部文件组织,不改变 `/api/runtime/puzzle/*` 路由、DTO、error envelope、SpacetimeDB schema、公开 gallery cache 语义或计费语义。领域规则后续仍应逐步沉到 `module-puzzle`SpacetimeDB 表、reducer、procedure 和 row shape 仍留在 `spacetime-module`。
- 影响范围:`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/api-server/src/puzzle/`、`server-rs/crates/api-server/src/modules/puzzle.rs` 的 handler 引用、后端架构文档。
- 验证方式:执行 `cargo check -p api-server --manifest-path server-rs\Cargo.toml`;后续若改动 puzzle API 行为,再按对应路由补充定向测试和 `npm run dev:api-server` `/healthz` smoke。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-18 Windows Jenkins PowerShell 统一改为显式 powershell.exe 启动
- 后续更新:该决策仅适用于历史 Windows Jenkins 节点;当前 `Genarrative-Stdb-Module-Build` 已改为 Linux agent,实际执行路径不再依赖该口径。
- 背景:`Genarrative-Stdb-Module-Build` 在 Windows Jenkins 本地环境里调用裸 `powershell` step 时触发 `CreateProcess error=5, 拒绝访问`,而 `powershell.exe` 本体与 workspace ACL 都正常。
- 决策:Windows Jenkins 上凡是需要执行 PowerShell 逻辑的流水线,优先通过 `bat` 显式调用 `%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File ...`,不要再依赖 Jenkins `powershell` step 的隐式启动器。
- 追加决策:`Genarrative-Stdb-Module-Build` 的 Checkout 逻辑应复用 Jenkins GitSCM 已完成的工作区状态。`COMMIT_HASH` 为空或已与当前 `HEAD` 一致时,不再额外执行 `git clean` / `git checkout`;只有需要切到指定且不同的 commit 时才补 fetch、校验和切换,避免在 Windows workspace 里二次清理触发权限拒绝。
- 影响范围:`jenkins/Jenkinsfile.production-stdb-module-build` 及后续所有同类 Windows 构建流水线。
- 验证方式:Jenkins 日志中应能看到 `[jenkins-powershell] user:` 和 `[jenkins-powershell] exe:`Checkout 阶段会打印当前 `HEAD` 与请求 commit,并在 `COMMIT_HASH` 为空或一致时直接继续;不再停在 `PipelineNodeTreeScanner... Cannot run program "powershell"` 或重复 `git clean` 的退出码 5。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/project-memory/shared-memory/pitfalls.md`。
## 2026-05-19 tracking outbox 改为 rotate 后异步 flush
- 背景:普通 route tracking 写入压力上来后,不能让 HTTP 请求线程等待 SpacetimeDB 批量入库。
- 决策:`api-server` tracking outbox 达到 `BATCH_SIZE` 时立即封存当前 active 文件并切新 activesealed 文件交给后台 worker 异步 flush`FLUSH_INTERVAL_MS` 只做长时间未满批的兜底封存;`MAX_BYTES` 只做磁盘保护阈值;成功后删除 sealed,失败保留重试,坏文件隔离为 `corrupt-*`。
- 影响范围:`api-server` tracking outbox、埋点文档、压测口径和后续排障记忆。
- 验证方式:HTTP route 请求在 SpacetimeDB 短暂不可用时仍可返回;恢复后 sealed 文件会被批量写入并清理。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-19 OTLP 默认开启但日志本地输出保留
- 背景:生产和容器环境需要默认把 OTLP 接到本机 Collector,但压测或排障时也要能显式关闭。
- 决策:生产与容器 `api-server` env 模板默认 `GENARRATIVE_OTEL_ENABLED=true`;生产 endpoint 用 `http://127.0.0.1:4318`,容器 endpoint 用 `http://otelcol:4318``OTEL_EXPORTER_OTLP_ENDPOINT` 只填 Collector HTTP base endpoint,不填 gRPC `4317` 或 Rider 端口;本地日志、Nginx 日志和 `GENARRATIVE_API_LOG` / `RUST_LOG` 仍保留。
- 影响范围:`deploy/env/api-server.env.example`、`deploy/container/api-server.env.example`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`scripts/loadtest/README.md`。
- 验证方式:检查 env 模板默认值与端点口径;压测若要关闭 OTLP,必须显式设置 `GENARRATIVE_OTEL_ENABLED=false`。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`scripts/run-otelcol.mjs`。
## 2026-05-19 容器 collector 可切 Grafana Cloud
- 背景:容器隔离压测时除了本地 debug exporter,还需要临时把 traces / metrics / logs 转发到 Grafana Cloud 做可视化验证。
- 决策:`deploy/container/docker-compose.loadtest.yml` 里的 `otelcol` 支持通过 `GENARRATIVE_CONTAINER_OTELCOL_CONFIG=./otelcol.grafana.yaml` 切换配置;`deploy/container/otelcol.grafana.yaml` 同时保留 debug exporter,并通过 `GRAFANA_CLOUD_OTLP_ENDPOINT` 和 `GRAFANA_CLOUD_BASIC_AUTH_HEADER` 转发到 Grafana Cloud。
- 影响范围:`deploy/container/docker-compose.loadtest.yml`、`deploy/container/otelcol.grafana.yaml`、`deploy/container/README.md`。
- 验证方式:容器 `otelcol` 启动日志应能看到 OTLP receiver readydebug exporter 仍可输出本地链路;Grafana Cloud 转发凭据只通过当前 shell 环境变量传入,不写入 Git。
- 关联文档:`deploy/container/README.md`、`scripts/loadtest/README.md`。
## 2026-05-17 容器化方案只作为隔离压测与预发模拟路径
- 背景:Windows 本机直连极高 VU 压测会放大本地连接与发送缓冲行为,和线上 Linux + Nginx + systemd 拓扑不一致;需要一个更接近生产网络层的模拟方案,但不能扰动当前生产发布链路。
- 决策:新增 `deploy/container/` 容器化方案,使用 Docker Compose 组合 Linux release `api-server`、容器 SpacetimeDB、容器 Nginx、`otelcol-contrib` debug exporter 和可选 k6。该方案只用于本机或预发压测模拟,不替换当前生产 `systemd + Nginx + Jenkins` 路径。
- 服务器模拟参数:2026-05-18 通过 `ssh genarrative-release` 采样,目标机器为 2 vCPU / 约 2 GiB RAM / Ubuntu 24.04 / Nginx `worker_connections=768`;容器方案按待发布运行口径使用 `nofile=4096`,并在 compose 中限制 `spacetimedb cpus=1.0 mem_limit=896m`、`api-server cpus=2.0 mem_limit=1g`、`external-generation-worker cpus=2.0 mem_limit=1g`、`nginx cpus=0.5 mem_limit=128m`、`otelcol cpus=0.25 mem_limit=128m`、`k6 cpus=1.0 mem_limit=512m`Collector 镜像默认使用 `otel/opentelemetry-collector-contrib:0.151.0`。
- 隔离边界:容器方案使用独立 `deploy/container/api-server.env`、独立 Nginx 配置、独立 compose 命令和默认 `18080` 端口;真实 token 不进入镜像、不提交 Git;生产 systemd 单元、Jenkins 发布脚本和 `deploy/nginx/` 模板仍是正式线上来源。
- 生产 Collectorserver-provision 可安装 `otelcol-contrib.service` 和本机 debug exporter 配置;当前二进制准备在目标部署 agent 的 `Prepare Provision Tools` 阶段完成,先复用目标机已有 `otelcol-contrib`,缺失或版本不匹配时再按 `PROVISION_DOWNLOADS_DIR` / `PROVISION_DOWNLOAD_PROXY` / 下载源准备。Jenkins 构建节点只上传 provision 脚本与配置,不上传 `provision-tools/otelcol-contrib`api-server 是否发送 OTLP 仍由 `GENARRATIVE_OTEL_ENABLED` 控制。
- 影响范围:`deploy/container/`、`scripts/container-compose.mjs`、`package.json` 容器命令、开发运维文档和容器 build context 排除规则。
- 验证方式:执行 `npm run container:config` 展开 compose 配置;需要真实运行时再执行 `npm run container:build`、`npm run container:up`、`npm run container:k6`,并结合容器 Nginx log 与 OTLP debug exporter 判断瓶颈。
- 关联文档:`deploy/container/README.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-05-19 生产 provision 改为 Windows 下载包后由目标机本地安装
- 后续更新:该口径已被后续 Linux provision 口径取代;当前 `Genarrative-Server-Provision` 不再走 Windows 下载阶段,也不在 Linux build 节点准备 `provision-tools/`。Jenkins 构建节点只准备并上传 provision 脚本和配置,SpacetimeDB / otelcol 工具包在目标部署 agent 的 `Prepare Provision Tools` 阶段按目标机现状生成。
- 背景:当前 `development` provision 目标实际就是 Linux agent `genarrative-build-01`,之前把 `Prepare Provision Tools` 放在 `linux && genarrative-build` 会让目标机自己连 GitHub 和 `install.spacetimedb.com`,违背“Windows 本机先下载再传到目标机”的运维要求。
- 决策:`Genarrative-Server-Provision` 拆成 Windows 下载阶段和 Linux 目标机安装阶段。Windows 节点的 `Download Provision Tool Archives` 只下载 `spacetime-x86_64-unknown-linux-gnu.tar.gz` 和 `otelcol-contrib_0.151.0_linux_amd64.tar.gz`,通过 `stash/unstash` 传到目标 Linux 节点;目标机执行 `scripts/prepare-server-provision-tools.sh` 时设置 `PROVISION_REQUIRE_LOCAL_DOWNLOADS=true`,只消费已下载件生成 `provision-tools/`,缺包直接失败,不回退外网下载。
- 追加决策:Server-Provision 的 Windows helper 不再对 Jenkins `writeFile` 刚写出的 `.ps1` 做原地 UTF-8 BOM 重写,而是由显式 `powershell.exe` 按 UTF-8 读入脚本文本,并用 `ScriptBlock::Create(...)` 在内存中执行;这样既保留中文脚本内容,又避免同一个 workspace 脚本被立即重写时触发 `拒绝访问`。
- 追加决策:GitHub release asset 的可用校验信息使用 `digest` 字段,实际是 `sha256:...`,不是 MD5Windows 下载阶段先查 digest,再决定是否复用已有文件。
- 影响范围:`jenkins/Jenkinsfile.production-server-provision`、`scripts/prepare-server-provision-tools.sh`、`scripts/jenkins-server-provision.sh`、生产运维文档。
- 验证方式:Jenkins 日志应先出现 Windows 节点的 `[jenkins-powershell] workspace:`、`[jenkins-powershell] loaded bytes:` 和 `[prepare-provision-downloads]` 下载日志,再在 `genarrative-build-01` 上出现“使用已下载的 ...”日志;目标机不应出现直接访问 `install.spacetimedb.com` 或 OpenTelemetry GitHub release 下载地址的回退日志,且不再需要 `spacetimedb-update-*` 作为离线交付包。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-05-19 公开 gallery 入口发布限流以快拒绝保护后端
- 背景:容器 2C / 2G 压测中,公开作品列表在约 5000 HTTP req/s 目标下可以保持 200 请求低延迟,但 SpacetimeDB 内存会随 api-server 重连和高压请求累积到容器上限附近。
- 决策:发布配置采用公开 gallery list 专用入口限流:Nginx `genarrative_gallery_rps rate=5000r/s`、`burst=4096`、gallery list `limit_conn=320`api-server 对应 `GENARRATIVE_API_GALLERY_MAX_CONCURRENT_REQUESTS=320`,公开详情维持更低的 `GENARRATIVE_API_DETAIL_MAX_CONCURRENT_REQUESTS=64`。超过容量时接受明确 `429`,不继续扩大入口并发。
- 影响范围:`deploy/nginx/` 发布模板、`deploy/env/api-server.env.example`、`deploy/container/` 隔离压测模板和生产运维文档。
- 验证方式:容器连续 10 轮不重启 SpacetimeDB 压测,`PEAK_RPS=2500` 等价约 5000 HTTP req/s,平均实际吞吐约 `4219 HTTP req/s`,总计 `0` 个 5xx200 请求平均 `p95=123ms`、`p99=234ms`;同时观察 SpacetimeDB 内存高水位,后续优化先处理连接 / 订阅 / tracking 下游状态。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`deploy/container/README.md`。
## 2026-05-19 新增玩法创作工具平台 SOP 冻结
- 背景:新增玩法的创作工具如果默认复制既有玩法的聊天式 Agent、轻输入 Agent 或专属素材模型,平台会不断复制出不可控分支,后续接入、测试和恢复语义都会漂移。
- 决策:新增玩法创作工具统一收敛为平台级 SOP:默认使用表单/图片输入创作工作台;单图资产统一通过 `CreativeImageInputPanel`;系列素材统一走批量规划、sheet 生图、后端切图、透明化、OSS 持久化和局部重生成流水线;不把任一玩法专属素材模型当平台通用模型。
- 影响范围:`CONTEXT.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`.codex/skills/genarrative-play-type-integration/SKILL.md`、后续新增玩法 PRD 和工程实现。
- 验证方式:新增玩法 PRD 必须显式声明单图资产槽位和系列素材槽位;新增工作台测试确认没有默认聊天式 Agent 输入;skill 通过 `quick_validate.py`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`.codex/skills/genarrative-play-type-integration/SKILL.md`。
## 2026-05-20 敲木鱼玩法按完整平台纵切接入
- 背景:敲木鱼玩法需要对齐拼图 / 跳一跳的创作闭环,不能做成孤立 demo 或前端本地计数工具。
- 决策:新增 `wooden-fish` 玩法,采用表单 / 图片输入工作台、单图敲击物资产槽位、敲击音效资产槽位和最多 8 条飘字配置;公开作品号前缀为 `WF-*`;运行态只在单次 run 内累计总敲击次数和词条计数。后端新增独立 `module-wooden-fish`、shared contracts、SpacetimeDB `wooden_fish_*` 表 / public views、`spacetime-client` facade 和 `/api/creation/wooden-fish/*`、`/api/runtime/wooden-fish/*` 路由,前端接入平台入口、生成页、结果页、运行态、公开详情和推荐试玩。
- 影响范围:`CONTEXT.md`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`packages/shared/src/contracts/woodenFish.ts`、`server-rs/crates/shared-contracts/src/wooden_fish.rs`、`server-rs/crates/module-wooden-fish/`、`server-rs/crates/spacetime-module/src/wooden_fish*`、`server-rs/crates/spacetime-client/src/wooden_fish.rs`、`src/components/wooden-fish-*`。
- 验证方式:执行敲木鱼契约 / module / facade / runtime model / platform entry 定向测试、`npm run typecheck`、`npm run check:encoding`、`npm run check:spacetime-schema`、`cargo check -p api-server --manifest-path server-rs\Cargo.toml`,本地 smoke 使用 mock 短信配置后检查 `/healthz`。
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-21 敲木鱼敲击音效当前只接受上传、录音或默认音
- 背景:敲木鱼按关键词生成的敲击音效约束不够稳定;当前创作阶段需要先关闭提示词生成音效,避免生成结果不符合敲击体验。
- 决策:通用 `/api/creation/audio/sound-effect` 对木鱼 `hit_sound` 目标也返回 `410 Gone`。木鱼工作台只支持上传或麦克风录制音频;若用户未提供音频,`api-server` 写回内置默认木鱼音 `/wooden-fish/default-hit-sound.mp3`。`hitSoundPrompt` 只作为历史兼容字段保留,当前创作流程不使用;`spacetime-client` 不得合成 `/generated-wooden-fish-assets/...` 假路径。
- 影响范围:`server-rs/crates/api-server/src/vector_engine_audio_generation.rs`、`server-rs/crates/api-server/src/wooden_fish.rs`、`server-rs/crates/spacetime-client/src/wooden_fish.rs`、`shared-contracts` / `packages/shared` 的 `creationAudio` 契约、敲木鱼 PRD 与平台链路文档。
- 验证方式:执行 `cargo test -p shared-contracts creation_audio --manifest-path server-rs\Cargo.toml`、`cargo test -p spacetime-client wooden_fish --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server wooden_fish --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server disabled_creation_audio_targets_return_gone_including_wooden_fish_sound_effects --manifest-path server-rs\Cargo.toml`、`npm run typecheck`、`npm run check:encoding`,本地 smoke 检查 `/healthz`。
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-21 敲木鱼默认敲击物使用内置透明 PNG
- 背景:默认敲木鱼图案若继续用“木鱼”关键词临时生成,image2 容易语义化重画并改变用户认可的原始造型。
- 决策:默认模板使用内置资源 `/wooden-fish/default-hit-object.png` 写回 `bundled-default` 敲击物资产;仅当用户输入自定义关键词、上传参考图或主动重生成敲击物时,才走 image2 -> OSS -> asset object -> entity binding 链路。创作入口卡片、结果页、运行态和公开列表兜底统一使用该 PNG。
- 影响范围:敲木鱼工作台默认提示词、api-server 木鱼默认资产编排、创作入口种子与迁移、平台公开卡片兜底、PRD 与平台链路文档。
- 验证方式:默认 `compile-draft` 返回的 `hitObjectAsset.generationProvider` 应为 `bundled-default` 且 `imageSrc=/wooden-fish/default-hit-object.png`;自定义关键词或参考图仍走 image2;前端静态资源可通过 Vite 直接访问。
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-23 敲木鱼创作请求需要独立长超时
- 背景:敲木鱼 `createSession` 和 `executeAction` 都会串行等待多段 image2 生成、去绿背景处理和 OSS 落库;共享创作工厂默认 15 秒对这条链路太短,容易让前端先报 `请求超时:15000ms`。
- 决策:敲木鱼 client 单独配置长等待窗口,同时覆盖会话创建和执行动作请求,不修改共享工厂默认值,避免影响其它轻量创作玩法。
- 影响范围:`src/services/wooden-fish/woodenFishClient.ts`、`src/services/creation-agent/creationAgentClientFactory.ts`、敲木鱼工作台与生成页请求行为。
- 验证方式:`npm test -- src/services/wooden-fish/woodenFishClient.test.ts`,并在本地敲木鱼创作时不再提前触发 15 秒超时。
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-21 RPG publish_world 设定文本以后端草稿真相派生
- 背景:RPG 结果页发布动作只保证提交 `{ action: 'publish_world' }`;旧 agent 会话可能没有 `seed_text`,但 `draft_profile_json` 已经通过 `publish_gate` 并可发布。
- 决策:发布正式世界时,`spacetime-module` 不再把 `session.seed_text` 当作唯一 `setting_text` 兜底,而是调用 `module-custom-world::resolve_custom_world_publish_setting_text(...)` 从 payload、当前草稿 profile 和 seed 依次派生。
- 影响范围:RPG / custom-world agent 发布链路、`custom_world_profile` 编译入库、公开 gallery 投影。
- 验证方式:`cargo test -p module-custom-world publish_setting_text --manifest-path server-rs\Cargo.toml``cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml`;本地 api-server 重启后检查 `/healthz`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/project-memory/shared-memory/pitfalls.md`。
## 2026-05-19 系列素材 n\*n 图集抽为 api-server 通用模块
- 背景:抓大鹅物品 sheet 已包含 prompt 组装、固定网格切图、绿幕 / 近白底透明化、切片 PNG 持久化和 prompt 追踪;继续留在 Match3D 私有模块会让跳一跳、后续地块 / 道具类玩法重复复制同一套算法和 OSS 元数据口径。
- 决策:`server-rs/crates/api-server/src/generated_asset_sheets.rs` 作为通用系列素材图集模块,`n` 作为必选 `grid_size` 参数;物品名称 prompt 模板与特殊设定 prompt 作为可选输入;模块负责 sheet prompt、`n*n` 切片、透明化、PNG 输出、OSS private upload 请求构造,以及 sheet / item / special prompt 的 base64 元数据持久化。玩法只负责生图 provider、计费、slot 规划、失败回写和把通用切片结果映射回自身 DTO / 草稿 / runtime 字段。
- 影响范围:`api-server` 系列素材生成、Match3D 物品五视角素材、后续新增玩法的地块 / 物品 / 障碍 / 装饰图集生成。
- 验证方式:`cargo test -p api-server generated_asset_sheets --manifest-path server-rs\Cargo.toml -- --nocapture` 覆盖通用 prompt、切片、`n` 校验和 prompt 元数据;玩法侧执行对应素材流水线定向测试。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-19 跳一跳玩法采用正式 scoring DTO 与 public view 投影
- 背景:跳一跳玩法新增后,前端、shared-contracts、SpacetimeDB 生成绑定和后端 mapper 对 scoring 字段口径不一致,schema guard 也要求 table / view 目录与 `migration.rs` 同步。
- 决策:跳一跳的 `JumpHopScoring` 统一采用 `chargeToDistanceRatio/maxChargeMs/hitBonus/perfectBonus`,公开广场优先使用 `jump_hop_gallery_card_view`,详情兼容投影保留 `jump_hop_gallery_view`。`spacetime-module` 新增的 `jump_hop_*` table 必须同步进入 `migration.rs` 和后端架构文档。
- 影响范围:`packages/shared/src/contracts/jumpHop.ts`、`server-rs/crates/shared-contracts/src/jump_hop.rs`、`server-rs/crates/spacetime-client/src/mapper/jump_hop.rs`、`server-rs/crates/spacetime-module/src/migration.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
- 验证方式:`cargo check -p shared-contracts --manifest-path server-rs/Cargo.toml`、`cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`。
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-16 公开作品列表短期由 BFF 订阅读模型缓存
- 背景:作品列表压测和实时性讨论中,曾考虑让浏览器前端直接订阅公开作品列表,减少 HTTP 拉取和 BFF 压力。
- 决策:本轮不直接把作品列表整体交给前端订阅。短期继续由 `api-server` / BFF 通过 `spacetime-client` 长期订阅 SpacetimeDB 公开 read model 并读取本地 cache,维持首屏、排序、字段归一、权限降级和 HTTP fallback。中期可以新增或统一稳定的专用公开作品列表 read model,例如 `public_work_gallery_entry`,作为前端可选直连订阅对象。
- 边界:未来前端直订阅只允许面向稳定、低基数、公开的专用 read model。前端不得直接订阅 `puzzle_work_profile`、`custom_world_profile` 等领域源表,也不得在前端自行 join、聚合或执行公开权限逻辑;这些逻辑必须先沉到后端投影 / read model。
- 后续准入:若要落地前端直订阅,必须先完成并验收权限边界、字段契约、排序 / 分页、埋点和 BFF 回退策略;缺任一项时继续走 `api-server` / BFF 订阅缓存方案。
- 影响范围:发现页、推荐流、各玩法公开广场、`api-server` 公开列表缓存、SpacetimeDB public view / public 读模型设计。
- 验证方式:新增公开作品列表订阅能力时,检查前端只消费专用 public read model 或 BFF HTTP DTO;检查源表 row shape、权限判断和跨玩法聚合没有下沉到前端页面。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
- 背景:压测与运行观测需要把 HTTP、SpacetimeDB 调用和应用日志串起来,同时保留本地 `journalctl` / 文件日志做故障排障。
- 决策:`api-server` 通过 OTLP HTTP base endpoint 发送 traces、metrics 和 logsCollector 统一用 `otelcol-contrib``npm run otel:debug` 负责 debug 采集,`npm run otel:rider` 负责转发到 Rider;Rider 只是接收与可视化端,不直接替代 Collector。
- 日志口径:Rider Logs 面板只展示 log event 自身字段,请求完成日志需要直接携带 `request_id`、HTTP method、规范化 route、scheme、path、status、status_class、latency 和 slow_request;更完整的 request attributes 仍以 trace/span 为准。
- 影响范围:`server-rs/crates/shared-logging`、`server-rs/crates/api-server`、`scripts/run-otelcol.mjs`、压测与运维文档。
- 验证方式:`cargo test -p shared-logging --manifest-path server-rs/Cargo.toml generic_otlp_http_endpoint_expands_to_signal_paths`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml observability_route_keeps_metrics_labels_low_cardinality`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml resolve_request_scheme_uses_forwarded_proto_first_value`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`scripts/loadtest/README.md`。
## 2026-05-14 创作页图像输入统一封装为图像组件
- 背景:拼图创作页已经具备“画面描述生图 / 多参考图生图 / 上传主图后 AI 重绘 / 上传主图后不重绘”四条路径,抓大鹅封面和后续创作页也会复用同一套交互;继续在页面内复制会导致参考图、预览、删除确认和重绘开关漂移。
- 决策:通用图像输入 UI 统一使用 `src/components/common/CreativeImageInputPanel.tsx`。组件采用受控模式,只负责主图上传卡、画面描述输入、参考图缩略图与预览、AI 重绘开关、错误展示和提交按钮;外层页面负责文件读取/裁剪、历史素材弹层、计费确认、自动保存和具体后端请求。
- 影响范围:拼图创作入口、后续抓大鹅封面生成入口、其它需要复用图像输入链路的创作页。
- 验证方式:拼图入口交互测试继续覆盖四种路径;后续页面接入时只传入业务回调与文案,不复制上传卡和参考图缩略图实现。
- 关联文档:`docs/technical/【前端体验】图像组件统一封装与复用边界-2026-05-14.md`。
## 2026-05-14 汪汪声浪创作入口改为创作 Tab 内嵌轻配置
- 背景:汪汪声浪入口最初走独立配置阶段,和拼图、抓大鹅的创作页内嵌结构不一致,用户在入口切换时会感觉像跳到了另一张页面。
- 决策:`bark-battle` 的创作入口只在创作 Tab 内嵌渲染轻配置表单,入口点击只切到创作页并选中该模板,不再使用 `bark-battle-config` 独立阶段;runtime 退出时回到创作页并恢复汪汪声浪模板选中态。
- 影响范围:`PlatformEntryFlowShellImpl`、`BarkBattleConfigEditor`、`BarkBattleRuntimeShell`、入口配置说明和相关交互测试。
- 验证方式:创作 Tab 中点击汪汪声浪后直接看到内嵌表单,不应再出现单独配置页;发布进入 runtime 后退出应回到创作页的汪汪声浪模板。
- 关联文档:`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`。
## 2026-05-14 拼图与抓大鹅生成页移动端收口为等待与计时双栏(历史)
- 背景:拼图与抓大鹅的草稿生成页在移动端同时展示“当前批次”“预计等待”“计时”时,模型执行视角过重,信息也显得散。
- 决策:这两类轻量玩法的生成页隐藏“当前批次”模块,只保留“预计等待”和“计时”并排展示;生成步骤进入页面时按顺序从左侧滑入,强化推进感。2026-05-23 起已被“所有玩法生成页统一圆环主视觉”取代,步骤列表不再作为当前口径。
- 影响范围:`CustomWorldGenerationView`、拼图与抓大鹅创作入口调用处、移动端生成页体验文档。
- 验证方式:拼图与抓大鹅生成页在手机竖屏下只显示等待与计时双栏,步骤卡按顺序滑入;其它未传入隐藏参数的生成页继续保留原批次模块。
- 关联文档:`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`。
## 2026-05-14 移动端输入法弹出时平台画布不压缩
- 背景:平台根壳使用 `100dvh` 后,手机浏览器输入法弹出会让可见视口变小,导致创作首页、推荐页等固定游戏式画布被重新压缩。
- 决策:主站入口统一注册移动端输入法聚焦适配;输入法未打开时记录稳定布局高度,输入法打开期间 `.platform-viewport-shell` 不跟随 `visualViewport.height` 缩小,只通过 `--platform-keyboard-focus-offset` 上移画面聚焦当前输入框,并临时隐藏移动端底部 dock。
- 影响范围:主站平台壳、移动端创作首页底部输入框、后续所有复用 `.platform-viewport-shell` 的输入表单;业务组件不重复注册键盘适配。
- 验证方式:手机竖屏点击输入框,画布不压缩,输入框移动到输入法上方;输入法关闭后画布回位,底部 dock 恢复。
- 关联文档:`docs/technical/【前端体验】移动端输入法不压缩画布聚焦方案-2026-05-14.md`、`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`。
## 2026-05-14 抓大鹅物品素材批量重新生成复用 item-assets 替换模式
- 背景:抓大鹅结果页 `素材配置 > 物品` 需要在不改变玩法物品映射的前提下,批量重新生成已存在物品的 2D 五视角图片。
- 决策:继续复用 `POST /api/creation/match3d/works/{profileId}/item-assets`,请求体通过 `mode = "replace"` 表达替换模式;前端面板预填当前素材名称,只提交仍能匹配到已有素材的名称。后端只替换匹配素材的 `imageSrc/imageObjectKey/imageViews/status/error`,保留原 `itemId`、列表顺序、模型兼容字段、UI 背景、历史背景音乐和点击音效字段;未匹配名称不计费、不新增、不持久化。
- 影响范围:Match3D 结果页素材配置、前端/后端 shared contracts、`api-server` Match3D item-assets 编排、运行态物品类型映射和素材生成技术文档。
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`cargo test -p api-server match3d_item_asset --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_regenerated_asset --manifest-path server-rs\Cargo.toml`、`npm run check:encoding`。
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
## 2026-05-14 拼图与抓大鹅音频生成入口临时关闭
- 背景:当前需要暂时关闭抓大鹅、拼图中生成背景音乐和音效的能力,并隐藏草稿中的相关入口。
- 决策:拼图 `compile_puzzle_draft` 不再自动生成背景音乐,结果页素材配置只保留 `UI`;抓大鹅 `match3d_compile_draft` 和批量新增只生成 2D 图片、背景和容器 UI,不再调用 Suno/Vidu,结果页隐藏 `背景音乐` 子 Tab 与点击音效生成控件;通用 `/api/creation/audio/*` 当前整体返回 `410 Gone`。历史已写入的 `backgroundMusic` / `clickSound` 字段保留,运行态继续兼容播放旧音频。
- 影响范围:`api-server` 拼图/抓大鹅草稿编排、通用创作音频路由、拼图/抓大鹅结果页、生成进度模型、相关技术文档。
- 验证方式:执行拼图/抓大鹅结果页定向测试、生成进度单测、`cargo check -p api-server --manifest-path server-rs/Cargo.toml` 和 `npm run check:encoding`。
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
## 2026-05-14 抓大鹅物品素材 sheet 改用 VectorEngine Gemini
- 状态:历史决策,已被 `2026-05-22 抓大鹅素材生成改为关卡整图派生三图` 取代;当前物品 spritesheet 走 `gpt-image-2` 参考关卡整图编辑生成 `2K 1:1`、`10*10` 绿幕图,上传 OSS 前扣成透明 PNG。
- 背景:抓大鹅 2D 五视角物品素材仍沿用 5x5 sheet、绿幕去背、切图、OSS 转存和 `generatedItemAssets` 持久化,但用户要求物品素材图片生成步骤改用 VectorEngine Apifox `api-381740608` 对应的 Gemini 原生图片接口。
- 决策:抓大鹅物品素材 sheet 生图固定走 VectorEngine `POST {VECTOR_ENGINE_BASE_URL}/v1beta/models/gemini-3-pro-image-preview:generateContent?key={VECTOR_ENGINE_API_KEY}`,请求体使用 `contents[].parts[].text` 与 `generationConfig.responseModalities = ["TEXT", "IMAGE"]`、`imageConfig.aspectRatio = "1:1"`;响应从 `candidates[].content.parts[].inlineData.data` / `inline_data.data` 读取 base64 图片。封面、9:16 纯背景图、1:1 容器 UI 图、切图、OSS、扣费和运行态消费链路保持不变;音频以后续“拼图与抓大鹅音频生成入口临时关闭”决策为准。
- 影响范围:`server-rs/crates/api-server/src/match3d.rs`、`server-rs/crates/api-server/src/config.rs`、`deploy/env/api-server.env.example`、抓大鹅素材生成技术文档。
- 验证方式:执行 `cargo test -p api-server match3d_material_sheet --manifest-path server-rs\Cargo.toml`、`cargo test -p api-server match3d_vector_engine_gemini --manifest-path server-rs\Cargo.toml`、`cargo check -p api-server --manifest-path server-rs\Cargo.toml`、`npm run check:encoding`。
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
## 2026-05-14 草稿页作品卡对齐分类页列表
- 背景:草稿页作品架原本偏封面大卡片,和发现页分类列表的横向卡片样式不一致;生成中状态也缺少整卡级的统一遮罩。
- 决策:草稿页作品卡统一收口为与分类页一致的横向列表卡结构,左侧承载标题/状态/类型/摘要与必要数据,右侧显示带透明度的封面图;移动端保持单列列表,网页端使用两到三列卡片式网格,避免宽屏长条列表。不再常驻“继续创作”“查看详情”“查看进度”等右侧动作按钮。原有删除、分享、积分激励、公开统计、未读红点全部保留,其中删除与分享进入左滑操作层,常态不显示删除按钮,也不得透出删除底层。生成中的作品在整卡上加半透明蒙版、旋转等待符号和“生成中...”标识,但不移除任何原有信息。
- 影响范围:`src/components/custom-world-home/CustomWorldCreationHub.tsx`、`src/components/custom-world-home/CustomWorldWorkCard.tsx`、相关样式与测试、草稿页 UI 文档。
- 验证方式:草稿页作品卡与分类页列表视觉口径保持一致;`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/design/MOBILE_CREATION_WORK_LIST_TWO_COLUMN_LAYOUT_2026-04-29.md`、`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`。
2026-05-14 补充:草稿页作品卡不再用“草稿 / 已发布”文字标识状态,改为图标化 UI 状态点;作品封面直接铺到卡片右半区并从右向左渐隐;已发布作品右上角常驻分享图标;草稿长按弹出删除面板,已发布长按弹出分享和删除面板。2026-06-02 追加:作品卡片右上角不再放删除按钮;删除只通过左滑、键盘展开或长按 / 右键展开的右侧操作区出现,避免与卡片主点击和分享入口抢占标题区。
## 2026-05-13 认证运行期同步直接导入正式认证表
- 背景:`auth_store_snapshot` 是 Stage 1 整包快照过渡表,主键固定 `default`,会让所有用户状态集中在一条 `snapshot_json` 中;Stage 2/3 已有 `user_account/auth_identity/refresh_session` 正式认证表,继续刷新 `default` 容易让运行时真相和表拆分目标混在一起。
- 决策:运行期认证变更继续由 `module-auth` 生成一致内存快照,但 `api-server` 改为调用 `import_auth_store_snapshot_json` 直接覆盖导入 `user_account/auth_identity/refresh_session``auth_store_projection_meta/default` 只记录正式认证表最近一次导入时间;`upsert_auth_store_snapshot` 与 `import_auth_store_snapshot` 仅保留为旧库迁移和兜底入口。
- 影响范围:`spacetime-module` auth procedures/tables、`spacetime-client` auth facade/bindings、`api-server` 认证同步和启动恢复、SpacetimeDB 表目录与认证 Stage 3 文档。
- 验证方式:执行 `npm run spacetime:generate -- --rust-only`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、认证相关定向测试和 `npm run check:encoding`。
- 关联文档:`docs/technical/AUTH_SPACETIMEDB_FORMAL_TABLE_RECOVERY_STAGE3_2026-04-24.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。
## 2026-05-27 auth_store_snapshot 改为行级记录,不再保留 default 聚合单行
- 背景:`auth_store_snapshot/default` 聚合 JSON 行会把整份认证快照收敛到单键,过期快照一旦被导入就可能覆盖 `user_account` / `auth_identity` / `refresh_session` 的整表状态。
- 决策:`auth_store_snapshot` 只保留行级记录,按 `meta/next_user_id`、`user/<user_id>`、`phone/<phone+user>`、`session/<session_id>`、`session_hash/<hash+session>`、`wechat/<provider_uid+user>`、`union/<union+user>` 拆分存储;`api-server` 启动恢复只认正式认证表,`auth_store_snapshot` 仅作为行级备查,不再作为文件快照替代源。
- 影响范围:`spacetime-module` auth procedures、`spacetime-client` auth facade、`api-server` 启动恢复、后端架构文档、开发运维文档、认证排障记忆。
- 验证方式:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server spacetime_unavailable_router_returns_service_unavailable_for_requests --manifest-path server-rs/Cargo.toml -- --nocapture`、`npm run check:encoding`。
## 2026-06-30 auth_store_snapshot 只做一次性迁移并切断运行中回灌
- 背景:同手机号重复账号暴露出认证工作集、正式认证表和旧 `auth_store_snapshot` 之间仍有互刷路径;运行中 Bearer / refresh session 未命中后再导出整包状态刷新内存,会把旧手机号索引或旧会话重新带回进程。
- 决策:`auth_store_snapshot` 不再保留行级备查;正式认证表为空时才从最新旧快照转移一次到 `user_account` / `auth_identity` / `refresh_session`,随后清空旧表。`api-server` 运行中不再因 Bearer 用户、token version、session 或 refresh token 未命中而从 SpacetimeDB 导出整包状态刷新 `InMemoryAuthStore`;启动恢复暂保留从正式表构建工作集,直到认证仓储改为直接读写正式表。
- 影响范围:`server-rs/crates/spacetime-module/src/auth/procedures.rs`、`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/api-server/src/auth.rs`、`server-rs/crates/api-server/src/refresh_session.rs`、认证排障记忆与后端架构文档。
- 验证方式:`cargo test -p spacetime-module auth_export -- --nocapture`、`cargo test -p module-auth phone_only_exists -- --nocapture`、`cargo test -p module-auth bind_wechat_phone_merges -- --nocapture`、`npm run check:encoding`、`git diff --check`。
## 2026-05-13 微信小程序支付以后端通知为唯一入账事实
- 背景:“我的”账户充值需要接入微信小程序支付,同时保留本地 / H5 mock 支付联调能力。
- 决策:`paymentChannel = "mock"` 继续创建即 paid 订单并立即入账;`paymentChannel = "wechat_mp"` 先在 `profile_recharge_order` 写入 `pending` 订单,再由 `api-server` 调微信支付 JSAPI 下单并返回小程序 `wx.requestPayment` 参数。小程序或 H5 的支付成功回调只触发刷新,不直接发放泥点或会员;最终入账只由 `/api/profile/recharge/wechat/notify` 验签、解密并确认 `trade_state = SUCCESS` 后完成。`provider_transaction_id` 保存微信支付平台交易号,用于对账、查单、退款和客服排障。
- 影响范围:`profile_recharge_order` 表、SpacetimeDB 充值 procedure、`api-server` 微信支付客户端、小程序 native 支付页、H5 充值弹窗与共享 contract。
- 验证方式:执行 `npm run typecheck`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`cargo test -p module-runtime recharge --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server wechat_pay --manifest-path server-rs/Cargo.toml`,后端联调仍用 `npm run dev:api-server` 和 `/healthz`。
- 关联文档:`docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。
## 2026-05-13 修改密码后全设备强制下线
- 背景:修改密码原本只递增 `token_version`,旧 access token 会失效,但旧 refresh cookie 仍可通过 `/api/auth/refresh` 重新签发新 token,不符合“改密后全设备强制下线”的账号安全预期。
- 决策:`POST /api/auth/password/change` 成功后必须在同一认证真相更新中撤销该用户全部 active `refresh_session`,继续递增 `token_version`,响应清除当前 refresh cookie;前端 `changePassword` 成功后清空本地 access token 并回到未登录态。用户需要使用新密码重新登录。
- 影响范围:`module-auth` 修改密码用例、`api-server` password management route、`AuthGate`、`authService`、密码登录/重置技术文档。
- 验证方式:执行 `cargo test -p api-server --manifest-path server-rs/Cargo.toml password_change_allows_login_with_new_password_only -- --nocapture`、`npm run test -- AuthGate.test.tsx authService.test.ts`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/technical/PASSWORD_LOGIN_CHANGE_RESET_DESIGN_2026-04-24.md`、`docs/technical/AUTH_SESSIONS_QUERY_DESIGN_2026-04-21.md`。
## 2026-05-13 refresh_session 会话组后端聚合与远端踢下线
- 背景:账号安全页中同设备同 IP 的多条 active `refresh_session` 会重复展示;退出登录没有稳定撤销当前 refresh session;前端“踢下线”只做本地状态变化,未真正让远端设备失效。
- 决策:`GET /api/auth/sessions` 由后端按“同设备 + 同 IP”聚合 active refresh sessions,响应保留代表 `sessionId` 并新增 `sessionIds/sessionCount`;组内包含当前 refresh hash 或 Bearer `sid` 时整组视为当前设备组,前端不展示踢下线。新增 `POST /api/auth/sessions/{session_id}/revoke`,只允许撤销当前用户自己的非当前会话,不递增 `token_version`,但认证中间件会校验 access token `sid` 对应 active refresh session,使被踢设备立即失效。`/api/auth/logout` 在 refresh cookie 缺失时回退用 Bearer `sid` 撤销当前 session;自 2026-06-07 起单设备退出也不再递增 `token_version`,避免误伤其它设备,只有退出全部设备和改密类安全动作提升账号级版本。
- 影响范围:`module-auth` refresh session service、`api-server` auth middleware/logout/sessions route、`shared-contracts`/TS auth contract、`AuthGate`、`AccountModal`、认证会话技术文档和路由/埋点索引。
- 验证方式:执行 `cargo test -p module-auth --manifest-path server-rs/Cargo.toml refresh_session`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml auth_sessions -- --nocapture`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml revoke_auth_session -- --nocapture`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml logout_succeeds_without_refresh_cookie_when_bearer_token_is_valid -- --nocapture`、`npm run test -- AuthGate.test.tsx AccountModal.test.tsx authService.test.ts`、`npm run check:encoding`、`git diff --check`,并用 `npm run dev:api-server` 检查 `/healthz`。
- 关联文档:`docs/technical/AUTH_SESSIONS_QUERY_DESIGN_2026-04-21.md`、`docs/technical/SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md`、`docs/technical/SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`。
## 2026-05-12 抓大鹅入口素材风格改为 2D 常见素材风格
- 背景:抓大鹅草稿素材生成已经收敛为多视角 2D 图片素材,但入口页和旧参考图仍沿用黏土、低多边形、塑料、木雕、体素、金属等偏 3D 素材语言,容易让后续生成链路和用户预期继续漂移。
- 决策:抓大鹅创作入口 `2D素材风格` 固定为 `扁平图标 / 赛璐璐卡通 / 像素复古 / 手绘水彩 / 贴纸描边 / 厚涂图标 / 自定义`;默认风格为 `flat-icon`。入口参考图统一由 `npm run assets:match3d-style-references -- --live` 调用 VectorEngine `gpt-image-2` 生成,输出到 `public/match3d-style-references/`。旧 3D 风格参考图不再保留为入口资产。
- 影响范围:抓大鹅统一创作工作台、抓大鹅入口交互测试、Match3D PRD、素材生成流水线技术文档、F1 入口文档和 `public/match3d-style-references/` 静态资产。
- 验证方式:执行 `npm run test -- src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx`、`cargo test -p shared-contracts match3d --manifest-path server-rs\Cargo.toml`、`npm run typecheck`、`npm run check:encoding`,并人工抽查 `.tmp/match3d-style-preview.png`。
- 关联文档:`docs/prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`、`docs/technical/MATCH3D_F1_CREATION_ENTRY_AND_AGENT_UI_2026-04-30.md`。
## 2026-05-12 拼图与抓大鹅草稿背景音乐按纯音乐自动生成
- 背景:拼图和抓大鹅需要在草稿生成阶段直接产出可试听、可重生成、可进入运行态循环播放的背景音乐。
- 决策:复用通用 VectorEngine Suno 创作音频链路,不新增 SpacetimeDB 表;拼图音乐保存到首关 `PuzzleDraftLevel.backgroundMusic`,运行态通过 `PuzzleRuntimeLevelSnapshot.backgroundMusic` 下发;抓大鹅音乐保存到首个 `generatedItemAssets[].backgroundMusic`。两者草稿生成都使用 `title` 驱动、`prompt = ""`、`make_instrumental = true`;自动草稿阶段必须拿到可播放 `audioSrc` 才能返回成功,失败时停留在生成页并允许重试同一 session/profile。结果页内的手动重新生成继续作为已有草稿的补救入口。
- 影响范围:`api-server` 音频生成、拼图草稿编译、抓大鹅草稿编译、Puzzle/Match3D 结果页和运行态音频播放。
- 验证方式:检查草稿 response / work detail 中的 `backgroundMusic.audioSrc`,运行态开局后隐藏 audio 循环播放;执行音频相关后端 check、前端 typecheck 和编码检查。
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`、`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
## 2026-05-12 拼图 UI 背景图复用 levels_json 持久化
- 背景:拼图草稿结果页需要像抓大鹅一样支持 UI 背景生成,但首版只需要作品级/首关背景,不应为图片生成结果新增 SpacetimeDB 表结构。
- 决策:拼图 UI 背景字段存入首关 `levels_json`,字段为 `uiBackgroundPrompt`、`uiBackgroundImageSrc`、`uiBackgroundImageObjectKey``compile_puzzle_draft` 草稿编译阶段自动生成首关 UI 背景,自动草稿阶段必须拿到 `uiBackgroundImageSrc` 或 `uiBackgroundImageObjectKey` 才能返回成功;结果页新增 `UI` Tab,可编辑提示词并触发 `generate_puzzle_ui_background`,手动生成失败只展示在当前面板。`api-server` 读取 `public/ui-previews/puzzle-image-compact-ui-2026-05-08.png` 作为非拼图 UI 参考图,调用 VectorEngine `gpt-image-2` 生成 9:16 背景并要求中央正方形拼图区与外部 UI 背景边界清晰。SpacetimeDB 只保存结果,不做外部 I/O。
- 2026-05-18 追加:为缩短首版草稿等待,`compile_puzzle_draft` 在首关命名和 `uiBackgroundPrompt` 稳定后并行启动首关关卡图生成与 UI 背景生成;上传主图且关闭 AI 重绘时,并行执行上传图持久化与 UI 背景生成。生成页预计完成时间按 5 分钟展示。
- 2026-05-21 追加:拼图结果页独立“素材配置”Tab 已移除,UI spritesheet 与关卡纯背景收口到每关图片生成资产包。每次 `gpt-image-2` 预计 90 秒;2026-05-24 起草稿首图生成单独按 4 分钟展示,草稿完整 AI 重绘路径约 448 秒,上传图且关闭 AI 重绘路径跳过首图生成约 208 秒。结果页关卡详情继续复用 `CreativeImageInputPanel`,本次上传/历史选择图优先成为主图卡片,正式图只作为无新参考图时的预览;仅有正式图时仍允许在画面描述框上传多张参考图。
- 影响范围:拼图结果页、拼图运行态背景渲染、拼图 agent action、`module-puzzle` / `spacetime-module` / `spacetime-client` 的拼图关卡 JSON 映射、拼图流程技术文档。
- 验证方式:执行 `npm run test -- src/components/puzzle-result/PuzzleResultView.test.tsx`、`cargo test -p api-server puzzle_ui_background --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/PUZZLE_FORM_CREATION_FLOW_2026-04-29.md`。
## 2026-05-12 抓大鹅结果页素材编辑统一走作品级资产面板
- 背景:抓大鹅结果页需要支持封面图上传 / AI 重绘、物品素材独立预览、单项删除和批量新增,且不能把素材编辑继续做成列表内联展开或前端临时状态。
- 决策:结果页 `作品信息` 的封面图点击打开独立面板,封面图面板对齐拼图入口上传卡。已有上传主图时,请求体传 `uploadedImageSrc`AI 重绘走 VectorEngine `/v1/images/edits`,后端把上传图作为 multipart `image` part 传入 `gpt-image-2`;关闭 AI 重绘时只写回上传图,不调用生图。没有上传主图但存在 `referenceImageSrcs` 时,多参考图同样走 edits 的多个 `image` part;完全无参考图时走 `/v1/images/generations`。生成结果统一调用 `POST /api/creation/match3d/works/{profileId}/cover-image` 并转存到 `generated-match3d-assets`。`素材配置 > 物品` 列表项点击打开独立预览面板,不再提供单项重新生成按钮;单项删除和批量新增都写回同一份 `generated_item_assets_json`。批量新增调用 `POST /api/creation/match3d/works/{profileId}/item-assets`;该接口的物品 spritesheet 生成口径已被 2026-05-22 决策更新为关卡整图参考、`10*10` 绿幕图和上传前透明化。
- 影响范围:Match3D 结果页、Match3D works shared contracts、`api-server` Match3D 作品路由、生成资产历史类型和草稿恢复路径。
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run typecheck`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`。
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
## 2026-05-12 平台法律文档入口与登录协议确认
- 背景:生产发布需要在个人页展示用户协议、隐私政策、免责声明和备案号;登录页首次登录需要显式确认法律协议。
- 决策:法律文档内容读取 `media/files/*.md`,统一通过 `LegalDocumentModal` 独立弹窗展示;“我的”页常用功能区固定 3 列,设置入口下方展示法律信息和 `京ICP备2026025677号` 外链。登录弹窗用 `genarrative.auth.legal-consent.v1` 记录本机确认,首次未勾选时短信 / 密码登录按钮禁用,法律链接不自动勾选。
- 影响范围:平台个人页、登录弹窗、法律 Markdown 渲染和前端认证交互测试。
- 验证方式:执行 `npm run test -- src/components/auth/AuthGate.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、触碰文件 ESLint、`npm run check:encoding`。
- 关联文档:`docs/prd/PROFILE_LEGAL_INFO_AND_AUTH_AGREEMENT_PRD_2026-05-12.md`。
## 2026-05-12 微信小程序待绑定手机号优先走原生手机号授权
- 背景:微信小程序 `web-view` 壳登录后若返回 `pending_bind_phone`H5 仍会展示手输手机号和短信验证码绑定页,体验上多了一步。
- 决策:小程序壳在 `pending_bind_phone` 时暂不打开 H5,先展示原生 `button open-type="getPhoneNumber"`;用户同意后把 `bindgetphonenumber` 返回的 `code` 作为 `wechatPhoneCode` 调用 `/api/auth/wechat/bind-phone`。后端通过微信 `stable_token` 与 `getuserphonenumber` 换取平台验证后的手机号,再复用现有微信待绑定账号合并逻辑并重新签发 active 系统 token。H5 旧短信验证码绑定流程继续作为非小程序环境兜底。
- 影响范围:`miniprogram/pages/web-view/index.*`、`server-rs/crates/platform-auth`、`server-rs/crates/api-server/src/wechat_auth.rs`、认证共享契约、微信小程序 web-view 壳技术文档。
- 验证方式:执行 `npm run check:encoding`、`node scripts/check-wechat-miniprogram-auth-smoke.mjs`、`cargo test -p shared-contracts wechat_bind_phone_request_accepts_mini_program_phone_code --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server wechat_miniprogram_bind_phone_code_activates_pending_user --manifest-path server-rs/Cargo.toml -- --nocapture`。
- 关联文档:`docs/technical/WECHAT_MINIPROGRAM_WEB_VIEW_SHELL_2026-05-03.md`。
## 2026-05-26 微信小程序进入即开 H5,登录按需走原生手机号授权
- 背景:当前产品要求微信小程序进入后不再立刻取手机号,而是默认直接进入 `web-view`,登录状态与 Web 端统一;只有 H5 触发受保护操作时才走微信手机号授权。
- 决策:小程序壳首次进入只打开 H5,不再把登录态当作启动前置条件;H5 侧在小程序运行态触发登录时,不展示普通登录弹窗,而是跳转到小程序原生手机号授权流程,授权结果再回灌到 H5。未触发登录时保持游客态,与 Web 端一致。
- 影响范围:`miniprogram/pages/web-view/index.*`、`src/components/auth/AuthGate.tsx`、`src/components/auth/LoginScreen.tsx`、`src/services/authService.ts`、相关测试与说明文档。
- 验证方式:执行 `npm run check:encoding`、`npm run typecheck`、`npx vitest run src/components/auth/AuthGate.test.tsx src/services/authService.test.ts scripts/miniprogram-web-view-auth.test.ts`。
## 2026-05-13 宝贝爱画先作为寓教于乐独立本地 Demo 落地
- 背景:第三关 `宝贝爱画` 需要默认出现在“发现 / 寓教于乐”板块下方,但本阶段只验证画板、手部绘制、绘画魔法和本地保存闭环,不进入创作模板、公开作品或正式持久化。
- 决策:`baby-love-drawing / 宝贝爱画` 先作为独立运行态接入,入口由发现页寓教于乐默认卡片打开,并支持 `/runtime/baby-love-drawing` 直达;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时前端不展示频道/卡片且直达路由回落主应用。绘画魔法统一走 `POST /api/creation/edutainment/baby-love-drawing/magic` 后端安全代理,使用 VectorEngine `gpt-image-2` 与原始画布 Data URL 参考图生成绘本风图片;保存只写 localStorage,正式持久化后续再设计。
- 影响范围:`packages/shared/src/contracts/edutainmentBabyDrawing.ts`、`src/components/edutainment-runtime/BabyLoveDrawingRuntimeShell.tsx`、`src/services/edutainment-baby-drawing/`、`src/routing/appRoutes.tsx`、`src/components/rpg-entry/RpgEntryHomeView.tsx`、`server-rs/crates/api-server/src/edutainment_baby_drawing.rs`、`src/index.css`、宝贝爱画 PRD 与技术方案。
- 验证方式:执行宝贝爱画 model/runtime/service/route 定向测试、`npm run typecheck`、定向 ESLint、`cargo test -p api-server edutainment_baby_drawing --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server resolves_runtime_paths_to_creation_type_ids --manifest-path server-rs/Cargo.toml` 和编码检查;真实魔法生成需配置 `VECTOR_ENGINE_BASE_URL` 与 `VECTOR_ENGINE_API_KEY`。
- 关联文档:`docs/prd/BABY_LOVE_DRAWING_EDUTAINMENT_LEVEL_PRD_2026-05-13.md`、`docs/technical/BABY_LOVE_DRAWING_RUNTIME_DEMO_IMPLEMENTATION_2026-05-13.md`。
## 2026-05-12 宝贝识物创作同时生成玩法视觉主题包
- 背景:`宝贝识物` 创作原本只根据两个关键词生成物品透明图,运行态背景、UI、礼物盒和篮子仍使用固定 CSS 绘本风,无法根据“小猪佩琪 / 奥特曼”或“苹果 / 橘子”等创作者提示词做主题化包装。
- 决策:`POST /api/creation/edutainment/baby-object-match/assets` 同一次 image-2 / VectorEngine 调用链返回两个物品图和 `visualPackage`。为降低调用成本,新链路只生成一张 `1024x1024` 的 `2x2` 素材 sheet 和一张 `1536x1024` 场景背景图;`2x2` sheet 固定左上物品 A、右上物品 B、左下篮子、右下礼物盒,服务端按格切图并把物品、篮子和礼物盒转透明 PNG。视觉包必需资源为 `background`、`gift-box`、`basket`;总风格保持寓教于乐明亮卡通绘本插画风,主题按两个物品关键词匹配。左右手位置指示器是运行态默认静态素材,使用项目内置第一人称半抓握手,不再随每次创作生成。运行态中礼物盒按约 2 倍视觉尺寸展示、篮子按约 1.5 倍展示,中央物品 UI 与篮子物品图标使用固定正方形槽位并等比 `contain` 缩放,礼物盒打开烟雾特效由 CSS 兜底;历史草稿中的 `ui-frame` / `smoke-puff` / `left-hand` / `right-hand` 仅兼容读取或忽略。前端草稿保存该包,运行态消费该包;旧草稿以 `visualPackage = null` 继续使用 CSS 兜底。
- 影响范围:`packages/shared/src/contracts/edutainmentBabyObject.ts`、`server-rs/crates/api-server/src/edutainment_baby_object.rs`、`src/services/edutainment-baby-object/babyObjectMatchClient.ts`、`src/components/edutainment-runtime/BabyObjectMatchRuntimeShell.tsx`、`src/index.css`、宝贝识物 PRD 与技术方案。
- 验证方式:执行宝贝识物 service / runtime 定向测试、`cargo test -p api-server edutainment_baby_object --manifest-path server-rs/Cargo.toml`、相关 ESLint 与编码检查;真实生图需配置 `VECTOR_ENGINE_BASE_URL` 与 `VECTOR_ENGINE_API_KEY`。
- 关联文档:`docs/prd/BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md`、`docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`。
## 2026-05-11 拼图与抓大鹅结果页音频资产复用通用创作音频链路
- 背景:拼图和抓大鹅结果页需要接入 Suno 背景音乐,抓大鹅还需要物体点击音效,但当前两类作品没有独立的作品级音频表或 metadata 字段。
- 决策:新增 `/api/creation/audio/*` 通用创作音频路由,后端统一负责 VectorEngine 音频任务、OSS 转存、`asset_object` 与 `asset_entity_binding` 写入;视觉小说旧路由保留并复用同一持久化逻辑。拼图背景音乐暂存到首关 `levels_json[0].backgroundMusic/background_music`;抓大鹅背景音乐暂存到 `generated_item_assets_json[0].backgroundMusic/background_music`,单物体点击音效存到对应 item 的 `clickSound/click_sound`。本轮不新增 SpacetimeDB 表和字段。
- 2026-05-12 补充:抓大鹅入口页新增 `generateClickSound` 开关,默认关闭;开启时 `match3d_compile_draft` 在生成首批 2D 物品素材后并行生成各物品点击音效,并继续复用通用创作音频路由的 OSS、资产绑定和扣费口径。
- 影响范围:拼图结果页、抓大鹅结果页、抓大鹅运行态音频播放、通用创作音频 shared contracts、`api-server` 音频路由和资产绑定。
- 验证方式:执行拼图/抓大鹅结果页定向测试、`npm run typecheck`、`cargo test -p api-server vector_engine_audio_generation`、`cargo test -p shared-contracts creation_audio`、`cargo check -p api-server`,真实生成需配置 VectorEngine 与 OSS 私密环境。
- 关联文档:`docs/technical/PUZZLE_MATCH3D_RESULT_AUDIO_TAB_2026-05-11.md`。
## 2026-05-11 寓教于乐公开作品使用独立 `edutainment` 来源接入
- 背景:`宝贝识物` 首关需要通过创作模板发布后进入寓教于乐板块,同时关闭入口时必须从发现页、搜索、详情深链、作品号和历史入口完全不可见;若继续落入 RPG 默认公共作品链路,容易出现误启动、误改造或近似标签误归类。
- 决策:寓教于乐公开作品在前端公共作品模型中使用 `sourceType = edutainment`,当前只承接 `templateId = baby-object-match`、`templateName = 宝贝识物`;进入“发现 / 寓教于乐”频道仍必须携带精确等于 `寓教于乐` 的公开标签,不因模板名或近似标签自动归类。公开详情、推荐运行态、改造、编辑、点赞和分享链路都必须显式识别 `edutainment`,不得回落到 RPG 默认处理。
- 影响范围:公开作品卡、发现页频道、作品号搜索、公开详情深链、分享、作品架聚合、后续儿童动作 Demo 模板的发布结果展示。
- 验证方式:执行第4线程定向单测、前端类型检查、ESLint 与编码检查;关闭 `VITE_ENABLE_EDUTAINMENT_ENTRY` 时确认精确 `寓教于乐` 作品不可通过任何公开入口访问。
- 关联文档:`docs/design/CHILD_MOTION_EDUTAINMENT_DISCOVER_ENTRY_2026-05-09.md`、`docs/prd/BABY_OBJECT_MATCH_EDUTAINMENT_TEMPLATE_PRD_2026-05-11.md`、`docs/technical/BABY_OBJECT_MATCH_CREATION_PUBLISH_IMPLEMENTATION_2026-05-11.md`。
## 2026-05-10 儿童动作 Demo 视觉资产统一为绘本草地舞台
- 背景:儿童动作 Demo 需要从暗色科技风切换到更适合儿童互动的卡通绘本草地风格,并且要让背景、地面、UI、地面指示环和用户轮廓使用同一套 image-2 资源口径。
- 决策:热身舞台及后续儿童动作 Demo 场景、物品、UI 资源统一采用明亮卡通绘本草地视觉语言。真实资源默认输出到 `public/child-motion-demo/`。背景沿用 `picture-book-grass-stage.png`;地面、指示环、角色指示器和 UI 已拆分为用途专属资源:`picture-book-foreground-grass-v2.png`、`picture-book-ground-ring-v3.png`、`picture-book-character-outline-v4.png`、`picture-book-hud-strip-v2.png`、`picture-book-calibration-strip-v2.png`、`picture-book-start-panel-v2.png` 和 `picture-book-ui-button-v2.png`。其中角色指示器 v4 基于 v2 本地后处理为更细的白色描边样式,内部透明,耳朵、手指、脚趾等细节已弱化,页面显示尺寸相对上一版放大 50%。生成脚本固定为 `scripts/generate-child-motion-demo-assets.mjs`,并通过 `npm run assets:child-motion-demo` 调用 VectorEngine `gpt-image-2`;透明资源使用品红底生成后本地去背,中间源图仅保存在 `tmp/child-motion-demo-assets/`。在缺少 `VECTOR_ENGINE_BASE_URL` 或 `VECTOR_ENGINE_API_KEY` 时,只允许 dry-run 和 CSS 兜底,不伪造 live 生图结果。
- 影响范围:`src/index.css`、`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx` 的舞台视觉层、儿童动作 Demo 技术文档、后续 image-2 资产生成流程。
- 验证方式:检查 `/child-motion-demo` 舞台是否在未生成资产时仍有可用草地绘本兜底;补齐 VectorEngine 私密配置后运行 `npm run assets:child-motion-demo -- --live` 或 `--live --only <asset-id>` 应能写出对应 PNG,并确认页面静态资源返回 `image/png`。若只调整透明去背、裁切或品红边缘,可运行 `npm run assets:child-motion-demo -- --live --postprocess-only --force --only <asset-id>` 复用源图后处理。页面接入时必须按资源原始比例等比使用,不得把方形软纸面板拉伸成 HUD、状态条或底部草坪。
- 关联文档:`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`、`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`。
## 2026-05-10 方洞挑战从创作页入口和作品架隐藏
- 背景:运营节奏要求创作页完全隐藏方洞挑战,不能只隐藏新建入口后仍从创作页作品架暴露已有方洞草稿或已发布作品。
- 决策:SpacetimeDB `creation_entry_type_config` 中 `square-hole.visible=false` 作为创作页统一开关;创作 Tab 模板入口、旧选择弹层、创作 Hub 卡带和创作页作品架都基于该开关隐藏方洞挑战。既有方洞详情、作品号、广场和运行态链路暂不删除,api-server 路由熔断只按 `open=false` 禁用玩法 API。
- 影响范围:SpacetimeDB 入口配置默认种子、`platformEntryCreationTypes`、`CustomWorldCreationHub`、`PlatformEntryFlowShellImpl` 以及创作入口相关文档和回归测试。
- 验证方式:执行入口配置、创作 Hub 和平台入口交互定向测试,确认看不到“方洞挑战” Tab、按钮和作品架条目。
- 关联文档:`docs/technical/NEW_WORK_ENTRY_CONFIG_2026-05-01.md`、`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`。
## 2026-05-14 视觉小说从创作页入口隐藏
- 背景:当前创作页需要关闭视觉小说模板入口,不能继续在模板 Tab、旧选择弹层或创作 Hub 卡片中展示。
- 决策:SpacetimeDB `creation_entry_type_config` 默认种子中 `visual-novel.visible=false` 且 `open=false`;旧默认可见配置会被迁移为隐藏和关闭。前端继续只消费 `GET /api/creation-entry/config`,不得用硬编码恢复视觉小说模板入口。
- 影响范围:SpacetimeDB 入口配置默认种子、api-server 测试配置、创作页模板 Tab、创作 Hub 测试和创作入口文档。
- 验证方式:执行入口配置、创作 Hub、平台入口交互和 api-server 路由熔断定向测试,确认“视觉小说”不出现在创作页且 `/api/creation/visual-novel/*` 默认被熔断。
- 关联文档:`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`、`docs/technical/ADMIN_CREATION_ENTRY_SWITCH_CONFIG_2026-05-11.md`。
## 2026-05-20 RPG 创作入口开放
- 背景:RPG 文字冒险能力已经具备历史 custom-world 创作和运行闭环,但入口默认种子仍 `visible=false`,创作页不展示。
- 决策:SpacetimeDB `creation_entry_type_config` 默认种子中 `rpg.visible=true` 且 `open=true`,旧默认隐藏配置只在标题、subtitle、badge、图片、排序和开关完全匹配时迁移为可见可创建。`airp` 仍保持 AI RPG 占位,不接管当前 RPG 链路。结构化创作 / RPG JSON 链路默认关闭 Responses `web_search`,需要联网增强时才通过 `GENARRATIVE_RPG_LLM_WEB_SEARCH_ENABLED=true` 或 `GENARRATIVE_CREATION_AGENT_LLM_WEB_SEARCH_ENABLED=true` 显式启用;未开通工具的上游会返回 `ToolNotOpen`,不能把这类失败暴露成“模型返回结果解析失败”。
- 影响范围:创作入口默认种子、旧库入口纠偏、`api-server` 入口熔断、创作页模板 Tab、创作 Hub 测试、玩法链路文档和后端路由文档。
- 验证方式:执行入口配置、api-server 路由熔断、创作 Hub 和平台入口交互定向测试,确认“文字冒险”出现在创作入口,`/api/runtime/custom-world*`、`/api/story/*`、`/api/runtime/chat/*` 都按 `rpg` 入口开关熔断。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-10 运行态输入设备抽象层全项目通用化
- 背景:拼图运行态接入 mocap 后,鼠标/触控和 mocap 各自维护输入逻辑会导致合并大块、拖拽语义和取消会话行为不一致;后续其他玩法也需要复用体感、摇杆、键盘等设备输入。
- 决策:前端运行态输入统一通过 `src/services/input-devices/` 承接,设备适配层只输出 `press / move / release / tap / drop` 等通用语义和通用坐标;玩法组件自己解释目标对象、落点和业务动作,输入层不得写拼图等玩法专用规则。
- 影响范围:拼图运行态鼠标/触控/mocap 输入、后续运行态设备接入、运行态输入技术文档与相关前端回归测试。
- 验证方式:执行 `npm run test -- src\services\input-devices\runtimeDragInputController.test.ts`、`npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx`、`npm run typecheck` 和编码检查。
- 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md`、`docs/technical/PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md`。
## 2026-05-11 前端调试模式统一判断
- 背景:拼图 mocap 调试面板此前在运行态常驻展示,生产构建和正式体验里容易遮挡棋盘内容;后续其它局部诊断 UI 也需要统一的调试模式入口。
- 决策:前端新增 `src/config/debugMode.ts` 作为全局调试模式判断,默认跟随 Vite 开发态,允许 `VITE_DEBUG_MODE=true/false` 显式覆盖。2026-05-14 起,拼图运行态已临时移除 mocap 调用、体感光标和 mocap 调试面板;调试模式仍供其它局部诊断 UI 使用。
- 影响范围:前端局部调试 UI、拼图运行态 mocap 诊断面板、`.env.example` 和运行态输入技术文档。
- 验证方式:执行 `npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx`、`npm run typecheck` 和编码检查。
- 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md`。
## 2026-05-10 儿童动作热身关直接消费 mocap 数据源
- 背景:儿童动作 Demo 不能只依赖浏览器摄像头状态和键鼠调试输入,否则真实硬件接入后会出现“mocap 在线但页面提示摄像头不可用”或“能看到画面但动作不推进”的卡点。
- 决策:热身关全流程直接接入 `useMocapInput`,通过本地 mocap WebSocket `/stream` 消费 `general.body.center_norm` 身体中心、`actions/action/gesture/gestures/event/name/type` 动作名,以及 `hands[]`、`leftHand/rightHand`、`left_hand/right_hand` 手部坐标;位置步骤由身体中心推进,`wave_greeting`、`wave_left_hand`、`wave_right_hand` 和 `jump_once` 由 mocap 手势/轨迹推进。浏览器摄像头只作为背景层,动作数据源状态优先展示,键鼠仍作为本地调试兜底。
- 影响范围:`src/services/useMocapInput.ts`、`src/components/child-motion-demo/ChildMotionWarmupDemo.tsx`、对应单测与热身关技术文档。
- 验证方式:执行 `npx vitest run src/services/useMocapInput.test.ts src/components/child-motion-demo/ChildMotionWarmupDemo.test.tsx src/components/child-motion-demo/childMotionWarmupModel.test.ts src/services/child-motion-demo/childMotionDebugInput.test.ts src/routing/appRoutes.test.ts`、`npx eslint ...`、`npm run typecheck`、`npm run check:encoding`,并确认 `http://127.0.0.1:8876/stream` WebSocket 可握手、`http://127.0.0.1:3000/child-motion-demo` 可访问。
## 2026-05-18 寓教于乐频道补充热身关入口
- 背景:用户希望在发现页的寓教于乐板块里直接看到热身关入口,而不是只依赖独立直达路由。
- 决策:`child-motion-demo` 作为寓教于乐频道的独立卡片展示,点击后直接进入 `/child-motion-demo`;该入口与 `宝贝爱画` 并列,仍复用现有独立热身关路由,不新增新的创作模板或运行态壳层。
- 影响范围:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
- 验证方式:执行入口回归测试、`npm run typecheck`、`npm run check:encoding`,并在发现页的寓教于乐频道确认热身关卡卡片可点击进入 `/child-motion-demo`。
- 关联文档:`docs/technical/CHILD_MOTION_DEMO_WARMUP_IMPLEMENTATION_SPEC_2026-05-09.md`。
## 2026-05-09 GPT-image-2 图片生成统一迁移到 VectorEngine
- 背景:仓库内 RPG、拼图、方洞和本地模板脚本的 GPT-image-2 生图此前依赖 APIMart 图片网关;团队要求参考 VectorEngine Apifox `api-448710071`,后续不再使用 APIMart 执行 GPT-image-2 图片生成。
- 决策:所有 GPT-image-2 无参考图生图请求统一走 VectorEngine `POST /v1/images/generations`,有参考图请求走 `POST /v1/images/edits` multipart,基础配置读取 `VECTOR_ENGINE_BASE_URL` / `VECTOR_ENGINE_API_KEY` / `VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS`,上游模型使用 `gpt-image-2`,请求体不再携带 `official_fallback`。当时 APIMart 仍保留给创意 Agent 的 `gpt-5` Responses 文本/多模态链路;该文本链路已被 2026-07-05 VectorEngine Chat Completions `gpt-5.4-mini` 决策覆盖。
- 影响范围:`api-server` 共享图片 helper、拼图图片生成、角色主图、RPG 场景图、开局 CG 故事板、方洞视觉资产、生产环境示例、gpt-image-2 本地 skill 和相关技术文档。
- 验证方式:执行 `npm run check:encoding`、`cargo test -p api-server openai_image --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server custom_world_ai --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server character_visual --manifest-path server-rs/Cargo.toml`,并用 `npm run dev:api-server` + `/healthz` 做后端 smoke。
- 关联文档:`docs/technical/VECTOR_ENGINE_GPT_IMAGE_2_GENERATION_2026-05-09.md`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。
## 2026-05-21 GPT-image-2 参考图统一走 edits multipart
- 背景:VectorEngine Apifox 创建 `api-446794806` 与编辑 `api-446794807` 明确区分无参考图创建和有参考图编辑;仓库旧实现曾把参考图塞入 `gpt-image-2` generations 的 `image` 数组,导致与供应商当前契约不一致。
- 决策:所有 GPT-image-2 无参考图生成调用 `POST /v1/images/generations`,所有有参考图生成调用 `POST /v1/images/edits`,模型固定 `gpt-image-2`,参考图作为 multipart `image` part 传入;仓库不再调用 `gpt-image-2-all`。
- 影响范围:`api-server` 共享图片 helper、拼图图片生成、Match3D 封面重绘和容器 UI 图、gpt-image-2 本地 skill、玩法链路文档和后端架构文档。
- 验证方式:搜索仓库不应再出现 VectorEngine 图片编辑路径调用;执行 `cargo test -p api-server openai_image_generation --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server puzzle_vector_engine --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server match3d_background --manifest-path server-rs/Cargo.toml`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-05-08 Hyper3D Rodin Gen-2 只通过后端安全代理接入
- 背景:需要接入 Hyper3D Rodin Gen-2 的文生 3D 模型与图生 3D 模型,但供应商 API Key 不能进入前端、文档或 Git;本次只是外部副作用代理,不需要新增平台真相表。
- 决策:Hyper3D 统一走 `api-server` 的 `/api/assets/hyper3d/*` 鉴权路由,配置只读取 `HYPER3D_BASE_URL` / `HYPER3D_API_KEY` / `HYPER3D_MODEL_REQUEST_TIMEOUT_MS` 及兼容 `RODIN_*` 变量;生成提交、状态查询和下载列表都由后端代理。首版不写 SpacetimeDB、不确认 `asset_object`,下载链接后续由调用方决定是否进入 OSS 资产链。
- 影响范围:`api-server` 外部服务配置、Hyper3D route、`shared-contracts` / TS contract、前端 service、生产环境示例和外部服务环境变量文档。
- 验证方式:执行 `cargo test -p api-server hyper3d --manifest-path server-rs/Cargo.toml`、`cargo test -p shared-contracts hyper3d --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck` 和编码检查;真实 API smoke 只在本地私密环境配置 key 后手动执行。
- 关联文档:`docs/technical/HYPER3D_RODIN_GEN2_MODEL_GENERATION_2026-05-08.md`、`docs/technical/API_SERVER_EXTERNAL_SERVICE_ENV_CONFIG_2026-05-07.md`。
## 2026-05-08 APIMart 接口统一携带 `official_fallback`
> 2026-05-09 追认:本决策中的图片生成部分已被“GPT-image-2 图片生成统一迁移到 VectorEngine”覆盖;2026-07-05 后 APIMart `gpt-5` Responses 文本/多模态链路也被 VectorEngine Chat Completions `gpt-5.4-mini` 覆盖,不再携带 `official_fallback`。
- 背景:APIMart 的图片生成和 Responses 接口在仓库内分散于 `api-server`、`platform-llm` 和本地 skill 脚本,若只修单点,容易出现不同入口的上游请求体不一致。
- 决策:凡是仓库内调用 APIMart 的 OpenAI 兼容接口,请求体统一携带 `official_fallback: true`;其中图片生成请求直接固定写入,`platform-llm` 的 APIMart GPT-5 client 通过显式开关开启,不默认扩散到 Ark 等其它 provider。
- 影响范围:`server-rs/crates/api-server/src/openai_image_generation.rs`、`server-rs/crates/api-server/src/puzzle.rs`、`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/platform-llm/src/lib.rs`、`.codex/skills/gpt-image-2-apimart/` 和相关技术文档。
- 验证方式:图片生成与 creative-agent APIMart 路径的单测都应断言 `official_fallback` 已写入请求 JSON;编码检查和相关 Rust 测试应持续通过。
- 关联文档:`docs/technical/PUZZLE_APIMART_IMAGE_MODEL_ROUTING_2026-05-01.md`、`docs/technical/RPG_IMAGE_GENERATION_GPT_IMAGE_2_MIGRATION_2026-05-02.md`、`docs/technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md`。
## 2026-05-07 server-rs Cargo 依赖集中到 workspace
- 背景:`server-rs` 多 crate 已稳定成 DDD workspace,成员 `Cargo.toml` 中重复散写第三方版本和本地 path 依赖,升级 SpacetimeDB SDK、`serde`、`reqwest`、`tokio` 等依赖时容易漂移。
- 决策:`server-rs/Cargo.toml` 的 `[workspace.dependencies]` 统一维护第三方依赖版本和 workspace 内部 crate path;成员 crate 默认使用 `{ workspace = true }`,只保留自身 feature、optional 或 target-specific 差异;OSS 与阿里云 OpenAPI 签名统一走 `sha2::Sha256` 对应的 V4/V3 口径。2026-07-15 起,`platform-matting` 已移除 VIAPI 共享临时桶的 OSS V1 SHA-1 例外,改用 `AuthorizeFileUpload` 返回的 Policy POST 授权;后续不得恢复 crate 内 SHA-1 签名。
- 影响范围:`server-rs/Cargo.toml`、所有 `server-rs/crates/*/Cargo.toml`、`platform-oss`、`platform-auth`、后续新增 Rust crate 或新增 Rust 依赖的开发流程。
- 验证方式:修改 Cargo 配置后先执行 `cargo metadata --manifest-path server-rs\Cargo.toml --format-version 1 --no-deps`,再按影响范围执行 `cargo check`、DDD 边界检查和编码检查。
- 关联文档:`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`。
## 2026-05-08 资料页反馈提交必须走 Rust 后端与 SpacetimeDB
- 背景:`/profile/feedback` 首版页面曾只做前端成功态,无法沉淀到用户账号和数据库,也容易与主站平台主题脱节。
- 决策:反馈提交统一走鉴权 HTTP 路由 `POST /api/profile/feedback`,由 `api-server` 取当前 access token 用户,调用 `spacetime-client` facade,再通过 `spacetime-module` procedure 写入私有表 `profile_feedback_submission`;前端只负责输入采集、Data URL 预览和提交元数据,不再保存 `File[]` 作为外部契约。
- 影响范围:`src/components/platform-entry/PlatformFeedbackView.tsx`、`src/services/rpg-entry/rpgProfileClient.ts`、`packages/shared/src/contracts/runtime.ts`、`server-rs/crates/shared-contracts`、`api-server`、`module-runtime`、`spacetime-client`、`spacetime-module`、表目录与 bindings。
- 验证方式:前端定向测试应覆盖 Data URL 预览与 `/api/profile/feedback` 请求体;后端变更需同步 `migration.rs`、`SPACETIMEDB_TABLE_CATALOG.md` 和生成绑定;API smoke 使用 `npm run dev:api-server` 和 `/healthz`。
- 关联文档:`docs/prd/PROFILE_FEEDBACK_ENTRY_PRD_2026-05-08.md`、`docs/technical/PROFILE_FEEDBACK_BACKEND_INTEGRATION_2026-05-08.md`。
## 2026-05-06 Maincloud 历史残留引用禁止再使用
- 背景:项目已经全面移除 Maincloud 运行口径,但历史脚本、测试名和文档仍可能让后续开发误用 `api-server:maincloud` 或 `GENARRATIVE_SPACETIME_MAINCLOUD_*`。
- 决策:`maincloud` / `Maincloud` / `MAINCLOUD` 相关代码、脚本、测试、环境变量、命令和文档要求全部视为历史残留,后续禁止新增、运行或引用;后端 API smoke 统一使用 `npm run dev:api-server` 并检查 `/healthz`。
- 影响范围:`AGENTS.md`、`docs/technical/`、`docs/project-memory/shared-memory/`、后端启动脚本、测试支撑和所有后续工程文档。
- 验证方式:新增或修改后端相关文档时,检查不得要求 `api-server:maincloud` 或 `GENARRATIVE_SPACETIME_MAINCLOUD_*`;触碰历史残留时同步删除或改名。
- 关联文档:`docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`、`docs/technical/SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md`。
## 2026-05-05 新手引导首版复用拼图本地运行时
- 背景:首次打开产品的新用户需要先体验输入想法、生成拼图、通关、登录保存、回到首页的闭环,但首版不应引入新的持久化表或独立玩法运行时。
- 决策:未登录首次访问由前端 localStorage 标记触发;生成入口走公开 BFF `POST /api/runtime/puzzle/onboarding/generate` 生成 1 关临时拼图;登录后保存走鉴权 BFF `POST /api/runtime/puzzle/onboarding/save`,由服务端创建当前用户拼图 agent session 并更新其草稿作品 profile;游玩阶段复用现有本地拼图运行时。
- 影响范围:平台入口首屏、新手引导 PRD、拼图 BFF、拼图作品契约与前端 puzzle runtime。
- 验证方式:未登录首次访问应展示新手引导;生成后只进入 1 关本地拼图;通关后登录保存应在当前用户拼图作品架出现草稿作品;不应产生 SpacetimeDB schema 变更。
- 关联文档:`docs/prd/FIRST_LAUNCH_PUZZLE_ONBOARDING_PRD_2026-05-05.md`。
## 2026-05-05 text-game 作为陶泥儿幕间文字游戏模板接入
- 背景:团队希望参考 MOKU / 幕间类 AI 文游,设计可在陶泥儿内落地的 AI 文字游戏模板,但不能把外部平台社区、支付、榜单、论坛、账号或私有存档迁入 Genarrative。
- 决策:新增 `text-game` 作为陶泥儿 AI 原生文字游戏模板口径,展示名可用“幕间”或“幕间文字”;它与 `visual-novel` 分离,重点是 AI GM、自由行动、状态后果、长期记忆、章节目标和轻量剧本模拟器;入口、作品、发布、资产、钱包、埋点、存档和广场全部复用陶泥儿平台接口;禁止新增 replay、外部社区、外部支付、外部榜单和私有存档系统。
- 影响范围:后续 `text-game` shared contracts、`module-text-game`、SpacetimeDB 表、`api-server` 路由、前端入口 / workspace / result / runtime、平台作品架和发现聚合。
- 验证方式:后续落地时确认路由使用 `/api/creation/text-game/*` 与 `/api/runtime/text-game/*`;确认正式业务真相在 Rust / SpacetimeDB 后端;确认没有 `replay` 能力和外部平台功能误入;确认 `text-game` 不复用 `visual-novel` step 契约作为运行态真相。
- 关联文档:`docs/prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md`。
## 2026-05-05 2048 玩法模板采用 `twenty-forty-eight` 工程域
- 背景:平台计划新增 2048 游戏玩法模板,需要同时适配前端 stage、HTTP 路由、Rust 模块、SpacetimeDB 表和公开作品号;裸 `2048` 不适合作为模块或文件命名前缀。
- 决策:面向用户展示名保持 `2048`,工程玩法 ID 固定为 `twenty-forty-eight`Rust 模块与表前缀使用 `twenty_forty_eight`,公开作品号前缀使用 `TF-`;玩法按完整闭环设计,包含 Agent 创作、结果页、试玩、发布、公开运行、后端棋盘裁决、排行榜和作品架 / 广场接入。
- 影响范围:后续 SpacetimeDB 创作入口配置、平台 `SelectionStage`、前端 `twenty-forty-eight-*` 组件与 service、`module-twenty-forty-eight`、`shared-contracts`、`spacetime-module` 表、`spacetime-client` facade、`api-server` 路由、作品号和 PRD 索引。
- 验证方式:后续落地时确认用户可见标题为 `2048`,代码、路由和表统一使用 `twenty-forty-eight` / `twenty_forty_eight`;移动、合并、生成新方块、目标达成、失败和榜单成绩由后端正式裁决,前端不伪造分数或目标达成。
- 关联文档:`docs/prd/AI_NATIVE_2048_GAMEPLAY_TEMPLATE_PRD_2026-05-05.md`。
## 2026-05-05 幸存者类玩法作为平台模板接入
- 背景:平台继续扩展新玩法模板,需要把幸存者 / 割草 / 轻度 Roguelite 类玩法纳入统一创作中心、作品架、广场和运行态体系,避免再起一套独立小游戏工程。
- 决策:新增 `survivor` 作为 Genarrative 平台玩法模板,统一使用 `server-rs + Axum + SpacetimeDB`,创作端、结果页、试玩、发布和运行态都复用平台接口;前端只负责表现和高频模拟,不承接正式规则真相。
- 影响范围:`docs/prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md`、后续 `survivor` shared contracts、前端入口 / result / runtime、`server-rs` DDD 分层、SpacetimeDB 表设计和平台作品闭环。
- 验证方式:后续落地时检查 `survivor` 入口、session、work profile、runtime run、checkpoint、升级候选和结算接口是否都落在平台统一链路内,并确认没有新增独立小游戏壳层。
- 关联文档:`docs/prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md`。
## 2026-05-05 视觉小说 TXT 玩法只作为平台模板接入且删除回放
- 背景:`Interactive-fiction-backend` 与 `Interactive-fiction-frontend` 是完整平台类工程,其中 TXT / Galgame 玩法可借鉴,但账号、商城、后台、公开市场、回放等平台能力不能迁入 Genarrative。
- 决策:`visual-novel` 只作为 Genarrative 视觉小说模板接入,保留想法 / 文档 / 空白创建、世界观 / 角色 / 场景 / 剧情阶段编辑、视觉小说 step 运行时、历史和重生成等模板能力;入口、作品、发布、资产、钱包、存档和广场全部使用 Genarrative 平台接口;彻底删除回放、分享回放、回放编译、回放路由、回放表和回放 UI。
- 影响范围:视觉小说 PRD、旧 TXT 文档口径、后续 `visual-novel` shared contracts、前端入口 / result / runtime、`server-rs` DDD 分层、SpacetimeDB 表设计和平台存档接入。
- 验证方式:后续落地时扫描前端、后端、契约、表和文档,确认不存在 `replay` 能力;确认视觉小说没有迁入外部平台账号、订单、会员、促销、后台、公开市场或私有存档系统;确认后端落在 `server-rs + Axum + SpacetimeDB`。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/prd/TXT_MODE_CORE_GAMEPLAY_PRD_2026-04-20.md`、`docs/technical/TXT_MODE_VISUAL_NOVEL_MIGRATION_EXECUTION_PLAN_2026-04-20.md`。
## 2026-05-05 视觉小说 VN-02 表与 spacetime-client facade 收口
- 背景:`visual-novel` 后续 API、创作工作台和运行时需要稳定的 SpacetimeDB schema 与 Rust facade,且必须延续“无回放、无私有存档”的产品边界。
- 决策:视觉小说首批数据库只落六张表:`visual_novel_agent_session`、`visual_novel_agent_message`、`visual_novel_work_profile`、`visual_novel_runtime_run`、`visual_novel_runtime_history_entry`、`visual_novel_runtime_event``visual_novel_runtime_event` 是 `public event` 审计事件表,不是 replay 数据源;运行历史只保存继续体验与历史重生成需要的 typed step 和快照哈希。`api-server` 后续接入必须经 `spacetime-client/src/visual_novel.rs` typed facade,不直接依赖生成 bindings。
- 影响范围:`server-rs/crates/spacetime-module/src/visual_novel.rs`、`migration.rs`、`server-rs/crates/spacetime-client/src/visual_novel.rs`、`module_bindings/`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`、VN-05 API 联调。
- 验证方式:执行 `npm run spacetime:generate -- --rust-only`、`cargo check -p spacetime-module`、`cargo check -p spacetime-client`、`npm run check:encoding`;扫描视觉小说 schema / facade / 表目录确认没有 `replay` 表、路由或私有 save 表。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。
## 2026-05-05 视觉小说 VN-07 前端创作闭环按阶段边界落地
- 背景:`visual-novel` 模板需要先完成创作工作台与结果页,真实生成和正式玩家 runtime 仍依赖 VN-05 后端路由。
- 决策:VN-07 前端只接入口、Agent 工作台、可编辑 `VisualNovelResultDraft` 结果页和测试 run`blank` 起点直接生成本地空白草稿进入结果页,`idea` / `document` 继续调用 `/api/creation/visual-novel/sessions`;结果页保存先更新当前 session 草稿,显式“编译草稿”才调用 `/compile`,测试 run 在真实 runtime 不可用时降级为本地 test run。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/visual-novel-creation/`、`src/components/visual-novel-result/`、`packages/shared/src/contracts/visualNovel.ts`、视觉小说 PRD。
- 验证方式:执行前端 typecheck、视觉小说工作台 / 结果页定向测试和编码检查;确认未新增 replay、作品聚合或正式 runtime 能力。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`。
## 2026-05-07 视觉小说 VN-11 负向扫描门禁
- 背景:视觉小说 TXT 模板进入收口后,需要一个可重复执行的守门方式,避免工程代码误入回放能力或外部平台功能。
- 决策:新增 `npm run check:visual-novel-vn11`,由 `scripts/check-visual-novel-vn11-negative-scan.mjs` 扫描 `src/`、`packages/shared/src/`、`server-rs/crates/`、`docs/` 与 `docs/project-memory/shared-memory/`;工程代码中不允许出现 replay / 回放 / 录制 / 复盘类直出命中;外部平台能力误入只在视觉小说实现路径内检查,避免把平台已有账号、会员、后台等能力误判为视觉小说迁入。
- 影响范围:视觉小说 VN-11 验收、后续 `visual-novel` 增量改动、同类新玩法负向扫描脚本。
- 验证方式:执行 `npm run check:visual-novel-vn11`,报告写入 `docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;当前扫描结论为工程代码无回放类直出命中,视觉小说实现路径无外部平台能力误入。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/audits/VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`。
## 2026-05-07 视觉小说 VN-12 采用单独验收门禁脚本
- 背景:VN-12 是视觉小说模板的全链路联调与自动化验收收口任务,需要把关键路径、API smoke、前端测试和报告输出固化成可复跑门禁,避免后续改动只靠手工口述结论。
- 决策:新增 `npm run check:visual-novel-vn12`,由 `scripts/check-visual-novel-vn12-acceptance.mjs` 校验 PRD、VN-11 报告、关键前端测试、视觉小说 service client、`api-server` / `module-visual-novel` / `shared-contracts` 相关文件和路由命中,并生成 `docs/audits/VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md`。
- 影响范围:VN-12 验收、视觉小说后续回归、同类玩法的收口门禁模式。
- 验证方式:执行 `npm run check:visual-novel-vn12 -- --write-report`,报告应覆盖自动化验收清单、API smoke、前端关键路径、桌面/移动端检查说明和已执行命令;若脚本失败,直接回流到对应 owner 修复。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/audits/VN12_FULL_CHAIN_ACCEPTANCE_REPORT_2026-05-07.md`。
## 2026-05-07 视觉小说 VN-13 文档与交接收口
- 背景:视觉小说模板主链已经落地完成,需要把 PRD、表目录、prompt 工具说明、负向扫描报告和维护经验收成新开发者可直接接手的一组文档,避免后续仍回头查旧 TXT 迁移方案。
- 决策:视觉小说后续维护的正式入口固定为 `AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`SPACETIMEDB_TABLE_CATALOG.md`、`VISUAL_NOVEL_PROMPT_AND_LLM_TOOLS_VN03_2026-05-05.md`、`VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md`、`VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md` 和 `VN11_NEGATIVE_SCAN_REPORT_2026-05-07.md`;旧 TXT 迁移文档仅保留历史参考地位。
- 影响范围:视觉小说 PRD 收口、技术文档索引、经验文档索引、项目共享记忆和后续维护阅读顺序。
- 验证方式:打开上述文档即可获得当前实现边界、表目录、Prompt 口径、负向扫描和维护经验;后续维护不需要把旧 TXT 平台工程文档重新当作实现目标。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/technical/VISUAL_NOVEL_IMPLEMENTATION_HANDOFF_2026-05-07.md`、`docs/experience/VISUAL_NOVEL_HANDOFF_AND_MAINTENANCE_2026-05-07.md`。
## 2026-05-05 平台移动端一级 Tab 改为推荐/发现/创作/草稿/我的
- 背景:移动端平台入口需要从旧“首页/排行/创作/存档/我的”调整为更直接的推荐流和应用式底部导航。
- 决策:前端内部继续复用 `PlatformHomeTab` 的 `home/category/create/saves/profile` 状态值,但用户看到的一级 Tab 分别为“推荐/发现/创作/草稿/我的”;`home` 直接展示公开推荐流,`category` 承载发现页及排行子 Tab,`saves` 承载草稿作品架,原存档结构并入“我的-玩过”弹层。
- 影响范围:平台入口导航、移动端推荐页、发现页子 Tab、创作中心作品架、个人页玩过弹层、相关设计文档。
- 验证方式:检查移动端底部导航文案和顺序,确认登录态为“推荐/发现/创作/草稿/我的”,未登录态为“推荐/创作/发现”且创作居中;“推荐”无搜索/频道栏直出作品流,“发现”包含搜索/推荐/今日/分类/排行,“创作”只显示新建入口,“草稿”显示作品架,“我的-玩过”可恢复存档。
- 关联文档:`docs/design/PLATFORM_MOBILE_RECOMMEND_DISCOVER_DRAFT_TAB_REDESIGN_2026-05-05.md`。
## 2026-05-14 推荐页卡片主视觉优先于底部作者热区
- 背景:移动端推荐页的卡片底部作者与操作区如果过高,会压缩作品运行态可视高度,影响首屏沉浸感。
- 决策:推荐页卡片底部信息区保持紧凑固定高度,切换手势仍只绑定在该区域;视觉主体高度优先扩展,不再让作者信息区占用过多首屏空间。
- 影响范围:`src/components/rpg-entry/RpgEntryHomeView.tsx` 的推荐页卡片布局,以及 `src/index.css` 中的推荐页卡片热区样式。
- 验证方式:移动端推荐页首屏应明显看到更大的作品内容区,底部作者信息区只保留紧凑一条,不再明显挤压运行态。
- 关联文档:`docs/technical/PLATFORM_MOBILE_RECOMMEND_CARD_SAFE_SWIPE_LAYOUT_2026-05-12.md`。
## 2026-05-05 创作 Tab 固定为智能创作首页,草稿 Tab 承接旧作品架
- 背景:创作首页需要变成面向对话式生成的智能创作页,旧模板卡和作品架继续保留但不应再占据创作首屏。
- 决策:`create` 只承载 `CreativeAgentHome` 智能创作首页与会话流,顶部品牌栏、问候、快捷胶囊、底部输入框和左侧抽屉是主结构;旧的新建作品类型卡不再在 `create` 里展示。原本的 RPG / 拼图 / 大鱼 / Match3D / 方洞 / 视觉小说作品架统一归到 `saves` 草稿 Tab。
- 影响范围:平台创作页布局、创作首页抽屉、草稿页作品架、相关交互测试、旧创作入口 helper。
- 验证方式:移动端点击“创作”直接看到智能创作首页;点击“草稿”看到旧作品架;旧模板入口不再从创作页出现。
- 关联文档:`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`。
## 2026-05-05 创意互动内容生成 Agent 采用 LangChain-Rust 六模块闭环
- 背景:需要支持用户输入文字、图片或文档后,先理解创作意图,再从多个模板候选中选择一个,并把内容填入拼图等目标玩法草稿契约中。
- 决策:新增方案改为基于 LangChain-Rust 的六模块 Agent 架构,核心模块是感知、思考、记忆、行动、反思、协作;首版只支持拼图玩法,但必须先展示多个拼图子模板候选,用户选择某个模板后,再确认该模板下的关卡模式、关卡数和预计积分范围,确认后才生成草稿;Agent 理解、规划和修订统一使用 APIMart Responses `gpt-5` 并支持文本/图像多模态输入;Agent 创作方式就是填充和修订模板草稿字段,表单化创作页与 Agent 自然语言修订都操作同一份 `PuzzleResultDraft`,且草稿可编辑字段只收敛为 `workTitle`、`workDescription`、`workTags`、`levels[].levelName`、`levels[].pictureDescription`、`levels[].pictureReference`;其中 `pictureReference` 已采用 `PuzzleDraftLevel.pictureReference` / Rust `picture_reference` 正式字段方案,不再走 metadata 过渡;单关卡/多关卡图片生成通过拼图模块 Tool 与模板协议实现;生成好的内容必须可立即试玩。
- 影响范围:创作中心入口、`platform-agent`、`module-creative-agent`、`module-puzzle` 拼图模板协议和工具、`shared-contracts`、`api-server` creative facade、SpacetimeDB creative agent 表、拼图玩法工具。
- 验证方式:后续落地时以创意互动内容生成 Agent 技术方案和 Phase 1 PRD 为编码依据,优先完成拼图 Phase 1,并执行 shared contracts、module、platform-agent、api-server、前端 typecheck 与编码检查。
- 关联文档:`docs/technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md`、`docs/prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md`。
## 2026-05-05 creative-agent Task C 首版平台 PoC 已落地
- 背景:Phase 1 的平台侧需要先把 LangChain-Rust 适配层、APIMart `gpt-5` 多模态 Responses 请求和工具注册边界立起来,才能继续接 API facade。
- 决策:新增 `server-rs/crates/platform-agent` 作为独立 workspace crate,保留项目自有 `CreativeAgentExecutor`、工具注册表、回调事件和 mock executor`platform-llm` 的 Responses 请求体扩展为可序列化 `input_text` / `input_image` content part。
- 影响范围:`server-rs/Cargo.toml`、`server-rs/crates/platform-agent`、`server-rs/crates/platform-llm`、任务 C 的后续 API / SSE 接入。
- 验证方式:`cargo check -p platform-agent`、`cargo test -p platform-agent`、`cargo test -p platform-llm responses_multimodal` 已通过。
- 关联文档:`docs/technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md`。
## 2026-05-05 creative-agent Task E API / SSE facade 已落地
- 背景:Phase 1 需要先把创意 Agent 的 HTTP/SSE 门面接入 Rust `api-server`,用于前端工作区调用和拼图模板确认闭环。
- 决策:`api-server` 挂载 `/api/runtime/creative-agent/*` 六个鉴权路由;creative session 在 Task D 表未收口前暂存在 `api-server` 运行态并按 authenticated user 校验 owner;未确认模板前不创建拼图 session`confirm-template` 后才通过既有 `spacetime-client` 创建/编译 `puzzle_agent_session`;当时 `gpt-5` 请求只从 `APIMART_BASE_URL` / `APIMART_API_KEY` 构造专用 Responses client,不复用通用 `GENARRATIVE_LLM_API_KEY`。该 LLM 来源已被 2026-07-05 VectorEngine Chat Completions `gpt-5.4-mini` 决策覆盖。
- 影响范围:`server-rs/crates/api-server/src/creative_agent.rs`、`creative_agent_sse.rs`、`app.rs`、`state.rs`、`module-puzzle` creative template/tool、Phase 1 PRD。
- 验证方式:`cargo check -p api-server`、`cargo test -p module-puzzle creative`、`cargo test -p api-server creative_agent`、`npm run dev:api-server` 后检查 `/healthz`、`POST /api/runtime/creative-agent/sessions`、`POST /api/runtime/creative-agent/sessions/{sessionId}/messages/stream`。
- 关联文档:`docs/prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md`。
## 2026-05-10 视觉小说入口收敛为单句创作 + 画风选择
- 背景:视觉小说入口页要对齐抓大鹅式的线性创作入口,只保留最小可用输入,避免再暴露文档 / 空白 / 对话式工作台。
- 决策:入口页只展示一句话创作输入框和横向视觉画风卡片;画风通过 `seedText` 追加 `视觉画风` 和 `画风要求` 两行透传给既有创作链路;点击生成后先进入 `visual-novel-generating` 过程页,再自动进入 `visual-novel-result`。画风卡片主视觉固定消费 `public/visual-novel-style-references/` 下由 VectorEngine `gpt-image-2` 生成的静态参考图,不在前端运行时现场调用生图接口。
- 影响范围:`VisualNovelAgentWorkspace`、`visualNovelEntryGeneration`、`PlatformEntryFlowShellImpl`、视觉小说 PRD 和创作 Tab 设计文档;不新增后端字段或数据库结构。
- 验证方式:执行 `npm run test -- VisualNovelAgentWorkspace`、视觉小说工作台相关 ESLint、`npx prettier --check` 和 `npm run check:encoding``npm run typecheck` 若失败需先区分是否来自无关 Match3D / RPG 既有改动。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`、`docs/design/PLATFORM_CREATE_TAB_CREATIVE_AGENT_HOME_2026-05-05.md`。
## 2026-05-10 用户标签只做后端白名单投影
- 背景:运营邀请码需要给账号打标签,但标签默认不能暴露到前端通用用户资料;拼图排行榜仅需展示特定标签。
- 决策:`user_account.user_tags` 保存账号标签,数据库默认 `None`,业务按空数组读取;后台预置邀请码使用后授予的标签不再使用独立列,统一存放并解析自 `profile_invite_code.metadata_json.userTags`,兼容读取 `user_tags`。通用登录态和个人资料不返回原始标签。首版只在拼图排行榜 `visibleTags` 中白名单投影 `北科`。
- 影响范围:用户认证表、邀请码后台、邀请兑换事务、拼图排行榜响应和 UI。
- 验证方式:表结构变更需同步 `migration.rs`、`SPACETIMEDB_TABLE_CATALOG.md` 和 SpacetimeDB bindings;后端运行 `cargo check -p api-server`,后台运行 `npm run admin-web:typecheck`。
- 关联文档:`docs/technical/USER_TAG_INVITE_AND_PUZZLE_LEADERBOARD_2026-05-10.md`。
## 2026-05-10 抓大鹅草稿元信息由 gpt-4o 生成
- 背景:抓大鹅草稿生成需要基于入口题材设定生成作品名称,结果页作品信息要对齐拼图草稿,不再把封面和作品名称拆成两个模块。
- 决策:`match3d_compile_draft` 使用 `gpt-4o` 生成 `gameName` 与 3 到 6 个标签;`summary` 默认保持空字符串;标签可由结果页 `作品信息` Tab 手动编辑或再次 AI 生成。草稿生成会按难度产出多视角 2D 物品图片并写入 `generated_item_assets_json`,运行态必须优先消费 `generatedItemAssets[].imageViews[]`,默认积木只做兜底。
- 影响范围:`api-server` Match3D 编译、Match3D works 标签接口、结果页 `作品信息` 与 `素材配置` Tab、运行态 `Match3DRuntimeShell` / `Match3DPhysicsBoard`、生成进度和 Match3D 技术文档。
- 验证方式:执行 `npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run test -- src/services/miniGameDraftGenerationProgress.test.ts`、`cargo test -p api-server match3d --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`,并用 `npm run dev:api-server` 检查 `/healthz`。
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md``docs/technical/MATCH3D_RODIN_ASSET_TAB_2026-05-10.md` 仅作历史参考。
## 2026-05-12 抓大鹅物品种类从消除次数中拆出并改为 2D 五视角素材
- 背景:结果页草稿素材已经能生成和预览,但标准 / 硬核难度仍可能按 `clearCount` 误判需要 12 / 20 种素材,且继续生产 GLB 会拉长草稿生成耗时。
- 决策:难度配置统一使用运行态 `物品种类`:轻松 3、标准 9、进阶 15、硬核 20;历史硬核 `clearCount=20` 在运行态仍升为 21 组三消,但类型池最多 20 种。新草稿和批量新增不再调用 Rodin、不再生成 GLB。每次固定从 `2K 1:1`、`10*10` 物品 spritesheet 解析并持久化 20 个物品、每个 5 个不同 2D 形态,物品信息列表全部展示 20 个;持久化行列索引按每行两种物品计算,不能超过 `1..=10`。发布必须校验已生成 `image_ready` 且有 `imageViews[]`、首图引用或可解析的物品 spritesheet 满足当前难度;试玩通过 `itemTypeCountOverride` 自动降到可用 2D 素材数量。历史模型字段只作为旧数据兼容,不再进入新生产链路。
- 影响范围:Match3D 结果页、运行态启动契约、`module-match3d` 初始 run 生成、SpacetimeDB start input / restart、发布校验和 Match3D 技术文档。
- 验证方式:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`cargo test -p module-match3d --manifest-path server-rs\Cargo.toml`、相关后端 check / tests。
- 关联文档:`docs/technical/MATCH3D_DRAFT_ASSET_GENERATION_PIPELINE_2026-05-10.md`。
## 2026-05-07 移动端整页缩放由入口统一锁定
- 背景:移动端游戏式页面如果允许浏览器整页缩放,容易把固定画布、HUD 和底部操作区一起放大或缩小,破坏操作节奏。
- 决策:主站入口统一使用 `viewport` 锁定 `minimum-scale=1.0`、`maximum-scale=1.0`、`user-scalable=no` 和 `viewport-fit=cover`,并在应用启动时调用 `lockMobileViewportZoom()` 拦截 iOS `gesture*` 与多指 `touchmove` 触发的页面级缩放。
- 影响范围:主站 `index.html`、`src/main.tsx`、后续所有依赖主入口的移动端游戏/画布页面;不要求每个画布组件重复实现缩放锁定。
- 验证方式:移动端打开主站后,双指捏合和快速双击不应再缩放整页;单指滚动、点击和组件内交互保持正常。
- 关联文档:`docs/experience/MOBILE_UI_DEV_EXPERIENCE.md`。
## 2026-05-07 视觉小说 VN-10 资产引用统一走平台资产对象
- 背景:视觉小说文档输入、封面、场景背景、角色立绘和音乐需要接入平台资产链路,不能在前端状态或 SpacetimeDB 中保存大 Data URL、二进制对象或外部 R2 路径。
- 决策:VN 上传统一复用 `/api/assets/direct-upload-tickets`、OSS 直传、`/api/assets/objects/confirm`、`/api/assets/read-url`。文档上传后只把 `assetObjectId` 放入 `sourceAssetIds``seedText` 仅放截断摘要;封面、场景、角色、音乐只写 `/generated-*` 引用和平台 asset id。角色立绘写入 `imageAssets[].source = platform_asset`。运行时图片渲染统一使用 `ResolvedAssetImage` 换签。
- 影响范围:`src/services/visual-novel-creation/visualNovelAssetClient.ts`、`VisualNovelAgentWorkspace`、`VisualNovelResultView`、`VisualNovelRuntimeShell`、`server-rs/crates/api-server/src/visual_novel.rs`。
- 验证方式:VN 定向前端测试、`npm run typecheck`、`npm run check:encoding`、`cargo test -p api-server visual_novel`、`cargo test -p api-server creation_agent_document_input`。
- 关联文档:`docs/prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md`。
## 2026-05-04 建立仓库级项目知识与工具边界
- 背景:团队有 3 名开发人员,需要独立拉取仓库、修改代码和本地测试,同时共享稳定的项目知识与工具约定。
- 决策:长期项目知识统一保存在 `docs/project-memory/`;仓库内 `.codex/` 仅保存可 Git 同步的 Codex skills、插件资源、hooks 和配置模板;个人 `~/.codex` 始终保持本机私有。
- 影响范围:`AGENTS.md`、`.codex/README.md`、`docs/project-memory/shared-memory/`。
- 验证方式:任一开发者拉取仓库后,先读 `AGENTS.md`,即可按入口读取同一套 `docs/project-memory/shared-memory/` 和 `.codex/skills/`。
- 关联文档:`.codex/README.md`、`docs/project-memory/shared-memory/team-conventions.md`。
## 2026-04-25 后端唯一落地口径固定为 Rust / SpacetimeDB
- 背景:项目经历过 Node/Express/PostgreSQL、Go 试验、Rust/SpacetimeDB 等多条后端路线,旧路线文档容易造成开发歧义。
- 决策:新功能以后端当前基线为准:HTTP 门面使用 Rust `api-server` / Axum,业务真相使用 SpacetimeDB,领域和契约在 `server-rs` 多 crate 分层维护。
- 影响范围:所有后端、数据真相、运行时状态、创作结果、用户系统、资产、任务、埋点、后台 API 等相关开发。
- 验证方式:开发前优先阅读 `CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`;旧 `server-node`、Express、PostgreSQL、Go 方向只允许作为迁移参考。
- 关联文档:`docs/technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md`、`AGENTS.md`。
## 2026-05-18 寓教于乐电视端入口概念图采用横屏乐园地图方案
- 背景:寓教于乐板块需要面向电视端 / 横屏大屏的一组图形化入口概念图,既要像儿童乐园地图,又要和现有绘本插画风一致。
- 决策:概念探索优先采用横屏乐园地图结构,推荐顺序为环形乐园岛、展开绘本地图、云朵空中岛、草地舞台地图;生成时优先复用 `public/child-motion-demo/picture-book-grass-stage.png` 作为风格参考,输出仅保留在 `output/imagegen/` 概念目录中,不直接进入正式资源目录。
- 影响范围:寓教于乐板块视觉探索、后续前端入口设计、`scripts/generate-edutainment-tv-map-concepts.mjs`、相关设计文档。
- 验证方式:概念图需保持无文字、无真实品牌 IP、无暗色科技风,并与现有草地绘本资源在配色和笔触上保持一致。
- 关联文档:`docs/design/【前端体验】寓教于乐电视端乐园地图入口概念图-2026-05-18.md`。
## 2026-04-28/29 server-rs DDD 分层与契约矩阵冻结
- 背景:server-rs 模块多、上下文多,需防止领域规则、SpacetimeDB 表、HTTP BFF、前端临时逻辑互相污染。
- 决策:按 DDD 总纲和 G1 契约/路由矩阵开发:`module-*` 承载领域,`spacetime-module` 承载表和事务,`spacetime-client` 承载 facade`api-server` 承载 HTTP/SSE/BFF`platform-*` 承载外部副作用,`shared-contracts` 承载 DTO。
- 影响范围:server-rs 全部 crate、前端 API client、SpacetimeDB schema、旧接口清理。
- 验证方式:执行任务前对照 DDD 总纲、并行任务清单、G1 矩阵;提交前运行相关 DDD 边界检查和定向测试。
- 关联文档:`SERVER_RS_DDD_FULL_REFACTOR_2026-04-28.md`、`SERVER_RS_DDD_G1_CONTRACT_AND_ROUTE_MATRIX_2026-04-29.md`、`SERVER_RS_DDD_PARALLEL_TASKLIST_2026-04-29.md`。
## SpacetimeDB 表结构变更必须显式维护迁移与表目录
- 背景:SpacetimeDB 的 schema 迁移模型不同于 PostgreSQL,部分变更会触发冲突或拒绝自动迁移。
- 决策:凡涉及 table、reducer、procedure、row shape 或 binding 变化,必须同步 `migration.rs`、表目录和生成绑定;涉及 private 表迁移时按 JSON 导入导出和分片导入流程处理。
- 影响范围:`server-rs/crates/spacetime-module`、`spacetime-client` bindings、`SPACETIMEDB_TABLE_CATALOG.md`、部署/发布脚本。
- 验证方式:发布前检查 `SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md` 清单,更新 `SPACETIMEDB_TABLE_CATALOG.md`,执行生成绑定和相关测试。
- 关联文档:`SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`、`SPACETIMEDB_TABLE_CATALOG.md`、`SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md`。
## 生产部署切换到 systemd + Nginx + 自托管 SpacetimeDB
- 背景:旧一体化启动脚本和历史 Jenkinsfile 已不再是生产发布唯一入口。
- 决策:生产部署以 systemd 托管 SpacetimeDB 与 Rust `api-server`Nginx 负责站点和代理,生产 Jenkinsfile 按 web/api/stdB module/build/deploy/publish 拆分。
- 影响范围:部署脚本、服务器目录、维护模式、Jenkins、Nginx、systemd 服务。
- 验证方式:生产发布、服务器配置、Jenkins Job 重建或回滚时,先看 `PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
- 关联文档:`PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。
## 2026-05-19 release server provision 需预装 Nginx Brotli 动态模块
- 背景:release 服务器的 Nginx 站点配置已经预留 Brotli 指令占位,但当前 provision 流程只装了基础构建依赖,没有把 Ubuntu apt 下的 brotli 动态模块一起装上,导致 release 机器即使模板支持也可能无法启用 Brotli。
- 决策:`scripts/jenkins-server-provision.sh` 在 apt 系统上额外安装 `libnginx-mod-http-brotli-filter` 与 `libnginx-mod-http-brotli-static`,然后继续用会先 `include /etc/nginx/modules-enabled/*.conf` 的临时 `nginx -t` 配置做能力探测;非 apt 系统仍只做探测不强制安装。不要用 `nginx -V` 判断该动态模块是否可用。
- 影响范围:`jenkins/Jenkinsfile.production-server-provision`、`scripts/jenkins-server-provision.sh`、`deploy/nginx/README.md`、release 服务器 Nginx 初始化。
- 验证方式:server provision 跑过后,目标机应同时具备 Brotli 模块包与 `nginx -t` 可接受的 brotli 指令;再由 Nginx 模板启用对应指令。
- 关联文档:`deploy/nginx/README.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-05-19 server provision 下载件固定由 Windows 节点断点续传
- 后续更新:该口径已被 `2026-06-01 生产 Jenkins 流水线统一改为 Linux 优先并先查 localhost` 取代;当前不再维护 Windows 下载阶段和 `.download` 断点续传 helper。
- 背景:`SpacetimeDB` 和 `otelcol-contrib` release 资产在 Linux 目标机直接下载很慢;改到 Windows Jenkins 节点下载后,GitHub 大文件仍可能出现 `curl: (18)` 响应体截断。
- 决策:`Genarrative-Server-Provision` 的 `Download Provision Tool Archives` 阶段继续只在 Windows 节点下载,再通过 `stash/unstash` 交给目标 Linux agent;下载前查 GitHub release asset `digest`,本地最终文件 SHA256 命中即跳过,`.download` 临时文件用于 `curl -C -` 断点续传,完整返回但 digest 不匹配才清理重下。
- 影响范围:`jenkins/Jenkinsfile.production-server-provision`、目标机 `scripts/prepare-server-provision-tools.sh` 的本地下载件消费路径、生产 provision 运维排障。
- 验证方式:Windows 下载日志应出现 digest 查询、已存在校验跳过或 `curl 断点续传`;Linux 目标机阶段只使用 `provision-tool-downloads/` 中的 tarball,不访问 GitHub 下载地址。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-06-01 生产 Jenkins 流水线统一改为 Linux 优先并先查 localhost
- 2026-06-19 更新:本节中“localhost 优先并回退公网域名”的 Git 源决策已被 `2026-06-19 Jenkins Git 源统一为 genarrative-station` 替代,保留此节仅作历史背景。
- 背景:生产流水线长期混用 Windows、Linux 和公网 Git 入口,导致构建 / 发布 / provision 的 checkout 口径分叉;同时 `Genarrative-Server-Provision` 还残留过 Windows 下载 helper,和当前 Linux 构建 / 发布部署路径不一致。
- 决策:生产 Jenkins 流水线统一把执行节点收口到 Linux label`Pipeline script from SCM` 仍保留公网域名,但所有生产流水线首次 `GitSCM checkout` 先尝试 `http://127.0.0.1:3000/GenarrativeAI/Genarrative.git`,失败后再回退到 `https://git.genarrative.world/GenarrativeAI/Genarrative.git``Genarrative-Stdb-Module-Build`、`Genarrative-Server-Provision`、`Genarrative-Notify-Email` 也都切到 Linux 节点。`Genarrative-Server-Provision` 的工具准备不再依赖 Windows helper,而是在 Linux build 节点直接生成 `provision-tools/` 后交给后续 Linux 发布阶段。
- 影响范围:`jenkins/Jenkinsfile.production-*`、`scripts/jenkins-checkout-source.sh`、`scripts/prepare-server-provision-tools.sh`、生产运维文档。
- 验证方式:扫描 Jenkinsfile 时应看到 `linux && genarrative-*` 节点和 localhost-first checkout 口径;`Genarrative-Server-Provision` 日志不再出现 Windows 相关 helper 输出,工具准备阶段应直接生成 `provision-tools/`。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-06-01 Web Deploy 只从 Jenkins 构建归档取发布包
- 背景:`Genarrative-Web-Deploy` 曾在发布阶段读取构建机本地缓存目录,release 目标还可能通过 `rsync` 回构建机拉取 `web.tar.gz`,导致发布依赖机器拓扑和本地路径。
- 决策:`Genarrative-Web-Build` 直接归档 `build/<version>/web.tar.gz`、`web.tar.gz.sha256` 和 `release-manifest.json``Genarrative-Web-Deploy` 只使用 Jenkins `copyArtifacts` 从指定上游构建复制完整 Web 发布包,不再维护 `WEB_ARTIFACT_ROOT`、`WEB_ARTIFACT_SYNC_HOST` 或 `web-artifact-pointer.txt`。
- 影响范围:`jenkins/Jenkinsfile.production-web-build`、`jenkins/Jenkinsfile.production-web-deploy`、Web 发布排障流程。
- 验证方式:deploy 工作区直接存在 `build/<version>/web.tar.gz`、`web.tar.gz.sha256` 和 `release-manifest.json`,随后由 `scripts/deploy/production-web-deploy.sh` 校验 checksum 并解压发布。
- 关联文档:`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 个人任务与埋点首版边界冻结
- 背景:“我的”Tab、任务、奖励、钱包和埋点涉及用户、运营、分析多条链路,需要避免范围泛化。
- 决策:埋点原始事实进入 `tracking_event`,聚合投影进入 `tracking_daily_stat`;个人任务配置/进度/领奖/钱包分别进入 `profile_task_config`、`profile_task_progress`、`profile_task_reward_claim`、`profile_wallet_ledger`;首版个人任务 scope 仅支持 `user`。
- 影响范围:用户侧任务中心、后台任务配置、运营查询、埋点查询、钱包流水。
- 验证方式:非 `user` scope 的个人任务配置应被 API 和领域构造层拒绝;任务查询与埋点查询分别放在 `docs/operations/` 和 `docs/tracking/`。
- 关联文档:`PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`、`RUNTIME_PROFILE_TASK_SCOPE_2026-05-04.md`、`ANALYTICS_DATE_DIMENSION_IMPLEMENTATION_2026-05-04.md`。
## 普通 route tracking 先写本机 outbox 再批量入库
- 背景:公开作品列表压测中,成功响应后的全局 route tracking 会逐条调用 SpacetimeDB,导致数据库内存和事务压力先到边界。
- 决策:普通 HTTP route tracking 先写入 `api-server` 本机 NDJSON outbox,后台按数量或时间阈值批量调用 SpacetimeDB`daily_login`、`work_play_start`、支付、任务领奖、钱包等关键事件保持同步直写。
- 默认阈值:每批 500 条或 1 秒 flush 一次;outbox 磁盘上限 256 MiB,超过后丢弃低价值 route 事件并记录指标 / 日志。
- 影响范围:`api-server` tracking 中间件、SpacetimeDB tracking procedure、部署数据目录、OTLP 指标和运维排障。
- 验证方式:数据库不可用时公开 route 请求不失败且 outbox 文件保留;恢复后批量写入成功并删除本地 sealed 文件;关键事件仍立即影响任务 / 统计。
## 2026-05-19 跳一跳平台公开链路采用独立玩法路由
- 背景:跳一跳玩法已接入平台入口、推荐、公开详情、试玩和运行态,后续继续扩展公开广场或推荐流时需要避免把它当成拼图兼容分支。
- 决策:跳一跳公开路由统一依赖 `sourceType='jump-hop'` 和 `JH-*` public code;平台首页、推荐、公开作品列表/详情、试玩和运行态都按 `jump-hop` 独立玩法分发。后端仍是作品、运行和发布状态的业务真相,前端只做展示、交互和临时 UI 状态,不在页面层补业务规则或权限判断。
- 影响范围:平台入口、推荐流、公开详情、试玩启动、跳一跳运行态、`api-server` / SpacetimeDB 公开投影和 shared contracts。
- 验证方式:从平台推荐或公开详情进入跳一跳作品时,路由 source type 为 `jump-hop`、public code 为 `JH-*`,运行态启动消费后端返回的完整 profile / run 数据;后端 smoke 统一使用 `npm run dev:api-server` 启动并检查 `/healthz`。
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-05-28 跳一跳重设计为 5x5 地块图集与弹弓拖拽
- 背景:旧跳一跳模板仍保留角色生图、有限路径、score/combo 和 `2x3` 地块图集口径,和当前“俯视角平台跳跃 + 主题生成地块池 + 无限路径”的产品需求不一致。
- 决策:`jump-hop` v1 创作端只保留主题输入;image2 生成一张 `5x5`、共 25 个 2D 地块图标的图集,后端按均匀网格切出 25 个 `JumpHopTileAsset`。角色不再单独生图,运行态使用陶泥儿 logo 透明 PNG 角色;运行态输入为按住后拉蓄力、松手反向弹出,前端提交 `chargeMs + dragVectorX + dragVectorY`,后端裁决落点。草稿试玩必须使用 `runtimeMode=draft`,正式作品使用 `runtimeMode=published`;排行榜按作品维度每玩家只保留 1 条最佳记录,排序为成功跳跃次数降序、游戏时长升序、更新时间升序。
- 决策补充:跳一跳创作入口的事实源仍是 SpacetimeDB `creation_entry_type_config`。默认种子和旧默认行都必须同步迁移到 `subtitle=主题驱动平台跳跃`、`image_src=/creation-type-references/jump-hop.webp`;后端只在系统默认旧值命中时自动纠偏,避免覆盖后台手动配置。
- 影响范围:`jump-hop` PRD、`api-server` 生成编排、`module-jump-hop` 领域规则、`spacetime-module` / `spacetime-client` 跳一跳契约、前端工作台 / 结果页 / runtime / 平台壳调用链。
- 验证方式:`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`、`cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`、跳一跳工作台和 runtime 定向前端测试。
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-01 跳一跳运行态地块视觉尺寸放大与命中 footprint 分离
- 背景:当前跳一跳运行态里地块视觉尺寸偏小,玩家反馈“很难跳上去”,但仅放大前端展示会造成画面和后端裁决脱节。
- 决策:`jump-hop` 运行态的地块视觉尺寸、`width/height` 玩法世界尺寸以及 `landingRadius/perfectRadius` 同步乘以 2;前端平台渲染抽成统一尺寸 helper,保证单测可以直接校验放大结果。后续校正:正式命中只看下一块可见顶面 footprint,不能让已 `2x` 归一化的 `width/height` 再把命中区二次放大;当前 footprint 使用归一化后宽度 28% / 高度 18% 的菱形,相当于旧未放大视觉规格的 56% / 36%,地块侧面、阴影和外沿不算正确落点。
- 影响范围:`server-rs/crates/module-jump-hop/src/application.rs`、`src/services/jump-hop/jumpHopRuntimeModel.ts`、`src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`、对应定向测试。
- 验证方式:`npm test -- src/services/jump-hop/jumpHopRuntimeModel.test.ts src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx`、`cargo test -p module-jump-hop --manifest-path server-rs/Cargo.toml -- --nocapture`。
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-02 跳一跳起跳距离减半并加入飞行动画缓冲
- 背景:用户反馈长按蓄力版本的跳跃手感偏硬,成功后角色容易被吸回地块中心,且后端回包或相机推进时会出现飞过很远再瞬间拉回的闪现。
- 决策:`jump-hop` 当前长按蓄力统一使用 `chargeToDistanceRatio=0.004`,相同蓄力时间的世界跳跃距离比上一轮 `0.008` 降低一半。前端 runtime 把“后端真实 run”和“当前屏幕显示态”拆开,松手瞬间先生成 `visualJump`,用当前角色位置作为起点、前端预测真实落点作为终点,播放约 `560ms` 的飞行动画;该路径不得等待后端新 run。角色弹到预测真实落点后若新 run 尚未返回,必须停在预测真实落点等待。成功落地后角色位置必须保留 `lastJump.landedX/landedY` 映射出的真实偏移,不得吸附回目标地块中心;飞行动画结束后保留约 `300ms` 落地停顿,再启动相机推进。相机推进以旧窗口真实落点和新窗口真实落点为锚点,使用约 `1440ms` 过渡;推进期间地块 DOM 层和 DOM 角色层统一包在同一个 camera layer 下移动,旧当前地块自然离开视野,新预览地块从上方露出,避免 p1/p2 单独 top/left 过渡导致角色和地块不同步。相机推进必须同时使用 X/Y 偏移,不能先横向瞬切居中再纵向推进。地块保留当前 / 目标 / 预览的深度尺寸差异,但该差异通过固定基准宽高上的 CSS transform scale 表达,并在相机推进期间同样使用 `1440ms` 缓动;当前态不再额外叠 CSS scale。
- 影响范围:`server-rs/crates/module-jump-hop/src/application.rs`、`src/services/jump-hop/jumpHopRuntimeModel.ts`、`src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`、跳一跳运行态定向测试。
- 验证方式:`npm test -- src/services/jump-hop/jumpHopRuntimeModel.test.ts src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx`、`cargo test -p module-jump-hop --manifest-path server-rs/Cargo.toml -- --nocapture`、`npm run check:encoding`。
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-03 跳一跳角色形象改为陶泥儿 logo 透明 PNG
- 背景:跳一跳运行态此前仍使用旧内置 / CSS 角色形象,和用户要求的陶泥儿 logo 角色不一致,也容易和 DOM 地块层出现遮挡层级问题。
- 决策:`jump-hop` v1 不再渲染内置 3D 角色几何体;运行态和结果页统一使用 `public/branding/jump-hop-taonier-character.png`,该文件由陶泥儿 logo 处理为透明 PNG 后接入。蓄力时角色沿拖拽方向明显拉长,落地后向反方向回弹两次。`characterAsset` 继续仅作为历史兼容描述字段,不能重新打开角色生图槽或把角色图片作为创作者可配置输入。
- 影响范围:`src/components/jump-hop-runtime/JumpHopRuntimeShell.tsx`、`src/components/jump-hop-result/JumpHopResultView.tsx`、跳一跳 PRD 和平台链路文档。
- 验证方式:跳一跳运行态 / 结果页测试需要断言角色图片 src 为 `/branding/jump-hop-taonier-character.png`,并确认旧默认角色 fallback 不再出现。
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-12 跳一跳地块间距以当前最远视觉距离为上限随机
- 背景:跳一跳服务端路径已有随机距离雏形,但前端可见窗口把目标地块固定投影到 `47%` 屏幕高度,导致用户看到的地块间距仍像固定值,无法调出“近到远”的节奏变化。
- 决策:各难度当前 `max_gap` 保持为最大世界间距,最小间距固定为 `max_gap * 55%`,服务端按 seed 在该非零区间内随机生成下一块;前端 `buildJumpHopVisiblePlatforms` 必须用相邻地块真实世界距离缩放屏幕 X/Y 投影,最大距离沿用当前最远 45 度视觉位置,较近距离沿同一 45 度方向靠近当前块,不能再把目标块强制固定在同一屏幕坐标。
- 影响范围:`server-rs/crates/module-jump-hop/src/application.rs`、`src/services/jump-hop/jumpHopRuntimeModel.ts`、跳一跳运行态测试、PRD 和平台玩法链路文档。
- 验证方式:`cargo test -p module-jump-hop --manifest-path server-rs/Cargo.toml -- --nocapture`、`npm run test -- src/services/jump-hop/jumpHopRuntimeModel.test.ts src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx`。
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
# 2026-05-20 陶泥儿主视觉配色回收为暖白/陶土橙
- 背景:用户要求只替换产品各界面的 UI 颜色,不改布局,并以两张陶泥儿主视觉图作为配色依据。
- 决策:平台亮色主题的主色回收到暖白 / 米杏底、陶土橙主按钮、深棕正文与浅杏边框;后台管理也同步切换到同一暖橙体系。主题变量和平台字体栈优先通过 `packages/shared/src/theme.css` 的 `--platform-*` token 统一控制,具体全局字体选择器留在消费端原有层叠位置,零散组件只做必要的局部替换。
- 影响范围:主站平台壳层、常用表单 / 按钮 / 卡片 / 背景、后台管理 UI、业务进度条和小游戏结果条的通用强调色。
- 验证方式:优先检查 `src/index.css` 与 `apps/admin-web/src/styles/admin.css` 是否还存在旧粉色主色;再用编码检查和可执行的本地 typecheck / build 验证。
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
## 2026-05-20 汪汪声浪 v1 公开闭环计划
- 背景:Bark Battle v1 需要把创作、生成、结果、发布、详情和正式运行态收成一条闭环,避免把草稿试玩、公开广场和正式成绩混在一起。
- 决策:`bark-battle` 入口改为 6 字段表单(作品标题、简介、主题 / 竞技背景描述 `themeDescription`、玩家形象描述、对手形象描述、难度);提交后进入 `bark-battle-generating` 独立生成页,自动生成玩家形象、对手形象和竞技背景三图,部分失败也继续进入结果页。旧“角色设定 / 狗狗皮肤预设 / themePreset”统一退场,配置和文档只使用“形象描述 / themeDescription”。结果页只保留单槽重试、重新生成和上传,不再保留一次生成按钮、音频配置入口、皮肤预设入口或排名配置。发布后先跳统一作品详情页 `/works/detail?work=BB-xxxxxxxx`,再由详情页进入正式 `published` runtime;正式 runtime 必须真实麦克风,`draft` 可试玩、可 mock 且不写正式统计。公开广场统一读取 `bark_battle_gallery_view` read model。
- 影响范围:`BarkBattleConfigEditor`、`BarkBattleGeneratingView`、`BarkBattleResultView`、`BarkBattleRuntimeShell`、`PlatformEntryFlowShellImpl`、`appPageRoutes`、Bark Battle creation/runtime client、公开广场聚合与相关交互测试。
- 验证方式:提交表单后先进入生成页;生成页部分失败仍能落到结果页;结果页只出现单槽重试 / 重新生成 / 上传;发布后先到 `/works/detail?work=BB-xxxxxxxx` 再进正式 runtime;正式 runtime 会要求麦克风并写基础统计,草稿试玩可 mock 且不写正式 run;公开广场读取 `bark_battle_gallery_view`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-22 汪汪声浪运行态与作品外显信息收口
- 背景:Bark Battle v1 在正式运行态、图片生成提示词和作品外部卡片上仍存在体验漂移:能量条推满后还要等计时结束、进入正式 runtime 后还要二次点击声控、角色形象 prompt 会默认注入狗主体、草稿 / 已发布卡片外部看不到创作者。
- 决策:能量条到玩家或对手边界即结算;正式 `published` runtime 从作品详情启动后立即申请真实麦克风权限,授权成功后立刻进入倒计时,并使用 start run 返回的 `runtimeConfig` 作为本局前端规则参数;结束后弹出独立结算弹窗,运行态固定提供返回按钮。玩家 / 对手形象图提示词保持用户填写的形象描述,只要求单个完整形象、正面和透明背景,不把非狗描述改写成狗;草稿架、已发布作品架、统一作品详情和公开广场列表都展示后端返回的 `authorDisplayName`。Bark Battle 卡片封面按竞技背景、玩家形象、对手形象、入口参考图兜底;works summary 优先读取 `publishedSnapshotJson` 的最终发布素材。拟声词进入配置 JSON,未手动编辑时随主题 / 形象描述重算,手动编辑后保持创作者自定义;触发阈值降到 `0.35`、冷却降到 `150ms`,后端 `BarkBattleRuleset.min_bark_gap_ms` 同步为 `150`,局内有效触发后快速随机展示高能词池。
- 影响范围:`BarkBattleSession`、`BarkBattleRuntimeShell`、`BarkBattleConfigEditor`、`BarkBattleConfig`、Bark Battle 生图 prompt、Bark Battle works/gallery summary、创作中心作品架卡片、公开作品码、`module-bark-battle` ruleset 和玩法链路文档。
- 验证方式:能量条推到 `100/-100` 的领域测试应提前 finished;发布态 runtime mount 后应自动调用麦克风 sampler、登记正式 run 并使用服务端 runtimeConfig;prompt 单测应覆盖透明背景、正面和非狗描述不强注入狗;作品架测试应覆盖草稿与已发布卡片作者展示和封面兜底;拟声词测试应覆盖主题自动重算、自定义保持和随机展示。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-19 汪汪声浪默认开放并区分草稿试玩与正式运行态
- 背景:`bark-battle` 已具备草稿结果页、发布链路与运行态 API,继续在入口层标记“敬请期待”会阻断创作闭环;同时草稿试玩不应污染正式成绩统计。
- 决策:默认入口改为 `visible=true`、`open=true`、`badge=可创建`,参考图固定为 `/creation-type-references/bark-battle.webp`。系统默认迁移只纠偏未被后台人工改过的汪汪声浪入口。发布后先进入统一作品详情页 `/works/detail?work=BB-xxxxxxxx`;正式 runtime 使用 `runtimeMode=published` 并必须真实麦克风,调用 `startBarkBattleRun` / `finishBarkBattleRun` 写正式 run;草稿结果页试玩仍使用 `runtimeMode=draft`,允许 mock 且不写正式 run。
- 验证方式:入口配置响应应返回汪汪声浪可创建和专属参考图;发布后地址应为 `/works/detail?work=BB-xxxxxxxx`;草稿试玩不调用 runtime run API;正式 runtime 无麦克风时不登记正式 run,结算后提交派生指标。
## 2026-05-20 汪汪声浪生成页负责三图自动生成
- 背景:结果页承载预览、修补和发布,若继续放“一次生成”按钮会把初始生成和结果修补职责混在一起。
- 决策:初始三图生成改由 `bark-battle-generating` 独立生成页自动执行,目标槽位只有玩家形象、对手形象和竞技背景;表单术语统一为 `themeDescription`、玩家形象描述和对手形象描述,不再回退 `themePreset`、狗狗皮肤预设或“角色设定”。部分失败也进入结果页。结果页不再提供一次生成按钮,音频配置和排名配置不进入 v1 公开闭环;结果页只保留单槽重试、重新生成和上传。发布时 SpacetimeDB `bark_battle_published_config.config_json` 使用规范化后的最终 `publishedSnapshot``published_snapshot_json` 同步保存同一份快照。
- 验证方式:表单提交后进入 `bark-battle-generating`;结果页不会出现一次生成按钮、音频槽、皮肤预设入口或排名配置;Bark Battle 发布后正式 runtime 应读取结果页最终图片素材而不是初始草稿素材。
## 2026-05-24 敲木鱼结果页先补录作品信息再试玩 / 发布
- 背景:敲木鱼工作台只应保留生成所需输入,作品标题、简介和主题标签适合放在生成草稿后的补录阶段。
- 决策:敲木鱼的 `workTitle`、`workDescription` 和 `themeTags` 从工作台首屏移到结果页;结果页编辑后在试玩或发布前先调用 `update-work-meta` 写回当前作品信息。主题标签编辑样式对齐拼图结果页的胶囊标签编辑器。
- 影响范围:敲木鱼统一创作工作台、`WoodenFishResultView`、`PlatformEntryFlowShellImpl`、敲木鱼 PRD 和平台入口链路文档。
- 验证方式:工作台首屏不再出现标题 / 简介 / 标签输入;结果页修改后点试玩或发布会先写回当前作品信息。
- 关联文档:`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-03 Profile Dashboard Presentation 收口
- 背景:`RpgEntryHomeView.tsx` 同时承载个人数据卡、钱包 chip 与“玩过”弹窗,计数压缩、累计时长、单作品时长、玩法标签和作品号兜底散在页面 Implementation 内,修改展示口径时缺少稳定测试面。
- 决策:新增 `src/components/rpg-entry/rpgEntryProfileDashboardPresentation.ts` 作为个人数据展示 ModuleInterface 收口为 `buildProfileDashboardPresentation`、计数 / 时长格式化和“玩过”列表标签 / 作品号格式化函数;页面只消费结果并保留 UI 编排与点击处理。
- 影响范围:RPG 首页“我的数据”卡片、移动端 / 桌面端钱包 chip、个人数据弹窗与“玩过”列表。
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryProfileDashboardPresentation.test.ts`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】ProfileDashboardPresentation收口计划-2026-06-03.md`。
## 2026-06-03 Recommend Feed ViewModel 收口
- 背景:推荐 feed 与正式 runtime 的上一条 / 下一条选择分别在 `RpgEntryHomeView.tsx` 和 `PlatformEntryFlowShellImpl.tsx` 手写公开作品去重、隐藏内容过滤、active key 兜底和相邻回环,存在推荐预览与 runtime 口径漂移风险。
- 决策:在 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts` 追加推荐 feed Module Interface`dedupePlatformPublicGalleryEntries`、`buildPlatformRecommendFeedEntries`、`selectPlatformRecommendFeedWindow`、`selectAdjacentPlatformRecommendEntry`;首页与 FlowShell 均消费该 Interface。
- 影响范围:移动端首页推荐 swipe、发现页推荐频道、桌面推荐格、推荐 runtime 队列与上一条 / 下一条跳转。
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recommend|edutainment"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "logged out home recommendation next starts the next puzzle work"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】RecommendFeedViewModel收口计划-2026-06-03.md`。
## 2026-06-03 Recommend Swipe Deck Model 收口
- 背景:移动端推荐首页 swipe deck 的拖拽阈值、offset clamp、commit 方向、rail class 和分享文案仍留在 `RpgEntryHomeView.tsx` 页面 Implementation 内,页面同时承载 DOM pointer 副作用和纯规则。
- 决策:新增 `src/components/rpg-entry/rpgEntryRecommendSwipeDeckModel.ts` 作为 Recommend Swipe Deck ModuleInterface 收口 `hasRecommendDragStarted`、`clampRecommendDragOffset`、`resolveRecommendDragCommitDirection`、`resolveRecommendCommitOffset`、`buildRecommendSwipeRailClassName`、`shouldAnimateRecommendSwipe` 与 `buildRecommendShareText`;页面仅保留 pointer capture、DOM 高度读取、动画 timer、clipboard 与 like/remix/open 副作用 Adapter。
- 影响范围:移动端推荐首页 swipe 手势、上一条 / 下一条动画、推荐分享文案与未登录时的直接切换行为。
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryRecommendSwipeDeckModel.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recommend|edutainment"`、`npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts -t "recommend"`、针对新 Module 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】RecommendSwipeDeckModel收口计划-2026-06-03.md`。
## 2026-06-03 Ranking ViewModel 收口
- 背景:排行 tab 的文案、metric label 与空态文案在 `RpgEntryHomeView.tsx`,排序和 metric value 在 `rpgEntryPublicGalleryViewModel.ts`,同一 `PlatformRankingTab` 的 Interface 分散且页面需要类型断言取 active config。
- 决策:在 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts` 收口 `DEFAULT_PLATFORM_RANKING_TAB`、`PLATFORM_RANKING_TABS`、`getPlatformRankingTabConfig` 与 `getPlatformRankingMetric`;页面仅保留 active tab 状态和渲染。
- 影响范围:发现页排行频道 tab 顺序、tab 文案、空态文案、排行项指标 label/value。
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "bottom category tab becomes ranking and switches ranking metrics|ranking"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】RankingViewModel收口计划-2026-06-03.md`。
## 2026-06-03 Category Option ViewModel 收口
- 背景:分类频道的筛选选项、排序选项、默认值、active label fallback 和排序循环仍留在 `RpgEntryHomeView.tsx` 页面 Implementation 内,而玩法过滤、排序和主指标已经在 `rpgEntryPublicGalleryViewModel.ts`,同一分类 Interface 被拆成两处。
- 决策:在 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts` 收口 `DEFAULT_PLATFORM_CATEGORY_KIND_FILTER`、`DEFAULT_PLATFORM_CATEGORY_SORT_MODE`、`PLATFORM_CATEGORY_KIND_FILTERS`、`PLATFORM_CATEGORY_SORT_OPTIONS`、`getPlatformCategoryKindFilterOption`、`getPlatformCategorySortOption` 与 `getNextPlatformCategorySortMode`;页面仅保留当前筛选 / 排序状态和渲染。
- 影响范围:发现页分类频道筛选弹窗、筛选按钮 label、排序按钮 label 与排序循环。
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryPublicGalleryViewModel.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "category"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md`。
## 2026-06-03 Match3D Runtime Profile 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 内仍直接承载抓大鹅公开详情转 work、session draft 转 profile、生成背景资产提升、runtime active profile 选择和 run / profile / public detail 素材优先级,平台壳需要理解抓大鹅生成素材内部结构。
- 决策:新增 `src/components/platform-entry/platformMatch3DRuntimeProfile.ts` 作为抓大鹅 runtime profile ModuleInterface 收口 `mapPublicWorkDetailToMatch3DWork`、`buildMatch3DProfileFromSession`、`normalizeMatch3DWorkForRuntimeUi`、`mapMatch3DWorksForRuntimeUi`、`promoteMatch3DGeneratedBackgroundAsset`、`hasMatch3DRuntimeAsset`、`hasMatch3DRuntimeBackgroundAsset`、`resolveActiveMatch3DRuntimeProfile` 与 runtime item/background/backgroundImage 解析函数;平台壳只保留启动 run、预加载、路由、错误和 state 编排。
- 影响范围:抓大鹅作品架、公开详情试玩、推荐 runtime、正式 runtime 与草稿结果页试玩前素材规范化。
- 验证方式:`npm run test -- src/components/platform-entry/platformMatch3DRuntimeProfile.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "match3d|抓大鹅"`、针对新 Module 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md`。
## 2026-06-03 Draft Generation Shelf Model 收口
- 背景:平台壳内散落创作生成 notice key、pending 作品架占位、作品详情更新回填、失败文案覆盖、拼图稳定 ID、持久化 generating/failed 判断与草稿 Tab 未读点,新增或调整玩法时需要在多处理解 `workId` / `profileId` / `sourceSessionId` / `draftId` 形状。
- 决策:新增 `src/components/platform-entry/platformDraftGenerationShelfModel.ts` 作为 Draft Generation Shelf ModuleInterface 收口 `collectDraftNoticeKeys`、`getGenerationNoticeShelfKeys`、`createPendingDraftShelfState`、各玩法 `buildPending*Works`、`buildCreationWorkShelfRuntimeState`、`collectVisibleDraftNoticeKeys`、`hasUnreadDraftGenerationUpdates`、`mergePuzzleWorkSummary`、`mergeBigFishWorkSummary`、拼图稳定 ID 与持久化状态判断;`PlatformEntryFlowShellImpl.tsx` 仅作为 React state、网络刷新、路由和弹窗副作用 Adapter。
- 影响范围:创作中心草稿 Tab 未读点、作品架生成中遮罩、作品详情更新回填、失败草稿摘要、pending 草稿占位、拼图 / 抓大鹅生成恢复和各玩法生成完成通知。
- 验证方式:`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`、`npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts -t "generation state|failure notice|failed puzzle"`、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating puzzle draft|persisted generating match3d draft|completed baby object match draft"`、针对新 Module 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md`。
## 2026-06-03 Creation Hub Shelf Items Interface 收口
- 背景:`creationWorkShelf.ts` 已把各玩法作品映射为 `CreationWorkShelfItem.actions`,但 `CustomWorldCreationHub.tsx` 的生产 Interface 仍接收 raw items 与 open/delete/claim 回调列阵,新增玩法时 Hub props 继续膨胀。
- 决策:`CustomWorldCreationHub.tsx` 生产 Interface 收敛为 `shelfItems: CreationWorkShelfItem[]` 与少量 UI 状态;`PlatformEntryFlowShellImpl.tsx` 在外层作为 Adapter 调用 `buildCreationWorkShelfItems` 注入完整 actionsHub 测试改经 `CustomWorldCreationHub.testAdapter.tsx` 把旧 fixture 转成 shelf items,不让测试继续依赖旧浅 Interface。
- 影响范围:创作 Tab / 草稿 Tab 作品架、RPG / 拼图 / 抓大鹅 / 方洞 / 跳一跳 / 敲木鱼 / 视觉小说 / Bark Battle / 宝贝识物作品打开、删除、生成态与拼图奖励领取。
- 验证方式:`npm run test -- src/components/custom-world-home/creationWorkShelf.test.ts`、`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.test.tsx`、`npm run test -- src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、相关 FlowShell creation hub 交互片段、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】WorkShelfModule收口计划-2026-06-03.md`。
## 2026-06-03 Creation URL State Model 收口
- 背景:平台壳内散落各玩法创作恢复 URL 的 `sessionId` / `profileId` / `draftId` / `workId` 组装、空值归一化、拼图 runtime query key 与拼图稳定身份互推,导致刷新恢复规则缺少稳定测试面。
- 决策:新增 `src/components/platform-entry/platformCreationUrlStateModel.ts` 作为 Creation URL State ModuleInterface 收口各玩法 `build*CreationUrlState`、拼图 `buildPuzzle*RuntimeUrlState`、URL state 非空判断和 runtime state key;新增 `src/components/platform-entry/platformPuzzleIdentityModel.ts` 作为拼图稳定身份 Module`platformDraftGenerationShelfModel.ts` 仅 re-export 旧入口以保持兼容。`PlatformEntryFlowShellImpl.tsx` 只保留路由、URL 写入和网络副作用 Adapter。
- 追加决策:初始创作 URL 恢复的已处理、非创作路径、无私有 query、平台配置加载中、受保护数据暂不可读与可恢复判定也收口到 `resolveInitialCreationUrlRestoreDecision`;壳层只按 `skip`、`mark-handled`、`wait`、`restore` 执行 ref 标记或进入原恢复副作用。
- 追加决策:创作直达恢复目标解析收口到 `resolveCreationUrlRestoreTarget(pathname, state)`Module 统一识别 big-fish、match3d、square-hole、puzzle、visual-novel、bark-battle、baby-object-match、jump-hop、wooden-fish 的 path、私有 query 归一化、生成路径标记和 big-fish workId 到 sessionId 兜底。壳层仍执行作品列表读取、草稿恢复、错误处理、stage 切换和 URL 写回;`/creation/rpg` 继续保持无具体恢复目标,后续要接入需先补规则与测试。
- 追加决策:创作 URL 恢复的作品 / 草稿身份匹配谓词、以及跳一跳 / 敲木鱼恢复后的阶段落点也归入 `platformCreationUrlStateModel.ts`。身份匹配只允许非空目标值命中,避免 query 缺失时用空值误开草稿;壳层只把已读取的列表项、session 或 work 交给 Module 判定,然后执行对应打开 / restore 副作用。
- 影响范围:创作流程刷新恢复、拼图草稿 / 发布 runtime 深链、作品架打开试玩、跳一跳 / 敲木鱼 work-backed 恢复、Bark Battle / 宝贝识物本地草稿恢复。
- 验证方式:`npm run test -- src/components/platform-entry/platformCreationUrlStateModel.test.ts src/components/platform-entry/platformPuzzleIdentityModel.test.ts`、`npm run test -- src/services/creationUrlState.test.ts`、`npm run test -- src/components/platform-entry/platformDraftGenerationShelfModel.test.ts`、针对新 Module 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md`。
## 2026-06-04 Platform Public Code Search Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 的公开搜索回调内联判断内部用户 ID、陶泥号、RPG 作品号、各玩法公开作品号前缀和 fallback 顺序,壳层同时承担纯搜索计划与网络 / 打开副作用。
- 决策:新增 `src/components/platform-entry/platformPublicCodeSearchModel.ts`,以 `resolvePlatformPublicCodeSearchPlan(keyword)` 返回 `normalizedKeyword` 与 `steps`。`user_` / `user-` 只查用户 ID;玩法前缀直达对应作品;`CW` / 纯数字先查 RPG 作品再查陶泥号;普通关键词和 `SY` 保持既有用户号、RPG 作品、汪汪声浪、用户号兜底顺序。壳层只按 step 执行既有查找、详情打开、Bark Battle runtime 特例和 missing work 归航。
- 影响范围:发现页 / 推荐页公开搜索、作品详情深链初始搜索、陶泥号命中面板、各玩法公开作品号直达。
- 验证方式:`npm run test -- src/components/platform-entry/platformPublicCodeSearchModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformPublicCodeSearchModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Platform Played Work Open Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 的个人“玩过作品”点击回调内联判断 `worldType`、`worldKey` 前缀、玩法别名、目标 ID、RPG fallback 详情和大鱼吃小鱼 fallback work,壳层同时承担打开意图与异步副作用。
- 决策:新增 `src/components/platform-entry/platformPlayedWorkOpenModel.ts`,以 `resolvePlatformPlayedWorkOpenIntent(work)` 返回 `noop`、各玩法公开详情打开意图、`open-big-fish` 或 `open-rpg`。Module 负责玩法别名、`worldKey` 前缀兜底、big-fish gallery miss `fallbackWork` 和 RPG `CustomWorldGalleryCard` payload;壳层继续负责关闭面板、刷新 gallery、命中真实作品、打开详情和错误提示。
- 影响范围:个人“玩过作品”面板点击打开、拼图 / 抓大鹅 / 方洞 / 跳一跳 / 敲木鱼 / 大鱼吃小鱼 / RPG 公开详情入口。
- 验证方式:`npm run test -- src/components/platform-entry/platformPlayedWorkOpenModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、相关 profile 面板交互片段、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformPlayedWorkOpenModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Platform Generation Progress Tick Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 的生成页进度 tick effect 内联维护 stage 到小游戏生成状态的三元链,并额外手写视觉小说 `startedAtMs` / `phase` 特例,壳层同时承担纯判定与 interval 副作用。
- 决策:新增 `src/components/platform-entry/platformGenerationProgressTickModel.ts`,以 `resolvePlatformGenerationProgressTickDecision(input)` 返回 `{ activeKind, shouldTick }`。Module 负责 stage 到 kind 映射、小游戏状态缺失 / 终态判定、视觉小说轻量生成判定;壳层继续负责 `Date.now()`、`window.setInterval`、progress now state 写入和 cleanup。
- 影响范围:拼图、抓大鹅、大鱼吃小鱼、方洞挑战、跳一跳、敲木鱼、宝贝识物和视觉小说生成页进度 tick。
- 验证方式:`npm run test -- src/components/platform-entry/platformGenerationProgressTickModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformGenerationProgressTickModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Platform Mini Game Session Mapping Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 顶部仍保留拼图 runtime 恢复、方洞 session draft 转 profile、视觉小说 work detail 转 Agent session、跳一跳 pending session、敲木鱼 detail 恢复、敲木鱼生成中作品摘要和敲木鱼 pending session 等纯 DTO 映射,壳层需要理解 sessionId 优先级、拼图稳定 ID、方洞草稿 profile 默认值、视觉小说 work/session fallback、敲木鱼生成中摘要和 pending draft 默认值。
- 决策:新增 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,收口 `buildPuzzleRuntimeWorkFromSession`、`buildSquareHoleProfileFromSession`、`buildVisualNovelSessionFromWorkDetail`、`buildJumpHopPendingSession`、`buildWoodenFishSessionFromWorkDetail`、`buildWoodenFishGeneratingWorkSummary` 与 `buildWoodenFishPendingSession`。Module 复用 `normalizeCreationUrlValue` 与 `platformPuzzleIdentityModel`;壳层只保留网络读取、React state、URL 写入和 stage 切换副作用。
- 影响范围:拼图 runtime URL 恢复、方洞挑战草稿 profile 构造、视觉小说草稿作品架恢复、跳一跳生成中作品架打开、敲木鱼生成中作品架摘要 / 作品架打开和敲木鱼草稿 detail 恢复。
- 验证方式:`npm run test -- src/components/platform-entry/platformMiniGameSessionMappingModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Platform RPG Agent Result Preview Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 内联维护 RPG Agent 结果页发布门禁展示修正和 result preview source label 映射,壳层需要理解 `CustomWorldProfile` 顶层字段、`creatorIntent`、`anchorContent`、章节蓝图和首幕 acts。
- 决策:新增 `src/components/platform-entry/platformRpgAgentResultPreviewModel.ts`,收口 `buildPlatformRpgAgentResultPublishGateView` 与 `resolvePlatformRpgAgentResultPreviewSourceLabel`。Module 只做展示层纯判定;壳层继续负责 session/profile 编排、发布副作用和结果页 props 传递。
- 影响范围:RPG Agent 结果页发布按钮门禁 blockers、publishReady 展示修正和预览来源 label。
- 验证方式:`npm run test -- src/components/platform-entry/platformRpgAgentResultPreviewModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Platform Mini Game Draft Generation State Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 内联维护小游戏生成状态恢复、失败 / 完成收尾、展示 rebase、拼图后端进度合并和 ready / generating 判定,壳层同时承担 API / background task 副作用和 `MiniGameDraftGenerationState` 生命周期细节。
- 决策:新增 `src/components/platform-entry/platformMiniGameDraftGenerationStateModel.ts`,收口恢复态、失败态、完成态、展示 rebase、拼图 progress phase 阈值和进度 metadata 合并。壳层继续负责 API、后台任务、React state 写入、作品架刷新、URL 和 stage 切换。
- 追加决策:抓大鹅轮询作品素材时的旁路进度合并也归入该 Module,由 `mergeMatch3DGeneratedAssetsIntoGenerationState(state, assets)` 统一统计可用图片素材、至少 5 个总素材计数、`match3d-generate-views` phase 推进和首个素材错误传播;壳层只负责轮询 session / work detail 与写入 state。
- 影响范围:拼图 / 抓大鹅 / 大鱼吃小鱼 / 方洞 / 跳一跳 / 敲木鱼 / 宝贝识物生成状态恢复、完成失败收尾、生成页返回展示和拼图轮询进度合并。
- 验证方式:`npm run test -- src/components/platform-entry/platformMiniGameDraftGenerationStateModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformMiniGameDraftGenerationStateModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Platform Mini Game Draft Payload Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 内联维护拼图 / 抓大鹅表单 payload、拼图作品更新 payload、拼图编译 action、跳一跳 / 敲木鱼生成 action、作品摘要回填 payload 和 pending 草稿 metadata,壳层需要理解描述字段优先级、formDraft 回退、结果页 draft 到作品更新字段的映射、跳一跳 / 敲木鱼 payload 与 draft 优先级、Match3D config / draft / anchorPack 优先级和数字解析。
- 决策:新增 `src/components/platform-entry/platformMiniGameDraftPayloadModel.ts`,收口 `buildPuzzleFormPayloadFromWork`、`buildPuzzleFormPayloadFromSession`、`buildPuzzleFormPayloadFromAction`、`buildPuzzleCompileActionFromFormPayload`、`buildPuzzleWorkUpdatePayloadFromDraft`、`buildJumpHopDraftActionPayload`、`buildWoodenFishDraftActionPayload`、`buildPendingPuzzleDraftMetadata`、`isPuzzleFormOnlyDraft`、`isEmptyPuzzleFormOnlyDraft`、`buildMatch3DFormPayloadFromSession`、`buildMatch3DFormPayloadFromWork` 与 `buildPendingMatch3DDraftMetadata``parseOptionalFiniteNumber` 留在 Module 内部。
- 影响范围:拼图 action 完成 / 执行前 / 失败恢复、拼图结果页试玩前作品更新、跳一跳 / 敲木鱼生成与重生成 action、拼图表单直生草稿、拼图 form-only 草稿恢复 / 分流 / 结果页渲染、拼图草稿架恢复、抓大鹅表单直生草稿与失败恢复。
- 验证方式:`npm run test -- src/components/platform-entry/platformMiniGameDraftPayloadModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformMiniGameDraftPayloadModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Platform Puzzle Draft Recovery Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 的拼图恢复链路只要 cover 或候选图存在就会把恢复 session 抬为 ready,可能让缺关卡画面、UI spritesheet 或关卡背景的半成品直接进入结果页完成态。
- 决策:新增 `src/components/platform-entry/platformPuzzleDraftRecoveryModel.ts`,收口 `normalizeRecoveredPuzzleDraftSession` 与 `hasRecoverableGeneratedPuzzleDraft`。恢复完成态必须同时具备首图、`levelSceneImage*`、`uiSpritesheetImage*` 与 `levelBackgroundImage*`;只有完整资产包成立时才把 draft 与首关 `generationStatus` 抬为 `ready`。
- 影响范围:拼图生成完成后刷新恢复、拼图 background compile task 完成态写入和结果页自动打开。
- 验证方式:`npm run test -- src/components/platform-entry/platformPuzzleDraftRecoveryModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "persisted generating puzzle draft"`、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformPuzzleDraftRecoveryModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Platform Puzzle Runtime State Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 在拼图排行榜提交回包后内联合并服务端 run 快照,壳层需要理解 `PuzzleRunSnapshot` 中哪些字段由前端即时裁决、哪些字段只由服务端补齐。
- 决策:新增 `src/components/platform-entry/platformPuzzleRuntimeStateModel.ts`,以 `mergePuzzleServiceRuntimeState(currentRun, serviceRun)` 收口服务端 run 合并规则。Module 保留当前前端关卡状态、棋盘和计时,只合并服务端 run 身份、`clearedLevelCount` 上限、排行榜与下一关 handoff;任一 run 缺 `currentLevel` 时直接返回当前 run。
- 影响范围:拼图排行榜提交、推荐 runtime isolated / default 运行态回包合并、下一关同作品 / 相似作品 handoff,以及后续 Puzzle runtime 快照字段调整。
- 验证方式:`npm run test -- src/components/platform-entry/platformPuzzleRuntimeStateModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformPuzzleRuntimeStateModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Puzzle Publish Asset Gate 收紧
- 背景:后端拼图待发布门槛与前端历史恢复逻辑一样偏弱,只要求标题、描述、标签、关卡名和 cover,导致缺关卡画面、UI spritesheet 或关卡背景的半成品可能被标为 `publishReady` / `ready_to_publish`。
- 决策:`module-puzzle::validate_publish_requirements` 新增三类资产 blocker,要求每关具备 `level_scene_image_*`、`ui_spritesheet_image_*` 与 `level_background_image_*``api-server::puzzle::tags::is_puzzle_session_snapshot_publish_ready` 同步使用完整资产包判定。
- 影响范围:拼图 result preview blockers、publishReady、标签生成后 session stage、从 action payload 构造 fallback session 的 ready 判定。
- 验证方式:`cargo test -p module-puzzle --manifest-path server-rs/Cargo.toml validate_publish_requirements`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml puzzle_image_generation_builds_fallback_session_from_levels_snapshot`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml puzzle_image_generation_fallback_session_ready_when_asset_pack_complete`、`npm run check:encoding`。
- 关联文档:`docs/technical/【后端架构】PuzzlePublishAssetGate收紧计划-2026-06-04.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-12 跳一跳判定范围必须和视觉顶面对齐
- 背景:跳一跳切到 Three.js 立方体后,曾用收缩后的顶面 footprint 做成功判定,导致指示器和角色视觉上已经落在方块顶面内,但后端仍可能判失败。
- 决策:跳一跳命中区必须严格等于当前视觉方块完整可见顶面 footprint,不论何时都不得隐藏收缩或额外放宽;如果后续调整方块视觉大小、顶面形状、相机角度、旋转或模型规格,后端裁决、前端落点指示器和 Three.js 顶面脚点投影必须同步更新。
- 影响范围:`module-jump-hop` 后端裁决、`jumpHopRuntimeModel` 前端预测、运行态指示器、飞行动画、PRD 和平台链路文档。
- 验证方式:边缘落点只要仍在完整视觉顶面内必须判成功;超出完整视觉顶面才失败。运行 `cargo test -p module-jump-hop --manifest-path server-rs/Cargo.toml -- --nocapture` 与 `npm run test -- src/services/jump-hop/jumpHopRuntimeModel.test.ts src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx`。
- 关联文档:`docs/prd/【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-04 Platform Profile Wallet Delta Model 收口
- 背景:`PlatformEntryFlowShellImpl.tsx` 内联维护钱包余额归一、本地 delta 乐观更新和服务端 dashboard 刷新后的 delta 抵消,壳层需要理解余额非负、整数截断、借贷方向和服务端快照对账。
- 决策:新增 `src/components/platform-entry/platformProfileWalletDeltaModel.ts`,收口 `resolveProfileWalletBalance`、`adjustProfileDashboardWalletBalance` 与 `reconcileProfileWalletLocalDeltaWithServerDashboard`。壳层只保留 API 请求、React ref、state 写入和刷新触发副作用。
- 影响范围:创作入口泥点展示、生成前泥点校验、扣点 / 返还后的个人 dashboard 乐观更新、后台刷新 dashboard 时的本地 delta 对账。
- 验证方式:`npm run test -- src/components/platform-entry/platformProfileWalletDeltaModel.test.ts`、针对新 Module 和 `PlatformEntryFlowShellImpl.tsx` 执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PlatformProfileWalletDeltaModel收口计划-2026-06-04.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-03 Public Work Presentation 收口
- 背景:作品卡、推荐 runtime meta、排行项、分类项、搜索结果和桌面 hero 共用玩法类型 label 与紧凑计数格式,但规则仍在 `RpgEntryHomeView.tsx` 页面 Implementation 内。
- 决策:在 `src/components/rpg-entry/rpgEntryWorldPresentation.ts` 追加单作品展示 Interface`describePlatformPublicWorkKind`、`formatPlatformCompactCount`、`resolvePlatformPublicWorkAuthorLookup` 与 `formatPlatformPublicAuthorAvatarLabel`;页面删除本地玩法类型、紧凑计数、公开作者 lookup 和头像首字实现。集合筛选、排序和指标选择继续留在 `rpgEntryPublicGalleryViewModel.ts`。
- 影响范围:公开作品卡片 aria label、推荐点赞 / 改造文案、排行数值、分类主指标、搜索结果、桌面 hero 玩法 label、公开作者摘要缓存 key 与无头像首字兜底。
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryWorldPresentation.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "recommend|ranking|category"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】PublicWorkPresentation收口计划-2026-06-03.md`。
## 2026-06-03 Profile Funds ViewModel 收口
- 背景:个人资金展示规则散在 `RpgEntryHomeView.tsx`,且账单来源 label 表漏掉后端契约已有的 `puzzle_author_incentive_claim`,会把原始枚举值直接外显。
- 决策:新增 `src/components/rpg-entry/rpgEntryProfileFundsViewModel.ts` 作为个人资金展示 ModuleInterface 收口账单来源文案、金额正负号、余额兜底、充值价格、商品主值与会员摘要;页面保留弹窗布局、支付流程、微信渠道和订单轮询副作用。
- 影响范围:泥点账单弹窗、充值商品卡片、账户充值弹窗会员摘要。
- 验证方式:`npm run test -- src/components/rpg-entry/rpgEntryProfileFundsViewModel.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "wallet ledger|profile recharge modal shows native qr code"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】ProfileFundsViewModel收口计划-2026-06-03.md`。
## 2026-05-26 前端不外露图片模型名
- 背景:拼图与相关结果页、生成进度和错误提示里直接显示 `gpt-image-2`、`gemini-3.1-flash-image-preview`、`image-2` 等名称,会把内部模型路由暴露给普通用户。
- 决策:前端展示层统一改用产品化名称,如“标准模式”“创意模式”,以及“素材”“图片生成模式”等中性文案;内部 `imageModel`、`generationProvider` 和后端契约值保留不变,只改 UI 文案与错误提示。
- 影响范围:拼图图片模型选择器、拼图结果页关卡重生成面板、拼图生成进度文案、宝贝识物结果页占位提示和相关错误提示。
- 验证方式:前端可见文本中不再出现 `gpt-image-2` / `gemini-3.1-flash-image-preview` / `image-2 资源`;相关交互测试改为断言产品化模式名,但提交 payload 仍保持原有模型 ID。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-05-27 微信新用户用户名与孤儿作品作者回退收口
- 背景:用户数据清空后,旧作品的 `owner_user_id` 可能落到空洞或顺序号账号上,新注册用户会错误顶替历史作品;同时微信新用户默认用户名过于固定,不便于区分 openid。
- 决策:微信新用户的用户名统一改为 `名字_openid`,内部 `user_id` 改为不可复用的 `user_` 前缀 UUID 风格;作品作者找不到真实账号时统一回退到占位作者 `wx-openid-placeholder`,显示名固定为 `失效作者`,公开陶泥号固定为 `SY-00000000`。
- 影响范围:`module-auth`、`api-server` 作品作者解析、`AppState` 启动初始化、历史孤儿作品离线回填脚本与相关文档。
- 验证方式:`cargo test -p module-auth --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml work_author`、`npm run test -- scripts/rebind-orphan-work-owners.test.ts`。
- 关联文档:`server-rs/crates/module-auth/src/domain.rs`、`server-rs/crates/module-auth/src/lib.rs`、`server-rs/crates/api-server/src/work_author.rs`、`scripts/rebind-orphan-work-owners.mjs`。
## 2026-06-11 前端组件收口补记
- 背景:个人中心 profile 弹层已抽成独立组件,但 `error / loading / empty / content` 仍在多个 modal 中重复分支,继续沿业务页各写一套会让后续 profile 面板收口越来越碎。
- 决策:新增 `src/components/common/PlatformAsyncStatePanel.tsx` 作为互斥异步状态骨架,只承接 `errorState / loadingState / emptyState / children` 四类 slot 的优先级切换;`PlatformProfileWalletLedgerModal.tsx`、`PlatformProfileTaskCenterModal.tsx`、`PlatformProfileRechargeModal.tsx`、`PlatformProfilePlayedWorksModal.tsx` 与 `PlatformProfileReferralModal.tsx` 已接入。若错误或成功提示需要与内容并存,继续留在业务组件外层,不把 `PlatformAsyncStatePanel` 扩成全能状态机。
- 决策:`src/components/common/PlatformSegmentedTabs.tsx` 支持 `layout="scroll"`,用于横向可滚动 tab rail`CustomWorldCreationStartCard.tsx`、`CustomWorldWorkTabs.tsx` 以及 `RpgEntryHomeView.tsx` 的排行 / 分类筛选已接入。共享组件先负责 tab 语义、滚动容器和基础交互;当同一类皮肤在首页、作品架、分类筛选或个人中心中重复出现时,沉淀到 `src/components/common/PlatformSegmentedTabPresets.tsx` 的薄 preset,业务页不再重复复制长 `itemClassName`。
- 决策:`src/components/PixelCloseButton.tsx` 保持为 RPG 语义薄封装,底层统一复用 `src/components/common/PlatformModalCloseButton.tsx` 的 `variant="pixel"`;共享 close button 现在负责 `absolute / inline` placement、默认 `title=label` 和可选 `stopPropagation` 点击拦截,业务 importer 不再各自维护像素风关闭按钮壳和冒泡控制。
- 决策:`PlatformSegmentedTabs` 继续承接首页 / 结果页剩余的横向 rail 与二选一切换;`RpgEntryHomeView.tsx` 的 discover channel bar、移动端 / 桌面端分类 chip rail`CustomWorldEntityCatalog.tsx` 的 `RESULT_TABS` sticky rail,以及 `PlatformProfileRechargeModal.tsx` 的“泥点充值 / 会员卡”切换条已迁移。像 `CustomWorldEntityCatalog` 这种“标题 + count”内容直接走 `ReactNode label`;首页 / 创作入口 / 作品架 / 个人中心里稳定复用的频道下划线、创作 pill rail、二列 option segment 皮肤走 `PlatformSegmentedTabPresets`。同类切换在测试里应优先按 `role="tablist" / "tab"` 查询,而不是把它们继续当普通 button。
- 决策:简单泥点确认流的开关状态机统一收口到 `src/components/common/useMudPointConfirmController.ts`,只暴露 `open / requestOpen / close / confirm`,不持有点数、标题、描述或禁用态等业务字段;`PuzzleCreationWorkspace.tsx`、`Match3DCreationWorkspace.tsx` 与 `Match3DResultView.tsx` 的两个批量素材面板已接入。`PuzzleResultView.tsx` 和 `RpgCreationRoleAssetStudioModalImpl.tsx` 这类节奏不同或携带 pending payload 的场景继续保留本地状态机,避免把简单 hook 扩成泛型动作路由器。
- 决策:标准平台 modal header 的关闭入口继续统一到 `PlatformModalCloseButton variant="platformIcon"`;结果页 / 工具页重复的白底 portal 弹窗壳层收口到 `src/components/common/PlatformToolModalShell.tsx`,由它统一承接平台主题 overlay、白底 remap panel、标准 header/body/footer spacing、关闭按钮和遮罩 / Escape 关闭策略。`PuzzleResultView.tsx` 的关卡详情 / 发布弹窗、`Match3DResultView.tsx` 的封面 / 发布工具弹窗,以及 `PuzzleHistoryAssetPickerDialog.tsx` 的历史素材弹窗已迁移;`UnifiedModal` 新增 `ariaLabel` 支持可见标题动态、可访问名称固定的场景。像素风 runtime、drawer collapse、玩法规则面板和运行态 overlay 不跟这条线混收,继续保留局部 close 语义。
- 决策:平台 portal 主题恢复下沉到 `UnifiedModal``portal=true` 默认从 `AuthUiContext` 注入当前 light / dark 主题,已显式给出主题的调用保留原选择,无 Provider 回退 light。`portalTheme="none"` 只用于全黑图片预览等完全自绘弹层,`portal=false` 仍使用原 DOM 主题作用域。图片信息、修改图片与画布快捷键弹窗在完整支持暗色样式前显式使用 `portalTheme="light"`,不将固定白底面板与暗色文本变量混用。共享业务壳不再重复读取 AuthUi 只为 portal 补 class,画布私有变量则继续通过 `ImageCanvasEditorPortal` 桥接。已退役玩法不因该底层修复恢复入口或维护范围。
- 决策:`PlatformUtilityInfoModal` 未显式传主题时必须沿用 `UnifiedModal` 的 auto 主题,不在共享壳里默认锁定 light。`PublishShareModal` 跟随当前 light / dark 主题;`PlatformReportDialog` 因包含二维码 / 扫码展示区,显式固定 light 以保证白底对比度和识别率。
- 决策:平台入口的创作前置泥点阻断提示只在 `platform-entry` 局部抽成 `src/components/platform-entry/PlatformDraftGenerationPointNoticeDialog.tsx`,并使用 `DraftGenerationPointNotice` union`insufficient-points` / `balance-load-failed`)承接业务真相;不要在 `common/` 再抽一个泛化 `BlockingNoticeDialog`,否则会把 `PlatformAcknowledgeStatusDialog` 的样式透传再包装一层而不缩小调用面。
- 决策:`PlatformAsyncStatePanel` 从 profile modal 扩展到作品架类白底 panel`CustomWorldCreationHub.tsx` 的作品架主体现在也统一走 `loadingState / emptyState / children` 三段 slot,但 error + 重试继续留在业务层外侧,不把共享组件扩成“banner + retry + content”全能状态机。后续白底作品架或列表 panel 若只是互斥的 `loading / empty / content`,优先直接复用这套骨架。
- 决策:`CopyFeedbackButton.tsx` 的 `actionSurface` 分支继续收口到 `PlatformActionButton``pill` 分支继续保留 `PlatformPillBadge` 风格;复制反馈按钮不再直接调用 `getPlatformActionButtonClassName` 手拼平台按钮基础 chrome。后续同类“复制状态机 + 平台动作按钮”组合优先直接复用 `CopyFeedbackButton`,不要在业务页重新混写图标、文案、aria 和动作按钮 class。
- 决策:白底 / 暗色面板里的轻量空态和普通 CTA 继续向共享组件收口。`PuzzleResultView.tsx` 的缺草稿提示、`RpgCreationAssetDebugPanel.tsx` 的空诊断提示、`VisualNovelEntityGrid` 的空实体列表、`AccountModal.tsx` 里账号安全分区的“无安全限制 / 无登录设备 / 无操作记录”以及 `LoginScreen.tsx` 的“当前登录入口暂不可用”都改为 `PlatformEmptyState``Match3DResultView.tsx` 的引用素材列表直接复用 `PlatformAssetPickerGrid` 自己的空态;`AdventureEntityModal.tsx` 的私聊按钮、`InventoryPanel.tsx` 的锻造 / 合成按钮、`RpgCreationRoleAssetStudioModalImpl.tsx`、`RpgCreationEntityEditorShared.tsx` 里的局部 `ActionButton` 包装层,以及 `RpgAdventurePanel.tsx` / `RpgAdventurePanelOverlays.tsx` 里标准 runtime CTA 都改为委托 `PlatformActionButton surface="editorDark"`。后续白底子面板里的只读空态优先使用 `PlatformEmptyState surface="subpanel"`;暗色编辑 / 运行面板里的普通动作优先使用 `PlatformActionButton surface="editorDark"`,若业务仍需 `stopPropagation`、tone 映射、运行态 icon 排版或局部字号,可保留薄包装层,但不要再直接写原生 `<button>` 基础 chrome。
- 决策:白底 / 浅色结果页和工作台顶部的“左箭头 + 返回文案”轻量返回入口统一收口到 `src/components/common/PlatformBackActionButton.tsx`;共享组件固定承接 `PlatformActionButton tone="ghost" size="xs"` 上的返回按钮骨架,并只开放 `compact / regular` 两档尺寸,分别覆盖紧凑结果页 header 与标准白底结果页顶栏。当前已覆盖 `PuzzleResultView.tsx`、`SquareHoleResultView.tsx`、`Match3DResultView.tsx`、`VisualNovelResultView.tsx`、`PuzzleClearResultView.tsx`、`JumpHopResultView.tsx`、`WoodenFishResultView.tsx` 与 `BabyObjectMatchResultView.tsx`;暖色生成页继续走 `GenerationHeaderBackButton``BigFishResultView.tsx` 这类 dark hero / 强品牌返回入口继续走 `PlatformIconButton darkMini`,不把三条视觉语义线硬并成一个组件。
- 决策:`CustomWorldNpcVisualEditor.tsx` 的本地 `ActionButton` 和 `SkillEffectPreview.tsx` 的“重新预览”按钮也继续并入这条暗色按钮收口线,统一委托 `PlatformActionButton surface="editorDark"`;局部包装层只保留 `stopPropagation`、图标排布、`tone` 映射和极少量视觉微调。后续暗色编辑器里的局部动作按钮若只是普通 CTA,不再新增原生 `<button>` 实现,优先沿用“薄包装 + 共享按钮本体”模式。
- 决策:RPG 创作侧标准 dark header / footer 动作也继续纳入同一条按钮收口线。`RpgCreationRoleAssetStudioModalImpl.tsx` 的 header“关闭”、`RpgCreationEntityEditorShared.tsx` 的 footer“取消”以及 `RpgCreationRoleAssetStudioFooter.tsx` 的“保存到当前角色”都改为委托 `PlatformActionButton surface="editorDark"`;局部壳层只保留布局、宽度/字号贴合和少量 tone 语义,不再为标准 dark close / cancel / save CTA 单独维护原生 `<button>` 基础 chrome。
- 决策:RPG runtime overlay 里的标准 dark CTA 和可点击 dark row 也继续纳入这条收口线。`RpgAdventurePanelOverlays.tsx` 的 goal panel“知道了”、任务详情里的“领取任务 / 返回交付”、任务完成提示里的“打开任务日志”都改为委托 `PlatformActionButton surface="editorDark"`;设置面板里的“运行统计”入口改为 `PlatformSubpanel as="button" surface="dark"`。像素风 choice button、HUD launcher、奖励物品格和输入 composer 保持 runtime 专属语义,不继续硬并到普通平台按钮。
- 决策:`PlatformToolModalShell` 继续承接 RPG 结果页发布检查弹窗;`RpgCreationResultActionBar.tsx` 只保留发布检查、封面预览、封面设置和发布动作语义,不再直接维护 `createPortal`、平台主题 overlay、白底 remap panel、header close、body/footer spacing 和遮罩关闭逻辑。后续结果页 / 工具页里同形态的白底 portal 弹窗优先迁移到 `PlatformToolModalShell`;编辑器大壳、暗色 runtime overlay 和需要专属布局的面板继续保留局部 shell。
- 决策:`PlatformToolModalShell` 继续承接方洞结果页图片槽弹窗;`SquareHoleResultView.tsx` 的封面 / 背景 / 形状 / 洞口图片查看与历史选择弹窗只保留当前图、上传、AI 生成和历史素材选择语义,不再直接维护 `createPortal`、主题 overlay、白底 remap panel、header close 和滚动 body。该弹窗使用 `ariaLabel` 保持“封面图查看 / 背景图查看”等固定可访问名称,历史生成区继续由 `PlatformAssetPickerGrid` 承接读取、错误和空态。
- 决策:`PlatformToolModalShell` 继续承接视觉小说结果页素材选择弹窗;`VisualNovelAssetPickerDialog` 只保留本地上传、AI 图片生成、历史素材读取、错误提示和素材选择回调,不再直接维护 `createPortal`、平台主题 overlay、白底 remap panel、header close 和滚动 body。视觉小说音频生成弹窗需要保留生成中禁止关闭,实体编辑器弹窗需要保留编辑 footer,后续逐个迁移并补对应交互测试。
- 决策:认证入口白底弹窗壳层收口到 `src/components/auth/PlatformAuthModalShell.tsx`;该壳层只承接平台主题 overlay、`platform-auth-card`、标准标题栏、关闭按钮、点击遮罩关闭和禁用 Escape 的认证弹窗策略,不持有短信 / 密码登录、重置密码、邀请码规范化、法律协议或错误状态。`LoginScreen.tsx` 与 `RegistrationInviteModal.tsx` 只保留各自表单状态和提交流程。
- 决策:账号弹窗可以继续复用 `PlatformAuthModalShell` 的平台主题 overlay 与 auth card 壳层,但通过 `overlaySpacing`、`overlayStyle`、`showHeader` 和尺寸透传保留账号 direct mode 的唯一 dialog 语义与 safe-area 布局,不把账号安全详情、换绑手机号或修改密码子面板并进登录表单语义。
- 决策:运行态弹窗先按玩法目录沉淀薄壳,只有跨玩法接口真正稳定后才上升到 `common/`。拼图运行态用 `src/components/puzzle-runtime/PuzzleRuntimeModalShell.tsx` 承接道具确认、设置、退出改造、失败和通关结算的 overlay / dialog / footer / button 骨架;抓大鹅和跳一跳结算分别保留在各自 runtime shell 内抽本地 settlement shell / summary / actions。`PlatformToolModalShell` 继续只服务平台白底工具弹窗,不强塞到像素风或游戏运行态 overlay;拖拽 ghost、飞行动画、原图查看和全屏 runtime 容器不按旧 modal 债务处理。
- 决策:NPC dark modal footer 和暗色明细空态也继续纳入同一条收口线。`NpcModals.tsx` 里的交易 / 赠礼 / 招募弹窗 footer 按钮和物品详情“关闭”按钮都改为委托 `PlatformActionButton surface="editorDark"`,交易右侧“请选择一件物品”提示改为 `PlatformEmptyState surface="editorDark"``CharacterInfoShared.tsx` 的 `BuildContributionDetailPanel` 空明细也改为 `PlatformEmptyState surface="editorDark"`。数量 stepper、赠礼 / 招募 option card、标签强度按钮这类带独立业务语义的控件继续保留局部实现。
- 决策:详情页头部动作组合统一收口到 `src/components/common/PlatformDetailTopbar.tsx` 与 `src/components/common/PlatformDetailShareActions.tsx`。`PlatformDetailTopbar` 只负责返回按钮、标题居中槽位和右侧动作槽位的布局,可在 `pill` / `icon` 返回入口之间切换;`PlatformDetailShareActions` 只负责“前置 badge 区块 + 作品号复制 + 分享复制”这组稳定动作,并允许按页面关闭复制或分享其中一项。`RpgEntryWorldDetailView.tsx` 已接入 overlay 版完整动作组,`PlatformWorkDetailView.tsx` 已接入 icon topbar 与 solid 版作品号复制动作,同时继续保留公开详情页自己的顶部 icon 分享入口和分享反馈提示。后续详情页若只是复用返回、标题、作品号复制或分享动作排列,优先组合这两个薄组件,不把作者、摘要、封面、轮播或业务 CTA 塞进共享配置对象。
- 验证方式:`npm run test -- src/components/common/PlatformAsyncStatePanel.test.tsx src/components/platform-entry/PlatformProfileReferralModal.test.tsx src/components/platform-entry/PlatformProfileWalletLedgerModal.test.tsx src/components/platform-entry/PlatformProfilePlayedWorksModal.test.tsx src/components/platform-entry/PlatformProfileTaskCenterModal.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/common/PlatformSegmentedTabs.test.tsx src/components/custom-world-home/CustomWorldCreationHub.test.tsx src/components/custom-world-home/CustomWorldCreationHub.interaction.test.tsx`、`npm run test -- src/components/common/PlatformModalCloseButton.test.tsx src/components/PixelCloseButton.test.tsx src/components/CharacterChatModal.test.tsx src/components/MapModal.test.tsx`、`npm run test -- src/components/common/useMudPointConfirmController.test.tsx src/components/match3d-result/Match3DResultView.test.tsx src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx src/components/unified-creation/workspaces/Match3DCreationWorkspace.interaction.test.tsx src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx src/components/platform-entry/PlatformProfileRechargeModal.test.tsx src/components/CustomWorldEntityEditorModal.test.tsx src/components/rpg-creation-result/RpgCreationResultActionBar.test.tsx src/components/unified-creation/shared/PuzzleHistoryAssetPickerDialog.test.tsx src/components/puzzle-result/PuzzleResultView.test.tsx`、`npm run test -- src/components/common/CopyFeedbackButton.test.tsx src/components/common/PlatformActionButton.test.tsx src/components/AdventureEntityModal.test.tsx src/components/InventoryPanel.test.tsx src/components/rpg-creation-result/RpgCreationAssetDebugPanel.test.tsx src/components/visual-novel-result/VisualNovelResultView.test.tsx src/components/common/PlatformEmptyState.test.tsx src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx src/components/auth/AccountModal.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.npcChat.test.tsx src/components/rpg-runtime-panels/RpgAdventurePanel.questOffer.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-05-26 敲木鱼发布后作品架与推荐流刷新口径
- 背景:敲木鱼已具备公开广场投影,但草稿 Tab 的作品架没有当前用户作品列表接口,导致已发布作品在发布后不能立即出现在“已发布”筛选和推荐流里。
- 决策:新增 `GET /api/creation/wooden-fish/works` 作为当前用户木鱼作品架事实源,返回 `WoodenFishWorksResponse.items` 摘要;平台壳在发布成功后必须同时刷新作品架和公开广场列表。
- 影响范围:`server-rs/crates/api-server/src/wooden_fish.rs`、`server-rs/crates/api-server/src/modules/wooden_fish.rs`、`src/services/wooden-fish/woodenFishClient.ts`、`src/components/custom-world-home/creationWorkShelf.ts`、`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`。
- 验证方式:发布一个木鱼作品后,草稿 Tab 的已发布筛选应立刻出现 `WF-*` 作品卡,推荐 / 最新流也应立即刷新出公开卡片。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/prd/【玩法创作】敲木鱼玩法模板PRD-2026-05-20.md`。
## 2026-05-27 认证快照完全去文件化并仅保留行级备查
- 背景:`api-server` 依赖本地 `auth-store.json` 或 `GENARRATIVE_AUTH_STORE_PATH` 恢复认证真相会在 SpacetimeDB 不可用时把旧快照回灌到 `auth_identity` / `user_account`,导致用户数据被清空或覆盖。
- 决策:`api-server` 启动时只允许从 SpacetimeDB 正式认证表恢复;`module-auth` 不再维护本地持久化文件,只保留内存工作集和 JSON 导入 / 导出;`spacetime-module` 的认证快照只保留行级 `auth_store_snapshot` 备查,不再提供旧 `get_auth_store_snapshot` / `upsert_auth_store_snapshot` / `import_auth_store_snapshot` 兼容入口。
- 影响范围:`server-rs/crates/api-server/src/state.rs`、`server-rs/crates/module-auth/src/lib.rs`、`server-rs/crates/spacetime-module/src/auth/procedures.rs`、`server-rs/crates/spacetime-client/src/auth.rs`、对应生成 bindings。
- 验证方式:`cargo check -p module-auth --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`cargo test -p module-auth password --manifest-path server-rs/Cargo.toml -- --nocapture`、`npm run check:spacetime-schema`、`npm run check:encoding`、`cargo test -p api-server spacetime_unavailable_router_returns_service_unavailable_for_requests --manifest-path server-rs/Cargo.toml -- --nocapture`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-06-07 创作入口泥点消耗改由统一契约驱动
- 背景:创作入口玩法卡封面右下角长期固定显示 `10-20泥点数`,无法在后台按玩法调整,也容易和真实钱包余额或活动奖池混淆。
- 决策:`creationTypes[].unifiedCreationSpec.mudPointCost` 作为入口卡泥点消耗数量字段,旧契约缺失时后端和前端都兜底为 `10`;入口卡由前端格式化为 `X泥点数` 展示,后端和后台不保存单位文案。该字段同时作为玩法新建草稿初始生成的扣费真相源,前端余额前置校验、拼图首图生成、抓大鹅完整草稿生成和汪汪声浪初始三图生成必须读取同一份后台入口配置;结果页单图重生成、发布、道具使用和其它独立资产操作继续使用各自业务成本。
- 决策补充:后台创作入口开关页不再直接暴露统一创作契约 JSON textarea;页面按契约结构展示为卡片和字段列表,点击“修改契约”后通过弹窗表单编辑 `title`、`mudPointCost` 和 fields,再组装回统一契约 payload 保存。`workspaceStage`、`generationStage` 和 `resultStage` 属于内部阶段标识,后台不展示也不允许编辑;保存时沿用已有契约值,新增契约时按 `playId` 的固定阶段映射自动带出。
- 影响范围:`shared-contracts` 的 `UnifiedCreationSpecResponse`、`/api/creation-entry/config` 响应、前端入口卡派生、后台入口开关页、玩法链路文档和创作入口回归测试。
- 验证方式:后台修改 `mudPointCost` 后保存,`GET /api/creation-entry/config` 返回同名数字字段;底部加号创作入口卡显示前端格式化后的泥点消耗;创作表单泥点不足提示和后端实际钱包扣费都使用该数字;关闭态卡片仍只显示 `暂未开放`。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-11 拼图与拼消消运行态剩余阻断层继续局部收口
- 背景:账号弹窗、拼图 runtime、抓大鹅结算、跳一跳结算和拼图 onboarding 收口后,允许范围内仍剩拼图“正在准备下一关”阻断层与拼消消 runtime 的等待 / 结算层各自手写 overlay;它们结构相近,但又都带着玩法本地语义。
- 决策:平台入口里的拼图“正在准备下一关”只在 `src/components/platform-entry/PlatformEntryFlowShellImpl/` 下新增 `PuzzleRuntimeBlockingOverlay.tsx` 做本地薄壳,继续复用 `UnifiedModal` 的遮罩、dialog 语义和关闭禁用策略,但不把这类运行态等待面板上推到 `common/`。拼消消 runtime 则在 `src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx` 内新增 `PuzzleClearRuntimeOverlayShell`、`PuzzleClearRuntimePendingOverlay` 与 `PuzzleClearRuntimeSettlementDialog`,统一 `!activeRun`、`level_cleared`、`finished`、`level_failed` 三类局部 overlay 的结构和动作出口。拖拽 ghost、swap flight、补牌 / 消除动画和全屏 runtime 容器继续视为玩法专属视觉层,不算旧 modal 债务。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleRuntimeBlockingOverlay.tsx`、`src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx`、相关测试与 PlatformUiKit 收口文档。
- 验证方式:`npm run test -- src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleRuntimeBlockingOverlay.test.tsx src/components/platform-entry/PlatformEntryFlowShellImpl.test.ts src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md`。
## 2026-05-31 拼消消底图 prompt 与 atlas 切片提示词收口
- 背景:拼消消生成资产检查时,用户需要区分主题词、场地底图主题词和复合图 atlas prompt 的职责;若小图案显式画出切分线或边框,运行态 1x1 切片会显得像错误素材。
- 决策:`boardBackgroundPrompt` 成为中央场地底图的优先 prompt 来源,只有该字段为空时才回退读取 `themePrompt`;用户上传底图时只执行平台资产持久化和换签,不用主题词重写上传资产。复合图 atlas prompt 只描述“可被服务端按等大 1x1 方格切分”,禁止模型在图案上绘制切分线、边框、网格线或裁切参考线。
- 影响范围:拼消消工作台 payload、`shared-contracts` / `packages/shared` 契约、api-server 生成编排、SpacetimeDB session/work snapshot、文档与生成进度展示。
- 验证方式:`npm run spacetime:generate`、`npm run check:encoding`、`npm run check:server-rs-ddd`、`cargo test -p module-puzzle-clear`、`cargo test -p spacetime-client puzzle_clear -- --nocapture`、`npm run test -- src/components/puzzle-clear-creation/PuzzleClearWorkspace.test.tsx src/services/miniGameDraftGenerationProgress.test.ts src/routing/appPageRoutes.test.ts src/services/publicWorkCode.test.ts`。
- 关联文档:`docs/prd/【玩法创作】拼消消玩法模板PRD-2026-05-30.md`、`docs/technical/【玩法创作】拼消消玩法模板技术方案-2026-05-30.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-06 统一创作页表头按契约 title 原样显示
- 背景:统一创作页长期使用固定表头 `想做个什么玩法?`,导致跳一跳等玩法希望按自身语义展示标题时只能改前端或默认契约。
- 决策:`creationTypes[].unifiedCreationSpec.title` 继续作为统一创作页表头传输字段,但读取和保存时都按契约内容原样显示和持久化,不再用入口 `title` 自动覆盖。默认 spec 可以给出玩法中文名;旧库中已经持久化为 `想做个什么玩法?` 的契约也保持原样,若需要改表头应在后台契约结构卡片中点击修改并编辑 `title` 字段。
- 影响范围:`shared-contracts` 默认 spec、`module-runtime` 入口配置响应、`spacetime-module` 后台保存校验、后台入口开关页摘要和前端 fallback spec。
- 验证方式:`GET /api/creation-entry/config` 中各玩法 `unifiedCreationSpec.title` 等于已保存契约内容;后台只修改入口名称时不应隐式改写已保存的统一创作页表头。
- 关联文档:`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
## 2026-06-11 Pingora 网关先独立二进制试点
- 背景:评估 Pingora 是否逐步替代当前生产 Nginx 时,需要先验证 Genarrative 的现有反向代理、静态资源、维护模式和最小 SpacetimeDB 公网路由口径。
- 决策:新增 `server-rs/crates/pingora-gateway` 作为独立 binary crate,默认监听 `127.0.0.1:18081` 做影子网关;当前不绑定 `80/443`,不替代 `deploy/nginx/genarrative.conf`,生产仍以 Nginx 为公网入口。
- 影响范围:Rust workspace、Pingora 依赖、网关环境变量、`deploy/pingora/pingora-gateway.env.example` 和运维技术方案。
- 验证方式:先执行 `cargo fmt --manifest-path server-rs/Cargo.toml -p pingora-gateway`、`cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml`;替换前必须补齐路由 parity、压缩、TLS、限流、systemd、Jenkins 和健康巡检。
- 关联文档:`docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md`。
- 决策补充:`/api` 通用路由必须同时检查 `Content-Length` 与实际流式请求体累计字节数;缺少长度头时超过上限也返回统一 `PAYLOAD_TOO_LARGE` JSON。影子部署模板使用 `deploy/systemd/genarrative-pingora-gateway.service`,默认读取 `/etc/genarrative/pingora-gateway.env`,仍只监听本机高端口;`/__genarrative_pingora/healthz` 只在配置并匹配 `X-Genarrative-Pingora-Probe` token 时返回 shadow JSON。
- 决策补充:生产 `genarrative-health-patrol.service` 只在显式配置 `GENARRATIVE_HEALTH_PATROL_PINGORA_BASE_URL` 与 `GENARRATIVE_HEALTH_PATROL_PINGORA_PROBE_TOKEN` 时检查 Pingora shadow probe;未配置时巡检口径不变。Pingora shadow 日志必须保留 request/route/upstream/body 字段,方便和 Nginx access log 做 canary 对照。
- 决策补充:生产健康巡检的公网入口模式必须显式区分 `nginx` 和 `pingora-direct`。默认 `GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE=nginx` 检查 API、SpacetimeDB 和 NginxPingora 直连接管公网后切到 `pingora-direct`,改为检查 API、SpacetimeDB 和 `genarrative-pingora-gateway.service`,不再要求 `nginx.service` active。目标机本机探测 `127.0.0.1` 时用 `GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST=<域名>` 保留正式 Host / vhost 语义。
- 决策补充:Pingora 影子网关产物不进入默认 API release;只有显式传 `--include-pingora-gateway` 或在 `Genarrative-Api-Build` 勾选 `INCLUDE_PINGORA_GATEWAY` 时,才构建并打包 `pingora-gateway` / `pingora-gateway.sha256`。真实构建 Pingora 前必须先检查 `cmake`、C 编译器和 C++ 编译器;Jenkins 勾选 `INCLUDE_PINGORA_GATEWAY` 时也要先 fail-fast 检查这些工具,避免进入 Cargo 后才因 `libz-ng-sys` 构建依赖缺失失败。`production-api-deploy.sh` 仅在两者同时存在时校验并复制到 current release,避免现有 API 流水线被 Pingora 构建依赖影响。发布包包含 Pingora 时,API deploy 会在提升 release 前读取 `systemctl cat genarrative-pingora-gateway.service` 和其 `EnvironmentFile`,拒绝 direct-entry `CAP_NET_BIND_SERVICE`、拒绝非 `127.0.0.1:18081` 的 shadow listen、拒绝 `TLS_LISTEN` / `HTTP_REDIRECT_LISTEN`,确认仍是本机 shadow 高端口后才切换 current;切换后执行 `systemctl restart genarrative-pingora-gateway.service` 并复核 active,让 shadow / canary 机器加载同一份 current release 网关二进制。该自动拉起不会启用公网 `80/443` 直连入口;已经进入 direct-entry 状态的机器应走正式直连 runbook 或先回退到 shadow。`npm run check:production-api-release` 必须同时验证默认 API release 不登记 Pingora,以及显式 `--include-pingora-gateway --skip-pingora-gateway-build` 时发布包包含 `pingora-gateway`、`pingora-gateway.sha256` 并写入 manifest。
- 决策补充:生产健康巡检的显式 `--timeout-ms`、`--slow-ms`、`GENARRATIVE_HEALTH_PATROL_TIMEOUT_MS` 和 `GENARRATIVE_HEALTH_PATROL_SLOW_MS` 必须是正整数,非法值直接失败,不静默回退默认 `5000ms` / `3000ms`。Pingora canary live 的 `--timeout-ms` / `GENARRATIVE_PINGORA_CANARY_TIMEOUT_MS`、direct live 的 `--timeout-ms` / `GENARRATIVE_PINGORA_DIRECT_TIMEOUT_MS`、canary access log 对账的 `--since-lines` / `GENARRATIVE_PINGORA_CANARY_ACCESS_LOG_SINCE_LINES` 也必须正整数。Pingora direct live 和 release readiness 读取的直连布尔 env 必须严格解析,只接受 `true/false`、`1/0`、`yes/no`、`on/off` 或空值,非法值直接失败,避免 `REQUIRE_WSS_UPGRADE`、preflight 开关或 `SKIP_WSS` 因拼写错误被当成 false。canary live 的 base URL、prefix、Host、额外 path 和 timeout 不能包含换行或 NUL;脚本必须在发起 canary 请求前失败,避免污染参数进入 URL、Host header 或 JSON 输出。canary access log 对账的日志路径、prefix、必需路径、tail 行数以及日志行中解析出的 URI / path 也不能包含换行或 NUL;脚本必须失败并给出对应参数或日志行诊断,不能把污染值写入 JSON 对账输出。direct live 的 HTTPS / HTTP base URL、Host、redirect Host、probe token、额外 path、SpacetimeDB 数据库名、access log 路径、timeout 和布尔 env 都不能包含换行或 NUL;脚本必须在发起 HTTPS / HTTP / WSS 请求前失败,避免污染参数进入请求头、URL、日志对账或 JSON 证据。Pingora 切换窗口调整巡检、live smoke、日志对账阈值或直连布尔开关时,把参数解析失败视为配置错误,而不是继续执行检查。
- 决策补充:即使不打包 Pingora 二进制,API release 也必须随包携带 Pingora release readiness 聚合门禁、直连启用 / 回退 / preflight / live smoke / current release 自审脚本、直连彩排状态脚本,以及 `deploy/systemd/`、`deploy/pingora/` 支撑配置;`pingora-direct-enable.sh` 和正式 cutover runbook 默认从 `/opt/genarrative/current` 推导这些路径,启用前 release readiness 基础门禁和启用后 `--require-direct` 复核也必须调用 `/opt/genarrative/current/scripts/check-pingora-release-readiness.mjs`,切换窗口不得依赖 Jenkins 工作区或目标机源码 checkout。API release 还必须携带 `build/<version>/scripts/deploy/production-api-deploy.sh` 和同目录 `maintenance-on.sh` / `maintenance-off.sh``Genarrative-Api-Deploy` 只能复制并执行 build 产物内的 deploy 脚本,禁止继续执行部署工作区根部脚本,避免 workspace 中的旧脚本掩盖发布包布局缺陷。`production-api-deploy.sh` 对数据库备份脚本、健康巡检脚本和 Pingora 直连依赖都执行 fail-fast,发布产物缺失时保持维护模式并停止部署,不再从部署工作区兜底复制;API deploy 必须要求 `--release-root`、`--current-link`、`--api-env-file` 使用绝对路径,且 `--version` 必须以数字或字母开头并拒绝点目录,再先写 `${RELEASE_ROOT}/.${VERSION}.staging.$`,全部复制完成后再用非合并语义提升为 `${RELEASE_ROOT}/${VERSION}`,并用固定替换语义切换 current 符号链接,同版本 release 已存在、提升前竞态出现或 current 路径不是符号链接时拒绝覆盖 / 合并,失败时清理 staging 且不留下正式 release`npm run check:production-api-release` 与 `npm run check:production-api-deploy` 必须进入 `check:pingora-release-readiness` 聚合门禁,前者用临时 `CARGO_TARGET_DIR` 和假 `api-server` release binary 验证 `build-production-release.sh --component api-server --skip-api-build` 产物自包含,后者用临时 release 和 fake `systemctl` / `curl` 验证从发布产物内执行 deploy 脚本后 current release 自包含,并覆盖缺少备份脚本、健康巡检脚本、release readiness 聚合门禁脚本、current release 自审脚本、直连彩排状态脚本、direct live smoke 脚本、相对 release root / current link / api env file、点目录或点开头 version、既有 release 目录、目录型 current 或提升前 release 目录竞态时的失败维护模式。
- 决策补充:正式直连 runbook 在采集状态快照前必须先执行 current release 自审:`/opt/genarrative/current/scripts/ops/pingora-current-release-audit.mjs --release-root /opt/genarrative/current --require-pingora-gateway --systemd-show`。该脚本只读检查发布包自包含、`pingora-gateway` 可执行,以及 systemd `ExecStart` 是否指向 current release 网关二进制;失败时应先修发布包、Jenkins 归档过滤、deploy 复制或 systemd 指向,再继续切换。
- 决策补充:直连前的 dev / release 彩排状态使用 `/opt/genarrative/current/scripts/ops/pingora-direct-rehearsal-status.mjs --release-root /opt/genarrative/current --expect-public-gateway nginx --require-pingora-shadow --require-realpath-canary --require-current-release-gateway --fail-on-critical`。该脚本只读读取 health patrol env、Pingora env、`systemctl`、`ss -H -ltnp`、realpath canary 配置和 current release 自审结果;`nginx` 期望模式要求公网 `80/443` 仍由 Nginx 监听,Pingora 只在 `127.0.0.1:18081` shadowrealpath canary 在 `127.0.0.1:18083`,不会写 `/etc`、reload systemd 或修改 Nginx / Pingora。
- 决策补充:Pingora 切换证据链正式纳入 `npm run check:pingora-current-release-audit`、`npm run check:pingora-cutover-status-snapshot`、`npm run check:pingora-cutover-evidence-bundle`、`npm run check:pingora-cutover-command-evidence`、`npm run check:pingora-cutover-evidence-verify`、`/opt/genarrative/current/scripts/ops/pingora-current-release-audit.mjs`、`/opt/genarrative/current/scripts/ops/pingora-cutover-status-snapshot.mjs`、`/opt/genarrative/current/scripts/ops/pingora-cutover-evidence-bundle.mjs` 与 `/opt/genarrative/current/scripts/ops/pingora-cutover-evidence-verify.mjs`。状态快照按 `pre-cutover`、`post-enable`、`post-rollback` 三个阶段输出只读 JSON evidence,收录 `summary`、`healthPatrolEnv`、`pingoraEnv`、`releaseArtifacts`、`systemd` 和 `checks`;直连 runbook 的三个证据包阶段都显式透传绝对路径 `--output-root` 和 `--require-pingora-gateway`,让 `checks.current-release-audit.details` 同步归档 Pingora 二进制、sha256、release manifest 和 systemd `ExecStart` 自审结果;证据包脚本把快照 JSON、stdout、stderr、命令记录和 manifest 写入 `--output-root` 下的新证据目录,manifest 对已生成 snapshot、direct live、stdout / stderr、命令记录和 parse-error 文件记录 `path`、`sizeBytes` 与 `sha256`;每个阶段证据目录生成、复制或归档后,都必须用随包 verifier 按 `manifest.files` 只读复核文件存在、大小和 sha256,路径逃逸、符号链接证据目录、非目录证据路径、缺文件、大小漂移或 sha256 漂移都应失败。命令证据脚本把 direct enable apply / rollback apply 的真实 stdout、stderr、退出码、脱敏命令记录和 manifest 写入同一证据根目录,命令记录同时保留脱敏后的可读命令和结构化 `executable` / `args[]`manifest 对 `command.stdout.txt`、`command.stderr.txt` 和 `command-record.json` 同样记录 `path`、`sizeBytes` 与 `sha256`,便于切换窗口后复核归档文件未漂移;runbook 必须在命令证据生成后立即用随包 verifier 验真 `<enable-apply-bundle-dir>` / `<rollback-apply-bundle-dir>`,再继续 health patrol 切换、回退后 env 复核或最终总审计,且 `--phase` 只允许 ASCII 字母、数字、点、下划线和短横线,非法阶段名直接失败,不做隐式清洗。自审和快照只读采集,证据包只写归档目录且不覆盖既有文件,证据验真脚本只读 manifest 和证据文件,命令证据脚本只执行 `--` 后面的真实命令并归档输出,证据目录权限固定为 `0750`,证据文件权限固定为 `0640`,这些脚本都不写 `/etc`、不 reload systemd,也不修改 Nginx 或 Pingoraprobe token 和其他 env 敏感值只允许以是否存在或 `<redacted>` 的形式进入证据链,聚合门禁真实执行日志、dry-run plan、cutover runbook、直连启用脚本 direct live 命令日志、直连回退脚本 shadow probe 命令日志、gateway smoke 命令日志和证据包命令记录都不得输出 token 原文,状态快照在收录健康巡检、current release 自审等子检查 stdout / stderr 前必须按 env 敏感值脱敏,状态快照、证据包、命令证据和证据验真自测必须确认 token 原文不会进入 stdout、snapshot、manifest、命令记录或子检查输出;正式 runbook 应在 `--fail-on-critical` 下把自审、快照、证据包和 manifest 验真当成阻断证据,避免把发布包 checksum / manifest 漂移、env 漂移、systemd capability 残留、巡检失败或归档文件损坏带入后续阶段。
- 决策补充:正式直连 runbook 的即时证据 verifier 和最终证据根目录总审计内部复用 verifier 时,都必须使用 `--require-summary-ok`;总审计不能退化成只验 `manifest.files` hash,还要把 `manifest.summary.status=OK` 作为底层 verifier 严格模式的一部分。
- 决策补充:Pingora 切换命令证据生成端必须在执行前约束真实命令身份:`-- <command>` 本身必须是绝对路径,不能是文件系统根目录;真实命令和每个真实命令参数都不能包含换行或 NUL 字符。正式 runbook 因此直接执行 current release 随包 enable / rollback 脚本绝对路径,禁止用 `node`、`bash`、脚本名或其它 PATH 裸命令名包装。
- 决策补充:Pingora release readiness、canary access log 对账、direct live、direct preflight 和 cutover evidence bundle 的显式日志 / env 文件路径必须是绝对路径且不能是文件系统根目录。`--live-nginx-access-log`、`--live-pingora-access-log`、`--direct-pingora-access-log`、`--direct-health-patrol-env-file`、`--direct-preflight-env-file`、canary 对账脚本的 `--nginx-log-file` / `--pingora-log-file`、direct live 的 `--pingora-access-log`、direct preflight 的 `--env-file` 与 evidence bundle `--run-direct-live` 的 `--direct-pingora-access-log` 都不能指向 `/`,避免切换窗口把日志扫描或 env 复核误绑到根目录。证据包 `--run-direct-live` 的 direct URL、Host、probe token、SpacetimeDB 数据库名、Pingora access log 路径和 access log tail 行数还必须在配置层拒绝换行或 NUL,避免污染参数进入状态快照或 direct live 子命令后才失败。
- 决策补充:证据根目录总审计按 `--require-phase` 查找阶段证据时,只接受不带 `manifest.commandName` 的状态快照证据包;enable / rollback apply 的命令证据只能通过 `--require-command`、`--require-command-executable` 和 `--require-command-arg` 审计,不能冒充同名 phase 的阶段证据。
- 决策补充:直连回退的 Nginx smoke 不能只看 `curl --fail` / HTTP 200。`pingora-direct-rollback.sh` 支持 `--nginx-smoke-expect-body <片段>`;正式 cutover runbook 必须显式传入切换前真实 Nginx 入口和响应片段,不再默认假定 `/healthz` 会返回 `"ok":true`。dev 真实直连验证确认公网 Nginx 首页 `https://dev.genarrative.world/` 与 `<!doctype html>` 是可用 smoke,而固定 `http://127.0.0.1/healthz` / `"ok":true` 可能误卡回退或验证到错误入口。
- 决策补充:`check-pingora-release-readiness.mjs --require-direct` 是启用后直连复核,不再强制要求 `--direct-preflight-check-ports-free``--dry-run-cutover` 和启用前 preflight 仍必须要求端口空闲,用于证明 Nginx、Gitea 等占用 `80/443` 的进程已释放。dev 真实 direct 接管 `80/443` 后,启用后复核应看到端口由 Pingora 占用而不是空闲。
- 决策补充:Pingora 网关行为变更必须运行 `npm run check:pingora-gateway-smoke`;该脚本用临时 mock 上游验证静态路由、API 代理头、请求体限制、429 接流保护、上游断连 JSON 错误、维护模式和 SpacetimeDB WebSocket Upgrade,避免只靠单元测试遗漏接流路径。代理路径的上游失败必须返回稳定 JSON code,例如 `GATEWAY_UPSTREAM_ERROR` / `GATEWAY_UPSTREAM_TIMEOUT`。静态协商缓存、非读取方法和 Range 边界请求必须用固定 `X-Request-Id` 反查 Pingora access log,确认 `304`、`405`、`206`、`416` 这些本地响应状态也可进入切换证据链。
- 决策补充:Nginx 到 Pingora 的 canary 先采用 `deploy/nginx/snippets/genarrative-pingora-canary.conf` 的前缀手动入口,Server-Provision 只安装 snippet,不默认 include;启用时必须替换 probe token、限制来源,并用 `X-Genarrative-Nginx-Handoff: pingora-canary` 与 Pingora shadow 日志证明请求确实经过 handoff。
- 决策补充:Pingora shadow / canary 必须配置可落盘 access log,默认示例为 `/var/log/genarrative/pingora-gateway.access.log`Server-Provision 创建 `/var/log/genarrative`、安装 `deploy/logrotate/genarrative-pingora-gateway`,并让 systemd 沙箱允许写该目录。canary 对照时同时看 Nginx access log、Pingora access log 与 tracing 日志。
- 决策补充:Pingora 前缀 canary live 通过后不能只看 `X-Genarrative-Nginx-Handoff` 响应头,还必须运行 current release 随包 `scripts/check-pingora-canary-access-log-parity.mjs` 按 `request_id` 对照 Nginx handoff access log 和 Pingora access log,确认 method/status/path 没有漂移。Nginx canary exact `/healthz` 按真实 snippet 映射到 Pingora shadow `/__genarrative_pingora/healthz`,其余 canary 前缀路径按 rewrite 后路径比对;该脚本只读日志,不修改日志、不 reload Nginx 或 Pingora。
- 决策补充:Pingora 启动必须 fail fast 拒绝不安全配置。`GENARRATIVE_PINGORA_GATEWAY_PROBE_TOKEN` 不能是占位值或短 token;开启 `GENARRATIVE_PINGORA_GATEWAY_TRUST_X_FORWARDED_FOR=true` 时必须同时设置 `GENARRATIVE_PINGORA_GATEWAY_TRUSTED_FRONT_PROXY_CONFIRMED=true` 并确认前置代理已清洗 `X-Forwarded-For`;接流保护 `BURST>0` 时对应 `RATE_PER_SECOND` 不能为 `0`。当前接流保护默认只覆盖单进程单实例;`GENARRATIVE_PINGORA_GATEWAY_PROTECTION_ENABLED=true` 且 `GENARRATIVE_PINGORA_GATEWAY_INSTANCE_COUNT>1` 时,必须先落地共享限流 / 共享并发保护层并设置 `GENARRATIVE_PINGORA_GATEWAY_SHARED_PROTECTION_CONFIRMED=true`,否则网关启动和 direct preflight 都要失败;关闭网关保护后的多实例必须明确由前置 Nginx / LB 承担全局接流保护。
- 决策补充:Pingora Nginx canary snippet 变更必须运行 `npm run check:nginx-pingora-canary`;该脚本静态校验本机来源限制、handoff 响应头、probe token 占位、前缀 rewrite、低缓冲和 SpacetimeDB WebSocket Upgrade。目标 agent 或 CI 有 Nginx 时必须运行 `node scripts/check-nginx-pingora-canary.mjs --require-nginx`,把 snippet 包进临时 `server {}` 强制执行 `nginx -t`。
- 决策补充:Pingora 与 Nginx 的核心路由 parity 以 `deploy/pingora/nginx-route-parity.matrix.json` 为共享检查输入;涉及 Nginx 模板、Pingora 路由、限流分组或路由文档时必须同步更新矩阵,并运行 `npm run check:pingora-route-parity` 与 `cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml matches_nginx_route_parity_matrix`。Rust 单测读取同一份矩阵验证 `classify_path`、body limit 和接流保护分组,Node 检查同时覆盖生产 / 开发 Nginx 模板和试点文档片段。
- 决策补充:Pingora 前缀 canary 在目标 Nginx 中人工 include 并 reload 后,必须运行 `npm run check:pingora-canary-live`;该脚本只读访问 `__genarrative_pingora_canary` 前缀下的 healthz、代表性 API、SpacetimeDB identity、静态资源和拒绝入口,并强制校验 `X-Genarrative-Nginx-Handoff: pingora-canary`,避免只通过 snippet 静态检查却没有证明真实 handoff 链路可用。
- 决策补充:Pingora release readiness 分为源码全量门禁和 current release runtime-only 门禁。源码 checkout / CI / 构建环境继续运行默认 `check-pingora-release-readiness.mjs`,覆盖 Cargo、npm、Docker、Nginx 静态 / 真机校验和发布包构建烟测;目标机 `/opt/genarrative/current` 的启用前基础门禁和启用后 `--require-direct` 复核必须调用随包 `scripts/check-pingora-release-readiness.mjs --release-runtime-only`,只执行 current release 自审、启用前直连彩排状态、live canary、access log 对账、direct preflight、health patrol env 复核和 direct live smoke。未带 `--require-direct` 时 runtime-only 必须自动运行随包 `scripts/ops/pingora-direct-rehearsal-status.mjs --expect-public-gateway nginx --require-pingora-shadow --require-realpath-canary --require-current-release-gateway --fail-on-critical`,确认公网 `80/443` 仍由 Nginx 接流、Pingora shadow `127.0.0.1:18081`、realpath canary `127.0.0.1:18083` 和 current release 自审均通过;启用后 `--require-direct` 复核不再要求 Nginx 接公网彩排状态,改为检查 direct preflight、health patrol 直连模式和 direct live smoke。API release / current release 必须随包携带 `scripts/check-pingora-release-readiness.mjs`、`scripts/check-pingora-canary-live.mjs`、canary access log 对账脚本、直连彩排状态脚本和 direct preflight / live 子脚本;runtime-only 模式不得依赖源码 checkout、npm project root、Docker 或目标机 Nginx 静态校验。
- 决策补充:Pingora 直连静态响应必须显式写入缓存头。HTML、目录 index 和 SPA fallback 默认 `Cache-Control: no-cache``/assets/*` 与 `/admin/assets/*` 中带 Vite 指纹文件名的资源默认 `Cache-Control: public, max-age=31536000, immutable`;非指纹静态和 ACME challenge 默认 `no-cache`。三档由 `GENARRATIVE_PINGORA_GATEWAY_HTML_CACHE_CONTROL`、`GENARRATIVE_PINGORA_GATEWAY_ASSET_CACHE_CONTROL` 和 `GENARRATIVE_PINGORA_GATEWAY_STATIC_CACHE_CONTROL` 覆盖,配置值不能包含换行或 NUL`npm run check:pingora-gateway-smoke` 必须覆盖这些缓存头,避免直连后入口 HTML 被长期缓存或指纹资源失去长期缓存收益。
- 决策补充:Pingora 直连同一公网 IP 上的多域名时,必须先保住非主站域名的 Host 语义。dev 上 `dev.genarrative.world` 与 `git.genarrative.world` 共用 `80/443`,因此 direct env 必须配置 `GENARRATIVE_PINGORA_GATEWAY_GITEA_HOSTS=git.genarrative.world` 和 `GENARRATIVE_PINGORA_GATEWAY_GITEA_UPSTREAM=127.0.0.1:3000`,命中 Gitea Host 的请求整站代理到 Gitea,且不走应用维护页、API body limit 或网关接流保护。当前 Pingora TLS listener 只加载一组 cert/key;同时接管 `dev.genarrative.world` 和 `git.genarrative.world` 前,证书必须覆盖两个域名,不能使用单域名 SAN 证书。
## 2026-06-11 资产计费边界改为 fail-closed 并补偿退款
- 背景:图片 / 资产生成入口曾在钱包或 SpacetimeDB 预扣费连通性异常时允许继续生成,且失败后同步退款如果遇到 SpacetimeDB 短暂不可用缺少本地补偿;拼图首图后台任务还使用 api-server 进程内 HashSet 互斥,多实例下不能防重复。
- 决策:暂不实现 token 限流。所有资产生成预扣费改为 fail-closed,预扣费失败直接返回错误;支持 retry 的计费 ledger id 统一包含 HTTP `request_id`,前端静默刷新重试复用同一个 `x-request-id`。生成失败后的退款先同步调用 SpacetimeDB,失败则写入 `wallet-refund-outbox` 本地文件并由后台 worker 重放。拼图首图后台生成互斥改为 SpacetimeDB `puzzle_background_compile_task` 表,使用 `task_id + request_id` 作为 claim id,释放时校验 claim id,避免旧任务误删新租约。
- 影响范围:`api-server` 资产计费包裹、钱包退款补偿、拼图首图后台生成、`spacetime-module` 拼图 task 表、`spacetime-client` bindings/facade、前端 API request id 复用和后端架构文档。
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run check:spacetime-runtime-access`、`node scripts/check-server-rs-ddd-boundaries.mjs`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml wallet_refund_outbox`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml asset_operation`、`npm run test -- src/services/apiClient.test.ts`、`npm run check:encoding`。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-11 图片画布编辑器作为独立画布工程接入
- 背景:网站需要新增 Lovart 风格图片画布编辑器能力,既要支持素材栏、平移缩放、工具模式、吸附线、元数据窗口和修改结果并排展示,也要能保存当前用户的画布视图、图层布局和资源元数据。
- 决策:主站新增 `/editor` 对应 `image-editor` 阶段,编辑器作为独立图片画布工程挂在平台壳下,并在创作 Tab 提供入口;工程与资源通过 `editor_project` / `editor_project_resource` 落到 SpacetimeDB,经 `spacetime-client` facade 和 `/api/editor/projects*` BFF 读写。图片生成 / 修改 provider、计费和真实任务进度暂不接入,本期修改结果允许使用 mock 生成资源,但必须按生成资源元数据形状保存。
- 影响范围:主站路由、平台创作入口、图片画布编辑器组件、editor project API client、`api-server` BFF、`spacetime-client` facade、`spacetime-module` 表 / procedure、后端数据契约文档和前端架构文档。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run test -- src/services/image-editor/editorProjectClient.test.ts`、`npm run typecheck`、`cargo check -p spacetime-client --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:spacetime-schema`、`npm run check:encoding`、`git diff --check`、headless Playwright smoke。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-14 图片画布素材库按账号级持久化
- 背景:图片画布需要 Lovart 式素材管理,素材不应只挂在单个 project 临时状态里;用户在任意项目上传的图片素材,都应作为账号素材库在其它项目中可见,同时画布自身的图层、视图和分组仍属于项目下的画布数据。
- 决策:新增 `editor_asset_folder` / `editor_asset` 作为账号级素材库表,以 `owner_user_id` 为归属;`editor_project` 继续承载工程元数据,`editor_canvas` 继续承载 project 下的画布视图和图层布局,`editor_project_resource` 继续承载具体画布资源引用。素材库 CRUD 统一经 `spacetime-client` facade 和 `/api/editor/assets*` BFF,前端只保留选择模式、框选、拖拽上传、图层打组和小地图拖拽等交互状态,不直接绕过后端持久化。
- 影响范围:`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`server-rs/crates/spacetime-client/src/editor_project.rs`、`server-rs/crates/api-server/src/editor_project.rs`、`src/services/image-editor/editorProjectClient.ts`、`src/components/image-editor/ImageCanvasEditorView.tsx`、后端数据契约文档和图片画布前端技术方案。
- 验证方式:`npm run spacetime:generate -- --rust-only`、`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/services/image-editor/editorProjectClient.test.ts`、`npm run typecheck`、`npm run check:spacetime-schema`、`npm run check:encoding`、`cargo check -p spacetime-client -p api-server --manifest-path server-rs/Cargo.toml`、`git diff --check`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-06-15 图片画布角色图层新增动画生成入口
- 背景:图片画布已有角色形象图层标记 `assetKind="character"`,需要只对角色图片开放动画生成,不让普通素材误触发角色动画链路。
- 决策:角色动画入口只由画布图层 `assetKind="character"` 控制,在图片上方浮动工具条和右键菜单显示 `生成动画`;非角色图层不展示入口。点击后打开独立 `角色动画生成面板`,桌面端锚定到图片右侧,移动端按底部面板承接。前端固定提交 `seedance2.0-fast`、分辨率 / 比例 / 帧数 / 时长等生成参数,不提交价格字段;后端经 `/api/editor/character-animations/generations` 使用角色图作为首帧和尾帧生成视频,按模型定价计算扣费,并立即抽取 32 / 40 / 48 帧、绿幕去背后写入 OSS。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/services/image-editor/editorProjectClient.ts`、`server-rs/crates/api-server/src/character_animation_assets.rs`、`server-rs/crates/shared-contracts/src/assets.rs`、图片画布技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/services/image-editor/editorProjectClient.test.ts`、`cargo test -p api-server editor_character_animation --manifest-path server-rs/Cargo.toml`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-16 图片画布图标素材面板采用 Lovart 式参考卡与横向增宽布局
- 背景:图标素材生成面板里,规范入口与素材描述项过于平铺,且子面板内部采用滑动列表,和 Lovart 风格画布的参考卡 / 物料卡不一致。
- 决策:`生成图标素材` 面板不使用内部纵向滚动列表;每新增一个素材描述项就让面板整体增宽,保持描述项横向卡片一眼可扫。图标规范入口改为 Lovart 式参考卡:缩略图、名称、绑定状态和轻量动作分区分开呈现,独立菜单只负责来源切换,不再承载说明文案。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/index.css`、图标素材生成专项设计文档。
- 验证方式:新增或增删素材描述项时,面板宽度应随项数变化;图标规范入口应呈现参考卡视觉而非纯文本按钮;移动端下仍应固定在底部锚定,不出现内部滚动条。
- 关联文档:`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-16 图片画布图标素材与角色生成支持双图片模型
- 背景:图片画布需要一次生成多枚 UI 图标素材,并以一个透明图集回填画布;角色形象生成也需要和图标素材共用同一套图片模型选择、比例和大小口径。
- 决策:底部 `生成图标素材` 入口创建一叠空白图标占位和独立面板;图标规范参考图只允许绑定 `assetKind="icon-spec"`。`生成角色形象` 与 `生成图标素材` 均支持 VectorEngine `gemini-3.1-flash-image-preview`UI 显示 `nanobanana2`)和 `gpt-image-2`,默认 `nanobanana2`,用户在两类面板中切换过模型后下一次打开继续沿用上次模型。前端提交 `model`、`aspectRatio`、`imageSize`;后端不再按图标数量分 `512x512/1024x1024`,而是按模型归一尺寸:`nanobanana2` 走 `/v1beta/models/{model}:generateContent`,把参考图写成 `inline_data`,并在 `generationConfig.imageConfig` 写入比例和大小,`0.5K` 传 `"512"``gpt-image-2` 无参考图走 generations,有参考图走 edits,按文档支持的 `size` 字符串映射。图标素材先生成绿幕 spritesheet,再由 `platform-image` 绿幕去背后作为 `assetKind="icon-spritesheet"` 图集回填;角色图层写入 `assetKind="character"`。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/components/image-editor/useImageCanvasGenerationWorkflow.ts`、`src/services/image-editor/editorProjectClient.ts`、`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/platform-image/src/vector_engine/*`、`server-rs/crates/platform-image/src/generated_asset_sheets/sheet.rs`、图片画布技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/ImageCanvasGenerationPlacementModel.test.ts src/services/image-editor/editorProjectClient.test.ts`、`cargo test -p api-server editor_generation_dimensions_follow_model_options --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-image nanobanana_generate_content --manifest-path server-rs/Cargo.toml`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-16 图片画布生成面板与浮层层级收口
- 背景:图片画布底部工具栏和角色参考图行都存在局部滚动 / 裁切容器,生成规范菜单和角色规范来源菜单如果仍内嵌在触发按钮附近,会被边界遮挡;同时生成类面板打开后隐藏底部工具栏会破坏连续创作节奏。
- 决策:`生成规范`、`角色规范来源` 和 `图标规范来源` 菜单统一通过页面级 fixed portal 渲染到 `document.body`,触发按钮只提供定位锚点;点击 `生成工具`、`生成角色形象` 或 `生成图标素材` 后底部 AI 工具栏保持可见。点击画布空白区域只关闭当前生成面板并清除图片选中样式,不删除新建的占位图。角色面板中的 `角色规范` 与 `上传常规参考图` 入口统一改为 Lovart 式参考图卡片。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/index.css`、`src/components/image-editor/ImageCanvasEditorView.test.tsx`、图片画布前端技术方案和角色形象生成设计文档。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-16 图片画布图片信息页不展示生图 Prompt
- 背景:图片画布中每张生成图片的信息页原来展示 `Prompt` 和复制 Prompt,但该字段可能是后端组装后的生图提示词,不适合作为用户可见的图片输入信息。
- 决策:图片信息页删除生图 Prompt 展示和复制入口,改为展示生成时的用户面板输入快照,包括普通生成提示词、规范表单字段、角色设定、图标素材描述、修改要求,以及角色规范、常规参考图、图标规范和修改参考图等参考图卡片,并只允许“复制信息”复制当前可见字段。旧数据或上传图片没有输入快照时显示 `-`,不得回退展示内部 Prompt。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、图片画布 layout snapshot、图片画布技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx` 应覆盖图片信息页无 `Prompt`、无 `复制Prompt`,并展示普通生成、角色生成、图标素材和修改结果的输入快照。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-16 图片画布按 Resolution 原分辨率显示
- 背景:图片画布图层曾同时维护展示 `Size` 与资源 `Resolution`,旧布局快照里的 `width/height` 可能把大图缩成小图,导致画布视觉和图片信息里的原始分辨率不一致。
- 决策:图片图层不再把独立 `Size` 作为用户可见字段或展示真相;画布图层渲染宽高、悬浮尺寸胶囊和图片信息页统一以 `originalWidth/originalHeight`(即 `Resolution`)为准。旧 layout 中的 `width/height` 只作为缺少 Resolution 时的兼容兜底,不再优先决定展示大小。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、图片画布 layout hydrate、新建 / 上传 / 生成 / 快速编辑 / 图标素材生成结果铺回画布逻辑,以及图片画布技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "hydrates canvas images from Resolution instead of saved Size|opens generated image info from the corner button and creates a real right-side edit result|shows image resolution on hover"`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-19 图片画布上传入口按来源区分入画布语义
- 背景:底部工具栏上传入口选择文件后只进入素材库,未创建画布图层;同时上传图层和素材记录先使用固定兜底尺寸,浏览器异步读到图片原始尺寸前已经把错误尺寸持久化。
- 决策:底部工具栏上传是“上传到画布”入口,选中文件后写入默认素材文件夹并立即在当前画布视口中心创建图层;素材栏文件夹内上传仍只写入目标素材文件夹。上传图片必须在创建占位素材、画布图层和账号级素材记录前解析原图 ResolutionPNG/JPEG/WebP/GIF/BMP 先读文件头,无法解析时才回退浏览器解码或上传兜底尺寸。
- 影响范围:`src/components/image-editor/ImageCanvasEditorView.tsx`、`src/components/image-editor/useImageCanvasUploadWorkflow.ts`、`src/components/image-editor/ImageCanvasFileModel.ts`、`src/components/image-editor/ImageCanvasUploadModel.ts` 和图片画布技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasFileModel.test.ts src/components/image-editor/ImageCanvasUploadModel.test.ts src/components/image-editor/useImageCanvasUploadWorkflow.test.tsx`、`npm run test -- src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx -t "bottom toolbar uploads|multiple files as account-level assets"`、`npm run typecheck`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-17 图片画布底部生成视频接入 Lovart 面板
- 背景:编辑器画板底部工具栏需要新增 `生成视频`,并和现有 Lovart 式生成类面板、泥点展示、占位图和画布结果图层保持一致。
- 决策:`生成视频` 点击后创建独立视频生成占位和极简面板,提交 `POST /api/editor/videos/generations`;首期前端仅开放 `seedance2.0-fast`、`seedance2.0`、`kling3.0`、`kling3.0-omni`,不展示 Veo 模型入口,默认 `seedance2.0-fast`。后端必须严格区分 Seedance 2.0 Fast 与标准版:`seedance2.0-fast` 映射 `doubao-seedance-2-0-fast-260128``seedance2.0` 映射 `doubao-seedance-2-0-260128`,不得混用;固定文字转视频、`16:9`、标准模式和静音。后端复用 Ark / VectorEngine content generation task 轮询链路,下载视频后持久化到 OSS。生成结果在画布中写入 `mediaType="video"` 与 `assetKind="video"`,图片信息弹窗按视频显示为 `视频信息` / `视频类型`。生成类泥点价格统一走 `editor_generation_config`:角色动画固定 `seedance2.0-fast` 仍为 480p 每秒 10 / 720p 每秒 20;生成视频按模型分档,`seedance2.0-fast` 为 10 / 20`seedance2.0` 为 12 / 24`kling3.0` 为 15 / 30`kling3.0-omni` 为 20 / 40Veo 旧布局兼容价为 10 / 20。
- 影响范围:图片画布生成工作流、前端 editorProjectClient、`shared-contracts`、`api-server` 视频生成 BFF、编辑器技术方案和生成类面板方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorView.test.tsx -t "opens the bottom generate video panel"`、`npm run test -- src/components/image-editor/ImageCanvasMetadataModalView.test.tsx`、`npm run test -- src/services/image-editor/editorProjectClient.test.ts`、`cargo test -p shared-contracts editor_video --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_video --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-17 图片画布生成器快照纳入画布布局
- 背景:生成占位图和生成器对话框里包含用户输入、参数、参考图、占位框位置和生成结果绑定,刷新后丢失会让已生成图片无法回到 Lovart 式跟随编辑状态。
- 决策:生成器对象统一作为 `editor_canvas` 布局 JSON 的 `itemType: "generation-dialog"` 项保存,不新增表;成功生成后仍保留生成器快照和最后占位框位置,并通过 `generatedLayerId` 锚定到成品图层,渲染时不重复显示灰色占位框。图片类和生成视频结果同步写入账号级素材库;生成视频素材当前没有独立 poster 字段,素材栏以视频图标叠层展示。
- 影响范围:图片画布 layout 序列化 / hydrate、生成工作流、生成器渲染、项目自动保存、素材库回填和编辑器技术方案。
- 验证方式:`npm run test -- src/components/image-editor/ImageCanvasEditorModel.test.ts src/components/image-editor/useCanvasGenerationDialogs.test.tsx src/components/image-editor/useImageCanvasProjectPersistence.test.tsx src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`、浏览器刷新 smoke。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-06-18 编辑器画板音效待生成占位类型化
- 背景:`/editor/canvas` 新建视频、角色形象、音效和背景音乐待生成对象时沿用图片占位 icon,音效面板仍把 `type` 与 `tempo` 分成两个旧字符串选项,不符合 Lovart 式简洁参数按钮和 BPM 输入需求。
- 决策:画布待生成占位按生成器模式渲染专属空白样式、icon 与右上角标签:视频、角色、音效、背景音乐不再统一使用图片 icon;角标继续按 viewport 反向缩放。原计划中的 `type + tempo/BPM` 音效参数已在 2026-06-19 被 Vidu `prompt + duration` 契约替代,后续不要再恢复 `单次·120BPM` 入口。
- 影响范围:`src/components/image-editor/ImageCanvasWorldView.tsx`、`ImageCanvasGenerationComposerView.tsx`、`ImageCanvasEditorTypes.ts`、`ImageCanvasGenerationSubmissionModel.ts`、`src/services/image-editor/editorProjectClient.ts`、`server-rs/crates/shared-contracts/src/assets.rs`、`server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs`、`server-rs/crates/platform-audio/src/request.rs`。
- 验证方式:`npx vitest run src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasGenerationDialogModel.test.ts src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/ImageCanvasEditorModel.test.ts src/components/image-editor/ImageCanvasGenerationModel.test.ts src/services/image-editor/editorProjectClient.test.ts --reporter verbose`、`cargo test -p shared-contracts --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-audio --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_sound_effect --manifest-path server-rs/Cargo.toml`。
## 2026-06-18 图片画布角色动画改为角色动作生成占位
- 背景:旧角色动画入口点击后打开窄侧边面板,和 Lovart 式新建图片 / 视频占位不一致;点击生成好的角色图时也容易被误解为会自动进入重绘或生成面板。
- 决策:点击已生成角色图只选中图层并显示浮动工具栏,不自动弹出重绘、快速编辑或角色动画面板。点击工具栏或右键菜单的 `生成动画` 后,创建 `mode="character-animation"` 的画布 generation dialog,占位走统一避让落点与视口居中;占位使用角色动作 icon、橙色动作配色和右上角 `动作` 标签。角色动画参数面板复用原内容,但作为统一 generation composer 跟随占位底部,宽度对齐图片生成面板。提交成功后以首帧创建 `assetKind="character-animation"` 的角色动作图片图层,右上角标签显示 `动作`。
- 影响范围:`src/components/image-editor/useImageCanvasGenerationWorkflow.ts`、`useImageCanvasGenerationSurface.tsx`、`ImageCanvasCharacterAnimationPanelView.tsx`、`ImageCanvasWorldView.tsx`、`ImageCanvasGenerationLayerModel.ts`、`useImageCanvasGenerationSubmissionWorkflow.ts`、`src/index.css`。
- 验证方式:`npx vitest run src/components/image-editor/ImageCanvasWorldView.test.tsx src/components/image-editor/ImageCanvasGenerationDialogModel.test.ts src/components/image-editor/useImageCanvasGenerationWorkflow.test.tsx src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx --reporter=dot`、`npx vitest run src/components/image-editor/ImageCanvasEditorGenerationIntegration.test.tsx -t "character animation" --reporter=dot`。
## 2026-06-19 编辑器游戏音效默认改用 Vidu 文生音频
- 背景:VectorEngine Apifox `创建文生音频任务` 文档明确 Vidu `/ent/v2/text2audio` 请求体使用 `model: "audio1.0"`、`prompt`、`duration` 和可选 `seed`;编辑器此前把游戏音效提交到 Suno `task: "sound"`,与当前游戏音效默认模型要求不一致。
- 决策:`/editor/canvas` 的 `生成游戏音效` 入口继续保留,但默认且暂时唯一可用模型为 Vidu `audio1.0`,前端请求固定发送 `prompt`、`model: "audio1.0"` 和 `duration`,面板只显示 `Vidu` 与 `2-10` 秒时长选项,默认 `5` 秒;不再展示 `type`、`tempo`、BPM 或 Suno 文生音效入口。后端 `/api/editor/audios/sound-effects/generations` 只接受空模型或 `audio1.0`,拒绝 Suno / `chirp-*`,并按模型定价计算扣费;提交到 VectorEngine 时对内 `prompt` 同步映射为上游 body 的 `prompt` 与 `sound`,兼容 Apifox 文档和线上网关实际 `missing field sound` 校验;提交和轮询改走 Vidu `/ent/v2/text2audio` 与 `/ent/v2/tasks/{taskId}/creations`。背景音乐仍保留 Suno `/suno/submit/music`、`/suno/fetch/{taskId}` 和 wav clip 兜底逻辑。
- 影响范围:`server-rs/crates/platform-audio`、`server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs`、`server-rs/crates/shared-contracts/src/assets.rs`、`src/services/image-editor/editorProjectClient.ts`、`src/components/image-editor/ImageCanvasGeneration*`。
- 验证方式:`cargo test -p platform-audio --manifest-path server-rs/Cargo.toml --test vector_engine_audio`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_sound_effect`、`cargo test -p shared-contracts --manifest-path server-rs/Cargo.toml editor_audio_requests_and_response_use_canvas_audio_shape`、`npx vitest run src/services/image-editor/editorProjectClient.test.ts src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts src/components/image-editor/ImageCanvasGenerationComposerView.test.tsx src/components/image-editor/ImageCanvasGenerationModel.test.ts --reporter=dot`。
## 2026-06-19 角色主图抠图补充内部镂空检测
- 背景:编辑器角色形象和 Big Fish 正式图复用 `character_visual_assets::try_apply_background_alpha_to_png` 的角色主图透明背景后处理;原算法主要从画布四边扩散清理绿幕 / 近白背景,对主体包围的内部绿幕或近白镂空不稳定。
- 决策:角色主图抠图继续保留在 `api-server` 的 `character_visual_assets` 口径内,不切换到 `platform-image::generated_asset_sheets`。在边缘连通背景 BFS 之后、软边扩展和边缘去污染之前,新增内部背景连通域检测:只清理不触画布边界、像素数达到阈值、且为高置信绿幕 / 近白背景的内部连通域,避免误伤普通角色纹理。
- 影响范围:`server-rs/crates/api-server/src/character_visual_assets.rs`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
- 验证方式:`cargo test -p api-server --manifest-path server-rs/Cargo.toml character_background_alpha_removes_internal_green_holes`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_character_image_postprocess`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 编辑器角色形象回填改用通用抠图
- 背景:画板编辑器里的 `生成角色形象` 属于编辑器图片生成链路,用户要求把“人物抠图”改成通用抠图方法,不再与 RPG / 资产工坊角色主图专用后处理绑定。
- 决策:仅 `/api/editor/images/generations` 中 `kind = "character"` 的编辑器角色形象回填改用 `platform-image::generated_asset_sheets` 通用绿幕 / 近白去背能力,并开启内部镂空检测;透明背景处理正常成功时输出归一为透明 PNG。`character_visual_assets::try_apply_background_alpha_to_png` 继续服务 RPG 角色主图与 Big Fish 等“角色主图口径”调用者,本轮不改变这些链路。
- 2026-06-22 补充:所有明确设置绿幕用于后续抠图的 prompt 都必须固定写明 `#00FF00 / RGB(0,255,0)`,不能只写“纯绿色绿幕”或“接近 #00FF00”;编辑器角色图通用抠图额外开启暗绿 / 灰绿绿幕背景识别,只作为生成模型偏离标准亮绿时的兜底。该宽松识别只参与从画布边缘连通扩散出的背景清理,不参与全图断开绿色区域删除,避免误伤角色衣物或纹理。
- 2026-07-16 补充:透明背景处理最终失败、但 provider 原图已持久化时,角色任务以 `completed + warning` 收口,provider 原图作为唯一主图放入画布,不创建透明处理图。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`。
- 验证方式:`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_character_image_general_cutout`、`cargo test -p platform-image --manifest-path server-rs/Cargo.toml generated_asset_sheet_muted_green_alpha_requires_explicit_option`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
## 2026-06-22 图片画布外部生成统一改走 worker 队列
- 背景:图片画布的图片、改图、图标素材、UI 素材提取、角色动作、视频和音频生成都可能长时间等待外部 provider;如果继续由 HTTP handler 同步执行,生产只能扩 API 进程,不能独立扩生成吞吐。
- 决策:`GENARRATIVE_EXTERNAL_GENERATION_MODE=queue` 下,画板所有外部 provider 生成入口统一入 `external_generation_job`job kind 使用 `editor_image_generation`、`editor_image_edit`、`editor_background_removal`、`editor_icon_spritesheet_generation`、`editor_ui_design_asset_extraction`、`editor_character_animation_generation`、`editor_video_generation`、`editor_sound_effect_generation` 和 `editor_background_music_generation`。worker 成功后由后端写 `editor_project_resource` / `editor_asset` / `editor_canvas.layers_json`;前端只轮询 BFF job 状态并重新读取项目快照,不从队列 payload 或本地临时状态重建完成图层。
- 2026-06-29 补充:手动点击图层“去除背景”也属于图片画布外部 provider 任务,`/api/editor/images/background-removals` 在 queue 模式只入队 `editor_background_removal`worker 完成后用新 resource 原地替换目标 layer。任务列表只展示服务器 `external_generation_job` 返回的任务,禁止再用前端 local task 伪造抠图进度。
- 2026-06-30 补充:手动“去除背景”在有项目上下文时也创建画布生成占位并随请求提交 `canvasCompletion`worker / BFF 完成后通过现有生成完成链路把结果写入该占位;无 `canvasCompletion` 的旧路径才原地替换目标 layer。画布任务列表展示服务器阶段文案,生成中才显示耗时,排队不计时也不展示百分比。
- 补充:带 `dialogId` 的 `canvasCompletion` 必须读取后端当前 layout 中的最新 generation dialog placeholder;等待期间用户移动占位时,结果层要跟随最新占位。无 dialog 的重绘 / UI 素材提取等入口使用明确的右侧完成占位;生成器已删除时不把结果重新塞回画布。
- 影响范围:`server-rs/crates/api-server/src/editor_generation_queue.rs`、`server-rs/crates/api-server/src/external_generation_worker.rs`、`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/api-server/src/character_animation_assets.rs`、`server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs`、`src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.ts`、`src/services/image-editor/editorProjectClient.ts`。
- 验证方式:`cargo test -p api-server external_generation_worker --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server editor_canvas_generation --manifest-path server-rs/Cargo.toml`、`cargo test -p shared-contracts --manifest-path server-rs/Cargo.toml`、`npm run test -- src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx src/services/image-editor/editorProjectClient.test.ts`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-07-13 画布 Agent 工具执行状态复用外部生成任务
- 决策:画布 Agent 的 OSS 工具消息使用 `status=not_completed|completed|failed|cancelled` 和可选 `externalJobId`;不使用 `cancelledAt`,不新增关联表。`external_generation_job` 是排队、执行、lease 与计费结算的唯一真相;OSS status 只表达该消息回填结果,不复制 queued / running。确认接口按 `conversationId + messageId + toolName` 稳定去重并复用既有编辑器 worker job kind。
- 懒回填:`GET /conversation` 会在持有 conversation lock 后扫描 `status=not_completed` 且有 `externalJobId` 的工具消息,按 job id 定向读取主任务;完成时复用原工具 formatter 更新 system text、写入轻量媒体引用并标记 `completed`,任务本身失败时写入 `error` 并标记 `failed`。任务结果读取或 completed payload 解析 / formatter 首次失败后,在同一次 GET 内最多重试 3 次,每次等待 100ms 并重新读取主任务;读取失败或 completed 任务暂缺 `result_payload_json` 时,本次重试耗尽后保留 `not_completed + externalJobId` 供下次 GET 继续 reconcile,确定性的 payload 损坏、结构不兼容或 formatter 错误才在重试耗尽后写为 `failed`,避免致命错误永久循环。排队 / 执行保持 `not_completed`。worker 的 `result_payload_json` 只保留 formatter 与媒体引用所需的轻量生成回包,不向通用 summary 状态接口投影。
- 影响范围:画布 Agent 共享契约、确认/取消接口、编辑器生成入队 helper、对话状态展示与恢复。
- 验证方式:`cargo check -p api-server -p shared-contracts --manifest-path server-rs/Cargo.toml`、画布 Agent 定向前端测试、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 原生壳 WebView 刷新能力只保留受控当前页刷新
- 背景:Expo 移动壳和 Tauri 桌面壳都需要一个真实的宿主级刷新入口,供 H5 在检测到资源、登录态或运行态需要重新载入时请求宿主刷新当前容器;该能力不能演变成任意 URL 导航或原生 WebView ref 透传。
- 决策:新增 HostBridge method `app.reloadWebView` 和 H5 facade `reloadHostWebView()`。移动端只调用当前 `react-native-webview` 的 `reload()`,桌面端只调用 Tauri 主 `WebviewWindow.reload()`;该 method 不接受 payload,成功只表示宿主已发起刷新,刷新后当前 H5 上下文会卸载。继续把同源跳转留给 `navigation.openNativePage`,外链离开容器留给 `app.openExternalUrl`。
- 2026-06-18 追加:Expo 移动壳的外链离开容器路径必须在协议白名单后再调用 `Linking.canOpenURL`WebView 外域导航只有当前设备确认可打开时才调用 `Linking.openURL``app.openExternalUrl` 在系统不可打开时返回 `host_error`。该收紧不新增 HostBridge method,不把危险协议、相对路径或设备不可处理的外链留在带完整 HostBridge 的 WebView 内。
- 2026-06-18 追加:`AuthGate` 登录态身份边界刷新改为优先调用 `reloadHostWebView()`,用于登录成功、退出登录或从已登录变为未登录后的主站重新初始化;宿主未声明、返回失败或不可用时再回退浏览器 `window.location.reload()`,普通 token refresh、账号资料更新、主题和音量变化仍不触发整页刷新。
- 2026-06-20 追加:Expo 移动壳的 iOS `onContentProcessDidTerminate` 和 Android `onRenderProcessGone` 首次触发时复用当前 WebView 的受控 `reload()` 路径;短时间内连续进程恢复失败必须记录 `mobile WebView process failed` 日志,并转入既有原生加载失败兜底层。用户重试会清空进程失败窗口并再次刷新当前 WebView;全程不改写 URL、不注入额外脚本、不新增宿主恢复页面。
- 2026-06-18 追加:Expo 移动壳的 `onError` / `onHttpError` 只对同源 H5 主页面展示原生加载失败兜底层,用户重试时仍复用当前 WebView `reload()`;兜底不接管外域、危险协议、`about:blank` 或 favicon 失败,也不向 H5 注入错误事件。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/hostBridge.ts`、`apps/mobile-shell/`、`apps/desktop-shell/`、原生壳能力检查脚本和 HostBridge 架构文档。
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 原生壳音频文件导入只返回受控内容副本
- 背景:木鱼等固定玩法的音频上传面板需要在 Expo 移动壳和 Tauri 桌面壳内走真实系统选择器;如果直接暴露设备 URI、本机路径或通用文件系统能力,会把一次用户选择扩大成长期本地文件权限。
- 决策:新增 HostBridge method `file.importAudio`、H5 facade `importHostAudioFile()` 和通用音频输入面板接入。移动端通过 Expo DocumentPickerpicker 展示范围包含 `audio/*` 和当前允许的精确音频 MIME;桌面端通过 Tauri 系统文件选择框。HostBridge 返回 H5 前,两端仍只接受 `audio/mpeg`、`audio/mp4`、`audio/wav`、`audio/ogg`、`audio/webm` 或对应扩展名,并要求音频 bytes 与归一后的 MIME 匹配,单次不超过 20 MiB。宿主成功时只返回清洗后的文件名、MIME、base64 内容和字节数,不返回设备 URI 或本机绝对路径,也不开放通用文件系统。H5 将宿主结果转换成现有浏览器 `File`,继续复用 `readFileAsAsset(file, 'uploaded')` 音频处理链路。Expo 音频导入的 DocumentPicker 调用、大小校验、base64 读取和 HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/files.ts``dispatch.ts` 只委托文件模块。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/hostBridge.ts`、`src/components/common/CreativeAudioInputPanel.tsx`、`apps/mobile-shell/`、`apps/desktop-shell/`、原生壳能力检查脚本和 HostBridge 架构文档。
- 验证方式:`npm run check:native-shells`、`npm run test -- src/components/common/CreativeAudioInputPanel.test.tsx`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 原生壳音频导出只写入 H5 已持有字节
- 背景:木鱼创作的本地录音 / 上传音频会在浏览器侧处理成 `Blob`,原生壳需要能把这份本地处理结果交给系统保存 / 分享;但宿主不能替 H5 读取任意本地音频文件,也不能把文件系统能力扩成通用读写。
- 决策:新增 HostBridge method `file.exportAudio`、H5 facade `exportHostAudioFile()` 和通用音频输入面板导出入口。H5 只传当前页面已持有的 `base64Data`、清洗后的文件名和允许的 `audio/mpeg`、`audio/mp4`、`audio/wav`、`audio/ogg`、`audio/webm` MIME;移动端写入 Expo 缓存音频后交给系统分享 / 保存面板,桌面端打开 Tauri 系统保存对话框并写入音频字节。单次不超过 20 MiB,成功只返回文件名和字节数。Expo 音频导出的 payload 校验、缓存写入、系统分享 / 保存面板和 HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/files.ts``dispatch.ts` 只委托文件模块。`CreativeAudioInputPanel` 只在当前资产包含本地 `Blob`、`fileName`、允许 MIME 且宿主声明 `file.exportAudio` 时显示导出入口;远端已上传音频不展示导出。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/hostBridge.ts`、`src/components/common/CreativeAudioInputPanel.tsx`、`apps/mobile-shell/`、`apps/desktop-shell/`、原生壳能力检查脚本和 HostBridge 架构文档。
- 验证方式:`npm run check:native-shells`、`npm run test -- src/components/common/CreativeAudioInputPanel.test.tsx`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳 dev 端口固定
- 背景:Linux 多用户 dev 脚本会把 `npm run dev:web` 自动映射到用户端口段,而 Tauri `devUrl` 固定为 `http://127.0.0.1:3000/`;如果桌面壳直接运行 `tauri dev``beforeDevCommand` 启动的 H5 可能不在 Tauri 加载的端口上。
- 决策:桌面壳 `apps/desktop-shell/package.json` 的 `dev` 脚本显式设置 `WEB_PORT=3000 tauri dev`,让根 H5 dev server 与 Tauri `devUrl` 使用同一固定本地入口。该固定只作用于桌面壳调试,不改变普通 `npm run dev` / `npm run dev:web` 的 Linux 多用户端口段机制。
- 影响范围:`apps/desktop-shell/package.json`、`apps/desktop-shell/scripts/check-config.mjs`、桌面壳方案文档。
- 验证方式:`npm run desktop-shell:typecheck`、`npm run check:native-shells`。
## 2026-06-18 创作 Agent 文档上传接入原生壳文本导入
- 背景:创作 Agent 工作台已有“上传文档”入口,但在 Expo / Tauri 壳内仍只触发浏览器隐藏文件输入;移动和桌面壳已经具备 `file.importText` 的真实系统文档选择能力。
- 决策:创作 Agent 工作台在 `native_app` 且宿主声明 `file.importDocument` 时优先调用 `importHostDocumentFile()`,把宿主返回的文本类文档或 DOCX base64 副本转换成浏览器 `File` 后继续调用现有 `/api/runtime/creation-agent/document-inputs/parse`;旧壳只声明 `file.importText` 时才调用 `importHostTextFile()` 兜底。原生壳只负责受控选择和返回文档副本,不暴露设备 URI、本机路径或通用文件系统,也不在前端绕过后端文档解析、256KB 解析限制、docx 支持或错误口径。普通浏览器、小程序和未声明能力的裁剪壳继续使用原 `<input type="file">` 路径。
- 影响范围:`src/components/creation-agent/CreationAgentWorkspace.tsx`、`src/services/host-bridge/hostBridge.ts`、Expo / Tauri HostBridge 文档。
- 验证方式:`npm run check:native-shells`、`npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 创作 Agent 会话导出接入原生壳文本导出
- 背景:Expo / Tauri 壳已经实现 `file.exportText`,但 H5 创作 Agent 工作台还没有消费该能力;原生壳内又禁止网页自动下载和 `<a download>` 直接落盘,需要通过受控 HostBridge 保存用户可带走的文本。
- 决策:创作 Agent 工作台在 `native_app` 且宿主声明 `file.exportText` 时显示“导出会话”图标按钮,把当前会话标题、摘要、进度、锚点、消息、流式回复和输入草稿组装为 `text/markdown`,并在 H5 侧按共享 5 MiB 上限计算 UTF-8 byte 后再调用 `exportHostTextFile()`。Expo 文本导出的 payload 校验、缓存写入、系统分享 / 保存面板和 HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/files.ts``dispatch.ts` 只委托文件模块。普通浏览器、小程序、旧壳或裁剪壳不展示该入口;宿主取消或 unsupported 不触发浏览器下载回退,错误统一显示在 composer 上方现有状态条。
- 影响范围:`src/components/creation-agent/CreationAgentWorkspace.tsx`、`src/services/host-bridge/hostBridge.ts`、Expo / Tauri HostBridge 文档。
- 验证方式:`npm run check:native-shells`、`npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 反馈凭证上传接入原生壳图片导入
- 背景:帮助与反馈页的上传凭证入口在原生壳内仍只能触发浏览器隐藏文件输入;Expo / Tauri 壳已具备受控 `file.importImage` 图片选择能力,Expo 移动壳还具备真实 `file.captureImage` 相机拍摄能力。
- 决策:`PlatformFeedbackView` 在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的图片内容副本转换成浏览器 `File` 后继续走现有凭证预览和提交逻辑;移动壳声明 `file.captureImage` 时额外展示“拍摄凭证”入口,调用 `captureHostImageFile()` 后复用同一转换、校验和提交链路。反馈页仍保留最多 4 张、单张 1MB、总 4MB、图片 MIME 和 data URL payload 校验;宿主不暴露设备 URI、本机绝对路径或通用文件系统。普通浏览器、小程序、Tauri 桌面壳和未声明拍摄能力的裁剪壳不显示拍摄入口。
- 影响范围:`src/components/platform-entry/PlatformFeedbackView.tsx`、`src/services/host-bridge/hostBridge.ts`、Expo / Tauri HostBridge 文档。
- 验证方式:`npm run check:native-shells`、`npm run test -- src/components/platform-entry/PlatformFeedbackView.test.tsx`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 个人头像上传接入原生壳图片导入
- 背景:个人资料头像上传在 Expo / Tauri 壳内仍只触发浏览器隐藏文件输入,移动端相册选择和桌面系统选择框能力没有被头像流程复用。
- 决策:`RpgEntryHomeView` 的头像上传在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的图片内容副本转换成浏览器 `File` 后继续走现有头像读取、类型校验、5 MiB 大小限制、方形裁剪和 `updateAuthProfile({ avatarDataUrl })` 链路。宿主只负责受控图片选择,不暴露设备 URI、本机绝对路径或通用文件系统,也不新增 React Native / Tauri 专属头像编辑页;普通浏览器、小程序和未声明能力的裁剪壳继续使用原 `<input type="file">`。
- 影响范围:`src/components/rpg-entry/RpgEntryHomeView.tsx`、`src/services/host-bridge/hostBridge.ts`、Expo / Tauri HostBridge 文档。
- 验证方式:`npm run check:native-shells`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "profile avatar upload"`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 邀请码和兑换码填入接入原生壳剪贴板读取
- 背景:Expo / Tauri 壳已经具备真实 `clipboard.readText` 纯文本读取能力,但 H5 个人中心的邀请码填写和兑换码弹窗仍只能手输;用户从聊天、短信或活动页复制代码后在原生壳内缺少受控粘贴入口。
- 决策:个人中心的邀请码填写弹窗和兑换码弹窗在 `native_app` 且宿主声明 `clipboard.readText` 时显示“粘贴”动作,通过 H5 facade 读取宿主返回的纯文本并填入现有受控输入框。该动作不自动提交,不代表兑换成功,不把剪贴板读取扩展成图片、HTML、文件或监听事件,也不绕过既有 `redeemRpgProfileReferralInviteCode` / `redeemRpgProfileRewardCode` 后端接口。
- 影响范围:`src/components/platform-entry/PlatformProfileReferralModal.tsx`、`src/components/platform-entry/PlatformProfileRewardCodeRedeemModal.tsx`、`src/components/platform-entry/platformProfileHostClipboard.ts`、Expo / Tauri HostBridge 文档。
- 验证方式:`npm run check:native-shells`、`npm run test -- src/components/platform-entry/PlatformProfileReferralModal.test.tsx src/components/platform-entry/PlatformProfileRewardCodeRedeemModal.test.tsx`、针对变更文件执行 ESLint、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 原生壳身份与 capability 作用域门禁
- 背景:Expo 移动壳和 Tauri 桌面壳已经具备首批真实 HostBridge 能力,但如果包身份、桥版本、Android 默认权限、Tauri 窗口列表或 capability 文件在后续迭代中漂移,会把 H5 主站装进更宽的宿主权限面。
- 决策:移动壳配置门禁固定 Expo `name`、`slug`、`userInterfaceStyle`、`assetBundlePatterns`、`extra.genarrativeHostBridgeVersion`Android 源 `app.json.permissions` 只能手写 `POST_NOTIFICATIONS` 供 `notification.showLocal` 即时本地通知使用、手写 `RECORD_AUDIO` 供同源 H5 实时声音玩法使用,`CAMERA` 必须只由真实 `expo-camera` / `expo-image-picker` 插件为扫码和拍摄能力生成到最终 Expo public config,仍通过 `blockedPermissions` 阻断当前不需要的高风险权限;相册、相机、麦克风、通知权限说明必须锁定为对应真实能力的最小描述,不能退化成泛化采集、后台或远程推送能力说明;`apps/mobile-shell/scripts/check-config.mjs` 检查源 `app.json``apps/mobile-shell/scripts/check-expo-config.mjs` 检查 Expo CLI 最终解析出的 public config,防止 config plugin 或解析阶段引入身份、资源、权限和权限文案漂移。新增权限必须先有真实宿主能力、系统权限说明和 H5 fallback。桌面壳配置门禁固定唯一 `label=main` 主窗口,`src-tauri/capabilities/` 只能存在 `main.json`,且该 capability 只能绑定 `windows=["main"]`、`permissions=["allow-host-bridge-request"]`,继续只暴露 `host_bridge_request` 一个受控入口。
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`、`apps/mobile-shell/scripts/check-expo-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 桌面壳 JS guest 依赖收口
- 背景:Tauri 桌面壳的生产前端实际只通过 `HostBridge` 和注入的 `window.__TAURI__.core.invoke('host_bridge_request')` 与 Rust 通信;如果根 H5 包或桌面壳包安装 `@tauri-apps/api` / `@tauri-apps/plugin-*` JS 客户端包,后续容易绕过唯一 command 与 capability 边界。
- 决策:根 H5 `package.json` 和 `apps/desktop-shell/package.json` 不安装 `@tauri-apps/api` 或任何 `@tauri-apps/plugin-*` JS guest 包;opener、clipboard、dialog、notification 等桌面系统能力只保留 Rust Cargo 插件,由 `host_bridge_request` 内部分发。`apps/desktop-shell/scripts/check-config.mjs` 对两个 package 都做依赖门禁,Tauri CLI 仅作为构建工具保留。
- 影响范围:根依赖、桌面壳依赖、桌面壳配置检查和 Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 HostBridge request id replay
- 背景:H5 transport 会为每个 HostBridge 请求生成 id 并设置超时,但系统分享、外链打开、文件选择 / 保存、本地通知和窗口导航等宿主副作用如果收到重复 id,不应因为消息重试、快速双发或 WebView 事件抖动被执行两次。
- 决策:Expo 移动壳缓存已完成响应,并让进行中的同 id 请求共用同一个执行 PromiseTauri 桌面壳在唯一 `host_bridge_request` command 外层通过 `HostBridgeReplayState` 让同 id 请求等待 / 回放首次结果。重复 id 只返回首次响应,不二次执行宿主能力。已完成响应缓存上限由 `packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_RESPONSE_CACHE_MAX` 统一声明,Expo 壳直接导入,Tauri 壳保留 Rust 镜像并由桌面配置检查反查共享常量。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`apps/mobile-shell/src/host-bridge/bridge.ts`、`apps/mobile-shell/src/host-bridge/protocol.ts`、`apps/desktop-shell/src-tauri/src/host_bridge/`、两端配置检查、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts apps/mobile-shell/src/host-bridge/bridge.test.ts`、`npm run desktop-shell:test`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 HostBridge request envelope 校验
- 背景:HostBridge 请求来自 H5 WebView / Tauri 注入通道,TypeScript 类型不能替代宿主运行时校验;空 id、控制字符 id、过长 id 或未知 method 如果进入 replay / 能力分发,可能污染缓存、绕过方法白名单或造成错误语义混乱。
- 决策:共享契约提供 `isHostBridgeMethod` 和 `normalizeHostBridgeRequestId`Expo 壳直接复用,Tauri 壳镜像同一 `HOST_BRIDGE_METHODS` 和 request id 规则。request id 归一后必须为 1-120 字符且不含控制字符;未知 method 在进入能力分发前返回 `invalid_request`,只有白名单内但当前壳未实现的登录 / 支付等 method 返回 `unsupported_method`。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`apps/mobile-shell/src/host-bridge/bridge.ts`、`apps/desktop-shell/src-tauri/src/host_bridge/`、`apps/desktop-shell/src-tauri/src/main.rs`、两端配置检查、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 HostBridge method 白名单跨壳门禁
- 背景:Expo 壳直接引用 TypeScript 共享契约,Tauri 壳必须在 Rust 中镜像 HostBridge method 白名单;如果只靠人工同步,后续新增 method 时容易出现 H5 已发送、Expo 已处理、Tauri 仍按未知 method 拒绝,或 Tauri 额外接受共享契约外 method 的漂移。
- 决策:`HOST_BRIDGE_METHODS` 以 `packages/shared/src/contracts/hostBridge.ts` 为唯一协议来源。移动壳配置检查解析 `handleRequest` 的 method case,拒绝共享契约外 method;桌面壳配置检查解析 Rust `HOST_BRIDGE_METHODS`,要求与共享契约逐项一致。新增宿主 method 必须先更新共享契约,再在 Expo / Tauri 中实现或明确返回 `unsupported_method`。
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run mobile-shell:typecheck`、`npm run desktop-shell:typecheck`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 HostBridge event 白名单跨壳门禁
- 背景:HostBridge request method 已有共享白名单和跨壳检查,但宿主注入给 H5 的 event 名如果仍是裸字符串,AI sandbox 或壳层新增事件时可能绕过契约,导致 H5 订阅到共享协议外事件,或 Tauri Rust 镜像与 TypeScript 契约漂移。
- 决策:`HOST_BRIDGE_EVENTS` 以 `packages/shared/src/contracts/hostBridge.ts` 为唯一事件名来源,当前只包含 `app.lifecycle`、`network.statusChanged`、`navigation.canGoBack` 和 `file.imageDropped`;事件名必须存在于 capability 白名单,各宿主壳只声明自身真实发射的事件 capability。Expo 移动壳事件注入函数必须使用共享 `HostBridgeEventName` 类型;Tauri 桌面壳 `shell/events.rs` 镜像同一事件清单并在脚本生成前拒绝未知事件;H5 `nativeAppHostBridge` 只分发 `isHostBridgeEventName()` 认可的事件。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/nativeAppHostBridge.ts`、`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/src-tauri/src/shell/events.rs`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts src/services/host-bridge/nativeAppHostBridge.test.ts`、`npm run mobile-shell:typecheck`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 H5 HostBridge 事件订阅必须同时声明事件通道
- 背景:`navigation.canGoBack` 订阅已经同时要求 `host.events` 和具体事件 capability,但 `app.lifecycle`、`network.statusChanged` 与 `file.imageDropped` 一度只校验具体事件 capability;旧壳或裁剪壳如果缺少 `host.events`,H5 仍可能绑定到不存在或不受控的事件通道。
- 决策:H5 所有 HostBridge 事件订阅入口统一使用“双能力门控”:必须同时声明 `host.events` 和对应事件 capability,才允许 `subscribeNativeAppHostBridgeEvent(...)` 绑定监听;缺任一能力时返回空取消函数。事件类 capability 继续不要求 request handler`host.events` 只表示宿主会通过 HostBridge message 注入受控事件,不作为 request method。
- 影响范围:`src/services/host-bridge/hostBridge.ts`、`src/services/host-bridge/hostBridge.test.ts`、`scripts/check-native-shells.mjs`、宿主壳能力协议文档和 Expo / Tauri 宿主壳方案文档。
- 验证方式:`npm run test -- src/services/host-bridge/hostBridge.test.ts`、`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`;根级原生壳门禁会反查共享事件清单、四个 H5 订阅 facade 和 `canUseNativeHostEventCapability(...)`,避免后续事件订阅绕过 `host.events`。
## 2026-06-18 HostBridge capability / handler 关系门禁
- 背景:`HOST_BRIDGE_CAPABILITIES` 同时包含可请求 method 和事件类 capability。壳如果声明了 request method capability 但没有 handlerH5 会展示入口后收到 unsupported;壳如果处理了未声明 methodH5 又无法根据 capability 决定是否调用,容易形成隐藏能力或跨端漂移。
- 决策:移动壳配置检查展开 `MOBILE_HOST_CAPABILITIES` / `IOS_MOBILE_HOST_CAPABILITIES` 并解析 `handleRequest` case;桌面壳配置检查解析 `capabilities()` 与 Rust request 分发 match。凡共享契约中属于 request method 的 capability,被壳声明后必须有对应 handlerhandler 处理的 method 必须已被该壳声明,登录 / 支付等等待真实 SDK 的 method 只能保留明确 `unsupported_method` 路径。`host.events`、`app.lifecycle`、`network.statusChanged`、`file.imageDropped`、`navigation.canGoBack` 等事件 capability 不要求 request handler。
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run mobile-shell:typecheck`、`npm run desktop-shell:typecheck`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 原生壳 capability profile 来源收口
- 背景:Expo 移动壳、Tauri 桌面壳和方案文档都需要维护真实 capability 子集;如果移动端源码、桌面 Rust 镜像和文档各自手写完整清单,后续新增能力时容易出现入口 URL、`host.getRuntime` 回包、文档和门禁漂移。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 中的 `HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES`、`HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES`、`HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES` 和 `HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES` 是三端宿主壳 capability profile 来源。Expo 移动壳只通过 `apps/mobile-shell/src/host-bridge/capabilities.ts` 引用共享 profile 并选择平台差异;微信小程序 `miniprogram/host-bridge/protocol.js` 和 Tauri 桌面壳 `capabilities.rs` 仍保留运行时镜像,但 `miniprogram/host-bridge/protocol.test.js`、`apps/desktop-shell/scripts/check-config.mjs` 和 `npm run check:native-shells` 必须反查对应共享 profile。根级门禁同时反查宿主壳方案文档里的微信 / Expo / Tauri 能力清单,新增 capability 必须先进入共享白名单和对应平台 profile,再补真实壳实现、H5 fallback、测试和文档。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`miniprogram/host-bridge/protocol.js`、`miniprogram/host-bridge/protocol.test.js`、`apps/mobile-shell/src/host-bridge/capabilities.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run mobile-shell:typecheck`、`npm run desktop-shell:typecheck`、`npm run test -- packages/shared/src/contracts/hostBridge.test.ts miniprogram/host-bridge/protocol.test.js`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 原生壳本地生成物边界
- 背景:`npm run check:native-shells` 会生成 Expo `.expo/` 日志、Expo export smoke 临时目录、Tauri schema、Tauri 自动生成权限和 Rust `target/` 产物。这些文件是本机工具输出,不是生产源码;如果进入生产壳敏感词扫描或被误提交,会让门禁受工具版本、构建日志或自动生成格式影响。
- 决策:`.gitignore` 显式忽略 Expo `.expo/`、Expo export smoke、Tauri `target/`、Tauri `gen/` 和 Tauri `permissions/autogenerated/`;根级 `check:native-shells` 的生产壳扫描同样排除这些本地生成目录,只扫描可提交的壳源码和配置。手写 capability / 权限配置仍保留在扫描范围内。
- 影响范围:`.gitignore`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`git check-ignore -v` 检查本地生成目录,`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 移动壳渠道 SDK 依赖收口
- 背景:Expo 移动壳运行时依赖可能从根安装树解析;如果只检查 `apps/mobile-shell/package.json`,根 H5 包仍可能直接引入 Expo Updates、Sentry、Firebase Analytics、PostHog、Amplitude、Segment、CodePush 等移动端发布通道、崩溃上报或 analytics SDK,让壳边界绕过真实渠道契约。
- 决策:`apps/mobile-shell/scripts/check-config.mjs` 同时检查移动壳包和根 H5 包的直接依赖;在真实发布通道、采集字段、用户授权、隐私披露、签名 / 回滚策略和团队发布流程落地前,两处都不得安装上述移动渠道 SDK。现有即时本地通知、系统分享、文件导入导出等真实宿主能力不受影响。
- 影响范围:移动壳配置检查、根依赖边界和 Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 移动壳系统深链声明收口
- 背景:移动壳已经通过运行时归一限制 deep link 只能进入同源 H5 路径,但 iOS associated domains 和 Android intent filter 也属于安装包级接管范围;如果后续只做“包含主站”校验,安装包可能额外接管外域、明文协议或更宽路径。
- 决策:Expo 源配置和 Expo CLI public config 都必须把 iOS `associatedDomains` 固定为唯一 `applinks:www.genarrative.world`Android `intentFilters` 固定为唯一 `VIEW` / `autoVerify=true` 的 App Link 过滤器,category 只能是 `BROWSABLE` 和 `DEFAULT`data 只能包含 `scheme=https` 与 `host=www.genarrative.world`,不得声明额外 domain、protocol、pathPattern 或其它接管范围。配置检查从共享 `HOST_BRIDGE_PUBLIC_WEB_ORIGIN` 解析 expected host 后反查这些平台 manifest 字段,避免移动壳脚本把主站域名维护成第二来源;运行时 deep link 继续只映射同源路径并附加 HostBridge 上下文。
- 影响范围:`apps/mobile-shell/app.json`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/mobile-shell/scripts/check-expo-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 原生壳命令入口收口
- 背景:Expo / Tauri 壳的源码、权限和构建配置已经进入门禁,但 package scripts 仍属于真实开发和验收入口;如果后续把根级或壳级 dev / build / typecheck / test 命令改成临时快捷命令,就可能绕过 Expo public config、Metro export、Tauri dev、Tauri release build smoke 或根 H5 构建。
- 决策:移动壳 `apps/mobile-shell/package.json` 的 `dev`、`android`、`ios`、`test`、`config:smoke`、`export:smoke`、`typecheck` 和根 `mobile-shell:*` 入口必须保持指向真实 Expo / RN / Vitest / Expo config / Metro export / 配置检查流程;桌面壳 `apps/desktop-shell/package.json` 的 `dev`、`build`、`typecheck`、根 `desktop-shell:*` 入口,以及 Tauri `beforeDevCommand` / `beforeBuildCommand` 必须保持指向真实 Tauri dev / build、根 H5 `dev:web` 和桌面壳配置检查流程。两端配置检查负责拒绝命令入口漂移。
- 影响范围:根 `package.json`、`apps/mobile-shell/package.json`、`apps/desktop-shell/package.json`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 H5 Tauri command 入口收口
- 背景:桌面壳 Rust 侧已经只暴露 `host_bridge_request` 一个 command,但 H5 `nativeAppHostBridge` 如果直接写死 command 名或以后调用其它 Tauri command,会绕过共享 HostBridge 契约和桌面 capability 审计。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 导出 `HOST_BRIDGE_TAURI_COMMAND='host_bridge_request'` 作为 H5 到 Tauri 的唯一 command 名;`src/services/host-bridge/nativeAppHostBridge.ts` 必须通过该常量调用 `window.__TAURI__.core.invoke`。桌面壳配置检查同时对齐共享常量、Tauri build manifest、Rust `generate_handler!` 和 H5 transport,拒绝 H5 侧写死 command 字符串或调用其它 Tauri command。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/nativeAppHostBridge.ts`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- src/services/host-bridge/nativeAppHostBridge.test.ts packages/shared/src/contracts/hostBridge.test.ts`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 H5 Tauri HostBridge 请求超时
- 背景:共享 HostBridge request 已包含 `timeoutMs`React Native WebView transport 会在 H5 侧释放超时请求,但 Tauri transport 如果直接等待 `core.invoke`Rust command 卡住时 H5 也会一直等待,违背“每个请求必须有超时”的壳层约束。
- 决策:`src/services/host-bridge/nativeAppHostBridge.ts` 的 Tauri transport 必须通过前端侧超时封装调用 `HOST_BRIDGE_TAURI_COMMAND`,与 React Native WebView transport 共享 `timeoutMs` 归一化和 `timeout / host_bridge_timeout` 错误语义。桌面宿主迟到返回时不能改写 H5 已拒绝的请求结果;`apps/desktop-shell/scripts/check-config.mjs` 锁定 Tauri transport 的超时封装,避免后续退回裸 `invoke`。
- 影响范围:`src/services/host-bridge/nativeAppHostBridge.ts`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- src/services/host-bridge/nativeAppHostBridge.test.ts src/services/host-bridge/hostBridge.test.ts`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 原生壳请求超时边界单一来源
- 背景:H5 的 React Native WebView transport 和 Tauri transport 已共享请求超时语义,但默认超时与最大超时如果继续留在 H5 transport 本地常量中,后续共享契约、测试和壳配置门禁容易出现边界漂移。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 导出 `HOST_BRIDGE_DEFAULT_REQUEST_TIMEOUT_MS` 与 `HOST_BRIDGE_MAX_REQUEST_TIMEOUT_MS`,作为原生壳请求默认超时和最大超时的唯一声明来源;`src/services/host-bridge/nativeAppHostBridge.ts` 必须导入共享常量做 `timeoutMs` 归一化,不得在 H5 transport 本地重声明默认 / 最大超时。`npm run check:native-shells` 和桌面壳配置检查会拒绝回退到本地超时边界。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/nativeAppHostBridge.ts`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts src/services/host-bridge/nativeAppHostBridge.test.ts`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 原生宿主 runtime 回读短超时单一来源
- 背景:H5 主 App 进入 `native_app` 后会通过真实 `host.getRuntime` 回读宿主能力,但该回读只用于补齐能力缓存,不应该沿用普通宿主请求默认超时,也不应该在 H5 facade 里散落本地毫秒数。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 导出 `HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS`,作为 `refreshNativeAppHostRuntime()` / `getNativeAppHostRuntime()` 请求 `host.getRuntime` 时的短超时唯一来源;`src/services/host-bridge/hostBridge.ts` 必须导入共享常量,不得本地声明 `HOST_RUNTIME_REFRESH_TIMEOUT_MS`。根级原生壳门禁和桌面壳配置检查会拒绝回退到本地 runtime 回读超时。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/hostBridge.ts`、`src/services/host-bridge/hostBridge.test.ts`、`scripts/check-native-shells.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts src/services/host-bridge/hostBridge.test.ts`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 原生宿主用户交互超时单一来源
- 背景:文件导入 / 导出、图片选择 / 拍摄等 H5 HostBridge facade 请求需要等待系统面板或用户选择,不能使用普通短请求默认超时;如果每个调用点手写 `timeoutMs: 30000`,后续调整壳层交互超时时容易遗漏。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 导出 `HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS`,作为 H5 facade 发起文件导入 / 导出、图片选择 / 拍摄等用户交互型 HostBridge 请求的长超时唯一来源;`src/services/host-bridge/hostBridge.ts` 必须导入共享常量,不得继续手写 `timeoutMs: 30000`。根级原生壳门禁和桌面壳配置检查会拒绝回退到本地字面量。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/hostBridge.ts`、`src/services/host-bridge/hostBridge.test.ts`、`scripts/check-native-shells.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts src/services/host-bridge/hostBridge.test.ts`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 原生壳关键依赖版本收口
- 背景:Expo / React Native WebView / Tauri / Cargo 插件版本会直接影响 WebView 安全默认值、managed config 解析、production bundle、Tauri capability、插件初始化和 release 构建行为;如果只改依赖声明,壳行为可能绕过 HostBridge 门禁和现有验收口径静默漂移。
- 决策:移动壳配置检查锁定 `apps/mobile-shell/package.json` 和根 `package.json` 中当前 Expo SDK 56、React 19、React Native 0.86、`react-native-webview`、`react-native-safe-area-context`、Expo Clipboard / DocumentPicker / FileSystem / Haptics / ImagePicker / Linking / Network / Notifications / Sharing / StatusBar、TypeScript 与 Vitest 版本,并检查根 `package-lock.json` 的实际解析版本。桌面壳配置检查锁定 `apps/desktop-shell/package.json` 与根 `package.json` 的 Tauri CLI / TypeScript 版本,检查根 `package-lock.json` 的实际解析版本,并锁定 `src-tauri/Cargo.toml` 中 `tauri-build`、`tauri`、`base64`、`serde`、`serde_json` 和 clipboard、dialog、notification、opener、single-instance 插件版本及 Tauri `tray-icon` feature,同时检查 `src-tauri/Cargo.lock` 中桌面壳 direct dependency 的实际解析版本。后续升级这些依赖必须同步更新配置门禁、方案文档、lockfile 和验证结果。
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 原生 HostBridge 入站消息来源收口
- 背景:H5 主站会同时承载原生壳 HostBridge 和后续 AI H5 sandbox / GameBridge;如果 H5 侧只按 JSON envelope 识别 HostBridge response / eventsandbox iframe 可以构造同形 `postMessage` 干扰待处理宿主请求或伪造宿主事件。
- 决策:Expo 和 Tauri 注入给 H5 的 HostBridge response / event 统一带 `origin: window.location.origin` 和 `source: window``nativeAppHostBridge` listener 只接受无外部 source 或当前窗口 source 的消息,并拒绝非当前页面 origin。AI sandbox 后续继续使用独立 GameBridge allowlist,不允许直接结算 HostBridge 请求。
- 追加:Expo 移动壳事件注入必须在运行时调用共享 `isHostBridgeEventName()` 校验事件名,只有 `HOST_BRIDGE_EVENTS` 中的事件才能生成注入脚本;普通 response 仍可复用统一 message script,但不能绕过事件 allowlist 伪造新的宿主事件类型。`apps/mobile-shell/scripts/check-config.mjs` 必须反查该校验。
- 影响范围:`apps/mobile-shell/App.tsx`、`apps/desktop-shell/src-tauri/src/main.rs`、`src/services/host-bridge/nativeAppHostBridge.ts`、两端壳配置检查和 HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run test -- src/services/host-bridge/nativeAppHostBridge.test.ts src/services/host-bridge/hostBridge.test.ts`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 三端宿主桥接层文件结构对齐
- 背景:微信小程序壳、Expo 移动壳和 Tauri 桌面壳都在承接宿主能力;如果微信页面继续散落 `index.shared.js`,桌面端继续把桥接分发堆在 `main.rs`,后续新增登录、支付、文件、通知或 sandbox 转发能力时会很难跨端对照 owner。
- 决策:三端桥接层按职责对齐,但保留各宿主真实边界。微信小程序页面路由不改,`miniprogram/host-bridge/protocol.js` 只沉淀微信壳能力、页面 URL、结果 hash / storage key 和分享消息类型等常量,`dispatch.js` 只作为 `protocol`、`webView`、`payment`、`shareGrid`、`subscribeMessage` 的薄索引,真实协议归一、支付 / 订阅 / 分享结果编解码仍分别放在 `webView.js`、`payment.js`、`shareGrid.js`、`subscribeMessage.js`,页面目录只保留生命周期和装配,不把微信小程序硬改成 Expo / Tauri 的 request 总线;Expo 移动壳拆成 `apps/mobile-shell/src/host-bridge/protocol.ts`、`capabilities.ts`、`dispatch.ts`、`files.ts`、`scanner.ts`、`share.ts` 和 facade `bridge.ts`,分别负责 envelope / request 校验 / ok-failure 响应 / replay 基础类型、能力清单与 iOS 差异、method 分发、文件能力、扫码能力、分享能力和 WebView message 入口 / request id replay 编排;根 `App.tsx` 只装配 `apps/mobile-shell/src/shell/ShellApp.tsx``apps/mobile-shell/src/shell/*.ts(x)` 负责 WebView 容器、URL、导航、网络、生命周期、安全区、扫码 overlay 和 WebView policyTauri 桌面壳拆成 `apps/desktop-shell/src-tauri/src/app.rs`、`host_bridge/protocol.rs`、`capabilities.rs`、`dispatch.rs`、`files.rs`、`share.rs` 和 command facade `mod.rs`,分别负责 Tauri builder / plugin / window 装配、envelope / method 白名单 / request 校验 / replay 状态、能力清单、method 分发、文件能力、分享能力和 `host_bridge_request` command / replay 编排;`apps/desktop-shell/src-tauri/src/shell/*.rs` 承接运行态、入口 URL、导航 / 下载、网络、生命周期、拖拽图片、HostBridge 事件注入、深链、托盘、窗口状态持久化和 WebView 门面,`main.rs` 只做薄入口并调用 `app::run()`。`scripts/check-native-shells.mjs` 锁定三端桥接层目录清单,并拒绝移动根入口和桌面根入口重新承接宿主能力装配。
- 影响范围:`miniprogram/host-bridge/`、`miniprogram/pages/*/index.js`、`apps/mobile-shell/src/`、`apps/desktop-shell/src-tauri/src/`、`scripts/check-native-shells.mjs`、宿主壳方案文档。
- 验证方式:`npm run test -- miniprogram/host-bridge/webView.test.js miniprogram/host-bridge/payment.test.js miniprogram/host-bridge/shareGrid.test.js miniprogram/host-bridge/subscribeMessage.test.js miniprogram/pages/web-view/index.style.test.js`、`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 三端宿主桥接层结构文档反查
- 背景:三端桥接层已经拆出移动 `scanner.ts`、桌面 `window_state.rs` 等职责文件,但如果只更新代码和目录门禁,`宿主壳能力统一协议` 与 `ExpoReactNative与Tauri宿主壳方案` 可能继续保留旧清单,后续开发者按文档扩展时仍会把能力放回错误 owner。
- 决策:`scripts/check-native-shells.mjs` 的三端桥接层目录清单同时作为文档反查来源。根级门禁会确认两份前端架构文档都按完整相对路径列出微信桥接层、微信 shell、微信页面包装层、移动源码根 `env.d.ts`、移动桥接层、移动 shell、桌面入口、桌面桥接层和桌面 shell 的当前生产文件,并拒绝这些目录出现未登记子目录或生产入口;`apps/mobile-shell/scripts/check-config.mjs` 必须用精确移动壳源码清单拦截 `src/host-bridge` / `src/shell` 和 `src` 根入口单端结构漂移,`apps/desktop-shell/scripts/check-config.mjs` 必须用 Rust 根目录 entry、`host_bridge/` 文件清单、`shell/` 文件清单和 Rust 模块清单共同拦截桌面壳单端结构漂移。新增、删除或改名这些职责文件时,必须同时更新脚本清单、两份架构文档、单端门禁和相关实现,不允许只改一端。
- 影响范围:`scripts/check-native-shells.mjs`、`docs/【前端架构】宿主壳能力统一协议-2026-06-17.md`、`docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md`、三端宿主壳源码布局。
- 验证方式:`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 HostBridge 载荷边界单一来源
- 背景:文件导入导出、剪贴板、角标、本地通知和 request id 都已经在 Expo 与 Tauri 两套壳里有运行时校验;如果 MIME 清单、字节上限或文本长度只靠人工同步,新增文件类型或调整上限时会出现 H5 契约、移动壳和桌面壳互相漂移。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 是 HostBridge 载荷边界的声明来源,导出文本 / 图片 / 音频 MIME 清单、文档导入 MIME 清单、导入 / 导出字节上限、导出文件名 fallback / 长度上限、request id 长度、角标上限、窗口标题长度、外链 URL payload、分享 payload、剪贴板文本长度、触觉反馈 style 和本地通知标题 / 正文长度。Expo 移动壳必须直接导入这些共享常量,`apps/mobile-shell/scripts/check-config.mjs` 会拒绝移动壳重新本地声明文件大小或 MIME 清单;移动壳 `file.importText` / `file.importDocument` / `file.importAudio` 必须在读取文本内容或 base64 前,通过 picker `size` 或 Expo `File.size` 拿到可信 byte count 并完成上限校验,无法拿到可信大小时直接拒绝导入。H5 facade 消费 `file.importText` / `file.importDocument` / `file.importImage` / `file.captureImage` / `file.importAudio` / `file.imageDropped` 返回结果时必须分别通过共享导入结果 normalizer 再校验文件名、MIME、内容、base64、字节数和图片拖拽坐标,避免旧壳或异常壳返回超界数据被业务层消费。`share.open` 必须通过共享 `normalizeHostBridgeShareOpenPayload()` 把 `url`、`href`、`path`、`targetPath` 和 `work` 归一到公开 H5 同源 URLH5 facade 和 Expo 移动壳都执行该边界,Tauri 壳用 Rust URL parser 镜像同一规则。`app.openExternalUrl` 必须先通过共享 `normalizeHostBridgeExternalUrlPayload()` 清洗为 `{ url }`H5 facade 和 Expo 移动壳都执行该边界,Tauri 壳用 Rust URL parser 镜像同一协议清单。`app.setTitle` 必须拒绝空值和控制字符,并按共享 80 字符上限截断;H5 facade 和 Tauri 壳都执行该边界。`clipboard.writeText` / `clipboard.readText` 两个方向都必须执行同一个 100000 字符上限;H5 facade 发起 `clipboard.writeText` 前先按共享上限归一化 payload,Expo 与 Tauri 壳仍必须再次执行同一边界,不允许只信 H5 facade 的预校验。H5 facade 发起 `haptics.impact` 前也必须按共享 style 清单归一化,未知 style 不发往宿主,Expo 壳仍二次拒绝未知值。`file.exportText` 必须在 H5 facade 发起请求前通过 `normalizeHostBridgeExportTextPayload()` 预校验文件名、文本内容、可选 MIME 和 5 MiB 上限;可选 `mimeType` 只能来自 `HOST_BRIDGE_TEXT_MIME_TYPES`,缺省为 `text/plain`Expo 与 Tauri 都必须拒绝图片、音频或二进制 MIME,避免 H5 通过文本导出通道伪装落盘;`file.exportImage` / `file.exportAudio` 必须在 H5 facade 发起请求前分别通过 `normalizeHostBridgeExportImagePayload()` / `normalizeHostBridgeExportAudioPayload()` 预校验文件名、MIME、base64 和共享导出上限,Expo 与 Tauri 壳仍必须按真实字节和 MIME 二次校验,不允许只信 H5 预检。两端 config check 必须反查该边界。Tauri 桌面壳按 Rust 运行时代码镜像实现,`apps/desktop-shell/scripts/check-config.mjs` 必须反查共享契约并拒绝漂移。
- 追加:Tauri 桌面壳 `host.getRuntime` 回包必须由 `host_bridge/runtime.rs` 的 `desktop_runtime()` 单一函数生成,内部统一读取 `desktop_platform()`、`env!("CARGO_PKG_VERSION")`、`HOST_BRIDGE_VERSION` 和 `capabilities()`;同文件的 `desktop_host_bridge_runtime_response(&request)` 负责把该结构包装成 HostBridge 成功响应,`resolve_host_bridge_request` 只做 method 委托。`apps/desktop-shell/scripts/check-config.mjs` 必须拒绝把 `HostBridgeRuntime` 或 `ok(json!(desktop_runtime()))` 重新内联到 match arm,避免平台、版本、capability 来源或响应形状分叉。
- 追加:Expo 移动壳 `host.getRuntime` 返回的 `platform` 与 `capabilities` 必须来自同一个归一化平台值,避免 iOS/Android 能力清单与上报平台分开计算后漂移。`apps/mobile-shell/src/host-bridge/dispatch.ts` 先通过 `getMobileRuntimePlatform()` 得到 `ios` / `android`,再把同一个值传给 `resolveMobileHostCapabilities(platform)``apps/mobile-shell/scripts/check-config.mjs` 必须拒绝恢复为无参 `resolveMobileHostCapabilities()`。
- 追加:Expo 移动壳的 DocumentPicker 调用也属于 HostBridge 文件边界的一部分。文本、文档和音频导入必须固定 `copyToCacheDirectory: true`、`multiple: false`,并分别使用文本导入清单、`MOBILE_DOCUMENT_PICKER_TYPES` 和 `MOBILE_AUDIO_DOCUMENT_PICKER_TYPES`;宿主只读取用户本次选择后复制到缓存的单个文件副本,不扩展为多选、目录访问或长期设备 URI 访问。`apps/mobile-shell/scripts/check-config.mjs` 必须按函数精确反查这些 picker option。
- 追加:Tauri 桌面壳的系统文件对话框过滤器也是 HostBridge 文件边界的一部分:文本导出只允许 `txt/json/md/csv` 保存,文本导入只允许 `txt/md/markdown/csv/json` 选择,文档导入额外允许 `docx`,图片导入导出只允许 `png/jpg/jpeg/webp`,音频导入允许 `mp3/m4a/mp4/wav/ogg/webm`,音频导出只允许 `mp3/m4a/wav/ogg/webm`。`apps/desktop-shell/scripts/check-config.mjs` 必须按 method 精确检查 `.add_filter(...)` 与 `blocking_save_file` / `blocking_pick_file` 归属,避免把一次用户选择扩大成任意本地文件访问。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`apps/mobile-shell/src/host-bridge/files.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/src-tauri/src/host_bridge/`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run mobile-shell:typecheck`、`npm run desktop-shell:typecheck`、`npm run test -- packages/shared/src/contracts/hostBridge.test.ts`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 创作 Agent 参考图复用原生图片导入
- 背景:Creation Agent 的文档导入和会话导出已接入原生壳 HostBridge,但参考图上传仍只触发浏览器隐藏文件输入;在 Expo / Tauri 壳内这会绕开已实现的受控 `file.importImage` 系统选择器体验。
- 决策:`CreationAgentWorkspace` 的参考图上传在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的 base64 图片副本转换成浏览器 `File` 后继续交给现有 `onReferenceImageChange` 校验、预览和上传链路。用户取消原生选择时停留在壳流程内,不连带弹出浏览器文件输入;普通浏览器、小程序和未声明能力的裁剪壳继续使用原隐藏文件输入。
- 影响范围:`src/components/creation-agent/CreationAgentWorkspace.tsx`、`src/services/host-bridge/hostBridge.ts`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- src/components/creation-agent/CreationAgentWorkspace.test.tsx`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 汪汪声浪结果页三图槽位复用原生图片导入
- 背景:汪汪声浪结果页的玩家形象、对手形象和 UI 背景槽位已经支持浏览器文件输入、单槽上传、单槽重生成、试玩和发布,但 Expo / Tauri 壳内点击上传仍只能触发 WebView 的浏览器文件输入,没有复用已落地的受控 `file.importImage` 系统选择器体验。
- 决策:`BarkBattleResultView` 的三图槽位上传在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的 base64 图片副本转换成浏览器 `File` 后继续交给 `uploadBarkBattleAsset` 上传和当前槽位写回链路。用户取消原生选择时停留在壳流程内,不连带弹出浏览器文件输入;普通浏览器、小程序和未声明能力的裁剪壳继续使用原隐藏文件输入。
- 影响范围:`src/components/bark-battle-creation/BarkBattleResultView.tsx`、`scripts/check-native-shells.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- src/components/bark-battle-creation/BarkBattleResultView.test.tsx`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 视觉小说结果页复用原生图片和音频导入
- 背景:视觉小说结果页素材选择弹窗已经支持封面、角色立绘、场景背景、音乐和环境音上传,以及历史素材选择和 AI 图片生成;但在 Expo / Tauri 壳内点击上传仍只能触发 WebView 的浏览器文件输入,没有复用已落地的受控图片 / 音频系统选择器体验。
- 决策:`VisualNovelResultView` 的素材上传在 `native_app` 且宿主声明 `file.importImage` 或 `file.importAudio` 时优先调用 `importHostImageFile()` / `importHostAudioFile()`,把宿主返回的 base64 副本转换成浏览器 `File` 后继续交给 `uploadVisualNovelAsset` 上传和当前封面、角色、场景素材字段写回链路。用户取消原生选择时停留在壳流程内,不连带弹出浏览器文件输入;历史素材选择和 AI 图片生成保持原链路;普通浏览器、小程序和未声明能力的裁剪壳继续使用原隐藏文件输入。
- 影响范围:`src/components/visual-novel-result/VisualNovelResultView.tsx`、`scripts/check-native-shells.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- src/components/visual-novel-result/VisualNovelResultView.test.tsx`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 方洞结果页图片槽位接入原生壳图片导入
- 背景:方洞结果页的封面、背景、形状和洞口图片槽位已经支持浏览器文件输入、历史图选择、AI 生成和自动保存,但 Expo / Tauri 壳内点击上传仍只能触发 WebView 的浏览器文件输入,没有复用已落地的受控 `file.importImage` 能力。
- 决策:`SquareHoleResultView` 图片槽位弹窗在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的图片内容副本转换为 `data:<mime>;base64,<data>` 并写回当前槽位 `imageSrc`。该动作继续走现有 result edit state、自动保存、试玩和发布链路,不新增后端上传路径,不暴露设备 URI、本机绝对路径或通用文件系统;普通浏览器、小程序和未声明能力的裁剪壳继续使用原浏览器文件输入。
- 影响范围:`src/components/square-hole-result/SquareHoleResultView.tsx`、`src/services/host-bridge/hostBridge.ts`、方洞玩法链路文档与 Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- src/components/square-hole-result/SquareHoleResultView.test.tsx`、`npm run typecheck -- --pretty false`、`npm run check:encoding`、`npm run check:native-shells`、`git diff --check`。
## 2026-06-18 移动壳主动导航保留宿主上下文
- 背景:Expo 移动壳启动 URL 和 deep link 已经会给 H5 追加 `native_app`、`expo_mobile`、平台、版本和 capability query;但 H5 通过 HostBridge 调用 `navigation.openNativePage` 主动跳转同源 route 时,壳层只把裸同源 URL 交给 WebView,目标页首屏可能短暂或持续按普通浏览器运行态识别。
- 决策:`navigation.openNativePage` 仍只接受同源 H5 route,不新增真实原生页面、不放宽外域导航;通过校验后的目标 URL 在进入 WebView 前统一调用 `buildMobileShellUrl(...)` 重新附加当前 `MobileShellUrlOptions`,确保主动导航后的页面继续带 `clientRuntime=native_app`、`hostShell=expo_mobile`、真实平台、宿主版本和当前 capability 清单。
- 2026-06-20 追加:`buildMobileShellUrl(...)` 对启动 URL、deep link 和主动导航目标补写宿主上下文时,必须先覆盖旧 `clientRuntime`、`hostShell`、`hostCapabilities` 等宿主 query,确保输出只包含当前 Expo 壳真实上下文;移动壳配置检查反查 URL 单测中的旧 query 清洗边界。
- 影响范围:`apps/mobile-shell/src/host-bridge/`、`apps/mobile-shell/src/shell/ShellApp.tsx`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run mobile-shell:test`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run typecheck -- --pretty false`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 桌面壳主动导航保留宿主上下文
- 背景:Tauri 桌面壳 release / dev 入口和 deep link 都会给 H5 追加 `native_app`、`tauri_desktop`、当前平台、版本和 capability query;但 H5 通过 HostBridge 调用 `navigation.openNativePage` 主动跳转同源 route 时,如果只把裸同源 URL 交给主窗口,目标页可能按普通浏览器运行态启动。
- 决策:`navigation.openNativePage` 仍只接受 `https://www.genarrative.world` 同源 H5 route,不新增真实原生页面、不放宽外域导航;通过校验后的目标 URL 必须复用 `desktop_h5_url_with_host_context(...)`,与桌面 deep link 一样重写宿主上下文 query,确保主动导航后的页面继续带 `clientRuntime=native_app`、`hostShell=tauri_desktop`、当前平台、宿主版本和真实 capability 清单。
- 影响范围:`apps/desktop-shell/src-tauri/src/shell/url.rs`、`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 H5 原生壳返回锚点与完整运行态保留
- 背景:Expo / Tauri 壳已经通过 `navigation.canGoBack` 事件告知 H5 当前可回退状态,但 H5 如果直达二级页且本地 history 没有应用导航条目,Android 返回键或桌面后退菜单会缺少可落回的平台首页;同时 H5 页面内导航若只保留小程序 query,会让原生壳中的后续页面丢失 `hostShell`、平台、版本、桥接版本和 capability 清单。
- 决策:`HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS` 必须同时覆盖微信小程序来源字段和原生壳 `hostShell`、`hostPlatform`、`hostVersion`、`bridgeVersion`、`hostCapabilities``pushAppHistoryPath()` / `replaceAppHistoryPath()` 写入应用 history state 并保留完整宿主上下文。H5 通过 `useHostNavigationCanGoBack()` 只在宿主同时声明 `host.events` 与 `navigation.canGoBack` 时消费返回栈事件;原生壳内直达非平台首页、非 runtime 的二级 H5 route 且当前 history state 没有应用导航标记时,App 先把当前条目替换成 `/` 返回锚点,再把当前路径推回 history。H5 不读取任意原生 back-forward list。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/routing/appPageRoutes.ts`、`src/hooks/useHostNavigationCanGoBack.ts`、`src/App.tsx`、`scripts/check-native-shells.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts src/routing/appPageRoutes.test.ts src/hooks/useHostNavigationCanGoBack.test.tsx src/App.test.tsx`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 桌面壳窗口状态持久化
- 背景:Tauri 桌面壳已经具备系统托盘、单实例、深链和受控 HostBridge 能力,但用户调整主窗口尺寸、位置或最大化状态后,重启桌面 App 仍回到固定初始窗口配置;如果直接保存完整窗口状态,又可能把托盘隐藏后的可见性状态带到下次启动。
- 决策:桌面壳接入 `tauri-plugin-window-state`,并把插件配置收口到 `apps/desktop-shell/src-tauri/src/shell/window_state.rs`。只保存 `SIZE`、`POSITION` 和 `MAXIMIZED`,不保存 `VISIBLE`、`FULLSCREEN` 或 `DECORATIONS`;该能力属于宿主壳自身体验,不进入 HostBridge capability,不暴露窗口状态插件 command 给 H5。插件注册顺序固定为 single-instance 优先,其后才是 window-state、deep-link 和其它系统插件。`window_state.rs` 必须保留直接 Rust 单测证明这组 flags 边界,桌面壳配置门禁会反查该测试。
- 影响范围:`apps/desktop-shell/src-tauri/Cargo.toml`、`apps/desktop-shell/src-tauri/Cargo.lock`、`apps/desktop-shell/src-tauri/src/main.rs`、`apps/desktop-shell/src-tauri/src/shell/window_state.rs`、`apps/desktop-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`、`npm run desktop-shell:build -- --no-bundle`、`npm run check:native-shells`、`npm run typecheck -- --pretty false`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 桌面壳应用菜单
- 背景:桌面壳方案要求 Tauri 承接系统菜单,但当前桌面壳只有系统托盘菜单和 HostBridge 受控能力;可分发桌面包缺少常规应用菜单会让刷新、退出、系统编辑和窗口操作只能依赖 WebView 或托盘。
- 决策:新增 `apps/desktop-shell/src-tauri/src/shell/menu.rs` 注册 Tauri 应用菜单。应用菜单只复用宿主壳级显示主窗口、后退、前进、刷新主窗口和退出应用动作;后退 / 前进只执行固定 `window.history.back(); true;` 与 `window.history.forward(); true;`,无历史记录时按浏览器 no-op 处理,不新增 HostBridge method,也不接收 H5 payload 或任意脚本;编辑菜单和窗口菜单使用 Tauri 原生预定义项承接剪切、复制、粘贴、全选、最小化、最大化和关闭窗口。该能力不进入 HostBridge capability,不开放菜单 API、shell API 或任意窗口控制给 H5;菜单注册失败直接阻断启动,避免生产桌面壳缺少系统菜单仍静默运行。
- 影响范围:`apps/desktop-shell/src-tauri/src/main.rs`、`apps/desktop-shell/src-tauri/src/shell/menu.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档。
## 2026-06-22 桌面壳取消原生菜单栏
- 背景:桌面壳当前不需要 Tauri 原生菜单栏;继续保留 `shell/menu.rs` 会让窗口顶部出现额外菜单,并让目录门禁与实际 UI 目标产生漂移。
- 决策:删除 `apps/desktop-shell/src-tauri/src/shell/menu.rs`,启动流程不再调用 `register_desktop_app_menu(app)?`,桌面 shell 清单和配置门禁同步移除 `menu.rs` 与应用菜单相关强制片段。显示主窗口、刷新主窗口、退出应用和窗口恢复仍由系统托盘、单实例唤醒、deep link 唤醒与现有 WebView 导航能力承接;不新增 HostBridge method,也不向 H5 暴露菜单 API 或任意窗口控制。
- 影响范围:`apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/shell/mod.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`、`npm run desktop-shell:build -- --no-bundle`、`npm run check:native-shells`、`npm run typecheck -- --pretty false`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 移动壳 WebView 下载协议阻断
- 背景:移动壳已经通过 WebView 注入脚本阻断 `<a download>` 点击,并丢弃 iOS `onFileDownload` 事件;但 `blob:`、`data:`、`file:`、`filesystem:` 等下载协议导航仍可能在 `onShouldStartLoadWithRequest` 中进入普通同源 / 外链分流,脚本创建的下载链接也缺少行为级测试覆盖。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS` 是移动 WebView 禁止下载协议清单的唯一来源。`apps/mobile-shell/src/shell/webViewPolicy.ts` 统一承接移动壳下载策略,导航拦截和 WebView 注入脚本都必须复用该共享清单,阻断下载链接点击、危险下载协议链接、`window.open` 下载 URL 和程序化 anchor click`ShellApp` 在同源 / 外链分流前调用 `shouldBlockMobileWebViewNavigationRequest(...)`,命中 `blob:`、`data:`、`file:` 或 `filesystem:` 直接拒绝,不进入带完整 HostBridge 的 WebView,也不交给系统外部应用。移动端文件保存仍只能走受控 `file.exportText`、`file.exportImage`、`file.exportAudio` HostBridge method。
- 影响范围:`apps/mobile-shell/src/shell/webViewPolicy.ts`、`apps/mobile-shell/src/shell/webViewPolicy.test.ts`、`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run mobile-shell:test -- src/shell/webViewPolicy.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run typecheck -- --pretty false`、`npm run check:encoding`、`git diff --check`。
## 2026-06-18 桌面壳通知权限门禁
- 背景:桌面壳已经声明并实现 `notification.showLocal`,且 Tauri capability 只授权 `allow-host-bridge-request`;但 Rust handler 在清洗 payload 后直接调用 `notification.show()`,没有显式检查系统通知权限,也没有把权限拒绝固定成 HostBridge 失败语义。
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs` 在发送即时本地通知前先调用 `permission_state()`,已授权才发送;处于 prompt / prompt-with-rationale 时只在 Rust 侧调用 `request_permission()` 后复判;最终未授权返回 `host_error: notification permission denied`。`notifications.rs` 统一承接 payload 校验、权限检查、系统通知调用和 HostBridge 成功 / 失败响应映射,`dispatch.rs` 只保留 method 委托。桌面壳仍不把 `notification:*` 插件命令加入 capability permissions,不向 H5 暴露 notification 插件 JS guest API、远程推送、定时提醒或通知 token。
- 影响范围:`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`、`npm run check:native-shells`、`npm run typecheck -- --pretty false`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 桌面壳主窗口启动门禁
- 背景:Tauri 的手动窗口创建示例容易从 `app.config().app.windows[0]` 取配置;如果后续配置顺序变化或缺少 `label="main"`,桌面壳可能静默创建错误窗口,甚至在无主 WebView 的状态下完成启动,导致 HostBridge、生命周期、托盘、菜单和拖拽事件都挂不到真实主窗口。
- 决策:`apps/desktop-shell/src-tauri/src/app.rs` 启动时必须通过 `desktop_main_window_config(app)?` 按 `label="main"` 解析主窗口配置,并在创建 `WebviewWindowBuilder` 前调用 `desktop_window_config_with_runtime_platform(...)` 补写宿主上下文。缺少 `main` 时返回 Tauri `WindowNotFound` 阻断启动;配置门禁拒绝按 `windows[0]` / `get(0)` 兜底或 `if let Some(config)` 静默跳过主窗口创建。
- 影响范围:`apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run desktop-shell:test`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 移动壳 HostBridge 版本运行时来源
- 背景:移动壳 H5 入口 query 和 `host.getRuntime` 回包都需要稳定 `hostVersion`。如果 `runtime.ts` 手写版本字符串,即使配置检查能对比 `app.json`,发布时仍存在多处版本源需要人工同步。
- 决策:移动壳 `MOBILE_SHELL_HOST_VERSION` 必须通过移动壳 `app.json` 的 Expo `version` 配置解析,异常配置只回退到与 `app.json` / `package.json` 一致的受检 fallback。不新增 `expo-constants`、OTA 更新、渠道分发、应用安装信息业务或发布通道 SDK;配置检查拒绝 `MOBILE_SHELL_HOST_VERSION` 重新写死字符串。
- 影响范围:`apps/mobile-shell/src/shell/runtime.ts`、`apps/mobile-shell/scripts/check-config.mjs`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run mobile-shell:test`、`npm run mobile-shell:typecheck`、`npm run mobile-shell:config`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 移动壳角标上限共享契约来源
- 背景:`app.setBadgeCount` 的数量上限已经由共享 HostBridge 契约声明,但移动壳 iOS 角标错误文案仍可能手写边界数字,后续调整上限时会让壳层提示与契约漂移。
- 决策:Expo 移动壳 `app.setBadgeCount` 的校验和错误文案都必须消费 `packages/shared/src/contracts/hostBridge.ts` 的 `HOST_BRIDGE_BADGE_COUNT_MAX`;配置检查拒绝移动壳本地重声明角标上限。
- 影响范围:`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/src/host-bridge/bridge.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/bridge.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 儿童动作热身入口接入原生壳受控导航
- 背景:平台首页寓教于乐频道的儿童动作热身 Demo 入口仍直接调用 `window.location.assign('/child-motion-demo')`,在原生壳中绕过了已落地的 `navigation.openNativePage` facade,无法由 Expo / Tauri 壳统一附加宿主上下文和导航策略。
- 决策:`PlatformEntryFlowShellImpl` 的儿童动作热身入口在 `native_app` 且宿主声明 `navigation.openNativePage` 时必须优先调用 `navigateHostNativePage('/child-motion-demo')`;宿主未声明、返回失败、普通浏览器或小程序运行态才回退原浏览器跳转。`/child-motion-demo` 仍是固定内置 H5 体验,不走代码包下载流程,也不新增真实原生页面。
- 影响范围:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、宿主壳能力统一协议文档。
- 验证方式:`npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "native app opens child motion demo through host navigation bridge"`、`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 创作 Agent 轻输入参考图接入原生壳图片导入
- 背景:`CreativeAgentInputComposer` 仍通过隐藏浏览器文件输入读取参考图;在 Expo / Tauri 壳中会绕过已经落地的受控 `file.importImage` 能力,移动壳也无法复用真实 `file.captureImage` 拍摄能力。
- 决策:轻输入 composer 在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的图片副本转换成浏览器 `File` 后继续走 `readPuzzleReferenceImageAsDataUrl` 的图片类型、大小、压缩和 data URL 预览链路;移动壳声明 `file.captureImage` 时额外展示拍摄参考图入口,调用 `captureHostImageFile()` 后复用同一链路。用户取消宿主选择或拍摄时停留在壳流程内,不连带触发浏览器文件输入;普通浏览器、小程序和未声明能力的裁剪壳继续使用原隐藏文件输入。
- 影响范围:`src/components/creative-agent/CreativeAgentInputComposer.tsx`、`src/components/creative-agent/CreativeAgentInputComposer.test.tsx`、宿主壳能力统一协议文档。
- 验证方式:`npm run test -- src/components/creative-agent/CreativeAgentInputComposer.test.tsx`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 抓大鹅发布封面接入原生壳图片导入
- 背景:抓大鹅结果页发布弹窗的封面图和封面参考图仍通过浏览器隐藏文件输入读取;在 Expo / Tauri 壳中没有复用已落地的受控 `file.importImage` 系统选择器。
- 决策:`Match3DResultView` 发布封面图和封面参考图在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的图片副本转换成浏览器 `File` 后继续走现有 `readPuzzleReferenceImageAsDataUrl`、AI 重绘开关、参考图集合和 `generateMatch3DCoverImage` payload 链路。用户取消宿主选择时停留在壳流程内,不连带触发浏览器文件输入;普通浏览器、小程序和未声明能力的裁剪壳继续使用原隐藏文件输入。
- 影响范围:`src/components/match3d-result/Match3DResultView.tsx`、`src/components/match3d-result/Match3DResultView.test.tsx`、宿主壳能力统一协议文档。
- 验证方式:`npm run test -- src/components/match3d-result/Match3DResultView.test.tsx`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 RPG 角色参考图接入原生壳图片导入
- 背景:RPG 角色资产工作室的角色参考图仍通过浏览器隐藏文件输入读取;在 Expo / Tauri 壳中没有复用已落地的受控 `file.importImage` 系统选择器。
- 决策:`RpgCreationRoleAssetStudioModal` 的角色参考图上传在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的图片副本转换成浏览器 `File` 后继续走现有 `readFileAsDataUrl`、参考图集合和角色形象生成 payload 链路。用户取消宿主选择时停留在壳流程内,不连带触发浏览器文件输入;普通浏览器、小程序和未声明能力的裁剪壳继续使用原隐藏文件输入。
- 影响范围:`src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModalImpl.tsx`、`src/components/rpg-creation-asset-studio/RpgCreationRoleVisualSection.tsx`、`src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx`、宿主壳能力统一协议文档。
- 验证方式:`npm run test -- src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 RPG 作品封面上传接入原生壳图片导入
- 背景:RPG 作品封面编辑器的上传封面仍通过浏览器隐藏文件输入读取;在 Expo / Tauri 壳中没有复用已落地的受控 `file.importImage` 系统选择器,也无法在用户取消原生选择时停留在壳流程内。
- 决策:`WorldCoverEditor` 的作品封面上传在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的图片副本转换成浏览器 `File` 后继续走现有 10 MiB 校验、data URL 读取、图片尺寸读取、16:9 裁剪和 `uploadCustomWorldCoverImage` 保存链路。用户取消宿主选择时不连带触发浏览器文件输入;普通浏览器、小程序和未声明能力的裁剪壳继续使用原隐藏文件输入。
- 影响范围:`src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx`、`src/components/CustomWorldEntityEditorModal.test.tsx`、宿主壳能力统一协议文档。
- 验证方式:`npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx -t "作品封面"`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 RPG 作品封面参考图接入原生壳图片导入
- 背景:RPG 作品封面 AI 生成弹层的封面参考图仍通过浏览器隐藏文件输入读取;在 Expo / Tauri 壳中没有复用已落地的受控 `file.importImage` 系统选择器。
- 决策:`CoverImageGenerationModal` 的封面参考图上传在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的图片副本转换成浏览器 `File` 后继续走现有 `readImageFileAsDataUrl` 读取、参考图预览和 `generateCustomWorldCoverImage` payload 链路。用户取消宿主选择时不连带触发浏览器文件输入;普通浏览器、小程序和未声明能力的裁剪壳继续使用原隐藏文件输入。
- 影响范围:`src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx`、`src/components/CustomWorldEntityEditorModal.test.tsx`、宿主壳能力统一协议文档。
- 验证方式:`npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx -t "作品封面"`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 RPG 场景参考图接入原生壳图片导入
- 背景:RPG 场景图片 AI 生成弹层的自定义参考图仍通过浏览器隐藏文件输入读取;在 Expo / Tauri 壳中没有复用已落地的受控 `file.importImage` 系统选择器。
- 决策:`SceneImageGenerationModal` 的场景图片参考图上传在 `native_app` 且宿主声明 `file.importImage` 时优先调用 `importHostImageFile()`,把宿主返回的图片副本转换成浏览器 `File` 后继续走现有 `readImageFileAsDataUrl` 读取、参考图预览和 `rpgCreationAssetClient.generateSceneImage` payload 链路。用户取消宿主选择时不连带触发浏览器文件输入;普通浏览器、小程序和未声明能力的裁剪壳继续使用原隐藏文件输入。
- 影响范围:`src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx`、`src/components/CustomWorldEntityEditorModal.test.tsx`、宿主壳能力统一协议文档。
- 验证方式:`npm run test -- src/components/CustomWorldEntityEditorModal.test.tsx`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 H5 支付跳转接入原生壳外链入口
- 背景:个人中心充值的微信 H5 支付链接仍直接调用 `window.location.assign(...)`,在 Expo / Tauri 壳中会把承载主站的 WebView 导向外部支付页;同时原生壳尚未接真实支付 SDK,不能声明或伪造 `payment.request` 成功。
- 决策:`redirectToPaymentUrl(...)` 先调用 `openHostExternalUrl()`,在 `native_app` 且宿主声明 `app.openExternalUrl` 时把 H5 支付 URL 交给宿主系统浏览器;宿主未声明、拒绝或失败时才回退原浏览器跳转。该流程不改变后端到账事实,不新增原生支付能力。
- 影响范围:`src/services/payment/paymentRedirect.ts`、`src/components/platform-entry/usePlatformProfileCenterController.ts`、宿主壳能力统一协议文档。
- 验证方式:`npm run test -- src/services/payment/paymentRedirect.test.ts`、`npm run test -- src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx -t "jumps to h5 payment"`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 微信登录授权跳转接入原生壳外链入口
- 背景:`startWechatLogin()` 拿到后端微信 OAuth 授权 URL 后仍直接调用 `window.location.assign(...)`,在 Expo / Tauri 壳中会把承载主站的 WebView 导向外部授权页;同时原生壳尚未接真实登录 SDK,不能声明或伪造 `auth.requestLogin` 成功。
- 决策:`startWechatLogin()` 先调用 `openHostExternalUrl()`,在 `native_app` 且宿主声明 `app.openExternalUrl` 时把微信授权 URL 交给宿主系统浏览器;宿主未声明、拒绝或失败时才回退原浏览器跳转。该流程只收口外链打开方式,不改变后端微信 OAuth 回调、绑定手机号或登录态刷新事实。
- 影响范围:`src/services/authService.ts`、`src/services/authService.test.ts`、宿主壳能力统一协议文档。
- 验证方式:`npm run test -- src/services/authService.test.ts -t "wechat login"`、`npm run typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 登录与支付外链纳入 HostBridge 必扫调用链
- 背景:`check:native-shells` 已能自动发现直接导入 HostBridge facade 的 H5 生产文件,但登录授权和 H5 支付跳转属于敏感外部跳转路径,后续如果被重构到薄包装层或兼容导出,单纯自动发现可能让它们脱离临时替身词扫描和调用链漂移门禁。
- 决策:`scripts/check-native-shells.mjs` 的 H5 HostBridge 真实调用链必扫清单固定包含 `src/services/authService.ts` 和 `src/services/payment/paymentRedirect.ts`。这两个文件必须持续通过 `openHostExternalUrl()` 承接原生壳外链打开,不得绕回未受控的登录 / 支付伪实现。
- 影响范围:`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档、共享开发流程记忆。
- 验证方式:`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 登录状态异常重试纳入受控 WebView 刷新
- 背景:`AuthGate` 的登录成功、退出登录和身份边界变化已优先调用 `reloadHostWebView()`,但登录状态异常页的“重新尝试”仍直接执行浏览器刷新,在 Expo / Tauri 壳内会绕过 `app.reloadWebView` 受控入口。
- 决策:登录状态异常页重试复用 `reloadCurrentPageForAuthStateChange()`,先请求原生宿主刷新当前 WebView,宿主未声明、失败或不可用时再回退浏览器刷新。`AuthGate.test.tsx` 进入 `check:native-shells` 的 H5 HostBridge 测试清单,避免认证页刷新路径再次分叉。
- 影响范围:`src/components/auth/AuthGate.tsx`、`src/components/auth/AuthGate.test.tsx`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档和共享开发流程记忆。
- 验证方式:`npm run test -- src/components/auth/AuthGate.test.tsx`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 微信壳 capability 绑定真实流程门禁
- 背景:Expo / Tauri 壳已有单端配置检查,能反查声明的 request capability 是否有真实 handler;微信小程序壳不使用统一 request dispatcher,而是通过 WebView 登录页、支付页、分享目标消息和九宫切图页承接 `auth.requestLogin`、`payment.request`、`share.setTarget` 和 `share.open`,此前根级门禁只确认 capability profile 与页面路由一致,未显式绑定每个 capability 的真实流程和测试。
- 决策:`scripts/check-native-shells.mjs` 新增微信 capability flow contract。每个微信 capability 必须对应真实 `miniprogram/host-bridge/*`、`miniprogram/shell/*`、`miniprogram/pages/*` 文件,源码中必须保留关键页面工厂或 `wx.login` / `wx.requestPayment` / `wx.requestVirtualPayment` / `wx.saveImageToPhotosAlbum` 等真实宿主调用,并且对应测试必须在 `check:native-shells` 的微信壳测试清单内。
- 影响范围:`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档、共享开发流程记忆。
- 验证方式:`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 Tauri Info.plist 进入生产替身词扫描
- 背景:桌面壳 macOS 权限说明通过 `apps/desktop-shell/src-tauri/Info.plist` 合并进分发包;此前配置检查会校验 plist 路径和媒体权限文案,但生产替身词扫描扩展名未包含 `.plist`,会让该分发配置绕过 mock / fake / placeholder / 临时 等替身词门禁。
- 决策:桌面单端配置检查和根级 `npm run check:native-shells` 都把 `.plist` 纳入生产源码扩展名集合;`Info.plist` 既要通过媒体权限专项校验,也要和其它可分发壳配置一样禁止脚手架或替身文本。
- 影响范围:`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档、宿主壳能力统一协议文档和共享开发流程记忆。
- 验证方式:`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳文档导入边界门禁补齐
- 背景:Expo 移动壳文档导入实现已经从共享 HostBridge 契约导入 `HOST_BRIDGE_DOCUMENT_MIME_TYPES` 和 `HOST_BRIDGE_IMPORT_DOCUMENT_MAX_BYTES`,但单端配置检查的共享 payload 边界清单此前只强制文本、图片、音频等边界,未显式覆盖文档导入,后续容易把文档 MIME 或大小限制改成本地常量。
- 决策:`apps/mobile-shell/scripts/check-config.mjs` 的 `sharedPayloadBoundaryImports` 必须包含文档 MIME 清单和文档导入大小上限;移动壳文本 / 文档 / 图片 / 音频文件导入边界都要持续来自 `packages/shared/src/contracts/hostBridge.ts`。
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档和共享开发流程记忆。
- 验证方式:`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳导入文件名归一来源收口
- 背景:HostBridge 导出文件名已由共享 `normalizeHostBridgeExportFileName()` 清洗路径字符、非法字符、空白和长度;导入结果此前只在共享契约里 trim,Expo 移动壳却复用导出清洗器返回清洗后的导入文件名,导致 H5 复核与壳返回语义存在隐性差异。
- 决策:共享契约新增导出 `normalizeHostBridgeImportFileName()`,导入文本、文档、图片和音频结果都通过该函数清洗文件名;Expo 移动壳文件导入实现必须直接使用该导入专用函数,配置检查强制反查,避免继续混用导出函数或本地文件名规则。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`packages/shared/src/contracts/hostBridge.test.ts`、`apps/mobile-shell/src/host-bridge/files.ts`、`apps/mobile-shell/scripts/check-config.mjs` 和宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test`、`npm run mobile-shell:typecheck`、`npm run test -- packages/shared/src/contracts/hostBridge.test.ts`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 本地通知结果语义收口
- 背景:Expo 和 Tauri 壳的 `notification.showLocal` 此前成功时返回裸 `true`,H5 只能理解为调用成功,容易被误读成“用户实际看见通知”;移动壳巡检也指出该能力真实语义应是系统调度 / 交付,而不是展示确认。
- 决策:共享契约新增 `HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_RESULT`,结果为 `{ action: 'delivered_to_system' }`Expo 和 Tauri 壳成功后返回该结构,H5 facade 接受结构化结果并暂兼容旧壳 `true`。该结果只表示通知已交给系统通知层,不承诺用户可见、点击或送达回执。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/hostBridge.ts`、`apps/mobile-shell/src/host-bridge/notifications.ts`、`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`、两端配置门禁、测试和宿主壳能力统一协议文档。
- 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts src/services/host-bridge/hostBridge.test.ts`、`npm run mobile-shell:test`、`npm run desktop-shell:test`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 原生宿主二维码扫码超时单一来源
- 背景:二维码扫码属于真实相机交互,等待时间应长于普通宿主请求;此前 H5 `scanHostQrCode()` 直接手写 `timeoutMs: 60000`,会让扫码等待边界和共享 HostBridge 契约漂移。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 导出 `HOST_BRIDGE_SCANNER_TIMEOUT_MS`,作为 H5 facade 发起 `scanner.scanQrCode` 请求的唯一超时来源;`src/services/host-bridge/hostBridge.ts` 必须导入共享常量,不得继续手写 `timeoutMs: 60000`。根级原生壳门禁和桌面壳配置检查会拒绝回退到本地字面量。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`src/services/host-bridge/hostBridge.ts`、`scripts/check-native-shells.mjs`、Expo / Tauri HostBridge 方案文档和共享开发流程记忆。
- 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts src/services/host-bridge/hostBridge.test.ts`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 桌面网络探测超时单一来源
- 背景:Tauri 桌面壳的 `network.status` 会从主站 origin 解析 host / port 后做短超时 TCP 可达性查询;此前 `DESKTOP_NETWORK_CHECK_TIMEOUT_MS` 只留在 Rust 本地,后续调整网络探测节奏时可能与共享 HostBridge 文档和门禁漂移。
- 决策:`packages/shared/src/contracts/hostBridge.ts` 导出 `HOST_BRIDGE_DESKTOP_NETWORK_CHECK_TIMEOUT_MS`,作为桌面壳主站可达性探测超时的声明来源;Tauri Rust 侧保留同名职责镜像 `DESKTOP_NETWORK_CHECK_TIMEOUT_MS`,由 `apps/desktop-shell/scripts/check-config.mjs` 反查共享值并拒绝漂移。该边界只服务桌面宿主内部网络状态,不新增 H5 任意网络探测能力。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- packages/shared/src/contracts/hostBridge.test.ts`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 原生壳导航桥接边界
- 背景:`app.openExternalUrl`、`navigation.openNativePage` 和 `app.reloadWebView` 已是受控 HostBridge 能力,但移动端和桌面端 `dispatch` 仍直接承接外链归一、同源 H5 跳转、宿主上下文补写和 WebView 刷新细节,后续继续补壳能力时容易让分发层重新变厚。
- 决策:Expo 移动壳新增 `apps/mobile-shell/src/host-bridge/navigation.ts`,统一承接 HostBridge 外链打开、受控 H5 跳转、WebView 刷新和成功响应包装,底层继续复用 `src/shell/navigation.ts` 与 `src/shell/url.ts`Tauri 桌面壳新增 `apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs`,统一承接外链打开、同源 H5 跳转和主窗口刷新,底层继续复用 `shell::navigation` 的 URL 归一和宿主上下文补写。两端 `dispatch` 只保留 method 委托,配置检查会拒绝分发层直接调用外链 opener、URL 归一、WebView navigate / reload 细节或包装导航成功响应。
- 影响范围:`apps/mobile-shell/src/host-bridge/navigation.ts`、`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run mobile-shell:typecheck`、`npm run mobile-shell:test -- src/host-bridge/bridge.test.ts`、`npm run desktop-shell:typecheck`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-19 原生壳网络查询桥接边界
- 背景:`network.status` 已由 Expo shell network 和 Tauri shell network 承接真实系统 / 主站可达性查询,但两端 HostBridge `dispatch` 仍直接调用底层 network helper 或异步阻塞包装,继续补壳能力时容易让分发层重新承接宿主细节。
- 决策:Expo 移动壳新增 `apps/mobile-shell/src/host-bridge/network.ts`,统一承接 `network.status` HostBridge 查询和成功响应包装,并复用 `src/shell/network.ts`Tauri 桌面壳新增 `apps/desktop-shell/src-tauri/src/host_bridge/network.rs`,统一承接 `network.status` HostBridge 查询、成功响应和 `host_error` 失败映射,并复用 `shell::network::resolve_desktop_network_status`。两端 `dispatch` 只保留 method 委托,配置检查会拒绝分发层直接导入 shell network、直接执行 `resolve_desktop_network_status`、包装移动网络成功响应或重新映射桌面网络错误。
- 影响范围:`apps/mobile-shell/src/host-bridge/network.ts`、`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/desktop-shell/src-tauri/src/host_bridge/network.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run mobile-shell:typecheck`、`npm run mobile-shell:test -- src/host-bridge/bridge.test.ts`、`npm run desktop-shell:typecheck`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面角标与通知响应边界
- 背景:Tauri `app.setBadgeCount` 和 `notification.showLocal` 的系统调用已分别收在 `badge.rs` 与 `notifications.rs`,但 `dispatch.rs` 仍把 `Result<(), HostBridgeResponse>` 映射成 `ok(true)`,继续让分发层知道能力成功响应形状。
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs` 和 `apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs` 统一返回 `HostBridgeResponse`,各自承接 payload 校验、系统调用、成功响应和失败响应映射;`dispatch.rs` 只保留 method 委托。桌面壳配置检查会拒绝 `dispatch.rs` 重新对这两个 method 做 `match` 或包装 `ok(true)`。
- 影响范围:`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/scripts/check-config.mjs`、`docs/project-memory/shared-memory/decision-log.md`。
- 验证方式:`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳 HostBridge 事件失败不可静默
- 背景:Tauri 桌面壳已经声明 `host.events`、`app.lifecycle`、`navigation.canGoBack` 和 `file.imageDropped`H5 会据此订阅生命周期、返回栈和拖拽图片事件;如果 WebView 事件脚本注册或发射失败仍被静默忽略,H5 会误以为宿主能力可用。桌面网络变化事件在 Rust 侧具备真实事件源前不声明 `network.statusChanged`。
- 决策:桌面壳启动阶段安装 `navigation.canGoBack` 脚本失败时直接阻断启动;生命周期首发、页面加载重放、窗口生命周期事件和拖拽图片事件阶段的 `app.lifecycle`、`navigation.canGoBack`、`file.imageDropped` 失败必须通过统一 helper 记录日志,不允许 `let _ = register_desktop_*`、`let _ = emit_current_*` 或 `let _ = emit_desktop_image_drop_event` 静默吞错。配置检查反查该错误处理路径,并禁止桌面网络事件回退到 WebView `online` / `offline`。
- 影响范围:`apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/shell/lifecycle.rs`、`apps/desktop-shell/src-tauri/src/shell/file_drop.rs`、`apps/desktop-shell/src-tauri/src/shell/webview.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳深链打开失败不可静默
- 背景:`genarrative://` 和同源 HTTPS 深链是桌面壳的生产入口;如果深链归一成功后 `window.navigate(...)` 或恢复主窗口失败却被静默忽略,用户会看到深链无反应且没有可排查日志。
- 决策:桌面壳深链打开必须让 `open_desktop_deep_link_url(...)` 返回 `tauri::Result<()>`,窗口导航和 `show_main_window(...)` 失败统一记录日志;配置检查拒绝深链模块继续使用 `let _ = window.navigate(...)` 或 `let _ = show_main_window(...)` 静默吞错。
- 影响范围:`apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳菜单托盘与单实例动作失败不可静默
- 背景:桌面壳托盘菜单和单实例唤醒是生产用户恢复、刷新和导航主窗口的宿主入口;如果这些动作失败仍被 `let _ = ...` 静默忽略,用户会看到托盘或二次启动无反应且无法排查。
- 决策:后退、前进、刷新主窗口,托盘显示、刷新主窗口,以及单实例唤醒主窗口动作失败必须走统一桌面宿主事件日志;配置检查拒绝这些入口继续对主窗口动作使用 `let _ = ...` 静默吞错。
- 2026-06-21 调整:托盘注册失败日志只记录 `desktop tray registration failed` 固定标签,不输出 Tauri tray 插件错误详情;配置检查拒绝重新拼接 `: {error}`。
- 影响范围:`apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳方案文档。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳外链与托盘关闭失败不可静默
- 背景:桌面壳 HostBridge 外链、WebView 新窗口外链接管和托盘关闭隐藏都会改变用户当前窗口状态或离开主 WebView;如果 opener、生命周期注入或窗口隐藏失败仍被静默忽略,用户会看到外链、关闭或托盘行为无反应且没有可排查日志。
- 决策:`open_normalized_desktop_external_url(...)` 返回 `tauri::Result<()>`HostBridge 外链打开和 WebView 新窗口外链接管失败都必须通过统一桌面宿主事件日志记录;托盘关闭主窗口前的 `app.lifecycle` 注入和 `hide()` 失败也必须记录日志。配置检查拒绝这些路径继续使用 `let _ = ...` 静默吞错。
- 影响范围:`apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳方案文档。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳深链注册失败不可静默
- 背景:Tauri 桌面壳深链 scheme 注册决定已安装桌面包能否从系统链接唤醒;如果 `register_all()` 失败后静默继续,用户会看到深链无反应且没有可排查日志。
- 决策:`register_desktop_deep_link_schemes(...)` 必须把 `app.deep_link().register_all()` 结果交给同日志格式的 `log_desktop_deep_link_register_result(...)`,并返回布尔结果供测试和门禁反查;失败日志只记录 `desktop host event failed for deep_link.register` 固定标签,不输出 deep-link 插件错误详情;桌面壳配置检查拒绝重新出现 `let _ = app.deep_link().register_all()`。
- 影响范围:`apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/scripts/check-config.mjs`。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::deep_link`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳冷启动深链读取失败不可静默
- 背景:Tauri deep-link 插件在桌面壳冷启动时通过 `get_current()` 交出系统传入的初始 URL;如果读取失败后只回到默认首页,用户会看到深链启动目标丢失,开发侧也无法区分是链接被拒绝、插件不支持还是系统读取异常。
- 决策:桌面壳冷启动当前 deep link 读取必须经过 `log_desktop_deep_link_current_result(...)`;读取失败只记录 `desktop host event failed for deep_link.current` 固定标签后安全回到默认入口,不输出 deep-link 插件错误详情;读取为空继续无声 no-op,读取成功再逐条执行受控 URL 归一和打开。桌面壳配置检查拒绝重新出现 `if let Ok(Some(urls)) = app.deep_link().get_current()` 静默分支。
- 影响范围:`apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/scripts/check-config.mjs`、宿主壳方案文档。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::deep_link`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动 HostBridge 外链打开异常不可静默
- 背景:Expo 移动壳的 `app.openExternalUrl` 会调用系统 `Linking.canOpenURL` / `openURL` 离开 WebView;如果系统 API reject 后只返回 H5 稳定失败,真机上外链无反应时缺少宿主侧排查线索。
- 决策:`openMobileHostBridgeExternalUrl(...)` 捕获系统外链打开异常时必须记录 `mobile HostBridge navigation failed for external.open`HostBridge 回包仍只暴露稳定 `host_error: external URL cannot be opened`,不透传系统异常细节。配置检查反查日志 helper、`catch (error)` 和对应测试断言。
- 影响范围:`apps/mobile-shell/src/host-bridge/navigation.ts`、`apps/mobile-shell/src/host-bridge/navigation.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`、宿主壳方案文档。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/navigation.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳生命周期窗口状态读取失败不可静默
- 背景:Tauri 桌面壳 `app.lifecycle` 事件会驱动 H5 游戏循环、背景音乐和固定玩法音频暂停 / 恢复;如果 `is_visible()`、`is_minimized()` 或 `is_focused()` 读取失败后静默使用默认值,生命周期状态可能错误且难以排查。
- 决策:`emit_current_desktop_lifecycle_event(...)` 必须通过 `resolve_desktop_lifecycle_window_flag(...)` 读取窗口可见、最小化和焦点状态;读取失败时记录 `desktop host event failed for app.lifecycle.<field>`,再使用保守默认值。桌面壳配置检查拒绝重新出现 `window.is_visible().unwrap_or(...)`、`window.is_minimized().unwrap_or(...)` 或 `window.is_focused().unwrap_or(...)`。
- 2026-06-21 调整:生命周期窗口状态读取失败和 `app.lifecycle` / `navigation.canGoBack` 等桌面宿主事件注入失败只记录固定阶段标签,不把 Tauri 错误详情写入可分发桌面壳 stderr;配置检查拒绝生命周期日志重新输出 `: {error}` 详情。
- 影响范围:`apps/desktop-shell/src-tauri/src/shell/lifecycle.rs`、`apps/desktop-shell/scripts/check-config.mjs`。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::lifecycle`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳返回栈状态同步失败不可静默
- 背景:Tauri 桌面壳 `navigation.canGoBack` 注入脚本会把当前 H5 history index 写入 `window.history.state`;如果 `replaceState(...)` 因页面状态异常或浏览器限制失败后静默吞掉,H5 返回栈事件可能漂移且无排查线索。
- 决策:`desktop_navigation_state_script()` 的 `replaceCurrentState()` catch 路径必须输出 `desktop navigation state sync failed` 浏览器 console 警告;配置检查拒绝该注入脚本重新出现空 catch。
- 影响范围:`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/scripts/check-config.mjs`。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::navigation`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳网络探测失败不可静默
- 背景:Tauri 桌面壳 `network.status` 会把主站 TCP 可达性作为当前宿主网络状态;如果探测目标解析、DNS 或 TCP 连接失败后只折叠为 offline,H5 可以收到离线状态,但开发侧无法判断是配置、解析还是连接问题。
- 决策:`resolve_desktop_network_status()` 必须通过 `resolve_desktop_network_reachability(...)` 得到可达性或失败原因;失败时只记录 `desktop network reachability probe failed` 固定标签,再继续按离线 payload 返回,不把解析目标、DNS 或 TCP 错误详情写入可分发桌面壳 stderr。配置检查拒绝网络探测重新用 `.to_socket_addrs().ok()` 或 `connect_timeout(...).map(...).unwrap_or(false)` 静默吞错,也拒绝重新拼接 `: {reason}`。
- 影响范围:`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/scripts/check-config.mjs`。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::network`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳 EAS 原生包构建 profile
- 背景:移动壳已能通过 Expo managed config 和 Metro production bundle smoke,但缺少原生安装包构建 profile;如果只保留 `expo export`,无法证明 Android / iOS 壳有进入原生分发链路的配置。
- 决策:新增 `apps/mobile-shell/eas.json` 和 `eas-cli` devDependency。Android `production` profile 使用本地 EAS build 产出内部 APKiOS `production-simulator` profile 产出 simulator release 包,避免在没有真实签名凭据时写入伪证书配置。两端 profile 都用本地 `app.json` 版本字段并设置 `EXPO_NO_DOTENV=1`,真实本地构建输出固定到根目录 `build/native/mobile/` 下的 Android APK 和 iOS simulator 压缩包。当前不配置商店提交、签名凭据来源、自动递增、OTA runtimeVersion 或 releaseChannel`check-eas-build-config.mjs` 必须从 `apps/mobile-shell` 目录执行本地 EAS CLI 版本检查,确认解析到受检版本,并校验固定输出路径和扩展名。`apps/mobile-shell/scripts/check-config.mjs` 必须反查根级门禁仍保留 EAS build profile、Expo config 和 Metro export 三个移动分发烟测。
- 影响范围:`apps/mobile-shell/eas.json`、`apps/mobile-shell/package.json`、`apps/mobile-shell/scripts/check-eas-build-config.mjs`、`apps/mobile-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、原生壳方案和验收文档。
- 验证方式:`npm run mobile-shell:build-config`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳 iOS Privacy Manifest 门禁
- 背景:移动壳使用 React Native、Expo FileSystem、Notifications 等原生依赖,这些依赖包含 required reason API 的隐私清单;如果 `app.json` 不显式声明并由配置检查反查,iOS 分发时可能因为合并缺失或依赖升级导致隐私声明漂移。
- 决策:`apps/mobile-shell/app.json` 在 `expo.ios.privacyManifests` 声明当前依赖需要的 `FileTimestamp`、`DiskSpace`、`SystemBootTime` 和 `UserDefaults` required reason API;不声明数据采集和 tracking domain。`apps/mobile-shell/scripts/check-config.mjs` 必须精确反查 API category、reason、空 collected data 和 tracking=false。`apps/mobile-shell/scripts/check-expo-config.mjs` 额外确认当前安装的 `@expo/config-plugins` 仍包含消费 `config.ios?.privacyManifests` 并写入 `PrivacyInfo.xcprivacy` 的 `withPrivacyInfo` 插件,避免该字段变成源配置里的死声明。
- 影响范围:`apps/mobile-shell/app.json`、`apps/mobile-shell/scripts/check-config.mjs`、`apps/mobile-shell/scripts/check-expo-config.mjs`、原生壳方案文档。
- 验证方式:`npm run mobile-shell:typecheck`、`npm run mobile-shell:config`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳 release 二进制产物验收
- 背景:桌面壳统一验收已经执行 `tauri build --no-bundle`,但如果只看命令退出码,后续产物路径、二进制名称或平台输出发生漂移时,可能无法证明本机确实产出了可执行桌面壳。
- 决策:`npm run check:native-shells` 在桌面 release build smoke 后必须运行 `desktop-shell:stage-release-binary`,把 `apps/desktop-shell/src-tauri/target/release/genarrative-desktop-shell` 或 Windows `.exe` 复制到根目录 `build/native/desktop/`,再检查 staged 二进制存在、体积非空,并按当前平台校验 Linux ELF / macOS Mach-O / Windows PE 文件头和可执行位。`apps/desktop-shell/scripts/check-config.mjs` 必须反查根级门禁仍保留 release build smoke、staging 步骤和二进制产物检查。该检查不启动 GUI,也不生成平台安装包。
- 影响范围:`scripts/check-native-shells.mjs`、`apps/desktop-shell/package.json`、`apps/desktop-shell/scripts/stage-release-binary.mjs`、`apps/desktop-shell/scripts/check-config.mjs`、原生壳方案文档。
- 验证方式:`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳 smoke 脚本进入生产扫描
- 背景:移动壳 `check-eas-build-config.mjs`、`check-expo-config.mjs` 和 `check-expo-export.mjs` 已经成为原生包构建、Expo managed config 和 Metro production bundle 的验收入口;如果它们不进入单端结构清单和替身词扫描,后续可能在验收脚本里留下临时绕过逻辑而不被发现。
- 决策:`apps/mobile-shell/scripts/check-config.mjs` 必须把上述三个 smoke 脚本登记为受控生产验收脚本,并纳入 mock / fake / placeholder / stub / TODO / FIXME / 占位 / 模拟 / 伪造 / 未实现 / 临时 扫描;`check-config.mjs` 本身继续由根级门禁调用,不自扫自身。
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`、原生壳方案文档。
- 验证方式:`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳 ShellApp HostBridge 事件注入必须可执行覆盖
- 背景:Expo 移动壳声明 `host.events`、`app.lifecycle`、`network.statusChanged` 和 `navigation.canGoBack`,但 ShellApp 真实 AppState、Network 和 WebView 返回栈注入链路需要和扫码链路一样有可执行测试覆盖,不能只靠字符串门禁。
- 决策:`apps/mobile-shell/src/shell/ShellApp.test.tsx` 必须覆盖 AppState 到 `app.lifecycle`、Expo Network listener 到 `network.statusChanged`、WebView native / H5 history 合成到 `navigation.canGoBack` 的真实注入脚本;页面 load 后网络状态重放失败必须记录日志,不允许静默 `.catch(() => undefined)`。移动壳配置检查反查这些测试片段和失败日志 helper。
- 影响范围:`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/src/shell/ShellApp.test.tsx`、`apps/mobile-shell/scripts/check-config.mjs`、宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test -- src/shell/ShellApp.test.tsx`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳 WebView 外链打开失败不可静默
- 背景:Expo WebView 外域导航会离开带 HostBridge 的主站容器并交给系统浏览器或系统应用;如果 `Linking.openURL(...)` reject 后静默吞掉,用户会看到点击外链无反应且开发侧无法区分协议、系统能力或原生模块失败。
- 决策:`ShellApp` 的 WebView 外链分流必须继续复用 `openMobileShellExternalNavigation(Linking, request.url)`,但 Promise reject 路径必须调用 `logMobileShellNavigationFailure('external_navigation.open', error)` 记录错误;配置检查拒绝 `ShellApp` 重新出现 `catch(() => undefined)`,并反查外链失败日志测试。
- 影响范围:`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/src/shell/ShellApp.test.tsx`、`apps/mobile-shell/scripts/check-config.mjs`。
- 验证方式:`npm run mobile-shell:test -- src/shell/ShellApp.test.tsx`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳返回栈状态同步失败不可静默
- 背景:Expo 移动壳通过 WebView 注入脚本追踪当前 H5 文档 history index 并回传 `navigation.canGoBack`;如果 `replaceState(...)` 写入 index 失败后静默吞掉,Android 返回键和 H5 返回按钮状态可能漂移且难以排查。
- 决策:`TRACK_MOBILE_WEBVIEW_HISTORY_SCRIPT` 的 `replaceCurrentState()` catch 路径必须输出 `mobile navigation state sync failed` 浏览器 console 警告,同时继续回传当前可返回状态;移动壳配置检查拒绝该注入脚本重新出现空 catch。
- 影响范围:`apps/mobile-shell/src/shell/webViewPolicy.ts`、`apps/mobile-shell/src/shell/webViewPolicy.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`。
- 验证方式:`npm run mobile-shell:test -- src/shell/webViewPolicy.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳生命周期映射门禁
- 背景:Expo `AppState` 的 `active`、`background`、`inactive` 以及未知状态都会进入 `app.lifecycle` 事件;如果归一化映射漂移,H5 游戏循环、背景音乐和固定玩法音频会错误恢复或暂停。
- 决策:`apps/mobile-shell/src/shell/lifecycle.test.ts` 必须直接覆盖 `active`、`background`、`inactive` 和未知状态到统一 `state`、`focused`、`nativeState` 的映射;`apps/mobile-shell/scripts/check-config.mjs` 反查映射函数和测试片段,确保未知状态继续归为 `inactive` 且只有 `active` 视为 focused。
- 影响范围:`apps/mobile-shell/src/shell/lifecycle.ts`、`apps/mobile-shell/src/shell/lifecycle.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`。
- 验证方式:`npm run mobile-shell:test -- src/shell/lifecycle.test.ts`、`npm run mobile-shell:config`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动扫码权限请求失败不可静默
- 背景:Expo 移动壳 `scanner.scanQrCode` 会通过真实 `expo-camera` 权限 API 启动扫码;如果权限请求 API 自身失败后只返回通用 `host_error` 而不记录原始错误,用户会看到扫码不可用但开发侧难以区分系统拒绝、原生模块异常或设备能力问题。
- 决策:`QrScannerOverlay` 的 `Camera.requestCameraPermissionsAsync()` reject 路径必须调用 `logQrScannerPermissionFailure(...)` 输出 `mobile QR scanner permission request failed` 日志,再通过 `failQrCodeScan()` 以稳定 `host_error: qr scanner unavailable` 结束当前请求;`QrScannerOverlay.test.tsx` 必须覆盖该 reject 路径,移动壳配置检查反查日志 helper、稳定错误语义和测试断言。
- 影响范围:`apps/mobile-shell/src/shell/QrScannerOverlay.tsx`、`apps/mobile-shell/src/shell/QrScannerOverlay.test.tsx`、`apps/mobile-shell/scripts/check-config.mjs`。
- 验证方式:`npm run mobile-shell:test -- src/shell/QrScannerOverlay.test.tsx`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动分享单测边界
- 背景:Expo 移动壳 `share.open` / `share.setTarget` 已经由 `share.ts` 承接共享 HostBridge 分享 URL 归一和缓存目标,但关键边界主要压在巨型 `bridge.test.ts` 中,后续拆分桥接 helper 时容易遗漏非法显式 payload 不回退缓存、空分享拒绝和缓存目标保留语义。
- 决策:新增 `apps/mobile-shell/src/host-bridge/share.test.ts`,直接覆盖显式分享 payload、缓存作品目标、同源路径归一、非法 URL / 协议相对 URL 拒绝、非法显式 payload 不回退缓存、空分享拒绝和无效 `share.setTarget` 不清空已有目标;移动壳单端配置检查和根级原生壳门禁登记该测试文件并反查关键断言片段。
- 影响范围:`apps/mobile-shell/src/host-bridge/share.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳方案文档、宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/share.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动网络 HostBridge 单测边界
- 背景:Expo 移动壳 `network.status` 已经由 `src/host-bridge/network.ts` 包装真实 Expo Network 查询,但可执行测试主要在 shell network 归一化和巨型 bridge 测试中,缺少对 HostBridge response 形状、离线状态和底层网络失败传播的直接 helper 覆盖。
- 决策:新增 `apps/mobile-shell/src/host-bridge/network.test.ts`,直接覆盖 `network.status` 成功响应、断网响应和原生查询失败传播;移动壳单端配置检查和根级原生壳门禁登记该测试文件并反查关键断言片段。该变更不新增 capability,不改变 shell network 归一化逻辑,也不在分发层包装网络状态。
- 影响范围:`apps/mobile-shell/src/host-bridge/network.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳方案文档、宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/network.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动外观 HostBridge 单测边界
- 背景:Expo 移动壳 `appearance.getColorScheme` 是 H5 判断宿主配色的只读系统能力,但此前直接 helper 边界主要压在巨型 bridge 测试中;后续拆分桥接 helper 时需要固定 light / dark / unknown 归一和 HostBridge response 形状。
- 决策:新增 `apps/mobile-shell/src/host-bridge/appearance.test.ts`,直接覆盖 React Native `Appearance.getColorScheme()` 的 light / dark、空值 / 未知值归一为 `unknown`,以及 `appearance.getColorScheme` HostBridge 成功响应形状;移动壳单端配置检查和根级原生壳门禁登记该测试文件并反查关键断言片段。该变更不改变 H5 主题策略,也不让壳层覆盖系统或用户偏好。
- 影响范围:`apps/mobile-shell/src/host-bridge/appearance.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳方案文档、宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/appearance.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动角标 HostBridge 单测边界
- 背景:Expo 移动壳 `app.setBadgeCount` 只在 iOS capability profile 中声明,Android 请求到达时必须明确返回 unsupported;此前 iOS 设置 / 清除、非法 payload 和 Android unsupported 主要压在巨型 bridge 测试中,缺少对 `badge.ts` helper 的直接覆盖。
- 决策:新增 `apps/mobile-shell/src/host-bridge/badge.test.ts`,直接覆盖 iOS badge 权限已存在、权限缺失时请求 `allowBadge`、权限拒绝不触碰系统角标、`setBadgeCountAsync(false)` / reject 映射为稳定失败、非法数量和缺少 payload 时不触碰系统角标,以及 Android 在 payload 校验前返回 `unsupported_capability` 的顺序;移动壳单端配置检查和根级原生壳门禁登记该测试文件并反查关键断言片段。该变更不把 `app.setBadgeCount` 加入 Android base capability。
- 影响范围:`apps/mobile-shell/src/host-bridge/badge.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳方案文档、宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/badge.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动运行态 HostBridge 单测边界
- 背景:Expo 移动壳 `host.getRuntime` 是 H5 回读宿主版本、平台和 capability profile 的入口;此前 iOS / Android 平台差异和回包形状主要压在巨型 bridge 测试中,缺少对 `runtime.ts` helper 的直接覆盖。
- 决策:新增 `apps/mobile-shell/src/host-bridge/runtime.test.ts`,直接覆盖 iOS runtime 的 `hostVersion`、`bridgeVersion`、能力清单和 `app.setBadgeCount`Android runtime 不声明 iOS 专属角标能力,以及 `host.getRuntime` HostBridge 成功响应形状;移动壳单端配置检查和根级原生壳门禁登记该测试文件并反查关键断言片段。该变更不新增 capability,不改变入口 query 或 H5 runtime 回读策略。
- 影响范围:`apps/mobile-shell/src/host-bridge/runtime.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳方案文档、宿主壳能力统一协议文档。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/runtime.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 原生壳能力声明必须绑定真实链路
- 背景:桌面壳 capability profile 已声明一组 HostBridge request 能力,Expo 移动壳也声明本地通知、拍照、扫码、网络事件和分享等原生能力,H5 平台入口还会通过 `navigation.openNativePage` 打开 `/child-motion-demo` 这类受控内置玩法路由;如果门禁只检查“有 method case”或“文件被扫描”,未来可能退化成 fallback-only 分支或普通 Web 跳转而不被发现。
- 决策:桌面壳配置检查必须反查每个已声明 request capability 对应的真实模块委托,并拒绝由 `unsupported_method`、`unsupported_capability` 或 fallback-only case 支撑的声明能力;根级 `check:native-shells` 新增 H5 native app route flow 合约,锁定 `/child-motion-demo` 的 `navigateHostNativePage` 调用、浏览器 fallback、路由表和命名交互测试;Expo 移动壳关键 capability flow 合约必须反查共享移动 profile、真实 Expo / React Native API、权限或配置片段、宿主分发文件和对应测试清单;Tauri 桌面壳关键 capability flow 合约必须反查共享桌面 profile、真实 Tauri 插件 / Rust 系统 API、宿主分发文件、事件注入链路和对应单端检查片段。
- 影响范围:`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳能力统一协议文档。
- 验证方式:`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 原生壳结构门禁清单必须自检唯一性
- 背景:`scripts/check-native-shells.mjs` 依赖显式期望清单锁定微信、移动和桌面壳文件结构;如果期望清单自身混入重复项,目录比对仍可能失去清晰错误定位。
- 决策:根级原生壳结构门禁在比对真实目录前必须先检查所有期望清单和微信页面文件清单的唯一性,发现重复项直接失败。
- 影响范围:`scripts/check-native-shells.mjs`。
- 验证方式:`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳 Deep Link 失败必须可观测
- 背景:Expo 移动壳已声明 `genarrative://` scheme、iOS associated domain 和 Android app link,冷启动 / 热启动 deep link 会决定用户是否落到作品详情、创作页或邀请码页;如果 `Linking.getInitialURL()` 读取失败或运行时 URL 被拒绝后静默回首页,真实安装包会表现为“能打开 App 但目标丢失”,排障也缺少证据。
- 决策:移动壳 deep link 解析必须返回 `default` / `mapped` / `rejected` 状态;外域、危险协议或非法路径继续回到安全默认主站入口,但必须记录拒绝日志。`Linking.getInitialURL()` reject 必须记录 `initial_url.read` 错误且不替换当前 WebView URL;运行时 URL 被拒绝必须记录 `runtime_url.rejected`,并继续落安全默认入口。配置检查反查 ShellApp 日志路径、deep link 状态 resolver 和对应测试。
- 影响范围:`apps/mobile-shell/src/shell/deepLink.ts`、`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/scripts/check-config.mjs`、宿主壳方案文档。
- 验证方式:`npm run mobile-shell:typecheck`、`npm run mobile-shell:test -- src/shell/deepLink.test.ts src/shell/ShellApp.test.tsx`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳外链失败与扫码超时边界
- 背景:Expo 移动壳外链导航会离开带 HostBridge 的主 WebView,扫码能力也会打开原生相机 overlay;如果系统外链 API 异常被 helper 吞掉,或扫码 pending 没有共享超时清理,用户会看到点击无反应或后续扫码一直提示通道占用。
- 决策:`openMobileShellExternalNavigation(...)` 只在非法 URL 或系统明确不能打开时返回 `false`,原生 `canOpenURL` / `openURL` 异常必须抛给 `ShellApp` 的 `logMobileShellNavigationFailure(...)` 记录;`scanner.scanQrCode` pending 状态必须使用共享 `HOST_BRIDGE_SCANNER_TIMEOUT_MS` 自动拒绝并清理,成功、取消、失败和测试 reset 都必须清理 timer。
- 影响范围:`apps/mobile-shell/src/shell/navigation.ts`、`apps/mobile-shell/src/shell/navigation.test.ts`、`apps/mobile-shell/src/shell/ShellApp.test.tsx`、`apps/mobile-shell/src/host-bridge/scanner.ts`、`apps/mobile-shell/src/host-bridge/scanner.test.ts`、`apps/mobile-shell/scripts/check-config.mjs`。
- 验证方式:`npm run mobile-shell:test -- src/shell/navigation.test.ts src/shell/ShellApp.test.tsx src/host-bridge/scanner.test.ts src/shell/QrScannerOverlay.test.tsx`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳门禁脚本与网络状态测试边界
- 背景:Tauri 桌面壳 `network.status` 已由 `host_bridge/network.rs` 统一包装,但成功响应和底层 resolver 失败映射主要靠字符串门禁;同时桌面单端检查没有把 `apps/desktop-shell/scripts/check-config.mjs` 自身纳入脚本清单和生产替身词扫描。
- 决策:`host_bridge/network.rs` 新增可注入映射 helperRust 单测直接覆盖 `network.status` 成功 response shape 和 resolver 失败不暴露原生细节;桌面壳单端配置检查登记并扫描 `scripts/check-config.mjs`,根级文档门禁改为只从“结构门禁按完整相对路径”canonical 段反查文件清单,短清单只保留指针文案。
- 影响范围:`apps/desktop-shell/src-tauri/src/host_bridge/network.rs`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/check-native-shells.mjs`、宿主壳方案文档、宿主壳能力统一协议文档。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::network shell::network`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳文件能力系统异常必须可观测
- 背景:Expo 移动壳已声明文本 / 文档 / 图片 / 音频导入导出、拍照和相册能力;这些能力会打开系统分享面板、DocumentPicker、相册、相机或读取缓存文件。如果原生 API reject 后只返回稳定 HostBridge 错误,H5 语义是安全的,但开发侧难以区分系统能力缺失、权限 API 异常、文件读取失败或分享面板失败。
- 决策:`apps/mobile-shell/src/host-bridge/files.ts` 必须在 Expo Sharing 可用性 / 分享面板、DocumentPicker、文本 / base64 文件读取、相册 / 相机权限请求和相册 / 相机打开失败时记录 `mobile HostBridge file failed for ...` 日志;HostBridge 对 H5 仍只返回稳定 `host_error` / `unsupported_capability` / `cancelled` / `invalid_request` 语义,不透传原生异常明细。移动壳配置检查反查日志 helper、关键 label 和对应单测。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/files.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳通知与角标系统异常必须可观测
- 背景:Expo 移动壳已声明即时本地通知,iOS 额外声明应用角标;这些能力会触发系统权限读取、权限请求、Android channel 设置、通知调度和角标更新。如果原生 API 异常只被折叠成稳定 HostBridge 错误,H5 语义安全,但开发侧无法区分权限模块异常、系统通知调度失败或角标 API 拒绝。
- 决策:`apps/mobile-shell/src/host-bridge/notifications.ts` 必须在权限读取 / 请求和通知投递失败时记录 `mobile notification failed for ...` 日志;`apps/mobile-shell/src/host-bridge/badge.ts` 必须在角标权限读取 / 请求、`setBadgeCountAsync` reject 和返回 `false` 时记录 `mobile app badge failed for ...` 日志。HostBridge 对 H5 仍只返回稳定错误语义;该约束不新增远程推送 token、后台通知、定时提醒或 Android 角标 capability。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/notifications.test.ts src/host-bridge/badge.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳剪贴板触觉网络异常必须可观测
- 背景:Expo 移动壳已声明剪贴板读写、触觉反馈和网络状态查询;这些能力会触发 Expo Clipboard、Haptics 和 Network 原生模块。如果原生 API 异常只被折叠成稳定 HostBridge 错误,H5 语义安全,但开发侧无法区分系统剪贴板不可用、触觉模块异常或网络模块查询失败。
- 决策:`apps/mobile-shell/src/host-bridge/clipboard.ts` 必须在剪贴板写入 / 读取失败时记录 `mobile clipboard failed for ...` 日志;`apps/mobile-shell/src/host-bridge/haptics.ts` 必须在触觉派发失败时记录 `mobile haptics failed for ...` 日志;`apps/mobile-shell/src/host-bridge/network.ts` 必须在 HostBridge 网络状态查询失败时记录 `mobile network failed for ...` 日志。HostBridge 对 H5 仍只返回稳定错误语义;该约束不新增后台网络探测、任意系统能力或 H5 业务兜底路径。
- 验证方式:`npm run mobile-shell:test -- src/host-bridge/clipboard.test.ts src/host-bridge/haptics.test.ts src/host-bridge/network.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳剪贴板与通知异常必须可观测
- 背景:Tauri 桌面壳已声明剪贴板读写和即时本地通知;这些能力会触发 Tauri clipboard-manager 和 notification 插件。如果插件异常只被折叠成稳定 HostBridge 错误,H5 语义安全,但开发侧无法区分系统剪贴板不可用、通知权限查询异常或系统通知投递失败。
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs` 必须在剪贴板写入 / 读取失败时记录 `desktop clipboard failed for ...` 日志;`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs` 必须在通知权限状态读取、权限请求和通知投递失败时记录 `desktop notification failed for ...` 日志。HostBridge 对 H5 仍只返回稳定错误语义;该约束不新增遥测 SDK、后台通知、自动更新或 H5 直连 Tauri JS 插件。
- 2026-06-21 调整:桌面剪贴板和本地通知失败日志只记录 `desktop clipboard failed for <stage>` / `desktop notification failed for <stage>` 固定标签,不输出 clipboard-manager 插件错误、notification permission / delivery 插件错误、系统剪贴板细节或其它平台异常字符串;配置检查拒绝 `clipboard.rs` / `notifications.rs` 重新拼接 `: {error}` 或把写入 / 读取 / 权限 / 投递错误传给日志函数。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::clipboard host_bridge::notifications`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳文件能力系统异常必须可观测
- 背景:Tauri 桌面壳已声明文本 / 文档 / 图片 / 音频导入导出;这些能力会打开系统文件对话框、转换系统路径并在后台线程读写文件。如果系统路径转换、后台读写或任务 join 异常只被折叠成稳定 HostBridge 错误,H5 语义安全,但开发侧无法区分系统对话框路径异常、文件系统失败或后台任务失败。
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/files.rs` 必须在导出路径转换、导出写入、导入路径转换和导入后台读取 join 失败时记录 `desktop file export failed for ...` 或 `desktop file import failed for ...` 日志。HostBridge 对 H5 仍只返回稳定错误语义,不透传本地路径、系统错误或线程细节;用户取消系统文件对话框仍返回 `cancelled`,不记录为异常。
- 2026-06-21 调整:桌面文件导入导出失败日志只记录 `desktop file export failed for <stage>` / `desktop file import failed for <stage>` 固定标签,不输出本地路径转换错误、文件读写错误、后台任务 join 错误或其它系统细节;配置检查拒绝 `files.rs` 重新拼接 `: {error}` 或把路径 / 读写 / join 错误传给日志函数。
- 2026-06-21 调整:桌面文件导入的 MIME、类型和大小校验错误继续以稳定 `invalid_request` 返回 H5`fs::metadata`、`fs::read`、`fs::read_to_string` 等原生读取失败统一折叠为 `host_error` / `file import unavailable`,只记录 `read.text`、`read.document`、`read.image`、`read.audio` 固定阶段标签,不把系统 IO 错误字符串作为 HostBridge 错误消息或 stderr 明细输出。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::files`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳窗口状态小能力系统异常必须可观测
- 背景:Tauri 桌面壳的外观、角标和窗口标题能力都依赖主窗口和平台系统 API;这些能力对 H5 必须保持稳定错误语义,但开发侧也需要知道是主窗口缺失、主题读取失败还是系统 API 调用失败。
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs` 和 `apps/desktop-shell/src-tauri/src/host_bridge/title.rs` 必须在主窗口缺失、主题读取失败、角标设置失败和窗口标题设置失败时分别记录 `desktop appearance failed for ...`、`desktop app badge failed for ...` 或 `desktop window title failed for ...` 日志。HostBridge 对 H5 仍只返回稳定 `appearance unavailable`、`badge unavailable` 或 `window title unavailable`,不透传系统错误、窗口内部信息或平台细节。
- 2026-06-21 调整:桌面外观、角标和窗口标题能力失败日志只记录 `desktop appearance failed for <stage>` / `desktop app badge failed for <stage>` / `desktop window title failed for <stage>` 固定标签,不输出主窗口缺失文本、Tauri `theme()` / `set_badge_count` / `set_title` 错误或其它平台细节;配置检查拒绝 `appearance.rs` / `badge.rs` / `title.rs` 重新拼接 `: {error}` 或 `&error.to_string()`。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::appearance`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::badge`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::title`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳导航系统异常必须可观测
- 背景:Tauri 桌面壳的外链打开、同源 H5 route 导航和 WebView reload 都直接影响原生壳内 H5 的完整流程;这些系统调用失败时,H5 只应得到稳定错误语义,但开发侧需要能区分外链打开失败、窗口导航失败、reload 失败和主窗口缺失。
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs` 必须在外链打开失败、同源 H5 route 导航失败、WebView reload 失败和主窗口缺失时记录 `desktop navigation failed for ...` 日志。HostBridge 对 H5 仍只返回稳定 `external URL cannot be opened`、`native page unavailable` 或 `webview reload unavailable`,不透传系统错误、窗口内部信息或平台细节。
- 2026-06-21 调整:桌面导航失败日志只记录 `desktop navigation failed for <stage>` 固定标签,不输出 opener、window.navigate、WebView reload 错误或主窗口缺失说明;配置检查拒绝 `navigation.rs` 重新拼接 `: {error}`、`&error.to_string()` 或把主窗口缺失文本传给日志函数。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::navigation`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳网络状态系统异常必须可观测
- 背景:桌面壳 `network.status` 通过后台任务解析系统网络状态,H5 只需要稳定在线 / 离线语义;但后台任务 join 失败时,如果只返回稳定 `host_error`,开发侧无法区分正常离线、解析任务失败和系统异常。
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/network.rs` 必须在网络状态解析失败时记录 `desktop network failed for status.resolve` 日志。HostBridge 对 H5 仍只返回稳定 `network status unavailable`,不透传 resolver、线程或系统错误细节。
- 2026-06-21 调整:桌面网络状态解析失败日志只记录 `desktop network failed for status.resolve` 固定标签,不输出后台任务 join 错误、resolver 异常或其它平台细节;配置检查拒绝 `network.rs` 重新拼接 `: {error}` 或把解析错误传给日志函数。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::network`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳系统分享异常必须可观测
- 背景:移动壳 `share.open` 调用 React Native 系统分享面板,失败时 H5 只需要知道分享不可用并保留复制链接等回退;但如果原生分享面板 reject 没有日志,开发侧无法区分分享面板不可用、系统取消异常或平台分享模块异常。
- 决策:`apps/mobile-shell/src/host-bridge/share.ts` 必须在 `Share.share(...)` reject 时记录 `mobile share failed for open.share` 日志。HostBridge 对 H5 仍只返回稳定 `share unavailable`,不透传原生分享面板异常明细。
- 验证方式:`npm run mobile-shell:test -- --run src/host-bridge/share.test.ts`、`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 桌面壳分享缓存内部异常不得透传
- 背景:桌面壳 `share.setTarget` 和 `share.open` 会读写 Rust 侧分享目标缓存;如果缓存锁异常直接返回 `share target lock poisoned`H5 会看到 Rust 内部同步原语细节,且开发侧没有统一日志标签定位读缓存还是写缓存失败。
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/share.rs` 必须在分享目标缓存读写失败时分别记录 `desktop share failed for target.lock` 或 `desktop share failed for target.store` 日志。HostBridge 对 H5 仍只返回稳定 `share unavailable`,不透传锁状态、内部缓存状态或 Rust 同步原语细节。
- 2026-06-21 调整:桌面分享缓存失败日志只记录 `desktop share failed for target.lock` / `desktop share failed for target.store` 固定标签,不输出锁污染说明、内部缓存状态或其它 Rust 同步原语细节;配置检查拒绝 `share.rs` 重新拼接 `: {error}` 或把锁异常字符串传给日志函数。
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::share`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 移动壳门禁脚本必须自登记自扫描
- 背景:Expo 移动壳单端检查已把 `apps/mobile-shell/scripts/` 纳入生产源码扫描入口,但 `check-config.mjs` 自身仍被排除在脚本清单和替身词扫描之外;这会让移动壳与桌面壳门禁结构不一致,也可能让后续门禁反查内容绕过生产替身词规则。
- 决策:`apps/mobile-shell/scripts/check-config.mjs` 必须登记 `check-config.mjs`、`check-eas-build-config.mjs`、`check-expo-config.mjs` 和 `check-expo-export.mjs` 的完整脚本清单,并将 `check-config.mjs` 自身纳入生产替身词扫描;脚本内反查测试 mock 片段时使用字符串拼接保留测试约束,不让门禁自身违反生产规则。
- 影响范围:`apps/mobile-shell/scripts/check-config.mjs`。
- 验证方式:`npm run mobile-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-20 原生分享动作按宿主真实表现展示
- 背景:`share.open` 是原生壳受控分享动作,不等同于每个宿主都打开系统分享面板;Expo 移动壳会打开系统分享面板,Tauri 桌面壳则把归一后的分享文本写入系统剪贴板并返回 `copied_to_clipboard`。
- 决策:H5 发布分享弹窗必须按 `hostShell` 展示分享动作文案:`expo_mobile` 继续显示“系统分享 / 已打开 / 分享失败”,`tauri_desktop` 显示“复制分享文案 / 已复制 / 复制失败”;根级原生壳门禁反查 `PublishShareModal` 源码和测试,防止桌面剪贴板动作再次被包装成系统分享面板。
- 影响范围:`src/components/common/PublishShareModal.tsx`、`src/components/common/PublishShareModal.test.tsx`、`scripts/check-native-shells.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- src/components/common/PublishShareModal.test.tsx`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-21 H5 原生能力必须来自真实 runtime 回包
- 背景:Expo / Tauri 壳会把 `clientRuntime`、`hostShell` 和 `hostCapabilities` 写入 H5 URL,用于保留宿主上下文和路由状态。如果 H5 在 `host.getRuntime` 异步回包前把 URL query 中的 `hostCapabilities` 作为真实能力来源,深链旧参数或伪造 query 会让首屏短暂展示或触发原生动作。
- 决策:H5 仍可用 URL query 判断宿主类型和保留上下文,但 `canUseNativeHostCapability` 只能信任真实 native bridge 存在且 `host.getRuntime` 已缓存的 capabilityquery 中的 `hostCapabilities` 不再参与能力门控。`host.getRuntime` 刷新只依赖真实 Expo WebView / Tauri invoke 注入,不依赖 query capability。桌面 Tauri 事件白名单必须等于桌面 capability 中已声明的事件子集,不得包含未声明的 `network.statusChanged`。
- 2026-06-21 调整:H5 生产代码不得直接 import `src/services/host-bridge/nativeAppHostBridge.ts` 低层 transport;业务层、组件层和 wrapper 必须经 `src/services/host-bridge/hostBridge.ts` facade 使用原生能力,保证真实 runtime 回读、capability 门控、payload 归一和事件订阅门控始终生效。`scripts/check-native-shells.mjs` 负责扫描生产 H5 源码并拒绝绕过 facade 的直接 transport 依赖。
- 影响范围:`src/services/host-bridge/hostBridge.ts`、H5 HostBridge 消费测试、`apps/desktop-shell/src-tauri/src/shell/events.rs`、`scripts/check-native-shells.mjs`。
- 验证方式:`npm run test -- src/services/host-bridge/hostBridge.test.ts src/services/runtimeAudioFeedback.test.ts src/App.test.tsx src/components/common/CreativeAudioInputPanel.test.tsx src/components/common/PublishShareModal.test.tsx src/components/platform-entry/platformHostBridgeSync.test.ts`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::events`、`npm run check:native-shells`。
## 2026-06-21 声明的原生壳能力必须有真实能力流证据
- 背景:Expo 移动壳和 Tauri 桌面壳的 capability 清单会直接影响 H5 是否展示和调用原生能力。如果新增 capability 只写进共享契约或 Rust / TS 能力清单,却没有登记真实宿主 API、分发入口、payload 边界和测试证据,H5 可能认为能力可用,但运行时没有对应真实链路。
- 决策:`scripts/check-native-shells.mjs` 必须对 Expo 移动壳和 Tauri 桌面壳做反向覆盖:共享契约中声明的每个移动端基础能力、iOS 额外能力和桌面能力,都必须在 `mobileCapabilityFlowContracts` 或 `desktopCapabilityFlowContracts` 中登记真实能力流证据。证据必须来自生产实现、宿主配置、边界测试或单端配置检查,不能用占位、生产 mock、fallback unsupported 分支或文档愿景替代真实链路。
- 2026-06-21 调整:Tauri 桌面能力流必须由根级 `desktop-shell:test` 保护,且每个 `desktopCapabilityFlowContracts` 条目至少关联一个带 Rust 单测的真实桌面壳模块;新增桌面 capability 时不能只登记 dispatch / 配置片段而没有 Rust 单元测试覆盖。
- 影响范围:`packages/shared/src/contracts/hostBridge.ts`、`apps/mobile-shell/src/host-bridge/`、`apps/mobile-shell/src/shell/`、`apps/desktop-shell/src-tauri/src/host_bridge/`、`apps/desktop-shell/src-tauri/src/shell/`、`scripts/check-native-shells.mjs`。
- 验证方式:`npm run check:native-shells`、`npm run typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-06-21 三端 HostBridge 模块必须先分类再扩展
- 背景:微信小程序壳、Expo 移动壳和 Tauri 桌面壳已经按相近目录结构拆出桥接层,但同名能力并不总是三端共享;如果后续只靠文件清单约束,新增模块可能在某一端随意落点,破坏“三端尽量一致、端专属能力明确隔离”的管理目标。
- 决策:`scripts/check-native-shells.mjs` 必须把 HostBridge 模块分成三端共同、Expo / Tauri 原生 App 共同、移动端专属、桌面端专属和微信端专属五类,并从现有文件清单反推实际分类。新增、拆分或迁移桥接模块时,必须先更新分类归属,再同步目录清单、文档和能力流证据。
- 影响范围:`miniprogram/host-bridge/`、`apps/mobile-shell/src/host-bridge/`、`apps/desktop-shell/src-tauri/src/host_bridge/`、`scripts/check-native-shells.mjs`、宿主壳能力统一协议文档、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-21 Tauri devUrl 与 Vite 端口必须显式对齐
- 背景:`npm run desktop-shell:dev` 通过 Tauri `devUrl` 固定加载 `http://127.0.0.1:3000/`,但 Linux dev 端口段逻辑会把未显式指定的 `dev:web` 主站端口映射到用户端口段,例如 `10000+`。只在桌面壳 package script 里设置 `WEB_PORT=3000` 不会让 `scripts/dev.mjs` 把 Web 端口视为显式 CLI 参数,结果 Tauri 仍打开 3000,而 Vite 实际监听其它端口。
- 决策:桌面壳 `beforeDevCommand` 必须执行 `npm --prefix ../.. run dev:web -- --web-port 3000 --strict-web-port`,用 CLI 参数锁定主站 Vite 端口并禁止静默漂移;`devUrl` 继续固定 `http://127.0.0.1:3000/`。如果 3000 被占用,应该释放端口后再启动桌面壳,而不是让 Vite 漂移后继续由 Tauri 加载旧端口。由于主窗口设置了 `create=false` 并由 Rust 手动创建,`app.rs` 在 dev build 下必须把主窗口 URL 替换为 `build.devUrl` 后再补写 HostBridge queryrelease 仍从 `index.html` 打包资源进入。
- 2026-06-22 调整:release 打包资源在 Windows WebView 内可能以 `http://tauri.localhost/index.html` 出现,这仍是 Tauri 内部资源,不允许被导航拦截交给系统浏览器;`shell/navigation.rs` 必须允许 `http` / `https` 的 `*.localhost` 留在 WebView。Windows release 二进制必须使用 GUI subsystem,避免正式包启动时额外弹出控制台窗口。
- 影响范围:`apps/desktop-shell/src-tauri/tauri.conf.json`、`apps/desktop-shell/scripts/check-config.mjs`、`scripts/dev.test.ts`、Expo / Tauri HostBridge 方案文档。
- 验证方式:`npm run test -- scripts/dev.test.ts -t "Linux 桌面壳显式指定 web-port"`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml desktop_main_window_config_uses_dev_url_in_dev_builds desktop_webview_navigation_stays_on_packaged_or_same_origin_pages`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
## 2026-06-23 后台默认入口切到 Dashboard 运营看板
- 背景:后台需要默认进入运营数据面板,而不是服务 / 数据库状态页;看板要同时支持日 / 周 / 月筛选,并展示生产素材、泥点消耗、注册、访问和当前使用人数。
- 决策:`apps/admin-web` 默认路由改为 `#dashboard`,原 `#overview` 保留为“服务总览”。Dashboard 统一通过 `GET /admin/api/dashboard` 读取 api-server 后端投影,不让前端绕过 BFF 直接访问 SpacetimeDB。后端不新增 SpacetimeDB schema,聚合现有 `editor_project_resource`、`profile_wallet_ledger`、`profile_dashboard_state`、`tracking_daily_stat` 和 `tracking_event`。
- 2026-06-24 补充:Dashboard 日期控件改为起始日期 / 终止日期;`granularity=period` 使用 `startDate` / `endDate` 自定义闭区间,`day` / `week` / `month` 保留 `anchor` 兼容;前端展示“本日 / 本周 / 本月”快捷按钮,只修改起止日期并刷新,页面总计数据和时段数据分区展示。
- 指标口径:生产素材数统计 `editor_project_resource.source_type = generated`;消耗泥点数统计 `profile_wallet_ledger.source_type = asset_operation_consume` 的负向流水绝对值;总注册用户和新增用户数均来自 `profile_dashboard_state`,其中新增用户数按 `created_at` 落入当前时间窗统计;访问次数只统计 `tracking_daily_stat.scope_kind = site`;访问人数和当前使用人数按登录用户去重,匿名访问人数需要未来补 visitor id 后才能统计。
- 影响范围:`/admin/api/dashboard`、`shared-contracts` admin DTO、`apps/admin-web` 默认路由和 Dashboard 页面、后台运营文档。
- 验证方式:`cargo test -p api-server --manifest-path server-rs/Cargo.toml admin`、`npm run admin-web:typecheck`、`npx vitest run apps/admin-web/src/pages/AdminDashboardPage.test.tsx apps/admin-web/src/app/adminRoutes.test.ts --reporter verbose`、`npm run check:encoding`、`git diff --check`。
## 2026-06-24 AI 游戏创作智能体使用独立 Tauri App
- 背景:AI 游戏创作需要本地项目落盘、受限命令、本地 HTTP 预览、多智能体编排和短期 / 长期记忆;这些能力不应塞进现有 `apps/desktop-shell` 主站宿主壳。
- 决策:AI 游戏创作桌面入口新建 `apps/ai-game-creator-shell`。普通用户界面只保留聊天和上传入口;任务、能力、文件、记忆、预览和日志只放在开发模式或开发窗口。Agent 能力、manifest、内置命令和权限枚举写入 `packages/shared/src/contracts/gameCreationApp.ts` 与 `server-rs/crates/shared-contracts/src/game_creation_app.rs`;专业组和种子任务图写入 `server-rs/crates/platform-agent/src/game_creation.rs`。`canvas.project_open` 只允许打开本机 Genarrative 编辑器 `/editor/canvas?projectid=...`,默认本机端口为 `3000`,不得扩展成任意 URL 打开能力。
- 本地边界:生成代码、上传资产、短期记忆、长期记忆和预览入口必须保存到用户授权的本地项目目录;正式预览使用只读 `127.0.0.1:<port>` HTTP server,不使用 `file://`。`game.generate_draft` 和 `asset.upload` 这类 `confirm` 命令先在聊天区形成待确认命令,用户确认后才写本地产物;开发窗口中的 `confirm` 命令使用原生确认门,取消时只写日志不执行。`command.run_limited` 只执行白名单内置命令,当前最小真实命令是 `game.static_smoke`,用于检查 `game/index.html` 是否具备 canvas、canvas 渲染上下文、绘制调用、主循环、输入监听、明确目标、失败或胜利状态和重开路径,且不使用远程资源、`eval`、`new Function`、`localStorage`、`fetch`、`WebSocket` 或 `ServiceWorker`,不得把任意 shell 执行暴露给普通用户界面。`.agent/manifest.json` 是本地最小状态源,记录专业组种子任务、资产、预览状态和受限命令运行结果;开发窗口专业组面板读取 manifest task state,不使用前端硬编码作为真相源。`file.list/read/write/delete` 只能访问本地项目目录内的相对路径,禁止绝对路径、`..`、反斜杠和符号链接逃逸。`asset.register` 只登记项目目录内已经存在的文件,并可记录 `uploaded`、`generated`、`canvas` 来源元数据。`canvas.asset_import` 是画板回流的本地落点,只导入项目内已有文件为 `canvas` 来源资产,并要求画板项目 ID 与 resourceId / assetObjectId 可追踪;`canvas.export_import` 复用现有画板素材导出 ZIP,把 `metadata.json` 引用的 `images/`、`media/`、`sequences/` 文件复制到本地项目 `assets/canvas-imports/` 并登记为 `canvas` 来源资产。
- 2026-06-24 调整:`game.generate_draft` 必须作为一次本地 agent 协作回合记录,用户确认后同时写入短期记忆、长期记忆、设计草案、数值配置、美术清单、音乐音效清单、发布包装草案、可运行 HTML、`.agent/logs/agent.log` 和 manifest `commandRuns`manifest 任务状态必须反映策划、数值、美术、音乐、程序组首轮完成,预览试玩等待确认。
- 2026-06-24 调整,2026-06-30 更新:`game.generate_draft` 必须通过 OpenAI-compatible LLM 生成结构化 JSON 草案;发布 App 读取 Tauri 应用配置目录中 `game-creator.config.json` 的 `llm.*` 配置项,开发 CLI 无 AppHandle 时才读仓库旁边的 fallback 配置。LLM 配置缺失、上游失败、返回非 JSON、HTML 非自包含、缺少 `canvas` / `requestAnimationFrame` 或把危险用户输入原样写入 HTML 时直接失败,不得静默回退固定模板并声称 AI 生成。
- 2026-06-24 调整:`game.generate_draft` 生成的 `game/index.html` 必须是可试玩原型,至少具备输入、主循环、目标、失败或胜利状态和重开路径;不得退回按钮计分、纯展示页或占位式游戏。
- 2026-06-24 调整:`game.generate_draft` 的设计草案、发布包装草案和 agent log 必须包含专业组 / 角色 / 产物交接摘要,作为 6 组 agent 协作的最小可追踪证据。
- 2026-06-25 调整:`game.generate_draft` 的 loop 不能只由 Generator 在提示词里模拟六组协作;每一轮必须在 Planner 之后分别调用策划、数值、美术、音乐、程序、运营 6 组下的角色 agent 产出 brief,写入 `.agent/passes/pass-N/groups/<group>/*.md`,再汇总为 `.agent/passes/pass-N/groups/*.md`,由 Generator 读取这些汇总 brief、spec、findings 和记忆整合成结构化草案。`.agent/run.latest.json` 必须记录 `llm.chat.group.<group>.<role>` toolCall 和 brief artifact,作为多智能体协作的最小真实证据。
- 2026-06-25 调整:专业组不再只对应单个 group call。共享契约、`platform-agent` 和本地 manifest 的种子任务图扩展为 6 组下 15 个组内角色任务,覆盖 `Director`、`Gameplay`、`Difficulty`、`Asset`、`Polish`、`SFX`、`Code`、`Preview`、`Playtest`、`Publish` 等角色;每轮 loop 先分别调用角色 agent,写入 `.agent/passes/pass-N/groups/<group>/*.md`,再由 `GroupCoordinator` 汇总为 `.agent/passes/pass-N/groups/*.md` 给 Generator 使用。`.agent/run.latest.json` 必须同时记录角色级 `llm.chat.group.<group>.<role>` toolCall、6 个组汇总 step 和这些角色 brief artifact,避免退回“6 组名义协作、组内无任务图”的实现。
- 2026-06-25 调整:每轮 loop 先由 Orchestrator 写 `.agent/passes/pass-N/agenda.md`。首轮 agenda 全量激活 15 个组内角色任务;返工轮从 `.agent/findings.md` 提取 Evaluator 问题,按任务图重跑命中的角色任务及其下游影响任务,未命中角色写入 carry-over brief 并在 trace 中标记 `carried-over` 与 `agent.task_graph.carryover.<group>.<role>`,避免把返工实现成无差别全员重跑,也避免上游产物变化后下游程序预览或运营包装沿用旧 brief。Generator 必须把本轮 agenda 作为输入路径读取。
- 2026-06-25 调整:每轮 Orchestrator 还必须写 `.agent/passes/pass-N/task-graph.json`,记录 activeTaskIds、carriedTaskIds、repairFocus 和按任务依赖排序的 dependencyWaves`.agent/run.latest.json` 顶层 `taskGraph` 必须同步 goal、readyTaskIds、activeTaskIds、carriedTaskIds、repairFocus 和当前任务状态。所有新 step 必须写 phase、taskId、group、role,开发窗口用这些结构化字段展示编排状态,不能只解析 summary 字符串。
- 2026-06-25 调整:返工轮不能只保留自然语言 repairFocusOrchestrator 必须把每条 Evaluator 问题转成 `repairRoutes[]`,写明 issue、taskIds 和 reason,并把同一结构写入 pass 级 `task-graph.json` 与 run 级 `taskGraph`,开发窗口展示该路由,测试必须断言输入 / canvas / 静态 smoke 类问题会路由到程序组角色任务。
- 2026-06-25 调整:返工路由、activeTaskIds、carriedTaskIds 和 dependencyWaves 的 pass 级决策下沉到 `server-rs/crates/platform-agent/src/game_creation.rs` 的纯编排内核,`apps/ai-game-creator-shell` 只调用该内核并负责 `.agent/passes/pass-N/agenda.md`、`task-graph.json`、run trace 和本地工具执行,避免 agent loop 语义只停留在 Tauri shell 私有实现。
- 2026-06-25 调整:`Evaluator` 写 `.agent/findings.md` 时必须附带 `## Repair Routes` JSON,包含 issue、taskIds 和 reason`platform-agent` 下一轮优先解析该结构化路由并清理未知 taskId / 重复 taskId,只有缺失或解析失败时才退回关键词路由,避免 loop 返工范围依赖自然语言猜测。
- 2026-06-25 调整:`platform-agent` 会把结构化 `repairRoutes[].taskIds` 自动扩展为下游影响闭包,并在 reason 上追加 `dependency-impact`;例如 `art-asset-plan` 变化会继续激活 `art-polish`、程序组预览链路和运营发布包装,`code-prototype` 变化会继续激活预览和运营包装,但不会倒回去重跑无关上游。
- 2026-06-25 调整:`game.static_smoke` 不能只检查 canvas、RAF 和输入事件字样;还必须拒绝空输入监听以及明显占位 / placeholder / 固定星核传送门模板词,避免占位页或固定模板被当作可试玩原型进入预览。
- 2026-06-25 调整:`game.generate_draft` 的 agent loop 跑满 3 轮仍未通过 Evaluator 时必须整体失败,只保留 `.agent/passes/pass-N/` 中间快照和失败 run trace,不写入 `memory/session.md`、`memory/project.md`、`game/game_design.md`、`game/balance.json`、资产清单、发布 README,也不把默认 `game/index.html` 覆盖成最终游戏产物;测试必须断言失败 trace 的 `stopReason=max-passes-exhausted`,且没有 `ArtifactWriter` / `Playtest` step。
- 2026-06-25 调整:`.agent/agent.db` 先作为最小 append-only JSONL 本地索引,不引入 SQLite 或新依赖。项目初始化写 `project.init`,每次 `game.generate_draft` 追加目标、标题和本地产物路径,上传 / 登记 / 画板导入资产时追加 `asset.register` 或 `asset.update`;正式状态仍以 `.agent/manifest.json`、`.agent/run.latest.json` 和 `.agent/runs/` 为主要事实源。
- 2026-06-25 调整:`game.generate_draft` 生成前必须从 `.agent/manifest.json` 派生本地资产上下文,把上传、登记和画板回流资产的 id、kind、mediaType、localPath、source 以及 canvasProjectId / resourceId / assetObjectId 等追踪字段传给 Planner、组内角色 agent 和 Generator;资产上下文必须排在长期记忆前,避免长期记忆过长时被 prompt 截断;trace 中 Planner、角色 agent 和 Generator 的 `inputPaths` 必须显式包含 `.agent/manifest.json`,避免开发窗口看不到资产上下文来源;不要新增平行资产记忆文件,也不要读取二进制资产内容塞进 prompt。
- 2026-06-25 调整:新增 `npm run ai-game-creator-shell:agent-run:smoke` 作为无密钥开发验证入口。脚本在本机启动 OpenAI-compatible 测试 provider,预置一个本地上传图片和一个本地上传音频,并复用真实 `--agent-run`、本地落盘、`game.static_smoke` 和本地 HTTP 预览;脚本会断言 provider 请求体包含图片与音频资产上下文、生成 HTML 引用 `/assets/...`、预览服务能用 `GET` 读取这些资产、用 `HEAD` 返回真实资源长度和对应 MIME、headless Chrome 打开预览后至少执行一帧游戏 JS,且通过确定性亮色探针采样证明 canvas 不是空白画布、第二轮重跑 Evaluator 命中任务及其下游影响任务,未受影响组 carry-over,再自动给 CLI 发送回车停止预览。该脚本仅验证 runtime,不作为产品生成 fallback。
- 2026-06-25 调整:新增根级 `npm run ai-game-creator-shell:check` 作为 v1 开发验收入口,串起壳 typecheck、`platform-agent` 编排测试、`shared-contracts` 契约测试、Tauri Rust 测试和无密钥本地 provider 端到端 smoke,避免测试口径散落成多条手工命令。
- 2026-06-25 调整:`scripts/check-native-shells.mjs` 的 AI 游戏创作项从单独 typecheck 升级为 `npm run ai-game-creator-shell:check`,让原生壳总门禁覆盖 agent loop、本地落盘、静态自检和本地 HTTP 预览 smoke。
- 2026-06-25 调整,2026-06-30 更新:普通用户通过聊天输入 `/llm-status` 触发只读 `llm.config_check`,用于检查 LLM base_url、model 和 API Key 是否已从客户端配置读取;状态消息不得显示或保存 API Key。终端可用 `npm run ai-game-creator-shell:llm-status` 做同类配置自检,缺配置时以非零状态退出。发布 App 的真实密钥只放 Tauri 应用配置目录中的 `game-creator.config.json`;主窗口“配置”面板可读写该文件,但 API Key 不写入聊天、本地项目、trace 或 manifest。
- 2026-06-25 调整:`npm run ai-game-creator-shell:dev` 固定加载 `http://127.0.0.1:3080/`Vite 继续 `strictPort` 与 Tauri `devUrl` 对齐。`beforeDevCommand` 改为先复用已经跑在 3080 且页面标题为 `AI 游戏创作` 的本 app Vite server,避免上次 Tauri 退出后遗留的同 app Vite 进程导致二次启动失败;如果 3080 是其它服务,仍直接失败并要求释放端口,不做端口漂移。
- 2026-06-25 调整:`preview.start` / `preview.stop` 必须追加 `.agent/logs/preview.log`,并把该日志列入 Preview trace step 的输出路径和 artifact 清单;这样 `preview-playtest` 任务声明的日志产物与实际本地 HTTP 预览行为一致。
- 2026-06-25 调整:AI 游戏创作 App v1 仍只维护一个全局本地 HTTP 预览实例;启动新项目预览替换旧预览时,必须 best-effort 把旧项目的 manifest preview 状态、`.agent/logs/preview.log` 和 run trace 记录为 stopped,避免旧项目状态残留 `running`。旧项目目录已删除时不阻断新预览启动。
- 2026-06-25 调整,2026-07-18 替代:正式用户 App 的项目运行工作台承载当前授权项目的本地游戏预览,release / dev CSP 都只允许 `frame-src http://127.0.0.1:*``/preview`、`/run` 和生成完成后的用户侧路径启动 `127.0.0.1` HTTP preview 后直接切换客户端运行视图,不再调用系统外部浏览器。
- 2026-06-25 调整:`project.create` 成功后的 durable 权限证据必须在聊天 `/project` 和开发窗口初始化两条入口统一写入 `.agent/logs/command.log`,避免同一能力因为入口不同导致 `/audit` 或开发排障证据不一致。
- 2026-06-25 调整:`.agent/run.latest.json` 和 `.agent/runs/<runId>.json` 必须记录 loop 的 `maxPasses` 与 `stopReason`,开发窗口直接展示该状态,避免只从 summary 文案推断 loop 是否跑满、通过、返工、写入产物或进入预览。本地 HTTP 预览的 `/` 映射到 `game/index.html`,路径解析必须 canonicalize 项目根目录和目标文件,只允许访问项目内 `game/` 与 `assets/`,拒绝 `memory/`、`.agent/`、`exports/`、`..`、反斜杠和符号链接越界;常见图片、音频、视频和 Web 资源必须返回对应 MIME。这样上传和画板回流资产能被生成游戏引用,但记忆、trace 和导出包不会被预览服务暴露。
- 2026-06-26 调整,2026-07-03 更新:AI 游戏创作 App 借鉴 Harbour 的控制平面思想,但不搬 Harbour 后台。最近 run 在 `.agent/run.latest.json` 增加可选 `lifecycleStatus`,并通过 `/agent-status`、`/agent-kill`、`/agent-retry`、`/agent-resume [说明]` 控制本地生命周期,写入 `.agent/activity.jsonl`、`.agent/output.jsonl` 和 `.agent/context.bundle.json`;聊天里的状态 / 控制结果可填入 `/read .agent/output.jsonl` 草稿继续查看 run 输出,但不直接读取文件或绕过 `file.read` 策略。v1 的 kill/retry/resume 只更新本地状态和上下文包,不伪装成能中断已发出的上游 LLM 请求;后续引入独立 runner 后再把 `pending` 接入 claim。
- 2026-07-03 调整:主窗口 Agent 状态栏新增“继续说明”,只把 `/agent-resume ` 填入聊天输入框,让用户补充说明后再走原确认流;策略快捷入口新增 project.index、asset.register、memory.write、preview.open、preview.stop、conversation.read 和 conversation.write 确认草稿,同样只填输入框,不直接写 `.agent/policy.json`。
- 2026-07-03 调整:主窗口 header 常驻项目摘要只从当前已加载的 manifest / trace 派生任务完成数、ready 数、资产来源分布和最近命令结果;未选择工作区时不显示,不为了摘要额外触发 Tauri 读取或写入,也不把任务、文件、run history 或预览开发面板搬进普通用户窗口。
- 2026-07-03 调整:普通用户通过聊天输入 `/brief` 触发项目简报入口,只基于主窗口当前已加载的 manifest、最近 run trace、预览状态、资产数量和最近命令生成聊天内简报,并提供 `/next` 作为后续草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览,也不得新增普通用户面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/goal` 查看创作目标,只基于当前 manifest.goal、最近 run goal 和 taskGraph.goal 汇总项目目标来源,并提供 `/agent-resume 细化目标:` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取 spec、上下文或 trace 文件,也不得新增普通用户目标面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/guide` 查看操作导引,只基于当前 manifest、最近 run trace、preview 和已加载命令状态判断未开始、需修复、可预览、可导出或已导出阶段,给出最多 3 个推荐命令和首选草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动 run、不得启动预览、不得写项目,也不得新增普通用户导引面板。`/guide` 只回答“下一步怎么操作”,不承接 `/brief` 的项目快照、`/mvp` 的最小范围或 `/plan` 的分工计划。
- 2026-07-04 调整:普通用户通过聊天输入 `/progress` 查看项目进度,只基于当前 manifest、最近 run trace、preview、任务、素材和已加载命令状态汇总项目阶段、任务完成度、最近 run、预览、素材和交付进度,并提供 `/run`、`/review`、`/share`、`/test-plan`、`/todo`、`/trace` 或 `/guide` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动 run、不得启动预览、不得导出试玩包、不得写项目,也不得新增普通用户进度面板。`/progress` 只回答“当前走到哪了”,不承接 `/status` 的项目状态详情、`/ready` 的试玩门槛判断、`/groups` 的逐组进度或 `/next` 的长命令目录。
- 2026-07-04 调整:普通用户通过聊天输入 `/spec` 查看创作规格包,只基于当前 manifest、最近 run trace、任务声明产物和 trace 输入 / 输出路径汇总 Planner 规格、玩法设计、数值表、美术清单、音频清单和发布说明状态,并提供 `/read .agent/spec.md` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取规格文件、不得启动预览、不得写项目,也不得新增普通用户规格面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/mvp` 查看本轮最小可玩范围,只基于当前 manifest、最近 run trace、preview、任务、资产和最近命令汇总 MVP 内、当前状态、试玩包状态和暂不做事项,并提供 `/review`、`/criteria`、`/trace`、`/run`、`/export`、`/exports` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包,也不得新增普通用户 MVP 面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/pitch` 查看试玩定位与卖点,只基于当前 manifest、最近 run trace 和 preview 状态汇总试玩定位、一句话、核心乐趣、当前可演示状态、测试者讲解口径和暂不承诺事项,并提供 `/mvp`、`/review`、`/trace`、`/open-preview` 或 `/run` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得直接继续 run,也不得新增普通用户定位面板。该入口服务试玩讲解,不承接 `/listing` 的作品页包装。
- 2026-07-04 调整:普通用户通过聊天输入 `/demo` 准备 30 秒试玩讲解稿,只基于当前 manifest、最近 run trace 和 preview 状态汇总开场、讲解顺序、口播稿、演示状态、最近试玩证据和收反馈口径,并提供 `/run`、`/open-preview`、`/trace`、`/review` 或 `/test-plan` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得发布作品,也不得新增普通用户讲解面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/rules` 查看玩法操作与规则,只基于当前 manifest、最近 run trace.taskGraph、trace artifacts 和 steps 汇总玩法目标、操作 / 胜负 / 重开口径、设计与入口产物状态、相关任务和最近程序 / 试玩步骤,并提供 `/read game/game_design.md`、`/agent-resume 操作说明:...` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取设计文件、不得启动预览或继续 run,也不得新增普通用户规则面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/tutorial` 查看新手引导检查,只基于当前 manifest、最近 run trace、preview 和任务状态汇总首屏目标、首局 30 秒引导、原型证据、试玩任务、最近引导证据和补齐项,并提供 `/rules`、`/review`、`/agent-resume 新手引导:...`、`/open-preview` 或 `/run` 草稿;该入口不得触发 Tauri 读写、不得读取设计文件、不得启动或打开预览、不得直接继续 run,也不得新增普通用户引导面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/mobile` 查看移动试玩检查,只基于当前 manifest、最近 run trace、preview 和任务状态汇总移动试玩目标、键盘 / 触屏输入口径、原型证据、移动检查项、关联任务和最近移动相关步骤,并提供 `/rules`、`/review`、`/agent-resume 移动试玩:...`、`/open-preview` 或 `/run` 草稿;该入口不得触发 Tauri 读写、不得读取代码文件、不得启动或打开预览、不得直接继续 run,也不得新增普通用户移动适配面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/compatibility` 准备兼容性说明,只基于当前 manifest、最近 run trace、preview 和静态自检状态汇总推荐环境、输入兼容、不承诺范围、反馈口径和参考命令,并提供 `/run`、`/mobile`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得上传云端、不得发布作品、不得写项目,也不得新增普通用户兼容性面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/accessibility` 查看可读性与无障碍检查,只基于当前 manifest、最近 run trace、preview 和任务状态汇总文字可读、颜色对比、按钮 / 状态命名、键盘等价、可见焦点、非颜色唯一反馈和静音可玩检查,并提供 `/rules`、`/review`、`/agent-resume 可读性与无障碍:...`、`/open-preview` 或 `/run` 草稿;该入口不得触发 Tauri 读写、不得读取代码或 trace 文件、不得启动或打开预览、不得直接继续 run,也不得新增普通用户无障碍面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/localization` 查看本地化与文案检查,只基于当前 manifest、最近 run trace、preview 和发布说明产物状态汇总默认语言、文案范围、关联任务、检查口径、暂不做事项和参考命令,并提供 `/read exports/README.md`、`/agent-resume 本地化与文案:...`、`/review` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得上传云端、不得发布作品、不得写项目,也不得新增普通用户本地化面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/performance` 查看性能与加载检查,只基于当前 manifest、最近 run trace、preview、资产数量和 trace artifact 摘要汇总入口自包含、首屏不空白、素材体积、主循环稳定、无远程依赖和预览启动检查,并提供 `/run-artifacts`、`/review`、`/open-preview`、`/run` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取产物或日志文件、不得启动或打开预览、不得直接继续 run,也不得新增普通用户性能面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/polish` 查看试玩前打磨清单,只基于当前 manifest、最近 run trace、preview、最近自检和资产数量汇总试玩前打磨范围、推荐检查顺序、关联任务和最近打磨相关步骤,并提供 `/agent-resume 打磨:...`、`/review`、`/feedback` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动预览、不得导出试玩包、不得写项目,也不得新增普通用户打磨面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/credits` 查看素材署名与来源,只基于当前 manifest.assets 汇总素材数量、上传 / 生成 / 画板来源分布、来源清单和交付前需要确认的授权 / 模型 / 画板资源口径,并提供 `/assets` 草稿;该入口不得触发 Tauri 读写、不得刷新资产、不得读取素材清单、不得导出试玩包,也不得新增普通用户署名面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/blockers` 查看当前阻塞项,只基于当前 manifest、最近 run trace、preview、最近命令、ready / failed 任务、导出记录和资产概况汇总当前阻塞项,并提供 `/run`、`/export`、`/todo`、`/review`、`/trace`、`/tasks`、`/logs`、`/art` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动预览、不得导出试玩包、不得写项目,也不得新增普通用户阻塞面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/ready` 查看试玩就绪度,只基于当前 manifest、最近 run trace、preview、最近自检、导出记录、ready / failed 任务和资产概况汇总可交付判断,并提供 `/run`、`/export`、`/todo`、`/review`、`/trace`、`/tasks`、`/art`、`/share` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动预览、不得导出试玩包、不得写项目,也不得新增普通用户就绪度面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/evidence` 查看当前验证证据台账,只基于当前 manifest、最近 run trace、preview、最近命令、静态自检、导出记录、素材和最近试玩步骤汇总已有验证证据与缺口,并提供 `/run`、`/export`、`/art`、`/logs`、`/review`、`/next` 或 `/ready` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得写项目,也不得新增普通用户证据面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/deps` 查看任务依赖链,只基于当前 manifest.tasks 和最近 run trace.taskGraph 汇总 active / carry / ready / 等待依赖、可执行任务与等待依赖,并提供 `/criteria`、`/todo`、`/tasks` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取任务文件、不得启动 run、不得修改项目,也不得新增普通用户依赖面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/revise` 准备下一轮改版说明草稿,只基于当前 manifest 和最近 run trace 汇总返工焦点、失败 / active / carry / ready 任务、最近评审 / 试玩步骤、预览和导出缺口,并填入 `/agent-resume 改版说明:...` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得继续 run、不得启动预览、不得导出试玩包、不得写项目,也不得新增普通用户改版面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/privacy` 查看隐私与导出边界,只基于当前 manifest、授权项目路径、最近 run trace、preview、资产来源和导出记录汇总 API Key、预览、本地试玩包、内部文件、素材来源和 trace 的隐私 / 交付边界,并提供 `/credits`、`/exports` 或 `/config` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得导出试玩包、不得启动预览、不得写项目,也不得新增普通用户隐私面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/risks` 查看当前项目风险,只基于主窗口当前已加载的 manifest、最近 run trace、预览状态、任务状态、资产来源和最近命令派生风险摘要,并提供首个风险处理草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览,也不得新增普通用户面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/criteria` 查看当前任务验收标准,只基于当前 manifest.tasks 和最近 run trace.taskGraph 汇总 active、carry、ready、失败或待处理任务的验收条件和产物,并提供 `/tasks` 草稿;该入口不得触发 Tauri 读写、不得读取任务文件或 trace 文件,也不得新增普通用户验收面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/groups` 查看专业组进度,只基于当前 manifest.tasks 和最近 run trace.taskGraph / passPlans 汇总六个专业组的完成、active、carry、ready、失败数量和下一步任务,并提供 `/tasks` 草稿;该入口不得触发 Tauri 读写、不得读取任务文件或 trace 文件,也不得新增普通用户专业组面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/budget` 查看最近 run 预算,只基于当前最近 run trace 汇总轮次、工具调用、stopReason 和下一步建议,并提供 `/review`、`/publish`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取 trace 文件,也不得新增普通用户预算面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/qa` 查看质量检查清单,只基于当前 manifest、最近 run trace、最近命令和 preview 状态汇总 Evaluator、任务、静态自检、试玩和产物状态,并提供 `/review`、`/tasks`、`/trace`、`/playtest`、`/publish` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取 trace 或日志文件、不得启动或打开预览,也不得新增普通用户 QA 面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/changes` 查看最近生成变更,只基于当前 manifest、最近 run trace 的 artifacts / steps 和最近命令汇总可验产物、最近输出、当前资产和真实差异查看方向,并提供 `/read <首个可验产物>` 或 `/run-artifacts` 草稿;该入口不得触发 Tauri 读写、不得读取产物或日志文件、不得执行 checkpoint diff,也不得新增普通用户变更面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/todo` 查看下一轮小步清单,只基于当前 manifest 和最近 run trace 汇总失败、active、carry、ready 或待处理任务,并提供 `/tasks`、`/review` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取任务文件、不得启动 run、不得修改项目,也不得新增普通用户小步面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/plan` 查看下一轮分工计划,只基于当前 manifest 和最近 run trace 汇总协作顺序、各专业组接手任务、空档组和首个继续执行草稿,并提供 `/agent-resume 下一轮计划:...`、`/review` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取任务文件、不得启动 run、不得修改项目,也不得新增普通用户计划面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/review` 查看 Evaluator 评审状态,只基于主窗口当前已加载的最近 run trace 派生通过 / 需返工状态、返工焦点、返工路线和最近评审步骤,并提供 `/read .agent/findings.md` 或 `/agent-resume ` 草稿;该入口不得直接读取评审文件、不得触发 Tauri 读写,也不得新增普通用户评审面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/context` 查看生成上下文来源,只基于当前 manifest 和最近 run trace 列出项目对话、短期记忆、长期记忆、项目黑板、Agent 对话、Agent 私有记忆、manifest、最近 trace 和最近 LLM 输入路径,并提供 `/read` 或 `/memory blackboard` 草稿;该入口不得触发 Tauri 读写、不得读取上下文文件,也不得新增普通用户上下文面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/timeline` 查看项目活动时间线,只基于当前 manifest.commandRuns 和最近 run trace 汇总最近命令、日志读取草稿和最近 Agent 步骤,并提供 `/read`、`/trace` 或 `/history` 草稿;该入口不得触发 Tauri 读写、不得读取日志或 trace 文件,也不得新增普通用户时间线面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/playtest` 查看试玩状态,只基于主窗口当前已加载的 manifest、最近 run trace 和 preview 状态派生原型是否通过、预览是否运行、Playtest 任务状态、最近试玩步骤和预览日志读取命令,并提供 `/run`、`/open-preview`、`/trace` 或 `/review` 草稿;该入口不得触发 Tauri 读写、不得启动或打开预览、不得读取日志,也不得新增普通用户试玩面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/test-plan` 准备手动测试计划,只基于主窗口当前已加载的 manifest、最近 run trace 和 preview 状态汇总手动用例、关联 Preview / Playtest 任务和最近试玩证据,并提供 `/run`、`/open-preview`、`/trace`、`/review` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得直接继续 run,也不得新增普通用户测试面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/audience` 查看首批试玩对象,只基于主窗口当前已加载的 manifest、最近 run trace、preview 和试玩任务状态汇总首批试玩人群、测试者规模、观察重点和暂不面向场景,并提供 `/run`、`/feedback`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得直接继续 run,也不得新增普通用户对象面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/invite` 准备试玩邀请文案,只基于主窗口当前已加载的 manifest、最近 run trace 和 preview 状态汇总邀请对象、短文案、发送前检查和收反馈口径,并提供 `/run`、`/feedback`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得写项目,也不得新增普通用户邀请面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/bug-report` 准备缺陷复现记录,只基于主窗口当前已加载的 manifest、最近 run trace 和 preview 状态汇总复现入口、最近试玩证据、记录模板、严重度口径和修复草稿,并提供 `/run`、`/agent-resume 缺陷修复:`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得写项目,也不得新增普通用户缺陷面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/survey` 准备试玩问卷问题,只基于主窗口当前已加载的 manifest、最近 run trace 和 preview 状态汇总问卷使用场景、五个核心问题、记录格式和追踪方式,并提供 `/run`、`/invite`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得写项目,也不得新增普通用户问卷面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/cover` 准备封面与缩略图检查,只基于主窗口当前已加载的 manifest、最近 run trace、preview 和资产状态汇总封面候选、用途尺寸、选择口径和补齐路径,并提供 `/run`、`/art`、`/listing`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得截屏、不得裁剪、不得读取文件、不得启动或打开预览、不得导出试玩包、不得上传云端、不得发布作品、不得写项目,也不得新增普通用户封面面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/screenshots` 准备宣传截图清单,只基于主窗口当前已加载的 manifest、最近 run trace、preview 和资产状态汇总截图目标、拍摄顺序、命名建议和作品页搭配,并提供 `/run`、`/listing`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得截屏、不得读取文件、不得启动或打开预览、不得导出试玩包、不得写项目,也不得新增普通用户截图面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/trailer` 准备试玩短视频脚本,只基于主窗口当前已加载的 manifest、最近 run trace、preview 和资产状态汇总 15 秒结构、镜头清单、口播节奏和录制提示,并提供 `/run`、`/share`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得录屏、不得读取文件、不得启动或打开预览、不得导出试玩包、不得上传云端、不得写项目,也不得新增普通用户录屏面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/faq` 准备试玩常见问答,只基于主窗口当前已加载的 manifest、最近 run trace 和 preview 状态汇总试玩问答、回答口径、测试者提醒和交付搭配,并提供 `/run`、`/share`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得上传云端、不得写项目,也不得新增普通用户 FAQ 面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/post` 准备社区发布文案,只基于主窗口当前已加载的 manifest、最近 run trace、preview 和资产状态汇总短文案、长文案结构、标签建议和 CTA,并提供 `/run`、`/store`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得上传云端、不得发布作品、不得读取文件、不得启动或打开预览、不得导出试玩包、不得写项目,也不得新增普通用户社区发布面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/store` 准备上架资料清单,只基于主窗口当前已加载的 manifest、最近 run trace、preview、资产和发布说明状态汇总必备资料、首发范围、上架前检查和参考命令,并提供 `/run`、`/listing`、`/review`、`/trace`、`/read exports/README.md` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得上传云端、不得发布作品、不得读取文件、不得启动或打开预览、不得导出试玩包、不得写项目,也不得新增普通用户上架面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/media-kit` 准备媒体资料包清单,只基于主窗口当前已加载的 manifest、最近 run trace、preview、资产和发布说明状态汇总对外资料、素材缺口、组装顺序和参考命令,并提供 `/run`、`/screenshots`、`/review`、`/trace`、`/read exports/README.md` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得截屏、不得录屏、不得读取文件、不得启动或打开预览、不得导出试玩包、不得上传云端、不得发布作品、不得写项目,也不得新增普通用户媒体包面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/release-notes` 准备试玩更新说明,只基于主窗口当前已加载的 manifest、最近 run trace、preview、资产和发布说明状态汇总本轮变化、主要产物、玩家可见说明和已知限制,并提供 `/run`、`/media-kit`、`/review`、`/trace`、`/read exports/README.md` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得上传云端、不得发布作品、不得写项目,也不得新增普通用户更新说明面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/known-issues` 准备已知问题清单,只基于主窗口当前已加载的 manifest、最近 run trace、preview 和任务状态汇总已知问题、试玩限制、反馈入口和发送前检查,并提供 `/run`、`/share`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得上传云端、不得发布作品、不得写项目,也不得新增普通用户已知问题面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/feedback` 准备试玩反馈和修改说明,只基于当前 manifest、最近 run trace 和 preview 状态列出反馈方向、反馈模板和参考命令,并提供 `/run`、`/agent-resume 试玩反馈:`、`/review` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得直接继续 run,也不得新增普通用户反馈面板。
- 2026-07-04 调整:普通用户通过聊天输入 `/retention` 准备首轮复玩/留存观察清单,只基于当前 manifest、最近 run trace、preview、最近试玩证据、素材数量、发布说明和导出状态汇总测试者样本、复玩信号、记录模板和暂不做事项,并提供 `/run`、`/feedback`、`/review`、`/trace` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览、不得导出试玩包、不得上传云端、不得发布作品、不得写项目,也不得新增普通用户留存面板;首版不做真实埋点、留存报表、用户画像、A/B 实验、排行榜或账号留存。
- 2026-07-03 调整:普通用户通过聊天输入 `/listing` 准备作品页文案清单,只基于当前 manifest、最近 run trace、发布组任务和资产清单汇总标题、一句话卖点、标签口径、封面素材、发布说明和最近运营步骤,并提供 `/read exports/README.md`、`/review`、`/art`、`/publish` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得读取发布说明、不得上传云端、不得发布作品,也不得新增普通用户作品页面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/handoff` 生成当前项目交接摘要,只基于主窗口当前已加载的 manifest、授权项目路径、最近 run trace、Agent 状态和已加载 run 历史生成交接信息,并提供 `/next` 后续草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览,也不得新增普通用户面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/runs` 查看已加载 Run 历史读取命令,只基于主窗口当前已加载的 latest trace 和最多 100 个历史 run 中已经载入的批次生成 `/trace` 或 `/read .agent/runs/...` 草稿;该入口不得额外触发 Tauri 读取、不得滚动加载更多历史、不得启动或打开预览,也不得新增普通用户面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/run-files` 查看 Agent 运行辅助文件读取命令,只列出 `.agent/output.jsonl`、`.agent/activity.jsonl` 和 `.agent/context.bundle.json` 对应 `/read` 草稿并提供首个草稿;该入口不得直接读取辅助文件、不得触发 Tauri 读写,也不得新增普通用户面板。
- 2026-07-03 调整:`/llm-status` 读取到的 agent 级 LLM 配置状态可回填到主窗口 Agent 状态列表、聊天侧 `/agents` 汇总和单 Agent 对话头部,显示 provider 类型、模型、流式开关和 API Key 是否已读取;密钥本体仍不能进入聊天、状态列表、manifest、trace 或本地项目文件。
- 2026-07-03 调整:开发窗口日志面板提供 `.agent/logs/command.log`、`.agent/logs/preview.log` 和 `.agent/logs/agent.log` 的只读查看入口,复用 `file.read` 授权策略;普通用户聊天输入 `/logs` 只列出这三个日志文件对应的 `/read ...` 草稿 / 命令并提供首个草稿,不直接读取日志,不新增普通用户日志面板,实际读取仍走聊天侧 `file.read`。
- 2026-07-03 调整:单 Agent 对话面板允许用户把当前输入手动追加到该 agent 的 `memory/agents/<group>/<role>.md` 私有记忆;写入复用 `memory.write` 项目策略、项目锁和 Tauri 本地目录能力,不把普通对话流水自动混入私有记忆;聊天侧 `/agent-conversations` 和 `/agent-memories` 只列出同一批 Agent 对话与私有记忆读取命令并提供首个 `/read` 草稿,不直接读取文件。
- 2026-07-03 调整:普通用户通过聊天输入 `/art` 查看美术素材,只基于当前 manifest 盘点图片、视频和序列帧素材的数量、来源、画板接入状态和路径,并提供 `/generate-art 首版核心美术素材` 或 `/read assets/manifest.art.json` 草稿;该入口不得触发 Tauri 读写、平台生成、画板同步或新增普通用户美术面板。
- 2026-07-03 调整:主窗口新增音效登记和画板音频导入快捷入口,只填入 `/asset-register assets/audio/sfx.wav audio audio/wav` 或 `/import-canvas-asset assets/audio/sfx.wav ` 草稿;聊天输入 `/audio` 只基于当前 manifest 盘点音频素材、来源和路径,并给出登记音效或读取 `assets/manifest.audio.json` 的草稿。音乐组仍复用现有资产登记 / 画板回流链路,不新增独立音频生成系统。
- 2026-07-03 调整:普通用户通过聊天输入 `/balance` 查看数值与难度口径,只基于当前 manifest.tasks、最近 run trace.taskGraph、trace artifacts 和 steps 汇总数值组任务、验收口径、`game/balance.json` 状态和最近数值步骤,并提供 `/read game/balance.json` 或 `/agent-resume 数值调整:...` 草稿;该入口不得触发 Tauri 读写、不得读取数值表、不得启动预览或继续 run,也不得新增普通用户数值面板。
- 2026-07-03 调整:主窗口新增常用生成产物读取入口,只把入口 HTML、设计、数值、美术清单、音频清单和发布说明对应的 `/read` 草稿填入聊天输入框;聊天命令 `/artifacts` 只列出同一组固定读取命令并提供首个读取草稿,`/run-artifacts` 只列出最近 trace 里的产物读取命令并提供首个 `/read` 草稿,`/logs` 只列出固定日志读取命令;实际读取仍走聊天侧 `file.read` 权限流,不直接读本地文件。
- 2026-07-03 调整:普通用户通过聊天输入 `/share` 准备试玩交付清单,只基于当前 manifest、授权项目路径、最近 run trace、preview 状态和 manifest.commandRuns 汇总原型通过状态、本地预览、本地试玩包、测试者说明和反馈收集方向,并提供 `/export`、`/exports`、`/trace`、`/review` 或 `/next` 草稿;该入口不得触发 Tauri 读写、不得导出试玩包、不得列出历史包、不得上传云端、不得生成公开分享链接,也不得新增普通用户分享面板。
- 2026-07-03 调整,2026-07-04 更新:普通用户通过聊天输入 `/next` 触发下一步建议入口,只基于主窗口当前已加载的 manifest、最近 run trace 和最近命令摘要生成聊天建议,列出 `/goal`、`/guide`、`/progress`、`/spec`、`/mvp`、`/pitch`、`/demo`、`/rules`、`/tutorial`、`/mobile`、`/compatibility`、`/accessibility`、`/localization`、`/performance`、`/polish`、`/blockers`、`/ready`、`/evidence`、`/deps`、`/revise`、`/privacy`、`/audience`、`/invite`、`/bug-report`、`/survey`、`/cover`、`/screenshots`、`/trailer`、`/faq`、`/post`、`/store`、`/media-kit`、`/release-notes`、`/known-issues`、`/tasks`、`/criteria`、`/groups`、`/balance`、`/budget`、`/qa`、`/changes`、`/plan`、`/todo`、`/trace`、`/review`、`/context`、`/timeline`、`/playtest`、`/test-plan`、`/feedback`、`/retention`、`/share`、`/listing`、`/run`、`/open-preview`、`/assets`、`/credits`、`/art`、`/audio`、`/publish`、`/artifacts`、`/run-artifacts`、`/passes`、`/run-files`、`/internals`、`/logs`、`/agent-resume ` 等安全命令草稿方向,并提供一个首选草稿;该命令不得直接执行 Tauri 读写、启动或打开预览、读取本地文件,也不得绕过原有命令确认和 `file.read` 权限流。
- 2026-07-03 调整:普通用户通过聊天输入 `/publish` 生成发布准备清单,只基于主窗口当前已加载的 manifest、最近 run trace、预览状态、资产来源和最近命令摘要列出原型通过、预览、任务、资产、音频、包装说明和试玩包状态,并提供 `/run`、`/trace`、`/agent-resume ` 或 `/export` 草稿;该入口不得触发 Tauri 读写、不得启动或打开预览、不得读取文件,也不得新增普通用户发布面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/internals` 只列出 `.agent/manifest.json`、`.agent/run.latest.json`、`.agent/spec.md`、`.agent/findings.md`、`.agent/policy.json`、`.agent/project.index.json`、`.agent/agent.db` 和 `.agent/conversations/project.jsonl` 的 `/read` 草稿,并提供首个读取草稿;该入口不得直接读取内部文件、不得触发 Tauri 读写,也不得新增普通用户内部文件面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/passes` 只从当前已加载的最近 run trace artifacts 中筛选 `.agent/passes/` 轮次产物,列出 `/read` 草稿并提供首个读取草稿;该入口不得直接读取轮次文件、不得触发 Tauri 读写,也不得新增普通用户轮次面板。
- 2026-06-25 调整:本地 HTTP 预览静态 `HEAD` 必须返回与 `GET` 相同的真实 `Content-Length`,但不返回 body;浏览器、图片、音频和视频探测不能拿到 `Content-Length: 0` 的假响应。
- 2026-06-25 调整:普通用户通过聊天输入 `/run` 触发待确认 `game.run_local`,确认后只能复用白名单 `game.static_smoke` 自检当前 `game/index.html`,通过后启动 `127.0.0.1` 本地 HTTP 预览。独立执行 `game.static_smoke` 时如果已有 `.agent/run.latest.json`,必须追加 `Playtest / game.static_smoke` trace step,避免“运行了代码但编排 trace 不可见”。
- 2026-06-25 调整:普通用户通过聊天输入 `/trace` 触发只读 `agent.trace_read`,读取 `.agent/run.latest.json` 并在聊天里摘要 loop 轮次、stopReason、nextStep、active / carry-over 任务、repairRoutes、agent 建议命令和最近 step。trace 面板仍只在开发窗口展示,普通用户窗口不新增面板。
- 2026-06-25 调整:普通用户通过聊天输入 `/import-canvas-export /绝对/画板素材.zip 画板项目ID` 触发待确认 `canvas.export_import`,读取现有 `/editor/canvas` 素材导出 ZIP。导入命令只读取用户指定 ZIP,写入当前本地项目 `assets/canvas-imports/`,基础护栏限制路径逃逸、文件数量和解压体积;导出包没有真实 resourceId 时,用 `canvas-export:<file>` 作为可追踪 assetObjectId,不伪造后端画板资源行。
- 普通用户通过聊天输入 `/sync-canvas-project 画板项目ID` 触发待确认 `canvas.project_sync`;普通模式用当前陶泥儿登录态读取 `/api/editor/projects/{projectId}` 并通过 `/api/assets/read-url` 换签,高级模式使用对应 External v1 路由。固定官方 origin、owner 和凭据均不写入 manifest、Agent DB、trace 或日志。
- `game.generate_draft` 在当前模式具备画板服务授权且美术组缺少 `canvas` 来源图片资产时,复用同一平台生成链路生成首版美术素材并下载到本地;普通模式使用登录态内部路由,高级模式使用 External v1。
- 美术组 `Asset` 和音乐组 `SFX` 在缺少对应 `canvas` 来源资产时建议同步;普通模式未登录或高级模式 Developer Key 缺失时只给出准确的能力不可用说明,不伪造生成结果。
- 2026-06-24 调整:同一本地项目多次 `game.generate_draft` 必须追加 `memory/session.md` 与 `memory/project.md`,不得覆盖历史对话和创作目标记录。
- 2026-07-01 调整:AI 游戏创作 App 在 `memory/session.md` 与 `memory/project.md` 之外新增项目级黑板 `memory/blackboard.md`,只记录重要跨 agent 决策、依赖和风险摘要;每个角色 agent 拥有私有记忆 `memory/agents/<group>/<role>.md`。角色 brief 必须读取自己的私有记忆和项目黑板;`game.generate_draft` 通过 Evaluator 与 `game.static_smoke` 后,追加项目黑板摘要和各角色成功产出摘要,不得覆盖既有记忆。失败 run 仍只保留 trace 和 pass 快照,不写最终记忆摘要。
- 2026-07-06 调整:AI 游戏创作 App 主聊天普通文本改为进入主聊天 Agent,而不是直接排队 `game.generate_draft`;主聊天 Agent 读取短期记忆、长期记忆、项目黑板、最近项目对话和本地资产摘要作为背景,支持 `agentLlm.chat` 单独 provider 配置,但只做自然语言交互、澄清和 slash 命令建议,不写项目、不运行工具、不伪装生成结果。显式 `/generate <创作想法>` 或 `/draft <创作想法>` 才进入 `game.generate_draft` 待确认流。
- 2026-07-09 调整:AI 游戏创作 App 新增 Agent Runtime V1 最小可观测状态。单 Agent 对话和生成 loop 中的角色 brief 必须写 `.agent/runtime/agents/<agentId>.json` 与 `.agent/runtime/events/<agentId>.jsonl`,记录 `agentId`、`taskId`、`sessionId`、`runId`、`source`、`status`、`phase`、当前任务 / 动作、计划、观测、允许工具、最近回复和错误;单 Agent 流式聊天事件要回传最新 `runtimeState`,开发单 Agent 聊天页和项目内单 Agent 对话弹窗只读展示该状态,读取失败必须可见提示,不得静默伪装为空状态。`source=agent-chat` 表示开发者单 Agent 对话,`source=generate-draft` 表示生成 loop 角色 briefcarry-over brief 只记录继承和完成,不伪装成重新调用 LLM。Runtime state 写入使用临时文件替换,event JSONL 读取跳过坏行,用户 prompt / 回复摘要进入 runtime 与 `agent.db` 前复用敏感上下文过滤;`.agent/runtime/` 是运行观测状态,不进入项目索引、checkpoint diff 或 restore 删除范围。该层仍是本地 JSONL 状态与事件,不引入 SQLite、常驻独立进程、远程 runner 或可中断上游 LLM 的承诺。
- 2026-07-10 调整:Agent Runtime state 新增 `toolPolicy`,从项目权限策略派生工具级 `allowedTools`、`autoTools`、`confirmTools` 和 `deniedTools`。后台 planning prompt 必须带入该快照,让 Agent 在规划阶段知道工具策略;执行阶段仍由 Runtime 白名单和项目权限 gate 决定。`blackboard.write` 继承 `memory.write` 策略,`agent.message` 继承 `conversation.write` 策略,`agent.delegate` 使用独立 `agent.delegate` 策略。
- 2026-07-10 调整:`.agent/policy.json` 支持 `agentPolicies`,用规范 Agent id 保存单个 Agent 的 `deniedCommands / confirmCommands`。Runtime 计算有效工具策略时把项目级策略和 Agent 级策略叠加,项目级策略继续对所有 Agent 生效,Agent 级策略只能进一步拒绝或要求确认,不能放宽项目级策略;拒绝优先于确认。主聊天新增 `/agent-policy-deny Agent 命令`、`/agent-policy-allow Agent 命令`、`/agent-policy-confirm Agent 命令` 和 `/agent-policy-auto Agent 命令`,继续通过 `project.policy_write` 确认卡写入策略。
- 2026-07-10 调整:后台 Agent 工具命中确认策略时不再当作 `blocked` observation 继续收尾,而是把当前 Runtime 写成 `status/phase = waiting-for-confirmation``waitingOn` 固定为等待开发者确认工具动作,`recentToolCalls`、事件流、任务记录和 `taskQueue.waitingForConfirmation` 都保留该事实;同一 Agent 的后台 drain 暂停,不继续消费后续 pending 任务。命中拒绝策略仍使用 `blocked` observation 交回 Agent 修正计划。
- 2026-07-10 调整:Agent Runtime 后台任务支持按 Agent / runId 取消和重试。取消先通过 `.agent/runtime/cancel/<agentId>/<runId>.json` 写入本地取消请求;pending 任务被取消后不会被 drain 消费,running 任务在原 worker 仍持锁时只投影为 `cancelling`,必须等当前 LLM 或工具调用返回后的检查点真正停下,才由持锁 worker 向任务 JSONL、事件流和 `agent.db` 追加 `cancelled` 审计,不再继续执行工具或保存最终 assistant 回复。`cancelling` 期间禁止重试;重试只能基于已有非 running / pending / waiting-for-confirmation / cancelling 任务创建新的 run,并继续走 `agent.resume` 自动权限和同一 Agent 队列锁。
- 2026-07-10 调整:Agent Runtime 后台任务的 `runId` 是同一 Agent 任务历史的身份,不允许复用覆盖。`start_game_creator_agent_runtime_task`、`agent.delegate` 和 retry 进入后台队列前会读取该 Agent 全量 task JSONL 历史;若调用方传入的规范化 runId 已存在,Runtime 自动追加 `-dup-<timestamp>-<attempt>` 生成实际 runId。任务队列、delegate observation 和 `agent.db` 审计都必须使用实际 runId,避免 `latest_game_creator_agent_runtime_tasks` 按 runId 去重时折叠掉不同任务。
- 2026-07-10 调整:Agent Runtime 的 `memory.write scope=agent` 只能写当前 Agent 自己的私有记忆。若 action 指定其他 `agentId / targetAgentId`Runtime 返回 `blocked` observation,不写目标 Agent 私有记忆、不写 `agent.runtime.memory.write` 审计;跨 Agent 共享稳定结论必须走 `blackboard.write`,给单个 Agent 留上下文必须走 `agent.message`。
- 2026-07-10 调整:Agent Runtime 和本地对话使用 append-only JSONL 作为事实源时,进程内必须按目标文件路径串行追加整行。`.agent/agent.db`、`.agent/conversations/**/*.jsonl`、`.agent/runtime/events/*.jsonl`、`.agent/runtime/tasks/*.jsonl`、`.agent/activity.jsonl` 和 `.agent/output.jsonl` 统一走共享追加 helper,避免多个后台 Agent 并行完成时 JSON record 与换行交错。
- 2026-07-10 调整:Agent Runtime 待确认工具动作改用 durable `AgentRuntimePendingToolAction`。Runtime 将精确 `action` 输入、当前 task/run、loop 轮次、action 序号、计划、已有 observations 与后续 loop 所需上下文先做敏感内容和项目绝对路径校验,再通过临时文件替换原子写入 `.agent/runtime/pending-actions/<agentId>/<runId>.json`;公共 runtime state 的 `pendingToolAction` 只暴露 `actionId / actionFingerprint / tool / inputSummary / reason / requestedAt` 安全摘要,完整输入不进入公共状态。`actionFingerprint` 绑定工具名、完整输入 JSON 与实际执行使用的 task context`actionId` 还绑定 run、loop、action 序号和 occurrence nonce,使同一 run 内输入相同的两次动作仍是两个不同发生。确认和拒绝都必须匹配 `runId + actionId`Runtime 会重算指纹并与私有落盘动作及公共摘要交叉校验,不一致时失败关闭。确认通过后在同一 run 直接执行持久化的原 action,把真实 observation 接回后续 Agent loop,不创建新 run,也不让模型重复生成待确认动作;拒绝不执行工具,写入 `blocked` observation 后在同一 run 继续规划。待确认账本按 `pending-confirmation / approved / executing / observed-approved / observed-rejected` 迁移:重启时 `approved` 可恢复精确动作,已持久化 observation 可直接续 loop`executing` 表示外部副作用结果未知,Runtime 必须进入 `failed / needs-reconciliation` 并禁止自动重放,开发者核对项目状态后只能先取消原任务。waiting run、完整待确认动作和安全摘要均已落盘,App 重启不会越过该 run 去启动后续任务;等待期间同 Agent 新任务只保持 `pending`,确认、拒绝或取消结束后再由同一 drain 串行排空。`.agent/runtime/` 是 Runtime 私有控制面,通用 `file.list / file.read / file.write / file.delete` 不得列出、读取、修改或删除;checkpoint/index/diff/restore 继续整体排除该目录。每 Agent 锁包含唯一 token,旧持有者析构时只删除自己的锁;Linux 上其他仍存活进程的锁不会因超过固定时长被抢占。确认、拒绝及工具 observation 分别写入 `agent.runtime.tool_confirmation.approved`、`agent.runtime.tool_confirmation.rejected` 和 `agent.runtime.tool_observation` 审计;pending 和 confirmation 文件只在 observation/终态可靠落盘后清理,失败清理会显式报错。
- 2026-07-10 调整:per-agent 互斥锁最终改用 OS 级文件锁,而不是依赖 JSON token、PID、超时和 `remove + create_new` 竞争所有权。Unix 使用非阻塞独占 `flock`,Windows 使用禁止共享的文件句柄;锁文件只保留诊断元数据并长期存在,进程退出会由 OS 释放所有权。确认、拒绝和取消必须先取得同一系统锁,再重新读取 runtime、latest task 和 durable pending action 后迁移状态;恢复入口也必须先拿锁,再读取 durable pending action 或 recoverable task,禁止用锁外旧快照覆盖并发结果。waiting 取消只短暂等待原 worker 释放系统锁,running 取消拿不到锁时只保留 tombstone,并由原 worker 在 LLM / 工具成功或失败返回后的检查点收束,不得根据 Runtime status 抢锁。这条最终实现取代上一条中的 token 删除和 Linux PID 存活判断描述。
- 2026-07-10 调整:`AgentRuntimePendingToolAction` 同时作为白名单自动工具的精确动作账本,新增 `executionMode = auto | confirmation`。自动动作执行前必须依次持久化 `approved` 与 `executing`,工具返回后先持久化 `observed-approved` observation,再写 Runtime task/state/event/audit;账本必须覆盖后续 LLM replan,只有下一条精确动作以新账本接管,或当前 run 的 completed / failed / cancelled 终态可靠落盘后才能清理。App 在 `approved + auto` 崩溃点可恢复同一精确动作,在 `observed-approved + auto` 崩溃点只能复用 observation 继续规划,不得重放工具;`executing + auto` 一律进入 `failed / needs-reconciliation`。`needs-reconciliation` 是恢复硬屏障,即使磁盘账本因前一轮写入失败仍停在 `approved` 也不得继续执行或排空队列。恢复时若项目策略从 auto 收紧为 confirm,原 action 保持同一 actionId / fingerprint 并转回 `pending-confirmation + confirmation`,等待开发者决定。自动动作另写 `agent.runtime.tool_action.executing`、`agent.runtime.tool_action.observed` 和 `agent.runtime.tool_action.needs_reconciliation` 审计。
- 2026-07-10 调整:`needs-reconciliation` 同时是整个 Agent 队列的准入屏障,不只保护当前 pending action。屏障存在时,通过开发窗口、`agent.delegate` 或 `agent.schedule_ready` 投递的新 run 只能追加为 `pending`,任何恢复和 drain 都不得启动后续任务;取消某个排队 run 也不能越过核对 run 去启动再后面的任务。新任务的 waiting / cancelling / reconciliation 准入判断必须在成功取得该 Agent 的 OS 锁后重新读取,禁止用锁外快照启动新 run;通过检查后也必须从 task JSONL 选择最早的 pending run 作为队首启动,不能直接启动当前调用方刚提交的 run。即使私有 pending ledger 意外缺失,也必须从 Runtime state 或 task JSONL 的最新 `needs-reconciliation` 记录识别屏障,继续禁止 retry;开发者人工核对后显式取消该 run,才允许既有 per-agent drain 按顺序恢复队列。
- 2026-07-10 调整:Agent Runtime 工具箱新增 `task.create`,用于让 Agent 把目标拆成新的 manifest 任务,而不只能更新 seed task。该工具默认 `confirm` 权限,写入前要求 taskId 唯一、依赖指向已有任务、列表长度受限,并写 `agent.runtime.task.create` 审计;策略要求确认或拒绝时不修改 `.agent/manifest.json`。
- 2026-07-10 调整:Agent Runtime 新增 `agent.schedule_ready` 调度入口,默认 `confirm` 权限。命令会扫描 `.agent/manifest.json` 中依赖已完成且仍为 `pending` 的 ready task,先把任务标成 `running`,再用 taskId 作为 Agent id 投递到既有后台队列,source 记为 `agent-ready-task-scheduler`,并写 `agent.runtime.ready_task.scheduled` 审计;后续执行仍走原 per-agent 锁、任务 JSONL、LLM loop、工具策略和事件流,不新增独立 worker。默认确认策略下该命令不会静默调度。
- 2026-07-10 调整:Agent Runtime state 新增 `recentToolCalls`,后台 loop 每次执行白名单工具后记录最近 20 条结构化动作,包含 tool、status、actionFingerprint、inputSummary、reason、summary、detail 和 updatedAt。状态面板展示最近动作与安全目标摘要时使用该字段,不解析 observation 文本;写入前继续过滤敏感上下文,不保存原始密钥、待写正文或任意未过滤输入。
- 2026-07-10 调整:Agent Runtime state 新增 `currentGoal` 和 `waitingOn`。`currentGoal` 固定表达本轮任务目标,`waitingOn` 表达当前等待 LLM、工具观察、开发者输入或失败处理;后台任务生命周期、`agent.run_status` observation、下一轮 planning prompt、开发单 Agent 对话页、项目内 Agent 对话弹窗和主窗口 Agent 状态列表都必须展示同一份目标 / 等待状态。
- 2026-07-10 调整,2026-07-12 更新:Agent Runtime state 新增 `loopIteration / maxLoopIterations / toolActionBudget`。后台 Agent loop 每轮规划前刷新当前轮次、当前 6 轮上下文压缩窗口的结束轮次和每轮工具动作预算;`maxLoopIterations` 随窗口推进显示 6、12 等结束轮次。开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示该进度;字段只做运行观测,不构成单个 run 的固定轮数上限,也不改变权限 gate。
- 2026-07-10 调整:Agent Runtime state 新增 `planSteps / activePlanStepIndex`。Runtime 从 Agent 输出的 `plan` 派生结构化计划步骤,并在 action / observation / response / error 生命周期中更新 `pending / active / completed / failed` 和 detail;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示步骤进度,不再只依赖不可定位的 plan 字符串。
- 2026-07-10 调整:开发单 Agent 对话页和项目内 Agent 对话弹窗的 Runtime 面板接入 `recentEvents`,展示最近 `thinking_summary / plan / action / observation / response / error` 事件,避免只从当前状态、observation 字符串或最近工具动作里倒推 Agent loop。
- 2026-07-10 调整:后台 Runtime 每次追加 `.agent/runtime/events/<agentId>.jsonl` 后会发送 `game-creator-agent-runtime-update` Tauri 事件,payload 带当前 `AgentRuntimeResult`;开发单 Agent 聊天页、项目内 Agent 对话弹窗和主窗口 Agent 状态列表实时合并该结果,但事件不替代 `.agent/runtime/agents`、`events` 和 `tasks` 的落盘事实源。
- 2026-07-10 调整:Agent Runtime state / result 新增 `taskQueue` 观测摘要,从 `.agent/runtime/tasks/<agentId>.jsonl` 中每个 `runId` 的最新记录汇总 `total / pending / running / completed / failed / latestRunId`。开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都使用该字段判断同一 Agent 是否仍有排队任务;它不是新的调度器、SQLite 或跨重启独立 worker。
- 2026-07-10 调整:Agent Runtime V1 后台工具箱新增只读 `task.list`。Agent 可自行读取 `.agent/manifest.json` 的 seed task 状态、依赖、产物交接和按依赖计算的 `readyTaskIds`,用于判断下一步任务;该工具必须受 `task.list` 项目权限策略保护,策略要求确认或拒绝时不得把任务图细节放进 observation。
- 2026-07-10 调整:Agent Runtime V1 后台工具箱新增受策略保护的 `task.update`。Agent 只能把 `.agent/manifest.json` 中已有 seed task 的状态更新为 `pending`、`running`、`waiting-for-confirmation`、`completed` 或 `failed`Runtime 必须复用项目写锁、`task.update` 权限策略和 `.agent/agent.db` 审计记录;策略要求确认或拒绝时不得修改 manifest,不得创建新任务。
- 2026-07-10 调整:Agent Runtime V1 后台工具箱新增只读 `file.list`。Agent 可自行列出项目文件摘要或相对路径范围内的条目,先观察项目结构再决定是否读取具体文件;该工具必须受 `file.list` 项目权限策略保护,observation 只返回项目相对路径、类型和大小,不读取文件内容、不返回本机绝对路径。
- 2026-07-10 调整:Agent Runtime V1 后台工具箱新增受策略保护的 `agent.delegate`。Agent 可把任务投递给另一个 Agent 的独立后台队列,复用目标 Agent 既有锁和 pending drain 语义;策略要求确认或拒绝时不得写目标 Agent 对话、不得启动目标任务,也不得写 `agent.runtime.agent.delegate` 审计记录。
- 2026-07-10 调整:Agent Runtime V1 新增 `resume_game_creator_agent_runtime_tasks` 恢复入口。客户端读取项目 Runtime 时对每个项目路径最多自动尝试一次恢复;恢复命令必须通过 `agent.resume` 自动权限,默认需要确认或被拒绝时不会静默启动。恢复扫描 `.agent/runtime/tasks/<agentId>.jsonl` 里的上一进程遗留 `running` 或 `pending` 任务,同一 Agent 同时存在二者时先重接遗留 `running`,再由既有 drain 串行继续 `pending`,并写 `agent.runtime.background_task.recovered` 审计记录。该能力只恢复本地 JSONL 队列到当前 App 进程,不是跨重启常驻 worker,也不承诺恢复已发出的上游 LLM 请求。
- 2026-07-10 调整:每个 Agent 新增独立持久化 Session 管理。legacy `agent-session-<agentId>` 继续读写 `.agent/conversations/agents/<agentId>.jsonl`;新 Session 写 `.agent/conversations/agents/<agentId>/sessions/<sessionId>.jsonl``.agent/runtime/sessions/<agentId>.json` 原子保存 Session catalog 和 active Session。开发单 Agent 聊天页支持列表、创建、切换、归档和归档历史只读查看;归档不删除消息,运行中、排队中、等待确认、取消中或 `needs-reconciliation` 的 Session 不允许改变 active/归档。聊天、流式回调、后台 run、任务历史、事件历史和 prompt 连续上下文按启动时 `sessionId` 归属并过滤,`conversation.read` 和 self `agent.run_status` 通过 runId 使用同一 Session;恢复或处理待确认动作前校验 task、runtime state 和 pending action 的 Session 一致性。Runtime 的 OS 锁、FIFO 队列和恢复屏障仍属于 Agent,同一 Agent 不因多个 Session 获得并行执行能力。
- 2026-07-01 调整:AI 游戏创作 App 借鉴 Godcoder 的本地工程护栏,但只收敛到五项本地机制:`ArtifactWriter` 写入前 checkpoint、写入后 diff、用户确认 restore;进入 LLM 前过滤密钥和本机配置痕迹;`.agent/agent.db` 继续作为轻量 JSONL 项目索引,`/index` 额外刷新 `.agent/project.index.json`;同一项目写入通过 `.agent/project.lock` 串行化;`.agent/policy.json` 记录项目级命令拒绝 / 确认策略。v1 不引入通用 IDE 插件、云工作区、SQLite 或任意 shell 代理。
- 2026-07-03 调整:主窗口最近 checkpoint 列表必须直接展示 checkpoint id、文件数、大小和创建时间,并提供直接对比、填入 `/diff`、确认回滚和填入 `/restore` 的轻量操作;回滚仍走 `project.restore` 确认卡,不在列表按钮中直接写项目文件。
- 2026-06-24 调整:普通用户通过聊天输入 `/help` 发现可用内置命令;命令发现必须留在聊天消息里,不得因此暴露开发面板。
- 2026-06-24 调整:聊天区待确认命令的日志语义必须区分 `permission.pending`、`permission.confirm` 和 `permission.cancel`;待确认卡片必须展示本地写入目标路径,避免用户在不知道落盘位置时确认。
- 2026-06-24 调整:普通用户通过聊天输入 `/status` 读取 `.agent/manifest.json` 的项目状态摘要,只在聊天消息里展示项目目录、任务状态、资产数量、预览状态和最近命令;不得为了状态查看暴露任务、文件或日志面板。
- 2026-06-24 调整:普通用户通过聊天输入 `/files` 触发只读 `file.list`,只在聊天消息里展示本地项目文件摘要;不得把文件读写面板暴露到普通用户窗口。
- 2026-06-24 调整,2026-07-03 更新:普通用户通过聊天输入 `/assets` 触发只读 `asset.list`,只在聊天消息里展示本地项目资产路径、类型和来源;资产列表消息可以填入首个资产的 `/read` 草稿,方便从聊天继续查看资产文本元数据,但仍不直接读取文件或绕过聊天命令;不得把资产面板暴露到普通用户窗口。
- 2026-06-24 调整:普通用户通过聊天输入 `/read 本地相对路径` 触发只读 `file.read`,只在聊天消息里展示项目内文本文件并截断长文本;不得开放聊天里的文件写入或删除能力。
- 2026-06-24 调整:普通用户通过聊天输入 `/tasks` 触发只读 `task.list`,只在聊天消息里展示专业组、角色、任务状态和产物交接;不得把任务面板暴露到普通用户窗口。
- 2026-06-24 调整:普通用户只能通过聊天触发内置命令;当前 `/smoke` 映射到白名单 `command.run_limited game.static_smoke` 并走待确认卡片,不允许扩展成任意 shell 或自由命令解析。
- 2026-06-24 调整:普通用户通过聊天输入 `/project /绝对路径` 触发 `project.create` 待确认命令,用于授权并初始化本地项目目录;相对路径不会生成待确认命令;不要把开发窗口项目路径输入框暴露到正式用户界面。
- 2026-06-25 调整:普通用户侧所有会写入、运行、查看 / 打开预览或导入本地产物的命令必须先完成 `/project` 初始化,包括 `game.generate_draft`、`asset.upload`、`game.run_local`、`command.run_limited`、`preview.start`、`preview.status`、`preview.open`、`preview.stop`、`memory.write`、`memory.delete`、`canvas.project_sync`、`canvas.asset_import` 和 `canvas.export_import`;没有已授权本地项目时只提示设置项目,不得落到默认 `/tmp` 草稿目录。
- 2026-06-24 调整,2026-06-30 更新:终端测试入口使用同一个 Tauri Rust 二进制的 `--agent-run <本地项目绝对路径> <创作需求>`,只复用现有 `game.generate_draft`、`game.static_smoke` 和本地 HTTP 预览链路,不另建第二套 agent runtime;发布 App 的 LLM 配置从 Tauri 应用配置目录读取,不写入仓库默认配置或项目文件。需要自动验证时可追加 `--no-wait`,生成预览 trace 后立即停止本地预览,避免命令卡在回车等待。
- 2026-07-04 调整,2026-07-08 更新:`apps/ai-game-creator-shell/src-tauri/src/main.rs` 拆成薄入口,继续只保留共享类型 / 常量、模块声明、CLI preflight、`tauri::Builder`、运行时配置初始化和 `invoke_handler` 清单;CLI 参数解析与终端运行输出放入 `cli.rs`Tauri command 包装放入 `commands.rs`,运行时配置 / LLM 配置检查放入 `config.rs`Agent loop 与生成编排放入 `agent.rs`,上传 / 画板 / 平台美术生成接入放入 `assets.rs`,本地项目文件、记忆、对话、权限、checkpoint、manifest 和通用路径工具放入 `project.rs`,本地 HTTP 预览 server、preview registry 和 preview Tauri command 放入 `preview.rs`,旧窗口 URL 与兼容 command 放入 `windows.rs`Rust 单测放入 `tests.rs`。拆分不得改变 Tauri command 名、JSON 字段、`.agent/*` 路径、项目权限策略或错误语义。
- 2026-06-24 调整,2026-07-08 更新:AI 游戏创作 App 的 release 配置只登记一个普通用户窗口,登录后在同一 WebView 中进入首页、项目组和项目开发占位;开发专用单 Agent 对话、任务、文件、记忆、预览、日志和能力面板只能通过 Vite dev 的 `?dev/#dev` 分支或 debug 构建自动打开的 `developer` 开发窗口查看,不进入普通用户窗口。旧工作区窗口切换 command 只保留兼容,用户主流程不得调用它。
- 2026-06-24 调整,2026-07-18 更新:`check:native-shells` 必须静态守住 AI 游戏创作 App 的用户 / 开发边界:release 只保留一个普通用户窗口,用户侧预览只在项目运行工作台嵌入当前 `127.0.0.1` 游戏,且 Tauri 激活命令不得调用 opener;开发面板只能在 `devMode` 分支或 debug-only `developer` 窗口渲染,`developer` 窗口当前使用 `index.html?agent-chat` 并复用 `.agent/conversations/agents/<agentId>.jsonl` 持久化单 Agent 对话;发布入口和普通用户窗口不得暴露 `Agent 聊天` 导航,也不得调用旧工作区窗口切换 command。
- 2026-07-10 调整:AI 游戏创作 App 的 Runtime 实时状态依赖 Tauri event listen。`src-tauri/capabilities/events.json` 必须覆盖 `client`、`developer`、`main`、`launcher`,只授予 `core:event:allow-listen` 与 `core:event:allow-unlisten`,不得向前端授予 emit`check-config.mjs` 静态守住窗口和权限边界。Vite 开发服务器必须把仓库根目录加入 `server.fs.allow`,因为 App 直接加载 `packages/shared/src`;否则真实 WebView 会因共享源码 403 白屏,即使 TypeScript 检查仍通过。
- 2026-06-25 调整:`check:native-shells` 在 `ai-game-creator-shell:check` 之后必须追加 `ai-game-creator-shell:build -- --no-bundle`,让原生壳总门禁同时证明 AI 游戏创作独立 Tauri 壳能完成 release 编译,而不是只证明前端 / Rust 逻辑测试通过。
- 2026-06-24 调整,2026-07-18 更新:普通用户通过聊天输入 `/preview` 触发待确认 `preview.start`,完成 `/project` 初始化后可通过 `/open-preview` 触发待确认 `preview.open` 并只激活当前已授权项目对应的 `127.0.0.1` 客户端运行视图,通过 `/preview-status` 查询当前项目预览,通过 `/preview-stop` 停止当前项目预览;用户工作台仅嵌入当前项目的 loopback 游戏,开发预览状态面板仍只在开发窗口可见,不能把 `preview.open` 扩展成任意 URL 打开能力,也不能展示或停止其它本地项目遗留的全局预览。
- 2026-06-25 调整:`/preview-status` 虽然是只读命令,也必须写入 `preview.status` 命令日志并向聊天返回错误,不得因查询失败产生未捕获异常或无审计记录。
- 2026-06-24 调整:普通用户通过聊天输入 `/memory [short]` 读取长期或短期记忆,通过 `/remember 内容` 待确认追加长期记忆,通过 `/forget-memory [short]` 待确认删除记忆;不得为了记忆查看或编辑暴露独立用户面板。
- 2026-06-25 调整:`/remember` 支持可选 scope`/remember short 内容` 追加短期记忆,`/remember long 内容` 或未写 scope 时追加长期记忆;仍统一走待确认 `memory.write`,不暴露独立用户面板。
- 2026-06-24 调整:普通用户通过聊天输入 `/canvas 画板项目ID` 触发待确认 `canvas.project_open`,只打开本机 Genarrative 编辑器 `/editor/canvas?projectid=...`;不得把它扩展成远程站点或任意 URL 打开能力。
- 2026-06-24 调整:普通用户通过聊天输入 `/import-canvas-asset 本地路径 画板项目ID 资源ID|object:资产对象ID [kind] [mediaType]` 触发待确认 `canvas.asset_import`,只登记项目目录内已有文件为 `canvas` 来源资产;只有 `assetObjectId` 时使用 `object:` 前缀,不伪造 resourceId;画板导出包回流使用 `/import-canvas-export /绝对/画板素材.zip 画板项目ID`。
- 验证方式:`npm run ai-game-creator-shell:typecheck`、`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`、`npm run test -- packages/shared/src/contracts/gameCreationApp.test.ts`、`cargo test -p shared-contracts game_creation_app --manifest-path server-rs/Cargo.toml`、`cargo test -p platform-agent --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
## 2026-06-30 唯一码和私有码按用户限兑一次
- 背景:运营私有码按内部 user_id 指定用户后,指定用户仍可能无法兑换;排查 release `SEEDUSERLUO0630` 时确认兑换校验把私有码当成全局次数上限,而不是每个允许用户各自限兑一次。
- 决策:兑换码使用校验中,公共码继续按 `max_uses` 控制单用户可兑次数;唯一码和私有码改为同一 `code + user_id` 只能成功兑换一次。私有码仍先校验 `allowed_user_ids`,命中允许名单后再按该用户历史使用次数拒绝重复兑换;`global_used_count` 只保留为统计字段,不再作为唯一码 / 私有码的兑换阻断条件。
- 影响范围:`module-runtime::validate_runtime_profile_redeem_code_usage`、`profile_redeem_code_usage` 计次语义。
- 验证方式:`cargo test -p module-runtime --manifest-path server-rs/Cargo.toml runtime_profile_redeem_code_usage_validation_matches_modes`、`npm run check:encoding`、`git diff --check`。
## 2026-07-05 VectorEngine LLM 默认使用 `gpt-5.4-mini`
- 背景:VectorEngine Apifox `api-349239079` 暴露 OpenAI-compatible `POST /v1/chat/completions`;创意 Agent 和通用 LLM 代理需要统一到 VectorEngine 文本服务,并将默认文本模型切换为 `gpt-5.4-mini`。
- 决策:创意 Agent 的 `CREATIVE_AGENT_GPT5_MODEL` 固定为 `gpt-5.4-mini`,协议切到 Chat Completions,不再携带旧 APIMart `official_fallback` 字段;画布 Agent 侧边栏聊天规划请求也复用该模型和 Chat Completions 协议,不再显式使用 `gpt-4o` / Responses。通用 `/api/llm/chat/completions` 代理使用 `GENARRATIVE_LLM_PROVIDER=openai-compatible`、`GENARRATIVE_LLM_BASE_URL=https://api.vectorengine.cn/v1`、`GENARRATIVE_LLM_MODEL=gpt-5.4-mini`。未单独配置 `GENARRATIVE_LLM_API_KEY` 时,api-server 可复用 `VECTOR_ENGINE_API_KEY`;前端 LLM 客户端必须兼容 OpenAI `choices`、api-server raw `{content}` 和项目 envelope `{ok,data:{content}}` 三种非流式响应,以及 OpenAI SSE delta 和 api-server `event: delta` 两种流式响应。
- 决策补充:画布 Agent 侧边栏的“规范图 / 视觉规范图 / 风格规范图 / 素材规范展板”是 Agent 规划 prompt 和 function-calling 工具选择约束,不是侧边栏 UI 说明文案。此类请求默认走 `generate-image`prompt 必须要求规范展板包含统一视角、线条粗细、色卡、材质、阴影、圆角、状态层级、尺寸标注等视觉规范元素;角色规范图若是规范展板也走 `generate-image`,只有实际角色立绘才走 `generate-character`,多个图标素材 / 图集才走 `generate-icon-spritesheet`。
- 影响范围:`server-rs/crates/platform-agent`、`server-rs/crates/api-server/src/config.rs`、`src/services/llmClient.ts`、`.env.example`、`deploy/env/api-server.env.example`、`scripts/test-ve-llm.mjs`。
- 验证方式:`npm run test -- src/services/llmClient.test.ts`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml from_env_reads_non_public_models_and_urls app_state_builds_creative_agent_gpt5_client_from_vector_engine_settings llm_chat_completions editor_agent_llm_request_uses_vector_engine_chat_model`、`cargo test -p platform-agent --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`。
## 2026-07-07 功能灰度以后端事实源判定
- 背景:平台需要把新功能先开放给部分用户,首个接入点是创作入口;灰度规则不能泄露用户标签或完整受众配置给普通前端。
- 决策:新增 SpacetimeDB `feature_gate_config` 表作为通用功能灰度事实源,后台通过 `/admin/api/feature-gates` 配置 gate。创作入口使用 `creation-entry:<id>` gate key 约定;`api-server` 在 `/api/creation-entry/config` 和入口路由熔断中按可选登录用户、用户标签、用户 ID 黑白名单和稳定百分比做判定,只返回当前用户过滤后的入口状态。
- 后台:灰度页的 Gate Key 选择器按 `prefix:suffix` 两段式配置;后续新增固定功能灰度 key 时,必须同步维护后台下拉框的固定目标配置,避免运营手输 key。
- 语义:未配置 gate 或 `enabled=false` 不限制访问;启用后黑名单用户 ID 优先,其次用户 ID 白名单、用户标签白名单、稳定百分比。`enabled=true` 且 `rolloutPercent=0` 是有意的 kill switch;后台选择尚不存在的新 target 时必须重置启用状态、比例和黑白名单,不能隐式继承上一条 gate 的规则。前端只消费过滤后的 `visible/open`,不承接灰度规则真相。
- 性能:`api-server` 只在当前判定涉及的已启用 gate 配置了用户标签白名单时读取用户标签;不因无关 gate 或纯用户 ID / 百分比灰度触发额外标签读取。
- 影响范围:`feature_gate_config`、`spacetime-client` runtime facade、`api-server` 创作入口配置与路由熔断、`apps/admin-web` 灰度发布页。
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo test -p module-runtime --manifest-path server-rs/Cargo.toml feature_gate`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml creation_entry_feature_gate`、`npm run admin-web:typecheck`、后台灰度页 Vitest、`npm run check:encoding`、`git diff --check`。
## 2026-07-10 官网 SEO 与主站 SPA 404 边界
- 背景:主站 Nginx 和 Pingora 原先会把任意未知路径回退到 `index.html`,导致 soft 404;共享 `index.html` 也缺少首页 SEO headrobots 和 sitemap 请求会落入 SPA fallback。
- 决策:新增真实 `robots.txt` 和仅首页的 `sitemap.xml`,首页共享 head 提供基础 SEO/OG/JSON-LD 文本但不使用未确认的 image/logo URL;首页 DOM 只保留一个稳定产品定位 H1。Nginx 与 Pingora 只允许当前完整 SPA 路径回退 `index.html`,同前缀未知路径必须返回 404;`/admin` 继续走独立子应用。路由变化必须同步三套 Nginx、Pingora、route parity matrix 和自动门禁。
- 2026-07-13 补充:浏览器导航到 Web 未知路径时继续保持 HTTP `404`,但正文统一返回 `public/404.html` 品牌页面和 `public/branding/taonier-404-page.png`API、探针及非 HTML 请求仍返回原有 `404` 响应,不得把品牌页 HTML 混入接口响应。Nginx 最终 Web catch-all 与 Pingora `Accept: text/html` 分支保持一致。
- 影响范围:`index.html`、`public/robots.txt`、`public/sitemap.xml`、首页组件、三套 Nginx、Pingora 网关和路由 parity 门禁。
- 验证方式:前端定向测试与构建、`npm run check:nginx-spa-routes`、`npm run check:pingora-route-parity`、`npm run check:pingora-gateway-smoke`、`npm run check:encoding`、`git diff --check`,部署后同时抽查根级未知路径和 `/creation/not-exist` 等同前缀未知路径。
## 2026-07-10 AI 游戏创作 Agent Runtime 执行边界
- 决策:开发单 Agent 对话默认使用可执行 Runtime,输入区通过 `执行 / 聊天` 分段控件显式区分;`执行` 调用 `start_game_creator_agent_runtime_task` 并保留工具策略、确认、取消、排队和状态事件,`聊天` 才使用无工具流式回复,不再保留并列的“后台运行”按钮。消息区使用固定响应式网格行和内部滚动,并在 Runtime 非终态期间显示当前等待对象。Runtime 完成前必须先把 assistant 回复写入发起 Session,再写 completed 终态和广播;落盘失败只能进入 failed。前端收到匹配当前项目、Agent、Session 和 runId 的终态后自动重读对话,切换 Session 会清除当前等待投影,旧 run 事件不得覆盖新 Session。
- 2026-07-12 修正:Runtime 状态为空时也要保留其网格行位,消息区和输入区显式固定到第 5、6 行,禁止空 Runtime 容器通过 `display:none` 让长消息落入 `auto` 行并撑高页面;等待 LLM 期间消息区同步使用 `aria-busy` 暴露忙碌状态。消息区只在用户仍接近底部时自动跟随最新片段,用户向上查看历史后暂停跟随,切换会话、重新读取或主动发送时再恢复。
- 2026-07-12 修正:OpenAI-compatible 流式响应中 `choices` 为空数组或 `null` 的 usage / metadata 包不得再报缺少 `choices[0]`,必须跳过元数据并继续等待正文。首个 delta 前只有 `StreamUnavailable / EmptyResponse / Deserialize` 协议兼容错误允许由 Rust 单 Agent 流式入口回退一次非流式请求;上游状态、鉴权、额度、超时、连接和请求错误直接保留原错误,前端不得再次发起普通 LLM 请求。已收到正文和完成原因后继续保留完整流式回复,不能被尾部坏包覆盖。
- 2026-07-12 修正:Tauri 聊天事件监听被拒绝后,前端选择的普通回复入口必须固定调用 `client.run`,即使 Agent 路由保留 `stream=true` 也不得再内部发 SSE。pending action 的 project revision 快照改为绑定 planning 请求发出前的版本;`file.delete` 取得项目写锁后必须再次校验 revision / verification gate,公共 pending 摘要必须与私有 ledger 完整相等,confirm / reject 只在迁移状态可靠落盘后启动 continuation。manifest 和 pending ledger 禁止 truncate/remove 旧文件后再替换,统一使用同目录临时文件的原子替换及可恢复 backup。
- 2026-07-12 修正:pending ledger 的 `inputSummary` 不是可独立修改的显示文本,每次读写都必须从完整 tool action 重新计算并全等校验。`file.delete` 已删除文件但 Agent DB 审计失败时,observation 必须标记 `needs-reconciliation`Runtime state / event 先落为 `failed / needs-reconciliation` 再尝试追加核对审计,禁止当作普通删除失败继续规划或重放。
- 决策:后台 Agent 首轮不得预加载任何需要工具权限控制的项目内容。planning 与 final reply 只拿身份、session/run 元数据、任务、工具策略和已获准 observation;记忆、黑板、对话、资产和文件内容必须通过对应工具进入。最新黑板、记忆和对话采用尾部保留截断。
- 决策:同一 Agent 的前台聊天与后台队列共享 per-Agent OS 执行锁,前台 LLM 等待期间不持有项目写锁;同 Agent 后台投递保持 pending,前台结束后把当前锁直接移交给 drain,drain 异常不得反写已经完成的聊天结果,不同 Agent 继续并行。
- 决策:重启恢复继续遵守 `agent.resume` 默认确认策略。自动 command 只允许 auto;默认 confirm 由主工作区或独立开发 Agent 聊天窗口的 UI 明确确认后调用独立 command,确认绑定发起项目,切换项目取消旧确认且旧项目异步结果不得污染新项目状态;独立 command 只忽略 confirm、不允许绕过 deny,临时失败必须允许重试。
- 决策:后台 Agent loop 只有空 actions 且不存在验证 blocker 才算收束。项目级 revision 的唯一事实源固定为 `.agent/runtime/project-revision.json`,每个 run 的验证门禁固定为 `.agent/runtime/verification/<agentId>/<runId>.json`。Runtime 在项目写锁内、执行 `file.write / file.patch / file.delete / project.restore` 之前先保守推进 revision,并把当前 run 的 `requiresVerification` 单向置为 `true`;即使修改随后失败或进程中断也不得回退 revision 或门禁,只允许因此多做一次验证,不能留下漏验证窗口。`requiresVerification` 一旦为 `true`,在该 run 生命周期内永久保留;成功验证只记录其绑定的 revision,不把门禁改回 `false`。`project.verify` 或 `command.run_limited / game.static_smoke` 只有成功且绑定当前 revision 才能作为完成凭证,后续任一修改会让旧凭证失效;未修改项目的只读 run 可保持 `requiresVerification=false`。observation、压缩上下文和 UI 摘要只用于规划与展示,不再作为 revision 或验证门禁的权威真相。
- 决策:per-run context bundle 的 schema 固定为 `game-creator-runtime-context-bundle.v2`durable pending action 的 schema 升级为 `game-creator-pending-action.v3`,除 per-run verification gate 外还绑定动作创建时的全局 project revision。旧版 pending action 和 v1 context bundle 恢复必须失败关闭,不得把缺失字段解释为可执行,不得自动重放动作或写 completed。批准或自动执行 pending action 前,当前全局 revision 与 gate 必须同时等于创建快照,任一漂移都进入 `needs-reconciliation`。准备写最终 assistant 回复或 completed 终态时,Runtime 必须先取得项目写锁,再重读 `.agent/runtime/project-revision.json` 与当前 run 的 verification gate;只有 `requiresVerification=false`,或成功验证绑定的 revision 与锁内重读到的当前 revision 完全一致,才允许在同一把锁内依次写入发起 Session 的 assistant 消息和 completed 终态。缺失、损坏、版本不支持、revision 漂移或验证未通过一律失败关闭,并追加 `runtime.verification` blocker 后继续同一 run 或进入明确失败,不能用锁外旧快照收束。
- 2026-07-12 修正:后台 finalization 的锁内复核结果区分 `Completed`、可恢复 `Stale` 和真正错误。每次可形成最终回复的 planning 或 final reply LLM 请求开始前都记录项目 `responseRevision`;锁内当前 revision 与它不一致即为 `Stale`,包括 `requiresVerification=false` 的只读 run。`Stale` 必须丢弃旧回复、把 response plan step 恢复为 pending、注入完整 `runtime.verification` blocker,并保持原 Agent、Task、Session、Run、loop 计数和 per-Agent 锁继续 planning;不得创建 retry run,不得写 assistant、completed 或 `background_task.failed`。revision / gate 无法读取或 stale continuation 无法持久化时才进入 failed;一旦 finalization journal 已进入 `prepared`,后续对话或终态落盘失败必须保持可恢复 `finalizing`,不得把当前 run 误记为 failed。
- 2026-07-12 修正:完成预检、finalization、恢复和取消收束遇到同项目另一个 Agent 的短暂项目写锁时,最多等待约 1 秒并重试;锁持续占用才返回 blocker。毫秒级并发收束不能被误判为验证缺失、不能因此回到 planning 或重新请求 LLM。
- 决策:最终回复固定使用 `.agent/runtime/finalizations/<agentId>/<runId>.json` 的 `game-creator-runtime-finalization.v1` journal 跨越多文件落盘,状态严格按 `prepared -> assistant-persisted -> runtime-completed` 推进。journal 单独使用 512 KiB 上限,必须容纳 32,000 字符的最大合法回复及元数据;跨平台替换在不能原子覆盖旧文件时,先把旧 journal 原子移动为同目录 `.previous` 恢复副本,再安装新文件,主文件缺失时读取恢复副本,成功推进或终态清理时同时删除副本,不得先删除唯一旧 journal。`resume` 必须在 pending action、普通 running/pending task 和 delegate receipt 修复之前优先恢复 finalization,只按 journal 补齐 assistant 与终态,不重新请求 LLM、不重放工具或 receipt 任务。Runtime state 只是可重建投影;状态文件缺失或损坏但 task ledger 仍能唯一定位主 journal 或恢复副本时,从 task ledger 重建同一 run 后继续恢复。journal 处于 `prepared` 且 assistant 尚未落盘时,若任务已取消,或 revision / verification gate 漂移已使回复过期,必须丢弃 journal 并分别保持取消终态或回到同 run planning。assistant JSONL 是用户可见提交点;取消 command 必须在 per-Agent 锁内检查 journalassistant 尚未存在时立即删除 prepared journal 再取消,assistant 已存在时完成原 finalization 并忽略迟到取消,不能留下孤儿 journal 或把可见回复改判为 cancelled。journal 损坏、版本不支持、身份/回复指纹/幂等 ID 冲突一律失败关闭,并将同一 live run 投影为 `status=running / phase=finalizing` 供 UI 明确显示,不得降级走普通任务恢复。
- 决策:conversation JSONL 的 `messageId` 为可选向后兼容字段,旧记录无需迁移。finalization 使用稳定 `messageId` 幂等追加 assistant;同一 Agent / Session 下已存在 role/content 一致的同 ID 消息时不重复写 JSONL,但 `.agent/agent.db` 缺少对应 `conversation.message` audit 时必须在 audit 追加锁内补写一次,已有 audit 不重复;同一 Agent / Session 作用域下相同 ID 对应不同 role 或 content 时按冲突失败关闭。completed task JSONL、Runtime state、`turn.completed / response` 事件、`agent.runtime.completed / background_task.completed` audit、pending/confirmation 清理和 delegate result 发布均必须按既有身份幂等补齐,重启不得制造第二份终态投影。
- 决策:每 6 轮只形成上下文压缩窗口,不是整个 run 的固定预算。窗口产生新的独立 observation 时压缩上下文并继续同一 run;最近 6 轮没有新进展或相邻窗口重复时写 `failed / budget-exhausted` 和 `loop-budget-exhausted`,不再生成总结后记成 completed。解析阶段保留 action 总数,超过单轮预算时写 `runtime.tool_budget` 并只执行前三个;Runtime 默认工具列表必须直接从可执行白名单派生。
- 2026-07-12 修正:`contextStalled` 是跨 same-run replan 和进程重启持久化的锁存状态,只能出现在非零上下文窗口边界,一旦成立不得在恢复时清除。`runtime.verification` observation 的窗口指纹忽略 `currentRevision / mutationRevision / verifiedRevision` 动态数值前缀,成功 `project.verify / game.static_smoke` 也不把动态命令输出计为新指纹;revision 数字和时间戳变化本身不构成独立进展,不能借此绕过停滞预算。
- 决策:`agent.delegate` 子任务必须 durable 保存 `parentAgentId / parentRunId / delegationId`,其中 `delegationId` 从已持久化工具动作的 `actionId` 派生,不能使用执行时随机值;终态任务记录必须保存经过统一凭据清洗和安全截断的 `terminalDetail`,不能依赖可能被后续 run 覆盖的 Agent 全局 state。子任务进入 `completed / failed / cancelled / budget-exhausted` 任一终态后,Runtime 必须在 delegation 级 OS 文件锁内按固定 receipt runId 幂等生成且至多生成一次 `agent.delegate.result` 回执;不同委派并发写同一目标 Agent 时,runId 分配与 pending 追加还必须在目标 Agent 任务账本 OS 锁内原子完成。失败、排队或活跃取消、预算耗尽与成功同等需要回执,`needs-reconciliation` 只有最终取消后才回执。父 Agent 通过既有队列接收 `source=agent-delegate-receipt` 的续跑任务,回执 prompt 禁止重复同一委派,并携带完整的已清洗 `terminalDetail`,不能只保留 UI 摘要;排队期间不提前写入父会话,真正执行时才幂等落盘,用户消息或回执消息落盘失败时不得进入 LLM。回执任务必须保留父 run 关联,真正开始或恢复前再次核验父 run,关联缺失或父 run 不存在时失败关闭;父 run 已取消或普通失败时只保留 suppressed receipt 审计,不自动复活。父 Session 存在未结束委派时禁止切换或归档,极端竞态下回执回落到父 Agent 当前可写 Session。续跑继续遵守同 Agent FIFO、per-Agent OS 锁、权限确认、取消、恢复和 `needs-reconciliation` 屏障,不允许直接重入、插队或重复投递;恢复必须先恢复 pending action / reconciliation 屏障,再补齐“子终态已落盘、回执未入队”的崩溃窗口。
- 决策:Agent loop 的语义事件类型固定为 `thinking_summary / plan / action / observation / response / error`。普通失败和预算耗尽必须先追加统一 `error` 事件,同时保留 `turn.failed / turn.budget_exhausted` 生命周期事件供旧读取方兼容;状态、phase 和清洗后的错误详情必须在两类事件中一致。Runtime 状态面板默认展示最新 4 条事件,但在当前后端最近事件窗口大于 4 条时必须允许展开全部返回记录,不能让 `plan`、早期 observation 或 thinking summary 永久不可见。
- 验证:Rust 覆盖首轮上下文不泄露、工具后 observation 可见、六类语义事件、确认前后内容边界、前后台同 Agent 串行、前台结束后队列 drain、恢复确认 gate、预算耗尽失败、默认工具白名单一致性,以及 delegate 成功 / 失败 / 取消 / 预算耗尽终态回执、`delegationId` 幂等去重和父 Agent receipt 续跑仍受 FIFO / 锁 / 确认 / 恢复门禁;revision / verification gate 还要覆盖修改前推进、失败不回退、`requiresVerification` 单向持久化、验证只绑定当前 revision、v1 context bundle / pending action 恢复失败关闭、修改 run 与只读 run 的 stale 回复不落盘、跨 Agent 漂移在同 run 自愈、stale context 重启恢复、动态验证输出不能绕过 stall、stall 跨 revision / restart 保持,以及最终 assistant / completed 在项目写锁内复核后才落盘;finalization 还要覆盖三个阶段边界的崩溃窗口恢复、`prepared` 后取消或 revision / gate 漂移丢弃、journal 损坏或身份冲突阻断并显示 `finalizing`、conversation `messageId` / audit 自愈与并发不重复,以及终态投影 / receipt 不重放;前端覆盖统一事件展示、主工作区和独立开发 Agent 聊天窗口的默认恢复确认条与显式恢复 command。
## 2026-07-11 Jenkins Secret File 默认值与 dev 定时发布
- 背景:Stdb Build / Publish / Full Job 改用 Secret File 后,live Job UI 默认值为空且会被 SCM Jenkinsfile 覆盖;Full Job 的 04:00 timer 又与默认人工 rollout gate 冲突。dev 服务器不对外,允许定时完整发布。
- 决策:三个 Jenkinsfile 将 `MIGRATION_BOOTSTRAP_SECRET_CREDENTIAL_ID` 默认固定为 `genarrative-spacetime-bootstrap-secret-dev-file`。Full Job 保留 04:00 timer,默认 `DEPLOY_TARGET=development`、`STDB_API_ROLLOUT_MODE=normal`,按 Stdb → API → Web 完整发布 dev;三个下游 Build 都显式传 `PUBLISH_AFTER_BUILD=false`,防止提前发布和顺序漂移。人工维护窗口才选择 `pause-after-stdb` 并强制校验 approvers。
- 凭据边界:Secret 原文以 Jenkins Secret File 为事实源;credential ID 与参数行为以仓库 Jenkinsfile 为事实源。旧 Secret Text 继续服务 Database Import / Export,不原地改类型或删除。
- 影响范围:`jenkins/Jenkinsfile.production-full-build-and-deploy`、`jenkins/Jenkinsfile.production-stdb-module-build`、`jenkins/Jenkinsfile.production-stdb-module-publish`、生产运维门禁与 live Job 参数 schema。
- 验证方式:`node --check scripts/check-production-ops-guardrails.mjs`、`npm run check:production-ops`、`npm run check:encoding`、`git diff --check`;推送后用首阶段 fail-closed 运行刷新三个 live Job 参数,再只读核对 credential 默认值、`normal` 默认值与 timer。
## 2026-07-12 维护模式只拦截公网流量
- 背景:此前维护 marker 只对内网放行后台,内网排障或人工维护仍无法访问主站、普通 API 和 SpacetimeDB 路由;当前维护目标是隔离公网访问,不应阻断可信内网流量。
- 决策:Nginx 与 Pingora 允许 IPv4 loopback / RFC1918 / link-local 和 IPv6 loopback / ULA / link-local 来源在维护期间访问整站,包括主站页面与静态资源、普通 API、后台页面与 `/admin/api/**`、SpacetimeDB 路由。公网应用主站、普通 API、后台和 SpacetimeDB 路由继续维护响应,应用层鉴权不变。
- 信任边界:Nginx 使用 TCP `$remote_addr`Pingora 使用 TCP peer,只有同机 loopback Nginx 才可通过其强制覆盖的 `X-Real-IP` 传递原始地址,禁止使用 `X-Forwarded-For` 做维护放行判断。
- 限制:网关放行不等于后端存活;`pause-after-stdb` 停止 api-server 时,内网普通 API 和后台 API 仍不可用。
- 影响范围:生产 / dev Nginx 模板、维护 snippet、Pingora maintenance gate、Nginx 静态门禁与 Pingora smoke。
- 验证方式:`npm run check:nginx-spa-routes`、`npm run check:pingora-route-parity`、`cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml`、`npm run check:pingora-gateway-smoke`、`npm run check:production-ops`、`npm run check:encoding`、`git diff --check`。
## 2026-07-12 Full 发布显式控制成功后维护状态
- 背景:Full Job 只能用 `STDB_API_ROLLOUT_MODE` 控制 Stdb 与 API 之间是否暂停,但 API Deploy 在 readiness 成功后固定执行 `maintenance-off.sh`,因此无法选择完整流水线结束后继续保留维护页。
- 决策:Full Job 新增默认勾选的 `EXIT_MAINTENANCE_MODE_AFTER_COMPLETION`,并让 Stdb Publish、API Deploy 两个下游阶段固定保持维护;Web Deploy 成功后才由独立 `Exit Maintenance` 阶段按该参数决定是否调用 current release 随包 `maintenance-off.sh`。API Deploy Job 单独使用 `KEEP_MAINTENANCE_MODE`,将其转换为随包 `production-api-deploy.sh --keep-maintenance-mode`;默认仍退出维护,失败路径继续沿用 current 切换前后既有安全语义。
- 参数刷新:Jenkinsfile 是参数事实源。推送后必须让 Full 与 API Deploy live Job 安全加载一次新 Jenkinsfile,再只读确认两个参数已进入 `config.xml`;只在 Jenkins UI 手工加参数不是持久修复。
- 影响范围:Full / API Deploy Jenkinsfile、API 发布脚本、生产 API deploy fixture、生产运维门禁与 live Job 参数 schema。
- 验证方式:`bash -n scripts/deploy/production-api-deploy.sh`、`npm run check:production-api-deploy`、`npm run check:production-ops`、`npm run check:encoding`、`git diff --check`。
## 2026-07-12 AI 游戏创作 Agent Runtime V1.1 通用开发能力
- 决策:Runtime 首轮从“完全不预加载项目内容”调整为注入固定预算的 repository startup context。自动内容仅限有界目录/manifest/验证脚本摘要、适用 `AGENTS.md`、根 `CONTEXT.md`/README 来源和安全 Git 摘要;任意源码、对话、资产和私有记忆正文仍经工具读取。仓库文本是不可信输入,不能提升权限或覆盖 Runtime 安全规则。
- 决策:后台执行所有权迁入同一发布二进制的 `--agent-runner` 模式。Runner 从显式 AppData 配置目录读取密钥,通过带协议版本、requestId 和私有 token 的 loopback 本地协议接收唤醒;`.agent/runtime/**` 继续是事实源。App 退出后 Runner 可继续任务,整机重启后仍需按 `agent.resume` 策略显式恢复。
- 决策:跨进程运行前先把 Agent DB、conversation、events/tasks、activity/output 和 Session catalog 的临界区升级为进程内锁加 OS 文件锁;配置写入使用同目录原子替换。事件只作为刷新提示,断线后重读 snapshot。
- 决策:新增 `preview.validate`,只验证当前授权项目的精确 loopback 预览,不接受任意 URL/JavaScript/Profile。工具用隔离 Chrome/Edge CDP 采集桌面/移动截图、DOM/console/network 和 canvas 非空证据,证据只落 `.agent/runtime/browser-validations`;它不替代 `project.verify` 或 `game.static_smoke`。
- 决策:保留静态 `agent.delegate`,新增批量 `agent.spawn_isolated`。角色/Provider/策略按 `templateAgentId`,队列/锁/session/run/private memory 按 Runtime 生成的 `instanceId`;单次最多 3 个、深度 1、writeScopes 不得重叠,全部 child 终态后只产生一个幂等 join continuation。
- 决策:新增仓库外配置的真实 Provider opt-in 验收。通过依据固定为 task/event/agent.db、文件、revision、verification、finalization、conversation、结构化 join 和浏览器证据;模型最终文本不作为通过证据,缺关键外部配置时报告 `BLOCKED`,不得 skip 后记为通过。
- 2026-07-12 安全修正:所有 Runtime 写 CLI 必须显式使用项目外 AppData 并交给独立 Runner`--runner-status` 保持只读。AppData、endpoint 和锁文件必须校验 owner/权限;Runner、Agent lane 和项目 execution-owner 安全打开时拒绝符号链接、硬链接和 Windows reparse point。同一项目的 execution-owner 跨 AppData 唯一并绑定 `bootId / protocolVersion`,不同 Runner 不能同时接管同一项目。
- 2026-07-12 安全修正:项目索引和 checkpoint 统一排除敏感配置、`.agent`、VCS、依赖及构建目录;checkpoint manifest 路径必须是唯一规范相对路径,create/diff/restore 均复用项目安全路径解析。通用文件工具不得访问 `.agent/checkpoints/**`restore 不得覆盖或删除本地敏感配置。
- 2026-07-12 修正:动态隔离 all-join 使用独立持久交付记录。固定 `joinRunId` 至多入队一次;原父 run 通过 `agent.run_status` 认领 ready join 时,必须持久标记并取消未执行 continuation;只有父 run 未认领时才在 lane 释放后执行唯一 continuation,恢复不得生成 `-dup-*` join run 或重复调用父 LLM。
- 2026-07-12 安全修正:Windows AppData、Runner endpoint、AppData lock 和 execution-owner 诊断文件使用禁止继承且只允许当前用户 SID 的 protected DACL;只读状态入口只校验,不创建目录、不收紧权限。项目 execution-owner 的 OS 锁文件与 JSON 诊断投影分离,Runtime 通过稳定目录句柄先取得系统锁,再原子修复缺失、截断或损坏的诊断,诊断内容不得作为接管依据。
- 2026-07-12 安全修正:通用文件、项目索引和 checkpoint 使用同一可移植相对路径语法,拒绝 Windows 盘符 / UNC / ADS、尾随点或空格、保留设备名和大小写碰撞;快照额外排除私钥、数据库和 dump。进入 prompt 的绝对路径脱敏同时覆盖 Unix、Windows 盘符和 UNC 路径。
- 2026-07-12 安全修正:`preview.validate` 只使用系统固定安装位置的 Chrome / Chromium / Edge,并在导航前通过 CDP Fetch request-stage 拦截覆盖全部资源。除精确预览 HTTP origin 及同 host / port WebSocket 外,跨 origin HTTP、redirect target、WebSocket 和其他端口必须在连接前阻断。
- 2026-07-12 修正:父 run 认领 ready all-join 必须绑定当前持久化 `agent.run_status` 的 `actionId`;同一 action 重试幂等返回,其他 action 不得重复消费,已开始的 continuation 不得被迟到认领覆盖。
- 2026-07-12 修正:所有 durable 工具动作执行前复核 repository fingerprint;规范漂移会作废旧动作并回到同 run planning,整个 `.agent` 控制面不参与 fingerprint,避免 checkpoint、日志和 Runtime 状态制造伪漂移。
- 2026-07-12 修正:动态隔离 instance 使用自己的 Runtime 私有临时 memory lane,只允许 `scope=agent` 读写本人,拒绝 sibling 和项目 / Session / 黑板共享写入;父任务进入 failed、budget-exhausted 或 cancelled 时必须幂等取消全部非终态 children。
- 2026-07-12 修正:`preview.validate` 必须固定同时生成 desktop / mobile 证据,每个视口都要有可见且至少两种 RGBA 状态的 canvas;单视口、无 canvas、透明或均匀纯色画布不能通过。
- 验证:真实 `gpt-5.5` 的 `llm-runtime` 套件已通过 Runner 强杀恢复、11 条合法工具协议、4 套完整确认生命周期、5 个零重放副作用动作、9 次结构化成功工具执行、checkpoint/修改、项目验证、Chrome 桌面与移动取证、3 个隔离实例和唯一 join;终态投影与 assistant audit 唯一,action/message/receipt 重复为 0,密钥与诱饵泄露为 0。未配置 External Editor API 时 `full` 套件按契约返回 `BLOCKED(editorApi)`。
- 详细契约与验收矩阵见 `docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`。
## 2026-07-12 AI 游戏创作 Agent Runtime V1.2 受控命令与推理档位
- 决策:新增 `command.exec` 补齐“复现 -> 读取真实输出 -> 修改 -> 再验证”闭环。输入固定为 `program / args / cwd / timeoutSeconds``program` 只能来自 Runtime 内置白名单,`args` 必须是逐项 argv,禁止 shell 字符串、管道、重定向、命令替换、环境变量注入、PTY、后台服务和用户指定可执行路径。
- 决策:`command.exec` 权限默认为 `confirm`,精确确认继续绑定 actionId、动作指纹、repository fingerprint、project revision 和 execution owner,并在取得项目写锁后重读策略及复核 pending action 身份;策略改 deny、actionId / 指纹或 revision 漂移都必须在启动前失败关闭。命令请求进入 durable action;每次真正启动前保守推进一次 revision,清洗后的 stdout / stderr、退出码、超时与源码指纹进入 observation。只有 `cargo check/test/clippy/fmt/build`、npm 测试或规范命名的验证脚本、精确 `node --test` 具备验证资格;Git、rg、cargo metadata 和普通 npm run 只作为诊断。验证型命令还必须退出码为 0、未超时、无源码漂移且命令日志、manifest、Agent DB 审计全部成功才能绑定当前 revision;Agent DB 审计失败先保持 failed gate 再进入 `needs-reconciliation``executing` 阶段中断不自动重放。
- 决策:`command.exec` 可执行文件必须解析为项目外绝对路径,子进程只使用安全绝对 PATH;npm 转发参数拒绝 shell 元字符,Node、Git、rg 分别拒绝可加载外部文件、pager / pathspec / object-path、follow / hidden / preprocessor / 类型覆盖等间接读取或执行能力,敏感搜索排除必须在用户选项之后注入。首版超时处理只请求终止受控进程组并检查调用结果,安全等级仍与 `project.verify` 相同,即“固定程序 + 参数级策略 + 用户确认”;当前不宣称具备 Codex CLI 级 OS sandbox 或完整 detached-process 隔离,在平台级沙箱落地前不得把默认权限改为 `auto`。
- 决策:AppData LLM 配置使用全局 `llm.reasoningEffort` 和可选 `agentLlm.<agentId>.reasoningEffort`per-Agent 配置有值时覆盖全局、缺省时继承全局。值只允许 `default / low / medium / high``default` 不向 Provider 发送推理档位;发布默认固定为 `high`planning、普通单 Agent 聊天和最终回复共用同一解析结果,不再硬编码 `low`。
- 验证:真实 `gpt-5.5` 的最终安全收紧版 `llm-runtime` 套件已完成失败 `command.exec` -> 精确修复 -> 不同 argv 复验通过,并覆盖 Runner 强杀恢复且 run/session 身份稳定、95 条 task、161 条 event、137 条 Agent DB、13 条合法工具协议、6 套确认生命周期、3 个隔离实例和唯一 join;两次命令只审计 args 数量与 SHA-256,副作用重放、重复 action / message / receipt 和密钥 / 诱饵泄露均为 0,临时项目按 sentinel 自动清理。
- 详细白名单、参数拒绝规则与验收口径见 `docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` 的“V1.2 对标 Codex CLI 增量”。
## 2026-07-12 AI 游戏创作 Agent Runtime V1.3 多文件变更集
- 决策:新增默认 `confirm` 的 `project.patchset`。一次 action 最多预检 12 个 create / update / deleteupdate / delete 必须绑定 `file.read` 返回的完整 SHA-256create 必须绑定“目标不存在”。所有路径、大小、匹配数、NFKC + Unicode 小写后的可移植碰撞、符号 / 硬链接、Agent 控制面和敏感路径在源码写入前失败关闭。
- 决策:patchset 预检通过后在同一项目写锁内自动 checkpoint,prepared 审计成功后才推进一次 revision 并应用全部变更。锁内再次校验 policy、pending 身份、revision / verification gate 和 repository fingerprint;应用中途失败必须回滚。revision / gate、回滚或完成审计不完整进入 `needs-reconciliation``executing` 恢复不得自动重放。成功审计只保存路径、operation、前后摘要与字节数,不保存源码正文。
- 决策:`project.diff` 增加可选的有界内容 hunks;使用成熟文本 diff 库在项目锁内比较 checkpoint 与当前项目,生成后重算路径差异,不一致时拒绝混合快照;二进制、非 UTF-8、文件 / 总预算截断必须显式标记。最新内容 diff 在 128 KiB context bundle 中作为压缩保护项最多保留 24,256 字符,避免被普通 observation 的 1,600 字符上限截断。Agent 完成 patchset 后先用返回的 checkpointId 审查内容 diff,再执行可验证命令。
- 验证:本地 441 项 Tauri 测试已覆盖多文件成功、预检全失败、大小写重复、敏感 / 链接路径、SHA 漂移、prepared / completed 审计失败、应用中断回滚和一次 revision。真实 `gpt-5.5` 的 `llm-runtime` 套件已形成唯一 patchset,同时更新 / 创建文件并读取绑定 checkpointId 的 2 项未截断内容 diff,通过最终命令、项目验证和桌面 / 移动浏览器验证;Runner 强杀恢复后副作用重放、重复 action / message / receipt、半完成文件和密钥 / 诱饵泄露均为 0。
## 2026-07-13 数据库冷备使用 OSS Multipart 上传并在清理前验真
- 背景:SpacetimeDB 冷备归档已经超过 OSS 单次 PutObject 的 5 GiB 上限,单请求上传会稳定失败并让本地归档持续积压;网络中断还可能让 CompleteMultipartUpload 的客户端结果不确定。
- 决策:`scripts/database-backup-to-oss.mjs` 对备份归档统一使用 OSS Multipart Upload,默认按 128 MiB 顺序分片;每个分片请求重新创建文件流、时间和 V4 签名,仅对网络错误、HTTP 408 / 429 / 5xx 做有限重试。V4 canonical query 必须同时支持无等号的 `uploads` 子资源和带值的 `partNumber` / `uploadId` 参数。
- 验真与清理边界:Complete 后必须发送签名 HEAD,并严格核对 OSS `Content-Length` 与本地归档大小;Complete 响应不确定时也先用 HEAD 判定对象是否已经完整落盘。只有验真成功后才能把 manifest 标记为 `uploaded`,并按 `keepLocal` 决定是否删除本地归档;失败时 best-effort AbortMultipartUpload,不得提前更新 manifest 或清理本地文件。
- 影响范围:数据库备份 OSS 上传实现、备份回归门禁、release 本地归档保留与 timer 恢复流程。
- 验证方式:`npm run check:database-backup`、`npm run check:production-ops`、`npm run check:encoding`、`git diff --check`;线上先对既有归档使用 `--upload-archive ... --keep-local`,确认 OSS 对象长度和可恢复性后再清理积压并恢复 timer。
## 2026-07-13 微信虚拟支付使用官方查单补偿
- 背景:`wechat_mp_virtual` 原先被误认为没有服务端查单能力,导致消息推送遗漏后订单只能停在 pending / expired,历史订单也无法按微信真实状态核对。
- 决策:`platform-wechat` 按官方协议调用 `POST /xpay/query_order`,使用小程序 `access_token` 与 `HMAC-SHA256(appKey, "/xpay/query_order&" + 实际 JSON body)` 支付签名。用户确认和订单到期补偿都可查虚拟支付订单,但只在单号、支付类型 `order_type=0/7`、金额、合法 `paid_time` 一致且微信状态为 `2/3/4` 时补入账;退款类型 `1/8` 不发放权益。`short_series_goods` 的 `status=2` 在本地入账后必须补调 `/xpay/notify_provide_goods`,失败可从本地 `paid` 状态只重试发货;token 明确失效时强制刷新并最多重放一次。
- 边界:查单使用订单所属用户的小程序 `openid`,不把 AppKey、AppSecret、access token 或 `openid` 下发前端;虚拟支付不得误用微信支付 V3 查单。
- 历史单:升级前遗留的 pending 订单不会被 expiration catch-up 覆盖,使用 `spacetime:wechat-virtual-payment:reconcile` 逐单 dry-run,再使用当次 `applyFingerprint` 明确 `--apply`。脚本每次重读本地订单与微信查单结果,指纹漂移、非 `2/3/4`、单号/金额/支付类型 `order_type=0/7` 不一致或非官方 endpoint 时默认拒绝入账。
- 验证方式:`cargo test -p platform-wechat --manifest-path server-rs/Cargo.toml`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml virtual_payment_query`、`npm run check:wechat-virtual-payment-reconcile`、`npm run check:encoding`、`git diff --check`。
## 2026-07-13 临时维护公告改为 release 外运行态覆盖
- 背景:一次性停服公告曾直接提交到 `public/maintenance.html`,后续 Web Build 将它持续打入 `web.tar.gz`,每次 Web Deploy 或再次进入维护都会重新显示已经过期的公告。
- 决策:`public/maintenance.html` 永久作为无日期、无具体时段的默认维护页,并使用 `public/branding/taonier-maintenance-page.png` 作为品牌视觉;生产 Web 打包必须对最终 `web/maintenance.html` 执行临时文案门禁。临时公告通过 `maintenance-on.sh --page-file <公告HTML>` 原子安装到 `/var/lib/genarrative/maintenance/page.html`Nginx 与 Pingora 优先读取该运行态文件,缺失时回退 Web 制品默认页。维护期间只精确放行 `/branding/taonier-maintenance-page.png` 与 `/branding/taonier-product-ip.png`,不得扩大到整个品牌或静态资源目录;网关 smoke 必须验证这两个路径仍返回 PNG,同时其它公网页面、API 与后台静态资源继续命中维护门禁。
- 生命周期:新维护窗口未提供 `--page-file` 时清理 marker 外残留公告;同一窗口内 Stdb / API 发布重复调用 `maintenance-on.sh` 时保留已安装公告;`maintenance-off.sh` 同时清理 marker 和公告页。Web Deploy 不再拥有临时公告事实源。
- 影响范围:默认维护页、维护开关脚本、Nginx snippet、Pingora 配置与 smoke、生产 Web 发布包门禁和生产运维文档。
- 验证方式:`npm run check:maintenance-page`、`npm run check:nginx-spa-routes`、`cargo test -p pingora-gateway --manifest-path server-rs/Cargo.toml`、`npm run check:pingora-gateway-smoke`、`npm run check:production-ops`、`npm run check:encoding`、`git diff --check`。
## 2026-07-13 兑换码生效日期范围对齐邀请码
- 背景:后台邀请码已支持可选开始时间和截止时间,但兑换码只有启用状态,运营无法预设活动时间窗口,用户兑换也没有后端时间边界校验。
- 决策:`profile_redeem_code` 在已有字段末尾追加可空 `starts_at` / `expires_at`,默认均为空;后台请求和响应使用可空 `startsAt` / `expiresAt`。时间窗口与邀请码一致:空边界合法,双边界必须严格满足开始早于截止,有效区间为 `[starts_at, expires_at)`。
- 兑换边界:时间是后端事实,`redeem_profile_reward_code` 必须用经后端构造的 `redeemed_at_micros` 拒绝未生效或已过期的兑换码;前端的状态标签只用于运营展示,不代替后端校验。
- 影响范围:`module-runtime` 兑换码命令与校验、`spacetime-module` schema / migration / procedure、生成 bindings、`spacetime-client`、`shared-contracts` / `packages/shared`、`api-server` 和 `apps/admin-web` 兑换码页。
## 2026-07-13 后台侧边栏与主内容独立滚动
- 背景:后台壳层只给 `.admin-shell` 设置 `min-height: 100dvh`,长页面会撑高 document;滚动时侧边栏与主内容一起移出视口,`.admin-content` 上的 `overflow: auto` 没有成为真正滚动容器。
- 决策:后台壳层固定为 `height: 100dvh` 并隐藏壳层 overflow;桌面侧边栏使用独立 `overflow-y: auto``.admin-main` 通过 `min-height: 0` 和 `overflow: hidden` 约束网格,`.admin-content` 使用 `min-height: 0; overflow: auto` 独立滚动。
- 响应式边界:小于等于 `980px` 时仍隐藏桌面侧边栏,主内容在视口高度内滚动,底部导航继续固定。
- 验证方式:`apps/admin-web/src/styles/admin.test.ts` 锁定壳层滚动契约;桌面浏览器滚动后应保持 `window.scrollY = 0`、侧边栏 `top = 0`,只改变 `.admin-content.scrollTop`;移动视口继续由 `.admin-content` 滚动。
## 2026-07-13 公开作品资产使用派生精确读授权
- 背景:资产 ACL 严格执行后,已登记为 `private` 的作品封面和正式资产不能再依赖 generated 前缀匿名读取;但公开作品仍需要允许访客读取它实际展示和运行的资产。
- 决策:已登记 `asset_object` 继续保持 `private`,新增匿名派生 view `public_work_asset_read_grant`。view 只从 `Published + visible` 作品(`custom-world` 另要求未删除)正式发布快照中收集实际使用的资产,历史作品随 view 计算自动补齐;资产读取 procedure 在同一事务快照内先取资产 owner,再使用各玩法 owner 索引定向计算该作者的 grant,不为每张图片执行全站 view,也不从连接级长期订阅 cache 判断 ACL。
- 授权边界:grant 携带作品 ownerAPI 只有在它与 `asset_object.owner_user_id` 一致,且 `asset_object_id` 或精确 `object_key` 命中时才允许匿名读取。隐藏、删除或取消发布会使 grant 自动消失;参考图、未选中候选图和 `generationInputs` 明确排除。Custom World 只遍历角色、地标、营地、章节和 opening CG 等已知正式根,不能递归 legacy payload 的未知预览 / 编辑字段。
- 禁止项:不得通过放开 `generated-*` 前缀或批量把历史对象改为 `PublicRead` 修复公开作品,两种方式都会让作品可见性生命周期与资产授权脱节,并重新引入跨账号读取。
- 影响范围:`module-assets` 公开资产授权判定、`spacetime-module` 跨玩法公开资产 view 与权威读取 procedure、`spacetime-client` facade 和 `api-server` 资产读取 ACL。
- 权威查询:`asset_object` 不进入 client 长期订阅。API 通过仅 runtime service identity 可调用的 procedure,按主键或 `(bucket, object_key)` 服务端索引读取事务内 metadata;只有位置查询明确返回不存在时才允许进入 legacy curated 前缀兼容,procedure 失败、超时或重复位置一律失败关闭。
- 一致性:隐藏、删除或取消发布提交后,后续读取 procedure 的事务快照立即按新状态判断,不等待任意池连接追上订阅水位。公开派生授权、`PublicRead` 和 legacy 兼容读取签名 URL 的有效期最多 600 秒,因此该能力仍不是对既有签名的瞬时吊销机制;owner / admin 读取保持原有效期口径。
- 验证方式:公开可见作品的正式资产可匿名读取;未选候选图、参考图、跨 owner 伪造 key 仍返回不存在;隐藏、删除或取消发布后新的读取请求立即拒绝,再恢复公开可见时新的读取请求立即恢复;超长公开 `expireSeconds` 被截断为 600 秒。
## 2026-07-13 AI 游戏创作 Agent Runtime V1.4 Git 工作树审阅
- 决策:新增一等只读 `git.inspect`,共享 command id 为 `project.git_inspect`且默认 `auto`。工具只接受 `includeDiff / maxFiles / maxChars`,返回精确 Git top-level 的 HEAD / branch、staged / unstaged / untracked 安全路径和有界 staged / unstaged unified diff;不改项目 revision 或 verification gate。
- 决策:Git 读取必须隔离 system/global config、hooks、fsmonitor、pager、external diff、textconv、optional locks、prompt 和网络;项目根必须就是 Git top-level。路径经可移植路径、项目边界、普通文件、硬 / 符号链接和敏感路径过滤;untracked 只列名不读正文,前后快照漂移时整次失败。
- 决策:本轮明确不开放 Git 写操作。`add / commit / push / pull / fetch`、分支切换、merge / rebase / reset / stash / clean、tag、submodule 和 worktree 继续禁止;后续本地 commit 必须单独设计 HEAD / index / 文件快照、精确确认与 Runner 崩溃不重放,不复用通用 `command.exec`。
## 2026-07-13 AI 游戏创作 Agent Runtime V1.5 长任务关键动作账本
- 决策:context window 压缩新增 `runtime.milestones` 安全摘要,跨窗口保留已成功完成的 `agent.spawn_isolated / agent.delegate / canvas.asset_generate / preview.validate / project.patchset / project.restore / task.create`,并明确禁止无任务依据的重复高成本或副作用动作。账本只用于规划连续性,不替代 pending-action、task/event、Agent DB、revision、verification 或 finalization 事实源。
- 决策:旧 `runtime.context / runtime.milestones` 不再占普通最近 observation 槽;账本在再次压缩时合并旧摘要与新里程碑,所有 detail 先做路径、凭据和长度清洗。`project.diff` checkpoint 内容 hunk 与 `git.inspect` 工作树 hunk 分别保留最新一项,不能互相顶掉;总 bundle 仍不得超过 128 KiB。
- 验证:新增连续窗口单测证明 spawn、patchset 和 checkpointId 经两次压缩仍存在,两类大 diff 同时保留。真实 `gpt-5.5` Git E2E 曾准确捕获一次上下文遗忘导致的重复 spawn;修复后 94 条 task、156 条 event、140 条 Agent DB、12 次成功工具执行中 `git.inspect=2 / patchset=1 / spawn=1 / join=1`revision=3Runner 强杀恢复身份稳定,验证与桌面/移动浏览器证据通过,副作用重放、重复 action/message/receipt、半完成文件、密钥和诱饵泄露均为 0。
## 2026-07-13 AI 游戏创作 Agent Runtime V1.6 持久动作回执与模型回查
- 决策:继续复用 `.agent/agent.db` 作为唯一长期审计源,不新增数据库或平行事实源。每个带 `actionId` 的已落盘终态 observation 必须追加或补齐 terminal receipt,身份固定包含 `agentId / taskId / sessionId / runId / actionId / actionFingerprint / tool / executionMode / status / inputSummary / summary / safeDetail / updatedAt`。
- 决策:`safeDetail` 只允许按工具类型和字段名双重白名单抽取,禁止整段复制工具 detail,禁止保存 `file.read` 源码、命令完整输出、diff 正文、消息 / 记忆 / 委派正文、密钥和绝对路径;首版只保留 `project.patchset` 的 checkpoint / revision / count 等结构化字段,无法安全还原 detail 的历史旧记录允许显式标记 `detailUnavailable`。
- 决策:动作历史读取必须把 receipt 当持久输入而不是可信展示 DTO,重新验证终态 status、actionId、fingerprint、executionMode、tool 和 task / session ledger 绑定,并按当前工具白名单重新解析 `safeDetail`;无法通过二次校验的 detail 只能标记 `detailUnavailable`。
- 决策:新增只读模型工具 `agent.action_history`,只能查询当前 Agent;输入支持 `runId / actionId / tool / status / limit``limit` 默认 5、上限 10,省略 `runId` 时只查当前 run。结果优先使用终态 receipt,并兼容折叠历史 terminal observation;旧记录无法还原安全 detail 时标记 `detailUnavailable`。未指定 `tool` 时默认排除 `agent.action_history` 自身,只有显式 `tool=agent.action_history` 才允许回查它,避免递归污染。
- 决策:`agent.action_history` 复用 `agent.audit` 权限,默认 `auto`,项目或 per-Agent policy 可改为 `confirm / deny`;查询不推进 revision、不改变 verification gate、不认领 join。receipt 写入失败时 durable action 必须进入 `needs-reconciliation`,恢复只按原 `actionId` 补齐 receipt,不得重放动作。
- 决策:receipt 判重命中后还必须全等复核 Session、fingerprint、tool、executionMode、status 和安全结果字段,冲突失败关闭。普通 append 禁止写 `agent.runtime.action_receipt`,幂等动作入口只接受字段完整的终态 receipt。Agent DB 只自动修复强杀造成的最后一条不完整 JSONL,中间损坏不跳过;单条记录上限 1 MiB,receipt 幂等全量扫描在文件超过 256 MiB 或记录超过 100 万条时失败关闭,禁止复用锁外快照。普通审计约在 192 MiB 或 999,936 条停止,并给字节 / 记录门槛预留 64 条最大 1 MiB terminal 记录;仅 terminal receipt、带 actionId 的终态 observation / observed 和 reconciliation 可用预留区,`command-failed / verification-failed` 也是终态。普通和终态追加都在同一 DB 句柄锁内真实计数,容量判断和判重失败关闭,不做轮转。普通读取使用最近 32 MiB 有界尾窗、最多保留 16,384 个完整 JSON object,并显式标记 `truncated`。
- 决策:Agent DB 不再依赖普通路径锁文件保障安全。Unix 必须从可信项目目录句柄使用 `openat + O_NOFOLLOW` 打开,校验普通文件与 `nlink=1` 后直接对 DB 文件句柄 `flock`Windows 必须用相对 `NtCreateFile` 打开并拒绝 reparse point / hardlink,同时以独占 share 持有句柄。每次 append、尾部补换行或截断修复执行 `flush + sync_data`Unix 新建 `.agent` 和 `agent.db` 后分别同步项目根目录与 `.agent` 目录,写入前后复核身份。32 MiB 尾窗恰好落在记录边界、UTF-8 半字符或精确 1 MiB 尾记录时不得丢弃合法记录;同 UID 恶意进程的 rename / hardlink ABA 不承诺绝对隔离。
- 2026-07-13 真实验收修正:pending action 的精确 project revision / verification gate 不再拦截纯读取工具;不同 Agent 并行推进 revision 后,`file.read / project.search / git.inspect / agent.action_history` 等读取动作必须读取最新事实并返回 observation。写入、命令、验证、预览证据和 `agent.run_status` join 认领仍复核原 revision / gaterepository fingerprint gate 也继续独立生效。该修正来自真实 Provider 首轮中隔离子 Agent 因父 Agent revision 推进而把 `file.read` 误判为 `needs-reconciliation`、导致 all-join 无法形成的失败证据。
- 决策:共享项目事实读取使用项目一致性锁;等待锁后必须重读 durable pending sidecar,并与调用方完整 pending 对象逐字段一致,再核对 policy 和 repository fingerprint。`agent.run_status` 的 all-join claim 必须在同一项目锁内重验 revision / gate 并完成认领,关闭检查与认领之间的 TOCTOU。policy denied 和未知工具也先形成 durable observed pending,再写 terminal receipt;一旦 terminal observation 已持久化,必须先成功落 receipt 才响应取消,receipt 失败统一保留 `needs-reconciliation` 补写入口且不得重放工具。
- 决策:父 run 存在 `joinMode=all` 隔离组时,所有子结果终态且 ready join 被当前父 run 的 `agent.run_status` action 认领前,最终回复和 `agent.action_history` 都必须失败关闭并要求继续查询状态;只有持久 join 认领完成后才解除门禁。
- 决策:最终回复在项目锁内创建 finalization journal 前必须再次复核 all-join 已由当前父 run 持久认领;未认领按 `Stale` 回到同 run planning,不写 journal、assistant 或 completed,不能只依赖 planning 阶段旧快照。
- 决策:父 run 在 `waitingGroups > 0` 时必须持久进入 `waiting-for-isolated-join`、保存原 context cursor 并释放 Agent lane;重复 resume 只返回等待状态,不请求 LLM、不推进 loop,也不取消仍在工作的 child。最后一个 child 就绪后写 `deliveryTarget=parent-wake` 并唤醒同一 parent run / session,由模型通过持久 `agent.run_status` actionId 认领;不得创建 join continuation。活跃 planning / running 父 run 直接保留 ready join 等待认领,重复 dispatch 不创建任务。旧 delivery 缺少 target 时按 continuation 单向兼容。
- 决策:action task / event 投影的幂等阶段键为 `runId + actionId + phase`,允许同一 action 从 waiting-for-confirmation 合法推进到终态 observation,同阶段冲突仍失败关闭。Agent DB 终态 observation 扫描跳过同 action 的非终态前置记录,只对既有终态做全字段一致性检查;`recentToolCalls` 按 actionId 原位更新,避免 waiting 投影遮住最终结果。
- 决策:动作历史结构化 detail 上限 7,200 字符;超预算时只能先删除可选字段,再按最旧优先删除完整记录,不得字符截断 JSON,也不得清空 `runId / actionFingerprint` 破坏身份。运行时文本清洗必须覆盖 Unix、Windows 盘符、正反斜杠 UNC、`file:/...`、`file:///...` 及百分号编码绝对路径,统一替换为 `<absolute-path>`,并保留普通相对文本与 HTTP(S) URL。
- 2026-07-15 修正:撤销后台 planning 和最终回复“瞬时错误额外自动重试”的旧契约。真实长 planning 的 TLS 失败证明“尚未形成 plan/action”不能证明 Provider 未接收或未计费;`Timeout / Connectivity / Transport / EmptyResponse / 408 / 429 / 5xx` 均不得在同一 lifecycle 内原样重放,底层 `LlmClient` 强制 `max_retries=0`。错误只保存 kind、SHA-256、字符数和脱敏摘要;是否再次请求必须经过显式恢复并使用新的 request slot/lifecycle。按错误指纹切换 TLS 栈、HTTP 版本或协议版本的实验没有真实收益且扩大共享依赖,继续不作为全局传输分支。
- 决策:本轮只交付模型工具,不新增前端动作历史弹窗;UI 继续显示最近动作投影,后续历史查看必须使用独立弹窗。
- 验证:Rust 全量 507 项中 504 通过、3 项真实浏览器 opt-in 用例按设计忽略;覆盖 receipt 折叠、组合过滤、默认值与上限、敏感清洗、旧记录、尾部修复、中间损坏失败关闭、身份冲突、句柄安全、目录同步、确认阶段到终态投影、`parent-wake` 和恢复补齐。最终真实 `gpt-5.5` V1.6 `llm-runtime` 套件中,模型实际调用 1 次 `agent.action_history` 并返回 1 条与 `.agent/agent.db` 全身份对齐的当前 run 记录;94 条 task、158 条 event、164 条 Agent DB、11 条合法工具协议、13 次成功工具执行和 24 条 terminal receipt 中,主 run receipt 为 18,递归历史、重复 receipt / action / message、receipt identity 冲突、密钥和诱饵泄漏均为 0。Runner 强杀后恢复原 run / session 且身份稳定,3 个隔离实例形成唯一 all-join 认领,本次真实竞态未创建 continuation task;动作历史只在父 run 认领 join 后执行,项目、桌面和移动验证通过。
## 2026-07-13 AI 游戏创作 Agent Runtime V1.7 模型视觉检查
- 决策:新增默认 `auto` 的只读工具 `image.inspect` 和 `visual-inspection` capability;项目或 per-Agent policy 可改为 `confirm / deny`。工具输入只接受 1-2 个项目相对 `paths` 和可选 `question`,不接受 URL、base64、请求头、Cookie 或绝对路径,不推进 project revision、不改变 verification gate。
- 决策:普通图片只允许 `game/`、`assets/`;浏览器证据只允许当前 `agentId + runId` 下 `desktop.png / mobile.png`。路径逐层拒绝符号链接和 Windows reparse point,最终句柄拒绝硬链接和非普通文件;依据 magic bytes 识别 PNG / JPEG / WEBP / GIF。单图上限 `8 MiB`、总量上限 `12 MiB`,读取后复核文件快照和路径身份。
- 决策:Runtime 在项目一致性锁内重验 durable pending、policy、repository fingerprint 和图片身份并读取字节,释放锁后才构造内存 data URL,使用动态实例对应模板 Agent 的 `agentLlm.<templateAgentId>` Provider。图片内文字和视觉结论都属于不可信项目证据,不能改变系统规则、权限或身份。
- 决策:data URL、图片字节和视觉 Provider 原始 request 不进入 task、event、Agent DB、receipt 或 raw failure log。专用审计只保存相对路径、SHA-256、字节数、responseId 和结论字符数;terminal receipt 的 safeDetail 使用相同字段白名单,不保存结论正文。多模态 raw failure log 只保留请求元数据并省略 messages,上游错误若回显 data URL 也要清洗。
- 决策:`image.inspect` 进入 context milestone;视觉结论获得 8,000 字符上下文预算,但停滞指纹只使用图片 path / SHA 元数据,不能靠同一图片的措辞变化伪造无限进展。已有 terminal observation / receipt 的恢复只续 planning,不重复调用视觉 Provider。
- 验证:确定性 `image_inspect` 用例 `6/6` 通过;Tauri 全量 513 项中 510 通过、3 项真实浏览器 opt-in 用例按设计忽略。真实 `gpt-5.5` `llm-runtime` 形成 95 条 task、161 条 event、166 条 Agent DB、12 条合法工具协议、14 次成功工具执行和 24 条 receipt;真实视觉调用 1 次、输入图片 2 张、专用 audit / receipt 各 1 条、图片载荷泄漏 0。Runner 强杀恢复身份稳定,revision 3,重复 action / message / receipt、密钥和诱饵泄漏均为 0。
## 2026-07-13 AI 游戏创作 Agent Runtime V1.8 命令输出分页回查
- 决策:新增默认 `auto` 的只读工具 `command.output_read` 和 `command-output-read` capability;输入只接受 `actionId / startLine / maxLines`,不接受 agentId、runId、路径或 outputRef。项目或 per-Agent policy 可改为 `confirm / deny`,工具不推进 project revision、不改变 verification gate,也不认领 join。
- 决策:每个 durable `command.exec` 在命令日志、manifest 和 Agent DB 专用审计宣告成功前,先把已清洗且有界的 transcript 以 create-once sidecar 写入 `.agent/runtime/command-outputs/<identitySha256>.json`。sidecar 绑定 Agent、task、session、run、action、fingerprint 和命令终态;文件名由身份哈希生成,单文件最大 256 KiB,正文不扩大现有 stdout / stderr 捕获上限。
- 决策:读取时先按当前精确 Agent 和源 actionId 在 terminal receipt 中定位唯一源 run,再交叉复核 task ledger、`agent.runtime.command.exec` 审计、outputRef、SHA-256、行数、截断、退出码、超时、源码漂移与 sidecar 身份;同一 Agent 的历史 run 可读,跨 Agent、旧版无 sidecar、重复冲突、损坏、超限或链接文件全部失败关闭。
- 决策:transcript 正文只进入当前模型 observation 和受限 context bundletask/event、Agent DB 专用审计、terminal receipt、`agent.action_history` 与验收报告只保存结构化元数据。`command.exec` 和 `command.output_read` 的 event detail 均省略;context fingerprint 只使用源 action identity、输出 SHA 和页范围,使同页重复不伪造进展、不同页仍可继续。
- 决策:命令已启动后 sidecar、日志、manifest、Agent DB、verification gate 或 receipt 任一步失败,都保持 failed gate 并进入 `needs-reconciliation`;恢复不得重跑命令。`command.output_read` 自身沿用 durable pending,已有 terminal observation 时只续 planning并补齐 receipt,不生成第二份读取动作。
- 修正:隔离子 Agent 的有效策略和锁内 enforcement 统一以模板 Agent 作为 per-Agent policy subject;动态 `child-*` 实例不再出现策略快照显示 deny、真实执行却按实例 ID 放行的偏差。
- 修正:Runtime prompt 显式列出合法静态模板 taskId,并冻结 `expectedArtifacts` 为完成时必须存在的项目内相对文件/glob、只读任务填写现有被检查文件、`writeScopes` 使用互斥非私有目录 glob。相同父 Agent/run 下相同 spawn request 的新 actionId 在创建实例前拒绝,避免长等待或上下文压缩后重复启动整组 reviewer。
- 修正:completed child 若因 artifact/evidence 结果契约无法构造 completed result,降级落盘为结构化 failed child result 并继续推进 all-join,不能只记 `result_failed` 后永久悬挂父 run。真实 E2E 的副作用判重只统计实际发生的动作;失败与修复后使用相同 argv 的 `command.exec` 由一失败一成功专门契约验收,预检失败不算副作用。可重复只读动作不限定总次数,省略默认参数和显式默认值等价,无依赖的视觉与动作历史只要求都早于最终回复。
- 验证:Tauri 全量 523 项中 520 通过、3 项真实浏览器 opt-in 用例按设计忽略;共享 TS 与 Rust 契约各 7 项、shell typecheck 和 Windows GNU `cargo check` 通过。无固定配方的真实 `gpt-5.5` `llm-runtime` PASS122 条 task、210 条 event、213 条 Agent DB、13 条工具协议、15 次代表性成功工具执行、6 套确认、8 个实际副作用 action 和 32 条 receipt;两次 `command.output_read` 覆盖 248 行并命中短 observation 之外的根错误,唯一 patchset、Runner 强杀恢复、revision 3、3 个隔离实例 / 2 个模板、唯一 continuation delivery、项目验证和双视口视觉检查通过。副作用重放、重复 action/message/receipt、命令正文边界泄漏、图片载荷、密钥和诱饵泄漏均为 0。
## 2026-07-14 AI 游戏创作 Agent Runtime V1.9 命令观察直接引用
- 决策:每个 durable `command.exec` terminal observation 在短 detail 前部直接返回 `sourceActionId=<当前 actionId>`,覆盖成功、非零退出、超时和 `observed-approved` 恢复;身份只能来自已校验的 pending action,不能由模型提供或从 outputRef 猜测。
- 决策:Runtime prompt 要求模型直接把该 ID 传给 `command.output_read`,不得为了读取刚完成命令先调用 `agent.action_history`。动作历史继续负责跨窗口和历史 run 的独立回查。
- 决策:不新增 observation 字段,不升级 pending/context schema。`sourceActionId` 只随既有私有 observation detail 持久化;command event 继续省略 detailAgent DB observation 和 receipt 继续使用顶层 actionId,命令正文隔离边界不变。
- 验收门禁:确定性测试必须同时检查下一轮 prompt 和 context bundle 的精确 ID;真实 Provider 第一次成功 `command.output_read` 必须早于唯一动作历史查询,并继续证明正文零泄漏、动作零重放和恢复身份稳定。
- 修正:真实 Provider 可在最后一次源码修改后按任意顺序完成项目验证和浏览器验证;验收器只要求 preview 位于 patchset 之后、视觉检查位于 preview 之后,不再把无依赖的“先预览、后 project.verify”误判为失败。
- 修正:`agent.run_status` 的 ready all-join 结果作为安全 milestone 跨窗口保留;认领后的后续状态查询显式返回 `claimedIsolatedJoins` 和“不要为同一组重复查询”。这避免 `scope=all` 的 900 字符静态 Agent 状态截断、上下文压缩后丢失 reviewer 结果并持续轮询。
- 修正:context compaction 把最新成功 `agent.action_history` 作为受保护观察保留,避免后续只读噪声把唯一动作回查证据挤出最终 bundleterminal receipt 仍是长期事实源。
- 验证:Tauri 定向用例覆盖精确 `sourceActionId`、ready/claimed all-join、跨两窗口 milestone 和动作历史保护。真实 `gpt-5.5` `llm-runtime` 最终 PASS146 条 task、248 条 event、255 条 Agent DB、16 条工具协议、15 次代表性成功工具执行、7 套确认、8 个实际副作用 action、43 条 receipt2 次 `command.output_read` 均早于唯一动作历史查询,Runner 强杀恢复身份稳定,唯一 patchset、失败/成功命令、项目/浏览器/视觉验证和 3 个隔离 reviewer 均完成。副作用重放、重复 action/message/receipt、正文/图片/密钥/诱饵泄漏均为 0。
## 2026-07-14 AI 游戏创作 Agent Runtime V1.10 Runner-owned 持久进程会话
- 决策:新增 `command.start / command.poll / command.stdin / command.terminate` 四个模型工具,专门承载受控前台持久进程。start / stdin / terminate 默认 `confirm`poll 默认 `auto`。`command.start` 复用 `command.exec` 的固定 program、逐项 argv、项目 cwd、白名单解析、安全 PATH、隔离环境和参数拒绝,不接受 shell、环境注入、用户 executable、管道、重定向或 daemonize / detachRunner 直接持有固定 `120x30` PTY、child handle、stdin writer 和输出泵,同项目最多 4 个、同 Agent instance 最多 2 个 running session。V1.2 对 PTY / 后台进程的排除只适用于一次性 `command.exec`。
- 决策:`processId` 是 start action create-once 的 opaque Runtime 身份,完整绑定 project、Agent instance、task、session、run、start action、action / command fingerprint 和 Runner boot;它不是 OS PID。poll / stdin / terminate 每次都从活 registry 和 durable record 交叉复核 owning 身份,跨 Agent、动态 sibling、run 或项目一律失败关闭,不能把知道 ID 当成授权。
- 决策:2026-07-27 起,独立 Runner 归 Tauri GUI 生命周期所有,同一 AppData 通过 OS GUI owner 锁只允许一个前端进程持有 Runner。GUI 启动子进程显式携带 `--gui-owner-required`Runner 若在启动检查前发现 owner 已释放就直接失败,不能退化成 CLI-owned Runner;就绪后仍必须调用 `runner.attach_gui_owner`。2026-08-05 补充:GUI 客户端把完整 attach 参数按规范化 AppData 保存为进程内登记,并在 `ensure_external_agent_runner` 复用或新启 endpoint 的成功出口按 `bootId` 重放;同一登记 generation 在同一 boot 上只发送一次,新 boot 必须在后续 Runtime 写请求取得 endpoint 前完成登记。只有 Runner 明确确认 attached 后才能记录成功 boot,失败时本次 ensure 失败且后续同 boot 继续重试;登记 mutex 只做快照和成功提交,网络请求期间不持有,锁序固定为 configure lock 后 registration mutex。普通 CLI 没有 GUI 登记,不得因启动、写入或只读 status 产生 attach 副作用。OS owner/watchdog 已建立不代表事件 sink 等进程内附加能力已恢复。Runner 由独立 watchdog 线程每 100ms 探测 owner 锁,不依赖服务端主循环;owner 丢失后先标记 draining / forced shutdown 并让服务端在 1.5 秒共享 deadline 内中断 Provider、回收 process session,若主循环或排空链路卡死则 watchdog 在 1.75 秒后复核 bootId、清理 endpoint 并由 Runner 自身进程硬退出。正常最终 `RunEvent::Exit` 仍同步请求专用 `runner.shutdown`GUI panic、SIGKILL 或构建中途失败不再只依赖退出回调。`runner.shutdown` 不得复用版本切换用的 `runner.shutdown_if_idle`,也不得以 busy 为由继续留在后台。GUI 侧使用专用短连接 / I/O 超时;endpoint 缺失或读取失败不能单独证明 Runner 已退出,必须结合实例锁释放,失败日志只输出脱敏阶段分类。GUI 客户端兜底在 Linux 通过同一 pidfd 校验 / 发信号,Windows 绑定同一进程 handle;macOS 没有等价稳定句柄,客户端不得按裸 PID 强杀,由跨平台 Runner 自身 watchdog 承担主循环卡死的最终兜底。旧 endpoint 缺 start identity 时,只有 GUI owner 路径且认证 ping 同时精确匹配 PID 和 bootId,才允许一次性迁移 busy 旧 Runner;普通 CLI 仍必须被 busy 阻断,不能按相同二进制猜测强杀。客户端强制终止后必须先取得同一 Runner 实例锁,再在锁内复核 bootId 并清理 endpointUnix endpoint 必须是当前用户持有的 0600 单硬链接普通文件。退出不得把任务伪造为 completed、不得重放工具副作用;未完成 run 保留既有 durable 状态,下一次启动按 reconciliation / recovery 合同处理。单个 WebView/子窗口关闭不触发 Runner shutdown,普通 CLI 退出也保持原行为,显式 `--runner-shutdown-if-idle` 仍只用于安全关闭空闲 Runner。Runner 重启只做 reconciliation:旧 boot 已进入 prepared / launching / running / terminating 且没有可信 terminal record 的会话进入 `needs-reconciliation`,不得重放 start 或 stdin,不得重发 terminate,也不得按持久化 PID 重连或接管 PTY;可信终态只补 observation / audit / receipt。首版连旧 boot 的 prepared 也保守核对,不自动推断为安全重试。
- GUI owner attachment 的完整成功条件固定为 `attached=true` 且 `eventSinkAttached=true`,登记保存并逐 boot 重放真实 sink port/token;任一确认缺失或失败时不得写入 `attached_boot_id`。sink token 不得进入日志、错误信息或公共状态。
- 决策:`command.poll` 使用绑定 processId 的 opaque cursor,并以 `maxChars / waitMs` 分页读取保留逻辑行边界的清洗后私有 PTY transcript;默认 / 最大返回 8,000 / 16,000 字符,最长等待 30 秒,同一 action/cursor 恢复必须稳定。后台输出泵独立等待 child 并排空尾部,单会话清洗后输出上限为 256 KiB,超限终止并落 `output-limit-exceeded`。输出正文只进入 owning Agent 的私有 transcript、observation 和 context bundletask/event/Agent DB/receipt/action history/activity/output/UI snapshot/report 只保存 cursor、字节数、SHA-256、截断和退出元数据。`command.stdin` 单次最终 UTF-8 bytes 上限 8 KiB,支持 `appendNewline / eof`,是不可重放副作用;公共确认与审计只留 `processId / bytesWritten / contentSha256 / stdinOpen / eof`,不得保存 data、摘要、前后缀或可逆编码。
- 决策:owning run 存在 launching / running / terminating 或未解决 reconciliation 会话时,final reply、finalization journal 和 completed 投影全部阻断。`runner.shutdown_if_idle` 同时检查活 registry、输出泵、终止任务和 durable unresolved record;取消 run 也必须先完成进程收束,不能留下会话后把 Runner 判 idle。
- 决策:terminate 必须携带最后一次 poll cursor,并返回同一 cursor 的零消费状态元数据;后续 poll 不得从 0 重读或跳过尾部。Unix 固定为 graceful request + 完整固定宽限等待、随后只 force kill 同组残留、再 wait / reap / drain PTYWindows 首版使用 Job force terminate + wait / reap,不宣称已有等价 graceful console event。只发送信号不算完成;signal / Job / wait / reap 或终态审计无法确认都进入 reconciliation。重复 terminate 只幂等返回已知终态,不能按 PID 再杀一次。
- 决策:容量预检同时扫描 registry 与 durable active / reconciliation record;未解决旧 boot 会话禁止新 start,同项目 4 / 同 Agent 2 的拒绝发生在 revision 推进和 OS spawn 前。终态 record 的 `needsReconciliation=true` 即使 status 为 failed / terminated 也继续阻止 final 和 idle,可信终态落盘后从 registry 清理。
- 安全边界:Linux child wrapper 监测 owning Runner parent PIDRunner 强杀后 fail-closed 杀死同一前台进程组;Windows 使用 kill-on-close Job Object。它们只提供默认同组 / 同 Job 生命周期,不是 OS sandbox,也不能阻止主动 `setsid`、外部 service、读取当前用户可读宿主文件或绕过代理联网。当前仍没有容器、namespace、seccomp、macOS sandbox profile 或 Windows restricted token / AppContainer;实现、UI 和报告不得宣称达到 Codex CLI 级沙箱或主动逃逸下的完整进程树隔离。
- 验收:真实 `gpt-5.5` `process-session` 在无工具配方任务中完成 1 次 start、3 次连续 cursor poll、1 次 stdin 和 1 次 terminate41 条 task、75 条 event、63 条 Agent DB、8 条 receipt、4 套确认生命周期、唯一 completed / assistantfixture launch=1,终态 PID / 端口、重放、重复、公共正文 / 密钥 / 诱饵泄漏均为 0。独立 `process-session-runner-kill` 在 readiness 后强杀 owning Runner21 条 task、34 条 event、36 条 Agent DB,新 boot 保持原 run / session,只产生 1 条 reconciliationlaunch=1、PID reconnect / completed / assistant / 重放 / 泄漏均为 0。两个 disposable 项目均按 sentinel 清理。
## 2026-07-14 AI 游戏创作 Agent Runtime V1.11 OS 强制工作区沙箱
- 决策:Linux `command.exec / command.start / project.verify` 的安全事实源从固定 program / argv 白名单或平行 npm spawn 升级为同一个 bubblewrap OS sandbox launcher。approval policy 继续决定是否确认,sandbox 独立限制文件系统和网络;普通 confirm 永远不能扩大 sandbox。
- 决策:Linux 只允许受信任系统 bubblewrap,缺失、权限异常或 namespace setup 失败必须在项目命令执行前失败关闭,不用裸 userns、代理变量或宿主全权限回退。当前机器 bubblewrap 0.11.1 已通过真实 namespace smoke,裸 userns 因 AppArmor uid_map 限制不可作为可靠 fallback。
- 决策:项目根可写,`.git / .agents / .codex` 只读,`.agent` 隐藏且不可写,项目外普通用户文件不挂载,network namespace 默认隔离;HOME / TMP / cache 使用 sandbox 私有目录,所有 shell、PTY 和后代继承同一边界。
- 决策:Linux sandbox 生效后,program 扩展为受信任 PATH 中的裸可执行名,argv 仅保留结构长度与控制字符门禁,允许 shell 管道和项目脚本;Windows 在等价原生 sandbox 落地前继续使用 V1.10 固定白名单与 Job Object,不能宣称通用命令或 Codex CLI 级隔离。
- 验收门禁:项目内构建 / 测试 / Git 读取成功;项目外读写、控制目录写入和网络访问失败;子进程与 PTY 会话继承相同边界;bubblewrap 不可用时零项目命令执行。真实 Provider 还需在无固定命令配方下自行发现并运行项目命令。
- 审计与发布:process record v2 保存 launch 当时的 backend / mode / network / profile,后续 process 工具从 durable/live 身份读取,preflight 失败使用 unavailable / not-established,不能按平台静态宣称已建立。共享 `os-workspace-sandbox` capability 只标记 Linuxdeb / rpm 声明 bubblewrap 依赖,AppImage 依赖宿主预装并保持 fail-closed。
- 长进程策略:`command.start` 只用于仓库清单确认的持续交互服务,短命令、探测、构建和测试走 `command.exec`;同一服务成功启动后只沿原 processId 操作。真实验收出现第二条 process record 时立即失败,防止模型主动重复 start 被误判成 Runtime 重放或一直等待总超时。
- 已知残余:项目 mount preflight 与真实 bwrap launch 是两次独立进程启动。第二次 setup 失败不会让目标程序脱离沙箱执行,但当前缺少 exec-ready 握手,revision 可能已推进且审计无法证明目标是否进入 exec;后续必须在 launcher 层补可信握手,当前文档和验收不得宣称该阶段具备原子保证。
- V1.11.1 决策:bwrap `child-pid` 只作为 child-created,不作为 sandbox-ready。Linux launcher 必须以受信任 trampoline 和独立私有控制通道完成 `SANDBOX_READY -> durable commit -> COMMIT_EXEC -> EXEC_ESTABLISHED`commit 前失败显式 kill/reap 且目标零执行,commit 后无 exec-ready 进入 launch-unknown reconciliation。PTY 控制帧不得混入 transcript。
- 验收修正:V1.10 process fixture 写 `.agent`、启动 TCP 并跨 namespace 使用 PID/端口,与 V1.11 安全边界冲突。V1.11 真实复验改为纯 PTY readiness/challenge/echo/stopped 协议,以唯一 durable start、cursor 链、stdin hash 和宿主项目 cwd 进程清零证明;Provider 502 的零工具计划失败单独记为外部瞬态错误。
## 2026-07-14 AI 游戏创作 Agent Runtime V1.11.1 一次性命令可信握手
- 决策:Linux `command.exec / project.verify` 统一进入 `bwrap child-created -> block release -> SANDBOX_READY -> durable callback -> COMMIT_EXEC -> EXEC_ESTABLISHED`。revision、旧验证凭证和 verification running 状态只在 ready 后持久化;target exec 失败保留已提交 revisioncommit 前失败不执行目标。
- 决策:当前 bubblewrap 0.11.1 没有可用的 `--preserve-fds`。一次性命令原本不接收 stdin,因此控制 socket 仅占 bwrap/trampoline 的 fd 0trampoline 启动真实目标时显式恢复 `/dev/null` stdinstdout/stderr 保持业务专用。PTY 不复用该方式,后续由 process child wrapper 在 PTY 外桥接同一帧协议。
- 决策:bwrap 使用 fd 4/5 接收 status/block,运行中 App 可执行文件由父进程预打开并通过 fd 6 + `--ro-bind-fd` 挂到固定 trampoline 路径。pre-exec 先把全部源复制到 64 以上临时 FD,再统一映射到固定号,避免并发时源/目标 FD 重叠导致通道被覆盖。
- 决策:bwrap COMMAND 分隔符固定取 launcher 插入的第一个独立 `--`,不能从目标 argv 末尾反查。durable commit 到 exec verdict 之间禁止 async awaitcommit 后协议/等待未知和执行后 command log、manifest、Agent DB、verification gate 落盘失败统一投影为 `needs-reconciliation`,只有明确 `TARGET_EXEC_FAILED` 可作为已知未 exec 的普通失败收束。
- 边界:本切片只完成 `command.exec / project.verify`。`command.start`、process record v3、PTY 零控制帧泄漏和真实 Provider process-session 仍未完成,不宣称 V1.11.1 已整体交付。
## 2026-07-14 AI 游戏创作 Agent Runtime V1.11.1 持久进程可信握手
- 决策:`command.start` 的 Runner 与 PTY child wrapper 使用 Linux abstract Unix socket 建立一次性私有 bridge,并同时校验 32 字节随机 nonce、`SO_PEERCRED` peer pid 和 uid。portable-pty argv 只保留内部 child mode,完整 bwrap/target launch plan 只走 bridgeendpoint、nonce、控制帧和宿主 launch plan 不进入 target argv/env、PTY transcript、process record、receipt 或 Agent DB。
- 决策:portable-pty 会关闭 fd 3 以上描述符,bubblewrap 也不会把未被 option 引用的 fd 3 传给最终 COMMAND,因此 process-session trampoline 仍以 fd 0 接收私有 gate。真实 target 的 stdin 由 trampoline 校验 fd 1 是 PTY 后复制同一 slave 得到;一次性命令继续使用 `/dev/null` stdin,两个模式不能混用。
- 决策:process record 升级为 v3,新增 `sandboxEstablishment / targetExec / launchFailureKind / sandboxReadyAt / execEstablishedAt`。Runtime 只在 `SANDBOX_READY` 后推进 revision、清除旧验证凭证并最后写入 `launching + established/not-attempted` commit record;只有 `EXEC_ESTABLISHED` 后才写 running、注册 live session、启动业务 timeout 并返回零消费 cursor。明确 `TARGET_EXEC_FAILED` 写同一 processId 的 failed recordcommit 后未知写 `launch-unknown + needs-reconciliation`。
- 决策:v1/v2 active record 无条件迁移为 `unknown / unknown + legacy-active-record + needs-reconciliation`,不按 PID 重连或自动重放;历史 terminal record 保留原终态和 transcript,可用 `unknown / unknown + legacy-record` 惰性读取。target exit 0/7 均沿原 processId 收束。
- 决策:PTY wrapper spawn 前先登记 pending launch reservationRunner shutdown 和 idle/final 门禁必须看见 reservationpid 激活前收到 shutdown 也必须取消,激活后终止整个 wrapper 进程组。start Agent DB 审计失败必须终止 live process 并把 record 标为 `start-audit-failed + needs-reconciliation`。
- 决策:process-session child 在 sandbox-ready 后只接受父侧显式 `COMMIT_EXEC / ABORT_LAUNCH`,不使用独立 3 秒 commit timeout。durable callback 慢于 launcher setup timeout 时 target 继续保持零执行;父侧失败必须先发送 abort,再 kill/wait/reap containment tree。
- 决策:Linux process-session target 在 child pre-exec 内暂时屏蔽 SIGTTOU,原子完成 setpgid + tcsetpgrp 并恢复信号掩码后才 exec,避免 immediate stdin read 以后台组停在 SIGTTIN。graceful terminate 经 Runtime bridge 和 trampoline 私有控制帧只向 target group 发送 SIGTERMdirect leader 先退出时 trampoline 仍检查同组后代,wrapper/bwrap 保持最多 800ms 宽限并继续承载 PTY,宽限后再强杀外层 containment group。Runner 强杀仍依赖 owner monitor 与 bwrap die-with-parent 回收整个 namespace。
- 决策:process record v3 使用封闭 launch 状态矩阵和逐项时间校验。`launch-unknown / start-audit-failed` 必须对应 `needs-reconciliation=true`,明确 target exec failure 只能是 `established/failed`;旧 boot prepared/launching 降级 target 为 unknown,非法 record 读取失败关闭,不能绕过 final/idle。Windows legacy start 的 durable callback 移到首次 action miss 之后,同 action replay 只返回原 processId,不重复推进 revision。
- 决策:`command.stdin` 写入和 flush 成功后,若 target 在 writer 释放后先形成可信 terminal,仍按成功返回并持久化 `stdinOpen=false`;只有写入部分失败或结果 record 无法落盘才进入 reconciliation。
- 决策:active process session 事实由 live registry、durable active/reconciliation record 和 Linux pending reservation 并集构成。capacity、cancel、final 和 runner idle 都必须先合并 live registryrecord 被删除或改名不能让 live session 失败开放,损坏 record 仍读取失败关闭。non-Linux live record 的 started/ready/exec 使用同一 launch 时间点,避免跨秒后违反 v3 时间顺序。
- 边界:确定性 bridge、PTY、迁移、fast-exit、target-exec-failed 和零执行测试通过后,只能宣称本地 Runtime 链路完成;真实 Provider `process-session` 与 Runner kill 套件重新通过前,不新增 V1.11.1 Provider PASS 结论。
## 2026-07-14 AI 游戏创作 Agent Runtime V1.12 受控本地 Git 提交
- 决策:新增且只新增 `project.git_commit`,补齐单 Agent 修改、验证、`git.inspect` 审阅后的本地提交闭环。该工具强制确认,项目策略和 legacy 空策略都不能降为 `auto`,但可显式 `deny`。首版不开放 remote、分支、merge / rebase、reset、stash、tag、submodule 或 worktree 写操作,`.git` 对 `command.exec` 继续只读。
- 决策:提交绑定 `message / paths / expectedHead / expectedSnapshotFingerprint`,最多 12 个显式安全路径;执行前要求标准仓库根、附着分支、空 staged index、当前 HEAD 与安全工作树快照一致,并要求当前 run 的非零项目 revision 已有 passed verification gate。跨动作快照排除 `.agent`、凭据和其它隔离路径的正常控制面变化,但绑定全部安全变更状态和文件内容;安全源码、HEAD、revision 或 gate 任一漂移都在 Git 写入前失败关闭。
- 决策:Runtime 用临时 index 构造精确 tree,以真实 index lock、`commit-tree` 和带 expected old HEAD 的 `update-ref` 推进本地分支,再安装与新 HEAD 对齐的 index;只读取仓库本地作者身份并禁用 hooks、签名、pager、全局配置、凭据和网络。执行中崩溃或 ref 前移后的不确定失败进入 `needs-reconciliation` 且不得重放。
- 决策:动态隔离 child 禁止调用 `project.git_commit`,最终提交只由父 Agent 统一发起。`update-ref HEAD` 使用固定安全 reflog message 同步 HEAD / branch reflogWindows index 安装必须使用 replace-existing + write-through 语义,不能用无法覆盖已有 index 的普通 rename。
- 审计:确认摘要不保存完整提交正文;成功 observation / receipt 只保留 parent / commit SHA、分支、路径数量、有限安全路径、message SHA-256 和剩余变更计数。已知 commit 成功但专用审计失败时,fallback terminal receipt 仍保存同一安全字段;执行中恢复不重放。编码级契约与验收矩阵见 `docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` 的 V1.12。
- 真实验收:`gpt-5.5` `llm-runtime` 已在无固定路径、脚本、值、HEAD、snapshot fingerprint 和工具顺序配方下完成唯一受控提交。验收器从原始 commit object 消息 SHA-256、真实 Git parent / HEAD / tree、空 staged index、提交后所选路径、封闭字段专用审计、terminal receipt 和 HEAD / branch reflog 交叉核对,证明 2 个目标路径进入提交、预存 sentinel 保持未跟踪;最终收紧版 151 条 task、258 条 event、266 条 Agent DB、7 套确认、9 个副作用 action 和 44 条 receipt 中,副作用重放、重复 action / message / receipt、密钥与诱饵泄漏均为 0。Runner 强杀恢复保持原 run / sessiondisposable 项目按 sentinel 自动清理。
## 2026-07-14 Agent Runner 临时端口耗尽与旧进程恢复
- 决策:Runner 正常仍优先 `bind(127.0.0.1:0)`。Linux 仅在该调用返回 `AddrInUse` 后懒读取 `ip_local_port_range / ip_unprivileged_port_start / ip_local_reserved_ports`,按 boot 随机化起点并扫描 61000-65535 中同时位于临时范围外、不低于实际非特权起点且未被 reserved ranges 占用的 loopback 端口;任一 sysctl 不可可信读取、候选耗尽或非占用类错误继续失败关闭。不得停止现有服务、绑定非 loopback 地址或移除 endpoint 私有 token。
- 决策:`runtime.resume` 先全局分类 reconciliation record,未知 Agent 立即失败关闭,再在每个 Agent lane 取得任务锁后处理所属旧 boot record。record 迁入 reconciliation 后,所属 task / state / queue / event / Agent DB 必须逐投影、可修复地幂等同步为 `needs-reconciliation`task 仅在尚未进入 reconciliation 时追加;state / queue 每次从 task ledger 重建;event 和 Agent DB 绑定原 `startActionId + actionFingerprint`,锁内修复截断 JSONL 尾记录,再按 Agent/task/session/run/process/owner boot 全字段检测后补齐。同键冲突必须报错,不能当成完成。任一中间写入成功后 Runner 再次崩溃,下一次 resume 仍要继续补齐其余投影,不能因 task phase 已更新而整体早退。
- 决策:恢复写入前必须逐条核对 process record 与 owning task 的 `agentId / runId / taskId / conversationSessionId`;任一冲突都失败关闭且不能改写原 task。同一 Agent 同时存在多个不同 owning run 的 reconciliation record 时也失败关闭;同一 run 的多个 record 只可在全部身份一致后聚合。缺 owning task、记录损坏或身份冲突时禁止恢复 LLM、按 PID 重连或重放 start。
- 验收门禁:Runner-kill 套件必须分别从全量 task、event、Agent DB、runtime state 和 process record 证明专用 reconciliation 各精确一次,并证明新 boot reconnect 为 0。activity / output 和可选证据目录只有 `ENOENT` 可视为空;权限、I/O 和 JSON 损坏必须让验收失败,runtime state 是必需证据并纳入公共正文泄漏扫描。
- 决策:模型即使在 prompt 明确禁止后仍可能把 `command.poll` 私有正文或短值复述到最终回复;只要本 run 存在非空私有 poll 输出,finalization 在 assistant journal 写入前就把模型回复整体收束为固定安全摘要。原始 PTY 正文仍只留在 owning Agent 私有 context,不能依赖模型自律或按长度猜 token 维持公共边界;没有私有 poll 正文的普通回复保持原样。
- 验收:真实 `gpt-5.5` `process-session` 与 `process-session-runner-kill` 均已 PASS。普通套件证明唯一 start、连续 cursor、精确 challenge/echo、graceful terminal 和零公共正文泄漏;强杀套件从 task / event / Agent DB / process record 各证明 1 条专用 reconciliationruntime state 身份一致,项目进程清零、新 boot 保持同 run / sessionreconnect / replay / final 均为 0。真实主机临时范围 32768-60999 被约 2.8 万连接占满时,Runner 使用范围外 loopback 端口完成两套验收。终审回归另通过 44 项 process-session 定向测试、Tauri 全量 587 passed / 4 ignored、Windows GNU check、客户端 typecheck、4961 文件编码检查、rustfmt、Prettier 和 diff check。
## 2026-07-14 AI 游戏创作 Agent Runtime V1.13 当前 Run 追加指令
- 决策:运行中输入默认形成 same-run steer,保持 `taskId / sessionId / runId` 不变;只有开发者显式选择“排队新任务”才创建新 run。动态隔离 child 首版拒绝 steer,终态、cancelling、finalizing 和 needs-reconciliation 同样拒绝。
- 决策:私有事实源为 `.agent/runtime/steers/<agentId>/<runId>.jsonl`,按 `prepared / conversation-persisted / queued / applied / closed` 只追加推进。正文只出现在 prepared 与确定性 messageId 的 user conversation;公共 task/event/Agent DB/Runner RPC 只保存身份、sequence、SHA-256、长度和状态。同 steerId 同 SHA 幂等,不同正文冲突;单条 4 KiB、单 run 16 条且总计 16 KiB。
- 决策:context bundle 和 Runtime state 保存 applied cursor 与安全 refspending action 指纹绑定 planned cursor。Provider 前、Provider 后、terminal observation 后和 finalization 前消费或复核;context 先持久化、applied 后追加,恢复以 context cursor 修复缺失 applied audit。自动动作进入 executing 时与 steer acceptance 使用同一项目写锁;确认中、approved 或 executing 动作保持原 fingerprintterminal receipt 后才消费。
- 决策:Runner typed `runtime.steer` 只携带 `root / agent / runId / steerId`,并先核对 durable ledger。中断 registry 只包围 planning 和 final reply HTTP future;不得 abort worker 或中断任何工具和副作用。prepared finalization journal、completed 终态与 steer acceptance 共用项目锁形成双向门禁,completed 前在同锁内关闭 ledger。新增方法把 Runner 协议提升为 v2;旧协议 Runner 只允许在 `shutdown_if_idle` 确认空闲并释放 endpoint 后升级,仍有任务时禁止强杀替换。
- 接口:Tauri 使用 `steer_game_creator_agent_runtime_task`CLI 使用 `--agent-steer <project> <agentId> <sessionId> <runId> <steerId> --stdin`。开发窗口与项目内 Agent 面板使用同一默认 steer / 显式排队交互,并把 cancelling 显示为“正在取消”。
- 验收:确定性 Rust 已覆盖幂等、冲突、并发 sequence、限制、错误状态、conversation、context/applied 崩溃修复、Provider in-flight 中断、旧写入计划零执行、自动动作 cursor 门禁、确认延后和 finalization 竞态;Runner/CLI 与两个 App 入口定向测试通过。仓库外真实 Provider same-run 专项已 PASS:一次 Provider 中断、原 run 唯一、五阶段 ledger、2 条 user/1 条 assistant、追加正文和已加载密钥零公共泄漏,并实际完成 Runner v1 到 v2 的空闲升级。V1.13 Runner kill 仍需独立复验,不把本次专项结果外推到强杀恢复。
## 2026-07-14 AI 游戏创作 Agent Runtime V1.14 会话分叉
- 决策:开发 Agent 窗口新增 `codex fork` 风格的 Session 分叉。分叉从 active、archived 或 legacy Session 完整复制分叉瞬间已持久化的 conversation,保留 role、content、agentId、messageId 和时间,创建带 `forkedFromSessionId / forkedMessageCount` 的新 active Session;源会话和后续消息互不写入,不复制 task/event、Runtime state、pending action、process session、finalization、run history、私有长期记忆或项目黑板,也不推进项目 revision。
- 决策:Session 新建、切换、归档、分叉与 Runtime 入队 / 启动共用 per-Agent session lane gate。未显式传 `sessionId` 的 Runtime 只能在线性化点内解析 active SessionRuntime 先入队时分叉看到未结束任务并拒绝,分叉先提交时后续 Runtime 读取新 active,不能成功分叉后把任务或用户消息写回旧会话。
- 决策:task journal 对 Session 变更失败关闭,只把 `completed / failed / cancelled` 且 phase 非 `needs-reconciliation` 视为终态;未知、矛盾、损坏和不可读父/子任务日志均阻断。分叉文件先 `create_new` 完整写入,再原子更新 catalog;catalog 写失败删除未登记文件,Session list 获取 catalog lock,不能把提交中的文件提前暴露为恢复会话。
- 接口:Tauri 新增 `fork_game_creator_agent_session(projectPath, agentId, sourceSessionId, title)`;开发 Agent 窗口提供分叉按钮、来源与复制消息数显示,成功后按返回的 activeSessionId 加载历史。运行中或 reconciliation 禁用;归档 Session 保持只读,但 lane 空闲时仍可作为分叉源。
- 验收:Tauri 全量 639 项中 635 通过、4 项真实浏览器 opt-in 用例按设计忽略;分叉定向覆盖空会话、消息与 messageId 精确复制、active / archived / legacy、源与分支隔离、重复分叉、非 active 源任务、委派 child、损坏 journal、catalog 失败清理、Runtime 入队竞态和未提交文件不可见。客户端测试目录 268/268 通过,覆盖精确源 Session、复制历史、新 Session 后续写入、切回源会话隔离、归档源分叉和忙碌禁用;shell typecheck 通过。
## 2026-07-14 AI 游戏创作 Project Supervisor 总控 Agent
- 决策:正式用户主聊天的规范 Runtime Agent ID 固定为 `project-supervisor`。它使用现有 External Runner、工具策略、active Agent Session、steer、黑板和 finalization,不新增平行 Runtime、队列或数据库;不进入 manifest、专业组和 isolated template 白名单。LLM 路由优先 `agentLlm.project-supervisor`,旧 `agentLlm.chat` 只作兼容回退。
- 决策:新的普通用户消息和 assistant 只写 Supervisor SessionReact 不再把同一轮双写到 `.agent/conversations/project.jsonl`。legacy project conversation 只作为有界历史背景,项目初始化和旧 slash 命令仍可保留原路径。普通用户界面固定使用 active Supervisor Session,不暴露开发用 Session 管理。后台任务在 Session lane 内完成 durable 入队,通知 External Runner 必须在释放 lane 后发送,避免 Runner 反向启动同一 Agent 时形成跨进程自锁。
- 决策:静态 `agent.delegate` 增加 durable delivery 与同一父 run 完成屏障。同一 Supervisor 父 run 最多同时等待 3 个 `dispatched / ready` 专业 Agent;第 4 个新委派在 child 创建前拒绝,已预留的同 action delivery 恢复必须复用原 target Session/run。同一工具计划的委派动作提交完毕后,只要存在 running child 或 ready 未认领回执,Runtime 就必须在下一次 Provider planning 前进入 `waiting-for-delegate-receipts` 并释放 lane;不能让模型反复轮询全量状态。子终态唤醒同一 run。正常 Supervisor 路径不创建第二个 `delegate-receipt-*` run。
- 决策:delivery journal 状态为 `dispatched -> ready -> claimed-by-parent / suppressed`claim journal 状态为 `Prepared -> Committed -> Observed`。`agent.run_status` 以当前 actionId 认领时,先持有 claim 锁,再对 delegationId 排序去重并按序取齐 delivery 锁;任一锁不可得时不创建 claim、不改写任一 delivery。全部锁就绪后才按 Prepared、delivery 绑定、Committed 推进,pending observation 持久化后再写 Observed;恢复可补交 Prepared,未 Observed 继续阻断完成。delivery / claim / pending observation 是事实源,Agent DB 只作 best-effort 诊断投影,审计追加失败不回滚已持久化协议。
- 决策:executing 恢复只对 `project-supervisor` 的 `agent.delegate / agent.run_status` 开放专用门禁;项目锁内必须重验 durable pending、Session/run/action fingerprint、Runtime 与 delivery/claim/child 完整身份,尚无副作用时还要重新执行 policy/确认判定。只有 delivery 预留且无 child 时可安全退回确认,用户拒绝必须 CAS suppress 该预留并清除完成屏障。`agent.run_status` 的 claim 身份由 delivery/claim journal 约束,对专业 Agent 写黑板或项目文件造成的全局 revision / repository fingerprint 漂移保持中立。其他 executing 动作或身份冲突直接进入 `needs-reconciliation`,不通用重放。
- 决策:parent-wake 以 project/Agent/run 做 coalescing singleflight;已有 worker 期间到达的新信号设置 rerun,worker 退出与信号消费在同一 registry 锁内完成。只对 lane 忙、暂时连接、连接中止、broken pipe、unexpected EOF、资源暂不可用和超时类错误做有界重试;损坏 journal、身份冲突和重启扫描中的损坏 barrier 投影 `needs-reconciliation`。External Runner `runtime.wake_pending` 的 requestId 由项目根、method、Agent、runId 和 loop iteration 稳定派生,只有精确目标已推进或无需推进时才缓存成功。子终态在 ready 或 suppression 前必须核对 parent Agent/Session/run/action、delegationId、target Agent/Session/run、child source 和反向链接;错配 child 不得 suppress 或改写原 delivery。父任务先进入 completed / failed / cancelled / budget-exhausted 时,终态写入路径枚举并 suppress 尚未认领的匹配 delivery,合法迟到 child 不能重新写 ready。
- 决策:finalization 在项目锁内复核 process session、isolated join、static delivery 的 waiting / ready-unclaimed / unobserved-claim 与 verification gate。只有全部清零时,原 Supervisor Session/run 的 finalization journal 才能幂等写入唯一 assistant 并投影 completed。
- UI:普通用户只看到总控 Agent 的紧凑状态、等待对象、协作数量、安全确认和唯一最终回复;不展示内部工具计划、原始 observation、动态 child 或开发控制台。Runner 未提供 token delta 时只显示真实状态,不做伪流式。
- 验收:Rust `project_supervisor_` 定向回归覆盖 ID/prompt/config、delivery/claim 幂等、排序锁零部分认领、Agent DB 旁路、未 Observed 门禁、Provider planning 前 durable 等待、parent-wake coalescing/结构性错误投影、重启损坏 barrier、完整身份与迟到 child suppression、delivery `.previous` 恢复、旧 receipt runId 冲突、executing `run_status` 续接与委派 policy 重验;Runner 内部回归覆盖定向 wake 只有目标推进后成功、可重试结果不缓存;Session lane 回归覆盖入队后才通知 Runner。客户端定向回归覆盖 active Supervisor Session、same-run steer、legacy 历史合并、确认/拒绝和唯一终态 assistant。修复后真实 Provider 已证明 design/art 两个专业 Agent 同秒进入 running 并重叠 20 秒,父 run 只写 1 条 waiting、同一 `Observed` claim 认领 2 份回执、首轮恰好 1 条 user / 1 条 assistant、无 reconciliation;同一 Session 第二轮引用上文完成且未新增委派。项目范围精确密钥扫描为 0。V1.15 首轮跑偏和本轮修复前 revision 误伤仍只保留为负向历史,不作为通过证据。
## 2026-07-15 AI 游戏创作 Agent Runtime V1.17 单 Agent 持久计划
- 决策:`submit_agent_tool_plan` 顶层新增 nullable `planUpdate={explanation,steps[{step,status}]}`。strict function arguments 必须出现该字段,无真实变化时传 `null`;结构化更新最多 8 个唯一步骤,状态只允许 `pending / in_progress / completed` 且至多一个 `in_progress`。旧文本协议可缺字段,legacy `plan` 只作 fallback;当前 run 一旦有 `planRevision > 0`legacy `plan` 不得再覆盖结构化计划。
- 决策:Runtime state 持久化 `planRevision / planExplanation / planSteps / activePlanStepIndex`。有效变化使 revision 单调递增,完全相同的更新幂等不增号,非法更新不改快照;已完成或历史快照中已有的失败终态步骤必须保留,completed 不得回退。外层 run 进入 `failed / budget-exhausted` 时保留最后一次可信计划的 revision、说明、步骤状态和 active index,不把未完成步骤机械改写为失败。结构化计划建立后,工具 action 下标和旧自动步骤 helper 全部失去进度写权限,Agent 必须依据真实 observation 显式更新计划。
- 决策:任一结构化步骤未完成时,空 actions、Provider response 和恢复中的 finalization 都由 `runtime.plan_update` blocker 拦截,不能写 assistant 或 completed。计划更新只属于 Runtime 私有元数据,不是工具 action,不读取或改写项目 policy,不触发 confirm/deny,不推进 project revision 或 verification gate,也不改变待确认动作 fingerprint。
- 审计边界:`thinking_summary` 公共 event 只留正文 SHA-256 与字符数,legacy `plan` event 只留步骤数;`agent.runtime.plan_update` 只留 explanation 哈希与字符数,以及 step 标题哈希、状态和数量。`agent.runtime.tool_plan.repair` 只留尝试计数、协议以及模型输出/调用体预览、解析错误、callId / functionName 的哈希与长度,不落原始正文、错误或 function arguments;仅当前 planning 的私有有界 repair 请求可保留经过过滤的必要上下文。
- 恢复与 steercontext bundle 升级为 `game-creator-runtime-context-bundle.v3` 并保存完整计划快照;v3 revision 或快照与 Runtime state 不一致时失败关闭,损坏 state 进入 `needs-reconciliation`。v2 继续可读,但只能在原身份、task、revision 与 verification gate 校验通过后从当前 state 补齐计划字段,后续 checkpoint 写 v3v1 仍拒绝。Runner 重启、确认续跑和 stale finalization 不得重建或自动完成计划。same-run steer 丢弃旧 actions / 旧回复但保留终态步骤和 revision,下一版只重审未完成部分。
- Finalizationjournal 升级为 `game-creator-runtime-finalization.v2`,在 `prepared` 时绑定最终完整计划快照与 `planSnapshotFingerprint`,并把计划指纹纳入幂等 `finalizationId`。assistant 已落盘而 Runtime state 丢失时,从唯一 task record 与 v2 journal 恢复原 structured plan 后补齐 completed,不请求 Provider、不重放工具;assistant 尚未落盘而 state 丢失时保留 journal 并进入 `needs-reconciliation`,不得只凭 task 或 prepared journal 猜计划并写回复。
- 展示:开发 Agent UI、项目内开发面板、CLI / `agent.run_status` 有界展示 revision、说明和最多 8 个完整步骤;刷新合并只沿用同一 Agent/Session/run。正式用户 Project Supervisor 只显示完成数、当前步骤、等待对象、下一步和专业 Agent 协作数量,不暴露 revision、内部说明、完整步骤、原始 observation、内部动作或动态 child。
- 验收:确定性回归覆盖 schema、native function 显式字段与文本兼容、限制、单调性、外层失败进度保留、终态保留、动作下标零推进、未完成 final 门禁、损坏状态、v3/v2 恢复、finalization v2 state 丢失恢复、公共审计零正文、计划元数据 revision/policy 中立和两类 UI。恢复/steer 专项必须证明同一 run/session、revision 不回退、终态不丢、旧动作零执行和副作用零重放。真实 Provider 必须在无计划/工具配方的 disposable 项目中自行建立并多次更新计划,经历一次 same-run steer 与一次 Runner 重启,最终只在全部步骤 completed 后写唯一 assistant,并由 Runtime state、v3 bundle、task/event/Agent DB/conversation 和副作用计数交叉取证;截至 2026-07-15 尚未记录该专项 PASS。
- 全量回归修正:context bundle v3 为保持计划快照一致,会在每个 action / observation 后同步;repository startup fingerprint 因此不能继续从最新 bundle 读取,否则同一 planning 批次的前置验证改变规范文件后,后续旧写动作会错误放行。pending action 升级为 `game-creator-pending-action.v4`,绑定 Provider planning 实际渲染的 repository fingerprint;同批 actions、确认和恢复统一复核该快照,旧 v1-v3 失败关闭。五类写动作 drift 回归证明旧动作零执行。
- 终审补充:finalization v2 读取边界必须再次要求所有结构化步骤 `completed` 且 active index 为空;仅重算合法 `planSnapshotFingerprint / finalizationId` 的未完成快照也失败关闭。开发 CLI 的 Runtime JSON 只输出状态和安全身份,递归移除 `sessionPath / eventPath / taskPath`,不把项目绝对存储位置写入命令 transcript;Tauri/App 内部结果结构保持不变。
- 历史验收:2026-07-15 的确定性回归已通过,但真实 `gpt-5.5 llm-runtime` 连续三轮均在首个 Provider planning POST 返回前因同一 TLS record-layer failure 失败,未产生 plan/tool/kill/steer 证据;第三轮绝对路径、密钥和诱饵泄漏为 0。当时 V1.17 保持未 PASS,不能以短请求或确定性测试代替完整重跑。
- 2026-07-16 恢复修正:Provider interrupted 和 Provider completed 两条“返回后发现并消费 steer”路径在持久化 continuation 时,`nextLoopIndex` 必须使用 `loop_index + 1`,因为当前循环已被消费且随后立即进入下一轮;循环入口和普通 tool observation 后仍保持各自既有索引语义。旧实现写入当前 `loop_index` 后继续,可能形成 context `nextLoopIndex=5`、Runtime `loopIteration=7`Runner 重启遂错误失败关闭为轮次不匹配。定向回归在替代 planning 请求期间同时核对 steer cursor 为 1 和 `context.nextLoopIndex + 1 == runtime.loopIteration`。
- 2026-07-16 真实结论:正式 `openai_chat / gpt-5.5` 的隔离 `steer-runner-kill` suite PASS。专用任务不再耦合预览、图片、隔离 reviewer、外部素材或 Git 提交;Agent 真实观察一次非零验收,以唯一 patchset 完成两文件原子修复,审阅完整正文差异并通过 Agent/宿主复验。计划 revision 4、3 个步骤已完成时注入一次 steerProvider lifecycle 唯一进入 `interrupted`Linux pidfd 强杀 Runner 并更换 boot 后仍保持原 Agent/Session/runcursor 单调保持 1,最终 revision 7 的 6 步全部完成。24 组 Provider request identity 全部闭合,旧动作执行、副作用重放、重复 action/message/receipt、遗留 finalization,以及任务/steer/根因正文、API Key、诱饵、项目路径和正式配置路径公共泄漏均为 0;唯一 assistant/completed 和隔离 Runner/AppData/项目 sentinel 清理全部成立。V1.17 当前门禁状态为 PASS。
## 2026-07-15 AI 游戏创作 Agent Runtime V1.18 单 Agent 持久 Goal mode
- 决策:Goal 规范记录使用 `game-creator-agent-goal.v1`current 路径固定为 `.agent/runtime/goals/current/<agentHash>/<sessionHash>.json`,终态 history 路径固定为 `.agent/runtime/goals/history/<agentHash>/<goalHash>.json`hash 取对应稳定身份 SHA-256 十六进制前 32 位。Goal 绑定 Agent/Session/run 和单调内容 revisionRuntime state 与 task 只保存身份、revision、状态投影,不复制 Goal 正文或建立第二份生命周期事实源。
- 恢复快照:context bundle 升级为 `game-creator-runtime-context-bundle.v4`,绑定 `goalId / goalRevision / goalStatus / goalSnapshotFingerprint`。Provider planning/final 中断或返回到 pause 安全边界时,先以恢复后的 `active` Goal 语义持久化 continuation,再把当前 Runtime/Goal 收束为 paused;不能只写暂停状态而丢失恢复轮次。
- 动作门禁:pending action 升级为 `game-creator-pending-action.v5`,在 project revision、verification gate、repository context fingerprint 和 steer cursor 之外绑定 `goalId / goalRevision / goalSnapshotFingerprint`;旧 v1-v4 全部失败关闭。Goal edit 提交新 revision 后,旧自动动作和旧待确认动作统一转成 `blocked` observation,在原 run 重规划,禁止执行旧副作用、从当前 Goal 猜回绑定或创建 retry run。
- 暂停恢复:Runner 重启先处理 cancel / Goal control,再进入 process reconciliation、finalization、pending action 和 runnable task`pause-requested` 必须先收束成 `paused``paused` 直接保持休眠。resume 的有效迁移只接受 `paused -> active`,先清理同一 run 遗留 cancel tombstone,再唤醒原 Agent/Session/run,不创建新 run;若 sidecar 已 `active` 但 Runtime 投影或 Runner 唤醒未提交,重复 resume 继续补齐同一 run,不能假成功。当前 Agent/Session/run 的 Goal sidecar 损坏或冲突时,即使 Runtime 缺少 legacy `goalId` 投影也失败关闭到 reconciliation。
- Finalizationjournal 升级为 `game-creator-runtime-finalization.v3` 并绑定 Goal revision/快照。assistant 按稳定 messageId 落盘后,先可靠写入 Runtime completed task/state,再提交 Goal completed,并补写携带 Goal 终态的 task/state projection;全部可靠后 journal 才进入 `runtime-completed` 并删除。assistant 尚未落盘且 Goal revision 漂移时丢弃旧 prepared journal 并 same-run 重规划,assistant 已落盘后只补投影,不再请求 Provider。
- 持久请求证据:background planning / final reply 的 request snapshot 固定绑定 project、Agent、task、Session、run、source、Goal ID/revision/snapshot fingerprint、applied steer cursor、request kind 和 request slotrequestId 从该闭集稳定派生。Provider future 真正开始前可靠追加 `agent.runtime.provider_request.lifecycle / started`,且该 lifecycle 只能发起一次物理请求;专用客户端强制 `max_retries=0`。返回、可观察失败或控制中断后以同一 requestId 追加唯一 `completed / failed / interrupted`,任何歧义错误不得原样自动重放;显式恢复必须创建新的 slot/lifecycle。记录不含 prompt、工具输入、URL、模型、回复或错误正文。
- Provider orphan barrier:注册后在项目写锁内复核 queued steer、cancel tombstone、规范 Goal、task/Runtime 身份和 steer cursor,再提交 `started`;已生效控制不写伪 `started`。启动新请求前全量扫描同 Agent/run 的 lifecycle;发现 `started` 没有可信唯一终态,或同 request 多终态、字段冲突、阶段重复/倒置时,立即把原 run 投影为 `needs-reconciliation`,阻断 Provider、工具和 finalization,禁止自动补发。paused 重启窗口必须以 started 数量零增长证明没有暗中请求。
- Finalization 顺序与容量:同一 `finalizationId / messageId` 的物理七槽严格固定为 `lifecycle/prepared -> conversation.message assistant 审计 -> lifecycle/assistant-persisted -> lifecycle/runtime-completed -> lifecycle/goal-completed -> agent.runtime.completed -> agent.runtime.background_task.completed`。prepared 成功即在独立的 128 条 lifecycle/finalization reserve 中同时预留后六条的记录数和最大字节容量;七条都不能占用 64 条 action receipt/reconciliation reserve。缺前序、倒序、重复、跨身份匹配失败或容量无法兑现时失败关闭;prepared journal 后首条审计失败保持 `finalizing` 并恢复补齐,不得改判普通 failed 或重放 Provider/assistant。
- Finalization 生产闭集:四条 lifecycle 只允许固定 lifecycle 字段和统一 `schemaVersion / updatedAt` envelope,并绑定 `responseChars / conversationPath`assistant 审计只允许 `recordType / agentId / sessionId / role / path / messageId / finalizationId`,两条 completed 审计只允许 `recordType / agentId / taskId / sessionId / runId / source / finalizationId / messageId / responseFingerprint / responseChars`,再加同一 envelope。匹配必须逐字核对 finalization/message、Agent/task/Session/run/source、Goal/plan 快照、response fingerprint/chars 和 conversation path,四阶段还必须核对 ordinal/previousStage 与 JSONL 物理顺序;任何额外生产字段都不能获得 finalization reservation。
- 公共投影边界:task、Goal/steer、委派任务、`project.verify` 命令和 Provider/Runtime error 正文只保留在对应私有执行事实中。event、Agent DB、receipt、activity、output 与报告统一只存身份、状态、SHA-256、字符/字节/条目计数和经 URL、项目根、其它绝对路径及凭据清洗的有界摘要;公共 task 固定不存正文,委派只存 `taskSha256 / taskChars`verify 只存脚本安全标识、`expectedCommandSha256 / expectedCommandChars`、timeout 和结果计数,error 只存 kind/fingerprint/chars 或脱敏摘要。禁止保留 task/Goal/委派/命令/error 的正文、preview、head 或 tail;普通非 Goal 任务也不例外。
- 真实验收器:revision 2 marker/path/content 不再预埋首轮项目 fixture;两个 revision 都必须命中同一交付路径的真实 `file.write` 或 `project.patchset create` 待确认动作,edit 前最终 marker/文件必须不存在,revision 1 已完成步骤在 revision 2 和终态不可回退。Goal suite 使用带 sentinel 的专用 AppData,配置只以 hardlink 复用并在清理前核对 inode/hash;全部 CLI 固定指向专用 config dirRunner 强杀绑定 endpoint、boot、实际二进制/argv 和 OS 启动指纹,endpoint 丢失只允许回收已认领的同指纹进程。CLI JSON 只接受精确 assigned 前缀,Goal completion evidence 按四项生产契约逐字核对,公共扫描同时包含完整正文和两个 marker,失败报告从现存 task/event/Agent DB/conversation 分面容错回收部分证据而不再全报 0。
- 展示边界:开发 Agent UI 使用 `执行 / 聊天 / 目标` 三段模式,Goal 创建/编辑通过独立弹层完成,并展示状态、revision、完成标准和暂停/恢复/清理;纯聊天 CLI 提供对应 `/goal` 命令。正式用户 Project Supervisor 页面不暴露 Goal 管理控件。
- 验收现状:确定性回归与 UI 覆盖不能替代真实 Provider 长链路。截至 2026-07-15 尚未记录 V1.18 真实 Provider PASS;最新现场仍在首轮 planning、零 plan/action 时由对端关闭长连接,Rust 25.2 秒短请求成功只能证明基础通道。恢复后必须用一次性项目完成 Goal edit、pause、Runner 强杀、重启保持 paused、显式同 run resume、唯一 assistant 和零旧动作重放的交叉取证。
## 2026-07-15 Project Supervisor 纯聊天短入口
- 决策:无 GUI 开发聊天省略 `parentAgentId` 时固定进入 `project-supervisor`;新增 `npm run agc:chat -- --config-dir <AppData> [--init] <project>` 作为总控入口。原 `agc:swarm` 和显式 `<parentAgentId>` 继续保留给专业父 Agent 调试,不改变既有调用兼容性。
- 边界:短入口只复用现有 Swarm CLI、External Runner、Supervisor active Session、conversation、黑板、记忆和 durable 委派协议,不新增 Agent、HTTP 服务、数据库或旁路 Provider 调用。
- 验收:CLI 单测覆盖省略 ID 默认总控和显式 ID 兼容;真实入口 smoke 用一次性项目启动 `agc:chat`,终端显示 `project-supervisor`、创建空总控 Session,并在未发起 LLM 请求时通过 `/quit` 正常退出和清理。
## 2026-07-15 后台 Agent 最终回复使用真实增量流
- 决策:对标 Codex streamed agent events 时,现有 Runtime state/event 继续承担工具和阶段进度,只有 `phase=response` 的最终用户可见回复输出 Provider SSE deltaplanning、function arguments、thinking 和 observation 不进入流,也不允许客户端拆字伪装。
- 持久边界:`.agent/runtime/response-streams/<agentHash>/<runHash>.json` 是绑定 Agent/task/Session/run/request slot/steer cursor/revision 的私有、可丢失展示缓存。路径 hash 取稳定身份 SHA-256 十六进制前 32 位;conversation assistant、Provider lifecycle、finalization journal 和 Runtime task/state 仍是完成事实源,公共审计只存流状态、sequence、字符数和哈希。
- 控制边界:流式配置只改变同一 lifecycle 唯一物理请求的传输方式,不增加 fallback 重放。steer、Goal 控制、取消、失败、revision 漂移和 reconciliation 会让旧流失效;最终候选仍经过原 verification/plan/Goal/finalization 门禁并恰好一次写入 assistant。
- 客户端:普通 Project Supervisor 用 runtimeOwned 临时 assistant 渲染匹配流,刷新从 Runtime 轮询恢复;CLI 按 accumulated text 增量输出并避免 settle 后重复整段。真实验收必须证明至少两个公开 delta 先于终态、最终全文一致、单物理请求和公共面零正文泄漏。
- 审计收口:`project.verify` 执行后只允许把精确的 `.agent/logs/command.log` 相对路径写入 Agent DBexpectedCommand 和 output 在公共审计落盘前必须替换项目根路径,其中 output 保留有界尾部供诊断。路径不在该精确位置时,执行结果进入 reconciliation,不能把宿主绝对路径写入公共面。
- 验收:2026-07-15 真实 `gpt-5.5` `response-stream` suite PASS。39 个不同非空快照先于终态,sequence `1 -> 418 -> 425 committed`,最终 883 字;唯一 assistant、唯一 final-reply `started -> completed` lifecycle、4 段 finalizationfallback replay、重复 message/receipt 均为 0。上游物理请求数未直接观测,报告明确使用 lifecycle slot 与 canonical response identity 证明模式。公共正文、API Key、thinking、诱饵、项目路径和 transcript/report 路径泄漏均为 0,隔离 Runner/AppData/项目完成精确清理。
## 2026-07-15 AI 游戏创作 Agent Runtime V1.20 受控联网检索
- 决策:复用 `platform-llm` 的 Provider 原生 Web Search,不新增浏览器、任意 HTTP 工具或平行搜索服务。配置事实源为默认关闭的 `llm.webSearchEnabled` 和可继承的 `agentLlm.<agentId>.webSearchEnabled`;当前只表达布尔启停,不把 Codex 的 `indexed / live` 模式写成已实现。
- 请求边界:普通/角色直聊和后台首个 tool planning 可以按解析配置开启;格式 repair、final reply、图片检查及其它请求固定关闭。搜索流失败时不做普通请求 fallback。Anthropic 与开启搜索的组合在保存、状态和构建阶段失败关闭;自定义网关是否支持必须由真实请求证明。
- 安全边界:网页和搜索摘要是不可信外部输入,不能改变系统规则、Agent 身份、Goal、权限、确认、沙箱或工具协议;禁止把密钥、Cookie、请求头、源码、绝对路径、私有对话、Agent 记忆和项目黑板正文作为搜索词。Provider-native 搜索无法在本地拦截模型生成的 query,因此能力保持显式 opt-in,不能仅凭提示词宣称确定性防泄漏。
- 审计:后台 Provider lifecycle 升级 v2 并只新增 `webSearchEnabled`v1 缺省 false 只读兼容,requestId 不变。状态/UI/CLI 展示解析后布尔值;公共 Agent DB 不保存 query、URL、结果或网页正文。真实 Provider 必须用隔离 AppData 验证,不支持时记录明确失败。
- 真实结论:2026-07-15 当前正式 `openai_chat / gpt-5.5` 路由三轮 `web-search` suite 均 FAIL。请求 lifecycle 显示搜索开启且上游完成,但模型明确报告没有 Provider 原生搜索能力,动态 GitHub release baseline 未命中;复验产生 3 个 planning request identity,也没有搜索结果证据。因此不得把“网关接受 `web_search_options`”当作能力可用,当前路由继续关闭该配置。最终验收使用正式 AppData 同级的 `0600` 私有配置副本,源配置 inode/nlink/timestamps/hash 前后完全一致;正式 AppData/Runner 零写入、零 endpoint 漂移,所有凭据/路径/诱饵泄漏计数为 0,隔离现场已完整清理。
## 2026-07-15 AI 游戏创作 Agent Runtime V1.21 token-aware 持久上下文压缩
- 顺序:MCP 动态工具目录与输出会进一步放大上下文,因此先补 Codex 风格 token-aware compaction,再进入 MCP。当前固定 12 条 conversation/observation 截断不再作为“已具备压缩”的完成证据。
- 配置:`llm` 增加 `contextWindowTokens=128000 / autoCompactTokenLimit=64000 / toolOutputTokenLimit=12000``agentLlm` 可逐 Agent 覆盖。预算估算必须包含 function schemaProvider usage 单独标记为真实值,不能与估算混用。
- 边界:只压缩旧 Agent/legacy conversation 和当前 run 的旧 observation,保留最近精确 tailGoal、任务、结构化计划、steer、pending action、project/repository revision、verification、process/join/delegate、receipt 和 finalization 身份保持规范事实,不进入摘要改写。
- 持久化:私有 `game-creator-runtime-context-compaction.v1` sidecar 绑定 Agent/Session、source prefix 指纹、可选 run、summary 指纹、预算与 usage;同源幂等,追加后 revision 单调,前缀漂移失败关闭。context bundle 只绑定压缩元数据,不复制 summary 正文。
- 请求安全:compaction 使用独立 Provider lifecycle、稳定 request slot、零工具和零 web search。未知 started 或 completed 后 sidecar 未提交均按 orphan barrier 进入 reconciliation,禁止自动重发;sidecar 已提交后恢复直接复用。
- 入口:自动压缩只发生在 background planning 安全边界;开发 Agent UI 与 `agc:chat` / `agc:swarm` 提供 `/compact`,但 in-flight Provider、执行中工具、pending confirmation 或未收束 Runtime 时拒绝手动压缩。正式用户 Supervisor 页面不增加压缩控件。
- 验收:除配置、幂等、篡改、恢复和公共零正文回归外,真实套件必须完成至少 30 轮、两次压缩和一次 Runner 强杀,证明请求低于阈值、原身份不变、工具零重放、唯一 assistant 与早期约束可召回;此前不得宣称整体 PASS。
- 语义修正:历史“每 6 轮形成上下文压缩窗口”的表述由本条取代;6 轮只形成进度 checkpoint 并执行停滞检测,不改写 observation。真正摘要只由 token 阈值或显式 `/compact` 触发。
- 实现收口:显式用户约束由确定性保留层逐字钉住并继续做凭据/绝对路径脱敏;`runtime.compact` 单独使用 6 分钟 IPC 响应窗口,其他 Runner 方法仍为 10 秒;普通后台任务公共审计只保存 `taskChars + taskSha256`;终态旧 bundle 只有在完整身份、Goal、revision、verification、observation、sidecar、steer 校验通过后才可刷新 legacy plan 投影。
- 真实验收:2026-07-15 正式 `openai_chat / gpt-5.5` 路由的隔离 `context-compaction` suite PASS。30/30 轮、两次 compaction revision、一次 pidfd Runner 强杀恢复、早期约束召回和 29134/64000 最大估算输入均满足;30 个 tool-plan 与 2 个 compaction lifecycle 唯一闭合,fallback replay、重复 message/audit、工具重放和公共正文/summary/API Key/诱饵/项目路径/正式配置路径泄漏均为 0。首轮第 22 轮 Provider transport 终态按规则 FAIL 且零重放,新 disposable 项目完整重跑取得 PASS,全部一次性现场已按 sentinel 清理。
## 2026-07-15 AI 游戏创作 Agent Runtime V1.22 Runner-owned MCP 动态工具
- 决策:在 V1.21 token-aware compaction 之后接入 MCP;不新建平行 Agent 或绕开 Runtime 的直连工具层。使用官方 Rust SDK `rmcp`,首切片同时覆盖 STDIO 与 Streamable HTTP、server instructions、Bearer/static header、工具 allow/deny 和工具级审批;OAuth、resources/prompts、sampling、elicitation 与 task-mode 后续继续扩展,当前不得伪装已支持。
- 配置:`mcpServers` 只存 AppData,不使用 `.env` 或宿主环境凭据回退。STDIO 可执行文件从项目外受信任 PATH 解析并清空继承环境;HTTP 默认只允许 HTTPS,显式开关才允许 loopback HTTP。正式默认零 server,敏感字段不进入状态、日志、报告或普通用户 UI。
- 模型目录:Runner initialize 后刷新 `tools/list`,应用 allow/deny、数量和 schema 总预算,再把有界 instructions、description 与真实 input schema 注入 `submit_agent_tool_plan` 的 `mcp.call` 目录。catalog/tool fingerprint 由 Runtime 注入并绑定配置、server info、instructions、schema、annotations 与 execution metadata,模型不能伪造。
- 权限与恢复:`mcp.call` 进入共享命令契约;项目/Agent policy 和 server/tool 配置保守叠加,隔离 child 默认禁止。所有调用复用现有 durable action 的 `approved -> executing -> observed`、确认、steer、Goal 与 reconciliationplanning 后目录漂移回同 run replanexecuting 后结果未知或 Runner 退出禁止重发。
- 隐私:完整 `CallToolResult` 只写 `.agent/runtime/mcp-results` 私有 sidecar;模型只收到 token-bounded text/structured content,二进制只给 MIME/大小/哈希。公共面只保存 server/tool、审批、状态、参数/结果计数和指纹、相对 sidecar 路径及安全错误分类,禁止 arguments、正文、instructions、凭据和绝对路径。
- 验收:STDIO 与 Streamable HTTP fixture 都必须由真实 Provider 发现并调用;有副作用 fixture 还要覆盖确认和 Runner 强杀未知窗口,证明唯一调用、零自动重放、结果回灌、同一 Agent/Session/run 以及全部公共零正文/零凭据泄漏。
- 真实验收:2026-07-15 正式 `openai_chat / gpt-5.5` 路由的隔离 `mcp-runtime` suite PASS。正常 run 的 STDIO lookup、HTTP lookup、确认后 STDIO mutate 各执行 1 次,action/私有 sidecar/terminal receipt 各 3、assistant 1HTTP mutate 副作用后 pidfd 强杀 Runner,恢复只产生 reconciliation 1marker 1sidecar/receipt/assistant/重复调用均为 0。公共 arguments、结果正文、instructions、Bearer/static header、API Key、项目和配置绝对路径泄漏均为 0;确定性 MCP 15/15、Tauri 全量 800 passed / 4 ignored,隔离 Runner、fixture、AppData 和项目已清理。
## 2026-07-16 AI 游戏创作 Agent Runtime V1.23 持久用户输入请求
- 决策:新增 `user.input_request`,让 Agent 在未完成 plan/Goal 时进入 `waiting-for-user-input`,回答后把精确结果作为工具 observation 回灌同一 run;不再用最终回复结束任务,也不把回答混入普通 steer。
- 协议:一次 1-3 个结构化问题,每题稳定 id、短 header、单句问题和 2-3 个选项,自由输入始终允许;该 action 必须单独出现且不能携带最终 response。委派专业 Agent 和 isolated child 不直达终端用户。
- 持久化:私有 sidecar 绑定 project/Agent/task/Session/run/action/Goal/steer 与稳定 question/answer messageId,状态单向推进 `pending -> answer-prepared -> answered | cancelled`。Runner 只可幂等修复本地 sidecar/会话,不重放 Provider 或外部副作用。
- 隐私与恢复:问题/答案正文只进 owning Session、私有 sidecar 和 observation;公共面只留数量、字符数与哈希。刷新、App/Runner 重启、Goal pause/resume 和重复回答保持同一 request/run,身份或正文冲突失败关闭。
- 客户端:Project Supervisor 主聊天、启动器开发 Agent 聊天和项目内 Agent 弹窗复用同一问题卡;等待时普通输入/steer 禁用,卡片不随 Runtime 详情折叠,失败重试保持同一 responseId。
- 真实验收:2026-07-16 正式 `openai_chat / gpt-5.5` 的 `user-input-runtime` suite PASS。Project Supervisor 自主提出 1 题/2 选项,Runner pidfd 强杀换 boot 后 Provider started 保持 `1 -> 1`,回答后同 Agent/Session/run 完成唯一最终 assistant;会话问题/答案各 1,重复 message、公共正文、API Key、项目/配置路径和报告泄漏均为 0,隔离现场已清理。
## 2026-07-16 AI 游戏创作 Agent Runtime V1.24 scoped AGENTS 仓库指令
- 决策:`AGENTS.md` 不再与 README/CONTEXT 一样标成纯数据。`repository-startup-context-v2` 为每份文档持久派生规范 scope;Agent 对目标路径只叠加祖先链规则,根到叶优先级递增,兄弟目录规则不适用。
- 系统边界:项目指令只约束代码风格、工作流、测试和交付,不能改变 Agent/Goal/Session/run,不能授予工具、网络、MCP、文件或命令权限,也不能替用户确认、放宽沙箱/隐私/verification/finalization 或授权副作用重放。README 与根 CONTEXT 继续使用不可信参考边界。
- 恢复:v2 fingerprint 纳入 schema、path、kind、scope 与清洗后正文;v1 pending fingerprint 在任何受仓库上下文保护的动作前都会形成 `repositoryContextDrift=true / blocked` observation,旧动作零执行并在同一 run 重规划。
- 确定性验收:16 条 repository context 测试覆盖根/父/叶/兄弟 scope、顺序、预算、来源清单、清洗和 fingerprintProvider 捕获请求证明 v2 schema、scope、指令/参考边界与正文真实进入 planning;5 类写工具 drift 回归和旧 v1 pending 回归均证明零副作用。
- 真实验收:正式 `openai_chat / gpt-5.5` 的 `scoped-agents` suite PASS。一次性项目只在根、`game` 父级及 `alpha / beta` 兄弟 scope 提供随机规则,任务不含规则正文、期望内容和工具配方,验收脚本只持有期望正文 SHA-256。最终脚本复跑中,Agent 以 2 个项目变更动作只修改两个目标文件,根/父/各自叶规则全部命中且兄弟串用为 0,真实 `project.verify` 与宿主复验均通过;8 组 Provider lifecycle 唯一闭合,最终 assistant/completed 各 1,重复 message/receipt、遗留 finalization,以及最终回复/公共审计/报告中的规则正文、API Key、诱饵、项目/配置路径泄漏均为 0,隔离 Runner/AppData/项目完整清理。V1.24 整体 PASS。
## 2026-07-16 AI 游戏创作 Agent Runtime V1.18 真实 Goal Provider 验收收口
- 真实结论:正式 AppData 的 `openai_chat / gpt-5.5` 路由通过隔离 `goal-runtime` suite。Goal revision 1 的旧待确认写动作在 revision 2 形成唯一 `runtime.goal / blocked` receipt 且零执行/零重放;Agent 取得真实退出码 1 后用一个 patchset 修复,暂停、Linux pidfd 强杀、Runner 换 boot 与显式 resume 全部保持原 Agent/Session/run,稳定窗口中 task/plan/conversation/Provider/action 零推进。
- 完成证据:最终代码快照复跑的结构化计划 revision 11 的 8 步全部完成,11 组 Provider lifecycle 均唯一闭合,finalization v3 四阶段与两层 completed projection 完整,Session 只有 1 条 assistant3 个副作用无重放,重复 action/message/receipt、Goal 正文和失败证据 canary、API Key、诱饵、项目/配置绝对路径以及报告泄漏均为 0。当前 context bundle 生产 schema 为 v5Provider lifecycle 为 v2。
- 同轮修正:`file.write` 只校验非空和上限,合法正文按原字符落盘,不能再经 prompt 清洗或静默截断末尾换行;旧 Goal action 的 `runtime.goal / blocked` receipt 是合法终态转换,验收器必须核对同 actionId/指纹及原输入摘要哈希,不能误判为重放身份冲突;公共 event 的 observation detail 复用安全 receipt 元数据,不保存 `file.read / project.diff` 正文;file/memory 专用审计只保存项目内相对路径,不写 `absolutePath` 或项目根。
- 恢复兼容:公开 observation event 收紧为安全 receipt 元数据后,恢复旧 action event 时只允许“旧 detail 存在、当前投影省略 detail”这一种向更严格投影迁移;Agent/Task/Session/Run/action、状态、阶段和摘要仍须完全一致。其他 payload 差异继续按幂等身份冲突失败关闭,避免旧项目因脱敏升级永久停在 `needs-reconciliation`。
- 验收约束:revision 2 的 Goal 明确要求任何修复前先运行项目声明的原始验收并观察非零退出,不指定命令或修复配方;一次性失败证据 canary 按 event/Agent DB/receipt/activity/output 分面扫描。保留现场完成二次核对后,隔离 Runner、AppData 和 disposable 项目均按 sentinel 清理。
## 2026-07-13 普通微信支付 V3 退款使用统一观察事务闭环
- 背景:普通微信支付 V3 的退款申请响应、退款结果回调、主动查单和商户平台手工退款发现可能重复、乱序或只出现其中一种;原充值订单只有单一终态,无法表达多次部分退款、权益回收欠款和会员人工处理。
- 退款事实:新增 `profile_recharge_refund`、`profile_recharge_refund_observation`、`profile_recharge_order_refund_settlement` 和 `profile_recharge_refund_bill_checkpoint`。所有已验签退款事实统一调用 `record_profile_recharge_refund_observation_and_return``out_refund_no` 是商户幂等键,微信退款单号保持唯一,重复 observation 必须核对原订单、微信支付单、金额、状态和事实指纹,不能仅按主键吞掉冲突。
- 订单与权益:部分退款保持原充值订单 `paid`,累计成功退款等于订单金额时才改为 `refunded`;`paid_at` 永久保留,退款不恢复首充资格。泥点按累计退款比例计算目标回收量,全额时精确收口原 `points_delta`;自动回收只扣普通永久泥点,不动每日免费和会员周期泥点。永久泥点不足时记录 `shortfall` 并冻结正式钱包消费,流水来源为 `recharge_refund_recovery`;会员退款统一 `manual_review`,不自动猜测有效期、档位或周期泥点回滚。
- 回调与现金事实:退款回调使用独立 `/api/profile/recharge/wechat/refund-notify`,不能复用支付 `WECHAT_PAY_NOTIFY_URL`。验签、解密、契约校验和 SpacetimeDB 持久化成功后返回 `204`;现金退款已成功但本地权益不足、订单冲突或会员待复核时仍先保存事实并 ACK,只有签解密、契约或持久化失败才让微信重试。诊断日志只保存脱敏结构化摘要和稳定引用。
- 查单与账单:`WECHAT_PAY_REFUND_RECONCILIATION_ENABLED` 代码默认关闭,只有具备真实商户凭据和 runtime service identity 的 HTTP 角色可开启;生产 env 示例与 deploy 会补 `true`,真实支付显式关闭时发布失败。非终态退款按 1 / 5 / 10 / 20 / 30 分钟衰减查单;成功退款早于支付通知时只对 `order_missing / order_not_paid` 继续重试,其他人工复核不自动放行;候选列表按分钟轮转分页,失败日志不回显 provider URL。北京时间次日 10 点后按 30 个稳定分片轮转补扫微信 API 可查询的近 90 天 `bill_type=REFUND` 交易账单,每 30 分钟覆盖完整窗口。单行失败不阻塞其他行和日期,但当日不写完成 checkpoint;昨日 `NO_STATEMENT_EXIST` 至少延迟到次日 10 点后再确认。`PLATFORM-ORIGINAL / PLATFORM-BALANCE` 只用于发现商户平台退款,发现后仍必须主动查单取得当前状态;账单申请响应验签,GZIP 内容按 SHA1 验真并使用 CSV parser 和十进制定点金额解析。
- 历史与入口边界:正式落账前已经 ACK 的旧退款通知不会因升级自动重放。已知商户退款单号通过受控服务端查单后进入统一事务,未知手工退款由 T+1 账单发现;超过微信 API 近 90 天窗口的历史数据需从商户平台导出核对后逐笔受控查单,禁止直接 SQL 写退款表。当前不开放匿名或普通用户退款、补录接口,退款申请只允许受控运维或后续管理员鉴权流程。
- 影响范围:`module-runtime` 退款领域策略与钱包来源、`spacetime-module` 退款表和事务、`spacetime-client` facade、`platform-wechat` 退款 / 查单 / 交易账单协议、`api-server` 退款回调与 reconciliation worker。
- 验证方式:退款相关 `module-runtime` / `platform-wechat` / `api-server` 定向测试,`npm run check:spacetime-schema`、`npm run check:spacetime-runtime-access`、`npm run check:server-rs-ddd`、`npm run check:encoding`、`git diff --check`;真实联调后只读核对退款单、observation、订单级 settlement 和账单 checkpoint。
## 2026-07-13 后台充值退款使用钱包占用与统一用户详情
- 背景:普通 V3 退款已经能从回调、查单和账单收口现金事实,但后台主动退款若先调微信再扣泥点,会在用户余额不足时产生本可避免的欠账;后台各页面也没有统一查询用户余额、绑定状态和充值订单的入口。
- 决策:新增 `profile_recharge_refund_hold`,后台执行退款前按累计部分退款公式原子占用本次应追回的永久泥点,再使用客户端稳定 `requestId` 派生 `out_refund_no` 调微信。重试必须同时匹配原订单、退款号、退款金额、管理员和归一化原因,不能绕过底层完整幂等校验。部分退款额外占用 1 泥点并发舍入缓冲,防止占用创建后到达的外部退款跨越累计 `floor` 边界;全额退款不加缓冲。`SUCCESS` 扣款并结算匹配占用,`CLOSED` 释放,`PROCESSING / ABNORMAL` 保持;结果未知时不擅自释放,至少等待 10 分钟并连续 3 次退款查单收到官方 `RESOURCE_NOT_EXISTS` 才由 worker 释放。普通消费必须排除全部活动占用。
- 欠账与冻结:支付侧已经成功退款时不能回滚现金事实;永久泥点不足部分继续只写订单 settlement 的 `unrecovered_points`,限制普通消费,后续永久泥点优先自动偿还。每日免费和会员周期泥点不参与。人工冻结单独使用 `profile_wallet_manual_restriction`,解除人工冻结不解除退款欠账限制。
- 人工复核:交易号或订单总额冲突只允许管理员确认退款归属;管理员 DTO 与确认面板必须并排展示本地订单和微信退款事实的交易号、订单总额与冲突类型。提交请求携带界面所见 `expectedErrorCode`SpacetimeDB 事务核对当前错误码一致后,退款行才追加不可变的管理员、原因、时间和获批错误码并重新运行标准结算;已处理请求只有管理员、归一化原因和错误码全部一致时才视为幂等重放,状态变化或任一审计内容不一致继续 fail-closed。获批错误码通过正式读取契约展示,一次确认只豁免对应冲突,其他不变量继续 fail-closed。该操作不是“直接解冻”,余额不足仍形成欠账。会员退款、未知错误和非法结算计划不显示该入口,也不能复用泥点钱包冻结语义。
- 发布基线:退款功能和退款事实表尚未进入 release,首次上线只以 release 已有 schema 和数据为迁移基线;不为 master 上未发布的退款中间结构保留旧行 normalization 或历史人工复核兼容。
- 后台边界:充值订单、预检、执行、应急退款号登记、用户详情和钱包冻结均只挂在管理员鉴权路由。用户详情由 `user_id` 或陶泥号经认证服务解析,返回头像、昵称、脱敏手机号、绑定状态、钱包分桶、占用、欠账和最近订单;后台语义明确的用户字段复用同一个图标按钮和弹窗,管理员主体及 `admin:*` 合成 ID 不打开用户详情。
- 部分退款预检:微信支付查单 `trade_state=REFUND` 只表示已发生退款,不代表全额退款。刷新已登记退款后,本地累计成功退款大于 0 且小于订单总额、且不存在非终态退款、活动 hold、欠账或人工冻结时,可以继续退本地剩余额度;没有本地成功退款事实能解释 `REFUND` 时继续失败关闭并要求登记或账单对账。
- 影响范围:`module-runtime`、`spacetime-module`、`spacetime-client`、`api-server` 管理员 BFF / refund worker、`shared-contracts` 与 `apps/admin-web`。
## 2026-07-14 Jenkins Git 源收口到本机 loopback
- 背景:Jenkins controller 与 Gitea SSH 当前同机运行,live Job 的 `Pipeline script from SCM` 已使用 `127.0.0.1:2222`,但仓库 Jenkinsfile 内部 checkout 仍固定到局域网 IP,导致入口 SCM 与执行阶段来源不一致。
- 决策:所有生产 Job 的 SCM URL 和 Jenkinsfile 内部源码准备统一使用 `ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git`,继续使用 `genarrative-local-gitea-ssh`,不保留局域网 IP、HTTP 内网或公网 fallback。该决策覆盖 2026-06-19 的局域网 SSH 地址口径。
- 目标机边界:`127.0.0.1` 只允许在 Jenkins controller / Built-In Node 用于 Git。数据库导入导出与 Server-Provision 都必须在带 `linux && genarrative-build` 标签的 Built-In Node 完成 checkout 和 commit 校验,再通过 stash 把必要脚本交给 dev / release 目标 agent;目标 agent 不得自行 checkout Git 或挂载 Git SSH 凭据。
- 影响范围:生产构建、Full、数据库导入导出和 Server-Provision Jenkinsfile,生产运维文档、共享踩坑记录与生产运维静态门禁。
- 验证方式:`npm run check:production-ops`、`npm run check:encoding`、`bash -n scripts/jenkins-checkout-source.sh`、`git diff --check`;只读核对 live Job `config.xml` 的 SCM URL,并在 Jenkins 凭据环境对 loopback SSH 地址执行 `git ls-remote ... HEAD`。
## 2026-07-14 后台 Dashboard 修正访问趋势并增加新增用户留存
- 背景:Dashboard 本月范围包含未来日期,四张图可停在不同横向窗口;“访问人数”又把整个时段 UV 塞到终止日,0 值仍显示短柱。访问模块分布从 `tracking_event LIMIT 50000` 的任意截断样本计算,页面却继续展示精确值并产生置顶告警。
- 决策:本周、本月快捷范围和手动日期均不晚于北京时间今天;访问人数趋势按日去重登录用户绘制,图头与时段卡保留跨日去重 UV,0 值不绘柱,四图同步横向滚动。“当前使用人数(五分钟统计一次)”纠正为滚动口径“近 5 分钟活跃用户”。
- 聚合边界:不新增持久化表或字段;新增仅 runtime service identity 可调用的 `get_admin_dashboard_stats_and_return` procedure,在 SpacetimeDB 事务快照内聚合现有 profile、tracking 私有事实,经 `spacetime-client` facade 返回紧凑投影;素材与钱包仍走原查询。api-server 不再依赖固定 50,000 行原始明细读取来生成访问模块分布或精确统计;该权威聚合失败时 Dashboard 请求失败,不把未知统计降级成 0。
- 扩展性边界:现有表缺少覆盖跨 scope、跨日期统计的现成索引,本阶段为保持精确性仍在 procedure 内遍历相关事实并监控耗时;数据规模继续增长时改为日期前缀索引或持久化日聚合事实,不恢复固定 `LIMIT` 截断。
- 留存口径:筛选范围内 `profile_dashboard_state.created_at` 的北京时间注册日构成 cohort;在精确 `D+1` / `D+7` 存在有效登录 user scope 日聚合即留存。观察日必须早于今天;D1、D7 分别返回留存人数、可观察人数和四舍五入后的基点率,按人数加权汇总,零分母前端显示 `-`。
- 影响范围:SpacetimeDB Dashboard 聚合 procedure、`spacetime-client` facade、`/admin/api/dashboard` 与 shared contracts、`apps/admin-web` Dashboard 页面和运营文档。
- 验证方式:SpacetimeDB 聚合与 api-server 定向 Rust 测试、Dashboard Vitest、`npm run admin-web:typecheck`、`npm run check:spacetime-schema`、`npm run check:spacetime-runtime-access`、`npm run check:encoding`、`git diff --check`,并用桌面 / 移动浏览器核对留存、日期、每日 UV、零值与滚动同步。
## 2026-07-15 后台 Dashboard 增加新增用户付费率
- 背景:Dashboard 已能展示筛选时段的新增用户数和 D1 / D7 留存,但缺少同一新增 cohort 的真实付费转化指标。
- 决策:新增用户付费率的分母为筛选期内 `profile_dashboard_state.created_at` 归属的新增用户,分子为其中截至查询时至少有一笔 `profile_recharge_order.paid_at` 的去重用户;已退款仍代表曾发生付费转化,未支付订单不计。
- 聚合边界:继续扩展仅 runtime service identity 可调用的 `get_admin_dashboard_stats_and_return` procedure,不新增持久化表或字段,前端只展示 BFF 返回的付费人数、新增用户数和基点率。
- 验证方式:SpacetimeDB Dashboard 聚合测试、api-server admin 测试、Dashboard Vitest、`npm run admin-web:typecheck`、`npm run check:spacetime-schema`、`npm run check:spacetime-runtime-access`、`npm run check:encoding`、`git diff --check`,并用桌面 / 移动浏览器核对付费率卡片。
## 2026-07-14 SpacetimeDB 调用池与缓存读连接分离
- 背景:HTTP 角色原先在每个 `GENARRATIVE_SPACETIME_POOL_SIZE` 槽位首次建连时订阅同一批 read model;release 配置为 8 时会保存 8 份相同行缓存和 subscription handles,放大 api-server 内存。
- 决策:`pool_size` 条连接只承接 procedure / reducer 调用并保持无订阅;HTTP 角色额外创建且只创建 1 条共享缓存读连接,全部 `read_after_connect` 读取统一路由到该连接。缓存连接只在应用 facade 中作为只读用途,不宣称 SDK 或 identity 具备连接级只读权限。
- 并发与恢复:缓存读连接通过 `Arc` 共享,读取不占用调用池 permit,也不使用单槽租约串行化;只有首次初始化和 broken 后重建使用单飞锁。首次建连与全部订阅共用一次总超时预算;required subscriptions 全部 applied、optional 阶段连接仍未 broken 后才发布新连接,旧连接由在途读取自然释放。
- 就绪边界:HTTP `/readyz` 同时验证调用池握手和缓存读连接;required subscription 失败必须不就绪。非 HTTP worker / controller 不创建缓存读连接,继续使用 1 条调用连接和各自的队列窄订阅。
- 运维口径:`GENARRATIVE_SPACETIME_POOL_SIZE=8` 表示 8 条调用连接,HTTP 基础拓扑另加 1 条缓存读连接;外部生成和充值过期监听的独立窄订阅不计入该值。读模型行缓存从 8 份降为 1 份,但 SDK 空 table metadata、8 条调用 socket 和 runner 仍存在,不承诺总 RSS 等比例降为八分之一。
- 验证方式:`cargo test -p spacetime-client --manifest-path server-rs/Cargo.toml --lib`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`;发布后在 8 个调用槽暖机后对比 api-server cgroup memory / PSS,并确认 `/readyz` 与代表性 gallery、公开详情、创作入口和用户标签读取正常。
## 2026-07-17 主站与 AI 游戏创作客户端复用微信 Native 充值生命周期
- 背景:主站个人中心已具备微信 Native 二维码确认重试和 SSE 自动到账监听,AI 游戏创作客户端又在大型 `App.tsx` 中维护一套简化充值状态,关闭、迟到响应和终态语义容易继续分叉。
- 决策:`packages/shared` 的充值组件目录新增宿主无关的 `useWechatNativeRechargeController`,通过注入确认、监听和余额快照回调统一管理二维码校验、手动确认重试、SSE 监听、终态映射和 lifecycle 隔离。主站只把 Native 分支委托给共享 controller,H5、JSAPI、小程序、登录恢复、任务和邀请码仍留在原 controller;AI 游戏创作客户端通过本地 `useRechargeController` 托管弹窗加载与固定 `wechat_native` 下单,`App.tsx` 只负责视图接线。
- 余额边界:AI 游戏创作客户端的 `useWalletStore.mudPointBalance` 仍是唯一余额真相;充值响应只把后端完整快照写入 store,支付成功后触发完整刷新,不在客户端本地推算或增减泥点。
- 验证方式:共享 hook Vitest、AI 游戏创作客户端充值与 Wallet Store 定向测试、主站充值渠道定向测试、两个 TypeScript 边界、`npm run check:encoding` 和 `git diff --check`。
## 2026-07-17 Project Supervisor 协作合同由 Runtime 强制执行
- 背景:V1.31 已真实证明同一父 run 可以组合 static delegate 与 isolated all-join,但模型仍可能漏掉某一类协作、只提交一个 static delegate,或在委派后由 Supervisor 自己执行项目修改。重复采样和继续堆 prompt 不能作为可靠性门禁。
- 决策:新增独立项目控制面 `.agent/collaboration-policy.json`,声明首波 `auto / static / isolated / mixed`、最少 static delegate、required static Agent、最少 isolated child 和委派后总控只编排开关。缺失 sidecar 时不强制特定协作拓扑,但默认在当前父 run 形成任何 delivery/group 后禁止 Supervisor 直接修改项目。
- 原子性:Supervisor 首波协作复用 Provider action batch 的整批预检;策略不满足或协作批次混入总控项目 mutation 时,任何 pending、确认、delivery、group、child、revision 和项目写入发生前整批返回 blocked observation。通过时 batch v2 固化策略与动作合同指纹,恢复时重新校验策略漂移。
- 恢复顺序:batch 成员必须在委派或 spawn 副作用前持久化为 `executing`;恢复、确认和 replay 必须先校验当前策略、完整协作合同、batchId 与 action 身份。策略漂移或旧协作 batch 缺少合同只能进入 `needs-reconciliation`,不得重放 child 副作用。isolated 最低 child 数量按单一 durable group 计算,不能拼接多个不足最低数量的小 group。
- 覆盖说明:上条关于“恢复时重验当前策略、live policy 漂移即 reconciliation”的部分自 V1.38 起不再是现行口径。V1.32 的其它 batch/contract/action 身份与副作用前门禁继续有效;现行策略选择、漂移和恢复顺序以本文件 V1.38 决策为准。
- 控制面边界:`.agent/collaboration-policy.json` 对 Agent 通用文件工具隐藏并拒绝写入;委派后的 Supervisor 只允许严格只读 MCP,注解不完整或 destructive MCP 失败关闭。`project.git_commit` 与 `canvas.asset_generate` 在取得项目锁后再次读取 durable 协作事实,堵住 dispatch 首检后的并发落盘窗口。
- 完成边界:finalization 只认可同一父 run 的 durable static delivery 和 isolated group。repair delegate、合法 isolated 检查、读取、状态查询和项目验证继续允许;源码写入、patch/restore、Git commit、命令启动及平台素材生成由专业 Agent 承担。
- 验证方式:运行 `supervisor_collaboration_`、`provider_action_batch_`、`project_supervisor_mixed_` 定向 Rust 回归,随后执行编码检查和 `git diff --check`;真实 Provider V1.32 必须在最终代码 diff 上独立完成,不能复用 V1.31 报告。
- 真实验收:2026-07-17 使用 `gpt-5.5 / openai_chat / high` 完成 `supervisor-swarm-collaboration-policy-mixed-recovery` 最终代码独立 PASS。隔离 AppData 副本启用 `maxRetries=2`,正式 AppData 与 Runner endpoint 保持未修改;86 个 Provider lifecycle 全部完成,本轮未触发重试。单一父 Session/run 完成首批 2 个 static delegate + 1 个三 child isolated group、Runner pidfd 强杀恢复、1 次 repair、3 次 delivery 认领、宿主验证和唯一最终回复;重复 delivery/group/instance/result/join/claim/message/action/receipt/lifecycle、残留 sidecar、私密正文、API Key、项目路径与正式配置路径泄漏均为 0。报告同时暴露 49 次 native tool plan 中有 30 次格式修复,作为后续性能与提示合同收敛风险保留。
## 2026-07-18 Agent 原生工具计划 repair 使用稳定分类与受限归一化
- 背景:V1.32 虽然完整 PASS,但 49 次成功 native tool plan 伴随 30 次格式修复;原有审计只有错误哈希和字符数,无法判断是正文混入、arguments JSON、schema 还是批次语义导致,也无法在失败 partial report 中比较分布。
- 兼容边界:`platform-llm` 排除明确 reasoning/analysis content partAgent 移除完整、嵌套闭合的 `<think>...</think>`。只有不含 `respond_to_user` 和旧 wrapper 的 native planning 响应可把剩余正文按 `planner-commentary` 归一化,并继续以 function calls 为权威动作;最终用户回复、legacy wrapper、未闭合或错配 thinking 标签继续失败关闭。
- 协议分类:固定使用 `response-shape / call-identity / unknown-function / arguments-json / arguments-schema / batch-constraint / plan-semantics / catalog-binding`。JSON 先递归拒绝顶层和任意嵌套 input 的重复 object key,再做 schema 解析;前七类按现有上限进入格式修复,目录 binding 冲突直接失败且成功报告中必须为 0。控制流不再从中文错误字符串反推类别。
- 审计与报告:repair 只新增 `protocolErrorKind`;成功归一化只保存固定 `complete-think-block / planner-commentary` kind、数量、字符数和 SHA-256。真实 E2E 报告增加 repaired loop、second repair 和固定补零直方图,完整与 partial 证据复用同一选择器和聚合器,分类总和必须闭合且不得携带原始错误、正文、arguments、preview 或单条身份;白名单必须包含 Agent DB 固有 `schemaVersion / updatedAt`,不能把安全 envelope 误报成正文泄漏。
- Prompt:原生 function arguments 的统一外壳明确为 `reason + input`legacy text JSON schema 只属于没有 function tools 的 Provider,避免工具自己的 input schema 与旧 actions JSON 示例互相竞争。
- 诊断过程:首轮旧保守正文规则得到 35 个成功计划、23 次 `response-shape` repair,且因 E2E 白名单遗漏 Agent DB envelope 误报 58 条泄漏而 FAIL;第二次新规则尝试在 2 个计划、0 repair 时因 Provider isolated write scope 不满足 fixture 提前停止。两轮都不作为完成证据。
- 真实验收:最终代码对应的正式 `gpt-5.5 / openai_chat / high` 同 suite 独立 PASS。46/46 个成功计划全部使用 `native_runtime_tools`,格式 repair 和八类直方图均为 0Provider lifecycle 为 54/54 started/terminal,其中 53 completed、1 次瞬态失败通过新 request identity 显式重试恢复,相比 V1.32 的 86 减少 32,总耗时 `631.6s`。static + isolated 混合协作、业务 delivery repair、Provider 真并行、pidfd Runner 强杀恢复、宿主验证和唯一 Supervisor assistant 全部成立;重复、残留 sidecar、正文、Key、项目 / 正式配置路径与报告泄漏均为 0,隔离现场完整清理。
## 2026-07-18 AI 游戏创作 Agent Runtime V1.34 动态隔离子 Agent writeScopes 命令绕过封堵
- 背景:动态 isolated child 的 `writeScopes` 只约束结构化 file/patchset 路径;现有 V1.11 OS sandbox 仍把项目根整体挂为可写。若 child 继承 `project.verify`、通用命令、持久进程或预览启动,shell、构建 hook 和后代进程可以绕过路径校验写到 scope 外。approval 不能替代 OS 级作用域隔离。
- 决策:在 scope-aware OS sandbox 完成前,动态 child 无条件禁用 `project.verify / project.git_commit / command.exec / command.start / command.stdin / preview.start / agent.delegate / agent.spawn_isolated / project.restore / agent.schedule_ready / canvas.asset_generate / task.create / task.update / blackboard.write` 和全部 MCP 动态函数/兼容调用。有效策略快照把对应内置工具和 `mcp.call` 显示为 `denied`;动态 MCP function 归一后执行同一拒绝。模板 Agent policy、项目 policy、legacy 快照和用户 approval 均不能放宽。
- 保留边界:继续允许固定只读且不接受任意 program/argv/shell 的 `command.run_limited`,同 child/run 身份的 `command.output_read / command.poll / command.terminate`,只验证既有精确 loopback 预览的 `preview.validate`,以及目标完整位于有效 `writeScopes` 内的 `file.write / file.patch / file.delete / project.patchset`。多文件变更含一个越界目标即在 checkpoint、revision 和真实写入前整组拒绝;通用验证交由父 Agent 或静态专业 Agent 完成。
- 原子与恢复:新单动作在 confirmation 与 OS launcher 前拒绝,不产生 spawn、revision 或项目副作用。新多 action batch 在选择 confirmation 模式前逐项校验,任一 denied member 使整批 abort,允许成员也不执行;只保留 `aborted / nextActionIndex=0` batch 事实,不发布独立 pending sidecar。旧 pending、approval 与旧 batch 真正进入执行器时仍重验当前边界;旧 executing 未知结果继续按既有 reconciliation 规则处理,绝不 replay。
- 验证方式:新增恶意 `bash -lc` sibling 写入回归,覆盖单动作、两动作 batch、策略快照和旧 executing pending 的执行器重验;断言 sibling 文件、nested delivery、独立 pending sidecar 和 revision 变化均为 0。工具作用域单测逐项覆盖拒绝集合与保留工具;同时运行 isolated 30 项、mixed 3 项、Supervisor collaboration 27 项、Provider batch 12 项和 Tauri 全量回归。
- 真实验收边界:V1.31/V1.32 已以 isolated mutation 为 0 的真实 Provider suite 证明 mixed 协作、all-join、Runner 恢复和唯一回复;V1.34 只做安全收紧,本切片不为此重跑两套 Provider,也不能把旧 PASS 当作未来新 child 写入语义的证据。只有后续 scope-aware OS sandbox 能把有效 `writeScopes` 变成项目根其余部分只读、链接/挂载不可逃逸且所有后代继承的强制边界,并通过独立跨平台门禁后,才可在新决策中重新评估命令工具;其余拒绝能力仍需各自单独评审。
## 2026-07-18 AI 游戏创作 Agent Runtime V1.35 多 ready isolated all-join 原子认领
- 背景:同一父 run 的一次 `agent.run_status` 可以同时看到多个 ready isolated all-join。若逐个取得锁并立即改写 delivery,后一个 join 锁竞争会让前一个 group 留在部分认领状态,破坏整次 action 的可恢复原子边界。
- 锁边界:先按 `delegationGroupId` 去重排序,再按该顺序一次性预取全部 join delivery 锁;全部锁就绪前不得创建 claim sidecar 或改写 delivery。任一后续 join 锁忙时释放已取得的锁,并保证零 delivery mutation、零 claim sidecar。
- 持久恢复:全锁就绪后,同一 action 使用一个 durable claim journal,按 `prepared -> committed -> observed` 单向推进。发生部分 commit 或 Runner 退出时,恢复必须复用同一 action journal、按相同顺序幂等补齐未提交 group,不创建新 action、新 journal 或重复 delivery claim。
- Observation 与完成:只认领可完整放入本轮 `readyIsolatedJoins` 观察预算的有序前缀,该区块固定置于 `agent.run_status` detail 首部;剩余 group 保持 ready,不能把已认领结果截断后让模型猜测。只有成功 observation 已持久写入 pending sidecar 后才能标记 `observed`;任一未观察 claim 都继续阻断 finalization。每个 group 的审计以 `actionId + delegationGroupId` 唯一,恢复只补缺失记录,不重复追加。
- 旧状态恢复:每个 `claimed-by-parent` delivery 必须被同一 `claimedByActionId + delegationGroupId` 的 journal 覆盖,无 journal delivery 继续阻断完成。`agent.run_status` 先重放已有未观察 claim;随后每轮只为一个稳定排序的旧 action 合成 journal 并完整输出,恢复 action 不取得 delivery。原 action 已有 journal 但遗漏 group 时不得扩写或倒退状态,同一 group 归属其他 action journal 时按身份冲突失败关闭;pending observation 只能标记本轮完整输出的 claim。
- 审计恢复:isolated group 审计通过 Agent DB 专用锁内幂等入口追加;同一锁内先修复 JSONL 截断尾行,再从文件头扫描有效数据库的完整记录范围,以 `recordType + actionId + delegationGroupId` 核对完整 payload。重复键、内容冲突或物理容量越界均失败关闭。
- Mixed 恢复:isolated claim 已提交、同一 `run_status` 后续 static receipt 认领失败时,下一 action 先完整重放旧 isolated claim,再继续 static 认领;旧 delivery/journal 仍绑定原 action,不产生第二份 isolated claim。恢复 observation 成功持久化后才能把旧 claim 标为 `observed`。
- 定向验收:覆盖后一个 join 锁冲突、mixed static 锁失败后新 action 重放 isolated 结果、Agent DB torn tail 后 prepared/partial claim 恢复、多旧 action 逐轮迁移、已有 journal 单调性与跨 action group 归属冲突;完整 observation 必须实际包含被标记 observed 的全部 group。`isolated` 36/36、`project_supervisor` 42/42、`supervisor_collaboration` 27/27、`provider_action_batch` 12/12 已通过,Tauri/Rust 全量为 915 passed、4 个环境依赖用例按设计 ignored。这些本地结果本身不替代真实 Provider 证据。
- 真实验收:2026-07-18 后续真实 Provider E2E **PASS**。同一父 Session/run 的初始 isolated all-join group 包含 2 个 child;首次 `parent-wake` 后、任何 join claim 前创建的 follow-up group 包含 1 个 child。两组的精确 `writeScopes` 集合互不重叠,一个状态为 `observed` 的 join claim journal 同时覆盖两个 groupRunner 强杀/恢复身份稳定。Provider lifecycle `53/53` 全部 completed、failed 为 `0`,重复、泄漏与残留均为 `0`。V1.35 的外部模型链路据此完成验收。
- 保留边界:V1.35 不等于 V1.34 的 scope-aware OS sandbox 已完成;后者仍未完成,V1.34 的动态 isolated child 工具禁用边界继续有效。本轮多 group PASS 证明当前业务合同与 Supervisor 提示能形成该分阶段轨迹,不等于 Runtime 能预知尚未生效的项目检查并通用禁止提前 `agent.run_status`;需要产品级强制阶段时应先扩展 collaboration policy 契约。本轮 PASS 也不替代 V1.36 的 static + isolated 混合 observation 完整性独立门禁。
## 2026-07-18 AI 游戏创作 Agent Runtime V1.36 混合协作 observation 完整性
- 背景:静态 delegate claim sidecar 可容纳远大于 Provider 单轮观察窗口的内容,而旧 `agent.run_status` 在最终 detail 超过 16000 字符时直接截断。多份静态回执或 static + isolated 混合返回可能因此只把部分 JSON 交给模型,却把整个 durable claim 标为 `Observed`。
- 预算:`readyDelegateReceipts` 完整 JSON 单批上限为 6000 字符;`readyIsolatedJoins` 在 isolated-only 时保持 10000 字符,在同轮可能携带静态回执时使用 6000 字符。普通 Runtime 状态、claimed join 和 claimed contract 摘要合计最多 3500 字符。最终 detail 仍以 16000 字符为硬上限,清洗后超限直接返回 failed,禁止截断任一 ready 证据区块。
- 静态分批:先完整保留当前 action 已绑定的 recovery receipts,再按 `delegationId` 为新 ready delivery 选择稳定前缀;只为最终选择的批次预取 delivery 锁,并在锁内重读核对预算选择快照。未选中的后续 delivery 保持 `Ready`,其锁竞争不得阻断必选恢复;必选集合本身无法放入预算时,必须在写 claim journal 和改写 delivery 前失败关闭。
- 精确观察:pending observation 从前置 `readyDelegateReceipts` 区块解析唯一 delegationId 集合,并与该 action durable claim 的 receipt 集合做精确相等比较;前置区块还必须唯一且显式为 `ready=true`。缺失、额外、重复、false 或无法解析的 ID 都不得推进 `Committed -> Observed`,未观察 claim 继续阻断 finalization。`readyIsolatedJoins` 继续按完整 group 集合执行同类门禁。
- 恢复顺序:预算提示只读取 delivery 状态,不提交 Prepared claim,也不改变旧恢复时序。mixed `run_status` 仍先认领 isolated join,再认领 static receiptstatic 锁或持久化失败后,后续 action 必须完整重放原 isolated claim,再继续静态认领。
- 验收边界:确定性回归覆盖默认 6000 字符下单份合法静态回执超预算零 mutation、稳定前缀留下后续 ready delivery、缺失 journal 的必选回执不受未选中 delivery 锁竞争影响、部分 delegationId 不能标记 observed、完整精确集合才能清除 barrier、重复/false 前置区块失败关闭,以及 mixed ready static / isolated JSON 不被静默截断。`project_supervisor` 46/46、mixed 5/5、`isolated` 37/37、`supervisor_collaboration` 27/27、`provider_action_batch` 12/12 均通过,Tauri/Rust 全量为 923 passed、4 个环境依赖用例按设计 ignored。本切片未重跑真实 Provider suite,不把 V1.31-V1.33 的既有 PASS 当作 V1.36 新协议证据。
## 2026-07-18 AI 游戏创作 Agent Runtime V1.37 分阶段 isolated group 首次认领硬门禁
- 背景:V1.35 的真实 Provider 已形成“首批 1 个 group、首次 claim 前再补 1 个 group”的正确轨迹,但该顺序仍依赖任务合同和提示,Runtime 没有通用硬门禁。
- Policy 兼容:collaboration policy v1 新增可选 `minIsolatedGroupsBeforeClaim`,默认 `0`、上限 `16`,零值序列化省略,以保持旧 policy / contract fingerprint 不变。initial preflight / contract 只检查既有首波要求,允许首批 1 个 groupfinalization completion 额外要求同一父 run 的 group 总数达到 policy。
- 首次认领:首次新 claim 在选定可完整输出的 ready group 批次后,必须同时确认已建立 group 数和 ready group 数达到 policy;不足时在 claim journal 和 delivery mutation 前失败关闭。已有 durable claim、未观察 claim 与 legacy claim 的恢复优先,继续按原身份重放,不被升级门禁卡死。
- Scope 边界:只读 isolated task 也必须声明 expected artifact 的最小目录 scope,不得扩大到 sibling scope 或共同父目录;该约束写入通用边界提示,不为单个验收任务硬编码。
- 定向验收:`supervisor_collaboration_policy_` 23/23、`project_supervisor_` 47/47 通过,E2E self-test **PASS**Tauri/Rust 全量为 930 passed、4 个环境依赖用例按设计 ignored。
- 真实 Provider:第一次独立运行因模型初始 child scope 不符合 expected artifact 最小边界而 **FAIL**child / claim / project mutation 均为 `0` 且现场自动清理,不与后续证据拼接。补强通用边界提示后的第二次独立运行 **PASS**policy=`2`2 个 group / 3 个 child1 个 `observed` join claim 覆盖 2 个 groupRunner 强杀恢复身份稳定,Provider lifecycle `64/64` completed、failed=`0`,重复、残留、泄漏均为 `0`,最终回复唯一。
## 2026-07-18 AI 游戏创作 Agent Runtime V1.38 父 run 协作策略持久快照与绑定记录
- 决策:首个非 `aborted`、携带 v2 `collaborationContract` 的 durable collaboration batch 是父 run 策略线性化点。Runtime 必须按 `v2 batch -> snapshot -> binding sidecar -> action side effects` 持久化:snapshot 位于 `.agent/runtime/collaboration-policy-snapshots/<agentKey>/<runKey>.json`,独立 binding 位于 `.agent/runtime/collaboration-policy-snapshot-bindings/<agentKey>/<runKey>.json`,用于持久证明“该 run 曾绑定”。没有既存 binding 的首次 `aborted` batch 不创建两类 sidecar;若 binding 已证明该 run 先前完成绑定,snapshot 丢失时可用完整验真的 matching v2 contract 恢复原快照,即使当前保留的 batch 为 `aborted`,这不构成新绑定。batch -> snapshot 与 snapshot -> binding 都是零副作用可恢复窗口。
- 数据契约:snapshot v1 固定且完整包含 `schemaVersion / projectId / parentAgentId / parentRunId / boundFrom / policy / policyFingerprint / snapshotFingerprint / boundAt`policy 先规范化,snapshot fingerprint 绑定除 `snapshotFingerprint / boundAt` 外的全部稳定字段。binding v1 固定包含 `schemaVersion / projectId / parentAgentId / parentRunId / boundFrom / policyFingerprint / snapshotFingerprint / boundAt`,必须与 snapshot 的绑定身份逐字段一致。两者按 project/parent run 共用锁并在锁内 CAS,冲突失败关闭,禁止通用 replace 覆盖。
- 路径身份:安全 Agent/run ID 可原样作为 `agentKey/runKey`;任何不安全或规范化后变化的 ID 必须使用有界安全前缀加原始完整 ID 的稳定 SHA-256,不能让 lossy 字符替换制造路径碰撞。锁 key 固定对完整 `parentAgentId + NUL + parentRunId` 计算稳定 SHA-256,不复用路径规范化结果。
- 恢复:优先级固定为 existing valid snapshot > 完整验真的 v2 batch contract > 符合严格状态门禁的 legacy 当前有效 policy。正常绑定使用 `boundFrom=initial-collaboration-batch`v2 contract 必须先独立校验 batch/project/action/contract 全身份与两层指纹,才能以 `boundFrom=legacy-provider-batch-contract` 补绑。snapshot 存在但 binding 缺失时从 snapshot 补写;binding 存在但 snapshot 丢失时只按 matching binding 与可信 v2 contract 恢复,没有可信 v2 contract 时禁止按 live policy 重绑。
- Legacy 与旧 batchcontractless/v1 collaboration batch 必须在任何 live policy 回退前失败关闭并进入 reconciliation,不能忽略旧 batch 后把已有 run 当成 fresh run。`boundFrom=legacy-current-project-policy` 仅允许无 snapshot/binding、无可信 v2 contract,且不存在上述旧 batch,并由 durable run 身份与状态明确证明属于 `pending / running / waiting-for-confirmation / waiting-for-user-input` 的旧父 runterminal、`needs-reconciliation` 或身份/状态未知 run 的状态读取不得新建 snapshot。没有任何 durable run/collaboration 事实的真正新父 run只能读取 live policy 构造首个 v2 contract,不创建 legacy snapshot。
- 漂移:snapshot 绑定后,后续 spawn、repair、新 claim、Supervisor mutation、MCP、prompt/status、completion/finalization 与恢复全部使用 snapshot。global policy 的 `matched / drifted / unreadable` 只作有界状态报告,不改变执行、revision、verification 或 reconciliation;已有 run 不重绑,新 policy 只由后续新父 run 采用。
- Claim 兼容:旧 durable claim、未观察 claim 和 legacy claimed delivery 的恢复先于 effective snapshot 解析及新 claim 门禁,继续按原 action/group 身份推进且不得取得新 deliveryglobal policy、snapshot 或 binding 故障不能把已提交 claim 卡死。新的 claim 必须先成功解析 effective snapshot 并核对 binding,失败发生在 journal、delivery 锁和 mutation 之前;随后仍执行 V1.35-V1.37 的全锁、预算、完整 observation 和 group 数量门禁。
- 覆盖与验收:本决策明确覆盖 V1.32 的 live drift reconciliation 旧口径,但不把 V1.32/V1.35/V1.37 历史 PASS 外推为 V1.38 证据。2026-07-19 self-test、snapshot/binding 双故障窗口与 CAS/丢失/篡改/旧 batch/危险 ID/claim 分流确定性覆盖、`supervisor_collaboration_` 52/52、`provider_action_batch_` 12/12、`project_supervisor_mixed_` 5/5 和 Tauri/Rust 全量 949 passed/4 ignored 已通过;终态 Runtime 清理后 snapshot/binding 保持原字节并继续解析为 `run-snapshot`。真实 mixed-swarm 独立功能样本已形成 2 group/3 child、policy drift、Runner 恢复、唯一最终回复和零重复/泄漏,但同轮正式 endpoint 被外部客户端重启;改用私有配置源后多轮又耗尽 transient Provider retry,最后在 `300000ms / maxRetries=3` 下于首批业务动作前形成 4 failed/3 retry 并干净终止。两类失败证据不得拼接,当前**仍不得声称 V1.38 真实 E2E 已 PASS**。
## 2026-07-19 AI 游戏创作 Agent Runtime V1.39 首次规划 Provider 持久重试
- 决策:tool-plan 首次请求及其自动 context-compaction 的可重试瞬态失败不再依赖 Runner 进程内 sleep。每个 Agent/run 使用唯一严格 v1 retry sidecar,持久绑定项目、Agent、task、Session、run、Goal、steer、请求和 Provider 配置指纹,以及下一个 `-transient-N` attempt 和绝对到期时间;一次只允许一个待重试 attempt。
- 提交与恢复:每次物理请求仍先闭合自己的 Provider lifecycle;随后按 retry audit -> 原子 sidecar/.previous -> `running / waiting-for-provider-retry` 投影提交,再释放 lane。Runner 启动扫描 primary/.previous,未到期只重建唤醒,到期后重验 durable control 和完整指纹并恢复同一 Session/run/loop/attempt。sidecar/torn projection 冲突失败关闭,不从 Agent DB 或 UI 状态猜回请求。
- 调度:等待态释放执行 lane,允许不同 Agent 并行;同 Agent 当前 running 等待任务继续阻挡后续 pending task,保持 FIFO。活跃 sidecar 阻断 finalization 与 `shutdown_if_idle`cancel、steer、终态和耗尽负责清理。
- 稳定身份:Provider 请求指纹不得包含工具策略 `updatedAt` 等非语义刷新时间。Goal pause 保留 sidecarresume 恢复原等待态;跨秒恢复必须仍命中相同 request fingerprint 和 `-transient-N` slot。final-reply、手动压缩与 tool-plan `repair-N` 继续使用进程内重试,待具备可无歧义重建的持久请求上下文后再单独升级。
- 证据边界:`provider_retry_` 19/19、`provider_transient_retry_` 6/6Tauri/Rust 串行全量 968 passed/4 ignored`cargo check`、rustfmt、编码与 diff 检查通过。本轮确定性验证与 V1.38 真实 Provider E2E 分开记账;任何旧失败轮、旧瞬态重试 PASS 或最小探针都不能拼接成 V1.39 真实 PASS。
- 真实验收:最终代码的独立 `gpt-5.5 / openai_chat / high` suite 以 Project Supervisor 首次 tool-plan 为受控故障目标,在子 Agent 请求产生前进入 `30s` 持久退避并执行一次 pidfd Runner 强杀。新 boot 恢复后 sidecar identity/字节/attempt/slot/retryAt 不变,强杀前、重启后、到期前请求数均为 1,metadata-only 代理证明第二个请求网络接收时间不早于 retryAt;随后同一父 run 由 static collaboration policy 强制同批两个指定专业 Agent,完整完成真重叠、2+1 delivery、唯一 repair、宿主验证和唯一最终回复。最终 `37 started / 37 terminal / 36 completed / 1 injected failed / 1 retry`incidental failure/retry 均为 0;重复、残留 sidecar、正文、Key、项目/正式配置路径泄漏均为 0,隔离现场完整清理,单轮耗时 `891.1s`。验收器按 request identity 分开统计受控注入与额外真实瞬态失败,额外失败仍须逐条通过原 lifecycle/retry/后继终态门禁且 failure/retry 计数相等;此前各失败样本不得与该 PASS 拼接。
## 2026-07-19 AI 游戏创作 Agent Runtime V1.40 最终回复 Provider 持久重试
- 决策:后台 `final-reply` 及其前置自动 context-compaction 分别以 `requestKind=final-reply / final-reply-context-compaction` 接入 V1.39 的 per-Agent/run retry sidecar。瞬态失败后不再在 Runner 进程内 sleep;物理 lifecycle 闭合、retry audit、sidecar 和 `waiting-for-provider-retry` 投影沿用原提交顺序并释放 laneAgent DB lifecycle 校验同步接受这两个独立种类。
- 可重建输入:final-reply prompt 不再包含随恢复阶段变化的 Runtime 投影,也不序列化 context bundle 无法无损保存的临时 tool-plan 结构。Provider 输入只使用稳定任务/Goal/steer/仓库与会话上下文、已获准 observation,以及按 context bundle 同一脱敏和截断规则生成的 `thinkingSummary / fallbackResponse` 收束摘要;retry sidecar 不新增任何 prompt、正文、工具输入或凭据字段。
- 恢复:进入 Provider 前先同步 response 阶段计划投影与 context bundle。Runner 重启后即使公共 state 先投影 planning,执行 pass 也先按当前 run sidecar 识别 `requestKind=final-reply / final-reply-context-compaction`,恢复原 `nextLoopIndex` 并跳过新的 tool-plan;后者先恢复同一压缩请求,再继续原 final-reply。重建请求或 Provider/Goal/steer/revision 身份漂移时删除旧 sidecar,只记录漂移字段名并在同一 run 重新规划,不提交旧回复。
- 流式与终态:失败 attempt 的半句只进入 failed/discarded response-stream,恢复 attempt 沿原基础 stream 身份推进;唯一 canonical assistant 仍只由 finalization journal 提交。确定性测试覆盖 sidecar-first 投影窗口、到期前零请求、失败/恢复 HTTP body 逐字节一致、无重复 tool-plan、唯一 assistant/completed/committed stream 和终局零 retry sidecar。
- 证据边界:当前 `provider_retry_` 21/21、`provider_transient_retry_` 5/5、`response_stream_` 16/16Tauri/Rust 串行全量 `969 passed / 4 ignored`。尚未完成真实外部 Provider 的 final-reply 退避期 Runner 强杀,因此不能复用 V1.39 首次 tool-plan 的真实 PASS;手动压缩和 tool-plan `repair-N` 继续保持进程内重试。Provider 成功返回到压缩 sidecar 或 finalization journal `prepared` 之间仍有崩溃窗口,重启可能重新请求 Providerfinalization journal 清理到 stream committed 之间也不是可恢复事务。本切片只保证失败重试与最终 assistant 幂等,不能声称成功请求 exactly-once 或 stream 终态事务已经完成。
## 2026-07-20 AI 游戏创作 Agent Runtime V1.41 Provider 成功交接与回复流终态恢复
- 持久所有权:`game-creator-provider-handoff.v1` 是 Provider 成功返回与消费端 durable commit 之间的私有交接记录,每个 Agent/run 最多一条。只允许无 tool call 的 `context-compaction / final-reply-context-compaction / final-reply`,绑定完整 retry identity、真实 request slot/attempt、真实 Provider lifecycle requestId、规范化文本响应及其指纹;不保存 prompt、请求消息、API Key、Provider URL、tool call/arguments 或错误正文。
- 提交顺序:物理 Provider 成功后先去 thinking、按既有规则脱敏并原子写入 handoff,再回读逐字段完全一致,之后才允许为 handoff 中保存的真实 requestId 写 `completed` lifecycle。handoff 成为 durable owner 后,恢复先补齐同一 requestId 的 `started -> completed`,再零网络回放响应;不得生成新 requestId、把真实成功记到 base requestId,或在 handoff 未回读成功时宣称 lifecycle completed。
- 所有权转移:上下文压缩必须先把规范 compaction sidecar 原子写入并回读一致,才可删除匹配 handoff;final-reply 先把回复转入 finalization journal `prepared`,随后由 journal 持有 assistant、Runtime/Goal 终态和 response stream 提交责任。终局清理不得早于下一 durable owner 建立;取消、steer、Goal 作废、失败或其它终态也必须按完整身份清理所属 handoff。
- 冲突与漂移:同一 run 的 handoff 与 retry identity/attempt/slot 完全一致时,以 handoff 为成功事实并清理 retry;任一字段冲突必须在网络调用前进入 `needs-reconciliation`,保留两份 sidecar 和真实 requestId 供核对。相同 durable run 的 Goal/steer/request/config 等身份漂移,先按 handoff 的旧真实 requestId 补齐 lifecycle,再只记录漂移字段名、删除旧 handoff/retry 并回到同 run planning;响应正文不得进入公共审计。跨 run 身份冲突直接失败关闭。
- 回复流事务:finalization journal 升级为 v4,并固定保存 `responseRequestSlot / responseSteerCursor / responseRevision / response`。assistant 与 Runtime/Goal 已幂等完成后,journal 仍必须保留到匹配 response stream 写成 `committed` 且立即回读身份、状态和正文完全一致;之后才可删除 journal 和剩余 handoff。提交使用 journal 的固定 Agent/task/Session/run/request slot/steer cursor/revision,禁止调用面向 UI 的可见性过滤或用当前全局 project revision 静默跳过既定 run 的 stream。
- 回复流恢复:stream 缺失或仍为 `streaming` 时,可用 journal 固定身份和正文重建 `ready` 后提交;已 `committed` 且正文一致时按幂等成功继续清理。既有 stream 身份冲突、ready/committed 正文冲突、写入失败或回读失败都必须保留 journal 并保持可恢复 finalization,不能删除证据、覆盖冲突正文或把 finalization 当成已经清理。
- Runner 与边界:primary、`.previous` 或损坏的 handoff 都使所属 root 保持 busy,并阻止 `runner.shutdown_if_idle`。handoff 原子提交并回读前强杀 Runner,仍可能留下 Provider 已成功但本地只有未闭合 `started` 的未知窗口;Runtime 只能失败关闭,V1.41 不因此承诺端到端 exactly-once。`tool-plan` 和 function arguments 明确不在本协议内,真实外部 Provider 的 final-reply 退避期 Runner 强杀仍需独立 E2E 后才能记 PASS。
## 2026-07-20 AI 游戏创作 Agent Runtime V1.42 Project Supervisor final-reply 瞬时重试 Runner 强杀真实门禁
- 决策:不修改生产 Runtime、retry、handoff、finalization 或 response stream 协议,只扩展一次性 loopback fault proxy 与真实 E2E harness。新 suite 为 `supervisor-swarm-final-reply-transient-retry`;旧 `supervisor-swarm-transient-retry` 保持首次 tool-plan 故障语义,只证明该边界,不能替代新 suite。
- 选择器隐私:proxy 的异步 selector 只能收到冻结的 `sequence / acceptedAtMs`,不得收到或保存 URL、header、body、API Key 或凭据。harness 只能选择当前 `project-supervisor` 同一父 Session/run 的唯一 base `final-reply` started lifecycle;不得命中 transient 后继、专业 Agent/child、tool-plan 或 compaction。
- 命中前门禁:持久 delivery 必须精确为 `2` 条初始加 `1` 条 repair,三者均已由父 Agent claim;两次 `observed` claim 必须完整覆盖 `3` 条 receipt,父 assistant 仍为 `0`。随后 selector 必须在 proxy reset/forward 目标请求前,以可信宿主 Node 在 disposable project cwd 同步运行固定的 `node verify-e2e.mjs`;只有 `real-e2e-command=passed` marker 成功且无失败 marker 才允许注入。失败、超时、非零退出或 marker 无效均不注入,捕获的 stdout/stderr 不得进入 selector state、checkpoint、report 或公共日志。父 run 的 `project.verify` audit/receipt/observation 计数与顺序仅作诊断,不是注入前提。
- 强杀与恢复:base final-reply 形成唯一 failed lifecycle、retry audit、持久 retry sidecar 和 `running / waiting-for-provider-retry` 后,在 `30s` backoff 内对 suite 自有 Runner 执行 pidfd `SIGKILL`。新 boot 保持 Session/run/task/request fingerprint/attempt/next slot/sidecar 字节/retryAt;重启后及到期前零新增请求,到期后只出现唯一 `<base>-transient-1`,网络 `acceptedAtMs` 不得早于 `retryAtMs`。
- 终态等待:task/Runtime 到达终态不等于 durable 清理已经完成。验收器必须在终态后继续显式等待相关 sidecar 全部清零,并设置 `10s` 硬超时;超时或仍有残留即判该轮失败,不能把早期采样到的 journal 与后续轮次拼接。
- 写锁修正:并行 Agent 的 `file.write / file.patch / file.delete` 统一使用 Runtime 短等待项目写锁。`file.write` 的锁竞争错误在写入 observation 和 pending 前脱敏,禁止携带绝对锁路径;该路径曾导致 pending 持久化拒绝并把 run 推入 `needs-reconciliation`,现以 `2` 条 Rust 回归测试固定短等待与错误脱敏边界。
- 终局:父 tool-plan 数在故障前后相等;唯一 `-transient-1` 成功后,只能有唯一成功 parent final-reply、唯一 Supervisor assistant 和唯一 committed response stream。retry/handoff/finalization artifacts、重复 delivery/claim/receipt/action/message/lifecycle,以及公共正文、API Key、项目/发布配置绝对路径命中全部为 `0`。复验命令为 `npm run ai-game-creator-shell:agent-runtime:supervisor-swarm-final-reply-transient-retry-real-e2e -- --config-dir <发布AppData绝对路径>`。
- 确定性证据:fault proxy `14/14`、E2E self-test **PASS**、前端 `308/308`,以及 shell typecheck、`platform-llm 41/41`、`platform-agent 17/17`、`shared-contracts 7/7` 均已完成。
- 六轮记录:真实外部 Provider suite 共执行六轮,前五轮均为 **FAIL** 且不得拼接。第一、二轮沿用既有失败记录;第三轮已走通故障、重试和唯一回复,但过早观察到 `1` 个 finalization journal;第四轮在 quality-review 普通 tool-plan 连续 transport/connectivity 失败并耗尽重试,未进入目标故障;第五轮命中上述项目写锁竞争与绝对锁路径泄漏问题。第六轮在同一轮内完整 **PASS**。
- 第六轮证据:正式路由为 `gpt-5.5 / openai_chat``2` 条初始加 `1` 条 repair delivery、`3` 条专业 Agent assistant,目标为 Project Supervisor base final-reply,可信宿主 verify marker 门禁通过;受控 Provider `failed=1 / retry=1`incidental `failure=0 / retry=0``30s` backoffpidfd `claim=2 / signal=2`Runner `resumed=true / identityStable=true`。父 tool-plan 故障前后均为 `13`parent final-reply 与最终 assistant 唯一,response stream `sequence=2 / committed`pending、retry、handoff、finalization、confirmation sidecar、全部重复计数及 API Key、私有正文、项目路径、正式配置路径和公共报告泄漏扫描命中均为 `0`。本决策不关闭 V1.41 handoff 原子落盘并回读前的 unknown-result 窗口,也不覆盖 tool-plan 成功响应/function arguments 的 durable handoff。
## 2026-07-20 AI 游戏创作 Agent Runtime V1.43 tool-plan 成功响应持久交接与 repair 链恢复
- 决策:保留 V1.41 `game-creator-provider-handoff.v1` 的无工具文本不变量,新增 `.agent/runtime/tool-plan-handoffs/<agentKey>/<runKey>.json` 与严格 `game-creator-tool-plan-handoff.v1`。同一 Agent/run 账本按 `(loopIteration, repairAttempt)` 单调记录 `repair-0..N`,每条绑定完整 retry identity、实际物理 requestId、slot/attempt、Provider/model、去 thinking 响应、完整 function call envelope/arguments、usage、指纹和时间。
- 提交顺序:Provider 成功后必须先追加并回读 tool-plan handoff,之后才可为同一实际 requestId 写 lifecycle `completed`,再进入 parser、repair 或动作预检。`repair-0` 与所有 `repair-N` 统一使用持久 transient retry;恢复从当前 loop base 开始按序回放已有 entry,已成功请求零网络,前序回放不得删除后继 repair retry。
- 隐私与失败关闭:function arguments 只存在于私有 handoff 和后续 pending/action batch,公共 task/event/Agent DB/CLI/report 只写安全身份、哈希与计数。tool-plan protocol/repair 公共审计共同保存 Agent/task/Session/run/source、loop/repair/slot、响应指纹、Provider request ID SHA-256 和 protocolprotocol 只额外保存 function call 数量、call ID SHA-256 数组、catalog-bound function names、response ID SHA-256/字符数和归一化元数据,repair 只额外保存 attempt/maxAttempts、协议错误/响应 preview 哈希与字符数、call ID/function name SHA-256,不保存原始 callId/callIds/responseId/providerRequestId。审计写入在 Agent DB append 锁内按完整身份做全历史 compare-and-append,不使用 32 MiB 尾部近似去重。参数为保持语义不得静默脱敏;命中密钥、配置痕迹、结构化可执行路径中的项目/其它绝对路径、超限、乱序、slot/identity/requestId/response 冲突时进入 reconciliation。源码正文与计划叙述只做密钥检查,不能把 HTML 闭合标签或叙述路径误判为执行参数。格式错误但安全有界的 opaque arguments 只用于重建 repair,严格 parser/schema/catalog 通过前不能执行;未闭合或孤立 thinking wrapper 只持久化无正文的无效元数据,重放时仍必须进入 repair。
- 所有权与清理:账本保留同一 run 的已成功 planning entry,直到 run 完成、取消、失败、作废或明确 reconciliation 清理;这样单动作、多动作、confirmation、协作 batch 和直接回复都不会在下一 durable owner 建立前丢失。steer/cancel/终态/漂移清理前必须按整本账本补齐所有实际 requestId lifecycle,任一条失败时保留账本并进入 reconciliation。Runner 恢复会严格扫描 hash 路径、primary/`.previous` 和安全原子临时文件,清理合法终态遗留;Unix 全程使用固定目录句柄和根目录/Agent 目录 `flock`,安装用 `RENAME_EXCHANGE` 复核回滚,删除用 `RENAME_NOREPLACE` quarantine、inode 复核和原 fd 清空同步;Windows 使用相对父句柄及 `GetFileInformationByHandleEx` 句柄枚举,拒绝 reparse point/junction/硬链接并以禁止共享的独占句柄表示活跃 temp。两端都不依赖 PID 存活判断。未知、链接、目录身份替换或内容冲突项失败关闭。primary、`.previous` 或损坏账本阻止 `runner.shutdown_if_idle`。非协作同 UID 进程可主动忽略 Unix advisory lock,属于宿主 OS 信任边界,不纳入完整沙箱承诺。
- 验收边界:确定性测试必须分别覆盖 base handoff 与 repair handoff 在 lifecycle completed 前停止,关闭 mock Provider 后恢复零网络、原 requestId 唯一闭合、repair/protocol audit 幂等、唯一 assistant/completed/committed stream和终局零 sidecar。独立非默认真实 suite `supervisor-swarm-tool-plan-handoff-runner-kill` 已实现并完成 Shell/Root 两级注册;它只使用 sentinel-owned sibling AppData 与 metadata-only zero-fault proxy,每轮随机 capability 严格绑定 project/Agent/run/实际 request slot。断点只能在 handoff 原子落盘并回读一致后、同一实际 requestId lifecycle `completed` 前 ACKACK 后才通过 pidfd `SIGKILL` 强杀 suite 自有 Runner。恢复必须在同一轮证明同一 requestId 唯一闭合、`networkReplayCount=0`、protocol/repair audit 幂等、handoff 与 durable batch plan fingerprint 对应、恢复消费前 action/pending/delivery/claim 等副作用为 `0`,并在终局得到零 sidecar、零重复、零临时资源残留和零正文/凭据/URL/绝对路径泄漏。2026-07-20 的真实单轮已经到达并通过上述 checkpoint,但随后因专业 Agent 连续连接失败而整轮 FAIL;另一独立轮因首批工具数不满足 fixture 也未通过,不能拼接为 PASS。Provider 成功到 handoff 原子落盘回读前的 unknown-result 及手动 context-compaction 仍不在本决策承诺内。
- 当前证据:`tool_plan_`、`tool_plan_handoff_`、`provider_handoff_`、`provider_retry_`、`response_stream_`、`finalization_` 与 `finalization_resume_` 定向门禁均保持通过;本轮 `tool_plan_handoff_` 为 `44/44`Supervisor collaboration 相关过滤为 `55/55`,权威返工合同用例为 `1/1`。Tauri/Rust 串行全量 1058 tests 为 `1054 passed / 4 ignored / 0 failed`Linux `cargo check --tests` 与 `x86_64-pc-windows-gnu cargo check --tests` 均通过;E2E self-test、typecheck、变更脚本 ESLint、encoding 和 `git diff --check` 通过。默认并发全量只作竞态诊断,不替代 `--test-threads=1`。实现过程中发现并修复 thinking 归一化、源码路径误判、repair 漂移删账本、durable control 清理遗漏后继 repair lifecycle、复数敏感 key/Provider ID 泄漏、malformed JSON trivia 路径绕过、Agent DB 审计字段扩张、PID 复用 temp 误判、中间目录/文件名称换绑 TOCTOU、Windows 路径枚举 ABA 和审计尾部近似去重问题。真实 suite 的 checkpoint 已有单轮外部证据,但整轮仍无 PASS。
## 2026-07-17 旧创作模板与入口停止维护
- 决策:旧创作模板、旧创作入口及其专属运行服务进入下线范围,不再为跳一跳、抓大鹅 Match3D、儿童动作 Demo 等旧链路修复兼容问题、补生成脚本或维持专属门禁。
- 边界:共享账号、钱包、资产、图片编辑器、公开作品、通用 HostBridge、API、SpacetimeDB、发布运维和安全能力不属于旧链路,仍需维持正式门禁。历史文档只作为背景材料,不再作为旧入口继续运行的依据。
- 清理方式:允许直接删除已经失效的旧素材生成命令、入口路由、专属服务和对应测试;删除工程链路时仍需核对是否被当前共享能力引用,不能连带移除仍在使用的公共契约或持久化事实。
## 2026-07-20 开发态 Project Supervisor 纯聊天独立窗口
- 背景:开发人员需要一个不依赖正式产品布局的最小 GUI,用于直接验证 `project-supervisor` 的持久多轮对话与 Runtime 行为。
- 入口决策:当前仅 Tauri dev 提供独立窗口,使用 `index.html?supervisor-chat&projectPath=...` 路由,并从现有 `index.html?agent-chat` 开发入口打开;`projectPath` 是 URL 编码后的项目绝对路径。重复打开同一项目只恢复并聚焦原窗口,切换项目时在同一窗口导航,不销毁未发送输入。
- 复用边界:窗口固定使用 `project-supervisor`,新 Run 固定选择 `standard` profile,继续复用现有 active Session、External Runner、Agent Runtime、AppData 配置与持久 conversation;不创建新聊天后端、本地 HTTP 服务、数据库或平行配置体系,也不继承正式构建流程的自主交付 profile。
- UI 边界:只显示持久消息区、输入框、必要的等待 / 错误状态、工具确认 / 用户追问卡片和设置;不显示 Agent picker、Session / Goal / 完整 Runtime 面板或专业 Agent 协作栏。Session 控制面可隐藏,但对话仍按 `project-supervisor` active Session 持久化;`supervisor-chat` 纳入只读 Tauri event capability,保证同一 Runtime 的状态更新可实时到达窗口。
- 产品边界:该窗口只是开发验证入口,不替换、不修改正式用户 `client` 窗口及其登录、首页和项目开发流程。
## 2026-07-27 开发态“游戏运行 + 聊天”临时入口
- 背景:开发验收需要一个初始只聊天、首版预览运行后自动同屏试玩的最小页面,同时展示当前 Supervisor 协作树的最新 Runtime 状态。
- 复用决策:窗口固定使用 `project-supervisor + autonomous-game-build`,复用 active Session、External Runner、持久 conversation、确认 / 追问链路和共享 `PreviewRegistry`;不新增玩法入口、后端 API、会话库、Runner 或预览服务。原 `supervisor-chat` 继续使用 `standard` profile 并保持纯聊天语义。
- Run 身份决策:External Runner 接受新任务后,命令响应中的 canonical `state` 允许暂时仍是上一轮 idle,而 `acceptedRunId` 才是新任务的权威身份。GUI 必须暂存该身份并开启轮询、放行对应 Runtime event,直到新 state 接管后再清除;自动预览授权同样绑定 `acceptedRunId`。忽略该字段会造成任务实际运行但界面永久显示“等待输入”。
- 状态决策:页面只聚合当前 Supervisor 父 run 及其直接委派专业 Agent 的事件,稳定去重后默认显示最新 4 条、可展开至 20 条;事件只作状态投影,不写入 conversation。无当前项目的有效 `running` PreviewRegistry 状态时只显示聊天;运行后桌面端显示“游戏 2 / 聊天 1”,移动端上下排列。iframe 只接受当前授权项目的 `http://127.0.0.1:*`,继续复用现有 CSP 和 sandbox 合同,远程 URL、`file://`、手填地址或陈旧 manifest 状态均失败关闭。
- 进度证据决策:聊天消息流内增加单条 Runtime-owned “Supervisor 进度播报”,从当前 run 的 manifest 任务图、结构化计划、真实 loop、直接委派 Agent 和持久事件确定性派生,聚合迭代轮次、当前工作、活跃专业 Agent、试玩 / 静态测试、返工、代码修改和截图检查。该卡同一 run 原位更新,不调用模型、不写 conversation、不制造额外 assistant 记录;详情有界并移除绝对路径,不展示 Provider 元数据、指纹或原始内部正文。顶部原始事件列表继续保留以便核验。2026-08-01 补充的逐条公开输出是独立的 `eventId + publicText` conversation 消息,不改变该进度卡自身不落盘的约束。
- 跨轮记录决策:父 run 进入真实 `completed / failed / cancelled` 终态且 `code-prototype / preview-readiness / preview-playtest` 三个阶段全部终态后,追加一次 `【Supervisor 阶段记录】` 项目 assistant 消息;内容只保留本轮、任务 / 计划完成度、最新测试、最近返工和成果图片路径。Runtime 先终态而 manifest 尚未刷新时暂存候选,manifest 刷新后补写;页面启动时若首个可见快照已是终态,也必须补齐缺失记录,但 `idle` 不得被当作真实终态。记录按“项目 + 父 run”内存幂等,继续经过 `conversation.write` 策略并写入项目 conversation,因此下一轮和重载后仍可见。该项目记录不进入 Supervisor Agent Session,不改变 Runtime 唯一 final assistant 合同。
- 图片成果决策:manifest 中已登记的 PNG / JPEG / WebP 项目资源通过既有 `read_local_project_image_preview` 安全读取,并在聊天流中以单张 Runtime-owned “Supervisor 成果图片”卡原位展示,最多 4 张。只接受当前项目 `assets/` 已登记路径及返回身份完全一致的 data URL,不从自然语言或 Markdown 解析任意路径,不读取 `.agent` 验收截图,不写 conversation;切换项目、资源移除、读取失败或解码失败时立即移除图片或显示固定失败状态。缩略图点击进入独立模态查看器,支持 50%–400% 按钮 / 滚轮缩放、指针拖拽、复位和 Esc / 遮罩 / 按钮关闭,移动端全屏,不在聊天消息下方内联展开。
- 授权决策:用户成功提交本轮自主生成需求,即授予“当前项目 + 当前 Supervisor 父 run”一次性 `preview.start`;授权只把项目路径与 accepted parent runId 持久化到客户端本地状态,允许 App / WebView 重启恢复,不新增后端接口。首版产物完成后仍经现有权限、项目写锁、审计与 PreviewRegistry 链路启动;成功、显式 deny、非瞬时失败、父 run 在首版完成前终止或切换项目后消费或清除授权。首版完成投影与专业任务写入并发时,`preview.start` 可能暂时命中项目写锁;该错误不得提前标记“已尝试”或清空授权,应在释放锁后重试,最终仍只成功启动一次。项目或 Agent 策略的显式 deny 始终优先,不因页面授权而降级。
- 验证方式:定向覆盖 debug / release 入口分流、项目选择和切换隔离、父 run 事件聚合、首版只启动一次、deny 优先、预览停止后隐藏、loopback / sandbox 安全以及桌面 / 移动响应式布局。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-18 AI 游戏创作项目工作台 Runtime 状态投影
- 事实源:正式项目工作台的总控与策划 / 美术 / 程序 Agent 状态必须来自当前 Supervisor 父 run 的真实 Runtime。专业 Agent 只有在 `parentRunId` 精确匹配该父 run 时才可进入当前列表;`manifest.tasks` 仅在没有匹配 Runtime 时回退,不得覆盖真实状态。
- 刷新与恢复:普通项目页在 Tauri event 之外保留只读 Runtime 轮询,兜底独立 Runner 缺失 App event 的情况。短暂读取失败保留最后可信快照,不清空、不倒退已知状态。
- 用户面投影:只展示真实运行阶段、计划完成数 / 总数、最近更新时间、失败、待确认和待回答等紧凑状态。专业 Agent 的确认与拒绝必须精确绑定 `agentId + runId + actionId`,不得只依赖卡片顺序或 Agent 类型。
- 隐私与真实性:正式面不展示内部 `currentAction`、`observation`、工具计划正文、Provider 错误原文、fingerprint 或字符计数;transport / timeout / 鉴权 / 限流等失败只映射为安全文案,不得从 manifest、动画或前端计时器伪造生产中、进度百分比或完成状态。当前父 run 或专业状态集合变化时状态区回到顶部,总控摘要在内部滚动期间保持可见。
## 2026-07-19 AI 游戏创作 Runner 构建身份与 LLM Rustls 传输
- 背景:正式 release GUI 曾按相同 protocol + ping 继续复用更早启动的 debug Runner;真实专业 Agent 请求又在 native-tls/OpenSSL 链路间歇出现 TLS record bad-MAC。只做 UI 脱敏会掩盖真实失败,单次重启也不能消除后续瞬态抖动。
- Runner 身份:AppData endpoint 增加当前 executable 内容 SHA-256。只有协议、可执行文件身份和 ping 都匹配时才能复用;旧 endpoint 缺身份、debug/release 不同或构建内容变化都视为待退役。退役必须复用 `shutdown_if_idle`busy 时明确阻止切换,不强杀活任务;公共错误不输出 executable 路径、fingerprint 或 token。
- Provider 重试:新建 Runtime 配置默认 `maxRetries=2 / retryBackoffMs=500`。既有显式配置保持用户选择;现场正式配置已从 `0` 调整为 `2`。只有 `timeout / connectivity / transport` 使用既有独立物理 lifecycle 和有界指数退避,其他上游、协议、配置与副作用错误不重试;显式设置 `0` 继续表示关闭重试。
- LLM TLSnative-tls 在 500ms / 1000ms 两次退避后仍连续三次命中同一 TLS record bad-MAC,证明重试只能兜底。`platform-llm` 的 LLM 专用 `reqwest` 改为 Rustls 并显式选择 Rustls backendMCP 等其他 HTTP 客户端保持原传输栈,避免扩大变更面。该变更消除了已观测的旧 OpenSSL bad-MAC 路径,但新 Rustls raw log 仍可出现 `connection error: cannot decrypt peer's message`,不得据此宣称 TLS/transport 根因已彻底关闭。
- 生命周期控制:构建切换时有在途 Provider 请求会按安全协议进入 `needs-reconciliation`。CLI 提供精确 `agentId + runId` 的取消和显式新 runId 重试;取消只要求已配置的项目外 AppData,以免“旧 Runner busy 阻止新二进制,而取消又要求新 Runner”形成闭环,重试和其他写命令仍要求当前构建 Runner。
- 现场验证:Provider 鉴权和配置模型可用,短/长认证 Chat Completions 均成功;旧 run 被显式取消后,新 endpoint 的 executable fingerprint 与 release 二进制 SHA-256 一致。Rustls Runner 下 `design-foundation` 完成并产生最终回复,但后续 `code-prototype` 仍在三次尝试后因 `connection error: cannot decrypt peer's message` 失败。这证明 Runner 身份修复和 Rustls 切换有效缩小了问题面,但未完成 TLS 根因验收;普通 UI 继续只显示安全状态。
## 2026-07-19 AI 游戏创作工作台专业 Agent 恢复与成果回执
- 失败恢复:当前 Supervisor 父 run 下的专业 Agent 失败时,正式工作台提供“在当前项目重试”,不要求新建项目。入口必须精确核对原 `agentId + runId + parentRunId`,复用原 task、active Session 和父 run 归属,并为重试生成新 runId;原失败 run 保留为历史审计事实。
- 能力边界:UI 重试是恢复入口,不是 Provider/TLS 根因修复。新 run 仍必须按真实 Runtime 结果展示 running、failed 或 completed,不得因点击重试而伪造成功或丢失旧失败证据。
- 成果真相:专业 Agent 曾完成但没有文件产物时,“没有文件产物”不等于“没有成果”。工作台必须读取该 Agent 持久对话中最新一条带合法 `agent-finalization-<32 lower hex>` messageId 的 assistant,以明确标注的“专业 Agent 文本回执”展示;普通失败 assistant 不得覆盖既有成果。
- 资源投影:上述回执同步投影到“资源管理 → 文档”,保留来源 Agent 和 run 身份。它是持久回执的可见视图,不得冒充 manifest asset、项目目录中的实际文件或可下载交付物。
- 重试确认:`agent.resume` 默认仍为 `confirm`。普通自动 retry command 保留 auto gate;正式失败卡的“在当前项目重试”按钮本身视为本次明确确认,调用单独的 confirmed retry command,但仍不得绕过 deny。点击后必须在原卡即时显示提交中、成功或安全错误,不能把错误放到专业列表末尾。若总控已为同一 delegation 准备精确 repair,按钮优先确认该 repair,不再创建重复的无合同重试。
- 回执命名:无文件的 completed 结果统一称“专业 Agent 文本回执”,不得称“美术产物”或直接暴露 `design-foundation / art-asset-plan / balance-seed` 等内部 ID。美术任务只完成计划且 manifest 没有图片时,普通界面明确显示“仅完成计划,尚未生成或登记图片”。
- 工作台布局:PDF 方案外的顶部项目标题条不进入项目工作台;本条原定的资源卡同分类、当前会话内拖拽重排已由 2026-08-03 mentor 最新决定取代,当前资源卡只允许自动布局与点击聚焦。原独立可拖动详情浮层已被同日后续阶段三替换为中央主视窗资源聚焦状态,右侧对话与底部 Agent dock 常驻,长正文在聚焦主体内独立滚动;退出恢复当前会话的列表上下文。工作区与 dock 精确占满客户端可用高度,不保留 dock 下方空白。
## 2026-07-20 AI 游戏创作策划与美术图片交付门禁
- 问题:`design-foundation` 与 `art-asset-plan` 的旧 seed / 委派合同允许空 `expectedArtifacts`,因此专业 Agent 只提交策划或美术计划文本也会进入 `evidence-ready / completed`;真实项目没有界面原型图或美术图片。
- canonical 合同:策划必须交付 `assets/ui-prototype.png`16:9 横屏界面原型),美术必须交付 `assets/art-spritesheet.png`(首版核心美术素材)。Supervisor 发起这两类新委派时,`expectedArtifacts` 必须包含对应确定路径;普通只读委派仍允许空产物。
- 完成门禁:Runtime 只在图片文件存在、manifest 中存在同路径 `image/*` 项、来源为 `canvas` 且 kind 分别为 `ui-prototype / art-spritesheet` 时允许专业 Agent 完成。策划 UI 图不能再以“文件存在”代替语义验收:`design-foundation` 必须用 `image.inspect` 对当前 `assets/ui-prototype.png` SHA 写入 `validationProfile=ui-prototype.v2`;信息 HUD、主要可玩区域、当前玩法关键实体、主要操作、失败/重开、移动布局意图、实现清晰度与原创主题八项全 true 且 issues 为空才通过。Runtime finalization 只接受同 run、当前 SHA 的 v2 证据;旧 v1 只保留历史审计可读性。视觉 transport/解析失败、任一检查失败、finalization run 不匹配或图片 SHA 变化均继续阻塞。旧 manifest 即使保留资产登记,只要真实图片已丢失或 UI 图未通过当前 SHA 验收也不能完成。缺 Key、待确认、生成失败、只有文本或只有未登记文件时保持明确阻塞,不得伪造 completed。
- 外部与本地一致性:`canvas.asset_generate` 生成前创建或复用与本地项目同名的 External Editor 画布项目和素材库目录;生成请求必须携带 `projectId + assetFolderId + canvasCompletion`,使结果同时进入画布与素材库,再下载到确定本地路径并登记 manifest。规范图走 images generations `kind=spec`UI 走 images generations `kind=ui-design` 并精确引用当前规范图 resourceId,透明图集只走 icon-spritesheets generations 并引用同一 resourceId;三者都持久 route/kind/reference 供 manifest 门禁校验。UI 原型走玩法无关专用 prompt 与 art spec,不得把塔防字段注入非塔防项目;External Editor 的 `ui-design` 负面词不得排除文字、边框、按钮和 UI 控件,应排除无界面场景插画、海报与地图。策划 UI 合同固定为 `2K + 16:9`。路径限定为项目 `assets/` 下 png/jpg/jpeg/webp,拒绝父目录、绝对路径和符号链接。旧正式图不合格时先由原委派形成 `needs-repair`Supervisor 认领后只允许原 owner 在唯一 repair 中 `replaceExisting=true` 原位替换,禁止先删除正式图。`postprocess-failed-source-preserved` 或无真实 alpha 的图集不得登记;登记失败时只删除本轮新写入文件。
- 用户面:已登记但 `design-foundation` 未完成的 `ui-prototype` 只显示为“画板 · 候选界面图 / 待视觉验收”,允许用户查看但不得称为正式 UI 原型;新的同 SHA 验收通过后才恢复正式资源名称。
- 历史恢复:旧 delivery 合同不可被 repair 扩大。已有项目缺图时创建新的独立补图委派并保持 `repairOf=null`;不得修改历史 delivery,也不得要求用户新建项目。
## 2026-07-20 AI 游戏创作总控失败恢复边界
- 正式工作台的项目总控进入 `failed` 后必须在失败摘要内提供“在当前项目重试总控”,并明确不会新建项目;提交中、成功和安全错误反馈固定在同一区域。旧总控下仍在运行的专业 Agent 继续按真实 Runtime 轮询和展示,不得因为父 run 终态就被前端隐藏或误报为已停止。
- 总控失败后的恢复事实是创建新的总控 run,由新总控重新建立专业委派合同。专业 Agent 的原委派父 run 已为 terminal 时,前端不得继续提供单独重试,Runtime 的 confirmed retry 也必须在创建新 task、delegationId 或 delivery 前拒绝,避免产生无法向父总控交付的孤立重试。
- 当前父 run 仍活跃时保留既有专业 Agent 恢复入口;无 parent 的普通后台任务仍按原 retry 契约运行。本门禁不取消或重放父总控失败时仍在途的专业 Agent 副作用。
## 2026-07-20 Agent Runtime 重试受理与同源幂等
- 外部 Runner 入队后返回的 Session Runtime 可能仍是旧 run 或当前 active run,不能把 `state.runId` 当作本次重试是否受理的确认。重试结果新增可选 `acceptedRunId`;新入队返回实际 runId,已有同源 successor 时返回被复用的 runId,普通 Runtime 读取不携带该字段。
- 同一 `agentId + sourceRunId` 的 retry 使用跨进程锁串行受理,并从持久 `agent.runtime.background_task.retry` 审计解析 successor。存在 pending、running、waiting-for-confirmation 或 waiting-for-user-input successor 时直接复用,不创建第二个 task、用户消息或 retry audit;审计扫描被容量上限截断时失败关闭,不猜测幂等状态。
- 前端收到 `acceptedRunId` 后立即显示“重试已受理”并禁用按钮,继续监听和轮询精确 successor;响应快照仍为旧 failed run 不得误报失败,也不得让用户重复点击。只有真实同步到 successor 后才切换总控状态。
## 2026-07-20 AI 游戏创作项目开发工作台分期合同
- 正式产品合同统一进入 `docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`。工作台继续复用现有项目页、Supervisor、Runtime、manifest、画板和本地 preview,不新建平行项目或资产系统;Agent 对话式工作台作为创作工具平台例外被显式记录。
- 正式预览只在客户端当前窗口载入受限 loopback URL。可运行版本不可变,资源替换创建下一迭代版本;依赖/类型两套布局分别持久化坐标并只在新资源首次进入时自动排版;类型兼容按大类、子类型、尺寸规格共同判断。
- 数值微调立即写编辑态 revision,已拉起 preview 与测试切片继续使用旧 revision,重新拉起后才消费新值。自然语言新增参数只能绑定预定义注册表,禁止修改代码。
- 六专业组固定为 `design / art / code / balance / audio / publishing`。底栏默认突出策划、美术、程序,可展开数值、音频、发布;泥点只能展示后端账本归因投影,无数据不估算。
- P0 只开放严格审批。高风险审批 Rank 进入 `docs/project-memory/todos/【待解决】AI游戏创作高风险审批Rank-2026-07-20.md`;风险/无需审批使用视觉不可用但可点击说明原因,不能静默改变 Runtime 策略。Agent.md 与自定义 Skill 在来源审核、版本、权限、sandbox 和回滚合同完成前不向普通用户开放。
- 当前 run 状态与项目历史成果是两个投影:前者继续按当前 `parentRunId` 过滤,后者只从专业 Agent 持久对话中合法 `agent-finalization-<32 lower hex>` assistant 恢复。新 run 失败或待确认不清除旧成果,普通失败 assistant 不得进入资源管理。
- 历史成果读取采用项目内单调合并:新的合法 finalization 可以替换同 Agent 的旧回执,但 Runtime 轮询引发的持久对话瞬时读取失败、空结果或新 run 普通失败消息都不得清空已恢复成果。资源卡必须明确标记“历史成果”,继续与 manifest 正式资产和项目文件区分。
- Tauri `read_local_conversation` 的公开消息 DTO 必须把持久 JSONL 的可选 `messageId` 原样投影给前端;否则真实 finalization 在客户端边界丢失身份,前端只能看到普通 assistant 并把资源区错误显示为 0。旧消息缺少 ID 时保持 `null`,不按正文或时间猜测成果。
## 2026-07-21 AI 游戏创作自主可玩项目确定性真实门禁
- 决策:新增独立 loopback OpenAI Chat Provider 与 `supervisor-autonomous-playable-lane-defense-deterministic` wrapper,复用正式自主构建 suite、真实 Runner、真实 Runtime 工具、真实 Chrome 双视口和 disposable 项目。Provider 只能返回原生 function calls,不能直接写项目或伪造验证结果;临时配置必须位于仓库外并由 sentinel 约束清理。
- 协作顺序:Supervisor 首轮并行委派程序与只读验收;验收回复因并行写入变成 stale 时必须基于最新 revision 再规划。首轮浏览器失败后,Supervisor 直接 mutation 必须被 orchestrator-only 策略拒绝,再创建后续程序委派;新 revision 的静态复验和浏览器复验必须放在同一 planning 批次,成功后再认领后续回执,最后才允许唯一 Supervisor 回复。
- liveness 语义:observation 兜底只判断最新一条 `preview.validate`。最新成功结果会取代同一 run 更早的失败 observation,不能在成功试玩后因历史失败再次强制委派;最新结果仍为失败时继续阻断收束。持久 verification gate 的 `failedPlaytestRevision` 仍是优先事实源,不因本次修正放宽。
- 验收证据:本轮确定性 wrapper 与正式子 suite 同轮 PASS。Provider `17` 次 planning、异常 `0`;项目 revision `0 -> 2``game.static_smoke` 与桌面/移动浏览器通过,`lane-defense-v1` 固定试玩 `37/37`;三份委派回执全部认领,唯一 Supervisor assistantpending、sidecar、reconciliation、重复、密钥/正文/路径泄漏均为 `0`,隔离 Runner、AppData、配置和项目全部清理。
- 边界:本门禁证明本地确定性 OpenAI-compatible 路由可以驱动完整正式链路,不代表任何外部 Provider 已通过。外部 Provider 的鉴权、网络与模型行为必须用独立同轮 E2E 记录,不能与本结果拼接。
## 2026-07-22 AI 游戏创作自主可玩构建按 revision 收敛
- 背景:外部 Provider 已把项目从失败试玩所在 revision 推进到更高 revision,且专业 delivery 已 ready,但父 Supervisor 仍被旧 `failedPlaytestRevision` 导向新的 `agent.delegate`。同一父 run 已有 3 个 active/ready delivery 时,第四次委派只会稳定命中容量上限,已通过的最新项目也无法进入最终收束。
- 决策:当父验证门仍有旧试玩失败,且存在 ready 未认领回执或 active delivery 已达 3 个时,liveness repair 只开放 `agent.run_status`。动作必须使用 `agentId=null / scope=all / delegationId=null` 原子认领当前父 run 的 ready delivery;不得创建第四次委派。认领后由现有 revision 门禁要求当前 revision 先取得静态验证,再由父 Supervisor 执行 `preview.validate`。
- 边界:当前 revision 自身产生的新试玩失败、没有 ready 回执且委派容量未满时,仍可进入新的专业修复委派;本决策不取消真实返工,只禁止旧 revision 失败越过已有交付重复派工。`agent.run_status` 只用于 ready 认领和当前状态收束,不恢复模型轮询式等待。
- 试玩合同:`generic-v1` 与 `lane-defense-v1` 的每个固定 `data-playtest-id` 在对应动作发生时必须恰好匹配一个可见、启用且真实可点击的 HTMLElement。多选项和多格 UI 只能各指定一个自动化入口,缺失、重复、隐藏或 disabled 均失败关闭,浏览器 validator 不做“取第一个”或文本定位回退。
- 回归:构造“父试玩失败 -> 专业 Agent 推进新 revision -> 2 个 dispatched + 1 个 ready delivery”,断言格式修复目录只含 `agent.run_status`、ready 回执被认领、active 数从 3 降到 2,且既有新 revision 复验回归继续通过。完整确定性与外部 Provider E2E 仍需分别同轮验收,不能拼接证据。
## 2026-07-22 自主试玩浏览器与截图检查使用短路径和固定别名
- 背景:真实外部 E2E 的隔离 AppData 会形成较长 `TMPDIR`。Chrome 134 在该目录下继续创建 `com.google.Chrome.../SingletonSocket` 时超过 Unix socket 路径上限并以 status `134` 退出;另一次真实轮次已通过桌面/移动试玩,但模型只按提示调用 `image.inspect(paths=["desktop.png","mobile.png"])`,无法知道带 Agent、run 和 revision 的持久截图路径。
- 决策:Unix 浏览器子进程统一在 `/tmp/ga-browser-*` 下创建临时根目录和 Profile,并显式把该短目录作为子进程 `TMPDIR`;证据仍写入项目内既有受控目录。`image.inspect` 仅把精确 basename `desktop.png / mobile.png` 解析为当前 Agent、当前 run 下数字最大的已有 revision 截图,不跨 Agent、run 回退,也不改变显式项目相对路径语义。
- 安全边界:截图别名仍经过项目根、祖先 symlink、普通文件、图片格式和总字节上限校验。没有当前 run 截图时失败关闭,不能为提高成功率搜索全项目或复用旧 run 证据。
- 验收状态:短路径真实 Chrome smoke、截图别名定向测试、Rust 串行全量 `1139 passed / 5 ignored / 0 failed` 和确定性自主构建 E2E 均通过。外部 Provider 后续轮次在已完成真实浏览器试玩后出现单次非重试 Provider lifecycle 失败,整轮仍为 FAIL;当前不能据此宣称外部 Provider 完整 PASS。
## 2026-07-22 AI 游戏创作客户端大型模块按稳定 facade 并行拆分
- 背景:首轮拆出 `agent.rs`、`project.rs`、`tests.rs`、`App.tsx` 和真实 E2E 脚本后,客户端仍有多个 4k 至 10k 行的单文件热点,继续把工具、Runner、进程会话和项目摘要堆在单文件中会扩大多人修改冲突和审查范围。
- 决策:本轮只做结构搬迁,入口文件保留原 API facade,不改函数名、测试名、Tauri 调用路径和业务行为。`runtime_tools.rs` 从 `9586` 行降到 `69` 行并拆为 15 个工具职责模块;`runner.rs` 从 `5450` 行降到 `31` 行并拆为 protocol、state、endpoint、project owner、dispatch、server、client 和 tests`process_session.rs` 从 `4837` 行降到 `23` 行并拆为 model、persistence、lifecycle、I/O、recovery 和 tests`projectSummary.ts` 从 `5801` 行降到 `138` 行,显式转导出原 112 个符号,具体摘要按常量、路径、Trace、产物、资产、规划、试玩、质量、交付、运行和引导拆分。
- 后续收口:`App.tsx` 从 `12983` 行降到 `9794` 行,项目工作区下沉到 `src/features/project-workspace/` 的 11 个模块;`runtime_actions.rs` 从 `12519` 行降到 `139` 行,拆为 19 个生产模块和 2 个测试模块;`runtime_driver.rs` 从 `10510` 行降到 `394` 行并拆为 11 个模块;`runtime_protocol.rs` 从 `8623` 行降到 `105` 行并拆为 14 个模块,单模块不超过 `1319` 行。`runtime_driver/main_loop.rs` 仍约 `2995` 行,因为它承载现有单一主循环函数;后续必须先按运行状态阶段建立边界再拆分,不能继续机械切割。
- Rust 可见性与兼容性:嵌套子模块会改变 `pub(super)` 的直接父级语义。`runtime_tools` 中原本要供 `crate::agent` 兄弟模块使用的符号最小化调整为 `pub(in crate::agent)`;同一 facade 下跨子模块 helper 保持直接父级 `pub(super)``runner` 和 `process_session` 的内部兄弟调用仍经父模块 facade,不扩大到 crate 公共 API。新增 `runtime_protocol::provider_retry` 后,访问 crate 根同名模块必须写为 `crate::provider_retry`;原 facade 的兼容重导出继续保留,编译器仅因当前文件未直接消费而告警时使用局部 `#[allow(unused_imports)]`,不得机械删除。
- 验收:稳定共享树的客户端 `cargo fmt --check`、typecheck、Prettier、ESLint 和编码检查通过;Rust 串行全量 `1139 passed / 5 ignored / 0 failed`,客户端前端 `329/329` 通过。确定性 E2E self-test 与完整 E2E 均通过;完整链路继续满足 17 次 Provider lifecycle、revision `0 -> 2`、真实浏览器 `37/37`,重复、残留和泄漏均为 `0`。
## 2026-07-22 AI 游戏创作自主构建完成后由 Supervisor 确定性收束回复
- 背景:最新真实外部 E2E 已推进到 revision 5,最终浏览器试玩 `37/37`、Supervisor 计划 `8/8``image.inspect` 视觉请求与最终回复却先后命中同一 deserialize fingerprint。终局 `114` 个 Provider request identity 中 `113 completed / 1 final-reply failed`,因没有 Supervisor assistant,整轮仍是 **FAIL**,不得记为外部 Provider 全链路 PASS。
- 决策:确定性最终回复只适用于 `autonomous-game-build` profile、规范 Agent `project-supervisor`,并且当前 revision 的 completion gates 已全部通过之后发生的 `final-reply` 收束。优先使用非空 `plan.response`;只有它为空时,才生成“当前 revision 已完成生成并通过静态、桌面和移动试玩”的确定性回复。
- 失败关闭:普通 Agent、尚未收敛的自主构建、任一完成门禁未通过或存在 reconciliation 时,继续沿用原失败路径,不得生成成功回复。该兜底不放宽工具、协作、验证、试玩或恢复门禁,也不从中间 planning 或失败 observation 推断项目已完成。
- 证据边界:Provider lifecycle 必须保留真实 final-reply failed identity、fingerprint 和终态,不得为了写 assistant 把失败请求改成 completed、隐藏或重编号。兜底只解决已完成项目缺少用户收束的问题,不能成为 Provider 成功证据。
- 验证:代码修复完成后必须重新运行独立真实外部 E2E,并在同一轮核对当前 revision、completion gates、唯一 Supervisor assistant、Provider lifecycle、残留、重复与泄漏。新一轮完整通过前,外部 Provider 全链路状态继续记为未 PASS。
- 最新真实轮次:新 fallback 已命中,父 Supervisor 终局为 `idle / completed``turn.report` 为 `settled`,只产生 `1` 条 `44` 字符的 Supervisor assistantpending、retry、handoff、finalization、reconciliation、重复、API Key 和路径泄漏均为 `0`。因此“完成后不回复”已在该轮解决。
- 轮次结论:该轮仍是 **FAIL**,不能记为 PASS。`105` 个 Provider identity 中 `103 completed / 2 failed`;两个原始专业 Agent 失败均已由 repair 恢复,但最终验收命中 `supervisor-swarm-private-body-public-event-leak`。
- 脱敏定位:两个专业 Agent 的失败正文分别为 `149 / 123` 字符,对应 SHA-256 前缀 `494ce8 / 3089ad`,共进入 `4` 条 `event.detail` 和 `2` 条 `agent.runtime.background_task.failed.error`。六处内容均属于 delivery result,不是 userTask、委派任务或对话正文,与 final-reply fallback 无直接关系。
- 修复原则:私有 `state.error` 和私有 delivery 保留诊断正文;公共 event 与 agentDb 只写 `errorSha256 / errorChars /` 稳定 `failureKind`。不得依赖正文黑名单,也不得为通过验收把真实失败改写为成功。
- 后续验收:完成上述公共投影脱敏后,必须另起一轮独立真实外部 E2E;在该轮完整通过前,当前外部 Provider 全链路状态仍为未 PASS。
- 最终独立真实外部轮次:公共投影脱敏修复后另起的新轮次独立取得完整证据,`status=PASS`、`evidence=complete`、`privacy scan=complete`。上述 `114` identity 与 `105` identity 两个 **FAIL** 继续保留为独立历史失败,不与本轮拼接;最终 PASS 是单个新轮次的完整证据,当前外部 Provider 全链路状态据此更新为 **PASS**。
- Provider 与任务终态:本轮共有 `84` 个 Provider identity`started / terminal / completed` 均为 `84``failed / retry / open / duplicate` 均为 `0`。`1` 个原专业任务以 `budget-exhausted` 终止,唯一 repair 已 `completed` 并标记 `recovered`;最终 child 为 `2 completed + 1 historical failed`,所有任务均处于终态。
- 父级收束:父 Supervisor 为 `idle / completed``turn.report` 为 `settled`;唯一 Supervisor assistant 为 `297` 字符,`completed audit=1`finalization 完成 `4` 个 stages。
- 项目与试玩:revision 从 `0 -> 4``game/index.html` 为 `7639` bytes 且内容已变化,`game.static_smoke` passed`lane-defense-v1` 的 desktop / mobile 浏览器验证均通过,固定试玩为 `37/37`。
- 零值、隐私与清理:pending / confirmation / user-input / provider batch / retry / handoff / tool-plan handoff / finalization 残留 / reconciliation / duplicate 全为 `0`Provider payload / private body / API Key / project path / config path / log / browser report leak 全为 `0`;人工 approve / answer / steer 全为 `0`。Runner 与 AppData 已清理,项目因 `--keep-project` 暂留后由主线程清理。
## 2026-07-22 AI 游戏创作客户端第三轮四 Agent 并行结构拆分
- 决策:第三轮继续由四个 Agent 按互不重叠的文件边界并行搬迁大型 Rust 模块,入口文件保持稳定 facade,不改变既有函数名、测试名、调用路径、公开字段或业务行为。`tool_plan_handoff.rs` 从 `5614` 行降到 `24` 行并拆为 `10` 个子模块;最大生产模块为 Unix `1219` 行、Windows `1040` 行,测试模块为 `1697` 行。Unix 与 Windows 文件存储分别承载完整的平台原子提交链,为保持平台内原子语义不再按行数机械切分。
- 生成与终端:`agent/generation.rs` 从 `4566` 行降到 `92` 行并拆为 `10` 个子模块,最大生产模块 `trace.rs` 为 `834` 行;原有 `123` 个 `pub(crate)` API 由 facade 显式重导出,仅 `4` 个确需跨 `crate::agent` 使用的 helper 最小化调整为 `pub(in crate::agent)`。`swarm_cli.rs` 从 `4420` 行降到 `68` 行并拆为 `9` 个子模块,最大生产模块 `observer.rs` 为 `843` 行、测试模块为 `1529` 行;原 `35` 个测试名以及 `turn.report` 的字段和顺序保持不变。
- 浏览器:`browser.rs` 从 `4036` 行降到 `24` 行并拆为 `11` 个子模块,最大生产模块 `capture.rs` 为 `733` 行,`playtest/mod.rs` 为 `612` 行,测试模块为 `1169` 行;搬迁前后内嵌 raw JavaScript 的哈希一致。
- 集成边界:集成修复只补齐 `tool_plan_handoff` 下沉测试不再继承父模块作用域后缺失的 `response_fingerprint`、`validate_ledger` 与 `AsRawFd` import,并对兼容重导出添加局部 `#[allow(unused_imports)]`;未删除兼容出口,编译警告总数仍为 `18`。
- 验收:客户端 crate 的 `cargo fmt --check`、`cargo check` 与 `cargo check --tests` 通过;`tool_plan_handoff` 为 `44/44``swarm_cli` 为 `35/35``browser` 为 `21 passed / 3 real Chrome ignored`。Linux 串行全量为 `1146 passed / 5 ignored / 0 failed`。确定性真实 Runner + Chrome E2E 为 **PASS**Provider lifecycle `17/17`,项目 revision `0 -> 2`,固定试玩 `37/37`,终局残留与泄漏均为 `0`。
- 残余验证缺口:Windows cross check 在进入项目代码前即因宿主缺少 `x86_64-w64-mingw32-gcc` 而停止;本轮不能据此宣称 Windows 交叉编译已通过,需在补齐宿主交叉链接器后复验。
## 2026-07-22 Agent Runtime 并行 revision 漂移自动重规划
- 背景:真实无人干预塔防 E2E 中,`quality-review` 已在其独立产物路径写入验证脚本并把项目 revision 从 `0` 推进到 `1`;并行的 `code-prototype` 随后准备写 `game/index.html`。该动作尚未执行,却因 planning 时保存的全局 revision 为 `0` 被标记为 `needs-reconciliation`CLI 立即返回,项目没有生成。
- 决策:pending action 在执行前发现 project revision 漂移时,必须持久化为 `blocked` observation,明确 `projectRevisionDrift=true / replanRequired=true / 旧动作未执行`,清理旧 pending,并让同一 Agent、同一 run 基于最新项目状态继续 planning。只有副作用可能已经发生、持久身份损坏或审计无法证明结果时才进入 `needs-reconciliation`。
- 锁内边界:`file.write` 与 `file.patch` 在取得项目写锁后再次核对 pending 身份、仓库上下文、revision 和 verification gate,避免预检后与另一 Agent 的项目修改交错。revision 漂移只拒绝旧动作,不忽略并发变化,也不直接执行可能覆盖他人结果的旧写入。
- 验收:三个定向回归、全部 `revision` 过滤测试 `32/32`、`cargo check --tests` 和 Linux 串行全量 `1147 passed / 5 ignored / 0 failed` 通过。确定性正式 E2E 继续以 `17/17` Provider lifecycle、revision `0 -> 2` 和 Chrome `37/37` 通过。新的独立真实 external-provider E2E 只写入一次塔防需求并立即 EOF,人工 approve / answer / steer 均为 `0`;父 turn `settled`,项目 revision `0 -> 8`static smoke 和真实 Chrome `lane-defense-v1 37/37` 通过,唯一 Supervisor assistant 写入,pending、confirmation、user-input、reconciliation、sidecar、重复与敏感信息泄漏均为 `0`。
## 2026-07-22 自主构建禁止进入人工确认等待
- 背景:自主构建虽然禁止 `user.input_request`,但项目权限、Agent 权限或 MCP catalog 仍可能把 `blackboard.write`、`project.git_commit`、完整命令、资产生成等动作判为 `RequiresConfirmation`。Provider 多动作批次会因此整体停在 `waiting-for-confirmation`,无人干预目标无法继续。
- 决策:`autonomous-game-build` 只允许固定 auto-safe 白名单消除默认确认;其余任何本地或 MCP 动态确认结果统一失败关闭为 `Denied`。批次含拒绝成员时先完整预检并持久化 `aborted`,整批工具保持零执行,再把拒绝 observation 交回同一 Session/run 重新规划。标准 profile 和显式 deny 保持原合同。
- 恢复:旧自主 `pending-confirmation` 必须在校验持久 Run Profile、Runtime、Session/run、action identity 和 batch member 后迁移。先原子写 `aborted` batch,再写 `observed-rejected` pending 镜像;两次写入之间退出时由 batch 重建 pending。恢复后的状态和审计使用 `runtime-policy-rejected`,不能误报自动动作已执行或开发者主动拒绝。
- 验收:自主构建测试 `20/20`、确认测试 `18/18`、旧等待批次完整恢复、批次零副作用、`cargo check --tests`、Rust 串行全量 `1149 passed / 5 ignored / 0 failed` 均通过。确定性 Runner + Chrome E2E 为 `17/17` Provider lifecycle、revision `0 -> 2`、试玩 `37/37`;独立外部 Provider E2E 为 `62/62`、revision `0 -> 5`、`game/index.html=7816 bytes`、试玩 `37/37`,两轮人工输入、等待态、残留、重复与泄漏均为 `0`。
## 2026-07-22 自主构建首批职责和专业交付按 durable 合同收束
- 背景:此前首批只固定 `code-prototype / quality-review` 两个 Agent ID,没有固定两者职责。质量 Agent 可能先写验证脚本推进全局 revision,程序 Agent 的旧写动作随即过期;非只读专业 Agent 也可能在零 mutation 或未验证时仅返回文字完成。父 run 同时看到 repairRequired 与 ready/unobserved receipt 时还可能先发 repair,跳过权威交付收束。
- 决策:`autonomous-game-build` initial wave 中,程序任务必须非只读且 `expectedArtifacts` 包含 `game/index.html`;质量任务必须显式只读、不得修改项目且 `expectedArtifacts=[]`。Provider 计划解析、batch prepare 与 durable batch 恢复均重验同一合同;Provider action batch 升级为 v3,仅 v3 应用新职责,升级前 v2 collaboration batch 与 v1 contractless batch 继续按原 fingerprint 和合同恢复。只读 specialist 的计划只允许纯读取与状态观察,任何文件、revision、命令、任务、记忆、黑板、资产或委派副作用都在执行前拒绝,格式修复只保留 `respond_to_user`。非只读 specialist 只有本人 run 已产生 mutation 且对应 revision 验证通过后才能回复;ready 未认领或 claim 未 observed 时只允许先执行 `agent.run_status`。
- 语义边界:只读识别接受明确只读审查/验收和不得修改指令,不再把任意“只读”子串视为只读合同。`非只读 / 不要只读 / not read-only` 必须保持可修改;否则模型照抄修复提示中的“非只读实现任务”会永久触发同一格式修复错误。
- 验收:阻塞终审修复前 Rust 串行全量为 `1163 passed / 5 ignored / 0 failed`;补入只读写入与 v3/v2/v1 恢复回归后共运行 1169 项并以退出码 `0` 完成,其中 5 项真实浏览器环境用例 ignored。Provider `148/148`、collaboration `142/142`、swarm CLI `37/37`、autonomous `24/24` 和 App Surface `294/294` 通过。当前树确定性 Runner + Chrome 为 `17/17` lifecycle、revision `0 -> 2`、试玩 `37/37`。最新独立真实外部轮次单次输入后立即 EOF,一个原始专业任务失败后由唯一 repair 恢复,父 Supervisor completedrevision `0 -> 6`、`game/index.html=8080 bytes`、真实 Chrome `37/37`、唯一 Supervisor assistant`88` 个 lifecycle 全部 terminal`75 completed / 13 failed` 和 `12` 条 durable retry audit 保留真实失败证据并自行恢复,终局人工输入、open lifecycle、sidecar、reconciliation、重复与泄漏均为 `0`Runner、项目和隔离 AppData 自动清理。
## 2026-07-24 Supervisor 与部门 Director 使用统一 Interaction Loop
> 后续更正:本条「非原生 tool Provider 使用同构严格 JSON envelope 适配」的描述已由 2026-07-27「Anthropic 与流式统一使用 Provider 原生工具」取代;三种协议都提供原生 toolinteraction loop 不再存在按协议切换 JSON envelope 的分支,开启流式时也能拿到流式工具调用。下文保留作历史记录。
- 背景:`--swarm-chat` 曾在模型调用前用字符串包含判断选择 Chat / Execute / Resume,否定句、复合请求和未列入词表的工作请求都会误路由;busy Runtime 期间的裸聊天还可能绕过 Agent lane 并与原 run 交错写同一 Session。
- 决策:删除自然语言关键词分类和硬编码自然语言直答。Project Supervisor 与角色目录中 `role.id=director` 的六个部门负责人使用统一 interaction loop;自然语言回复与 `project_location / runtime_execute / runtime_resume` 都来自同一次 Provider turn 的直接文本或原生 function tool。叶子专业 Agent 保持合同执行者,不接入该外层决策能力。
- Canonical 输入:`runtime_execute` 不允许模型提交 task 参数,真正入队始终使用用户原始消息,避免模型改写时丢失否定、范围和验收条件。非原生 tool Provider 使用同构严格 JSON envelope 适配;模型只能提出 intention,不能选择 runId、越过权限或直接执行项目副作用。
- 并发与 Runner`SwarmChat` 恢复为 External Runner 写入口,启动前必须显式使用项目外 AppData。已有 active Goal 或 busy Runtime 时,新输入只进入同 run durable steer;空闲 direct reply 的 user / assistant 在 Agent Session lane 内成对落盘。`/resume` 是显式控制命令,不能再由“继续”等字符串特判。
- 扩展边界:首版 interaction capability 由一个定义同时派生工具名、描述、schema 和 dispatch kind,作为后续统一 Tool Registry 的窄入口。现有 Runtime Store、Tool Host、Goal、delegation、sandbox、revision、verification、finalization 和 exactly-once 保持自研且不迁入 Prompt 或 Skill;本轮不引入 Pi Node sidecar,也不宣称已完成全量工具 registry、PromptSection 或 Cargo crate 拆分。
- Provider 兼容:真实 OpenAI-compatible smoke 发现部分网关会在纯文本回复中返回 `tool_calls: null``platform-llm` 将该字段按缺省空列表解析,并保留真实工具调用数组语义。
- 验证:interaction parser `7/7`、swarm CLI `39/39`、Runner/config 门禁回归和 `platform-llm` null-tool-calls 回归通过。隔离 AppData 的真实 Provider 连续验证了身份直接回复、否定执行的架构解释、模型选择 `project_location` 和模型选择 `runtime_execute`;执行轮产生 `[已投递]` 后以 `turn.report outcome=settled`、busy/pending/reconciliation 均为 `0` 收束。
## 2026-07-24 自主构建按画布配置强制生成首版美术素材
- 背景:`agc:test:chat` 已把正式 AppData 中的 `editorApi.baseUrl / editorApi.apiKey` 私有复制到隔离配置,但 `autonomous-game-build` 缺省首批只固定 `code-prototype / quality-review`,因此真实运行可以只交付自包含 `game/index.html`,完全不调用已配置的 External Editor API。
- 决策:当有效运行时配置中的 `editorApi.apiKey` 非空且项目还缺少规范首版美术素材时,缺省 Supervisor 协作策略把 `art-asset-plan` 加入首批必需静态 Agent,与程序和只读质量审查同批委派;已有自定义项目协作策略只补入这一美术职责,不额外注入缺省的程序和质量职责。美术委派必须是非只读生成任务,`expectedArtifacts` 必须包含 `assets/art-spritesheet.png`;继续复用既有专业完成门禁,只有真实图片存在、manifest 登记为 `canvas / image/* / art-spritesheet` 后才能完成。已有同路径、同 kind、`canvas` 来源且本地文件存在的有效素材时,后续修复轮不重复生成或扣费。
- 自主权限:`canvas.asset_generate` 只在 `autonomous-game-build` profile 的 `design-foundation / art-asset-plan` 视觉职责中加入固定 auto-safe 白名单,使单次无人值守测试可以使用用户已经配置的画布 API Key;Supervisor、程序和其他非视觉 Agent 一律拒绝该工具,避免多个并行 Agent 对同一规范路径重复生成和扣费。普通 Runtime 仍沿用项目确认策略,显式 `deniedCommands` 在自主 profile 中也继续优先拒绝。Key 未配置时缺省首批仍保持程序与质量审查,不伪造画布生成能力或素材产物。
- 并发边界:External Editor 项目、素材库、生图和下载请求都在项目锁外执行,请求阶段只能读取现有 manifest 快照,不能补 seed task 或重写 manifest;下载完成后先按声明的图片类型校验 PNG/JPEG/WebP/GIF 魔数,再取得项目写锁,复核协作边界与输出路径并提交文件、manifest、revision 和验证凭证。专业 Agent 的完成门禁要求同一 run 的 `verifiedRevision >= mutationRevision`,不能被并行 Agent 的后续全局 revision 误判为过期,也允许更晚 revision 的复验覆盖本人修改;当前全局 revision 的集成验证仍由 Project Supervisor 精确负责。
- 复用边界:已有规范素材只有同时满足固定路径、`art-spritesheet / image/* / canvas` 登记、本地普通文件存在且 PNG 签名有效时才跳过生成;空文件、伪 PNG 或损坏占位必须重新进入美术委派。
- 恢复边界:首批 policy snapshot、durable provider batch 与恢复校验继续绑定同一 required Agent 集合;格式修复必须根据当前 policy 补齐可选的 `art-asset-plan` 固定产物合同,不能只修复程序与质量委派后绕过美术交付。
- 2026-07-25 决策:Swarm CLI 的 `busy` 仅表示当前 Agent 仍有运行、等待、reconciliation 或排队工作,不能作为 steer 目标判定。steer 能力统一复用 Runtime 协议层门禁;terminal canonical run 即使汇总出 pending queue 也永远不可 steer。CLI 发现旧 completed/cancelled run 后仍有 pending run 时先通知独立 Runner 恢复;旧 cancelled run 的 tombstone 不能让恢复扫描跳过后续 pending。新 turn 以 mutation 返回的 `acceptedRunId` 为权威 baseline,失败、交互、收束和 `turn.report.parentRunId` 都只归属该 runcanonical 已推进到下一 run 时从 append-only task journal 恢复目标 run 终态。相同且已落盘的最后一条用户消息只恢复观察原 run,不能重复写 conversation、steer ledger 或 task ledger。active Goal 也必须精确匹配当前 Runtime 身份与可 steer 状态,不能只依据 Goal 的 `active` 字符串直接追加。连续 run 的 assistant 回复按 `agentId + sessionId + runId` 派生的 finalization message ID 归属,失败终态按 `(agentId, runId)` 聚合且完整 task journal 优先于可能滞后的 state 投影;`turn.report` 的运行和队列计数同样读取完整 journal 并仅统计目标 parent run 及其直接 children。
## 2026-07-25 autonomous-game-build 升级为正式项目产物 DAG
- 背景:当前自主构建的终局合同主要要求 `code-prototype`、只读 `quality-review`、可选美术回执和可玩 `game/index.html``agc:test:chat` 也主要以该入口文件收束。这只能证明可玩原型,无法证明策划、数值、美术清单、音频需求、项目记忆和发布包装已形成正式产物。
- 决策:保留单波最多 3 个并行职责和现有 16 个 seed task,不新增平行任务系统。配置画布 Key 时,视觉 DAG 先由既有 `art-director` 生成正式规范图,`design-foundation / art-asset-plan` 再分别引用它生成 UI 原型与透明图集;`balance-seed / audio-asset-plan` 仍按依赖就绪并行,`code-prototype` 再整合上游产物,其后顺序执行 `quality-review`、当前 revision 的静态检查和真实试玩,最后由 `publish-package` 生成发布包装。下游 task 不得在上游合同完成前提前投影为 `completed`。
- 正式产物合同:无画布 Key 时最小必需路径为 `memory/project.md`、`game/game_design.md`、`game/balance.json`、`assets/manifest.art.json`、`assets/manifest.audio.json`、`game/index.html` 和 `exports/README.md`;美术清单必须明确记录素材尚未生成。配置 Key 时再额外强制 `assets/art-spec.png`、`assets/ui-prototype.png` 和 `assets/art-spritesheet.png`,三张图片必须由受控画布链路生成、真实可读并完成 manifest 登记。Key 已配置但生成无效或失败时不得完成,任何路径都不得使用占位图或伪造登记。`assets/manifest.audio.json` 只是 BGM/SFX 需求清单,不代表真实音频文件。
- 收束门禁:Project Supervisor 必须同时看到本轮必需 seed manifest tasks 全部 `completed`、当前配置对应的 7 项或 10 项正式产物齐全并通过可解析性/类型验收,以及当前最新 revision 的 `game.static_smoke + preview.validate` 通过,才能收束父 run。子 Agent delivery 完成或 evidence-ready 只是待 Supervisor 语义验收的证据,不能单独放行最终回复。严格 `agc:test:chat` 必须精确核对固定 16 个 manifest task 的唯一 ID 与 `completed` 终态、同一父 Run 下各任务唯一 logical run / 一次 started / 一次 completed / 零 failed-cancelled / 一次 manifest projection、正式路径、配置画布 Key 时的真实 PNG,以及绑定当前 project revision 的静态检查和桌面 / 移动浏览器 playtest;历史 revision 成功、文件仅存在或 PNG magic 命中都不能放行。当前脚本已同时绑定 current revision 的 static smoke 与浏览器证据,并对 PNG 执行 CRC、zlib、scanline、PLTE 和未知 critical chunk 校验。
- 调度与恢复边界:新根 run 重置全部 16 个 seed taskDAG 只在 `agent.run_status` claim 被可靠观察且静态屏障清空后启动,普通 preview / smoke bookkeeping 不修改自主 DAG。已 `ready / claimed-by-parent` 的相同终态 delivery 恢复重放保持幂等,保留首次冻结结果,不再写重复 `result_failed`。
- 验证状态:确定性 `npm run agc:test` 已通过,16 个 manifest task exactly-once,父子 run 全部完成,最终 revision 为 `11`,基础正式产物、两张画布 PNG、静态 smoke、桌面 / 移动 `37/37` 试玩通过,pending、reconciliation、Provider 失败、重复和泄漏均为 `0`。这是 loopback Provider 下的 Runtime / 文件 / 浏览器证据,独立外部 Provider 仍需单独验收。
## 2026-07-25 GUI 与终端共用 AppData 配置并区分自动测试和手工聊天
- 配置事实源:GUI“配置”面板与 `npm run agc:config` 统一读写 Tauri identifier `world.genarrative.ai-game-creator` 对应系统 AppData 中的 `game-creator.config.json`,不建立 CLI 专用配置或 `.env` 回退。终端向导提供 OpenAI、DeepSeek、Anthropic、火山 Ark 和自定义 Provider 预设,采集 Base URL、模型及隐藏输入的 API Key;更新 LLM 配置时必须保留 `agentLlm`、`editorApi`、`mcpServers` 等现有节点,保存后复用 `llm-status` 检查。
- 密钥边界:所有终端入口禁止 `--api-key` 参数,避免凭据进入 shell history、进程列表和任务日志。API Key 只能通过隐藏交互输入写入 AppData;隐藏输入临时调用 `stdin.resume()` 后必须在成功、取消、stdin 异常和 `SIGINT / SIGTERM / SIGHUP` 路径恢复原 raw mode,并在原本 paused 时执行 `stdin.pause()`,信号路径恢复后重发原信号。显式 `--config-dir` 必须是以 `world.genarrative.ai-game-creator` 命名的独立 AppData 叶目录,不能对 `/tmp`、AppData 根或其它共享目录整体执行 `0700` / 私有 DACL。POSIX 下目录权限保持 `0700`、文件权限保持 `0600`,使用同目录临时文件原子替换;Windows AppData 与隔离测试目录必须在写入或复制任何密钥字节前先建立仅当前用户可访问且禁止继承的私有 DACL,随后再写文件并复核 ACL。任何状态、错误或报告都不得回显密钥。
- 首次运行:`agc:test:chat` 自动发现配置失败时,只在 stdin / stdout 都是 TTY 的人工会话中询问是否启动 `agc:config` 向导;无 TTY、自动化和 CI 必须非零失败并给出确定命令,不得等待交互、静默生成配置或退回仓库模板。向导保存并通过配置检查后可以继续当前真实测试。
- 模式边界:`agc:test:chat` 默认提交固定植物塔防需求,作为单轮非交互真实测试,不依赖 stdin 或 EOF。它只有在本轮必需 seed task 全部完成、正式产物合同满足、最新 revision 静态检查和 Runtime 浏览器验收通过后才能成功退出;成功后关闭空闲隔离 Runner,并依保留参数清理 sentinel 测试 AppData 与项目,不进入长期 preview。
- 手工聊天:`agc:test:chat:manual` 不注入 `--task`,进入多轮 stdin 聊天,并在本轮收束后保留持续 localhost preview 供人工试玩,直到用户显式退出。自动测试和手工聊天共用相同 Provider、Runtime、隔离 AppData 以及产物 / current revision 检查;16-task journal exactly-once 的单轮硬验收只属于自动入口,因为手工模式没有同一份自动父 Run `turn.report` 生命周期。
- 超时与退出:自动真实测试必须有硬截止时间。POSIX 以独立进程组终止整棵 Cargo / CLI / Runner 子进程树,Windows 使用 `taskkill /T`;首次终止后只有短暂宽限期,随后强制终止,并给 Runner shutdown 与清理步骤各自设置有界期限。只向直接 Cargo PID 发送一次信号、等待无界 `close`、或在进入清理前撤销唯一计时器都不构成硬超时;超时和信号退出必须保留非零退出码与无法安全清理的现场。
- 真实外部验收状态:2026-07-27 新起的一轮独立外部 Provider + External Editor API E2E 使用 `npm run agc:test:chat -- --timeout-minutes 75`,约 `59m50s` 后以退出码 `0` 完整收束。该单轮真实生成并登记 `assets/ui-prototype.png``2829418` bytes)与 `assets/art-spritesheet.png``1361906` bytes),固定 `16` 个 manifest task 全部满足当前父 Run 下唯一 logical run、一次 started、一次 completed、零 failed / cancelled 和一次 manifest projection;七份基础产物与两张图片均通过正式产物校验,当前 revision 的 `game.static_smoke`、desktop / mobile `lane-defense-v1` playtest、浏览器报告和 PNG 截图全部通过。`turn.report=settled` 且唯一 assistantbusy / pending / running / confirmation / user-input / reconciliation 均为 `0`;隔离 Runner、一次性项目和隔离 AppData 已自动清理。此前失败轮继续保留为历史失败,不与本轮拼接;当前这套“16 任务正式产物 + 两张真实画布图片”外部验收状态据此更新为 **PASS**。
## 2026-07-28 AI 游戏创作正式视觉规范与透明 spritesheet DAG
- 16-task 边界:继续复用现有 seed manifest 的 `art-director / design-foundation / art-asset-plan` 三个任务,不新增平行任务、会话或素材系统。`art-director` 是正式视觉规范前置:先通过 `/api/external/v1/editor/images/generations` 的 `kind=spec` 生成 `assets/art-spec.png`,并登记为 `assetKind=icon-spec`。`generationInputs.artSpec` 只是辅助结构化上下文,不能替代这张真实规范图。
- 路由与依赖:`design-foundation` 以已登记 `assets/art-spec.png` 的 External Editor 稳定资源 ID 作为 `referenceImageSrcs` 中的视觉规范参考,通过 `/api/external/v1/editor/images/generations` 的 `kind=ui-design` 生成完整 `assets/ui-prototype.png``art-asset-plan` 以同一 art-spec 资源 ID 作为必填 `referenceId`,调用 `/api/external/v1/editor/icon-spritesheets/generations`,提交具体 `iconDescriptions` 与 `screenColor=auto` 生成透明 `assets/art-spritesheet.png`。严禁把 `assets/ui-prototype.png` 当作规范图引用;`art-spec.png` 缺失、不是当前画布的 `icon-spec` 或缺少稳定 `resourceId` 时,两个下游任务都必须等待 `art-director`,不得把本地路径、Data URL / Blob URL 当成稳定引用,也不得退回普通生图。单波最多 `3` 个静态职责的资源上限保持不变,调度只调整现有任务的依赖边和就绪顺序。
- UI extraction 边界:`/api/external/v1/editor/ui-designs/assets/extractions` 只适用于已有且带红框标注的 UI 设计图,不是 UI 设计图生成接口,也不进入本次 canonical DAG。后续若要生成独立 UI spritesheet,必须先补红框源图生成与正式产物合同,不得直接对无标注 `ui-prototype.png` 调用 extraction。
- 完成门禁:External Editor 2xx 只表示生成请求完成。通用 `warning` 优先于 `sliceWarning``postprocess-failed-source-preserved` 表示 provider 源图是唯一权威结果,但不满足透明图集合同,客户端保留服务端事实并失败关闭,不登记本地正式 spritesheet、不伪造切片、不自动重跑。仅 `sliceWarning` 时完整透明图集有效,Runtime 把原始 reason 写入私有审计与 Agent observation,但不得声称独立切片存在。下载结果还必须解码并至少包含一个真实透明像素,纯 RGB 或全不透明 RGBA 一律拒绝落盘和 manifest 登记。
- OpenAPI 同步事实:2026-07-28 从 `https://www.genarrative.world/api/external/v1/openapi.json` 获取的线上合同与 `docs/openapi/genarrative-external-v1.openapi.json` 原始 SHA-256 均为 `00fa39ea8781605b895b331a579fbf097eea7892962350cb2a82bed7e1024135`,逐字节一致,因此不制造无意义 JSON diff;实现按现有公开 `screenColor`、`assetLabel`、`warning` 与 `sliceWarning` 契约更新。
- 验收重置:2026-07-27 的独立外部轮次是“UI 原型 + 美术图集”两图合同的历史 PASS,没有 `assets/art-spec.png` 证据,不能作为新三图 DAG 的 PASS。实现新合同后必须新起同一父 Run 的独立单轮,同时证明三张图的真实 External Editor 资源身份、依赖顺序、不重复生成、透明图集门禁和现有 16-task exactly-once 收束。
- legacy 升级:旧 UI / spritesheet 缺少持久 route、kind 或当前 `art-spec.resourceId` 精确引用时,即使文件存在或通用视觉检查通过,也只能作为 legacy 候选。`design-foundation` 与 `art-asset-plan` 分别建立 owner 精确原合同,父 run 认领 `needs-repair` 后在同一批次各自发起唯一 repair,两个 repair 合称一个显式视觉返工阶段。委派合同在落盘前校验固定视觉路径的 owner,拒绝把 UI / spritesheet 合并给 `art-director`;没有回退为直接删除旧图、自动覆盖或无审计重复扣费。
- manifest 波次并发:自动调度在项目锁内为每个 ready child 预占对应 Agent Runtime lane,使入队只落 durable child journal;项目锁释放后才把已预占 lane 交给 drain 启动首轮 Provider planning。Runtime 的项目写锁统一提供约 10 秒有界等待,使 Provider request build / rebuild / capture、并行只读结果投影和其它同 run 控制面写入都能跨过异常慢的本地 manifest 波次,而不是只加固首轮 planning。专业 child 到达终态时只校验父 run 身份、活跃状态、Profile 与完成合同,不再要求独立静态委派屏障已经清空;该屏障仍只阻止下一波调度和父 run 收束,不能让已完成 child 的 manifest projection 丢失。
## 2026-07-26 固定画布产物返工与 design-foundation 职责隔离
- 固定输出合同:`art-director` 的视觉规范图固定为 `assets/art-spec.png / icon-spec``design-foundation` 的规范界面图固定为 `assets/ui-prototype.png / 16:9 / 2K / ui-prototype``art-asset-plan` 的首版美术图固定为 `assets/art-spritesheet.png / 1:1 / 1K / art-spritesheet`。普通生成始终 `replaceExisting=false`,已有有效登记时复用,不得删除后重生、改路径、改规格或重复扣费。
- 覆盖授权:`replaceExisting=true` 只允许来自 Project Supervisor 建立的唯一静态 repair delivery;当前 run 必须绑定带 `repairOfDelegationId` 的静态专业 Agent,原 delivery 已由同一父 Agent / 父 run 认领,目标 Agent 与固定 `expectedArtifacts` 逐项一致。普通首轮、动态 child、Supervisor 直接动作、未认领原回执、返工的再次返工或不在原合同内的路径一律失败关闭。
- stale 防护:发起外部生成前冻结待替换固定路径与原文件 SHA-256;下载完成并取得项目写锁后,提交前重新解析相同路径并复算 fingerprint。路径、文件内容或 fingerprint 在请求期间发生变化时拒绝覆盖,保留并发产生的当前文件;不能因远端生成已经计费或成功就用过期结果覆盖新 revision。
- `design-foundation` 边界:该 Agent 只拥有 `memory/project.md`、`game/game_design.md`,以及配置画布 Key 时固定的 `assets/ui-prototype.png`。Runtime 文件写入 / patchset / delete 门禁必须阻止其修改 `game/index.html` 或其它程序、发布、音频和美术文件;它不得调用 `game.static_smoke`、`preview.start`、`preview.validate`、进程工具、整项目恢复或自行进行桌面 / 移动试玩。只有 `preview-readiness` 可额外执行固定 `game.static_smoke`,只有 `preview-playtest` 可额外执行 `preview.validate`;预览与 playtest 不能仅依赖 prompt 自律。
## 2026-07-26 完成基线、画布审计与并发补验失败关闭
- 完成合同:自主根 Run 使用 `game-creator-autonomous-completion-contract.v2``baselineArtifacts` 是必填、排序稳定且参与 `contractFingerprint` 的不可变基线。旧 v1、缺少基线、基线条目不安全或提交前基线身份变化都失败关闭,不能复用历史产物冒充本轮完成。
- 并发补验:确定性 Provider 只在 Runtime 明确返回 revision blocker、专业 verification-only repair、成功验证 observation,或项目锁 / repository context drift 这两类可恢复 observation 时重放终态;每个 logical run 最多 16 次。只读职责不得借补验调用未授权命令,验证失败或缺少 `ok` observation 不能交付,Provider completion 计数始终 exactly-once。
- 画布审计:资源 manifest 可以保留生成 prompt 作为本地来源元数据,但公开 `asset.register / asset.update` 审计记录必须移除 `source.prompt`,只保留 canvas/resource/task/model 等身份字段,避免完整生成正文进入公开 Agent DB 表面。
- 当前测试事实:已有回归覆盖固定画布合同不允许被模型改写、已登记 spritesheet 禁止先删除、只有静态 repair 可原位替换、替换期间原文件 fingerprint 漂移时拒绝覆盖,以及 `design-foundation` 对 `game/index.html` 的 write / patchset / delete 和预览工具均被 Runtime 策略阻断。2026-07-27 的独立 75 分钟上限外部真实 E2E 已按上一节单轮证据完整 **PASS**;后续合同变化仍须新起独立轮次,不能复用这次结果替代未来验收。
- 最终落地:本次退役范围覆盖整个旧创作模板体系,包括 RPG / 自定义世界、拼图、拼消消、大鱼吃小鱼、敲木鱼、方洞挑战、视觉小说、汪汪声浪、寓教于乐、Creative Agent、Match3D、跳一跳和儿童动作 Demo。全部相关历史表继续作为数据壳参与 `spacetime-module` 编译,`migration.rs` 白名单与历史数据不变;旧 reducer/procedure/view、API 路由/handler/worker、前端页面/工作台/运行态、共享业务 DTO、纯业务 crate、未挂载旧实现和归档源码均删除,现役公共能力与历史表 schema 保留。
- 兼容读取:只保留历史审计、迁移和资产归属核对所需的最小读取定义;旧 `worldType`、公开作品号、URL、详情页和专属运行态均不再形成用户可访问入口。
- 方案文档:`docs/technical/【架构下线】旧创作模板业务退役方案-2026-07-17.md`。
## 2026-07-18 恢复现役平台公共壳但禁止旧业务依赖回流
- 背景:旧创作模板退役时误把新版 `/creation`、桌面公共侧边栏和“我的”完整资料页一起缩减;只恢复视觉后,现役 profile client 又经 `rpg-entry` barrel 把旧作品库、旧 runtime request 和展示模型重新带入 Vite 与 TypeScript 图。
- 决策:桌面端继续使用原平台公共结构,一级导航固定为 `创作 / 项目 / 我的`;顶栏保留编辑器项目 / 素材搜索、泥点入口和账号胶囊;“我的”全宽保留资料编辑、陶泥号、三项统计、充值、兑换码、社区、反馈、通用设置、API Key 和法律信息。搜索只面向编辑器项目与公开编辑器素材,不恢复旧公开作品搜索。
- 依赖边界:公共 dashboard、钱包、充值、兑换码、邀请码、API Key 和设置请求迁入 `services/platform-entry`,公共账单展示迁入现役 profile model。Vite 新增退役模块 graph 门禁,ESLint 对现役源码禁止导入旧目录;目录 watch ignore、Tailwind source、tsconfig include 和 tree-shaking 都不能作为依赖隔离证明。
- 路由与响应式边界(2026-08-03 纠正):`/creation`、`/project`、`/profile` 都是稳定路由,但旧模板退役不授权扩大移动端创作范围。桌面端使用 `创作 / 项目 / 我的` 侧边栏;移动端底部 dock 只保留“我的”,直达 `/creation`、`/project`、`/editor/canvas` 或从首页触发项目 / 画布动作时统一显示桌面端提示,不挂载创作主页、项目列表或图片画布。2026-07-18 同批加入的移动端三入口口径无效,不作为产品决策依据。
- 公共设置边界:`runtime_setting` 保持原表结构与历史数据,但它是音乐音量和平台主题的现役账号级公共能力,不归入旧玩法数据壳。鉴权后的 `GET/PUT /api/runtime/settings` 必须经 `spacetime-client` 调用 `get_runtime_setting_or_default` / `upsert_runtime_setting_and_return`;保留该路由不构成恢复旧 runtime API 的先例。
- 编译门禁:除旧业务目录外,`src/uiAssets.ts`、`src/types.ts`、`src/types/**`、`src/services/runtimeAudioFeedback.ts` 和 `src/services/publicWorkCode.ts` 也是顶层退役 module,必须同时退出 Vite module graph、TypeScript、ESLint 和 Vitest`/audio/**`、`/chat.png`、`/fusion-pixel.ttf` 及旧 pixel / story-tab / 玩法 CSS 不得进入 dev 服务或生产产物。验收时必须同时检查 `tsc --listFilesOnly`、Vite 依赖图 / 产物和退役资产路径,不能只依赖 tree-shaking。
- Rust 产物边界:`module-runtime` 继续承载账号、钱包、公共设置、追踪和 feature gate,但 `CreationEntry*`、旧公开作品、存档、浏览历史与游玩统计 DTO / command / mapper / 规则必须退出实际 rlib;只保留历史表需要的 `RuntimeBrowseHistoryThemeMode`、完整保序的钱包流水来源枚举等持久化 ABI。`check:server-rs-ddd` 必须执行 `check:module-runtime-artifact`,同时验证旧符号和字面量为零、必要 ABI 仍存在,不能以源码存在 `#[cfg(any())]` 或路由未挂载代替产物证明。
- 外围编译边界:`platform-auth` 不再编译 runtime guest token`platform-wechat` 不再编译旧玩法生成结果订阅消息,小程序不再注册订阅授权页;旧公开作品资产授权 view 退出 SpacetimeDB module,匿名素材读取只保留现役 editor showcase 派生授权。
- 历史队列边界:现役 external generation worker 只领取 `source_module = editor-canvas` 的任务,历史旧玩法 pending / running 行保持原状态,不得被新 worker 领取后改写为失败。
- Agent crate 边界:`platform-agent` 的执行器、工具注册表、回调和拼图 Phase 1 输入均属于已退役 Creative Agent 业务,不得因现役编辑器 Agent 共用一个模型名常量而留在 workspace 或 `api-server` 依赖图。该常量收口到 `platform-llm``platform-agent` 与仅由它引入的 `langchainrust` 退出在运 Cargo resolve graph,相关源码已删除。
- AI 游戏创作兼容边界:独立 AGC Tauri 壳仍复用 `platform-agent::game_creation` 的任务图与隔离协作数据模型。`platform-agent` 继续排除在 `server-rs` workspace 之外,但其独立 manifest 默认只编译 `game_creation` / `error`,旧执行器、工具注册表、回调、拼图 Phase 1 与 `langchainrust` 统一受关闭的 `legacy-creative-agent` feature 隔离;AGC lock 不得重新引入这些退役依赖。
- 防回流补充:顶层 `creationEntryConfigService`、`creationUrlState`、`customWorld*`、`runtimeGuestAuth`、`runtimeRequest`、`input-devices`、`useCombatFlow`、`useStoryOptions`、`useMocapInput` 和微信生成订阅 facade 同样属于退役前端模块;Vite dev 对旧 `/api/creation*` 与 `/api/public-works*` 前缀直接返回 404,不能回落 SPA HTML。
- Vite 全量边界补充:`src/games/**`、`src/data/**`、`src/prompts/**`、旧顶层 App / Playground、旧路由和 `services/ai.ts` 必须由 pre-transform 门禁直接拒绝;所有同源 `/generated-*` 裸读在 dev 与生产统一为空 `404`,历史对象只经现役签名读取接口兼容,不允许 SPA fallback 伪装成资产成功响应。
- 前端混合根目录补充:`src/components`、`src/hooks`、`src/persistence`、`src/routing`、`src/services` 的根级文件实行现役白名单,Vite 与 ESLint 使用同一口径阻断旧 RPG / 玩法根文件;子目录仍按现役目录和退役目录分别管理,新增公共根文件必须显式登记。
- 影响范围:`PlatformEntryActiveFlowShell`、`PlatformActiveProfileView`、编辑器 / 项目搜索、平台 profile clients、`module-runtime`、`platform-llm`、Cargo workspace / resolve graph、Vite / ESLint / Rust 产物门禁及旧业务退役方案。
## 2026-07-20 VectorEngine 图片任务预算收口到 worker deadline
- 决策:`editor_image_generation`、`editor_image_edit`、`editor_icon_spritesheet_generation` 和 `editor_ui_design_asset_extraction` 使用默认 `1800s` long job 预算。worker 从同一起点计算绝对 job deadline,并向 provider 提前保留 `min(60s, job 预算 / 2)` 作为审计、OSS 和终态写回窗口。deadline 只经进程内 `RequestContext` 传递;VectorEngine 单 attempt 取配置 timeout 与剩余预算的较小值,退避加下一次 attempt 无法落在同一 deadline 内时停止重试,参考图和响应图片下载也受同一 deadline 限制。普通 HTTP / `inline` 保持无 deadline 行为;`VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS` 默认仍为 `1000000`,配置加载层允许显式值更低。lease 续租 / fencing、迟到写回仲裁、attempt 耗尽和原子退款语义不变。
## 2026-07-21 VectorEngine 图片首选 gpt-image-2 并以 gpt-image-2-c 兜底
- 决策:前端、DTO、计费配置、持久化和 `platform-image` 的 `/v1/images/generations` / `/v1/images/edits` provider 首选请求统一使用 `gpt-image-2`;符合条件时才回退到兜底模型 `gpt-image-2-c`。不在业务 handler、前端或价格表中新增平行模型。
- 回退边界:明确模型不存在 / 不支持、408、非内容拒绝类 429、5xx、响应解析失败或非拒绝类缺图可以切模型;401 / 403、普通参数 / 内容安全拒绝、本地配置与参考图错误、发送 / 连接错误、request budget 耗尽和已生成图片下载失败不切模型。一次业务请求总发送上限仍为 5 次,两个模型共享同一 worker provider deadline 和 attempt 预算。
- 观测边界:审计 `image_model` 记录实际 provider attempt;首选 `gpt-image-2` 失败但兜底 `gpt-image-2-c` 恢复成功时,首选失败仍写入 `external_api_call_failure`,最终成功运行摘要记录 `recoveredFailureCount`。日志用 `fallback_from_model` / `fallback_to_model` 标识切换,不改变业务模型、扣费、素材 metadata 或终态语义。
- 脚本边界:仓库 `gpt-image-2-apimart` skill 的现役生成脚本采用同一首选 / 回退顺序;认证、请求发送不确定错误和下载失败不重新生图,避免重复上游成本。
## 2026-07-21 图片画布滚轮与中键平移统一为二维视口移动
- 背景:画布中键拖拽的平移模型已同时计算 X / Y,但普通滚轮分支只消费 `deltaY`,横向滚轮或触控板的 `deltaX` 被丢弃,且缺少中键横向拖动的状态机回归覆盖。
- 决策:普通滚轮原样消费设备上报的 `deltaX / deltaY` 二维平移 viewport;当按住 Shift 且设备上报 `deltaX = 0` 时,输入适配层把 `deltaY` 映射为横向位移并将纵向位移置零,核心平移模型不感知修饰键。`Ctrl / Cmd + 滚轮` 继续只负责围绕指针缩放;中键和抓手拖拽继续同时更新 X / Y。
- 验证:交互模型单测覆盖原始 `deltaX / deltaY` 和缩放边界;viewport hook 单测覆盖二维滚轮、Shift 横向适配与 Ctrl 缩放;stage 状态机单测覆盖中键水平、垂直同时移动。
## 2026-07-22 Gitea CI 使用预构建工具链 Job 镜像
- 背景:Gitea Actions 的四个 job 彼此隔离,原 workflow 在每个 job 内重复运行 apt、setup-node、rustup 和原生系统依赖安装,后端与原生壳仅安装阶段就消耗数分钟,并重复承受软件源和代理瞬时失败。
- 决策:新增 `deploy/container/gitea-ci-job.Dockerfile`,固定 Ubuntu job base digest `sha256:58ea92624c7c09582e05594d95488331045053d3a3f34cf09649f2a32313a614`、Rust stage digest `sha256:19817ead3289c8c631c73df281e18b59b172f6a31f4f563290f69cddd06c30e9`、带 SHA-256 校验的 Node `22.23.1` 发行包、Google Linux 主签名指纹和 Chrome `150.0.7871.181-1`。镜像预装 Rust 1.96、`rustfmt`、Chrome、`bwrap`、`rg`、`ffmpeg`、`clang/lld` 及 Tauri / 后端系统依赖,按当前锁预热根 npm、server-rs 与桌面壳 Cargo 下载缓存,并设置 `RUSTUP_AUTO_INSTALL=0`。构建脚本通过 NUL 分隔白名单 tar 流只发送约 `1.638 MB` 的 Dockerfile、checkout 脚本与依赖 manifests/lock,不发送业务源码、素材或本地私密文件。
- 镜像事实:当前验证镜像约 `1.788 GB`,默认 tag 为 `genarrative/gitea-project-ci:20260722.2`,完整 Image ID 为 `sha256:548431a2529d325b5ab546f242799a4076f979779ee832a0871ac1af881a4946`。runner config 保留 `ubuntu-latest`,并新增 `genarrative-ci:docker://sha256:548431a2529d325b5ab546f242799a4076f979779ee832a0871ac1af881a4946`;内层 Docker 数据持久化,`force_pull: false`,精确 ID 缺失时失败关闭,不回退浮动 tag 或现场拉取。
- workflow 边界:`Repository checks`、`Frontend tests`、`Backend tests` 和 `Native shell tests` 统一 `runs-on: genarrative-ci`,删除 GitHub checkout action、apt、setup-node 和 rustup 安装 step;镜像内 checkout 直接从当前 Gitea 拉事件 commit,并带 5 次有界重试。随后以 `GENARRATIVE_GITEA_CI_CHECK_RUNTIME=1` 执行 `scripts/check-gitea-ci-job-image.sh`,同时校验缓存锁、工具链、bwrap sandbox 与 Chrome headless。每个 job 仍各自执行干净的 `npm ci`,以当前 lockfile 为准隔离 PR 依赖;命中镜像 cache 时只做本地解包,锁新增依赖时经受控网络补齐。不烘入 `node_modules` / Cargo `target`,不挂载跨 PR 可写 cache。仓库 toolchain 变更时先重建镜像,不允许 job 现场下载 Rust。
- 运维与回滚:用 `scripts/gitea-ci-job-image.sh build|verify|export|load-runner` 管理镜像,按 `build/verify -> export 仓库外镜像归档和 SHA-256 sidecar -> load-runner -> 确认无活跃 job -> 备份 config -> 增加或替换 label -> docker restart --timeout 660` 切换。config 与镜像归档只放仓库外受控位置;共享文档只记录通用备份规则,不记录宿主绝对路径、注册信息或 token。重启后先验证真实 CI 再清理旧镜像;回滚先把 workflow `runs-on` 改回 `ubuntu-latest`,再恢复 config 备份并重启 runner。
- 影响范围:`.gitea/workflows/project-ci.yml`、`deploy/container/gitea-ci-job.Dockerfile`、`scripts/gitea-ci-job-image.sh`、`scripts/check-gitea-ci-job-image.sh`、`scripts/check-gitea-ci-job-runtime.sh`、runner label/config 和 Gitea CI 运维文档。
- 验证方式:构建脚本校验宿主与 runner 内层 Image ID 一致;环境脚本校验 Node、Rust、`rustfmt`、Chrome、bwrap、原生命令与 pkg-config 依赖;runtime 脚本执行完整 bwrap 和 Chrome headless canary;真实 PR 的四个 job 全部通过,同时复核 `Privileged=false`、`Binds=[]`、`MaskedPaths=[]`、`ReadonlyPaths=[]` 和独立网络。
## 2026-07-23 BgFilter 父侧连接失败有界重连与冷启动宽限
- 背景:主机重启或 worker 崩溃拉起期间,父侧对 loopback BgFilter worker 的 TCP 连接失败此前直接映射 `internal_error`complex(队列 `max_attempts=1`)终态失败不可自愈,flat 被迫降级。systemd 层修复被否决——`After=` 在 `Type=simple` 下只提供进程启动排序,不构成「已监听」的 readiness 保证;而任何显式 readiness 交接(`Type=notify`、阻塞式 `ExecStartPost` 探活、socket activation 等)一旦成为 API / external worker 的启动硬依赖,都会把 BgFilter 故障扩大为整套服务不可启动。
- 决策:仅对「TCP 连接从未建立」的失败(连接拒绝 / 不可达 / connect 阶段超时)做有界退避重连——这类请求从未进入 worker admission,无副作用、天然幂等;连接已建立后的任何失败(结果未知)与收到任何 HTTP 响应(含 5xx)维持原「不重试」禁令。每轮重连前按现有公式重算 `maxQueueWaitMs`,不突破「预算不足不发送」不变量。计量单位澄清:一次逻辑调用至多被 worker 接收一次 RPC,重连增加的只是连接尝试次数。
- 冷启动宽限:重连配额按「本进程是否已连通过 worker」(收到任意 HTTP 响应即算,`AppState` 级标记)分档——冷启动档 flat 22.5s / complex 约 62.5s(覆盖开机竞态与慢开机),常规档 flat ≤1.5s(不侵蚀 39s fallback 预留)/ complex 22.5s(覆盖 `RestartSec=5s`+ 启动窗);档位单次调用内锁定。新增 `bgfilter_internal_connect_retry_total{mode}` 指标。
- 平台差异(Windows 开发环境):连接已关闭的 loopback 端口不回 RST 而是挂到 connect timeout,错误呈现为 `deadline_exceeded` 且 `is_connect` 为真;重连判定只看 connect 分类,不看错误码。生产 Linux 即时拒绝,呈现 `internal_error`。
- 安全不变量测试:除配额 / 跨窗 / deadline 地板路径外,专项覆盖「TCP 已 accept、未回任何 HTTP 字节即断开 → 不得发起第二次连接」,以 mock listener 的 accept 计数证明父侧未重连。
- 影响范围:`server-rs/crates/api-server/src/bgfilter_worker.rs`、`state.rs`、`editor_project.rs`、调度方案 §5.1/§7/§9.3/§11、数据契约「BgFilter 连接复用、超时与动作帧流水线」条目、运维文档及各编辑器专题文档的旧禁令措辞统一改为「不重试已被 worker 接收的内部 RPC」。
## 2026-07-23 生产 API 发布按实际路径渲染 worker systemd unit
- 背景:Server-Provision 支持自定义 current link、API env 和角色 env,并在首次安装时渲染三个 worker unitAPI deploy 为下发随 release 更新的 unit 又原样覆盖目标机配置,导致自定义路径在下一次发布时退回模板默认值。
- 决策:`production-api-deploy.sh` 继续随 release 安装默认命名的 BgFilter、external-generation worker 和 controller unit,但安装前必须用本次部署参数渲染临时文件;新增 `--controller-env-file` 补齐 controller 专属 env 输入。release 内模板保持默认路径,供 provision 和 deploy 共同作为单一模板来源;自定义服务名仍由目标机自行管理,不强制覆盖。
- 影响范围:`scripts/deploy/production-api-deploy.sh`、`scripts/check-production-api-deploy.mjs`、`jenkins/Jenkinsfile.production-api-deploy`、`jenkins/Jenkinsfile.production-full-build-and-deploy`、`scripts/check-production-ops-guardrails.mjs`、生产运维文档和 worker systemd 发布契约。
- 验证方式:`bash -n scripts/deploy/production-api-deploy.sh`、`node --check scripts/check-production-api-deploy.mjs`、`npm run check:production-api-deploy`、`npm run check:production-ops`、`npm run check:encoding`、`git diff --check`。
## 2026-07-23 Gitea CI 镜像刷新到 SpacetimeDB 2.7.0 锁
- 背景:`server-rs/Cargo.lock` 已从镜像预热时的 SpacetimeDB 2.6.1 前移到 2.7.0runtime 校验因此报告 `server_rust_cache_lock=partial`。受控 Cargo egress proxy 连续返回 CONNECT tunnel 502 时,Backend job 在 `check:module-runtime-artifact` 依赖解析阶段失败,尚未进入 workspace tests。
- 决策:刷新默认镜像 tag 为 `genarrative/gitea-project-ci:20260723.1`,完整 Image ID 为 `sha256:c04b114b1f145072c9df7842c4c974e1bb2eaaf391d95d84c9212a460546b7d5`,并将 Runner `genarrative-ci` label 映射到该精确 ID。镜像内 server-rs lock SHA-256 为 `ab1e07479b5716a98ddab9824bf95664121935aea30ab719f76f0705e1f96bbb`,包含 `spacetimedb`、`spacetimedb-lib` 和 `spacetimedb-sdk` 2.7.0 缓存。
- 构建边界:两个 `cargo fetch --locked` 在 Cargo 自身网络重试外再执行最多 5 次整命令级重试,处理 registry index / config TLS 握手直接失败;版本解析仍受 lockfile 固定,构建末尾继续以 `CARGO_NET_OFFLINE=true cargo fetch --locked` 证明缓存闭合。
- 运维边界:镜像归档、SHA-256 sidecar、切换前 Runner config 与注册文件备份只保存到仓库外受控目录。切换前连续确认 Gitea 无 `in_progress` run 且内层 Docker 无容器,切换后等待 rootless Docker socket 恢复,再验证 Image ID、label 注册、bwrap 与 Chrome canary;旧镜像在真实 CI 通过前保留。
- 验证方式:新镜像在 `--network none` 下按当前锁完成 `cargo fetch --offline`,并完成 `module-runtime`、`platform-auth`、`platform-wechat` 构建;宿主与 runner 内层 Image ID 一致。真实 master CI 还必须确认 cache lock 命中并完成原 Backend workspace tests。
## 2026-07-22 陶泥儿精选改为顺序循环分列 Masonry
- 背景:CSS multi-column 会按纵向高度平衡卡片,少量素材或活动卡与普通素材高度差较大时,桌面首行会只放一到两张,后续卡片提前从左侧下一段开始,无法满足“每行填满三张再换行”。
- 决策:`/creation` 陶泥儿精选保持平面 DOM 顺序,按当前列数将第 `index` 张循环分配到 `index % columns` 列。三列下第 1/2/3 张分别进入第 1/2/3 列,第 4/5/6 张再分别接续三列;不采用最短列贪心排序,避免同一组多张连续进入同一列。
- 宽度与高度边界:列数由精选容器实际宽度、0.92rem computed gap 和 288px 首选最小列宽共同决定,最多三列。卡片先获得目标列宽,再按真实 preview aspect ratio 和内容测量高度;同列紧凑堆叠,不拉伸、裁切或等待其它列高卡。循环分列只消除列内空洞,较短列在整个容器底部仍可有尾部高度差。
- 动态与可用性边界:`useLayoutEffect` 首次同步测量,`ResizeObserver + requestAnimationFrame` 在容器变宽、卡高变化、筛选重排和 cursor 追加后全量重排。只有当宽度、卡数和所有高度完整时才进入 absolute ready;否则保留 Grid fallback,防止卡片重叠和分页 sentinel 提前触发。DOM/Tab/读屏顺序始终不变,容器与卡片显式为 list/listitem。
- 兼容边界:保留现有 `.creation-landing__asset-waterfall` 类名、筛选、排序、cursor 分页、预览与点赞链路;只替换布局算法。该决策覆盖 2026-07-07 multi-column 及本日早先 row-major Grid 的布局部分,不改变精选仍是动态素材流的产品定位。
- 验证方式:纯函数测试锁定容器临界宽度、循环列序、列内 top 和容器高度;`src/index.test.ts` 锁定 Grid fallback 与 Masonry ready。Playwright 在同一 viewport 中变更容器宽度,核对 3/2/1 列、每列 gap、容器高度、DOM 顺序、无重叠/横溢出和 console/page error。
## 2026-07-23 恢复通用灰度发布后台控制面
- 背景:旧创作模板退役时,后台灰度页因同时加载 `creation-entry:*` 动态目标与现役 `image-editor:agent-sidebar` 固定目标,被整页从路由、TypeScript、ESLint 和 Vitest 编译链摘除;通用 feature gate 后端、权限和现役画布 Agent 判定仍在,形成有 API 无正式控制面的不一致。
- 决策:恢复后台 `#gray-release` 导航、member Tab 权限展示、前端 DTO/client、页面渲染和页面测试;页面只读取和写入 `GET/PUT /admin/api/feature-gates`,不再请求已退役 `/admin/api/creation-entry/config`。
- 目标边界:固定目标列表只登记现役 `image-editor:agent-sidebar`;管理员仍可直接输入其他通用 Gate Key。不得恢复 `creation-entry:*` 动态目标、入口公告、入口开关、旧作品可见性页面或任何旧模板接口。
- 运行语义:环境变量继续是画布 Agent 总开关,feature gate 只在总开关开启后做黑名单、白名单、标签和稳定百分比受众限制;本次不修改 SpacetimeDB schema、灰度优先级或后端契约。
- 验证方式:后台路由与灰度页面 Vitest、`npm run admin-web:typecheck`、定向 ESLint、`npm run check:encoding`、`git diff --check`。
## 2026-07-23 手机号认证统一使用国家码与纯号码双字段
- 决策:普通手机号认证请求统一使用可选 `countryCode` 与必填 `purePhoneNumber`,省略国家码时默认中国大陆 `86`,直接替换旧 `phone` 字段。前端把浏览器 E.164 自动填充值拆成这两个字段;后端先验证国家码,再复用纯手机号规范化并生成 E.164 存储。
- 2026-07-27 补齐:AI 游戏创作客户端的密码登录、验证码发送和验证码登录统一复用共享 TypeScript 请求契约,固定把中国大陆输入拆成 `countryCode=86 + purePhoneNumber`,不再发送旧 `phone`。认证 HTTP 错误只有在响应为合法 JSON envelope 时才展示后端安全消息;Axum 422 等非 JSON 正文回退到当前动作的中文错误,不向用户展示 JSON 解析器异常或原始反序列化文本。
- 微信边界:小程序客户端仍只上传 `wechatPhoneCode``platform-auth` 必须要求微信成功响应中的 `phoneNumber`、`countryCode` 与 `purePhoneNumber` 均存在且非空,但只使用后两项执行国家码校验和 E.164 构造。腾讯官方仅说明境外 `phoneNumber` 会带区号,并未承诺 E.164 格式,中国号码示例中它与纯号码相同,因此不得校验 `phoneNumber == +{countryCode}{purePhoneNumber}`。微信字段缺失时失败关闭,不能使用普通请求的 `86` 默认值。
- 数据边界:认证投影与 SpacetimeDB 的 `phone_number_e164` 保持不变,不新增国家码或纯号码列,也不需要 schema 迁移或 bindings 生成。
## 2026-07-28 编辑器媒体类型统一由 assetKind 判定
- 决策:`assetKind` 是编辑器资源和素材唯一权威媒体类别;不新增或返回 `mediaType`。`character-animation` 渲染为序列帧,`video` 渲染为视频,`audio/sound-effect/background-music` 渲染为音频,其余类别渲染为图片。前端内部可保留派生的 `CanvasMediaType` 选择渲染器,但不能把它作为后端事实。
- 持久化边界:`editor_project_resource` 与 `editor_asset` 表尾只保存 `image_sequence_frames_json` 与 `image_sequence_duration_ms`,默认均为 `None``editor_showcase_asset` 作为提交时冻结的审核与公开快照,同样在表尾保存这两个字段并从账号素材逐字段复制,旧行默认均为 `None`。正式帧对象和角色动作生成响应都不保存或返回 `frameIndex`,数组位置是唯一播放顺序;帧数取数组长度,FPS 由帧数和毫秒时长即时推导,不持久化 `frame_count`、`fps` 或通用 `duration_seconds`。后端处理抽帧和逐帧去背时仍保留内部 `frame_index`,仅用于乱序并发收口、OSS 命名、日志和错误定位;仓库内旧 helper 同步升级,不为它保留外部兼容字段。
- 时长口径:`image_sequence_duration_ms` 只表示角色图片序列完整播放一次的毫秒时长,与音频 / 视频生成请求中的 `durationSeconds` 完全分离。角色动作与视频生成响应仍可携带各自既有的请求 / 结果级 `frameCount/fps/durationSeconds`;通用音视频秒数不进入资源 / 素材正式列、`EditorAsset`、`CanvasLayer` 或画布 layout,只允许把上传探测值或生成请求值格式化为用户可见字符串后写入 `generation_inputs_json.fields[]` 供素材详情展示。素材详情和画布 ZIP 用户可见元数据只透传实际存在的 `fields[]` 时长项;缺少时直接省略,不生成 `--:--` 占位。素材放置与工程恢复不做媒体探测,也不从 layout / resource 回退;音频播放只使用媒体 `loadedmetadata.duration`。不得为音频生成响应新增 `durationSeconds`,也不得把音视频秒数写入图片序列字段。前端角色动作播放器使用 `imageSequenceDurationMs / imageSequenceFrames.length`Spine 导出时才换算秒数并推导 FPS。
- 写入与复用边界:`assetKind=character-animation` 必须在 SpacetimeDB storage/procedure 边界同时提供至少两帧有效数组与大于 0 的图片序列毫秒时长;其他类别不得携带图片序列字段。同项目同源同媒体资源只允许 `None → Some` 单调回填,非空冲突失败关闭,延迟重试的 `updated_at` 取请求时间与既有时间的较大值。
- 生成关联边界:角色动作生成响应同时返回已经持久化的最终 `resource` / `asset`;带项目上下文时前端结果图层必须直接使用 `resource.resourceId` 及其预览视频来源血缘,缺少 resource 直接失败。无项目放置也必须绑定响应中的正式账号素材,不能从响应 `frames` 构造无资产真相的本地动作结果。“动作(原始视频)”只是 `asset_kind = video` 的 provider 中间产物,其 `editor_project_resource` 与 `editor_asset` 两行都固定 `generation_inputs_json = NULL`,不得携带引用或开放参数复用;完整用户生成配方只保存在最终 `asset_kind = character-animation` 的序列资源 / 素材。角色动作规范化迁移对权威证明的 preview video 同样清空整份生成输入,但不改写其 `source_resource_id`。
- repair 幂等边界:legacy 音频 repair 不再派生或回填任何资源级通用时长,因此新列上线前已完成 repair 的重放继续按原字段精确匹配;图片序列字段在音频行上均为 `None`。
- 布局边界:`editor_project_resource` 的正式字段是角色动作唯一媒体真相;动作 layout 只保存资源引用和 placement,不保存帧、时长、预览、生成输入、资源元数据或顶层 `mediaType`。前端仍可从 `assetKind` 派生内部 `CanvasMediaType`,但后端响应清洗不得把普通 layer 的旧 `mediaType` 传回前端,也不得递归删除 `generationInputs.references[*].mediaType`。
- 迁移边界(2026-08-04 收口):存量 `generation_inputs_json.characterAnimation`、已证明动作行的 helper 顶层 `frames/previewVideoPath/frameCount/fps/durationSeconds`、`screenColorHex`、正式帧 `frameIndex`、误标预览 MP4 和动作 layout 副本,由 `normalize_editor_character_animation_metadata_and_return` 按 `asset → project-resource → showcase → canvas` 一次性规范化。顶层字段只有先证明动作身份后才解释;普通图片 / 视频任意 JSON 中的同名字段不动。canvas dry-run 可消费前置 scope 的计划态结果,但 apply 仍要求前置 scope 已物理完成;同 task 候选先按权威对象规划分类并排除预览视频,只有唯一最终图片序列可补建资源。正式序列每帧必须按首帧 bucket 的稳定路径精确匹配同 owner / task 的图片 `editor_character_animation` 对象并补齐 `objectKey/assetObjectId`。迁移永不验证 layout 复制的 `sourceResourceId`,补建资源采用最终素材的 DB 血缘;新生成直接来源链仍严格校验。正式/旧版冲突、最终候选为零或多个、对象不匹配形成 blocker;apply 必须绑定同批 dry-run SHA-256,结束后全量复核零匹配、零 blocker。迁移完成后删除 api-server、admin、Web 与 helper 的 action fallback。
- 新写入边界:`assetKind=character-animation` 的 `generationInputs` 若含旧运行字段或 `screenColorHex`,正式帧若含 `frameIndex`api-server 和 SpacetimeDB storage 均失败关闭;门禁只对角色动作生效,不误伤其它素材的任意 generation input JSON。
- 交互边界:角色动作素材下载导出完整序列 ZIP;点击、HTML5 拖放和指针拖放创建可移动、循环播放且可保存恢复的序列图层。`assetKind=character-animation` 但缺少有效帧时按损坏素材失败关闭,不回退首帧 PNG。
- 精选展示边界:`/creation` 精选卡片和预览弹窗继续扩展各自现有媒体 renderer,不复用或重构画布图层组件;卡片静止时只读取首帧,hover / focus 后才加载并播放完整序列,预览弹窗提供播放暂停。后台素材查询与精选审核继续共用 `AdminEditorAssetMedia`,列表只读首帧,预览弹窗才逐帧使用管理员换签。两端均按 `imageSequenceDurationMs / imageSequenceFrames.length` 切帧,下一帧未就绪时保留上一帧且禁止淡入;损坏动作不退回普通图片。
- 精选帧授权边界:公开换签只对当前已通过、已展示、返还完成且未删除的精选动作,按同 owner 的冻结帧 `assetObjectId` / `objectKey` 形成 exact grant;不从 `imageSrc` 或前缀推导。隐藏、拒绝、删除或快照损坏后逐帧授权随当前事务真相撤销,`read-url` 与 `read-bytes` 继续共用该判断。
## 2026-07-24 后台用户详情展示历史花费泥点
- 口径:`historicalConsumedPoints` 表示用户历史总消费,只累计 `profile_wallet_ledger.source_type = asset_operation_consume` 且 `amount_delta < 0` 的绝对值;`asset_operation_refund` 不冲减,充值退款追回、余额重置、赠送和退款 hold 均不计入。
- 投影边界:新增 `profile_wallet_consumption_total`,已有投影时消费流水成功落账在同一 SpacetimeDB 事务内按主键 O(1) 原子累加;退款不回减。首次上线在停止业务写入的维护窗口由 owner 调用 `POST /admin/api/profile/users/initialize-consumption-projections`,一次扫描全部权威钱包流水,为每个已有钱包流水的用户建立存量投影,成功后才能恢复流量。维护遗漏或新用户缺行时,首次消费和 runtime service identity 受限的 `admin_get_profile_wallet_detail_and_return` 都可按用户索引兜底重建一次;消费事务重建已包含当前流水,不重复加本次金额。不得用最近 50 条流水列表近似,也不得把全量流水扫描塞进充值订单每行复用的通用钱包快照。
- 对账边界:保留管理员显式手动对账。owner 始终可用;member 必须单独持有 `profile-wallet-consumption-reconcile` 独立操作权限,任意 Tab 都不隐式授予。`POST /admin/api/profile/users/reconcile-consumption` 经二次确认后调用 runtime service identity 受限 procedure,扫描该用户全部权威流水、比较并校准投影,记录管理员与对账时间。
- 展示边界:现有共享“用户详情”弹窗的钱包区增加“历史花费”,前端只展示 BFF 顶层字段,不自行汇总账单;只有 BFF 返回 `canReconcileConsumption=true` 时展示手动对账按钮。
- 验证方式:SpacetimeDB 钱包聚合测试、api-server / admin-web 定向测试、`npm run spacetime:generate`、`npm run check:spacetime-schema`、`npm run check:spacetime-runtime-access`、`npm run admin-web:typecheck`、`npm run check:encoding`、`git diff --check`。
## 2026-07-28 图片生成风格使用可扩展字段并以纯内存像素规整首发
- 契约:普通图片 / 角色共用的图片生成请求和图标图集生成请求增加可选字符串 `style`,当前公开合法值为 `none / pixelArt`。省略、`null`、空字符串和 `none` 归一为内部 `None` 且不告警;未知字符串、或在 `spec / quick-edit / ui-design / publication-material` 等不支持的图片 `kind` 上请求 `pixelArt` 时,按 `None` 继续原管线并返回 `unsupported-image-style` 通用告警;非字符串 JSON 返回 `400`。旧队列 payload 缺少字段时兼容为 `None`。
- UI 边界:只有普通 `生成图片`、`生成角色形象` 和 `生成图标素材` 显示 `像素艺术` 勾选项;当前选择可进入已有生成器快照和请求 / 队列 payload,但不写入 `generationInputs`、素材元数据或新表。画布 Agent 和其它生成 / 编辑入口不开放该选项。
- 处理边界:`PixelArt` 由 `platform-image` 的纯同步、纯内存 Rust 模块执行,不运行 Python、不访问 OSS / 数据库 / 画布。普通图片直接使用 provider 图;角色和图标必须等 BgFilter 成功并把 Alpha 回贴到 provider 原尺寸后,以 provider 平底原图分析网格、以透明 RGBA 图采样。固定参数为分析色数 16、Alpha 覆盖阈值 0.375、像素尺寸自动、相邻边缘峰间距使用线性插值 P30 估算步长、无固定色板、K-means 最大采样 262144;单格 RGB 按 Alpha 加权,输出 Alpha 只为 0 / 255,逻辑低分辨率结果用 nearest 恢复交付尺寸并跳过 Lanczos。2026-07-29 合并「角色带背景原图与透明图统一交付尺寸」后本条修订:像素模式不再豁免提前归一,网格分析源是已按业务像素矩阵 `resize_to_fill`Lanczos 重采样 + 居中裁切)后的交付尺寸平底图,不再是 provider 原生分辨率图;像素规整在交付尺寸上完成、由 snapper 自行还原回输入尺寸,因此不再执行后置的 nearest 二次恢复。
- 执行边界:像素规整 CPU 工作使用进程级最大并发 2;取得并发许可的排队时间与实际处理时间共享最多 30 秒预算,同时不得晚于当前请求 deadline,最终取更早者。输入图片任一边上限为 10000 像素、总像素上限为 8294400;超限、排队超时或处理超时均按 best-effort 非致命降级,不持久化部分结果。
- 去背边界:不修改 BgFilter `flat` 参数、`cross_check`、fallback、Alpha 回贴和默认关闭 despill 的现有行为。BgFilter 最终失败时不运行像素规整;像素规整失败按 best-effort 非致命降级,保留进入该步骤前的图片并通过既有通用 `warning` 完成任务,不退款。
- 持久化边界:逻辑低分辨率图、像素化前后对比图、预览、诊断和报告一律不持久化;像素模式只替换原本即将上传的最终图片字节。普通图片、角色、图标的 OSS PUT、asset / project resource 和画布 item 数量必须与 `None` 模式完全一致;角色 / 图标最多因复用失败增加一次对已有 provider 对象的 OSS GET,不得增加 PUT、资源类型、画布项、队列类型或 schema 字段。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/openapi/genarrative-external-v1.openapi.json`。
## 2026-07-28 画布 Agent 的通用 function-calling harness 与画布 prompt 分层
- 背景:画布 Agent 的 JSON 输出协议、tool schema 注入、memory / hook、轮次保护和“全部工具待确认即结束回合”原先位于 `platform-editor-agent/src/framework`,与规范展板、已有图编辑路由、模型超时和画布工具混在同一 crate;八类工具还重复携带待确认控制话术。旧 `platform-agent` 已随 Creative Agent 退役,不能作为新公共层复活。
- 决策:新增无旧玩法依赖的现役 `platform-agent-harness`,只承载业务中立的 function-calling 执行协议;`platform-editor-agent` 通过兼容 re-export 复用该 crate,并继续承载画布 LLM profile、角色 prompt、公共美术工具路由策略、图片上下文和工具实现。无工具场景同样注入 JSON 响应格式;prompt 不再宣称工具并发执行;request 级 system prompt 必须真实进入本轮请求。待确认卡片的对话路由必须使用正向、条件化语义:只在当前意图匹配一条现存 pending 调用时引导用户点击该卡片,该确认 / 取消意图不产生新 tool call;不在 prompt 中写“不得重新发起相同工具调用”一类全局否定句,因为实测证明模型会将其过度泛化为拒绝后续明确的新生成、修改或重做请求。cancelled 调用不再确认,pending 调用不阻塞无关新任务。
- 执行与失败决策:prompt 每轮通过 `AgentMemory::begin_staged` 使用与调用方 memory 行为等价、写入隔离的 `StagedAgentMemory` 事务;成功或已有工具活动时显式 `commit()`,直接 drop 表示回滚。无工具活动失败时回滚本轮 staged 增量,已发生工具活动后失败时提交已发生工具事实并追加 terminal error closure。外部 future drop / abort 若发生在工具完成后,提交工具结果与取消闭环;若发生在工具执行中,提交“已启动、结果未知”与取消闭环,后续先 reconcile,不能假装副作用未发生。harness 通过 `PromptRunError { error, partial_outputs }` 显式返回终态错误和失败前输出;结构化工具失败还必须向调用方保留 `ToolFailure.kind/retryable/fatal` 与原始 `output`,不在 harness 内压成单一字符串。api-server 的 18 分钟总 deadline 以 runtime future 下沉到 runnercompletion 可被 deadline 终止,工具在开始前检查、开始后等待返回、返回后携带结果收口;禁止外层 timeout drop prompt 或中途取消 effectful tool 后伪造空 partial。
- 保留边界:会话幂等、OSS 消息、120 秒前端软提示、20 分钟 transport、18 分钟 handler 总 deadline、1024 tokens、8 分钟 provider attempt、泥点计费、确认入队和 external job 懒回填均不进入公共 harness。SpacetimeDB schema、前端 wire DTO 和侧边栏 UI 不变。
- 验证方式:`cargo test -p platform-agent-harness`、`cargo test -p platform-editor-agent`、`cargo test -p api-server editor_agent`、`cargo check -p api-server --locked`、DDD 边界检查、Rustfmt、编码检查和 `git diff --check`。
## 2026-07-29 图标图集拆分数量只由有效连通域决定
- 背景:图标素材生成前端曾把单个提示词按换行、逗号、顿号等分隔符解析成描述数组,后端再用数组长度作为期望切片数。这会把“各种敌人头像:骷髅 哥布林 强盗 龙 蝙蝠等”一类自然语言错误地解释为固定数量,并在图集中存在更多有效素材时截断结果。
- 决策:画布前端不再从提示词解析素材数量,完整提示词作为 `iconDescriptions` 的唯一数组元素提交以兼容现有请求契约;后端仍允许其它调用方提交多条文本,但数组长度只参与 prompt 组装,绝不作为切片数量或切片命名依据。生成后的自动拆分与手动 `拆分图集` 复用同一套全连通域识别、视觉阅读顺序和 `素材 N` 命名,识别多少个有效素材就拆多少个;手动按钮与 `/api/editor/icon-spritesheets/slices` 路由继续保留。
- 失败与限制:两条图标拆分路径共同限制单边 `4096`、总像素 `2048×2048`、最多 `64` 个切片,并在持久化前完成校验。自动拆分仍是 best-effort,失败后保留整张透明图集并返回 `sliceWarning`;手动拆分失败返回接口错误。UI 设计图素材提取继续使用全连通域识别,不受提示词数量影响。
- 验证方式:调整既有前端提交、Prompt、连通域切片、上限和响应契约测试,不新增仅用于证明旧解析函数已删除的测试;运行前后端定向测试、类型与 Rust 检查、编码检查和 `git diff --check`。
- 关联文档:`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/openapi/genarrative-external-v1.openapi.json`。
## 2026-07-27 Anthropic 与流式统一使用 Provider 原生工具
- 背景:`platform-llm` 的 Anthropic 分支从未实现工具——请求体没有 `tools` / `tool_choice` 字段,`validate()` 还会以「Anthropic api_kind 暂不支持 function tools」本地拒绝,响应解析只取 `text` block 并硬编码 `tool_calls: Vec::new()`。App 侧因此在 `provider_request_builders.rs` 与 `interaction.rs` 用 `api_kind != Anthropic` 绕开原生工具,改用长提示词描述工具并要求模型输出单个 JSON object,等于让 Anthropic 退回 V1.26 之前的状态。三种协议的流式路径同样恒返回空工具调用,靠「无文本 → EmptyResponse → 非流式重打」兜底;模型若在工具调用前先输出解说文本,该兜底不触发,工具调用会被静默丢弃并把解说当成最终回复。
- 前提验证:MiniMax 的 Anthropic 兼容层与真实 OpenAI 均完整支持工具调用,说明这是本地实现缺口而非上游限制。实测覆盖 `tools` + 四种 `tool_choice`、并行多工具、`tool_result` 回传与流式增量;`tool_choice` 必须是对象,裸字符串返回 400。
- 决策:Anthropic 与 Chat / Responses 使用同一套原生工具目录。请求体顶层发送 `tools``name / description / input_schema`,无 `function` 包装层与 `strict`)与对象形态 `tool_choice``Auto → {"type":"auto"}`、`Required → {"type":"any"}`),响应解析 `tool_use` block 并把 `input` 序列化成 `arguments`;解除 `validate()` 对 Anthropic function tools 的拦截,`web_search`、图片内容和至少一条非 system 消息三条校验保留。App 侧删除两处 `api_kind != Anthropic` 守卫与对应的「Provider 不提供 function tools」提示词分支。
- 流式:三种协议的工具增量统一按槽位聚合成完整调用——Chat 用 `delta.tool_calls[].index`、Responses 用 `output_index``output_item.added` 给身份、`function_call_arguments.delta` 拼参数、`.done` 覆盖为权威值,并从 `response.completed` / `response.incomplete` 的 `output[]` 再兜底一次)、Anthropic 用 content block `index``content_block_start` 给身份,`input_json_delta` 拼参数,`content_block_start` 里的空 `input` 不得用于初始化)。收尾必须校验参数为完整 JSON,截断流不返回半截参数;`response.incomplete` 携带的工具调用按未完成响应拒绝,纯正文可作为降级结果保留。`LlmStreamDelta` 仍只承载文本,工具调用不进增量回调。上游已表明本轮是工具调用却一个都没聚合出来时返回 `StreamUnavailable`,让调用方回退非流式,不允许静默丢弃。
- 兼容边界:旧 wrapper 与 text JSON parser 只保留为历史响应、确定性 fixture 和模型不守协议时的降级解析,**不再是任何 Provider 的正常请求路径**`agent.runtime.tool_plan.protocol` 审计在 Anthropic 正常路径下取值为 `native_runtime_tools`。Chat 的 `ChatCompletionsToolCall` 字段放宽为可选并新增 `index`,否则流式后续分片(只带 `index` 与 `arguments`)会直接反序列化失败。
- 影响范围:`server-rs/crates/platform-llm`、`apps/ai-game-creator-shell/src-tauri/src/agent/interaction.rs`、同目录 `runtime_actions/provider_request_builders.rs`,以及 Runtime V1.1 与 App 实施计划两份技术方案。取代 2026-07-16「使用 Provider 原生工具目录」中把 Anthropic 与历史 fixture 并列的兼容描述、2026-07-12 关于 Anthropic 文本 JSON 回退的补充,以及 2026-07-24「统一 Interaction Loop」中「非原生 tool Provider 使用同构严格 JSON envelope 适配」的表述。
- 验证方式(当时记录):`cargo test -p platform-llm` 52 项通过,其中 8 个流式工具用例的 SSE 原文取自真实抓包;`server-rs/crates/platform-llm/tests/live_stream_tool_calls.rs` 为默认 `#[ignore]` 的真实端点验收,靠 `PLATFORM_LLM_LIVE_*` 环境变量运行,已对 MiniMax(anthropic / openai_chat / openai_responses) 与 OpenAI(gpt-4.1 openai_chat / gpt-5.5 openai_responses) 五种配置确认流式解析出完整工具调用。App 侧回归用 stash 对比法确认无新增失败——本机该测试套件存在大量与改动无关的既有失败,不能直接看绝对失败数。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-07-27 校正 platform-llm 流式工具验收证据边界
- 更正:上一条把固定 SSE fixture 的真实抓包来源、确定性 parser 覆盖和真实端点 smoke 合并描述,并写成“证明转录没有偏差”,超出了实际测试证据。本次验收命令为 `cargo test --manifest-path server-rs/Cargo.toml -p platform-llm`;固定 fixture 和本地解析测试只验证 parser / 配置归一结果,实时测试只验证最终归一后的工具名、id、完整参数 JSON 和文本增量字符数。测试数量随用例自然变化,不作为共享文档中的固定契约。
- 当前口径:`server-rs/crates/platform-llm/tests/live_stream_tool_calls.rs` 是默认忽略的真实端点工具调用 smoke;`on_delta` 只接收文本,工具调用从最终 `LlmRunResponse.tool_calls` 读取。现有测试没有原始 SSE 录制、事件类型/slot/分片顺序保存或逐事件比较,因此两类测试都不能证明 raw SSE fidelity 或抓包转录无偏差。
- 现有确定性流式工具覆盖应与普通 Anthropic 文本流测试分开统计:三协议真实来源 fixture、Responses 仅有 completed / incomplete 终态事件时的恢复、并行 slot 聚合、截断参数和无片段 `StreamUnavailable` 等用例共同覆盖 parser 边界;未来若需证明转录一致性,必须另行增加受控原始 SSE capture/compare 能力。
## 2026-07-27 图片画布左侧素材库统一稳定多选与批量操作
- 范围边界:本次只重写左侧素材库选择模式,不改变中央画布舞台和图层列表的选择、框选或下载语义。选择集合以全部上传完成且媒体地址有效的素材为有效性边界,不因搜索、折叠或展开变化而收缩;只有素材被删除、进入上传中 / 失败态或媒体地址失效时才清理对应选择和范围锚点。
- 输入语义:鼠标、键盘、触摸和笔输入单击都只切换当前素材,不替换其它已选素材;`Shift + 点击` 按当前可见顺序把连续区间增量加入现有选择,锚点当前不可见时退化为切换目标素材并建立新锚点。当前搜索结果的全选 / 取消全选只增量增删已展开的可见素材并保留其它选择,同时清空上次单项选择的范围锚点。退出选择模式、关闭素材栏或切到图层栏统一清空选择、锚点和框选状态,非选择模式不显示历史选中高亮。触摸素材卡仍可单击切换,但触摸列表空白区域必须保留纵向滚动,不启动框选;鼠标 / 笔框选使用素材列表内容坐标承接滚动偏移,并以 `pointerup` 的最终坐标提交,`pointercancel` 只取消框选。
- 工具栏与导出:批量工具栏作为素材滚动列表的固定非滚动底栏,展示跨搜索与折叠状态保留的全部已选数量,并提供当前可见范围全选 / 取消全选、下载、删除和取消。下载消费完整选中集合;删除完整选中集合时,如果其中存在当前未显示素材,必须先用危险确认弹窗明确展示全部删除数量和未显示数量,用户确认前不得执行删除。选择模式隐藏单行下载 / 重命名并禁用行拖拽和右键菜单;内置、上传未完成、上传失败或无可读来源的行显示为不可选择,不暴露虚假的可用按钮。移动端选择模式使用独立的侧栏高度状态,并让素材列表恢复纵向滚动,避免普通模式 `14rem` 高度上限被固定底栏、标题和搜索区吃完。一个选中素材直接下载,多个选中素材复用画布素材导出管线生成 `项目名-选中素材-YYYYMMDD-HHmmss.zip`,根目录为 `项目名-选中素材/`;单素材、序列帧和两类集合 ZIP 的下载名统一包含到秒的本地时间戳。素材卡整行是统一选择命中区,标题和空白区不得绕过单项切换或 Shift 范围处理。序列帧层的可导出性以至少一帧具有可读 `imageSrc / objectKey` 为准,不依赖层级 `src / objectKey`;单项、选中集合和整画布导出共用一个前端互斥锁,避免下载和状态提示互相覆盖。
## 2026-07-29 抽取通用多 Agent Runtime 公共内核第一阶段
- 背景:AI 游戏创作 Runtime 已有独立 Runner、持久任务、Provider 恢复、Goal、计划、静态/隔离协作和 finalization,但实现仍属于 Tauri package;内建 capability、Agent 目录和 Run Profile 缺少第二个产品可直接依赖的公开契约。
- 决策:新增独立 `agent-runtime-core` 纯 Rust crate,只依赖 `serde / serde_json`。第一阶段公开泛型 dispatch 的 Capability Registry、Agent Catalog、Run Profile Catalog 和 Completion Policy;不公开或复制 `.agent/runtime/**`、Runner IPC、Provider DTO、权限、执行器、Prompt 或游戏完成合同。
- 生产接入:AGC interaction 和全部 native Runtime function 由同一 registry 生成并反向解析,重复 capability/function binding 在构造时失败关闭;现有 Supervisor/部门角色和 `standard / autonomous-game-build` 作为 game adapter 注册,静态 Agent/profile normalization 读取 catalog。权限继续以 tool policy snapshot 为权威,完成继续以现有 finalization/自主游戏合同为权威,不形成双重事实源。
- 边界:动态 MCP 继续作为外部不可信目录独立校验;`platform-agent::game_creation` 继续保留游戏任务图和旧隔离合同。本轮不迁移当前正在演进的 GUI owner、Provider retry/handoff、Runner 和 sidecar schema,也不宣称已完成 Scheduler、Store、PromptSection、Artifact/Event 接口、多租户或远端 Runner。
- 验证:纯内核单测与无游戏语义文档审查 conformance、interaction/native registry、AGC adapter/profile 定向测试、AGC tests 编译、依赖树、encoding 和 diff 门禁。`npm run ai-game-creator-shell:check` 增加 `agent-runtime-core:check`,避免公共内核成为不执行测试的旁路 crate。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` V1.48。
## 2026-07-29 通用多 Agent Runtime 执行内核第二阶段
- 背景:V1.48 只有 capability/Agent/profile/completion 契约,无法在脱离 AGC 后执行 run`agent-runtime-core` 命名与实际能力不匹配。
- 决策:在同一 core 中增加 versioned snapshot、run/action/observation/event/delegation 模型、CAS `RuntimeStore`、`RuntimeClock`、`ToolHost`、per-Agent lane、确定性 step driver、spawn/all-join、completion 收束和 reconciliationaction 入队必须经 V1.48 `CapabilityRegistry` 验证,不允许 engine 绕过 catalog 执行未注册 capability。core 仍只依赖 `serde / serde_json`。
- 副作用契约:action 必须先独立 commit `executing` 再调用 ToolHost,调用后 observation commit 失败或宿主返回 Unknown 时进入 `needs-reconciliation`;重载和重复 resume 都不得重放 ToolHost。
- 生产接入:AGC 现役恢复队列的 running/waiting/pending/idle 优先级改由 core `next_recovery_step` 判定;AGC 仍负责 JSONL、去重、状态字符串校验、终态父回执抑制和后续 recovery driver。
- 边界:不迁移 Runner IPC、Provider/handoff、finalization 多文件提交、`.agent/runtime/**` 或游戏 completion context;这些仍是 AGC adapter/store 的唯一事实源,后续逐段迁移而不双写。
- 验证:非游戏文档审查 Runtime 已覆盖 action、双 child lane、反向完成下的稳定 all-join、observation commit 故障、序列化重载、零重放、显式 reconciliation 和 completion blocker/ready;完整 `ai-game-creator-shell:check` 退出 0。
## 2026-07-30 通用 LLM Provider 通过实例注册接入 Runtime Core
- 背景:`platform-llm` 已有 OpenAI Responses、OpenAI Chat 和 Anthropic 的稳定 HTTP/SSE 实现,但 Runtime/AGC 直接依赖 `LlmClient / LlmApiKind / LlmRunRequest`,新宿主无法只依赖通用内核注册 Provider。
- 决策:`agent-runtime-core` 新增中立 Provider instance/protocol ID、descriptor、七项能力、request/response/stream/error DTO、object-safe adapter 和 `Arc` registry。Provider 实例 ID 与 wire protocol ID 分离;同 protocol 可注册多个隔离实例,重复实例、未知实例、protocol 漂移和能力不匹配在 adapter 调用前失败关闭。
- 平台边界:`platform-llm` 实现三个 adapter 和可扩展 builder,只转换中立 DTO,仍复用唯一 `LlmClient::run/stream_run`、request body、auth、raw failure log 和 parser。Key、base URL、HTTP client 与 raw-log 目录继续绑定 `LlmClient/LlmConfig` 实例,不进 core 或进程全局 registry。
- 生产接入:AGC Agent interaction 的 stream、普通请求及原 stream-unavailable/empty/deserialize fallback 已改由 `ProviderRegistry` 执行;对外 `LlmStreamDelta`、function name/schema、错误文案、Runner IPC 和 Provider retry/handoff/finalization 持久协议不变。
- 扩展边界:新 Provider 可直接实现 core `ProviderAdapter` 并注册,不修改 core enum/match。`platform-llm` 当前 DTO 不支持的 tool role/result、toolChoice none/specific 和 reasoning minimal/x-high 在 adapter 转换层零网络失败关闭;工具调用仍以最终 response 为权威。
- 关联文档:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` V1.50。
## 2026-07-30 已有静态图片增加免费一键完美像素化
- UI 决策:图片选中浮动工具栏的栅格处理顺序固定为 `裁扩 → 去除背景 → 完美像素`。完美像素只对当前活动的静态栅格图层一键执行,不打开参数面板;音频、视频、图片序列和 `character-animation` 不显示。请求期间按 layer id 禁用并显示 busy,首个 await 前用同步 ref 防双击重复提交;结果保留源图并在右侧新增同尺寸 PNG。
- API 与执行边界:新增登录态 `POST /api/editor/images/pixel-art-snaps`,复用 `platform-image` 纯内存 snapper、进程级 CPU 并发 2 以及既有输入尺寸上限。该入口免费 inline,不调用外部 provider,不创建 `external_generation_job`,不打开或刷新任务侧栏,也不进入泥点扣费 / 退款;它与生成请求 `style="pixelArt"` 的 best-effort 后处理是两个契约。2026-07-31 修订:并发控制改为两层——端点级并发闸最大 4、等待队列上限 2048,必须在首次 IO 之前取得,队列满返回 `503` 并带 `Retry-After`,等待超预算返回 `504`;内层仍是共享的 CPU 并发 2。30 秒预算的起算点同时从「下载完成后」前移到 handler 入口,现在覆盖归属校验的 SpacetimeDB 读取、OSS 下载、两层排队与规整全过程,而不再只是 CPU 排队加处理。该端点的 OSS 读写共用带 `connect 10s / total 120s` 的进程级 HTTP 客户端,不再每次新建无超时客户端。来源解析同时对齐图集拆分:带 `sourceResourceId` 且 `sourceImageSrc` 能免查确认指向同一张图时,来源资源已随 owner-scoped 项目读取完成鉴权,改为显式断言 `resource.ownerUserId` 与 `resource.projectId` 后直接取用其 objectKey,不再做全账号项目与素材库扫描;两个字段指向不同图片直接拒绝,不退回扫描路径。跨记录 asset_kind 扫描随之省略,存储类型点查保留,动图仍由下载后的静态编码门禁按实际字节拒绝。
- 媒体与归属:前端先创建关闭 composer 的右侧占位,再解析或上传源图以取得稳定引用,随后 flush 包含该占位的当前项目布局;正式请求使用 `sourceImageSrc` 承载源图 `objectKey / resourceId / assetId` 候选稳定引用,`projectId / canvasCompletion` 必填且 `canvasCompletion.dialogId` 必须非空,并可携带 `sourceResourceId / assetKind / generationInputs / assetFolderId / assetLabel`。请求禁止 `data:` / `blob:`、signed URL 和普通外链。BFF 下载前必须将候选解析为当前 owner 已登记的私有 OSS object key,并校验 project / resource / asset 归属。
- 失败与持久化:已有图片入口使用 strict 语义,只接受静态 PNG / JPEG / WebP,拒绝 GIF、APNG、动画 WebP 和非静态素材。strict 完全复用生成风格的 legacy profile、峰值估算、单轴步长补全、walker、采样与编码;唯一差异是横纵两轴都未检测到步长时,不执行 `min(width,height)/64` 统一网格兜底而返回不适用。任一轴已检测到步长时,strict 与 legacy 行为及输出必须一致。读取、解码、输入校验、并发排队、像素规整或 PNG 编码失败 / 超时 / 不适用时,不保存原图副本冒充成功,不执行最终 OSS PUT,也不创建 asset object、project resource、账号素材或结果 layer。成功时只对最终 PNG 做一次 PUT,至多各创建一个 `editor_project_resource` 和一个 `editor_asset`;源图已有正式 project resource 时,结果以 `source_resource_id` 关联该资源,再由 `canvasCompletion` 写入至多一个右侧派生 layer;不保存逻辑低分辨率图、诊断图或前后对比图。
- 非事务边界:strict 零写入只覆盖首个最终 PNG PUT 前的引用 / owner / 项目 / 类型 / 静态编码 / 元数据 / 网格适用性 / CPU 处理门禁。进入持久化后沿用现有 `OSS + asset object → project resource → editor asset → canvas completion` 非事务顺序,后段失败可能保留此前已确认对象或记录;不做删除补偿或 unsafe POST 自动重放,按 `task_id / object_key / resource_id` 读取权威快照排障,跨系统单事务留待独立 procedure 方案。
- 占位删除与重试:completion 必须读取当前权威 dialog;若删除已先持久化,只跳过画布 layer / dialog 写回,不得使用请求中的旧 placeholder 复活图层,已经成功持久化的 project resource / 账号素材允许保留。若回包时本地占位已删除,前端不得应用完成快照或写历史;现有布局 CAS 没有 deletion tombstone,因此 completion 先提交、删除保存后冲突的极端竞态仍按权威快照收口,绝对“删除意图胜出”留待 targeted delete / tombstone 方案。该路由是 unsafe POST,客户端不得配置 `EDITOR_REQUEST_RETRY_OPTIONS`;请求字节可能已发出后不因 transport 异常或 `408 / 425 / 429 / 502 / 503 / 504` 自动重放,Bearer 中间件在 handler 前拒绝请求后的既有认证恢复继续保留。结果未知时先 GET 权威项目 / 素材快照,由用户显式决定是否再次执行。
- 历史边界:成功加入画布时写一条 `perfect-pixel` 历史,中文标签为“完美像素”,并纳入新增结果保护;撤销不得让派生 PNG 消失。像素处理失败或 completion 因占位删除未落画布时不写该历史。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【图片画布】撤销范围与操作提示方案-2026-07-17.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【编辑器】图片画布结构化持久化与迁移回滚方案-2026-07-19.md`。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-07-31 autonomous 单一任务图、预览 fail-fast 与逐 Agent 推理默认
- 决策:`autonomous-game-build` 只允许固定 manifest DAG 作为缺省首轮专业执行链;通用 Project Supervisor collaboration 仍服务 standard profile 和显式项目 policy,但不得再在 autonomous 缺省路径复制 code、quality 或视觉职责。
- 决策:预览的业务失败与浏览器基础设施失败分流。基础设施失败按稳定 kind 持久化并立即失败结束当前 runpreview readiness/playtest 的 manifest 完成分别绑定当前 revision smoke 和根合同 browser receiptread-only 文本交付不能绕过。
- 决策:规范 Agent reasoning 默认由角色职责分层,显式 per-Agent patch 优先;配置状态对外展示实际 timing/retry,避免全局文件、per-Agent resolver 与历史 run snapshot 混淆。
## 2026-08-01 生成风格 pixelArt 同时约束提示词
- 背景:`style="pixelArt"` 此前只驱动 provider 返回后的确定性像素规整,完全不参与提示词拼接。但 `platform-image` 的 snapper 是几何对齐器——先检测网格步长再按格重采样;provider 交一张柔和渐变图时横纵两轴都检测不到步长,生成路径使用的 legacy profile 会退到 `min(width,height)/64` 统一网格兜底,产出的是马赛克而不是像素画。也就是原语义等于「随便生成什么,然后强行网格化」。
- 决策:`pixelArt` 从「纯后处理风格」改为「提示词约束 + 后处理」。注入点固定在 `generate_editor_image_for_owner` 与 `generate_editor_icon_spritesheet_for_owner` 各自构造 `submitted_prompt` / `prompt` 的位置,包住既有 builder 的返回值,builder 签名与其既有输出契约不变。三个入口(登录态路由、外部 API v1、异步 job worker)都汇聚到这两个函数,一处注入全覆盖。`None` 必须原样返回原提示词。
- 作用域按链路分三条措辞,不共用同一句:普通图片没有抠像底色,用「画面为像素风格」;角色形象与图标图集生成后都要按纯色抠像,绿幕底必须保持平整,分别用「角色主体为像素风格」和「每个图标素材均为像素风格」,都不得出现「画面」级别的像素化要求,否则与同一段提示词里既有的「纯色背景必须平整无纹理、无渐变」互相拆台。角色形象的提示词已禁止出现角色以外的场景内容,因此只点名角色;图标图集一张图内是多个彼此分离的素材,需要逐个点名。
- 强度边界:实测只提「像素风格」效果已可接受,因此不注入网格密度、色板色数、抗锯齿等约束。约束句一律追加在提示词末尾并独立成行,不前置、不改写 builder 内部语句。`kind` 为 `spec / quick-edit / ui-design / publication-material` 时 `pixel_art_supported` 已把 `pixelArt` 降级为 `None`,注入对它们不生效;「修改图片」链路的 DTO 没有 `style` 字段,完全不受影响。
- 反向提示词:同步从画布四个生图入口(普通图片、角色形象共用一条,UI 设计图,修改图片两个 provider 分支)的 negative prompt 中移除「低清晰度」——该词按字面否定低分辨率,与以低分辨率重采样为本质的像素风直接对冲。其余玩法(拼图、消除、跳跃、方洞、大鱼、吠叫、自定义世界场景图)的同名词条不动,本次只收口画布项目。
- 持久化影响按链路不同,不能一概而论:`output_prompt` 初值是 `submitted_prompt`,但只有普通图片会保持到最后写入 `editor_project_resource` 的 prompt 列,该列因此从存用户原文变为存原文加一行约束句。角色形象链路的 `output_prompt` 在抠图成功后被无条件覆盖为 `"去除纯色背景"`,其原图 project resource 存的是 `role_setting`(用户原文),因此约束句在角色的任何 project resource 里都不出现。图标图集链路的原图 spritesheet resource 存工程化提示词(含约束句),透明结果存 `"去除纯色背景"`,自动拆分的切片存 `"自动拆分图集"`。不新增 OSS PUT、项目资源、素材记录或画布图层。
- 角色链路的完整提交提示词是否留存取决于 provider`persist_editor_provider_source_image` 写 asset object 元数据时用的是 `actual_prompt.unwrap_or(prompt)`provider 未回 `actualPrompt` 时才存 `submitted_prompt`(含约束句),回了就存 provider 改写后的文本。因此 provider 回 `actualPrompt` 的场景下 `submitted_prompt` 在系统内一处都不落——外部 API 审计的 `request_payload` 只记 `promptChars` 字符数,没有提示词原文。排障时按 `object_key` 查 asset object 元数据只在前一种场景下有效。该行为与 `web/master` 逐行一致,属既有可观测性缺口,本次未改。
- 响应体三条链路并不一致:普通图片和角色形象返回 `role_setting`(用户原文),前端显示不变;图标图集返回的是 builder 构造并追加约束句后的工程化 `prompt`,即调用方(含外部 API v1)能直接看到绿幕子句、间距要求和本次新增的像素约束。图标请求本身没有 `prompt` 字段(收的是 `iconDescriptions`),「返回用户原文」对它不成立。该响应字段行为同样与 `web/master` 一致,本次只是让被回传的模板多了一行。
- 上述 prompt 列的写入规则全部是既有行为,与 `web/master` 逐行一致,本次未改动一行。但该列同时被用户侧素材库搜索(`buildAssetSearchValues` 把 `asset.prompt` 计入匹配项)和后台素材查询页读取,而它当前混着三种语义:用户输入、工程化提示词、以及派生步骤描述。由此带来的模板噪声污染搜索(角色模板含「绿幕」「纯色背景」等词)、派生产物按源提示词搜不到(透明图的 prompt 列是 `"去除纯色背景"`)等问题均为存量,需单独立项与原设计者对齐后再动,不在本次范围内。
- 未覆盖:图标图集尚未约束各素材共用同一像素块大小(`estimate_step_size` 取全图相邻峰间距的第 30 百分位,块大小不一时步长估计会偏);角色形象提示词里既有的「严格基于图1的角色美术视觉规范的美术风格」与像素约束存在潜在冲突,未改写。两项都等实测。snapper 当前无任何日志,`resolve_step_sizes` 走检测还是走统一网格兜底在外部不可观测,注入效果暂时只能靠人工看图判断。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】画板角色形象生成入口设计-2026-06-15.md`、`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/openapi/genarrative-external-v1.openapi.json`。
## 2026-08-01 完美像素端点补齐保留审计字段剥离
- 缺陷:`POST /api/editor/images/pixel-art-snaps` 自新增之日起未调用 `sanitize_editor_client_generation_inputs`,只对 `generationInputs` 做了可序列化性校验(`serialize_editor_asset_metadata`)便原样写入 `editor_project_resource` 与 `editor_asset`。登录用户因此可以自行声明 `screenColorHex / mattingProvider / mattingModel`,让后台 raw mapper 看到伪造的处理元数据。该 sanitizer 与其余 14 个生产调用点(`editor_project.rs` 13 处覆盖普通图片、角色、图标图集、UI 设计、快速编辑、抠图、上传等,`external_editor_api.rs` 1 处)在此端点加入前就已存在,属于新端点漏配既有约定,不是设计取舍。补上本端点后生产调用点为 15 个。
- 决策:在 handler 解析 payload 之后、任何 IO 之前调用同一个 sanitizer,位置与其余入口一致。这三个字段是服务端产出的处理事实——`screenColorHex` 由背景色决策写入,`mattingProvider / mattingModel` 由 `apply_editor_matting_metadata_to_generation_inputs` 在 bgfilter 实际执行后写入——一律不接受客户端声明。完美像素是纯几何规整、不抠图(`model = "Perfect Pixel"`、`provider = "Genarrative"`),任何 matting 元数据出现在这类记录上本身就是伪造。
- 影响边界:只能污染攻击者自己的记录(`owner_user_id` 取自 access token,不可控),不构成越权、信息泄露或计费漏洞;该端点 `generation_cost_mud_points = 0`。危害限于按这些字段做的后台统计、排障与审计出现假数据。
- 验证:sanitizer 单元测试 `editor_client_generation_inputs_cannot_forge_internal_audit_fields` 已覆盖字段剥离与其余字段保留;端点接线由 `explicit_pixel_art_snap_is_inline_strict_and_persists_only_after_processing` 的顺序断言钉住,`sanitize_editor_client_generation_inputs` 必须排在 `resolve_editor_pixel_art_processing_deadline` 及之后全部 IO 之前,被挪到 IO 之后会直接失败。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-01 完美像素按钮补齐素材类型保存门禁
- 缺陷:完美像素按钮自新增之日起未接入 `isPersistingAssetKind`handler 也未复用 `persistingAssetKindLayerIdsRef` 同步守卫。把图层的非空 `assetKind` 改成另一个非空值后,在异步资源保存完成前点击该按钮,请求会同时带上新 `assetKind` 和旧 `sourceResourceId`,后端 `resolve_editor_pixel_art_snap_asset_kind` 检出请求类型与来源权威类型不一致直接返回 `400`。两道防护与相邻的拆分图集按钮在完美像素按钮加入前就已存在,属于新入口漏配既有约定。
- 决策:完美像素按钮的 `disabled / aria-busy` 与拆分图集共用同一套门禁(`isPersistingAssetKind || isPerfectPixelProcessing`),handler 侧同样先查 `persistingAssetKindLayerIdsRef` 再提交——`disabled` 只挡下一帧,同步 ref 才挡得住 `setState` 生效前的那一次点击。
- 无障碍:保存态名称不得直接复用拆分图集的「素材类型保存中」。`icon-spritesheet` 图层会同时渲染两个按钮,撞名后读屏用户无法区分控件,既有测试也会因 `getByRole` 命中多个元素而失败。完美像素改用与「完美像素处理中」同构的「完美像素等待素材类型保存」。
- 影响边界:`400` 发生在纯函数前置校验阶段,此时尚无 OSS PUT 与资源创建,不写坏数据;用户可在资源保存完成后重试成功,代价是需要手动清理失败占位。仅当「非空类型改为另一个非空类型」时触发——权威类型为空时走兜底分支不比较。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-01 完美像素归属校验单次取数并纳入处理预算
- 缺陷:`POST /api/editor/images/pixel-art-snaps` 在合法 `assetId` 且不带 `sourceResourceId` 时,取得端点准入许可后到 OSS 下载之间会顺序执行 8 次 SpacetimeDB 调用,且全是裸 `await`,第一次真正应用 30 秒预算的是下载。其中 `list_editor_projects` + `get_editor_asset_library` 这对全账号扫描重复了三轮——`resolve_editor_reference_object_key_for_owner` 内部两个子函数各扫一轮,`resolve_editor_pixel_art_source_for_owner` 再扫第三轮,拉的是同一份数据。
- 契约澄清:预算从 handler 入口「起算」不等于「覆盖」。此前文档写成覆盖 SpacetimeDB 读取是错的:deadline 是绝对时刻,早起算只让后续余额更少,中间不检查就不会在该阶段返回 `504`,请求会一路走到下载才失败并返回下载相关文案,误导排障。同时端点准入许可全程被这些无界调用占用,把并发闸自身变成瓶颈。
- 决策一(去重):参照抠图入口 `resolve_editor_background_removal_source` 的既有范式——扫一次,注册 ID 解析、归属校验和跨记录 `asset_kind` 收集全部交给 `_from_records` 纯函数在内存里完成。`resolve_editor_pixel_art_source_for_owner` 自行取一次 owner 快照后复用,不再调用 `resolve_editor_reference_object_key_for_owner`;该包装是给没有任何上下文的调用方用的,保持不动。归属校验命中已登记记录即短路,只有两份记录都查不到时才回落 `ensure_editor_reference_asset_object_owned` 点查。全账号 RPC 由 6 次降为 2 次。整段合法 `assetId` 链由 8 次降为 4 次——`get_editor_project`、`list_editor_projects`、`get_editor_asset_library`,加上恒定执行的 `get_asset_object_by_location`(承担存储 taxonomy 的非静态门禁,成本与账号规模无关,两条路径都保留);objectKey 未登记在 owner 任何记录里时多一次 `ensure_editor_reference_asset_object_owned` 的点查,最多 5 次。该兜底分支上同一个 objectKey 会被点查两次(归属校验一次、存储门禁一次,参数相同),可合并但收益远小于已削掉的全账号扫描,暂不处理。
- 决策二(预算):`get_editor_project` 到来源解析结束整体包进 `tokio::time::timeout_at`,超时返回 `504` 且文案指向归属校验而非下载。不重复写 `Instant::now() >= deadline` 预检——紧邻的 `acquire_editor_pixel_art_snap_permit` 已做该预检,成功即意味着未超预算,且本块首个 await 是网络 IO 不会立即就绪,不构成 `timeout_at` 先 poll 再判超时的陷阱。
- 验证:顺序断言新增 `tokio::time::timeout_at(` 与超时文案;参照 `937378ab9` 的做法用 `assert_function_occurrence_count` 把 `resolve_editor_pixel_art_source_for_owner` 内的 `.list_editor_projects(` 和 `.get_editor_asset_library(` 各钉为 1 次,并用 `assert_function_not_contains` 禁止该函数重新调用取数包装。后者断言的是调用形式 `resolve_editor_reference_object_key_for_owner(state` 而非裸函数名,否则会命中生产代码里说明「老包装保持不动」的注释——该陷阱在编写时即由测试抓出。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
- 预算:父 Run 接受请求后以 `240` 秒作为首版软预算;父 Run、所有 child Run、等待、回收和确定性验收共享 `300` 秒累计硬上限。硬上限是从 root `bound_at` 计算的绝对 deadline,必须包住 Provider、图片生成、文件写入、静态检查、`preview.validate` 和 final-reply 的在途等待;超时先强制持久化 `failed` 终态,再清理 pending action、Provider batch、confirmation、recovery 和进程会话,不能留下 `needs-reconciliation` 悬空态。软预算后不再扩展 Provider 规划,只能运行受控 fallback、`game.static_smoke` 和 `preview.validate`;硬上限未形成当前 revision 的通过证据时必须失败关闭,单轮确定性收束也必须复核累计时间,不能在上限后补写 completed。
- Provider:首版最多一次 Provider 规划 / 写入请求,禁止同一首版自动传输重试、第二次 tool-plan 或无限 repair。Provider 结束后由 Runtime 按当前 revision 依次执行确定性静态 smoke 与浏览器试玩。
- 兜底:fallback HTML 必须自包含、无远程运行依赖,从 `ready` 开始并真实绘制 Canvas,持续更新 `playable-web-game-state.v1`,提供键盘 / 触控、start / primary-action / restart 和胜负状态;primary-action 后可保持 `playing`restart 后可稳定恢复 `ready | playing`,不得开始前固定 `lost` 或用固定失败充当完成。fallback 只有在 `assets/art-spec.png` 已有效登记并真实存在时才能生成,而且必须把该平台图片显著绘制为主要背景、玩家和目标;不得以纯 Canvas 视觉绕过平台图片硬门。
- Windows preview 稳定性:非阻塞 listener 接受连接后必须先把 accepted socket 恢复为阻塞模式,再有界读完拆分到达的请求头;完整响应 `flush + shutdown(Write)` 后执行短时有界 drain。Chromium speculative socket 导致的 `ConnectionAborted / ConnectionReset / Interrupted / TimedOut` 归为可继续监听的瞬时 accept 错误;单个中止连接不得令后续 `preview.validate` 复用或重建时连续得到 `net::ERR_SOCKET_NOT_CONNECTED`。
- Runtime 恢复确认:GUI 自动扫描 `agent.resume` 前必须先用只读方式判断是否存在可恢复任务或 durable recovery artifact;全新项目与已完全终态、无任何恢复工作的项目直接返回空结果,不弹出“恢复未完成 Runtime 任务”;一旦存在 task、retry、handoff、finalization、pending action 或 reconciliation 等可恢复工作,仍必须经过原 `agent.resume` policy 门禁,不得通过吞掉 policy error 绕过确认。
- 每条输出入聊天:事件文件中的原始 `summary / detail` 仍是私有 Runtime 证据,不可由前端直接持久化。Rust 只对白名单用户进度生成 `publicText`,同时为每次真实追加生成 `eventId`action 重放沿用 action 身份,普通事件使用进程、毫秒与单调序列组成唯一身份。前端把 `eventId + publicText` 和四阶段专业 Agent 的 durable final reply 作为独立 assistant 消息,按顶层 `messageId` 幂等写入项目 conversation;重载恢复、轮询与实时事件并发不得重复或漏掉当前已观察输出。
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、`docs/project-memory/shared-memory/development-workflow.md`。
## 2026-08-01 完美像素未知结果先对账再定性
- 缺陷:`snapImageToPerfectPixels` 的 catch 对所有错误一视同仁——标 `failed`、`finally` 解锁、按钮恢复可点。transport 异常、abort 和 120 秒客户端超时因此被谎报成明确失败,而服务端此时很可能已经完成 OSS PUT、asset object、project resource、账号素材和画布回填,只是响应没回来。用户按提示重试就再造一整份对象、资源与素材。这直接违反本功能自己立下的契约:「结果未知时先 GET 权威项目 / 素材快照,由用户显式决定是否再次执行」。客户端未配 `EDITOR_REQUEST_RETRY_OPTIONS`(禁自动重放)这半条一直是达标的。
- 判别依据:`ApiClientError` 只在拿到服务端 `Response` 时由 `buildApiClientError` 构造,transport 异常、`AbortError` 和 `TimeoutError` 在重试判定后原样抛出。因此 `error instanceof ApiClientError` 即「服务端明确响应过、结果已知」,其余一律按未知处理。已知结果不发对账 GET,避免每个 `400` 都多打一次权威读取。
- 决策:未知结果先 `loadEditorProject` 取权威快照,再按占位是否存活分流。占位已被 completion 消费掉说明这次其实成功,按快照收口并写入正常的 `perfect-pixel` 历史,不报错。占位仍在说明画布没收到结果,同步快照消除本地与服务端偏差但**不写历史**,文案明确告知结果未知且素材库可能已有派生图、要求用户先核对再决定是否重试——持久化是非事务的,OSS 对象与账号素材可能已落库而画布回填未完成。对账 GET 本身失败时给出「权威快照读取失败」的独立文案,不退回谎报。
- 未覆盖:刷新页面后停在 `generating` 的占位仍无自动收口。占位在 POST 前已由 `flushProjectPersistence` 落库,`hydrateCanvasGenerationDialog` 原样恢复 `generating`,而该链路不进 `external_generation_job`,任务侧栏轮询看不到它,inline POST 的 Promise 随旧页面销毁。需要在 hydration 后加对账,且要先给 dialog 增加「属于无 durable job 的 inline 链路」标记,改动面大于本次,单独立项。客户端 120 秒超时相对服务端 30 秒预算是四倍冗余,调小可让对账更早发生,未处理。
- 验证:新增三条用例分别覆盖「未知但实际成功→按快照收口并写历史」「未知且占位存活→只同步快照、标失败、文案要求先核对」「`ApiClientError` 已知失败→不发对账 GET、不动快照」。既有用例 `keeps a failed perfect-pixel placeholder` 原本用裸 `Error` 表达「服务端识别不到网格」,语义不准且会误入对账路径,改为 `ApiClientError`。测试 harness 新增 `dialog-error` 输出,否则对账文案不可观测。
- 补充(同日):对账只对真正发出过 POST 的失败生效。占位创建、源图解析和 `flushProjectPersistence` 都在 POST 之前,它们失败时请求根本没发出,此时给出「素材库可能已存在派生图」是反向谎报,与本条要修的谎报是镜像关系;用 `perfectPixelPostAttempted` 标记划界,同时省掉一次无意义的权威读取。占位存活分支也不再调用 `applyProjectSnapshot`:传给该 hook 的是 `ImageCanvasEditorView` 的 `applyGeneratedProjectSnapshot`,其 action 默认值为 `generate-image`,不传 action 会写一条类型错误且受撤销保护的历史,而权威快照此刻与本地一致(占位都在),套用只会覆盖用户在请求期间的未保存编辑。这次 GET 的用途是判定,不是同步。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-01 完美像素持久化阶段由服务端显式告知客户端
- 背景:上一版对账用 `error instanceof ApiClientError` 判定「结果已知」,即「服务端响应过就等于没落库」。这个等式不成立——持久化非事务,`complete_editor_canvas_generation` 走 CAS 写入,冲突时 `spacetime-module` 抛「图片画布版本冲突」,经 `map_editor_project_error` 变成 `409``403 / 404 / 400` 同理。也就是说带响应的 4xx 同样可能发生在 OSS 对象、asset object、project resource 和账号素材全部落库之后。完美像素要跑满 30 秒,用户在这期间改动画布把 revision 推进并不罕见,因此该场景触发频率高于最初估计。
- 否决的两个方案:把所有 POST 后失败都当未知(每次常见校验失败多两次读取,且给不可能产生素材的场景附上「请核对素材库」的不适用提示);按状态码分类(`409` 确实只来自写操作,但 `403 / 404` 和 `5xx` 在持久化前后都会出现,分不干净,等于把猜测写进代码)。
- 决策:由服务端显式告知。`AppError` 新增 `with_detail_field`,在已有 details 上补字段而不是像 `with_details` 那样整体替换,保留下游写入的 provider / message——客户端要靠 message 定位、靠新字段决策。`snap_editor_image_to_pixel_art` 在第一次 OSS PUT 之后的四条失败路径(账号素材持久化失败、项目资源缺失、账号素材缺失、画布回填失败)置 `resultPersistenceStarted: true`。客户端只对「完全无响应」和「带该标记」的失败做对账,常见的纯校验 `400`、排队 `503`、预算 `504` 既不多打读取也不附加提示。
- 配套修复:对账成功分支补上 `hasCanvasGenerationDialogById` 检查——权威快照里占位消失有两种原因,服务端消费掉或用户在请求期间删除,后者契约要求不应用完成快照、不写历史,成功路径同一处早有这道检查而对账路径漏了。对账同时刷新素材库(新增可选 `refreshAssetLibrary` 贯穿 `ImageCanvasEditorView` → surface → workflow),否则只 GET 项目却让用户核对素材库,他看到的仍是旧列表,不满足契约的「项目 / 素材快照」。占位存活分支刻意不调 `applyProjectSnapshot`:传入的是 `applyGeneratedProjectSnapshot`,其 action 默认值为 `generate-image`,不传 action 会写一条类型错误且受撤销保护的历史,而权威快照此刻与本地一致,套用只会覆盖未保存编辑。
- 验证:服务端 `assert_function_occurrence_count` 把标记钉为 4 处,并用顺序断言要求它只出现在 `persist_editor_generated_image_owned` 之后——漏标一处或误标在校验阶段都会失败。客户端新增用例覆盖「带标记的 409 触发对账并刷新素材库」「未带标记的 400 不对账、文案保持服务端原文」「用户删除占位则不应用快照不写历史」。
- 文案边界:对账尾句只下指令、不断言素材库已刷新。`refreshAssetLibrary` 在 `canAccessProtectedData` 为 false 时直接 return,读取失败也只在鉴权错误时弹登录框、其余一律吞掉,返回 `Promise<void>` 不带成败信号,且它本身是可选 prop——三种情况下「已刷新」都是假话,会让用户对着旧列表判定「没有派生图,可以重试」,重新走回这条修复要避免的重复创建。刷新照旧调用(成功时用户白赚一份新列表),但文案在刷新失效时也必须成立。
- 未覆盖:刷新页面后停在 `generating` 的占位仍无自动收口(已由 2026-08-03 的 `requiresLiveSession` 条目解决)。客户端 120 秒超时相对服务端 30 秒预算是四倍冗余,未调整。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 完美像素刷新后的孤儿占位由归属标记收口
- 缺陷:完美像素在 POST 前 `flushProjectPersistence()` 把 `status: 'generating'` 的占位落库,随后走同步 HTTP。此时刷新页面,浏览器断连、handler future 被丢弃,服务端不会走完画布回填;新页面 hydrate 时 `status` 被原样还原(`isGenerationStatus` 认 `generating`),而加载期没有任何对账、轮询或重试 GET,占位就永久停在转圈状态。它还消不掉——Esc 被 `useImageCanvasKeyboardShortcuts` 的 `status === 'generating'` 挡,composer 关闭被 `closeCanvasGenerationComposer` 的同一判断挡。
- 归因:序列化设施是存量,但这个组合是本分支首次出现。同类路径逐条比对——去除背景的 `EditorBackgroundRemovalResult` 的 `queueState` 是非可选字段,恒走 durable job,worker 会在服务端替换占位;拆分图集根本不创建占位;图片生成等提交类链路在默认 `ExternalGenerationMode::Queue` 下同样入队,只有显式设 `GENARRATIVE_EXTERNAL_GENERATION_MODE=inline` 的部署才同步执行。完美像素的 handler 353 行全同步,`tokio::spawn` / `enqueue_` 各 0 次,`EditorPixelArtSnapResult` 无 `queueState`,是唯一「持久化 generating + 无 durable job」的链路。
- 方案取舍:不能从写入侧解决。`validate_editor_pixel_art_snap_placeholder_exists` 要求占位必须已落库,否则返回 `409`「完美像素画布占位不存在或尚未保存,请重试」——不持久化占位会让每一次完美像素都失败。POST 前那句 flush 正是为满足该门禁而存在。因此只能在读取侧收口,纯前端。
- 决策:给 dialog 增加 `requiresLiveSession` 标记,加载时由 `dropDeadInlineGenerationPlaceholders` 剥离置位且仍为 `generating` 的占位。判据是结构性不变量而非时间阈值:这类占位的收口只能由创建它的会话完成,而活着的那一份始终在内存里、永远不经过快照 hydrate,所以凡是从服务端快照读回来的必然属于已死会话。因此不需要时间戳,也不必猜阈值。队列型占位一律不置位——它们的 job 在服务端继续跑,误清会让用户以为操作没发生而重复提交。hydrate 只认布尔 `true`,缺字段的历史占位按队列型处理,不会被误清。
- 作用域:剥离只用在项目首次加载的两个调用点(会话缓存与权威快照都要,否则首屏会先闪一个永远转圈的占位)。**不能**下沉进 `hydrateCanvasGenerationDialog`——会话内 `applyQueuedEditorGenerationProject` 也会重新 GET 项目并套用,那时候占位对应的操作正在进行,套用剥离会把自己的活占位清掉。唯一置位点是完美像素占位的创建处。
- 提示文案:服务端持久化顺序 `OSS PUT → asset object → project resource → editor asset → 画布回填` 是非事务的,加载时还看到 `generating` 只说明最后一步没做完,前面几步可能已成功。所以不能断言「什么都没发生」,只提示「画布占位已清理,请确认素材库是否已生成派生图」,与同链路的对账文案同一口径。计数用累计值而非布尔——同一会话可能连着切换多个项目,布尔只提示一次。
- 已知残留:剥离是本地的,不主动回写。`applyProjectSnapshot` 会置 `skipNextProjectLayoutSaveRef`,加载后的第一次 effect 被消费掉,所以清理要等用户下一次布局变更才随防抖落库;在此之前重复打开会重复提示。刻意不强制回写:那会加剧多标签页问题——B 标签加载时会误判 A 标签正在跑的占位为孤儿,只在本地剥离时 A 的回填仍能成功,一旦立即回写就会让 A 撞上 `409` 占位不存在。
- 更正(2026-08-03):上一条里「多标签页另有 CAS `expected_revision` 兜底」是错的。CAS 挡的是基于陈旧 revision 的覆盖写,而 B 是以**当前** revision 写入一份合法布局,必然放行。触发也不需要 B 去点完美像素——B 加载后任何布局改动都会触发防抖保存,把「不含 A 占位」的布局写回服务端。后果已核到底:`complete_editor_canvas_generation` 在占位缺失时不报错,走 `Ok(None)`A 的响应是 200 且 `project: null`,A 的前端据此移除本地占位并提示「完美像素结果已保存到素材库,画布占位已不存在」。所以 A 的 OSS 对象、项目资源、素材库记录三样都在,用户也被准确告知,丢的只是画布自动落位,需手动从素材库拖回。定级 P3,真正的修法是给占位加会话归属标识、只允许创建者剥离,单独立项。
- 验证:`dropDeadInlineGenerationPlaceholders` 四条单测覆盖「剥离已死 inline 占位」「保留队列型占位(缺字段与显式 false 两种)」「保留已终态的 inline 占位与普通图层」「标记经 hydrate 与序列化往返不丢失」——最后一条钉住白名单式 hydrate 漏字段会让标记在一次「加载→保存」后消失。工作流测试新增 `live-session-dialogs` 探针,正向断言完美像素占位置位、反向断言去除背景占位不置位。`vitest src/components/image-editor` 893 通过 / 72 文件,typecheck、eslint、check:encoding 通过。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
- 显式协作合同:autonomous 的旧 `code-prototype + quality-review` 首批合同退出。显式 project collaboration policy 或持久 batch 恢复若进入首批 `agent.delegate` 路径,只允许且要求三个 Director 各一次;策划与程序 Director 是只读规划且 `expectedArtifacts=[]`,美术 Director 是非只读规范图任务且必须交付 `assets/art-spec.png`。任何非 repair 底层委派与 isolated child 都在首批失败关闭;默认 manifest DAG 仍是唯一自动首轮执行链,不额外复制三个 Director 委派。
- 输出决策:保留未提交 `streaming / ready` 的当前 revision 门;已提交的专业 Agent final reply 继续使用既有 durable response-stream 身份,后续项目 revision 变化不再隐藏早期阶段回复。
- 图集事务退役补充:九路径快照在创建和恢复读取时都使用跨平台不跟随符号链接 / reparse point 的文件句柄核算 64 MiB 总预算,marker、journal 与快照均通过有界双次读取和句柄元数据复核拒绝同长度并发改写;实际读取仍受剩余预算限制,稀疏或并发增长文件不能触发无界分配。恢复开始时锚定可信事务目录句柄,每次读取控制文件前后都复核目录身份,拒绝 rename、junction 或替换目录。恢复必须先把全部 journal 条目和九路径快照完成结构、大小与摘要校验并形成内存计划,随后缓存全部 canonical 路径的恢复前状态;每项落盘前再次校验目标与父目录,后续项失败时按逆序回滚本轮已应用项,但回滚前必须 CAS 证明目标仍等于本轮安装结果,外部修改不得被覆盖并进入 reconciliation。末尾路径竞态或快照损坏不得留下静默的新旧混合合同。`committed` 持久化后先删除并同步 `prepared`,再清理 `.previous / .replacement`、同步 canonical 合同并最后删除事务目录;递归删除中断后最多留下只有 `committed` 的可清理事务,不能重新落入 rollback 分支。
- 关联:`apps/ai-game-creator-shell/scripts/start-tauri-dev.mjs`、`start-dev-stack.mjs`、`src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`、`response_stream.rs`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-03 完美像素对抗性审查第一批修复
- 范围:本分支相对 `web/master` 的 17 条审查发现里,只修其中三条——它们互相独立、改动小、无需设计决策。其余按链路分批,队列化改造(把完美像素接进 `enqueue_editor_generation_job`)因改动面超出本分支预期而未采纳。
- A1 持久化标记漏洞:`snap_editor_image_to_pixel_art` 对 `persist_editor_generated_image_owned` 用的是裸 `?`,而该 helper 内部顺序是 `PUT → HEAD → confirm_asset_object`。HEAD 或 confirm 失败时 OSS 对象已存在,错误却不带 `resultPersistenceStarted`,客户端的 `outcomeMayBePersisted` 因此为假、对账根本不执行,直接报普通失败;用户重试会用新 `task_id` 生成新 object key,首个对象成为无从发现的孤儿。这是三条里唯一会让收口机制完全不触发的。
- A1 的标记边界:标在 helper 内部而不是调用点——调用方拿到的是同一个 `AppError`,无法自行判断内部走到了哪一步。边界取在第一次 PUT`prepare_put_object` 与「OSS 未配置」这两处失败都在 PUT 之前,标了会让客户端对着什么都没落库的失败去核对素材库,是与本条镜像的反向谎报。PUT 自身也标——响应丢失时字节可能已落盘,属于契约要覆盖的未知结果。共四处:PUT、HEAD、asset object 入参构造、`confirm_asset_object`。该 helper 为 9 条编辑器持久化流程共用,新增的 details 字段对其余调用方语义同样成立(持久化确实已开始),只是目前只有完美像素前端消费。
- A3 失败反馈第三态:catch 尾部只处理「有占位」与「没有 dialogId」,缺「有 dialogId 但占位已被删」。删除生成中占位是产品支持的流程(`requestRemoveCanvasGenerationDialog` 对 `generating` 会先弹确认),用户删完之后请求才失败时,`errorMessage` 被算出来又整段丢弃,界面零反馈。丢的不只是失败提示——对账得出的「请确认素材库是否已生成派生图」在同一句里,用户会在毫不知情的情况下重试。改为 `else` 兜底走全局提示。兄弟路径 `split-atlas` 无条件 alert、`remove-background` 直接 rethrow,都不存在这个第三态。
- E1 测试并行竞态:`pixel_art_snap_permit_reports_exhausted_budget_without_waiting` 对进程级 `EDITOR_PIXEL_ART_SNAP_QUEUE_DEPTH` 做绝对断言 `== 0`,而相邻用例会在自己的作用域里持有两个 guard,Rust 测试默认并行,两者撞上就随机变红。当时改为 before/after 相对断言,但后续第三批确认两次 load 之间仍可被并行用例插入,该方案未修复竞态并已撤回。过期预算用例只应断言 `504`;guard Drop 由相邻独立用例负责。
- 验证方法:两条修复都先回退生产代码确认测试变红,再恢复。前端新用例在缺 `else` 分支时超时失败;服务端守卫在去掉任一处标记时报 `left: 3, right: 4`。首次验证时跑错了测试名——`explicit_pixel_art_snap_is_inline_strict_and_persists_only_after_processing` 里已有一组同名断言(钉的是 handler 内四处),新加的这组在 `editor_matting_releases_source_buffers_at_oss_boundaries`,两者同名不同域。
- 验证结果:api-server 676 通过 / 3 失败(`wallet_refund_outbox` 本机环境失败,与基线一致);`vitest src/components/image-editor` 894 通过 / 72 文件;`cargo fmt --check`、typecheck、eslint、`check:encoding` 通过。
- 未修(已立项):A2 客户端 120s 早于服务端最坏合法时长(约 270s);B1 并发闸许可跨越无预算的持久化阶段;C1/C2/C3 `requiresLiveSession` 链路;D、E 组其余清理项。E1 当时仅改成相对断言,后续第三批重新打开并完成修正。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 完美像素静默路径收口(审查第一批续)
- 背景:第一批只修了失败尾部那一处静默(`else` 兜底)。复审指出对账分支还有一处早返回同样不说话,追查后发现它有个孪生分支在成功路径上,两处形状一致——操作其实已经落库、用户删掉了占位、没人告诉他素材库多了一份。
- 决策:提示与「移除占位」解绑。成功路径 `!result.project` 分支原先把提示写在 `if (hasCanvasGenerationDialogById(...))` 里面,占位不在就整段静默;改为无条件提示、条件移除。对账分支 `!placeholderSurvived` 且本地占位也没了时,不应用快照仍然正确(删除意图胜出),但要补同一句提示——走到这里意味着权威快照里占位已被 completion 消费,本分支下一步正是据此把结果当成功套用,结论一致:结果已落库。
- A4 的取舍:会话缓存里剥掉的占位数**不能**直接并入提示计数。完美像素成功后 `applyProjectSnapshot` 会置 `skipNextProjectLayoutSaveRef`,加载后的第一次 effect 不落库,所以会话缓存可能停留在完成之前的版本;重新加载时缓存里那个陈旧占位被剥掉、计数加一,而权威快照其实是成功的,并入就会报一条「上次处理未完成」的假告警。改为单独计数,只在权威加载失败、没有第二个来源可以纠正这幅画面时才提示。
- A4 的可观测性核实:`isProjectReady` 只被启动意图消费和自动保存 effect 使用,不参与画布渲染门禁,所以权威加载失败时画布照常显示,用户看到的确实是一张静默少了占位的画布,提示有必要。鉴权失败与项目失访两条路径各自弹窗或跳转,不在这里重复打扰——为此在 `replaceAppHistoryPath` 后补了 `return`,该分支原本就没有后续语句,行为不变。
- 验证:新增用例覆盖「对账发现两侧占位都没了 → 仍提示素材库结论」,去掉提示后该用例失败。`vitest src/components/image-editor` 895 通过 / 72 文件,typecheck、eslint 通过。
- 未覆盖:A4 没有专用测试。`readEditorProjectSessionCache` 是持久化 hook 内的局部函数而非可 mock 的模块,要测得在 jsdom 里按缓存键格式播种存储再让权威加载失败,成本高于这三行改动本身;改动本身是「捕获计数 + 失败分支上报」,无分支逻辑变化,暂按未覆盖记录。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 完美像素持久化阶段纳入预算,服务端最坏时长收进客户端超时
- 缺陷:处理预算(30 秒)只覆盖到规整为止,持久化阶段完全无界,仅受 OSS 客户端每请求 120 秒约束,而 PUT 与 HEAD 各自独立计时,再加三次无超时 SpacetimeDB 调用,服务端最坏合法时长可达 270 秒以上,远超客户端 `snapEditorImageToPixelArt` 的 120 秒。客户端因此会在服务端仍在合法工作时先 abort:对账虽然照常执行(abort 不是 `ApiClientError``outcomeMayBePersisted` 为真),但它采样的是一个仍在途的操作——占位还在、`confirm_asset_object` 未跑完所以素材库还空,用户照提示核对什么也看不到,重试就用新 `task_id` 造出孤儿 OSS 对象。
- 决策:给持久化整段套独立预算 `EDITOR_PIXEL_ART_MAX_PERSISTENCE_DURATION = 60` 秒,用第二个 `tokio::time::timeout_at` 包住从 `persist_editor_generated_image_owned` 到 `complete_editor_canvas_generation` 的全部写入。服务端最坏 30 + 60 = 90 秒,落在客户端 120 秒内并留 30 秒余量给网络往返与计时精度。
- 为什么独立起算而不与处理预算取 min:持久化已经付出了 OSS PUT 的代价,因下载慢而被砍预算、中途放弃只会留下孤儿对象。取 min 会让「下载越慢、越容易留孤儿」,方向正好反了。
- 超时必须带标记:这条超时发生在 PUT 已经发出之后,对象可能已落盘也可能没有,正是 `resultPersistenceStarted` 契约要覆盖的未知结果。不带标记客户端会判成确定失败、直接诱使用户重试。handler 内该标记的钉定计数因此由 4 升为 5,注释同步说明第五处是什么——这个升级由既有守卫自己报出来(`left: 5, right: 4`),不是事后补记。
- 验证:新增 `pixel_art_server_worst_case_fits_inside_the_client_timeout` 钉住跨端不变式,把两侧数值和 30 秒余量都写死;顺序守卫新增「预算在前、写入在后」与超时文案 + 标记两项,任何把 persist 挪到 `timeout_at` 之前的改动都会失败。把持久化预算临时调到 120 秒可确认该测试变红。api-server 677 通过 / 3 失败(`wallet_refund_outbox` 本机环境失败,与基线一致),`cargo fmt --check` 通过。
- 未覆盖:跨端不变式靠常量断言维系,客户端那侧的 120 秒仍是 `editorProjectClient.ts` 里的字面量,改动它不会让 Rust 测试失败。真正的双向钉定需要共享契约常量,本次未做。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 完美像素孤儿占位判据由结构不变式改为有界时间窗
- 缺陷:`dropDeadInlineGenerationPlaceholders` 原先依赖一条结构性不变式——置位 `requiresLiveSession` 的占位其收口只能由创建它的会话完成,而活着的那份始终在内存里、永不经过 hydrate,所以从服务端快照读回来的必然属于已死会话。这条在单标签页下成立,多标签页下是假的:B 标签打开同一项目会 hydrate 到 A 标签正在用的活占位,据此剥离,再由 B 下一次布局保存以**当前** revision 合法写回,把 A 的占位删掉。
- CAS 的作用要说准:它挡的是基于陈旧 revision 的覆盖写。B 在 A 完成**前**写入时 revision 是当前的,CAS 放行——这是有害的那一半;B 在 A 完成**后**写入时 revision 已陈旧,CAS 拒绝——所以「B 把 A 的成品图层写没」这种更严重的情况本来就不会发生。此前 decision-log 笼统写「CAS 兜底」是错的,纠正后也不应反过来说 CAS 完全无用。
- 决策:判据改为有界时间窗,只有超过 180 秒才判定为孤儿。窗口上界由两侧共同封死——服务端最坏合法时长是处理 30 秒加持久化 60 秒(都由 `timeout_at` 强制,见同日持久化预算条目),客户端整个 POST 又被 120 秒超时封顶;120 秒之后客户端必已 abort 并把占位改成 `failed` 或移除。取 180 = 120 客户端上限 + 60 余量(网络往返、标签页挂起后的时钟漂移)。
- 关键依赖:这个方案在持久化预算落地**之前**不成立。那时服务端最坏时长无界,任何时间窗都是拍脑袋;把最坏时长收进 90 秒之后,时间窗才有硬依据。
- 复用既有字段:时间戳用 `generationStartedAt`,由 `withGenerationTimestamps` 在占位进入 `generating` 时自动打戳,已序列化、已 hydrate,无需新增字段。
- 撤回先前方案:此前多次记录「真正的修法是给占位加会话归属标识、只允许创建者剥离」。该方案不成立——B 拿到一个不同的会话 id,推不出 A 是死是活,照样只能猜。会话 id 只能识别「不是我的」,不能识别「已经没人要了」。
- 兜底方向:缺 `generationStartedAt` 时按可剥离处理。实践中不会出现(两个字段同一次创建一起写),但按「保留」会让这类占位永久留在画布上,按「剥离」最坏只是退回引入时间窗之前的行为。
- 时钟:取读取方的 `Date.now()`。同机多标签共享时钟,正是要修的场景,判定精确;跨设备有偏移风险,但此前是无条件剥离,任何时间窗都不会比原行为更差。
- 已知残留:A 真死了而用户在 180 秒内重新加载时,占位会继续转到窗口过后的下一次加载才清掉。可以加客户端定时器在剩余时间后自行收口,但要多一套定时器生命周期管理,先不加,观察实际是否困扰。
- 验证:新增四条用例覆盖窗口内保留、边界包含式、超窗剥离、缺时间戳兜底;去掉时间窗判断后其中两条变红。`vitest src/components/image-editor` 899 通过 / 72 文件,typecheck、eslint 通过。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 完美像素并发闸竞争路径与 snapper 错误分档补测
- 背景:审查留下的两处覆盖缺口。并发闸的三条竞争路径(队列满 503 + `retry-after`、等待槽位超时 504、信号量关闭 503)此前零测试——`retry-after` 在整个 crate 里只出现在生产代码一处;`map_editor_pixel_art_snapper_error` 的四档状态码只有 `GridNotDetected → 422` 被间接覆盖。
- 测试方式的取舍:不去把全局信号量或队列计数打满。两者都是进程级 `static`,在测试里填满会让并行跑的其他用例连带失败——这与当时误以为已修掉、后续第三批才真正纠正的 E1 属于同类竞态,不能一边修一边再造一个。改为把三条路径的错误各自抽成构造函数,直接断言状态码、文案和 `retry-after` 头;「哪条路径用哪个构造函数」由 `snap_editor_image_to_pixel_art` 的既有顺序守卫钉住,CAS 边界本来就有本地计数器的用例覆盖。
- 覆盖边界要说清:这样覆盖的是错误形状与分档,不是端到端的竞争行为。真要覆盖后者需要把限流器与队列计数改成依赖注入,改动面超出补测本身,未做。
- 分档的双向后果写进了断言注释:把用户上传的坏图(`Decode`)报成 500 会让客户端当服务端故障去重试;把服务端自身失败(`Encode` / `Processing`)报成 400 又会让用户以为是自己的输入有问题。另断言底层文案原样带上,否则「识别不到网格」与「解码失败」在用户侧无法区分。
- 验证:删掉 `retry-after` 或把 `Decode` 改判 500,两条新用例分别精确变红。api-server 679 通过 / 3 失败(`wallet_refund_outbox` 本机环境失败,与基线一致),`cargo fmt --check` 通过。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 托管 MCP 未鉴权响应提供安全接入引导
- 决策:`/api/external/v1/mcp` 缺少、格式错误或无法验证 Bearer API Key 时继续返回相同 HTTP `401`,并增加 `WWW-Authenticate: Bearer realm="genarrative-external-editor"` 与机器可读 `details.guide`。引导只说明 Bearer Header 格式、登录后在「开发者 API Key」创建密钥、原始密钥只显示一次、凭据不得进入聊天或仓库、配置后重试 `initialize`,以及公开 manifest、Skill 与 OpenAPI 地址。
- 安全边界:三种鉴权失败不得通过 code、message、details 结构差异暴露 Key 是否存在;未鉴权响应不得包含 MCP tools、resources、owner 或内部鉴权诊断。其它 External v1 业务路由继续使用原通用 401,不继承 MCP 专用引导。
- 关联:`server-rs/crates/api-server/src/external_api_auth.rs`、`server-rs/crates/api-server/src/modules/external_api.rs`、`docs/openapi/genarrative-external-v1.openapi.json`、`docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md`。
## 2026-08-03 Agent Runtime Prompt 使用版本化 Bundle
- 决策:把 `prompt.rs` 中依赖长自然语言精确匹配的链式 `.replace()` 拆成仓库内版本化 Prompt Bundle`build.rs` 读取、校验并生成静态 Rust 定义编译进 Tauri 二进制,生产源码不再直接引用 `prompts/runtime` 的单个 Markdown。
- 边界:Supervisor 的角色选择、并行委派、all-join、视觉返工、claim gate 和 repair 自然语言合同,以及 Supervisor / 六组专业 Agent 的编译期静态节点目录进入 Bundle。Bundle 不是完整可执行 graph,也不是生产 Skill;正式 DAG 依赖边、权限、安全门和完成合同继续由 Rust、`shared-contracts` 与校验后的项目协作策略掌控。
- 一致性:manifest 是 section、组合顺序、平台 / Editor 变体、role overlay、Provider 协作 fragment 和静态节点目录的单一来源;role overlay 只接受 `rootSourceKind` 强类型语义 selector,构建期拒绝未知 kind,运行期再把权威 source 常量映射为生成的 kind,禁止在 manifest 中复制易漂移的 durable source 字符串。isolated / all-join、autonomous 首轮、首批协作修复、delivery 收敛、manifest wait、试玩后续委派,以及 Supervisor interaction / background planning / final-reply 身份合同不得在 Provider 或 `prompt.rs` 源码中复制。公共 runtime system header 保持身份中立;Supervisor 共享核心身份与最终回复专属规则拆成两个 sectionbackground planning 的 system composition 必须复用核心身份 sectioninteraction / final-reply 的 system prompt 按 manifest composition 组合核心身份与最终回复两段,所有 user context 都不重复注入 Supervisor 身份合同正文。每个 section 只能属于 runtime composition、Supervisor composition、chat 字段、platform variant、visual variant、role overlay 或 Provider fragment 中一个语义所有者;唯一例外是同一 identity section 由 Supervisor planning 与 `supervisorChat.identity` 显式复用。这样同时阻断 Supervisor 指令外泄和动态 variant 与静态 composition 的重复注入。构建脚本同时监听 Bundle 每一级目录、manifest 和已登记 section,保证任意嵌套目录新增孤立 Markdown 都会触发增量构建并失败关闭;同时拒绝未知字段、非法 / 重复 / symlink 路径、未知 / 重叠 selector、节点 / alias / 生成标识符冲突,并强制 Supervisor planning composition 复用 chat identity、专业节点 taskId / group / role 与正式 seed DAG 一致。原生工具目录仍从 `agent_runtime_native_executable_tools()` 生成,`mcp.call` 不混入静态原生目录;MCP 工具只从当前请求的动态 catalog 暴露。
## 2026-08-03 AI 游戏生成泥点不足使用确定性中断说明
- 决策:钱包返回 `泥点余额不足` 或 `可消费泥点不足:...` 时,API 统一按 HTTP 409 业务冲突处理并只公开固定的“泥点余额不足”;Agent Runtime 转换为稳定原因 `mud-points-insufficient`,禁止进入瞬态 Provider 自动重试。
- 恢复边界:泥点不足表示平台已经明确拒绝计费与生成,即使本地保留 accepted External Generation ledger,也必须标为 `failed`,不能因账本存在而进入 `needs-reconciliation`。充值后的新输入“继续”仍走现有失败 successor 合同,从原任务和当前已提交项目事实接着完成,不重放旧生成请求。
## 2026-07-31 External v1 生成统一异步并提供托管 MCP 与完整 Skill 包
- 异步契约:External v1 的图片生成、图片编辑、图标图集、UI 素材提取、角色动画、视频、音效和背景音乐八类 POST 固定持久化入 `external_generation_job` 并返回 HTTP `202 + operationId/statusUrl/pollAfterMs`;不受站内 `GENARRATIVE_EXTERNAL_GENERATION_MODE=inline` 影响。每次逻辑生成必须携带稳定 `Idempotency-Key`;提交结果未知时复用原 endpoint、原始正文和原键恢复 POST,轮询超时时保留已有 `operationId` 并只继续 GET,不得换键重提。
- 查询与结果:新增 owner-safe `GET /api/external/v1/generations/{operationId}`。`queued/running` 返回 phase/progress`completed` 返回 compact 稳定 artifact 引用,`failed` 返回脱敏错误,跨 owner 按不存在处理。compact result 允许 objectKey、resource/asset ID、assetObjectId、尺寸、媒体类型、taskId 和告警;禁止完整 project/canvas、Data URL、Blob URL、临时 signed URL、内部 provider 原文和 lease/fencing 控制字段。
- 客户端 durable 查询约束:私有生成账本同时绑定 base URL/API Key 配置指纹,指纹不一致不恢复 POST 或查询旧 operation。旧 `200` 兼容结果只持久恢复允许字段和安全媒体引用。operation 明确 failed 的账本保留到 pending observation 和 Provider batch 终态落盘后再清理。只有首次提交直接取得契约明确的 `400 / 401 / 403` 才可判定为入队前拒绝并清理 prepared 账本;首次结果已经未知后,恢复 POST 的临时 `401 / 403` 等响应不能证明原请求未入队,不得删除账本。其它非成功状态一律保留账本进入对账。账本路径解析、扫描和删除逐级拒绝符号链接,非法控制路径失败关闭。
- MCP:新增托管 `/api/external/v1/mcp`,使用现有 External API Key Bearer 鉴权和无协议 session 的 Streamable HTTP JSON direct 模式。MCP tools 从同一 OpenAPI operation 形成并复用 External REST router;生成 tool 显式要求 `idempotencyKey`,另有统一任务查询 tool。MCP resources 提供使用说明、OpenAPI、Skill 入口 `SKILL.md` 和 `references/capability-routing.md`、`references/api-operations.md`、`references/authentication-and-safety.md`、`references/requests-and-outputs.md` 四篇稳定 reference;日后新增 reference 时必须同步新增独立 resource。MCP Agent 直接调用托管 tools,不安装 CLI,也不将脚本、测试或 workflow 暴露为 MCP resources。禁止开放内部 SpacetimeDB MCP、worker procedure、controller 或队列控制面。
- Agent 发现:新增公开 `agent-integration.json`、`skill/SKILL.md` 和 `skill.zip`。manifest 同时声明 MCP、OpenAPI、完整 Skill archive、SHA-256 和包内清单;archive 必须包含 `SKILL.md`、上述四篇 references、stdlib Python helper 和 `agents/openai.yaml` 七个声明文件,不能只提供 OpenAPI JSON,也不能包含 API Key、本机路径或个人配置。完整 `skill.zip` 只供不支持 MCP 或需要本地文件上传编排的 Agent 使用,不作为 MCP resource。
- 兼容边界:这是基于「截至 2026-07-31 尚无外部第三方存量调用方」接受的 v1 原地 breaking change;一旦出现外部活跃 Key、公开契约或联调方,后续破坏性变更必须保留兼容、经过弃用期或升级 `/api/external/v2`。
- 关联文档:`docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md`、`docs/technical/【后端架构】外部生成Worker化方案-2026-06-03.md`、`.codex/skills/genarrative-external-editor-api/SKILL.md`。
- 2026-08-23 补充:新增 `POST /api/external/v1/editor/images/background-removals` 后,External v1 生成 POST 由八类增至九类;该入口继续使用 `editor:image-generate` scope、稳定 `Idempotency-Key`、`202 + operationId` 与统一查询合同。外部请求只允许 OpenAPI 声明的去背景字段,拒绝内部 `taskId` 和其它未声明字段;入队前按当前 owner 解析稳定来源并规范化为权威 objectKey,同时预检、规范化项目与素材目录目标,未登记、越权引用和无效目标不创建任务;任务 ID 仅由服务端队列生成。托管 MCP 不再维护异步生成 operation 的幂等硬编码名单,而是从 OpenAPI operation/path 的 required `Idempotency-Key` header 自动生成 `idempotencyKey` 工具参数并转发同名 HTTP 头,避免新增 operation 只出现在 `tools/list` 却无法实际提交。
## 2026-08-04 图片画布 BGM Prompt 采用唯一可见规范化文本与面板级同步提交锁
- 背景:图片画布背景音乐链路原先会在前端、BFF 和 Suno body 构造阶段执行不一致的 Prompt 清理,并为空值提供默认回退,可能形成输入框不可见的实际提交文本;新增预设和 AI 助手后,需要统一唯一可见 Prompt、首尾 Unicode 空白 canonicalization,以及与 SFX、请求字段和 External v1 的边界。
- 决策:本规则只适用于 `/editor/canvas` 的 `audio-background-music`,并在该模式内取代 2026-08-03「编辑器持久化的 `prompt` 统一表示规范化用户意图」的泛化表述;其它图片、角色和图标生成语义不变。输入框是唯一 BGM Prompt 真相,预设可见文本和 AI 成功结果写回后都成为当前 Prompt;不得在输入框之外维护或向 Suno 发送另一份隐藏 Prompt。进入预设追加、AI 补全、简化、撤销或正式生成边界前,只允许执行一项 canonicalization:移除首尾属于 Unicode `White_Space` 属性的 code point,并在后续动作前把结果同步写回同一个输入框;TypeScript 不得用会额外移除 U+FEFF 的 `String.trim()` 代替 Unicode `White_Space` 判定。canonical Prompt 内部的空格、CR / LF 和其它 Unicode 空白保持原位,U+200B、U+FEFF、组合字符、ZWJ emoji 等非 `White_Space` code point 即使位于首尾也必须保留;除此之外不得做 NFC、空白折叠、换行转换、标点替换或静默截断。canonical Prompt 满足 `有效字符数 = 0 ⇔ 总字符数 = 0`,所以任意长度的纯 Unicode `White_Space` 原始输入规范化后都禁止补全、简化和正式生成;只有至少含 1 个有效字符且总数超过 200 才允许简化。预设 ID、分组、颜色、助手系统模板和内部引导不得拼入 Suno Prompt;Prompt 助手可以使用服务端固定模板生成可见结果,但正式 Suno 请求只携带输入框可见的 canonical Prompt。200 字上限和有效字符数都以 canonical Prompt 计算,有效字符定义为非 Unicode `White_Space` code point0 个不能补全或正式生成;1 个且总数不超过 200 时只能正式生成;至少 2 个且总数不超过 200 时可以补全和正式生成;至少含 1 个有效字符且总数超过 200 时禁止补全和正式生成,只允许简化。请求字段继续使用 `gptDescriptionPrompt` / `gpt_description_prompt`,不得改名为 `actualPrompt``actual_prompt` 继续保留资源 / 素材审计语义,canonical 输入框、BFF、队列载荷、Suno body、生成记录 `prompt` / `actual_prompt` 和结果响应 `prompt` / `actualPrompt` 必须等值,BGM 成功响应同时填充后两者。AI 补全与 `180 -> 170`、最多两次的一键简化只走登录态内部 BFF,不调用 Suno、不创建任务、不扣正式音乐生成泥点;简化全程冻结入站 canonical `originalPrompt`,第一次以它为 `currentPrompt`、目标 180,第二次优先以第一次响应对象中可提取且包含有效字符的 canonical 字符串 `prompt` 为 `currentPrompt`、目标 170,即使其它 envelope 字段无效;连对象或字符串 `prompt` 都无法提取,或候选 canonicalize 后不含有效字符时回退原文,同时始终用同一 `originalPrompt` 作保真参照。内部 envelope 固定为 `prompt: string`、`isDirectWritebackFormat: boolean`、`isContentComplete: boolean`、`hasObviousFragment: boolean`;候选仅在四字段结构有效、canonical 后包含有效字符且不超过 200 字、后三个字段依次为 `true / true / false` 时通过。`isDirectWritebackFormat` 表示候选只含一条可直接写回的中文 BGM Prompt,不含解释、标题、Markdown、JSON、代码块、字数报告、处理过程或删改说明;程序只解析字段、canonicalize、计数和执行布尔结果,不用关键词或未定义正则猜测语义判断。程序不截断,也不硬编码内容保留规则,成功后只保存一层 canonical Prompt 交换快照。正式生成在点击事件内、任何 `await` 前同步锁定当前 BGM generation dialog,并完成 canonicalization、写回、校验和请求值冻结,后续重复点击忽略;拒绝时保留 canonical Prompt 和既有快照,接受后进入现有 `queued/generating` 占位,不锁整个画布或其它 dialog。
- T2 助手补充决策:补全与简化共用 `prompt: string`、`isDirectWritebackFormat: boolean`、`isContentComplete: boolean`、`hasObviousFragment: boolean` 四字段内部 envelope;补全候选也只有在结构与 canonical 字符校验通过、三个判断为 `true / true / false` 时才成功。助手显式使用现有 OpenAI Chat 协议,envelope 只允许由完整 `response.text` 中唯一一个 JSON object 承载;服务端仅用 `serde_json` 对完整文本全量解析,允许 object 外围 JSON whitespace,但不接受代码块、前后解释、多个 JSON 值、子串提取或自动修复。请求不发送 function tools,不做运行时双协议 fallback,响应出现 tool call 也按结构非法处理。补全固定一个业务语义轮,简化固定最多两个业务语义轮;单轮内部由现有 `LlmClient` 执行的 transport retry 不计入业务语义轮数。简化第一轮最终发生 transport、超时或上游失败时直接失败,不进入 170 字内容修复轮;只有成功取得第一轮响应但候选不合格时才派生第二轮。第二轮只有在第一轮完整文本已全量解析为单个 JSON object 后,才可从 object 读取字符串 `prompt`;禁止从未完整解析的响应中捞取候选。助手成功响应只暴露 canonical `prompt` 和程序计算的 `charCount`,失败响应不得暴露任一未通过候选、可提取的 `prompt`、内部 envelope 或判断字段。
- 影响范围:画板音乐权威设计、BGM composer 与临时状态模型、`editorProjectClient`、`shared-contracts` 内部助手 DTO、`api-server` 登录态 Prompt 助手与正式 BGM BFF、正式 generation queue 载荷、`platform-audio` Suno body builder,以及 BGM 提交与端到端测试。SFX 继续使用 Vidu `audio1.0`、现有规范化、默认 Prompt、1500 字限制和时长契约,不应用 BGM 的 canonicalization 或 0 / 1 / 2 有效字符规则;本规则不修改 SpacetimeDB schema,也不改变或扩展 External v1 / OpenAPI 的背景音乐请求、异步语义和路由,Suno 三字段 body、固定模型 / 泥点展示和现有 LLM 原文日志策略不变。
- 验证方式:TypeScript 与 Rust 对 CR / LF、CRLF、组合字符、ZWJ emoji、U+0085、U+200B、U+FEFF 和 199 / 200 / 201 code point 得出一致结果;首尾 U+0085 等 Unicode `White_Space` 被移除并同步反映到输入框,U+FEFF 与零宽字符不被误删。状态测试分别锁定全空白、0 / 1 / 2 个有效字符、预设追加、补全 / 简化失败不覆盖 canonical Prompt、单层交换撤销、迟到响应和同 dialog 双击;端到端断言 canonical 输入框、BFF、队列载荷、Suno body、记录和响应等值,且不存在隐藏 Prompt。SFX 请求体、默认 Prompt、1500 字限制和时长不变,External v1 契约测试无差异。文档阶段运行 `npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
---
## 2026-08-04 BGM Prompt 助手补齐输入、完成状态与埋点边界
- 背景:BGM Prompt 助手基线已经固定四字段 envelope、完整 `response.text` JSON 解析和 `180 → 170` 简化轮次,但权威设计仍把所有 201 字以上输入视为可简化,也没有明确 OpenAI Chat 未完成响应、HTTP body 上限及成功路由埋点;这会让超长文本消耗共享模型额度,并可能把 `finish_reason = length / content_filter` 的偶然闭合 JSON 当作完整候选。
- 决策:本条补充并取代上一条中“总数超过 200 即只允许简化”的无上限表述。一键简化只接受至少含 1 个有效字符且总数为 `2012000` 的 canonical Prompt,最大值等于正式生成 200 字上限的 10 倍;超过 2000 时保留完整文本但禁用 AI 补全、一键简化和正式生成,服务端以现有 `400 BAD_REQUEST` 和字段 `currentPrompt` 拒绝。两个助手路由分别设置 `32 KiB` HTTP body 上限,body 超限保持 Axum `413 PAYLOAD_TOO_LARGE`,字符上限与 body 上限独立校验。
- LLM 完成状态:在解析 envelope、canonicalize 候选或提取重试 `prompt` 前,使用 `platform-llm` 现有 API-kind-aware 未完成原因判断检查 OpenAI Chat `finish_reason`;去除外围空白并忽略 ASCII 大小写后的 `length` / `content_filter` 均不可信,即使正文形成合法 JSON 也不得接受或提取。补全遇到两者均直接失败;简化第一轮 `length` 只以冻结的 `originalPrompt` 进入 170 字轮,第一轮 `content_filter` 直接失败,第二轮出现任一未完成原因都最终失败。缺失、空值或未知自定义 reason 不因该字段单独拒绝,继续执行其余门禁,不采用 `stop` 白名单。`platform-llm` 只公开复用 predicate,不改变其它普通文本调用方的降级行为。
- 限流与埋点:不增加 Prompt 助手专属的用户级、IP 级、时间窗口、令牌桶或本地额度限流,不新增功能级 `429` / `Retry-After`;现有 api-server 全局并发背压、前端防重复操作、Nginx 保护和上游真实 `429` 安全映射不变。两个成功路由进入 `tracking.rs` 静态映射:补全为 `editor_background_music_prompt_completion`,简化为 `editor_background_music_prompt_simplification`,两者均使用 `module_key = editor`、User scope;普通 route tracking 继续只记录成功响应并走现有本机 outbox,助手 handler 不同步写 SpacetimeDB。
- 影响范围:BGM composer 动作状态与 client、`api-server` Prompt 助手路由和候选验收、`platform-llm` 公共未完成原因 predicate、route tracking 静态映射及相应测试;不修改 SFX、正式 BGM 200 字限制、canonicalization 算法、SpacetimeDB schema、External v1 / OpenAPI 或 LLM 原文日志策略。
- 验证方式:覆盖 canonical 2000 / 2001、两个路由 `32 KiB` / `413`、补全与两轮简化的 `length` / `content_filter`、缺失和未知 reason 兼容、连续合法请求无功能级 `429`,以及两个成功路由的 event key、`editor` module 和 User scope;运行 `api-server` 与 `platform-llm` 定向测试、`npm run check:encoding` 和 `git diff --check`。
- 关联文档:`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`。
---
## 2026-08-05 BGM 正式提交保持站内 canonical 载荷与分层回调所有权
- 决策:登录态站内 BGM queue / inline 在分流前生成唯一 canonical payloadqueue serializer 与 Suno body 只消费其中的 canonical `gptDescriptionPrompt` 并固定 `makeInstrumental=true`。正式 BGM POST 遇到 retryable HTTP 状态或 transport error 不由客户端自动重发,避免一次点击产生重复任务或扣费。
- 回调所有权:正式任务接受后,钱包刷新只校验账号;任务列表通知同时校验账号与项目;dialog、canvas、asset 和 layer 写回校验账号、项目、scope version 与原 BGM dialog。dialog 删除或同账号切项目不应阻止账号级钱包刷新,账号切换即使暂时保留相同 project ID 也不得触发旧账号的任务列表回调。
- 外部边界:上述 canonical payload 只属于登录态站内链路。External v1 继续保留调用方原始 BGM payload,并按原始 payload 执行既有 Idempotency-Key 等值语义;不能把 canonical 等价值误判为相同重放。
- 影响范围:图片画布 BGM 正式提交与回调门禁;不新增持久提交锁、自动重试、计费设计或 External v1 契约变化。
- 关联文档:`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`。
---
## 2026-08-04 BGM 撤销按钮按快照存在性显示、按面板锁定禁用
- 背景:需求《BGM生成优化需求 V1.0》第三节要求“AI 开始处理”时撤销按钮显示但禁用,权威设计也只要求处理中禁用撤销,但没有写明可见性;T4 界面方案据此把渲染条件收窄为“存在可撤销快照”,而状态模型在发起 AI 操作时会把可撤销快照转为本次临时快照,两者叠加会让撤销按钮在处理期间消失,与需求不一致。
- 决策:撤销按钮的可见条件为存在可撤销快照或本次 AI 操作的临时快照,启用条件为可见且当前 BGM 面板未处于 `completing`、`simplifying`、`submitting` 或现有 `generating` 锁定状态。因此 AI 处理期间显示并禁用,AI 失败、点击预设和完全无快照时隐藏,AI 成功、手动编辑 AI 结果和连续撤销互换时显示并启用,`submitting` 期间有快照显示并禁用、无快照隐藏,解除锁定后按快照恢复。禁用必须使用真实禁用态并保留按钮在 DOM 与可访问树中的位置,不得用隐藏、透明度或其它视觉伪装代替,也不得因锁定改变按钮占位。发起 AI 操作时仍以当时的 canonical Prompt 替换旧快照,不得为了让处理期间按钮可见而保留旧快照;那会与本日第一条“AI 失败不恢复已被替换的旧快照”冲突,并让失败后的撤销指向不相关内容。视图只按该矩阵决定显示与启用,是否真正执行撤销仍由状态层在非 `idle` 或无快照时拒绝,两处不得各写一套判定。
- 影响范围:`/editor/canvas` 的 `audio-background-music` 面板撤销按钮与其定向测试;不改变单层交换快照语义、canonicalization、提交锁、Suno 契约或后端 Prompt 助手,也不修改状态模型字段,`temporaryPromptSnapshot` 已在公开 dialog 状态中且只在 `completing` / `simplifying` 期间非空。本条不适用于 SFX,V1.0 不改动 SFX 的一键优化与撤销行为。
- 验证方式:按矩阵逐行覆盖初始隐藏、首次与再次 AI 处理期间显示并禁用、成功启用、失败隐藏、手动编辑后仍启用、点击预设隐藏、`submitting` 有无快照的两种表现、解除锁定后恢复,以及连续撤销互换保持启用;并断言处理期间按钮仍在可访问树中且为真实禁用态。
- 关联文档:`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`。
## 2026-08-03 完美像素对账判据改看 dialog 收口状态,网关合成响应归入未知结果
- 缺陷一(对账把真成功判成失败):对账用「同 ID 的 generation-dialog 是否还在权威快照里」判定成败,而服务端成功回填时**保留**该 dialog 并就地改写——`apply_editor_canvas_generation_items` 置 `status: "idle"`、`composerOpen: false`、写入 `generatedLayerId`、清掉 `errorMessage`,该行为另有服务端测试断言 `dialog["generatedLayerId"]` 钉住。所以响应丢失但服务端其实已完成时,判据反向:用户被告知「画布未收到完美像素结果,请确认素材库」,而结果早已在画布上,重做一遍就造出第二份;这条分支还刻意不套用快照,本地也看不到那个新图层。
- 逃过测试的原因要单独记:那条「未知但实际成功」的用例夹具写的是 `layers: []`,是服务端永远不会产生的形状。**测试不是漏了,是主动为错误判据背书**——用一个假前提把反向逻辑测成了正确的。修复顺序因此定为「先改夹具、看它变红、再改判据」,让这件事显式暴露一次而不是被新判据顺手掩盖。
- 决策一:判据改看 `status` / `generatedLayerId`,并复用既有语义。`projectHasUnresolvedGenerationDialog` 早就是本仓库对「这个生成收口了没有」的定义,只是原先埋在队列轮询里;原始 record 查询现由收集全部同 ID 记录的 `findCanvasGenerationDialogRecords` 与 `isUnresolvedCanvasGenerationDialogRecord` 两处共用,不自创新判据——自创正是本次出错的起点。取原始 record 而不 hydratehydrate 会给缺失 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` 是在网关就被拒、根本没到应用,属于确定失败,收进来会让普通节流也弹出「请核对素材库」,变成与本条镜像的反向谎报。
- 两条共性:都是用代理信号代替事实——用「占位在不在」代替「操作完成没有」,用「有没有 HTTP 响应」代替「应用层有没有表态」。两处都是没有去读被代理的那个事实的真实形状。
- 验证:新增四条用例(网关 504 触发对账、网关 429 不触发、应用层无标记 502 不触发、快照无 dialog 时不套用快照)。同时破坏两处修复后,四条精确变红。后两条是对照用例,专门守住「放宽判据不得退回反向谎报」。`vitest src/components/image-editor` 907 通过 / 72 文件,typecheck、eslint、check:encoding 通过。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 完美像素链路复查:补齐第三处静默分支并区分两条结果文案
- 背景:对账判据与网关分类修完之后做的整链复查,目标是找遗留与新引入的问题,不是重复已知项。
- 遗留(第三处静默):成功路径拿到 `result.project` 后,若本地占位已被用户删除则直接 `return`。不套用快照是对的(删除意图胜出),但什么都不说。此前已修过同一形状的两处(`!result.project` 分支、对账早返回),这是第三处,复查才发现。既有用例 `does not apply a completed project after the perfect-pixel placeholder was deleted` 只断言「不套用」,没断言「要说话」,所以也没挡住。
- 新引入(文案混用):上一次修复让「对账发现已收口 + 本地占位已删」这一支沿用了「结果已保存到素材库,画布占位已不存在」。**两种情况的事实不同**——快照里没有 dialog 时服务端 completion 返回 `Ok(None)`,画布上确实没有结果图层;而 dialog 已收口时服务端画布上**有**结果图层,只是本地按删除意图没套用,重新加载即可见。用前一条文案会让用户以为画布上没有,再做一遍,正是本链路要消除的重复创建。
- 决策:两条文案抽成常量并按事实分派——`PERFECT_PIXEL_ASSET_ONLY_NOTICE` 用于「服务端画布也没有」,`PERFECT_PIXEL_APPLIED_REMOTELY_NOTICE` 用于「服务端画布已有、本地未同步」。四个使用点各归其位。
- 测试补位过程值得记:第一次回归验证只有 1 条变红——说明「对账已收口 + 本地已删」这条分支根本没有用例,文案改动是无覆盖的。补上该用例后再破坏,2 条同时变红。**如果止步于第一次验证,就会把一处无覆盖的改动当成已验证。**
- 复查中核过、确认不是问题的两点:其一,网关放宽的作用域正确——`/api/*` 走 `is_generic_api_proxy_path`,读超时默认 `3600` 秒,远高于服务端 90 秒预算与客户端 120 秒,次序是 `90 < 120 < 3600`,网关不会在服务端合法工作期间截断,它合成 502/504 只可能是连接失败或进程不可用,确属未知结果;唯一变数是有人把 `GENARRATIVE_PINGORA_GATEWAY_UPSTREAM_API_READ_TIMEOUT_SECONDS` 调到 90 秒以下。其二,`projectHasUnresolvedGenerationDialog` 由 `some(...)` 改为「取首个匹配再判」存在极低风险的语义收窄,同 id 多 dialog 时行为不同,但 id 唯一,实际不可达。
- 验证:`vitest src/components/image-editor` 908 通过 / 72 文件,typecheck、eslint、check:encoding 通过。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 inline 占位补到期清理,收掉存活窗口引入的回归
- 缺陷:存活窗口把「立即刷新也能清掉孤儿占位」这条旧行为换掉了。剥离只在项目首次加载执行一次,窗口内被保留的占位再没有任何东西会重新判定——页面保持打开就一直转,必须等到用户下一次加载且距创建已满窗口才收口。这是引入 TTL 时的已知取舍,本次补上。
- 复查中发现的关键事实:**手动收口入口本来就存在**——`requestRemoveCanvasGenerationDialog` 已接入键盘快捷键,对 `generating` 占位会先弹确认再删。所以缺的从来不是删除手段,而是「它已经死了」这个信号;占位看起来和正在干活一模一样。
- 决策:加一次性到期定时器,到点走与加载期**完全一致**的处置——移除 + 同一条文案(抽成 `DEAD_INLINE_PLACEHOLDER_NOTICE` 共用)。未采纳「标记 failed 而不移除」:`failed` 会被自动保存持久化,而剥离只处理 `generating`,那张卡片会跨刷新长期存在,与当初选「刷新后占位消失」的用意相反,把一次性噪音变成永久残留。
- 三条必须守住的实现约束,都写进了 hook 的文档注释:其一,到期回调只推进 tick 让 effect 重跑,判定始终在 effect 体里用当前 dialogs 和当前时间做——挂上定时器之后占位可能已被拥有者会话正常收口,按闭包旧值行动会清掉一个已完成的占位;其二,清理用底层 `removeCanvasGenerationDialogById` 而不是 View 的 `removeCanvasGenerationDialog`,后者是用户主动删除的语义(写 `delete-generation-result` 历史、清空选中、切回选择工具),自动清理记用户没做过的历史、抢走当前选中态都是错的,加载期剥离同样不做这些;其三,本会话自己在途的占位不会被误清(客户端 120 秒就 abortcatch 会把它推离 `generating`),但不依赖该推理,靠第一条的重新判定兜住。
- 作用面划分:`dropDeadInlineGenerationPlaceholders` 跑在**快照**上、只在加载时执行;`collectExpiredInlineGenerationDialogIds` / `resolveNextInlineGenerationDialogExpiryAt` 跑在**内存 dialog** 上、供页面打开期间使用。同一条规则、两种数据形状,边界(到期时刻含等号不算过期)与缺时间戳的兜底方向都保持一致。
- 到期时刻可精确计算,所以挂一次性定时器而不是轮询;多给 50ms 余量,避免贴着到期时刻醒来判定为未到期、白白多挂一轮。
- 验证:纯函数四条用例覆盖边界、队列型与已终态不到期、缺时间戳立即到期、最早到期时刻;hook 四条覆盖到期清理、醒来重新判定(占位期间被收口则不清)、队列型永不挂定时器、已超窗立即清理。去掉定时器重挂逻辑后第一条变红。`vitest src/components/image-editor` 916 通过 / 73 文件,typecheck、eslint、check:encoding 通过。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 到期清理链路复查:一个被证伪的假设与它留下的用例
- 复查怀疑:到期定时器要熬三分钟,而 `canvasGenerationDialogs` 变动很频繁(提交工作流三十余处变更点,队列型生成轮询期间持续更新状态)。把 effect 依赖挂在数组身份上,看起来会被无关变动不断重挂定时器、永远等不到触发,整个机制静默失效。据此把定时器改挂在计算出的到期时刻上,并加 ref 读取当前 dialogs。
- 结论:**假设是错的**。为它写的回归用例(每秒一次无关变动、持续到超窗)在改回数组依赖后仍然通过——数组身份变化会让 effect 重跑,而 effect 体每次都重新判定到期,频繁变动带来的是更频繁的判定,不比定时器差;不变动时数组稳定,定时器正常存活。两条路径都收口。
- 处置:撤回 `useMemo` + `useRef` 的改动,回到更简单的数组依赖版本——既然简单版本本来就正确,多出来的间接层没有收益。用例保留,但注释改写为它**实际证明**的性质:判定必须留在 effect 体里;将来若把它挪出去(例如只在定时器回调里判定),这条不变式才会真的失效,用例届时会变红。
- 记这一条是因为过程本身有价值:先假设、再写用例、用例证伪假设、据此撤回改动。若跳过验证直接保留那次「修复」,就会在没有缺陷的地方永久留下一层多余的间接。本次会话里同类错误(凭推断得出结论而不验证)已出现多次,这次是验证挡住了。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 外部 API skill 文档同步 style 的提示词注入语义
- 缺陷:`references/requests-and-outputs.md` 把 `style` 描述为「只控制 deterministic post-processing」,缺了本分支给它加的另一半语义——`pixelArt` 会在发给 provider 的提示词末尾追加一行像素风约束。
- 归因是文档漂移而非遗漏:`docs/openapi/genarrative-external-v1.openapi.json` 的两处 schema **一直是对的**,连具体子句都写了。master 的 `c00dd099e` 把 `api-selection.md` 拆成四篇新参考文档时是从注入之前的版本重写的,于是 skill 文档退回旧语义,而 OpenAPI 保持正确。
- 我上一轮合并时的核查不到位:只 grep 了「`byte-for-byte` 那个错误说法有没有复活」,确认没有就收工。**验证旧错误的缺席不等于验证新事实的在场**,两者要分别查。
- 决策:让 skill 文档与 OpenAPI 对齐,措辞不新造。改动限于三行——说明它同时追加提示词子句与启用后处理、`none` 一档不追加也不后处理、`pixelArt` 一档追加一行且是追加而非替换。
- 刻意不复制子句字面文本:Rust 常量是真值源,OpenAPI 已复制一份,skill 文档再抄第三份就是把同一事实摊到三处——这次漂移正是这么发生的,只是方向相反。文档改为指向 OpenAPI 并写明「本指南刻意不复制」,让下一个读到的人知道那是有意为之而非遗漏。
- 校验面已确认:这批文档由 `external_skill_api.rs` / `external_mcp.rs` 以 `include_str!` 编译期内联,SHA 在运行时从内容算出、测试只断言「算出的与返回的一致」,没有钉死具体摘要,改文档无需同步任何清单。api-server 700 通过 / 3 失败(`wallet_refund_outbox` 本机环境失败,与基线一致)。
- 关联文档:`docs/openapi/genarrative-external-v1.openapi.json`。
## 2026-08-03 到期清理误删本会话在途占位:补归属登记与前置阶段预算
- 缺陷:到期清理只按 `generationStartedAt + 180 秒` 删除 `requiresLiveSession` 且 `generating` 的占位,区分不出它属于已死会话还是本会话仍在执行。占位在创建后还要走源图解析/直传和 `flushProjectPersistence` 才轮到 POST,而 `snapEditorImageToPixelArt` 的 120 秒**只从最终 POST 开始计**。前置阶段慢起来越过窗口时,定时器会删掉本会话正在用的占位并把删除持久化,随后 POST 因占位不存在返回 `409`;若删除的落库晚于 POST 到达,则 completion 找不到占位返回 `Ok(None)`,结果只进素材库、不落画布。
- 我写在 hook 注释里的安全性论证是错的,两条都错:其一「客户端 120 秒就 abort180 秒时不可能还是 generating」——120 秒不覆盖前置阶段;其二「不依赖该推理,到期重新判定本身兜得住」——重新判定只能识别**已经收口**的占位,识别不出**仍在合法运行**的占位,后者正处于要被删除的那个状态。第二条错得更本质:它给了自己和读者一道并不存在的第二防线。
- 前置阶段此前完全无界:直传 `postEditorDirectUploadFile` 是裸 `fetch`、没有 signal`saveEditorProjectLayout` 的 `requestJson` 没传 `timeoutMs`(同文件其余接口都写了),而 `composeAbortSignal` 在缺失时不设任何默认值。两者各自还有重试(上传最多 3 次尝试、布局保存最多 4 次)。
- 决策一(归属登记):`activeInlineGenerationDialogIdsRef` 记录本会话仍在执行的占位 id,创建后**紧挨着**注册(中间不能有 await,否则留出「已存在但未登记」的窗口),`finally` 释放;到期清理跳过其中的 id。到期清理本来就只该针对别人留下的孤儿。
- 决策二(整段预算而非逐请求超时):给「占位创建 → POST 发出」整段 40 秒预算。逐个请求加超时的最坏总时长会因重试累加到远超 180 秒窗口,窗口的前提仍不成立;整段封顶后客户端最坏 40 + 120 = 160 秒,落在窗口内并留 20 秒余量。超时抛裸 `Error` 而非 `ApiClientError`,归入未知结果走对账——上传可能已完成、素材可能已落库,正是对账要处理的情形。
- 决策三:`saveEditorProjectLayout` 补 `timeoutMs: 60_000`。这是独立缺陷,与本条无关也该修——它被 `flushProjectPersistence` 同步等待在提交路径上,挂住会连带把占位拖过窗口。
- 「窗口计时起点应改为 POST 发出时刻」未采纳:归属登记之后窗口不再需要覆盖本会话,只用于跨标签页;而对孤儿占位只有创建时刻这一个可用时间戳,改起点无从实现。整段预算已经让窗口的前提重新成立。
- 验证:新增两条用例——本会话持有期间超窗不清理、释放归属后同一超窗占位立即清理。去掉归属过滤后两条同时变红。`vitest src/components/image-editor` 923 通过 / 74 文件,typecheck、eslint、check:encoding 通过。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 归属登记后的链路复查:一处新引入的忙等、两处无界读写、一处过紧预算
- 复查对象是上一条修复本身,找的是新引入的问题,结果三处新增、一处遗留。
- 新引入(忙等,最严重):归属过滤只加在了 `expiredIds`,没加在 `resolveNextInlineGenerationDialogExpiryAt`。被本会话持有的超窗占位不进 `expiredIds`,却仍被算出一个**已经过去**的到期时刻,`delayMs` 塌成 50ms,定时器触发 → tick → effect 重跑 → 状态没变 → 再挂 50ms,变成每 50 毫秒一次 `setState` 的忙等,持续整个持有期。改为先按归属过滤出 `unownedDialogs`,两处判定共用。
- 该忙等的可达性不是理论的:前置预算加 POST 之后,catch 里还要做对账 `loadEditorProject`,而归属要到 `finally` 才释放,这段完全可能越过存活窗口。
- 遗留(无界读取):`loadEditorProject` 同样没传 `timeoutMs`,而 `composeAbortSignal` 在缺失时不设默认值。它正是对账路径上的读取,挂住会让 catch 迟迟不结束,连带把占位拖过窗口——即上一条忙等的直接助推。补 `60_000`。至此该文件里落在完美像素链路上的三个接口(POST、布局保存、项目读取)都有了显式上界。
- 新引入(预算过紧):上一条把提交前置阶段封顶在 40 秒。该阶段在源图是 inline / 未登记时会真的直传一张画布图层,几 MB 的图在较差移动网络下要几十秒,40 秒会把原本能成功的操作改判为失败——**用「无界」换「过紧」同样是回归**。改为 90 秒,并把存活窗口从 180 秒同步提到 240 秒,维持 90 + 120 = 210 < 240 且留 30 秒余量。
- 三个常量构成一条跨文件不等式(提交前置预算、客户端 POST 超时、占位存活窗口),任一处被单独调大都会破坏它,后果是跨标签页误删。新增用例把这条不等式连同 30 秒余量一起钉住。本会话自己的占位另有归属登记豁免、不依赖该窗口,所以窗口只需覆盖跨标签页那一侧——这一点也写进了常量注释。
- 验证:忙等用例用 `vi.getTimerCount()` 直接断言「不该挂定时器」,把 `resolveNext...` 改回未过滤版本后该用例变红。`vitest src/components/image-editor` 924 通过 / 74 文件,typecheck、eslint、check:encoding 通过。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 补齐 editorProjectClient 超时契约的断言
- 缺陷:给 `saveEditorProjectLayout` 与 `loadEditorProject` 补超时后,`editorProjectClient.test.ts` 的两条精确参数断言失败。`toHaveBeenCalledWith` 要求参数个数与内容完全匹配,新增第四个参数即不匹配。CI(任务 1645)在 `aa8ea401a` 上报出其中一条,另一条由本地复跑发现。
- 处置:更新断言把 `{ timeoutMs: 60_000 }` 写进去,而不是放宽成 `expect.anything()`。超时是契约的一部分——这两个接口一个被 `flushProjectPersistence` 同步等待在提交路径上、一个在未知结果对账路径上,没有上界会把在途占位拖过存活窗口。断言写死之后谁删掉它测试就会红;放宽则等于让刚建立的上界失去看守。已验证:去掉生产代码里的两个超时,两条断言同时变红。
- 真正的问题是验证方式而非测试:改的是 `src/services/image-editor/editorProjectClient.ts`,验证却只跑了 `vitest src/components/image-editor`,改动面与验证面完全对不上。这个盲区在本次会话中期分析另一份 CI 日志时已由我自己指出过,却没有改掉习惯,于是同一个盲区再次漏出——而且这次不是难复现的跨文件竞态,是本地一跑就红的确定性失败。
- 约定:这条链路横跨 `src/components/image-editor/` 与 `src/services/image-editor/`,往后验证至少同时覆盖两处。不跑全量套件——本机有九条稳定的环境失败(符号链接、`0600` 权限模式、缺客户端 AppData 配置),噪音大于收益。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 对账整段设界,并把提交前置预算变成真正的取消
- 缺陷一(对账可被素材库读取永久挂住):对账用 `Promise.all` 同时等项目快照与 `refreshAssetLibrary`,而 `loadEditorAssetLibrary` 至今没有 `timeoutMs``composeAbortSignal` 缺失时不设默认值)。`.catch()` 只接住拒绝、接不住永不 settle;catch 体内的 await 不返回,`finally` 就永远不执行——占位归属登记与源图层锁都释放不掉,而到期清理又豁免已登记的占位,页面永久停在 `generating`,同一源图也无法再次操作,刷新前无解。
- 这个洞是上一条修复留下的:给 `loadEditorProject` 加界时写的注释已经把机制说对了(「挂住会让 catch 迟迟不结束」),却只给同一个 `Promise.all` 里两个 await 中的一个加了界。逐个接口补超时这条路已经漏过一次。
- 决策一:给**整段对账**设 75 秒上界,而不是继续逐个接口补。往对账里加任何新的 await 都自动受约束。超时必须**解析为 null 而不是拒绝**——这段代码本身位于 catch 内,抛出会穿出整个 async 函数,而调用方是 `void snapSelectedLayerToPerfectPixels(...)`,结果是未处理的 rejection;解析为 null 则落进既有的「权威项目快照读取失败」分支,语义正好一致。
- 缺陷二(预算只停止等待、不取消):`withPerfectPixelPrePostBudget` 原先只是 `Promise.race`,超时后底层继续跑。直传 `fetch` 没有 signal,被放弃的上传会一路走到 confirm 并注册对象,用户重试再产生一份。
- 决策二:把同一个 `AbortSignal` 贯穿凭证请求、直传 POST 与 confirm 三步,由前置预算到期时 `abort`。只中止直传会留下未 confirm 的 OSS 对象,只中止 confirm 又会让实体已写入却无记录——要停就整条链一起停。`requestJson` 从 `init.signal` 取信号并与自身超时合成,所以凭证与 confirm 只需在 init 里传入。
- 未采纳评审建议的全量贯穿(再覆盖重试等待与 flush/save):那要再动两个模块,而收益只是少产生一些用户不可见的存储孤儿;`flushProjectPersistence` 现已有 60 秒上界,最多多挂 60 秒后自行结束。改动面从四个模块降到两个,绝大部分收益保留。
- 后果分级要说清:缺陷一是永久性的 UI 卡死,缺陷二只是存储层孤儿对象(confirm 注册的是 asset object,不是素材库条目,用户基本不可见)。两者同为 P2 但不同量级。
- 验证:新增用例让项目快照读取永不 settle,断言 120 秒后完美像素状态回到「空闲」——即 `finally` 确实执行。去掉对账 deadline 后该用例变红。`vitest src/components/image-editor src/services/image-editor` 974 通过,typecheck、eslint、check:encoding 通过。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 完美像素第一批:稳定 operation 与单事务数据库提交
- 被纠正的旧边界:完美像素原先在 OSS PUT / HEAD 后顺序执行 asset object confirm、project resource、账号素材与 canvas completion 四次独立数据库调用。任一后段失败或本地 timeout/drop 都可能留下可见的部分事实;重试又使用随机 task / object / resource / asset ID,无法把同一次逻辑操作识别为重放。历史 decision 条目保留为当时证据,本条取代其“继续沿用非事务顺序”的现役结论。
- operation identity:规范化 `canvasCompletion.dialogId` 即 operationId,由 owner + project 共同限定作用域。`taskId = pixel-art-snap-{operationId}`,让响应丢失时的客户端无需服务端回包即可计算;asset object、project resource 与账号素材 ID 用 `SHA256(editor-pixel-art-result-v1 + owner + project + operation + record kind)` 的前 16 bytes 稳定派生。请求 fingerprint 为 64 位 SHA-256,覆盖来源 object key、来源和输出字节摘要、来源资源、素材类型、规范目录 / 标签、canonical generationInputs、canvas completion 与算法版本;最终 OSS object key 必须携带该 fingerprint。
- 原子边界:OSS PUT / HEAD 仍在事务外。验证上传后,`persist_editor_pixel_art_result_and_return` 受 editor generation runtime service identity 保护,在一次 `try_with_tx` 中校验并写入 asset object、project resource、editor asset,并在同一事务内读取最新 canvas、按当前 revision 完成 dialog。handler 禁止在 procedure 前调用旧 `confirm_asset_object`、resource、asset 或 completion helper。该保证只覆盖这四类结果事实;前置 owner-scoped 项目 / 素材读取仍可能沿用既有 `ensure_default_canvas / ensure_default_asset_folder` 懒建基础记录,不宣称整个 preflight 对数据库零写入。
- 重放与冲突:三条稳定记录完整且业务内容相同才返回 `AlreadyApplied`,重放不得再次执行 layout CAS 或推进 revision;任一稳定 ID 指向不同内容、同 object location 被其他 ID 占用、同 operation 输入漂移或三条记录只有部分存在都失败关闭并映射 HTTP `409`。时间字段不参与 exact replay 内容比较。权威 dialog 已删除时三条记录同事务提交、canvas / revision 不变并返回 `DialogMissing`。
- 未知结果语义:本地 procedure future 的 timeout/drop 不能撤销远端事务,所以首个 PUT 后继续设置 `resultPersistenceStarted=true`;该标记现在表示“OSS 或整笔数据库事务的结果未知”,不再表示数据库可能部分提交。事务失败后允许留下无引用 OSS object,本批不做破坏性删除或历史孤儿清理。
- 明确延期:本批只交付后端原子性与可重放身份。前端仍需后续批次持久化 operation 请求快照、让素材刷新退出 verdict、轮询项目事实、引入 `pending-confirmation`、刷新后只恢复 GET,并让人工重试复用原 operation;在此之前不能宣称 unknown-result 已端到端闭环。
- 2026-08-03 第二批边界:generation dialog 持久化版本化 `perfectPixelOperation`,绑定规范化 dialog/operation、固定 `pixel-art-snap-{operationId}` task、稳定来源解析后的完整 POST 请求以及 `submittedAt / reconcileUntil` 整链绝对窗口。只有布局 PATCH 已确认包含该快照才允许首次 POST;素材刷新退出 verdict。响应未知后按稳定 task resource 与 dialog/layer 的原子事务形状有界轮询项目 GET,未终态或读取到期统一保持 `pending-confirmation`,不标普通失败、不自动重放。显式人工重试必须 byte-for-byte 复用持久请求和同一 identity,当前 UI、来源、目录、类型或标题变化不得改变请求;无效快照失败关闭。首次提交或重试在途时 owner、project 或组件生命周期改变后,旧响应的素材、项目、提示和对账副作用全部忽略。
- 2026-08-03 前端 hydrate 收口边界:hydrate 后对有效 `generating` / `pending-confirmation` operation 只做 GET-only 恢复,禁止自动 POST、上传或重建请求;切换 owner/project、卸载或权威 revision 前进时取消旧观察。新写入的 v1 快照固定使用 75 秒跨度;读取侧兼容第一批曾写入的 240 秒 v1 形状以保留 operation identity。跨设备时钟让 `submittedAt` 落在可接受的未来区间时,先把它规范化到当前时间,再把实际截止压到 `min(持久截止, 规范化 submittedAt + 75 秒, 当前时间 + 75 秒)`;这样既不借兼容延长观察,也不会写出 `reconcileUntil < submittedAt` 的二次 hydrate 无效形状。有效 durable operation 退出 legacy `requiresLiveSession` TTL,任何标签页都不得清理;无 operation journal 字段的历史 inline 孤儿继续按 TTL 兼容,且剥离时同步顶层与 `canvas.layers` 两份布局。轮询耗尽仍保留 operation 和待确认状态,只有显式重试进入第二批 exact replay。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-04 完美像素第二批:object-only 上传与 GET-only unknown 收口
- 上传边界:inline 源图不再调用会在 confirm 后继续换签的完整上传 helper,只执行 `ticket → OSS PUT → confirm → objectKey`。完美像素在创建占位后立即确定 dialog / operation ID,并把它作为稳定 upload ID;源 fetch、图片解析边界、ticket、PUT、confirm 共用前置预算的同一个 `AbortSignal`。完整 helper 的 signed URL 调用也防御性透传 signal。这样 confirm 成功后没有新的换签失败窗口,同一 operation 的内部重试也不会换对象路径。
- verdict 边界:POST 成功不再直接采用响应体的 `project`POST 中的 `asset` 也只有在项目 GET 已确认终态且 response task / resource 与 GET resource 一致时才允许本地 upsert。项目 GET 是唯一 verdict 来源;匹配 task resource + 已收口 dialog/layer 为画布成功,无 dialog + 匹配 task resource 为 asset-onlyresource 已出现但 dialog 仍 generating 继续等待,无 dialog 且无匹配 resource 也继续等待。重复匹配 resource 或已收口 dialog 与 resource / layer 不一致失败关闭为 conflict,不猜测成功。
- 时间边界:`submittedAt / reconcileUntil` 从稳定请求快照写入时形成单个 75 秒整链绝对窗口;POST 正常回包或异常都不能替同一次 operation 续期,只有用户显式 exact replay 才开启新的 75 秒窗口。每轮先立即 GET,一次读取即使发现窗口已过期也必须执行;随后退避上限 5 秒。读取始终失败或窗口耗尽时保持 `pending-confirmation`,不声称素材已保存。滚动升级时兼容读取旧 240 秒 v1 journalhydrate 会先把可接受的未来 `submittedAt` 规范化到当前时间,再把截止收紧到规范化提交时间和当前时间各自允许的 75 秒上限,并在下一次布局持久化时写回仍可再次 hydrate 的收紧形状。
- identity 与删除:unknown 保留原 dialog 上的完整 `perfectPixelOperation`,人工重试原样发送持久化 request;普通按 ID 删除和随源图层删除均保留未收口 durable operation。对话框删除入口会激活原占位并提示继续核对 / 原样重试;Delete 快捷键若只命中受保护 operation 则在写历史、清选择或执行副作用前完整 no-op,混合选择只统计并删除其它可删除目标。刷新恢复只做 GET,owner / project 切换或卸载会取消旧观察。完全没有 operation journal 字段的 legacy inline 占位仍沿用既有 TTL;字段存在但损坏时保留失败关闭标记,不能降级成可清理的旧占位。
- 投影刷新:`refreshAssetLibrary` 只在项目终态后 best-effort 触发,并同时吞掉同步 throw 与异步 reject;永不 settle 的刷新 Promise 也不参与 await,因此不能阻塞项目应用、提示或 `finally` 解锁。
- 验证:第二批定向覆盖 POST 成功后仍走 GET、unknown 的 pending → completed、no-dialog 正反证据、75 秒绝对截止与 5 秒退避、过期后至少一次 GET、stable upload ID、object-only 上传、整条 signal、刷新永挂 / 同步抛错 / 异步拒绝、删除保护、hydrate GET-only 与 byte-for-byte replay。Atomic 全局相对断言及其它文档清理在本次第二批提交时尚未纳入,后续由下一条第三批完成。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-04 完美像素第三批:移除全局 Atomic 相对断言并完成文档收口
- 竞态根因:过期预算用例先读取进程级 `EDITOR_PIXEL_ART_SNAP_QUEUE_DEPTH`,再在断言前读取一次;相邻 Drop 用例可在两次 load 之间创建或释放 guard。相对 before/after 与绝对 `== 0` 一样没有跨测试隔离,Rust 默认并行时仍会随机失败。
- 测试边界:`pixel_art_snap_permit_reports_exhausted_budget_without_waiting` 只构造过期 deadline 并断言 `504`,不再观察全局队列深度。`pixel_art_snap_queue_depth_returns_to_zero_after_guards_drop` 继续作为独立 Drop 契约用例;未引入 `--test-threads=1`、全局串行锁或其它掩盖手段。
- 文档收口:后端数据契约、连接池 Drop 说明、图片画布方案、decision log 与 pitfalls 同步撤回“相对断言可消除并行竞态”的错误保证。连接池 lease 的 Drop 只保证本地 slot / permit 可回收,不表示 handler timeout/drop 能取消或回滚已经发出的远端 procedure。
- 验证结果:`cargo test --manifest-path server-rs/Cargo.toml -p api-server pixel_art_snap` 为 17 通过 / 0 失败;`npm run check:rustfmt`、`npm run check:encoding`5153 个文件)与 `git diff --check` 通过。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【后端架构】SpacetimeDB连接池租约Drop兜底与取消安全-2026-06-11.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-04 inline 占位 ownership 释放改为可观察信号
- 覆盖缺口:2026-08-03 的“释放归属后同一超窗占位立即清理”测试在 `rerender` 时同时创建了新的 dialogs 数组和 callbackeffect 实际由这些依赖变化唤醒;它没有证明 `finally` 里单独执行 `Set.delete()` 会重新判定。生产实现把稳定 ref 对象放进依赖,但 React 不观察 `.current` 内容变化,因此旧测试与旧实现之间存在同一个盲区。
- 决策:Set 继续作为首个 await 前同步可见的 ownership 真值,但封装进 `useInlineGenerationPlaceholderOwnership`,不再向 View 暴露可变 ref。`claim / release` 只有在 membership 真变化时才推进 version;到期 effect 同时依赖稳定 `has` 和 version。首次提交与人工 exact replay 通过同一份 ownership 登记 / 释放,hydrate GET-only 恢复只查询这份 ownership 来避开本页 live Promise,不能在 View 创建第二份 registry。
- 时序边界:`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`。
## 2026-08-04 完美像素对账读取改用请求全生命周期绝对截止
- 缺陷:`loadEditorProject` 的 `timeoutMs` 只包住业务 `fetch` 等响应头。缺 token 时的登录恢复发生在该 timer 建立前,401 后的共享 refresh 等待也不受它限制;收到响应头后 timer 已清理,成功和错误响应的 `response.text()` 又可继续永久挂起。任一环节不 settle,完美像素对账都无法重新检查 75 秒窗口,首次提交的 dialog ownership 与图层锁也无法进入 `finally` 释放。
- 决策:`requestJson` 新增 opt-in `deadlineAt`,从函数入口建立一次 lifecycle signal,覆盖缺 token 补票、业务 fetch、401 refresh 等待、所有 GET attempt、退避和成功 / 错误响应体读取。等待共享 refresh 只取消当前调用者,不把该 signal 传入共享 refresh 请求,避免一次图片对账超时取消 AuthGate 或其它请求正在复用的刷新;signal 到期也不得被鉴权 catch 吞掉后继续发业务请求、清 token 或广播登录态变化。未传 `deadlineAt` 的调用保持既有 `timeoutMs` 行为。
- 对账边界:窗口内每次 GET 的 deadline 为 `min(reconcileUntil, readStartedAt + 10 秒)`operation 已过期但从未读取时仍执行一次即时 GET,该例外最多 10 秒。deadline 只把本次读取视为失败,不穿透成未处理 rejection;轮询随后返回 `pending-confirmation` 并让首次提交 / hydrate 恢复的既有清理链执行。
- 验证范围:`apiClient` 定向覆盖缺 token 与 401 refresh 永久等待、响应体永久等待;`editorProjectClient` 钉住 deadline 透传且普通读取仍保留 60 秒默认 timeoutgeneration workflow 钉住窗口内和过期单次读取的 deadline。未扩大为全站请求超时迁移。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-04 完美像素 confirm 后 strict 保存失败保留原 operation
- 缺陷:源图 `ticket → PUT → confirm` 成功后,首次 strict layout flush 仍沿用从上传开始计算的 90 秒预算。预算耗尽会把占位标成普通 `failed` 并解锁;既有重试只接受 `pending-confirmation`,普通按钮遂创建新 dialog、重新上传并换 operation identity。旧 confirmed source object 无法再由用户路径引用。
- 决策:源准备 90 秒与 operation journal strict save 60 秒拆开。confirm 后立即形成稳定 `perfectPixelOperation`strict deadline 覆盖等待活动保存、PATCH、revision conflict reload 和 transport retry;到期会取消 strict request/waiter、阻止自动转移或继续 POST,并释放本地活动保存槽。浏览器 abort 不等于远端撤销,迟到 PATCH 仍可能提交,但其本地 Promise 不再触发 POST;后续重试依靠 revision CAS/reload 收口。
- 状态与重试:POST 尚未发出时的 strict 失败保留 `failed + perfectPixelOperation`,不做结果 GET;原占位提供 exact retry,复用同一 request、source objectKey、dialog/operation/task identity,不重新执行 ticket、PUT 或 confirm。普通完美像素入口把该状态视为未收口,不能创建第二条 operation;普通删除和随源图删除同样保留 identity。
- 预算不变式:源准备 90 秒加 strict journal 60 秒仍小于 legacy inline 占位 240 秒窗口;POST 只会在 operation journal ACK 后开始,因此不再计入该 legacy TTL。布局保存本身使用请求全生命周期绝对 deadline,覆盖鉴权等待、业务 fetch 与响应体读取。
- 剩余边界:confirm 成功后浏览器立即崩溃、且 operation 首次 PATCH 尚未落库时,仍可能留下 object-only 记录。这里的 object-only 是指 OSS 中已有私有源图文件、数据库也已有对应 `asset_object / objectKey` 登记,但尚无 `perfectPixelOperation` journal、项目 resource、素材库 asset、完美像素结果或画布结果图层。用户界面不可见且刷新后无法复用该 identity,再次点击可能重新上传;影响限于不可达的源图存储与垃圾记录累积,不代表结果已生成、重复扣费、越权或数据泄露。
- 本 PR 的修复边界到此为止:只保证 operation 已形成后,strict 保存失败或超时不会丢失 identity、不会重新上传,并且迟到 PATCH 不会继续触发 POST;不继续引入服务端 durable upload journal、上传 reservation、孤儿对象扫描/回收或历史数据清理,也不宣称撤销已发送的 PATCH。彻底消除上述崩溃窗口需要独立设计、评审和交付,不作为本 PR 的合并阻断项。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-04 generation 占位右键删除复用统一请求保护
- 缺陷:快捷键删除已在写历史前过滤未收口完美像素 operation,但 generation 占位的右键菜单仍直接调用低层 `removeCanvasGenerationDialogById`。低层会保留受保护 operation,上层却已写入 `delete-generation-result` 历史、清空选择并关闭交互,形成“占位未删但出现伪历史和 UI 副作用”的不一致;普通 generating dialog 也会绕过既有删除确认。
- 决策:纯 generation-dialog 的右键删除在任何历史或选择副作用前委托给 `requestRemoveCanvasGenerationDialog`。未收口完美像素只激活原占位并显示继续对账/原样重试提示;普通 generating 进入现有确认弹窗;终态占位才执行真实删除。层命令保留低层回调给快捷键和混合选择的既有可删除目标,不扩大本次改动为删除系统重构。
- 验证:层命令定向测试构造 `pending-confirmation + perfectPixelOperation` 右键目标,断言请求保护入口只调用一次、历史为零、选择保持、低层删除未调用且菜单收口。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-04 完美像素最终 PNG 在 PUT 前执行只读 preflight
- 缺陷:最终 PNG object key 虽已稳定,目录、画布 completion 和布局大小门禁仍只在 OSS PUT / HEAD 之后的原子 procedure 内判定。可预知的自定义目录缺失、目录越权、重复 dialog 或 2 MiB / 512 KiB 布局拒绝会先产生无引用 OSS object,再返回确定失败。
- 决策:上传 helper 拆成纯 prepare 与 execute。prepare 只生成精确 object key / request,不访问 OSShandler 用同一 object key 构造候选 project resource,调用受 runtime service identity 保护的只读 `preflight_editor_pixel_art_result_and_return`。preflight 允许尚未创建的默认目录,要求自定义目录存在且属于 owner,复用 `plan_editor_pixel_art_canvas_completion`,并对 legacy / structured 结果布局执行 2 MiB 总量与 512 KiB 单项门禁。通过后才执行 PUT / HEAD,再调用既有原子 persist。
- 预算与 unknown 边界:preflight、PUT / HEAD 和最终 persist 共用既有 60 秒绝对 deadline。preflight 失败或超时发生在第一次 PUT 之前,不带 `resultPersistenceStarted`;从第一次 PUT 发出开始继续沿用 unknown 标记和项目 GET 对账。
- 权威性与剩余风险:preflight 不创建锁、reservation 或新表记录;最终 `persist_editor_pixel_art_result_and_return` 仍在同一事务内重复目录、布局、幂等 identity 和 revision 校验。preflight 通过后若目录或画布并发漂移,最终事务仍可能在 PUT 后拒绝并留下无引用 OSS object;彻底消除该 TOCTOU 需要 durable reservation / journal 或事务协调,不在本 PR 的最小修复边界内。
- 契约影响:只新增 SpacetimeDB procedure ABI 与生成 bindings;没有表字段、index、migration、HTTP DTO、路由、状态码、OpenAPI 或 shared-contracts 变化。
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-03 主站与 AI Game Creator 复用单一泥点钱包 Store
- 背景:主站顶部优先读取 dashboard 总额,图片画板独立轮询 dashboard,充值 controller 和 AI Game Creator 又分别保存充值中心明细;不同请求返回时序不一致会让总额与分桶同时显示不同快照,快速生成或切换账号时旧响应还可能回滚余额。
- 决策:在 `packages/shared` 提供依赖注入式 `createProfileWalletStore`,统一保存 `ownerUserId`、完整 `ProfileMudPointBalance`、读取状态与错误,并在每个实例的独立闭包内完成请求合并、尾随补读、generation 失效和 owner 校验。主站注入 `getPlatformProfileRechargeCenter`AI Game Creator 注入 `getClientProfileRechargeCenter`;两端不共享 transport、认证或重试实现。主站顶部、图片画板顶部和“我的”统计只消费同一 Store 快照,总额固定取 `totalPoints`dashboard 的 `walletBalance` 只保留后端兼容,不再作为钱包 UI 数据源。
- 并发与账号边界:同一 owner generation 同时只执行一个余额读取;读取期间的新变化通知在当前请求结束后补读,直到覆盖最后一次通知。切换或退出账号立即清空快照、提升 generation、中止并脱离旧 generation 的 active 请求,新 owner 不得等待旧 transport 收束;不响应 abort 的旧 transport 可以在后台结束,但其结果必须忽略。消费端在 owner 绑定 effect 提交前也必须按当前用户 ID 同步屏蔽 owner 不匹配的快照,不能让旧余额与新账号身份同屏。充值中心读取或 mutation 响应必须携带请求开始时捕获的 owner,owner 不符时忽略。刷新失败保留已有快照;响应缺少 `mudPointBalance` 时进入错误状态,不用总额反推分桶。
- UI 与 mutation:充值 controller 继续保存商品、订单和支付状态,但充值中心响应必须同步写入共享快照,余额 mutation 应在应用响应后再通知一次合并刷新。充值弹窗的余额和分桶由当前 Store 快照覆盖。生成完成、失败退款、兑换码和邀请奖励等事件只发送余额可能变化通知;账号变化同时清理旧充值中心、账单与支付临时状态。`limitedPoints` 只按既有后端快照原样保存,本决策不新增或调整会员限时泥点展示与结算。
- 2026-08-05 审查补充:主站 transport adapter 必须通过既有请求 options 真实透传 `AbortSignal`;页面恢复时相邻的 `visibilitychange / focus` 合并为一次余额通知,并在卸载时清理待执行任务。Store 的 owner 输入统一在边界 trim,活动请求清理同时观察 Promise 成功与失败,不能用无人接收的 `finally` 派生 Promise。
- 2026-08-05 账号隔离补充,2026-08-07 完善 legacy 总额入口:账单读取、奖励码和邀请码兑换使用各控制器自己的账号生命周期 / 请求 revision,不依赖共享 Store owner effect 的提交时序;旧账号回调不得更新新账号 UI、结束新请求或刷新新账号钱包。充值下单、邀请码兑换和奖励码兑换三条写请求还必须共享账号生命周期 `AbortController`,在切号 effect cleanup 与卸载时中止旧 signal,使 `fetchWithApiAuth` 的 refresh 等待和写请求退避立即结束,禁止旧 POST 重试重新读取新账号 Token。AI Game Creator 的账单与充值使用独立 lifecycle,账号切换 render 必须同步屏蔽旧账单、充值和支付状态。充值中心兼容响应暂缺共享明细时,弹窗保留响应自带的 `walletBalance / mudPointBalance`,不把有效总额改写为 `0`owner 匹配的 legacy `walletBalance` 同时可供个人中心统计卡和图片画板顶部等纯总额入口兜底,但 `mudPointBalance`、钱包展开明细和账单分桶继续保持空,不从总额反推或伪造分桶。
- 2026-08-07 审查收口补充:共享 Store 显式保存 owner 隔离的 `legacyWalletBalance`,确保首次生命周期读取旧响应时无需先打开充值弹窗即可展示纯总额;较新的 legacy-only 响应必须原子清除旧 `mudPointBalance`,该字段不能生成分桶。直接余额快照附带单调 operation sequence;充值中心、支付确认和 watch 等异步响应都在请求发起前捕获快照,后发操作先落地后拒绝更早快照回滚。刷新错误只向 UI 暴露稳定中文提示,不透传 transport / 后端实现文案。
- 2026-08-07 lifecycle 所有权补充:主站钱包坚持全应用唯一 lifecycle,由 `AuthGate` 根认证边界绑定 ready user;现役平台壳、图片编辑器和 profile controller 只消费 Store,不重复声明 owner。根边界在账号失效、依赖切换、StrictMode effect replay 和最终卸载 cleanup 时统一 `resetWalletBalance`,中止活动请求并清除 owner、明细和 legacy 总额;禁止在子页面按相同 user ID 各自清理 module-level Store。
- 2026-08-07 refresh 发布隔离补充:`apiClient` 把公开 token 设置与清理视为认证代际变更,共享 `/api/auth/refresh` 只在“代际 + 发起时 token”快照相同时复用。refresh 成功只能以同一快照 CAS 发布新 token,旧账号晚到成功必须拒绝;旧 refresh 的 401/403 也只能在原快照仍当前时清 token。新账号进入新代际后立即发起独立 refresh,不等待也不加入旧账号 Promise。
- 2026-08-05 生成扣退费时序补充:external generation 入队时尚未扣费,worker 领取为 `running` 后的资产操作才预扣,业务失败则先退款再写任务失败态。主站钱包因此以账号下全局 active external task 为轮询生命周期,每轮成功状态读取都通知共享 Store 合并刷新,终态轮同时覆盖成功结算与失败退款;画布内容刷新仍只限当前项目的 `completed`,不因其它项目或 `failed` 刷新画布。
- 验证:共享 Store 覆盖首次读取、尾随补读、旧响应、账号切换、错误保留和错误 owner;主站覆盖 dashboard 与充值中心不一致时三处 UI 仍一致、切换账号清空及 focus 刷新;AI Game Creator 覆盖 adapter、账号 owner 和 focus 刷新。运行定向 Vitest、两端类型检查、编码检查与 `git diff --check`。
- 关联文档:`docs/【项目基线】当前产品与工程约束-2026-05-15.md`。
## 2026-08-04 AI 游戏项目 manifest 存储与工作台实时投影
- 存储决策:`.agent/manifest.json` 的版本追加不可变约束由同目录持久专用锁保护,读取旧状态、校验版本前缀、安装临时文件和安装后回读必须处于同一临界区;进程内 Mutex 不能替代跨进程文件锁。
- UI 决策:Project Supervisor 持有运行中 manifest 状态并向外层启动器同步完整快照;外层项目上下文继续是工作台投影的唯一输入,只接受当前项目路径的更新,不另建资产、任务或版本平行状态。
- 依赖图决策:Agent DB 尾部读取一旦截断,审计 producer、task flow 与对应 `cyclicTaskIds` 失败关闭;独立 `dependencyDepths` 仍由 Rust 从当前 manifest、精确资源引用和仍可信的任务深度下限构建,前端只做资源存在性与非负安全整数校验后继续消费。manifest 精确资源引用、reference connection index、资源环和 unresolved reference 与审计生产者证据分离。SVG 保持装饰性,辅助技术消费画布关联的文本关系列表。
## 2026-08-05 AI 游戏项目实时 manifest 失效与资源焦点状态机
- 失效源决策:后台 `task.update`、`canvas.asset_generate`、任务起止 / 终态投影、正式版本追加和 autonomous manifest reset 都处于 Runtime 动作或生命周期内,并在写入后回到共用 Runtime emitter;因此以该 emitter 作为统一 manifest 失效因果点,不在 WorkspaceLauncher 新增平行回调,也不轮询 manifest。Rust / TypeScript 的 `game-creator-agent-runtime-update` 合同增加 `manifestInvalidated`App 在全部 Supervisor、selected agent、session / run early return 之前消费它。
- 跨进程决策:External Runner 没有 GUI `AppHandle`,不能假设普通 Tauri Runtime event 会跨进程到达。Runner IPC 协议升级为 v5GUI owner attach 同时登记 GUI 创建的 loopback 随机端口和 64 位随机十六进制令牌;Runner 内同一 Runtime emitter 发送最小 `projectPath + agentId` relayGUI 校验令牌后转成 `game-creator-manifest-invalidated`。GUI 内 Runtime 继续直接发送完整 Runtime update。两条路径汇合到同一个 App manifest 重读器。
- 重读决策:`get_local_game_manifest` 按项目 single-flight;同项目读取中再次失效只排队一轮后续读取,不启动并发请求。响应应用必须同时匹配 mounted、活动项目路径和 project scope version,旧项目、旧 scope 或卸载后的响应全部丢弃。重读后的 App state 继续沿既有 `onManifestChange -> currentProjectContext -> ProjectDevelopmentView` 单向投影,不复制资产 / 任务 / 版本状态。
- 焦点决策:资源详情焦点以稳定 `resourceId` 的转换而非重建后的资源对象决定。`null -> id` 和 `idA -> idB` 聚焦详情;`idA -> idA` 保留详情内部 active element。显式收起 / Escape 恢复滚动并优先返回触发卡片;资源已删除时清理 focused / matching selected ID 并聚焦资源搜索框;项目或运行视图切换清除旧 trigger 与 restore 标志,禁止跨项目恢复。
## 2026-08-04 图片画布素材类型采用资源默认值与布局覆盖双层模型
- 背景:画布复制逻辑曾为副本生成 `local-resource-copy-*`,导致同一媒体被伪装成未登记资源;随后改为复用 `resourceId`,但手动修改图层标签仍通过“按新 `assetKind` 查找 / 创建项目资源并换绑当前图层”实现。这会让单纯标签修改增加资源行、漂移 `resourceId`,并在异步回填与复制交错时形成“新类型 + 旧资源”的副本。
- 决策:`editor_project_resource.asset_kind` 是跨布局共享的资源默认类型,`editor_canvas_layer.asset_kind_override` 是当前布局实例的可空类型覆盖;有效类型唯一按 `override ?? resource default` 计算。点击当前图层标签只新增、修改或清除 override,不创建资源、不更换 `resourceId`;清除后恢复继承。若另行提供资源默认类型编辑,必须原地更新同一资源行,并只影响没有 override 的引用图层。
- 复制语义:`layerId` 是同一 canvas 内唯一的布局实例身份,`resourceId` 是允许多图层共享的项目媒体身份。复制、粘贴、创建副本和剪切后粘贴只生成新 `layerId`,复用来源 `resourceId` 并复制 `assetKindOverride`;已登记资源不得再次上传或创建,副本后续可独立修改 override。
- local 状态:`local-*` 只是 ID 形状,不能直接解释为“素材仍在保存”。新上传 / 新生成素材是否 pending 取资源登记在途状态;严格满足兼容谓词的历史自包含本地角色动作序列是持久化终态,不得误报等待。若当前版本尚不能复制这类序列,以准确原因失败关闭;既非 pending 又不满足历史谓词的 unresolved local 图层也失败关闭,但不得承诺稍后一定自动恢复。layout PATCH pending 不参与资源登记判断,系统剪贴板图片导入不受影响。
- schema 与迁移:在现有 `EditorCanvasLayer` 结构体末尾追加 `#[default(None::<String>)] asset_kind_override: Option<String>`,不删除、改名、重排或改类型。legacy 图层类型与资源默认相同则迁移为 `None`,不同则迁移为 override;资源无默认值时只有全部引用图层显式同值才补资源默认,否则保留各自 override;自包含历史序列的显式类型迁入 override,不伪造资源。同步 `migration.rs`、表目录 / 数据契约、生成 bindings、HTTP DTO 与结构化 canonical hash,并运行 `npm run spacetime:generate`、`npm run check:spacetime-schema`。
- 并发边界:未登记图层被禁止复制后,不再按临时资源 ID 合并项目资源创建请求,也不再用一次响应批量改写共享临时 ID。每个合法新增图层保留自己的响应快照与回调;layout PATCH 的串行 latest-wins 队列、共享资源的多布局引用和 session 资源快照按 `resourceId` 去重继续保留,它们与资源创建 single-flight 是不同机制。
- 媒体兼容边界:override 只允许在资源默认类型的同一媒体族内变化。动作、视频各自独立成族,`audio/sound-effect/background-music` 同属音频族,其余类型与空默认值同属图片族,`scene` 明确属于图片族。前端可选标签与 SpacetimeDB `EDITOR_CANVAS_ASSET_KINDS` 必须同步包含现役用户可覆盖类型;前端菜单禁用跨族标签且更新入口重复校验;后端对每个结构化图层按资源完整校验,跨族值清空后回退资源默认类型,不因该兼容错误拒绝整个保存。
- 历史恢复:客户端读取到已持久化的跨族 override 时必须保留图层、回退资源默认类型、显示明确提示并自动提交清理后的布局;不得再用 `hydrateLayer() -> null -> filter(Boolean)` 静默隐藏持久层仍存在的图层。修复保存只替换命中图层的规范化布局项,其他尚不能 hydrate 的历史项原样保留,避免修复一个标签时顺带删除无关数据。
## 2026-08-03 Agent Runtime 原生工具合同本地失败关闭
- MCP 边界:动态 MCP 函数的 `arguments.input` 必须在创建 durable pending 前按当前 catalog 的原始 `inputSchema` 本地校验;native parser 负责把错误归类为可修复的 arguments-schema,统一 enrichment 覆盖 legacy 兼容解析并把错误接回同一 repair 链。实际 MCP 调用前还必须按当前 catalog schema 重验一次,阻断升级前遗留的 schema 外 durable pending。校验器关闭 HTTP 与文件解析能力,外部 `$ref`、无效 schema、required/type/enum/additionalProperties 不匹配全部失败关闭,错误不得回显参数或 schema 私密值。
- Native PromptProvider 请求只描述实际广告的 `update_agent_plan`、动作函数、`respond_to_user` 和动态 MCP 函数;内部 `mcp.call` wrapper、`thinkingSummary/planUpdate` envelope、空 actions 以及无 function-tools 文本回退不再进入实时 Prompt。required-nullable 字段未使用时显式传 JSON `null`,空对象 input 只允许权威空 schema 工具。
- Supervisor 合同:当前模式具备画板服务授权时,`art-asset-plan` 的 owner 产物统一为 `assets/manifest.art.json` 与 `assets/art-spritesheet.png`;普通模式未登录或高级模式未配置 Developer Key 时只要求 `assets/manifest.art.json`,不得伪造或要求三个 PNG。版本化 Bundle 的视觉合同和 playbook 必须从同一授权事实派生。
## 2026-08-04 图集事务与 Tetris 完成门使用句柄和 AST 收口
- 图集事务:九文件旧合同在写 `prepared` 前必须全部持有可信源句柄并整体复读;Unix 事务控制文件统一通过锚定目录句柄的 `openat / unlinkat + O_NOFOLLOW + O_NONBLOCK` 操作,FIFO 等非普通文件必须在读取前失败关闭,恢复前态也必须在同一叶子句柄上稳定双读并复核前后元数据与当前 inode。Windows 祖先 pin 只请求读访问并拒绝 delete sharing,可重复持有;只有事务叶子句柄请求删除访问。事务捕获与恢复 CAS 从 canonical 项目根句柄逐组件打开或创建父目录,staging、no-replace link/move 与 unlink 均相对固定父目录句柄执行;清理事务证据前再次复核整组安装结果。安装后的任何清理错误都按实际 canonical 状态把当前项纳入逆序回滚,不能留下新旧混合合同。
- 图集事务 live identity`prepared` 后继续由同一 trusted transaction directory handle 贯穿 canonical 提交、`committed` 清理和 live rollback,不再按路径重开并接受替换目录;发布 `committed` marker 前后都必须验证 retained handle 的权威 pathname identityUnix rename 漂移不得降级为成功 warning。清理前把权威叶子以 no-replace rename 原子隔离到 retired 名称,复核 retained inode 后清空,最终删除前再次复核;重启恢复先幂等清理已隔离目录。恢复在任何写入前冻结九项 canonical 全部前态,晚序普通文件变化必须 CAS 失败且不得被旧快照覆盖。没有 durable journal 的 legacy `.previous / .replacement` 只做锚定识别并进入 reconciliation,不自动恢复 canonical 或删除残留。
- JavaScript / ESMTetris 静态连续性检查以 Oxc parser、semantic 与 AST visitor 为权威。无效语法、ASI、template interpolation、正则 / 注释、表达式体箭头、参数和词法遮蔽、export alias、import 后再 export 的 bridge、re-export 与缺失导出链接不再由字符串扫描猜测;跨模块 alias 保留 origin 根绑定名称,只按解析到 import symbol 的 reference span 改写 importer。同一 export 的多个本地 alias 按大小写敏感的 symbol identity 保序保留,同一 dependency 的多条 import declaration 合并绑定;投影在 importer 内按最终 origin 聚合,因此同一 origin 经不同 dependency 或 bridge 到达时也只生成一次根声明。组合单元为 importer 和所有 origin 根绑定分配无冲突的确定性名称,匿名 default、同源私有根名、importer 局部根名及不同 origin 都不得合并;重命名后必须重新通过 parser 与 semantic,只有最终启发式扫描文本统一小写。object shorthand 展开后保留原键;namespace 只改写绑定到 import symbol 的完整 member span,同文本属性和局部遮蔽均不得连带改写。HTML `type` 存在时优先于 legacy `language`。源码投影仍只是静态语义门,最终完成继续要求绑定当前 revision 的真实 Chromium 固定试玩回执。
- JavaScript / ESM 声明与 namespace 补充:顶层 function/class/variable declaration 直接按 Oxc statement span 投影,禁止用首个分号或换行截断箭头函数、多行 initializer 或多 declarator;对象、数组、默认值和 rest 解构中的全部 binding 必须递归进入导出图,同一声明只投影一次。import symbol 不从 importer 自有 root binding 集合按小写文本扣除,大小写不同的合法绑定继续隔离;模块组合按依赖层数迭代到稳定闭包,使被导出函数引用的 imported dependency 继续进入最终 consumer 单元,同时限制最终 span replacement 后的单个组合单元最多 `2 MiB`、整轮投影累计处理最多 `32 MiB`,分支或循环图超限失败关闭。固定字符串 dynamic import 同样由下游实际使用的 export 反向驱动加载,未使用 export、未调用嵌套函数和恒假分支中的 source 不进入模块单元;被选声明中只有由 `await import` 解构、namespace member 或 `.then(...)` 静态解析到的 export 才投影。callback 参数、解构 alias 和 namespace member 的引用必须按 semantic symbol span 改接到投影根,禁止靠同名文本共现绕过局部遮蔽。完成全部 span replacement 后重新校验完整组合 unit,最终启发式扫描必须先按原始大小写完成 AST 解析和掩码,再归一化文本。匿名 default function / arrow 在原始 AST 中也必须以 collision-safe synthetic binding 注册可外调 root span,保证 wrapper 内 imported member 的传递依赖继续传播;synthetic binding 必须避开真实根绑定,冲突改名只更新 `default` target,不得改写用户同名命名导出。renamed re-export 的 namespace 投影同时携带 importer member 名和 origin export 名,分别用于定位 consumer span 与 origin declaration。named import、namespace import 及 namespace 解构 alias 的成员调用必须保留完整静态成员路径,并用调用 span 的恒假分支可达性过滤后再向上游传播 demand;对象 / class 的直接成员、解构 alias、实例 alias 与下游 wrapper 都遵循同一规则。对象的 method shorthand、函数表达式值、箭头函数值以及 class function-valued field 均按精确函数 span 注册成员根,不能因声明写法不同漏载其可达 dynamic dependency,也不能把同一属性内未调用的嵌套函数误当根。
- JavaScript / ESM occurrence 与成员根补充:dynamic import demand 必须以 source 和 import occurrence position 共同隔离,同 source 的可达裸 import 不得借用不可达 occurrence 的 export;动态 namespace 保留首段 export 后的完整成员路径,声明 initializer 与后续赋值式 `await import` 都绑定 semantic symbol。named / namespace 成员作为回调参数、对象解构 alias、实例 alias 和下游 wrapper 时仍按实际调用 span 传播 demand。constructor、`new Game().method()` 与实例 alias 分别建立精确成员根,`this.method()` 只匹配同一 class / object owner。顶层声明位置直接保留 Oxc span,不得用源码文本 `find` 反查。
- JavaScript / ESM alias 与 receiver 补充:imported member 作为参数时只允许受控的 callback API 建立执行 demand,普通日志或元数据传参不得推断为调用。普通 member alias 同时支持声明 initializer 与后续赋值,`new ns.Game()` 等完整 constructor path 必须传播到实例 alias。局部对象、class、实例、`this` 与 `super` 的方法调用统一按 semantic receiver owner 匹配,禁止再按末级方法名跨 owner 扩散到同名 decoy。
- JavaScript / ESM 控制流身份补充:member alias、局部 receiver 与动态 namespace 的赋值必须保存 assignment position 和 enclosing function scope;函数体使用按真实 invocation position 选择当时事件,多次调用跨越赋值边界时合并可能 owner,恒假分支、未调用函数或调用之后的赋值不得覆盖更早使用点。导出的 function 与 class/object member 额外以模块初始化结束作为潜在外部调用点,使声明后生效的顶层赋值进入 demand,同时保留更早本地调用状态。动态依赖传播以原始 owner 模块 AST span 为权威,不因投影重排声明或省略独立赋值语句重算 alias。class method owner 进一步区分 static / instanceclass expression 与实例化 alias 使用同一 owner 图。callback API 只接受 semantic 未解析的已知全局调度函数,以及可由 AST 证明的 literal array / dynamic import 调用;被用户定义或遮蔽的同名 `setTimeout / map / then` 不得推断执行参数。
- JavaScript / ESM 深层可达性补充:constructor、`.call/.apply` 与受控 inline callback 建立真实 invocation;具名 function expression 不再生成遮蔽外层 binding 的重叠节点,普通 inline function / arrow 未被执行时保持不可达。受控 callback API 名大小写敏感,并以精确参数索引建立执行边:timer、microtask、RAF、Promise 与数组迭代取第一个参数,`addEventListener` 取第二个参数,delay、initial value、event type、options 和额外参数保持普通值。条件/循环赋值合并执行与跳过状态,conditional expression 合并各 owner,未知确定赋值显式 invalidation;已调用函数对外层 alias 的副作用按调用位置传播,`super` owner 固定在 class 定义点。恒假扫描先屏蔽 parser 识别的注释和 literal;恒假分支区间随单次函数可达性 analysis 预计算、排序合并并以借用二分索引查询,不再使用 thread-local 完整源码 key 或命中时 clone ranges。投影 canonical 根名避让两侧全部非 import bindingdynamic shorthand 保留原键,循环模块按相同原始声明去重,同名 dynamic export 不得拉入无引用本地声明。
- JavaScript / ESM 构造、继承与 callable 补充:`new` 沿冻结的 class owner 图执行本类显式 constructor、显式 `super()` 或隐式 derived constructor,并支持直接 class expressioninstance / static 成员未 override 时继续沿 `extends` 链查找。普通嵌套 function 不继承 class `this`arrow 保持词法 owner。`let binding; binding = function/arrow`、本地 function alias 和 Function.prototype `.bind()` 结果都建立 callable identity`.call/.apply/.bind` 只有在 receiver 可解析为 callable 时采用 Function.prototype 语义,业务对象同名方法仍作为普通 receiver method 执行。
- JavaScript / ESM callable、callback 与视觉可达性补充:callable conditional expression 合并两端全部身份;声明式与后续赋值式 member `.bind()` 都冻结成员 owner。受控 callback API 穿透 callee / receiver 外层括号,Promise `.then` 取 fulfilled 与 rejected 两个 callback 槽,`.catch/.finally` 仍仅取首槽。视觉资产路径与 `drawImage` 启发式可使用 ASCII 小写副本,但 AST、semantic binding 和函数可达性只解析原始大小写 JavaScript,大小写不同的 `MainLoop/mainloop` 不得合并。Canvas 视觉门复用受 256 文件、累计源码 `2 MiB` 与投影处理 `32 MiB` 限制的 inline / external module 链接与投影结果,逐 unit 关联路径、图片变量和可达绘制;inline `type=module`、本地 `src` module 及真实可达依赖可作证据,未链接文件、恒假动态依赖、未调用函数、跨 unit 拼接与纯 HTML 路径诱饵均失败关闭。
- 浏览器因果:状态证据仍只冻结 trusted input listener 及其点击派生微任务内的变化;完整手势身份改由宿主在成功完成 Chromium 元素鼠标输入后调用隔离世界 finish。更早注册的 `window` capture listener 即使调用 `stopImmediatePropagation()` 也不能阻断探针自身的完成身份,页面脚本不能伪造 host finishRAF / timer 继续不计入动作结果。
- 验证边界:Linux 定向回归覆盖目录相对读写与清理、祖先 symlink、CAS 安装后错误、九文件混合快照、Tetris AST 反例和七项真实 Chrome generic 试玩。Windows cfg 代码必须继续在真实 Windows CI / 发布构建验证;本地缺少 MinGW C compiler 时,安装了 Rust target 也不能把交叉 `cargo check` 失败误报为源码失败。
- JavaScript / ESM 循环与体积补充:投影声明按 `(origin module, original root binding)` 保存身份,canonical 重命名不能改写原始身份;删除回流声明后仍把 import 引用改接到 importer 已有 canonical,并给固定点保留“模块数 + 1”轮的产出与稳定确认预算,未收敛时失败关闭。inline module 先按浏览器可执行标签提取原文并计入与外部脚本共享的累计 `2 MiB` 源码预算,再做语法和语义校验;无效超限模块不能被 helper 静默过滤,无效小模块也明确失败关闭。
- JavaScript / ESM alias 求值顺序补充:receiver alias 保存赋值完成时刻并在该时刻解析 source owner,后续 source 重赋值不得倒灌。调用事件按内层参数 / RHS 先于外层调用 / assignment 生效;`switch case/default` 赋值一律保留跳过与各分支可能状态,普通函数内无条件 `return / throw` 截断之后的 alias 副作用。恒真 / 恒假关键字大小写敏感,可能被局部或参数遮蔽的 `undefined` 不再作为文本恒假值。
- JavaScript / ESM callee 与终止顺序补充:identifier callee 和 `new C(args)` 的 constructor / instance owner 在实参前冻结,invocation effect 保留在实参之后;callable assignment 到 RHS 完成后才生效,`start = start()` 继续调用旧值。`return / throw` 表达式中的 assignment / call 先执行,截断点取表达式之后的 AST statement end;函数体使用 Oxc body span,不从默认参数或解构参数中的首个 `{` 猜测。未知 guard clause 后续与 `catch` 体一律按 conditional effect 合并旧状态。
- JavaScript callable 分支与参数快照补充:conditional expression 必须在 test 求值完成后,分别于 consequent / alternate 自身起点冻结 callable identity;受控 callback 参数按该参数自身起点解析,前置参数产生的 alias 副作用先于后续 callback identity 生效,callback 的执行边仍保留在注册调用完成位置。
- JavaScript / ESM live binding 投影补充:被选 export root 的直接顶层 assignment 及其 RHS 依赖必须与原声明共同投影,覆盖 `export let x; x = impl`、导出对象成员安装和 class prototype 安装;assignment target 以 semantic root symbol 归属,函数体写入、嵌套控制流和无关 root 写入不得因同名文本进入投影。共享 declaration 的写入按原始源码位置合并,继续参与 canonical 重命名、循环去重和既有 `2 MiB / 32 MiB` 门禁;全部依赖声明必须先于延后的初始化写入输出,不能因 projection traversal 产生 TDZ。
- JavaScript callee 短路、构造与 Promise 链补充:sequence callee 保留前序求值副作用并只调用末项,logical expression 及 `||= / &&= / ??=` 按已知 callable 真值 / nullish 状态短路,未知状态才保留可运行分支。`new` 支持 assignment / conditional callee,并让未被 class 静态业务成员或函数对象 own override 覆盖的 `.bind()` 结果继续指向原 class construct target。`delete` 对 `.call/.apply/.bind` 的括号包装不改变 own-property identity,删除后恢复 Function.prototype intrinsic。dynamic import Promise 的连续 `.then/.catch/.finally` 任意深度都建立 callback 边,但参数槽固定为 `then=[0,1]`、`catch/finally=[0]`,额外参数不得升级为执行 demand。
- JavaScript callback 时序、内建覆盖与 class expression owner 补充:受控异步 callback 的注册位置只建立可达调用边,闭包读取的外层 alias 状态选取注册所在同步作用域收尾点,不能冻结在注册点;函数体写副作用仍不得同步提交到注册调用末尾。数组字面量上的已知迭代 callback 继续按同步执行传播外层 alias 变化。函数对象自有 `.call / .apply / .bind` assignment 按函数对象身份形成成员 callable 状态,普通函数别名共享同一对象覆盖,`.bind()` 结果保持独立对象身份;存在覆盖时禁止回退到 Function.prototype 语义。`new (class { ... })` 赋给局部变量时冻结 class expression 的 instance owner,使后续实例方法调用保持可达。
- JavaScript / ESM assignment root 与直接动态 namespace 补充:被选 export 的顶层 live-binding function / arrow assignment、对象成员安装和 class prototype 安装同时成为对应 root / member 的 projected reachability rootclass static 与 instance assignment 不得串线;`(await import('./dep.mjs')).run()` 及等价静态 computed member 直接记录 occurrence-scoped `run` export demand,恒假分支、未调用函数与其它既有可达性边界继续生效。
## 2026-08-04 静态视觉门脚本与 ESM 求值顺序
- HTML 输入只在非 raw-text 区域屏蔽真正的 `<!-- -->` 注释;`script / style` 等 raw-text 原文保持逐字不变,再交给各自 parser / semantic 处理。禁止在整份 HTML 上按文本删除 `//` 或 `/* */`,否则字符串中的 `https://`、路径和注释形状会被破坏;HTML 注释内的标签、脚本和素材路径仍不得形成视觉证据。
- classic script 分析单元把 inline 与无 `defer / async` 的本地 external 正文按 `game/index.html` 标签顺序交错组成 parser-blocking 段,再把 classic external `defer` 按文档顺序放到解析完成后的 deferred 段;不得把 defer-before-inline 误投影为外链先执行。classic external `async` 的下载完成顺序不可静态证明,当前静态门直接失败关闭。带 `src` 标签的 inline body 继续忽略;外部文件仍执行可信普通文件、`game/` 边界、文件数与累计体积门禁,重复标签按浏览器出现次数保留求值位置。
- Canvas 尺寸、可见性、元素绑定和 stylesheet 选择器扫描只消费浏览器可渲染标记;`template / textarea / noscript / title / style / xmp / iframe / noembed / plaintext` 内的 Canvas、标签和样式诱饵全部跳过。活动顶层 stylesheet 与可见标记分开提取,既允许真实 CSS 参与隐藏/尺寸判断,也不把 CSS raw-text 中的伪标签当作 DOM。
- ESM 组合单元按 dependency 初始化先于 importer 顶层求值排列。import reference 的 span replacement 仍基于原 importer 完成,随后把已闭包的 dependency projection 放在 importer 前并对最终单元重跑 parser、semantic、单元 `2 MiB` 与累计投影 `32 MiB` 门禁;循环模块继续按 `(origin module, original root binding)` canonical identity 去重并要求有界固定点收敛。
## 2026-08-04 JavaScript 延迟状态与复合调用边
- 受控异步 callback 的 alias 读取按完整 enclosing invocation 链延迟到各层函数同步收尾,最外层再延迟到当前 job 末尾;callback 写入仍不在注册点同步提交。conditional / assignment expression callee 分别在 test / RHS 求值后建立调用边,`new` 同时执行普通 function constructor 及 alias。
- 函数对象自有 `.call / .apply / .bind` 覆盖允许以普通对象静态 member callable 作为 RHS,并继续按函数对象身份跨普通 alias 共享。`delete` 自有覆盖后恢复 Function.prototype intrinsic;条件删除合并覆盖与 intrinsic,非 callable 自有值仍视为属性存在并禁止 intrinsic 回退。
- callable 运算结果只把普通 `=` 与实际执行分支的 `||= / &&= / ??=` 视为可调用值来源,算术、位运算和移位复合赋值不得直接采用 RHS callable。logical callee 对 boolean / numeric / string / null 字面量先执行真实短路;sequence receiver 的末项保持函数对象身份,用于 `.call / .apply / .bind` own override 与 `delete`。Promise callback 只接受固定 dynamic import、未遮蔽原生 `Promise` 构造 / 静态方法及可证明变量、alias 和连续链,业务 thenable、未知返回值和遮蔽 `Promise` 保持普通成员调用。直接 `new (Fn.bind(...))()` 在 intrinsic bind 仍可能时继承普通 function `Fn` 的 construct target,确定 own override 时不回退。
- ESM 最终绑定与 occurrence 补充:外部 callable root 只认模块初始化完成时同一 live binding / member 的最后一次直接顶层 assignment,旧 RHS 不得加载;直接 awaited namespace member 在赋值、受控 callback、constructor 和深层静态 member path 中仍按 `(source, occurrence)` 传播并替换首段 export。assignment class expression 的 static / instance member demand 分离。
- ESM 初始化与阻塞补充:projection declaration 保持原模块源码顺序,dependency origin 保持 importer 声明顺序;循环 canonical 去重同时删除声明和对应初始化写入。side-effect static import 只联结按 dependency-before-importer 排列的直接 `globalThis / window` 顶层 effect,不暴露 dependency local binding;静态可判定永不完成的 top-level await 至少对未遮蔽全局 `await new Promise(() => {})` 失败关闭,嵌套函数同形 decoy 和可完成 await 保持允许。
## 2026-08-04 ESM 解构写入与模块完成性收口
- side-effect static import 若读取 dependency-local declaration,组合投影必须把该声明及其 semantic 依赖按原源码位置放在 effect 前;同一声明也被 importer 使用时按 origin declaration identity 去重并统一做 collision-safe canonical 重命名,既不泄露无关 local,也不引入 TDZ 或重复声明。
- exported live binding 的顶层 object / array destructuring assignment 纳入 projection;最终一次写入中与目标 root 对应的属性或槽位 callable 才作为 dynamic demand root,更早写入和相邻 decoy 不得回流。
- 未遮蔽全局 top-level `await new Promise(executor)` 的 executor 若不调用或传递 resolve / reject、也不显式 throw,则返回值不参与 Promise settle,静态门按不完成失败关闭;resolver 按 semantic symbol 识别,Promise 遮蔽与嵌套函数内 await decoy 继续保留。
- dynamic import `.then` callback 的对象解构只建立 occurrence-scoped binding,不再仅因读取 export 属性就形成 callable demand;只有该 binding 的可达引用或调用才向 export 内部传播动态依赖,未使用和恒假引用保持关闭。
## 2026-08-04 静态视觉证据绑定与只读 namespace
- classic HTML script 组合在每个 Script Record 之间保留硬语句边界,parser-blocking 与 defer 各自保持浏览器顺序,禁止因 ASI 把相邻标签拼成一个表达式。
- Canvas 视觉门使用 Oxc AST 和 semantic symbol 关联可见 DOM Canvas、实际 context、精确 `drawImage` member callee、图片 binding 及绘制位置的 `.src` 状态。fakeDrawImage、离屏或隐藏首选 Canvas、恒假 / 未调用 / 绘制后覆盖均不作证;未知条件保持失败关闭。
- dynamic import namespace member 写入保持真实只读失败语义,视觉投影不得把写 target 改造成可变本地;可达写入的 RHS 与后续语句不提供视觉证据,纯 namespace read 继续传播 export demand。
- 身份与 telemetry 扫描只消费可渲染文本、可执行 inline JavaScript 和已链接外部 unit,排除 inert/raw-text、HTML 注释、带 `src` body 与非 JavaScript script。资产路径比较保持大小写,Linux 文件身份不得经 ASCII 小写副本合并。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-04 图片画布快速编辑改用统一正向白名单(2026-08-05 修订)
- 背景:画布生成结果统一显示快速编辑,但部分角色动作 / 序列帧和音频结果会进入不受支持的图片编辑链路;不同入口各自判断时也容易继续漂移。
- 决策:快速编辑只支持普通静态图片、角色图、规范图、完整图标图集、图标规范、UI 设计图、宣发图和游戏场景图。视频、单个拆分图标、角色动作 / 序列帧、音效与背景音乐不支持;新增媒体或素材类型默认不开放。浮动工具栏、图层右键菜单、独立图片菜单、打开面板入口和提交门禁统一调用同一个正向白名单;后端图片编辑 BFF 基于目标图层的有效素材类型与媒体类型执行同一正向门禁。
- 边界:角色动作继续通过对应的动作生成链路处理,不再把当前帧当作可快速编辑图片。
- 2026-08-06 修订:画布 Agent 的 `edit_image` 只接受图片输入,新任务以 `assetKind=null` 表示普通静态图片,不再使用 synthetic `editor_agent_edit_image`。worker 仅按服务端生成的 `editor-agent:` dedupe namespace 识别并归一历史排队 payload;普通调用伪造旧值继续被拒绝。已持久化资源中的旧值只有在后端从真实目标图层 / 项目资源解析后才兼容为空类型,避免历史 Agent 结果失去快速编辑能力,同时不扩大请求白名单。
- 2026-08-07 修订:站内与 External v1 图片编辑请求统一只接受必填 `sourceReferenceId`,且该值必须是当前账号已登记的项目资源 ID 或素材 IDobjectKey、URL、Data URL、Blob URL 以及旧 `sourceImageSrc/sourceResourceId/assetKind` 字段全部返回 400,不提供兼容别名。后端用共享窄查询分别按两张表主键定点解析,双表同 ID、未命中、跨账号、对象缺失或越权均失败关闭;权威类型完全来自业务记录,只允许普通静态图片、规范图、角色图、完整图标图集、图标规范、宣发图和 UI 设计图。请求带 `targetLayerId` 时必须同时带 `projectId`,来源与目标优先比较 `assetObjectId`,任一方缺失才比较 canonical `(bucket, objectKey)`,且来源默认类型必须与目标资源默认类型一致;最终类型取目标覆盖值或目标资源类型。HTTP 入队写入版本化服务端解析快照,worker 执行前按同一业务 ID 再次定点解析,身份或类型漂移即失败关闭。旧任务只把已有资源 ID 或旧来源字符串本身当业务 ID 迁移,绝不按 objectKey 反查。Canvas Agent 必须从 `ImageMetadata.reference_id` 取主来源;红框标注上传图只作为辅助 `referenceImageSrcs`,不能冒充被编辑资源。仅以素材 ID 编辑时,队列审计与 `generationInputs.references` 保留素材 ID,不伪造项目资源关系。
- 2026-08-08 修订:`scene` 是单张静态图片素材,加入前端快速编辑正向白名单和 api-server 权威来源白名单;编辑结果继续保留 `scene`。用户标签覆盖侧的前端菜单与 SpacetimeDB 结构化布局白名单也必须显式覆盖 `scene`。通用图片生成接口仍拒绝 `scene`,避免绕过结构化场景生成契约。
- 2026-08-08 修订:画布 Agent 的 `edit_image` 工具内部可先形成待确认的 `EditorImageEditRequest`,但确认接口必须在通用入队前复用站内图片编辑的来源解析与目标预检,写入 `{ version, request, source }` 服务端快照。worker 只解析正式 versioned payload 与既有历史 payload,不接受当前 direct request 作为 fallback;这样未上线的 Agent 路径在生产端原位修正,不扩大消费端协议。
- 2026-08-08 修订:图片编辑业务引用按 `objectKey` 找到权威 `asset_object` 后,若资源或素材记录同时保存了 `assetObjectId`,必须验证两者指向同一对象;不一致时在 SpacetimeDB resolver 边界失败关闭,不能把未验证的记录 ID 与已验证的对象路径组合进 snapshot。缺少 ID 的历史记录继续以 canonical `(bucket, objectKey)` 作为对象身份。
- 2026-08-10 永久修订:画布视频快速编辑永久下线,上传、生成和历史视频图层均不再展示入口,程序化打开与直接提交同样失败关闭;不设置 feature flag、兼容桥或数据迁移。普通视频生成、视频“改造”、下载、`referenceVideoSrcs` 以及 V2 `videoReference` 恢复保持不变。External v1 从未正式声明“视频快速编辑”,只提供带可选 `referenceVideoSrcs` 的通用视频生成,因此不修改 `/api/external/v1` 路由、DTO、异步语义或 OpenAPI。当前服务已完全停止且无在途 / 可重试任务,不新增旧任务收口逻辑。
- 2026-08-14 改造边界澄清:`image.edit` 是图片快速编辑通过 `targetLayerId` 原位替换后的配方记录,只属于已知 action,不属于可改造 action;结果图继续显示快速编辑,不显示改造。其它可改造生成产物按原 `action` 恢复对应 generation dialog 并生成新产物,不恢复旧 quick-edit 改造面板,也不额外承诺提交成功后保持面板打开。
- 验证:模型测试覆盖允许与拒绝类型,工具栏和两类右键菜单覆盖视频、单个拆分图标、角色动作及音频不展示,打开与提交工作流覆盖视频等不支持类型绕过入口时仍拒绝;后端表驱动测试覆盖全部现役图片素材 / 媒体类型与未知类型,锁定图片编辑端点失败关闭。
- 关联:`src/components/image-editor/ImageCanvasGenerationModel.ts`、`ImageCanvasSelectedLayerToolbarView.tsx`、`ImageCanvasContextMenusView.tsx`、`useImageCanvasGenerationWorkflow.ts`、`useImageCanvasGenerationSubmissionWorkflow.ts`。
- Canvas 目的区域证明以 Oxc symbol、调用实参、计数循环和所属 Canvas 身份为权威。大 classic script 的调用图保持整轮 visited;函数内 alias 重绑定必须按作用域和写入位置解析。格子坐标无法进一步化简时只可在 `COLS × ROWS × CELL` 唯一合同下建模为完整棋盘轴区间,已知调用参数或循环边界优先,越界调用继续失败关闭。
- 动态坐标只新增一种受限可见性证明:未遮蔽的全局 `Math.min(currentCanvas.width|height - size, Math.max(0, dynamic))`。尺寸成员必须属于创建当前绘图 context 的 Canvas;其它 Canvas、被遮蔽的 `Math`、缺少上下界或普通未知动态坐标均不得作证。
- autonomous parent wake 的瞬态重试预算耗尽后必须形成 durable reconciliation。lane 忙时先写 deferred recovery signal;获得同一 execution lane 与项目写锁后,重新读取原始 Runtime state、最新 task、cancel tombstone 和 DAG 进展。只有仍指向同一非终态根 Run 时才能以 CAS 追加 reconciliation task 并原子替换 statemanifest 已损坏时也不能让普通 hydrated writer 先阻断对账证据。
- autonomous 测试夹具必须先建立带完整 parent/delegation identity 的 linked Pending child,再由正式启动路径写第一条 Running;禁止先启动无父身份再补 journal,也禁止把 terminal runId 复活成 Running。Completed-only 深验按任务逐项执行:Pending 任务不深验,但同一 manifest 中已 Completed 的美术任务仍必须验证其切片合同。
- code-prototype 只有在本人当前 Run 已有 `status=ok` 的真实 mutation action、对应 mutation revision 已通过 `game.static_smoke`,且完整 `runtime.autonomous_completion` 完成门无阻塞时,才允许快车道返回确定性交付或把结构化计划全部标为 completed。verification gate 的 mutation revision 可能因保守失效策略在失败 patch 前推进,不能单独证明文件已修改。若 static smoke 已过但完成门仍报告素材、正式产物或其它诊断,计划尚有未完成步骤时用单一 in-progress 修复步骤替换首个非终态步骤,并把其余非终态步骤保持 pending;计划已全 completed 且仍有容量时才追加修复步骤。这样既保留 completed 单调历史,也不会因 steer 合并后超过 8 步而永久卡在 `runtime.plan_update`;不得重复返回同一交付计划直至耗尽 loop budget。
- mutation ownership 以当前 run 的最后一条同工具调用和 Agent DB receipt 为联合权威;pending action 的 `plannedSteerCursor` 必须在 recent tool-call 与 receipt 中使用同一 fingerprint。结构化计划含 failed 步骤时 code-prototype 立即失败关闭;8 个 completed 步骤仍有 blocker 时不追加第 9 步,改走只允许读取、真实 mutation 与重新验证的外部 repair lane。
- 当前根 Run 有 durable active child 时,即使 manifest 快照把全部 seed task 写成 CompletedDAG 仍保持 in-progress;任一 seed task 为 Failed 时继续立即失败关闭。所有项目修改在取得项目写锁后再次核对 ready child 的确定性 runId、父绑定、durable Running 状态和当前活跃根 Run;新根 Run 建立后旧 child 不得推进 revision 或修改文件。
- 大 classic game script 的 direct function invocation graph 只建立一次;单次可达性查询使用整轮不回退的 visited 集合,每个 function node 最多访问一次,禁止只用递归栈去环后在扇入图中指数回溯。Canvas alias 的全 `None` 历史直接返回,单一稳定祖先 scope 的初始化只在该 scope 最后一次写入前没有同步调用时采用顺序快路。可见 Canvas 的整画布 `canvas.width / canvas.height` 绘制与能由唯一数值 `const` 证明落在 `COLS × ROWS × CELL` 画布范围内的格子绘制属于有效目标;普通 `player.x / player.y` 等无界动态坐标仍失败关闭。
- 根 Supervisor 的 manifest completion gaps 只对 status 已为 Completed 的 seed task执行正式产物与 Canvas 深验;pending/running/failed 本身已经构成完成阻塞,禁止提前扫描后续波次。自动唤醒 200 次瞬态重试预算耗尽后必须写入 `needs-reconciliation`,不能静默返回并留下假运行状态。
- parent wake 的 terminal reconciliation task 是 durable commit markerstate / queue / event / Agent DB audit 是可幂等重建投影;非瞬态 task journal 读取错误直接失败关闭。restart 只在 raw state 具有完整 Agent/task/Session/run/source/profile/binding/task 身份时修复其当前 run;state 缺失、损坏、空对象或关键身份为空时只取 journal 最后 logical run,完整有效的新 Run 阻止历史 marker 覆盖。event/audit 必须完整 payload 唯一匹配,同键冲突或重复失败关闭;旧 task 终态、Runtime 非 waiting 或新 Run 接管时,durable deferred signal 追加 resolved/superseded 后才返回 obsolete。
- 升级恢复兼容 v1 决策,但不沿用旧责任链:读取时严格复核 v1 fingerprint,从根完成合同的有效任务恢复 `intentSummary`,并保留旧 fingerprint 只用于核对已有 route 身份。旧 `code-director` coverage/route 对当前单主完成门表现为 migration pending;当前 `code-prototype` 必须重新 `asset.list`,再原位写入自己的 coverage/route。这样同一根 Run 可以继续,又不会把旧 Director 审计冒充成主 Agent 本人的完成证据。
## 2026-08-04 静态视觉状态流与可见证据收口
- Canvas、2D context 与图片变量统一按 semantic symbol 记录声明和整体赋值事件;重新指向离屏 Canvas、无效 context 或新图片时立即失效旧视觉身份,只有绘制点可证明的当前状态才作证。
- 同一 Program、函数体或普通 block 内,无条件 `return / throw` 之后的语句统一进入不可达区间;ESM dependency 的直接顶层 `throw` 作为初始化副作用排在 importer 前并阻断后续 importer 视觉证据,嵌套函数或恒假分支中的 throw 不扩大阻断范围。
- telemetry 只扫描可见 DOM 文本和 AST 可达的 JavaScripthidden DOM、字符串/注释、恒假分支、未调用函数和 inert/raw-text 内容不得补齐状态字段;已链接 classic/module 单元沿同一可达扫描口径判定。玩法 identity 保留独立的现有识别口径,不能反向补齐 telemetry。
- CSS `url(...)` 的资产路径保持原始大小写解析,stylesheet 证据必须同时命中实际可见元素;未命中 selector、元素自身或祖先 hidden、以及匹配隐藏规则的节点均不作证。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion.rs`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
## 2026-08-03 图片画布生成产物统一“改造”契约
- 背景:画布生成结果统一显示“改造”,但部分动画 / 音频结果无法恢复面板;另一些非生成型派生结果继承最近生成输入,产生错误可执行动作。中文标题和素材类别被同时当成显示文案、参数键和路由键,改名后容易漂移。
- 决策:用户动作 `改造` 的语义是恢复原生成输入、编辑并生成新产物。沿用 `generation_inputs_json`V2 以稳定 `action`、`fields[].id`、`references[].id/refType/refId` 作为唯一执行契约,`title` / `label` 只用于展示,字段值保留基础类型。引用只匹配当前已 hydrate 的画布图层,媒体类型取匹配图层的运行时数据,不重复写入快照,也不新增 owner-only 工程资源 / 素材库 resolver。面板直接上传引用和已移出画布的引用不恢复:可重新选择的槽位留空并提示,提交门禁继续校验必填槽位;必须依赖原 `source` 图层才能构造面板的 action 仍按 capability 保留改造按钮,source 缺失时点击后显示明确错误并拒绝改造,运行期来源变化时仍必须复检。有效 V2 不因引用缺失降级到 legacy adapter。提交前参数只归一一次,请求与持久快照共用同一归一值。
- 兼容:恢复优先级为有效 V2 → 完整历史生成对话框 → legacy adapter。legacy 允许使用 `assetKind/mediaType`、历史标题别名、资源模型 / 尺寸 / 时长 / `sourceResourceId` 和当前默认值,但必须显示恢复告警;不回填存量数据,不做 SpacetimeDB schema 迁移。
- 边界:独立裁扩、手动去背景和手动图集拆分结果不继承生成输入,不显示 `改造`;原生成任务内自动后处理产物可保留原输入。Owner 读取保留 V2 执行字段;匿名公开素材 payload 暂不返回 `generationInputs`,不沿用 owner 可执行配方 DTO。
- 影响范围:图片、规范、角色、图标、UI、宣发、视频、音效、背景音乐、角色动作和 UI 素材提取;`image.edit` 只保留为历史已知配方读取,不提供改造;不影响作品详情“作品改造”、`AI重绘` 或常规 `快速编辑`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`。
## 2026-08-05 画布图层元数据以资源行为准,读边界补齐 sourceType
- 背景:结构化画布保存要求图层布局项里的资源权威字段与 `editor_project_resource` 行逐字相等,否则整次 PATCH 报「与项目资源不一致」,而该 400 属于 non-retryable,会被前端保存队列静默吞掉。但读边界并不把这些值原样下发:`sanitize_editor_user_model` 会脱敏内部处理模型、`provider` 被无条件省略(见 2026-07-31 修正抠图内部元数据的普通用户读取边界),`sourceType` 则在结构化保存校验通过后被归还资源行、图层列置空,读回时整个键不存在。客户端拿不到权威值只能自己补——`resolveHydratedLayerModel` 沿来源链推导出展示用生图模型,`hydrateLayer` 把缺失的 `sourceType` 猜成 `uploaded`——再原样回写,判等于是必然失败。前者命中含 2026-07-30 之前抠图派生资源的画布,后者命中所有 generated 图层;两者都在项目重新加载后的首次保存触发,用户侧表现为「改动悄悄没保存」,完美像素因为提交前是严格保存才把服务端原文暴露出来。
- 决策:被读边界脱敏或不下发的字段,一律以资源行为准,客户端不参与回写。`serializeLayer` 对**挂着项目资源行**的图层(`resourcePersistenceState === 'registered'`)不再输出 `model` / `provider`;缺资源行的自包含 legacy 本地图片序列(角色动画逐帧层等)必须继续输出——服务端 `normalize_structured_canvas_layer_against_resource` 对 `resource == None` 走早退分支,只摘 `assetKind` 就把 item 原样写回,`item_json` 是这类图层元数据的唯一存储,停发会让模型信息在下一次保存后永久丢失。`normalize_structured_canvas_layer_against_resource` 对这两个字段改为直接丢弃而不判等——它们属于纯丢弃字段,判等通过与否都不写回资源行(区别于会合并回资源的 `assetKind` / `generationInputs`),放宽不影响任何持久化状态。`sourceType` 属于意外丢失而非有意脱敏,改为在读边界按图层自己声明的 `resourceId` 回填权威值,口径与既有 `objectKey` / `assetObjectId` 一致;客户端 `hydrateLayer` 同时把缺键回落到资源值作为兜底,不再猜 `uploaded`。
- 不变式:凡是 owner 读边界会脱敏或省略的图层字段,写边界不得对其判等;凡是写边界要判等的图层字段,读边界必须原样下发或可由资源行回填。改动任一侧时必须同时检查另一侧,只改一侧即构成本条缺陷的复发。
- 影响范围:`src/components/image-editor/ImageCanvasEditorModel.ts` 的 `serializeLayer` 与 `hydrateLayer`、`server-rs/crates/api-server/src/editor_project.rs` 的 `EditorPayloadMediaReference` 与 `sanitize_editor_payload_media_value`、`server-rs/crates/spacetime-module/src/editor_project_storage.rs` 的 `normalize_structured_canvas_layer_against_resource`。不修改 SpacetimeDB schema、迁移或绑定,不改动历史数据,不改变对外契约。
- 遗留:历史资源行的 `model` 列仍存有 2026-07-30 之前写入的内部处理模型,读边界继续脱敏它。把该列回填为源生图模型、原值移入 `generationInputs.mattingModel`,并据此删掉两侧的脱敏与推导逻辑,另行排期,不在本次范围。
- 验证方式:前端覆盖已登记资源的图层产物不含 `model` / `provider`、自包含本地序列仍保留并可往返,以及「序列化后去掉 sourceType → hydrate → 再序列化」仍为 `generated` 的往返不变式;api-server 覆盖读边界按 `resourceId` 回填 `sourceType`、且缺资源行的 legacy 本地序列保持自带值;spacetime-module 覆盖资源行存内部处理模型而图层带推导值时不再报错、读回时 `sourceType` 键确实被丢弃、以及显式冲突的 `sourceType` 仍失败关闭。运行 `npx vitest run src/components/image-editor`、`cargo test -p api-server --manifest-path server-rs/Cargo.toml editor_project::`、`cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml --all-targets`、`npm run typecheck`、`npm run check:encoding`、`npm run check:rustfmt`。spacetime-module 的单测二进制在 Windows 本机链接失败(缺 SpacetimeDB 宿主符号),本机只能做到 `cargo check --all-targets`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-05 完美像素占位恢复为可删除,删除保护条款作废
- 背景:2026-08-04「完美像素第二批:object-only 上传与 GET-only unknown 收口」把「未收口的完美像素 operation」定为不可删除,理由是结果 unknown 时要保留 identity 供原样重试;同日「confirm 后 strict 保存失败保留原 operation」又把 POST 尚未发出的 `failed` 一并纳入,理由是从源图重开会重新执行 ticket / PUT / confirm,让上一次已 confirm 的源图对象失去引用。两条叠加后,右键删除、Delete 快捷键、随源图层删除、多选删除全部豁免该占位,到期清理也不覆盖它,用户画布上出现了删不掉的元素。
- 缺陷判定:删除占位**不撤销任何在途请求**——完美像素没有取消接口,结果照常落库并进素材库;服务端 completion 发现 dialog 已不在会返回 `DialogMissing`,客户端本就有对应提示。封锁买到的只是「结果自动回填画布」这一便利,代价却是用户文档不可编辑。至于孤儿源图对象,「confirm 后 strict 保存失败保留原 operation」自己的「剩余边界」一节已把同类残留定性为「影响限于不可达的源图存储与垃圾记录累积……不作为本 PR 的合并阻断项」;为避免同一种残留而禁止用户删除自己画布上的元素,权衡不自洽。
- 决策:本条取代上述两条中关于**删除**的条款。完美像素占位在 `generating` / `pending-confirmation` / `failed` 任何状态都可删,且不弹确认。低层 `removeCanvasGenerationDialogById` 恢复为无条件删除——低层对上层抗命正是「占位未删却写出伪历史」的根因;随源图层删除不再豁免;Delete 快捷键与多选删除不再过滤该目标。删除确认的判据收敛为具名的 `requiresGenerationDeleteConfirmation`:现成弹窗讲的是「已消耗的泥点不会返还」,只对计费生成成立,而完美像素 `generation_cost_mud_points = 0`。运行时标记 `perfectPixelOperationInvalid` 时必须同时丢弃 `perfectPixelOperation`,与 `hydrateCanvasGenerationDialog` 口径一致,不再留下「重试按钮因 invalid 消失、快照却还挂着」的矛盾态。
- 保留不变:保留 operation identity 供「在原占位原样重试」的能力不变,重试仍复用同一 request、source objectKey 与 dialog / operation / task identity。从源图重新发起仍被 `existingOperation` 闸拦住,另行处理。带 operation 的占位继续豁免 inline 占位 TTL 到期清理——用户主动删除与系统替用户删除是两回事。
- 已知后果:删掉 `pending-confirmation` 占位后,刷新恢复不再对账这条 operation;结果若已生成只会出现在素材库,不回填画布。这是用户主动放弃的结果,不是回归,不得据此判定为缺陷。
- 影响范围:`src/components/image-editor/useCanvasGenerationDialogs.ts`(删除 `isUnsettledPerfectPixelOperationDialog`,新增 `requiresGenerationDeleteConfirmation`)、`ImageCanvasEditorView.tsx` 的 `requestRemoveCanvasGenerationDialog`、`useImageCanvasLayerCommands.ts` 的 `deleteSelectedLayer`、`useImageCanvasGenerationWorkflow.ts` 的恢复失效分支。不修改服务端、契约或数据。
- 验证方式:覆盖三种状态下按 id 删除与随源图层删除均真正移除、完美像素占位任何状态都不要求确认而普通 `generating` 占位仍要求、快捷键与混合选择删除会写入历史并触发副作用、在途删除后已知失败退回全局提示、删除后 applied verdict 走 asset-only 提示且不回填画布、标记失效时快照被丢弃。运行 `npx vitest run src/components/image-editor`、`npx vitest run src/components/platform-entry`、`npm run typecheck`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-05 完美像素请求账本移出项目布局,严格布局保存整体删除
- 背景:完美像素是唯一没有 durable job 的生成路径——免费、同步、不走 `enqueue_editor_generation_job`,服务端没有任何一行记录「这次请求发出过」。为了让刷新后还能 GET-only 对账,请求账本 `perfectPixelOperation` 被写进了**用户的画布布局**,并由此派生出一条严格布局保存通道:发 POST 前必须拿到布局保存的 revision ack,否则整条链路中止。该耦合直接造成两类缺陷:一是账本寄生在用户数据上,占位一度被禁止删除(已由同日「完美像素占位恢复为可删除」作废);二是任何布局校验失败都会升级成完美像素的硬阻断,「画布图层元数据以资源行为准」那条缺陷正是因为严格保存才从静默重试变成用户可见的死锁。
- 决策:账本改由 `src/components/image-editor/perfectPixelOperationStore.ts` 存在本机 localStorage,按 owner + project 双键隔离;布局里只留 `perfectPixelOperationId` 标记,用来把这类占位与队列型占位区分开。发 POST 前先同步写本机账本,再**尽力而为**地保存布局;布局保存失败不再让客户端拒发 POST。(**更正**:本条原文写作「布局保存失败不再拦 POST,只是把结果降级为『只进素材库』」,不准确——服务端要求占位此前已持久化,占位从未落库时 POST 会被 409 拒收。被解除的是「客户端在拿到 revision ack 前拒发」这一**客户端侧**阻断,不是端到端依赖;见 2026-08-05「pre-POST flush 是服务端硬前置」。)严格布局保存通道(`strictCompletion` 全套机制、`flushProjectPersistence` 的 `requireSuccess` / `requiredDialogId` / `deadlineAt` 选项、`PERFECT_PIXEL_STRICT_LAYOUT_SAVE_BUDGET_MS`)整体删除,只保留一个不改变失败语义的 `preferLatestGenerationDialogs`,用于取到刚创建、尚未回流到 ref 的占位。
- 为什么这样仍然安全:本机写入是同步的、不过网络、不受服务端校验影响,因此它能提供严格布局保存想提供的那个保证——「请求可被追溯」——却不引入任何阻断点。由此新出现的「账本有、占位没写进布局」窗口,由恢复 effect 覆盖:它同时遍历内存占位与孤儿账本条目,对后者照常 GET 对账,终态给出 asset-only 提示并清账本。
- **本机账本是明确设计,缺失只降级、不得构成阻断**:换设备、换浏览器、清缓存、隐私模式、配额写满都会读不到账本。这些情况下带标记的**未收口**占位一律 hydrate 成 `failed + perfectPixelOperationInvalid` 的**可删除**占位,用户删掉重来即可(收口态占位不适用,见下一条决策)。任何代码路径都不得因为「读不到账本」而拒绝用户发起、重试或删除,也不得把占位停在无从收口的处理中态。跨设备不再自动收口是本决策已知且接受的代价:完美像素免费且同步,重做成本极低,用它换掉「用户数据里混着系统对账状态」的耦合是划算的。后续复查发现该行为时,应对照本条确认为设计,不得判定为回归。
- 兼容与生命周期:布局内联账本是 legacy 形状,hydrate 侧继续认,滚动部署期间的在途操作不会被一次性判死;写入侧不再产生新的内联账本。本机账本按 7 天保留期与 32 条上限裁剪,终态(applied / dialog-missing / 快照与项目不匹配 / 无占位可挂错误)立即清除。读取沿用与布局快照相同的 v1 白名单校验,任何字段漂移失败关闭,绝不据一份可疑账本重放 POST。
- 影响范围:新增 `perfectPixelOperationStore.ts``ImageCanvasEditorTypes.ts` 新增 `perfectPixelOperationId``ImageCanvasEditorModel.ts` 的 `serializeDialogReferences` / `hydrateCanvasGenerationDialog` / `splitCanvasLayoutItems` / `dropDeadInlineGenerationPlaceholders``useImageCanvasProjectPersistence.ts` 删除严格保存机制并在 hydrate 时读账本;`useImageCanvasGenerationWorkflow.ts` 的提交、重试与恢复 effect`useImageCanvasGenerationSurface.tsx` 的 props 类型。不修改服务端、SpacetimeDB schema 或对外契约——服务端从来不认识这个字段。
- 验证方式:账本单测覆盖往返、owner / project 隔离、跨账号整条丢弃、被篡改条目失败关闭、保留期与条数裁剪、终态清除、以及存储不可用时静默降级;模型层覆盖「布局只留标记且不含源图地址」「标记在而账本缺失时收口为可删除失败态」「账本 id 与占位不符时失败关闭」;工作流覆盖「布局保存失败仍照发 POST 并保留可重试的 operation」与「孤儿账本条目照常对账并在终态清账本」;持久化层覆盖「布局保存 400 / 403 与缺 authority 时 flush 均不抛、下游照常执行」。运行 `npx vitest run src/components/image-editor`、`npm run typecheck`、`npm run lint:eslint`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-05 账本寿命短于标记寿命:收口态不需要账本,孤儿账本不看对账窗口
- 背景:上一条把请求账本移到本机后引入了一条判据——「布局里有 `perfectPixelOperationId` 标记、本机没有账本 ⇒ 该占位无效」。这条判据按构造就是错的,因为两者寿命根本不对称:标记写进布局后**寿命无限**(服务端完成 completion 时只做字段级改写,置 `status: "idle"`、`composerOpen: false`、写入 `generatedLayerId`、清 `errorMessage`,从不摘掉标记,见 `editor_project_storage.rs` 的 `plan_editor_pixel_art_canvas_layout`),而账本**寿命很短**(收口即清、75 秒对账窗口、7 天保留期、换设备即无)。账本消失是正常终态,不是异常。同一个不对称还以第二种形态出现在恢复 effect 里:孤儿账本按 `reconcileUntil` 短路。
- 缺陷一(每一次成功都被判成失败):`settleLivePerfectPixelVerdict` 在 applied 终态先清账本,紧接着 `applyProjectSnapshot` 用真实 hydrate 重新套用权威快照;此时布局里标记还在、账本已清,于是成功结果被判成 `failed + perfectPixelOperationInvalid`,用户点开刚生成的图层会看到「操作快照无效」。更糟的是这个状态会被下一次自动保存序列化回服务端,覆盖服务端正确的 `idle``perfectPixelOperationInvalid` 一旦落库,此后单凭它就能强制 `failed`,**自我固化**。历史上早已完成的完美像素占位(当时带内联账本)在标记化改写后同样中招。
- 缺陷二(兜底分支在唯一目标场景下失效):孤儿账本对账是「删掉严格布局保存仍然安全」的全部依据,目标场景是「POST 已发、布局没落盘、浏览器关闭、稍后重开」——而重开几乎必然晚于 75 秒对账窗口,`operation.reconcileUntil <= now` 的短路让这条分支基本永不生效,条目还会在本机躺满整个保留期反复被跳过。
- 决策一:凡是「缺账本 ⇒ 无效」的判据,一律先排除**收口态**。收口的定义复用既有的 `isUnresolvedCanvasGenerationDialogRecord` 取反:带非空 `generatedLayerId` 且状态不是 `generating` / `pending-confirmation`。收口态占位不需要账本——`generatedLayerId` 本身就是服务端已回填的证据;它同时**无条件忽略**已落库的 `perfectPixelOperationInvalid` 标记与残留 `errorMessage`,并把状态强制归位到 `idle`,让被上一版写脏的行在下一次 hydrate 时自愈。写边界同步收窄:`serializeDialogReferences` 对收口态占位不再输出 `perfectPixelOperationId`,让标记的寿命与账本对齐,不再在布局里堆积。
- 决策二:孤儿账本**不按 `reconcileUntil` 短路**。`reconcileUntil` 的语义是「结果可能还在飞,值得多读几次」,而孤儿是上一个会话留下的、发出它的标签页早已不在,需要的是一次能回答「到底落没落」的确定性读,不是轮询。为此新增 `readPerfectPixelOrphanVerdict`(单次 `loadEditorProject` + `inspectPerfectPixelProjectSnapshot`),不复用 `reconcilePerfectPixelProject` 的轮询循环——后者在窗口耗尽时确实会因 `hasAttemptedRead` 初值为 false 而强制读一次,但那是实现副作用而非契约,寄生在上面迟早被重构静默破坏。收口口径:`dialog-missing` → 刷新素材库 + asset-only 提示(这正是布局尽力保存失败的典型结局);`applied` → 静默刷新素材库(本次读到的就是当前项目的权威状态,结果本就在用户眼前,弹提示只是噪音);`pending` / `conflict` → 完全静默(什么都没落库,用户无需知道)。三者都清账本;**读失败不清**——那是「不知道」而非「知道没有」,留给下次加载。
- 保留不变:未收口占位在账本缺失时仍然收口成可删除的失败态,上一条决策的「缺失只降级、不得构成阻断」原样有效。本条只是把「缺失」的适用范围限定在它本来就该管的那一半。
- 同类判据的通用要求:本仓库中任何「持久化标记 + 短寿命本地状态」的组合,判据都必须先问「这个标记所指的事情是不是已经结束了」。只要标记比它依赖的状态活得久,`marker && !state ⇒ invalid` 就一定会把正常终态误判成异常。
- 影响范围:`ImageCanvasEditorModel.ts` 新增 `isSettledPerfectPixelDialogRecord` 并改写 `serializeDialogReferences` / `hydrateCanvasGenerationDialog``useImageCanvasGenerationWorkflow.ts` 新增 `readPerfectPixelOrphanVerdict` 并改写恢复 effect 的孤儿分支。不修改服务端、SpacetimeDB schema 或对外契约。
- 测试缺口的根因与补救:缺陷一能溜过整套测试,是因为工作流用例里所有 applied 场景的 `applyProjectSnapshot` 都是空桩,「收口 → 清账本 → 真实 hydrate 重新套用」这条**跨 hook 协作**从未被跑过;模型层用例又只覆盖了 `generating` + 账本缺失,没有 `idle + generatedLayerId` 这一真实终态形状。补救不是多加两条断言,而是新增一条把 `verdict.project` 真正喂进 `splitCanvasLayoutItems` 的集成用例,并把共享 fixture `createPerfectPixelProject` 补上服务端真实会保留的 `perfectPixelOperationId`——fixture 不还原真实形状,下游所有用例都在测一个不存在的世界。
- 验证方式:模型层覆盖「收口态无账本仍有效且序列化不再输出标记」「被上一版写脏的行自愈成 idle 且清掉残留错误文案」「收口态的内联 legacy 账本被剥离且不留标记」;工作流层覆盖「applied 结果经真实 hydrate 回来仍是 idle」(该用例已实证:回退修复后报 `expected 'failed' to be 'idle'`)、「过期孤儿仍做且只做一次读并清账本」、「未落库的孤儿静默清账本、不提示、不刷新素材库」。运行 `npx vitest run src/components/image-editor src/components/platform-entry`、`npm run typecheck`、`npm run lint:eslint`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-05 pre-POST flush 是服务端硬前置,对账窗口改在 flush 之后锚定
- 事实更正:`server-rs/crates/api-server/src/editor_project.rs` 的 `validate_editor_pixel_art_snap_placeholder_exists` 在处理前检查占位是否**已经持久化**到项目布局;既没有同 ID 的已持久化 dialog、也没有同 operation 的稳定 resource 时返回 **409**。因此 POST 前那次 `await flushProjectPersistence` 不是可省的画布同步,而是服务端硬前置,不能简单取消。上一条决策里「布局保存失败不再拦 POST,只是把结果降级为『只进素材库』」的说法就此更正:准确表述是——**占位从未持久化时服务端返回 409best-effort flush 不再提供成功 ACK,因此客户端无法证明该前置条件已经满足,只能提高满足它的概率**(占位可能已被此前的 450ms 自动保存落库,PATCH 也可能成功而 ACK 丢失)。被解除的是客户端侧「拿不到 revision ack 就拒发」的阻断,不是端到端依赖。
- 缺陷:首次提交与人工 exact retry 都在这次 flush **之前**就算好 `submittedAt / reconcileUntil`。该 flush 没有整体上限(单次 PATCH 60 秒 × 最多 4 次尝试,且 flush 的等待循环会清掉退避定时器立刻重跑),慢保存足以在 POST 发出前烧光整个 75 秒窗口,请求带着已过期的 reconciliation deadline 发出,对账退化成「强制读一次即以 pending 收尾」。
- 决策:窗口一律锚在 POST 发出的时刻。flush 返回且 authority 复核通过之后,调用 `createPerfectPixelReconciliationOperation` 重新设置 `submittedAt = 当前时间`、`reconcileUntil = 当前时间 + 75 秒`,按同一 `operationId` 覆盖本机账本与 dialog,并登记新的 recovery keykey 含 `reconcileUntil`),随后立即 POST。只覆盖时间字段:`request` 与 dialog / operation / task identity 逐字节不变,也不产生第二条账本。
- 为什么保留 flush 前的预写而不是整体后移:flush 期间另一标签页可能加载同一项目,此时服务端已有带 `perfectPixelOperationId` 的占位,而 localStorage 跨标签共享——本机若还没有账本,那条占位会被直接 hydrate 成 `failed + invalid`。预写的 provisional 账本正好堵住这个可长达数分钟的窗口,因此采用「预写 + flush 后重新锚定」,不采用「把首次账本写入整体挪到 flush 之后」。
- 人工 exact retry 同此口径:flush 之前继续沿用旧 operationUI 可以先切到 `generating` 让用户看到重试已开始),flush 完成、authority 复核通过后才重新锚定、覆盖账本与 dialog,然后 POST。先刷新窗口再等 flush 等于把窗口烧在等待上。
- 明确不在本次范围:flush 本身的无上限等待,以及删除 `strictCompletion` 后每次 PATCH 重起 60 秒 deadline 的连带效果。既然等待是硬前置,给它加上限只会把「慢」换成「409 失败」,不构成改善;真要治需要服务端接受「占位随请求一起提交」,属于接口契约变更。
- 影响范围:`useImageCanvasGenerationWorkflow.ts` 的 `snapSelectedLayerToPerfectPixels` 与 `retryPerfectPixelOperation`。不修改服务端、SpacetimeDB schema 或对外契约。
- 同步更新的文档:本文件上一条的错误声明已就地更正;`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md` 中「strict layout save 60 秒预算 / strict revision ACK 前 POST 为零」「未收口 operation 不可删除、不写 delete-generation-result 历史」「durable operation 不得被普通删除路径清理」等已被近几个提交推翻的条款一并修正。历史 commit message 只能靠重写 Git 历史才能改动,不为此改写历史,以本条追加说明为准。
- 验证方式:两条受控时钟用例分别覆盖首次提交与 exact retry——让 pre-POST flush 期间时钟前进 90 秒(超过整个 75 秒窗口),断言 POST 那一刻账本里是刚建立的完整 75 秒窗口、`submittedAt` 等于 POST 时刻、账本仍只有一条、`taskId` 与 `request` 逐字节未变;retry 用例另断言 flush 期间 dialog 上挂的仍是旧 `submittedAt`,证明窗口没有被提前刷新。两条用例均已实证:回退修复后报 `expected 1800000000000 to be 1800000090000`。运行 `npx vitest run src/components/image-editor src/components/platform-entry`、`npm run typecheck`、`npm run lint:eslint`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-05 专题文档补齐同步:账本位置、窗口锚点、删除权与孤儿对账
- 背景:近五个提交连续翻转了完美像素的多条前端契约,但只追加了 decision-log。`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md` 是这些条目自己声明的「关联文档」,其中仍写着已被推翻的旧契约,实现与验收依据互相矛盾——按旧文档做验收会把当前正确行为判成缺陷。
- 已更正的条款:①「请求快照写入占位并 flush 布局」→ 账本写本机 `perfectPixelOperationStore`,布局只留 `perfectPixelOperationId` 标记;②「strict layout save 60 秒预算 / strict revision ACK 前 POST 为零」→ 通道已删除,改为 best-effort flush,并写明服务端要求占位此前已持久化(否则 409)、客户端无法证明该前置只能提高概率;③「`submittedAt / reconcileUntil` 从快照写入起算」→ 从 POST 发出时刻起算,首次提交与人工重试同口径;④「POST 前必须取得布局保存成功确认,否则 POST 为零」→ 保存失败不再让 POST 为零;⑤「未收口 operation 不可删除、不写 `delete-generation-result` 伪历史」与「durable operation 不得被普通删除路径清理」→ 任何状态可删且不弹确认,确认只对计费生成成立;TTL 豁免(系统不替用户删)与用户主动删除是两回事。
- 新增到文档的不变式:标记与账本寿命必须对齐——收口态占位不再写出标记,也不得因「有标记、没账本」被判无效;账本读不到时只有**未收口**占位收口成可删除失败态。恢复必须覆盖孤儿账本,孤儿走一次确定性的读而非轮询,不按 `reconcileUntil` 短路,三种结论的提示口径与清账本规则一并写明。
- 保留为已知缺口而非静默修正:「普通按钮不得创建第二个 operation」原文是绝对断言,但该保证只由 `existingOperation` 闸提供,而它只扫描内存 dialog 列表;占位可删之后,删掉再从源图发起会产生第二个 identity。文档改为如实描述现状并标注缺口与闭合方向(让本机账本参与防重),代码侧不在本次范围。文档的职责是描述系统实际行为,写一条做不到的保证比留一个标注清楚的缺口更糟。
- 影响范围:仅文档。不改代码、不改测试。
- 验证方式:`npm run check:encoding`;核对文档中不再残留「布局保存成功确认」「strict revision ACK」「从快照写入起算」等已推翻表述。
## 2026-08-05 legacy 内联账本一次性迁入本机;Undo 复活占位记为已知限制
- 背景:账本移出布局后,`hydrateCanvasGenerationDialog` 仍然认布局里的 legacy 内联快照,但**没有任何路径把它写进本机账本**;而 `serializeDialogReferences` 会在下一次保存时把内联快照剥成 `perfectPixelOperationId` 标记。先前提交声称「滚动部署期间的在途操作不会被一次性判死」只对了一半:**第一次 hydrate 活下来,第二次就变成 `failed + invalid`**,永久失去 exact retry 的 identity。
- 决策:在 `applyProjectSnapshot` 读账本、`splitCanvasLayoutItems` 之后补一次性迁移——把带内联账本、本机却读不到、且**仍未收口**`generating` / `pending-confirmation`)的 operation 写进本机账本。三个条件都必要:只补写缺失的(本机那份可能刚在 pre-POST flush 之后被重新锚定过,比布局里的新,不能覆盖);只补写未收口的(收口态本就不需要账本,迁移只会造出立刻被裁剪的垃圾条目)。影响范围一次性且有界,仅限部署那一刻仍在途的历史操作。
- 未采纳:「删除占位后立即 flush 布局,压缩『被放弃的 operation 仍可能往画布插入图层』的竞态窗口」。核查后发现删除**已经**触发既有的 450ms 防抖自动保存(布局自动保存 effect 的依赖里就有 `canvasGenerationDialogs`),所以该改动只能在一个由服务端处理耗时(数秒)主导的竞态里省下 450 毫秒,代价却是让一个高频操作绕过防抖、增加 PATCH 量。收益与代价不成比例,不做。
- 未采纳:「用户删除未收口占位后从源图重做时弹确认框」。完美像素免费,重复的最坏后果是素材库多一份;为此在常用路径上加一次确认属于给用户制造摩擦。另外账本里能用来匹配同源的只有 `request.sourceResourceId`,纯本地图层根本匹配不到——一个覆盖不全的提醒比没有提醒更容易让人误以为安全。
- 记为已知限制而非缺陷:删除仍在处理中的占位、待原请求收口后再 `Ctrl+Z` 撤销删除,复活的占位在**当前会话内**不再被对账,会一直显示处理中;刷新即自愈,用户也可以再删一次。根因是 `observedPerfectPixelRecoveryKeysRef` 同时承担「并发保护」和「本会话已驱动过」两种语义。已推演的四种修法各有硬伤:删除瞬间剪 key 会被同一轮的 claim 检查重新标记;改成「重新出现时剪」会被 `applyProjectSnapshot` 的整批替换误触发;由删除路径显式清观察记录需要向四个删除入口铺跨 hook 通路;不把未收口 operation 放进可撤销历史则直接砍掉「误删可撤销」。为一个刷新即愈的限制付上述任一代价都不划算,留到重构该记账时一并解决。
- 影响范围:`useImageCanvasProjectPersistence.ts` 的 `applyProjectSnapshot`。不修改服务端、SpacetimeDB schema 或对外契约。
- 验证方式:新增「legacy 内联账本在加载后被迁入本机,且剥离内联快照后仍能凭本机账本往返回有效的 `pending-confirmation` 占位」用例,已实证:回退迁移后报 `expected +0 to be 1`。运行 `npx vitest run src/components/image-editor src/components/platform-entry`、`npm run typecheck`、`npm run lint:eslint`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`(已同步 legacy 迁移要求与 Undo 已知限制)。
## 2026-08-05 孤儿账本只在正向终态清除;legacy 迁移判据对齐 exact retry
- 缺陷一(孤儿过早清账):上一条把孤儿改成「读到任何结论就清账本」,但 `pending` / `conflict` 不是结论。浏览器关掉不会中止服务端处理——api-server 的处理与持久化预算合计可达 90 秒,远端 procedure 也可能还在跑;重开项目时单次 GET 没看见 resource 只说明「还不知道」。此刻清账,服务端稍后落库便再无对账凭据,用户永远等不到「结果已进素材库」的提示,同时又多开一个 identity 的口子。
- 决策一:只有 `applied` / `dialog-missing` 这两个**正向终态**才清账本;`pending` / `conflict` 与读失败一律保留,留给下次加载重读。残留由 7 天保留期与 32 条上限兜住,代价是极少数永不落库的条目每个会话多一次 GET——比丢失凭据便宜得多。
- 缺陷二(legacy 迁移漏 `failed`):迁移判据按状态白名单列举了 `generating` / `pending-confirmation`,漏掉 `failed + perfectPixelOperation`。那是旧严格保存失败的合法持久化形状(请求已备好、POST 从未发出),`retryPerfectPixelOperation` 明确接受该状态,面板上的「重试同一完美像素操作」也正是在这个形状下出现。漏迁的后果与缺陷本体一致:下一次保存剥成 marker 后再加载即 `failed + invalid`,重试按钮消失,用户只剩删掉重做——而那正是新 identity,正是 exact retry 存在的意义所在。
- 决策二:迁移判据改用 `isUnresolvedCanvasGenerationDialogRecord` 取反,与 `hydrateCanvasGenerationDialog` 判定收口态用的是同一个函数,两处不会漂移。**通用要求**:涉及「这条 operation 还需不需要账本」的判断一律问「它收口了没有」,不要列举状态——状态白名单会随着新增状态或语义变化而静默漏项,本条就是实例。
- exact retry 的价值必须记清楚,它不是「省一次操作」:原样重放同一 identity 时服务端幂等生效(稳定 task / object / resource / asset ID 由 `owner + project + dialogId` 派生,请求带 fingerprint),同内容重放返回 `AlreadyApplied` 而不是再造一份。删掉它意味着对账查不出结论时用户只能新建 identity,旧的若其实成功就会重复。曾评估过「直接删除该功能以减少用户困扰」,权衡后保留——困扰来自文案与状态不清晰,可以单独治理,而幂等保证一旦删掉无法用文案补回。
- 同步更正的文档:专题文档三处残留矛盾——`submittedAt / reconcileUntil` 仍称「从稳定请求快照写入时建立」(应为 pre-POST flush 之后、POST 之前)、仍称「不会让布局校验失败升级成硬阻断」(应限定为解除了客户端侧拒发,端到端 409 依赖仍在)、以及「删除后不再对账」(应限定为结果不再自动回填画布,对账本身继续进行)。
- 影响范围:`useImageCanvasGenerationWorkflow.ts` 的恢复 effect 孤儿分支、`useImageCanvasProjectPersistence.ts` 的 legacy 迁移判据。不修改服务端、SpacetimeDB schema 或对外契约。
- 验证方式:孤儿用例翻转为「单次读仍无法判定时保留账本、不提示、不刷新素材库」;新增「legacy `failed + operation` 被迁入本机」用例。两条均已实证:回退修复后各报 `expected +0 to be 1`。运行 `npx vitest run src/components/image-editor src/components/platform-entry`、`npm run typecheck`、`npm run lint:eslint`、`npm run check:encoding`。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-05 完美像素的优先级判据:低成本操作不为「丢资源」加限制
- 背景:完美像素链路上反复出现同一种压力——为了防止「已经生成的结果丢失关联」,不断有人提议加限制:禁止删除未收口占位、禁止从源图重新发起、让本机账本参与防重。历史上前两条真的加过(2026-08-04 两条决策),造成用户画布上出现删不掉的元素,后来被逐条作废;最近一轮评审又把「删除后可创建第二个 identity」报成必须闭合的 P2 缺口,闭合方向正是第三条。这种压力不会自己停,必须写成判据。
- 事实前提:该操作免费(`generation_cost_mud_points = 0`)、同步、纯几何规整、不进外部生成队列,重做一次的代价接近于零。它与计费生成的风险结构根本不同,不能套用同一套「必须防止重复提交」的直觉。
- 决策(优先级判据):凡是「已生成的资源在意外情况下丢失关联、需要用户重做或自行去素材库取回」这一类问题,一律**不构成阻断项**。只有主链路本身出问题才阻断——发起被拒、处理失败、结果没落库、已落库的结果既不回填画布也不进素材库。评审与复查把前一类现象报成必须修复的缺陷之前,先确认它落在主链路上。
- 决策(禁止的补救方向):不得为了防止上述丢失而新增任何限制用户操作的特性,具体包括但不限于「禁止用户删除占位」「禁止用户重试」「禁止同一张图被处理两遍」。用户对自己画布上的元素始终保有删除与重做的权利;重复处理的最坏后果只是素材库多一份、用户可自行删除,这个代价远小于剥夺用户操作权。已作废的同类封锁不得以任何理由重新引入。
- 连带处置:先前记为「已知缺口、闭合方向是让本机账本参与防重」的那条,改记为**明确接受的行为**——该闭合方向正是本条禁止的内容。既有的 `existingOperation` 闸是本条确立之前的遗留,方向与本条相反,后续应放宽而非加固。
- 与 exact retry 的关系:本条不否定 exact retry。它是**用户自愿选择**的幂等路径(原样重放同一 identity,服务端同内容重放返回 `AlreadyApplied`),属于给用户多一个选项,不是限制;被禁止的是把它变成用户唯一能走的路。
- 影响范围:仅文档与后续评审口径。不改代码、不改测试。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`(已写入同名判据与禁止清单)。
## 2026-08-05 完美像素跨会话续命层记为「迁移到 durable job 时整层清除」
- 背景:本分支相对 master 新增约 16758 行,其中测试占 45%Rust 内联 `#[cfg(test)]` 按行号切开重算:`editor_project.rs` 新增 2882 行里 1221 行是测试,`editor_project_storage.rs` 是 24%)。功能本体极小——像素规整算法在 `pixel_art_snapper.rs` 只改了 100 行。体量几乎全部来自「这条链路没有 durable job」这一个架构选择:免费 + 同步 + 不进生成队列,服务端不留任何「这次请求发出过」的记录,于是「结果是否落库」这个在其它生成路径由 job 行免费回答的问题,必须由客户端自造一整套机制回答。
- 三层划分(本条的核心结论):**A 层**服务端正确性(单事务原子落库、preflight、归属校验、稳定 object key、预算边界,约 2800 行)与是否有 job 无关,任何形态都保留;**C 层**POST 后一次 GET 对账(applied / dialog-missing / unknown 三档,约 180 行)保护的是「结果未知却谎报失败」,属于主链路,保留;**B 层**跨会话续命(本机账本、刷新恢复与孤儿对账、75 秒窗口与锚点、marker 与账本的寿命对齐、exact retry、inline 占位到期与归属登记,约 1100 行生产 + 2500 行测试)只因为没有 job 而存在。
- 决策:**现在不删 B 层**——它刚写完、刚测过、刚修完六个缺陷,删除本身是有风险的改动,收益兑现在未来的维护成本上。但**迁移到 `enqueue_editor_generation_job` 时必须整层清除,不得与队列并存**:两套收口机制并行会产生「谁是终态权威」的二义性,比任何一套单独存在都糟。该要求已写入专题文档,作为阶段 4 的验收条件之一。
- 支撑该结论的实测(免得后来者重新推导):B 层只服务完美像素——`requiresLiveSession: true` 全仓仅一处置位,`claimActiveInlineGenerationDialog` / `releaseActiveInlineGenerationDialog` / `hasActiveInlineGenerationDialog` 的全部五个调用点都在完美像素的提交、重试与恢复路径上。因此整层删除的边界清晰、不会波及其它生成链路。
- 缺陷密度佐证:2026-08-05 那轮对抗性复查的七条发现里,F1–F6 六条**全部**落在 B 层(F7 是文档同步)。B 层的核心不变式「持久化标记的寿命必须与短寿命本地状态对齐」反直觉且容易写错,是这条链路缺陷最密集的地方。
- 仓库内既有的廉价答案:手动图集拆分同样免费、同步、无 durable job,客户端只有约 85 行——失败即 `window.alert` 报错,`taskId` 用 `build_prefixed_uuid_id("editor-atlas-split-")` 随机生成,结构上不可能幂等,也没有任何对账。它符合上文的优先级判据,**完美像素的 B 层才是特例**,不得据它给其它链路加同样的机制。
- 顺带记录的待办(不在本次范围):图集拆分的 catch 只做 `window.alert('拆分图集失败')`,不区分「确定失败」与「网关合成的未知结果」。服务端可能已经切完并落库 N 个 asset 却报失败,用户照提示重拆就会拿到双份——这属于谎报,落在主链路一侧,与「丢资源不阻断」不是一类问题。最小修法是复用现成的 `isGatewayUnknownOutcomeError` 改文案,十几行,不需要照搬 B 层。
- 影响范围:仅文档。不改代码、不改测试。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`(已写入三层划分、B 层边界实测与清除条件)。
## 2026-08-05 删除确认判据改看 marker,覆盖源准备阶段的免费占位
- 缺陷:`requiresGenerationDeleteConfirmation` 的判据是 `status === 'generating' && !dialog.perfectPixelOperation`,而 `perfectPixelOperation` 要到源图解析 / 直传完成后才写入。未登记的本地图片要走 `ticket → PUT → confirm`,预算上限 90 秒;这段窗口里占位是 `generating`、只有 `requiresLiveSession: true`、没有账本,于是判据返回 true——用户删一个**免费**操作会被告知「已消耗的泥点不会返还」。与同日「完美像素占位恢复为可删除」里「任何状态直接删、不弹确认」的决策直接矛盾。已登记资源走短路解析、窗口接近于零,暴露只在未登记本地图片上成立。
- 决策:`perfectPixelOperationId` marker 从占位**创建那一刻**就写上(`operationId === dialogId` 在创建时已知),判据改看 marker`status === 'generating' && !dialog.perfectPixelOperationId`。marker 的语义也因此更准确——它表示「这个占位属于一次完美像素操作」,而不是「账本已存在」。
- 未采纳「删掉弹窗入口」:删除入口全仓只有 `requestRemoveCanvasGenerationDialog` 一条,右键、Delete 快捷键、工具栏全部汇入,完美像素没有自己的删除路径可以摘除。「从本链路删除入口」在实现上等价于「让判据认得出本链路」,绕不开识别问题。真正删掉弹窗只能对所有生成占位一起做,那是另一个产品决定(对计费生成而言该文案是真实信息),不作为修此缺陷的副产品。
- 未采纳「新增 `generationCostMudPoints` 字段让判据直接问是否计费」:语义上最正,但今天唯一的生产者只有完美像素,图集拆分根本不创建占位,第二个消费者并不存在,属于为一个调用方过度设计。
- 未采纳「判据加 `requiresLiveSession === true`」:该字段全仓确实只有完美像素一处置位,一行即可修,但它的含义是「只能由本会话收口」而非「免费」。换一个代理不解决问题——**用短寿命字段的存在性判断长期属性**正是本缺陷(以及 F5)的成因模式。
- 已核过的连带影响:会话内到期清理不受影响,`inlineGenerationPlaceholderExpiryAt` 看的是 `perfectPixelOperation` 而非 marker,源准备阶段被放弃的占位照常到期消失。受影响的只有加载时的快照清理 `dropDeadInlineGenerationPlaceholders`——它的豁免判据接受 marker,因此「源准备中途关标签页」的占位不再被静默清掉,而是在下次加载显示为可删的失败卡。这与「TTL 豁免 = 系统不替用户删,用户主动删除始终允许」一致,判为改善而非退化。id 撞车时 `openCanvasGenerationDialog` 会另生成 idmarker 会暂时指向旧值;它此刻只被当作存在性标记使用(效果仍是不弹确认),写 request 时按真实 dialogId 纠正,不影响任何判等。
- 测试缺口的根因:原用例的夹具 `durablePerfectPixelDialog` 带着 `perfectPixelOperation`,编码了与判据相同的错误假设,结构上不可能覆盖 operation 形成之前的窗口。夹具已补上 marker 以还原真实形状,并新增「只有 marker、尚无账本」的用例,已实证:回退判据后报 `expected true to be false`。
- 影响范围:`useCanvasGenerationDialogs.ts` 的判据、`useImageCanvasGenerationWorkflow.ts` 的占位创建。不修改服务端、SpacetimeDB schema 或对外契约。
- 关联文档:`docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`。
## 2026-08-05 布局 flush 不再等待封面链
- 缺陷:`flushProjectPersistence` 显式关掉 `queueProjectLayoutSave` 内建的 fire-and-forget 封面分支(传 `persistCover: false`),自己另起一份并在函数最后 `await coverSave`。于是每一个 `await flushProjectPersistence(...)` 的调用方都被挂在封面链后面。封面渲染要为**每个可绘制图层**取 signed URL、再用 `new Image()` 加载——那个 Image 只有 `onload` / `onerror`**没有 timeout、没有 AbortSignal**,外层的 `try { } catch { }` 只接得住 reject、接不住「永不 settle」。一张图不 settle,生成 POST 就永远发不出去。
- 归因:这是 2026-08-05「完美像素请求账本移出项目布局」把严格通道与普通 flush 合并成一条路径时引入的**回归**。改动前 `if (requireSuccess) { …; await strictCompletion.promise; return; }` 在封面链启动之前就返回,封面与 pre-POST 路径是结构性隔离的。图集拆分(`void flushProjectPersistence().then(() => splitSelectedIconSpritesheet(layer))`)一直走非严格路径,因此它的暴露是既有的、不是本次引入;但两者同源,一并解开。
- 影响面分级:**必然发生**的是延迟——封面签名含未量化的 `viewport.x/y/scale`,而完美像素创建占位时 `openPlacedCanvasGenerationDialog` 会 `setViewport(centerViewportOnPlacement(...))`,所以几乎每次调用都会触发全量重渲染(逐图层取 signed URL + 加载 + 渲染 + 上传 OSS + 登记资源),这些与服务端那个 409 前置毫无关系。**可能发生**的是永久挂死,此时 `snapSelectedLayerToPerfectPixels` 的 `finally` 永不执行,图层锁与 inline 占位归属登记被永久持有;用户即使删掉占位,闸的另一半 `perfectPixelLayerIdsRef.current.has(sourceLayer.id)` 仍为真且**静默 return**,本会话内再点完美像素不会有任何反应。图集拆分没有这层脏状态——它的锁在 `splitSelectedIconSpritesheet` 函数体内才取,flush 挂住时根本没被调用,表现只是「点击无反馈」。
- 决策(删除式修复):删掉 flush 里的 `persistCover: false` 覆盖、独立的 `const coverSave = persistProjectCoverSnapshot(...)` 与末尾的 `await coverSave`,让封面回到 `queueProjectLayoutSave` 内建的 fire-and-forget 分支。参数逐项等价(`layoutInput.viewport` 即 `coverDisplayViewport`、同一个 `refs.layersRef.current`、flush 不传 `delayMs` 故走立即分支),**封面照存,只是不再有人等它**。未采纳「给 flush 加一个跳过封面的选项」:那会把同一个结构性问题留在图集拆分身上,并且多一个需要每个调用方正确设置的开关。
- 代价:`returnToProjects` 不再等封面就跳转。该保护本就很薄——跳转是 SPA 路由切换而非页面卸载,fire-and-forget 的 promise 在同一 JS 上下文里会跑完;真正会打断它的是浏览器关闭/硬刷新,而 `await` 在 `beforeunload` 里同样救不了。实际损失只是「点返回后立刻关标签页」这个窄窗口里封面可能没传完,而封面是缩略图、下次任意保存会重新生成。
- 遗留(建议单开,不在本次范围):`loadProjectCoverImage` 里无 timeout / 无 AbortSignal 的 `new Image()` 本身仍是隐患,自动保存路径一样会踩。本次只是把它移出生成链的关键路径,没有消除它。
- 影响范围:`useImageCanvasProjectPersistence.ts` 的 `flushProjectPersistence`。不改服务端、不改契约。
- 验证方式:既有用例「flush 等待封面缓存」翻转为「flush 不等封面、但封面链照常跑完并完成上传与资源登记」;新增「封面永不 settle 时 flush 仍返回」——用永不 resolve 的 blob 模拟 `new Image()` 不 settle,并断言 `createProjectCoverSnapshotBlob` 确实被调用过以防用例空过。已实证:回退修复后新用例报 `expected 'false' to be 'true'`。运行 `npx vitest run src/components/image-editor src/components/platform-entry src/services`101 文件 / 1241 项)、`npm run typecheck`、`npm run lint:eslint`、`npm run check:encoding`。
## 2026-08-05 编辑器生成请求与参考图权威契约
- 主站编辑器生成 POST 不做浏览器自动重试,避免 inline 模式在响应丢失后重复调用 providerapi-server 仍使用独立 namespace + owner + job kind + request id 生成队列 `dedupe_key`,让显式复用同一请求标识的队列重放原子返回已存在任务,并对同键不同 payload 返回 `409`。外部 v1 的 `Idempotency-Key` 保持独立 namespace。
- 参考图数量以产品上限与 provider 容量的较小值为准,前端添加 / 上传 / 提交、api-server 入队与执行、`platform-image` provider 边界均明确拒绝超限;任何层都不再用 `.take(...)` 把第 N+1 张静默丢弃。角色 / UI 从一开始预留主图槽位;并发上传计入在途数量并在持久化前复验。任一参考图上传批次在途时锁定模型切换、画布选图、提交生成、关联源图删除 / 剪切 / 素材删除及生成面板切换 / 关闭,并以原面板上下文标识在持久化前后复验,完成或失败并释放 reservation 后才允许继续操作;批量部分失败时仍挂接成功项并刷新素材库。模型降容或后补主图会超限时拒绝操作并保留现有引用。
- 图片类最终 `generationInputs.references` 不信任客户端输入;队列 payload、完美像素及直接创建资源 / 素材入口删除客户端 referencesworker / inline 路径按本次真实参考源与 owner 范围内的项目资源、账号素材重建 `refType/refId`。只有 owned objectKey 但没有正式行时不生成伪 provenance。完美像素继续使用升级前 canonical 客户端输入计算 operation fingerprint;新操作只持久化权威重建值,历史同 task/resource 重放复用服务端既存 metadata 通过精确比较。
- 升级前 External 幂等任务可能仍在 payload 中保留客户端 references;重放比较只对白名单内已迁移的图片生成、图片修改、去背景、图标图集和 UI 提取任务,在旧侧有 references、当前侧已删除时移除旧字段,其他字段变化仍返回 `409`。音频 / 视频 / 角色动作等未迁移 job kind 始终完整比较,不能扩大兼容面。
- 本次复用既有 `external_generation_job.dedupe_key` 唯一索引和 `spacetime-client` 查询,不改 SpacetimeDB schema、迁移或 bindings。
## 2026-08-05 图标规范生成拆分请求预检与最终执行,图集规范引用改为窄事务查询
- 图标规范 HTTP handler 在调用文本 LLM 前,先通过可复用图片请求预检完成参考图稳定性、owner 授权、Provider 配置与运行时定价校验;随后补齐 `ExtraParam` 与最终 prompt,并继续走既有 `editor_image_generation` inline / queue 分流,不新增图标规范专用 worker job。最终执行仍重新校验请求,以处理排队期间发生的权限或资源变化。
- 图标图集主规范引用由通用 SpacetimeDB procedure `resolve_editor_reference_and_return` 在同一事务快照内解析和校验 owner。ID 使用资源 / 素材主键;objectKey 按规范化 `image_src="/<objectKey>"` 索引读取唯一行,并使用 `asset_object(bucket, object_key)` 复合索引校验对象 owner。procedure 不接收业务 / 存储类型 allowlist,复用既有 `EditorProjectResourceSnapshot` / `EditorAssetSnapshot` 返回完整单行,不新增图标专属 DTO,也不再拉取完整项目和素材库;`icon-spec` 业务类型与游戏类型由 API 业务代码校验和提取。缺失引用、跨 owner、asset object 不匹配、业务类型错误、元数据解析与数据库错误全部失败关闭;仅“合法记录没有 genre”允许返回 `None`。
- 关联文档:`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`、`docs/【编辑器】画板图标素材生成入口设计-2026-06-15.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
## 2026-08-06 图标规范与图标图集使用独立任务及 ID-only 主规范引用
- 本条取代上一条“不新增图标规范专用 worker job”和“图标图集主规范允许 objectKey”的结论。图标规范生成使用独立 `editor_icon_spec_generation` job kind;队列保存原始强类型业务参数,worker 在统一计费操作预扣成功后执行 ExtraParam LLM、构建最终 prompt,再进入共享图片生成和既有后处理 / 持久化链。SpacetimeDB 编辑器生成结果 operation kind 白名单必须显式包含该独立 job kind,确保 Provider 成功后可以进入统一 durable receipt 原子提交;新增正式生成 job kind 时必须同步扩展白名单和模块回归。余额不足不调用文本 LLM,执行失败沿用统一退款;普通 `editor_image_generation` 的 DTO、payload 与执行行为保持不变。
- `editor_project_icon.rs` 独立承接图标规范、图标 spritesheet、自动切片和手工拆分。普通图片模块只暴露可复用的 prompt builder 执行入口与强类型 provider 请求分流;nanobanana、无参考图 generation、有参考图 edit 的选择集中在同一个 helper,图标图集复用该 helper,不复制 provider 分支。
- 图标规范生成的可选参考图与图标图集的必选主规范统一使用 `referenceId`,只接受当前 owner 的项目资源 ID 或账号素材 ID,不接受 objectKey、URL 或临时 key。SpacetimeDB `resolve_editor_reference_and_return` 因而只接收 `reference_id` 并按两张表主键查询,删除 `image_src` 二级索引。图标图集的普通附加参考图 `referenceImageSrcs` 仍允许 owned objectKey、项目资源 ID 或素材 ID,并继续走既有 owner 校验与真实图片下载链。
- External v1 图标图集请求与 OpenAPI 同步改为必填 `referenceId`。前端和 Editor Agent 优先传正式 resource ID,其次传 asset ID;本地临时 ID、objectKey 和图片地址不得回退成主规范引用,未登记时明确失败并要求先上传或登记。
- `resolve_editor_reference_and_return` 的歧义只在当前 owner 范围内判断:两张表先按 `owner_user_id` 过滤,同名但属于其它账号的行不阻断合法引用。记录存在 `asset_object_id` 时按该 ID 定位并同时核对 bucket、object key 与 owner,只有缺少 ID 的兼容旧行才按位置查询。图标规范入队 / inline 预检只验证这组行与对象元数据;inline 与 worker 共用的最终执行入口还必须在把 `referenceId` 转入通用图片请求前再次执行同一 ID / owner 校验。最终共享图片执行器再下载一次参考图正文,避免同一引用在入队、worker 预检和生成阶段重复下载。
- 关联:`server-rs/crates/api-server/src/editor_project_icon.rs`、`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/spacetime-module/src/editor_project_storage.rs`、`docs/openapi/genarrative-external-v1.openapi.json`。
## 2026-08-06 音效与背景音乐恢复共享音频 Composer
- 决策:图片画布的 `audio-sound-effect` 与 `audio-background-music` 只保留一个 `ImageCanvasAudioGenerationComposerView`,组件内以 `isSoundEffect = dialog.mode === 'audio-sound-effect'` 分流。撤销的是完整 `ImageCanvasBackgroundMusicGenerationComposerView` 这一层视图拆分,不撤销 BGM Prompt 纯模型、助手 controller、预设模型或预设跑马灯的独立职责。
- 业务隔离:共享组件不等于共享规则。SFX 继续使用 Vidu `audio1.0`、210 秒、默认 5 秒、现有 Prompt 回退、1500 字限制、价格和提交链路;BGM 继续使用 canonical Prompt、200 字生成限制、30 个预设、AI 补全 / 简化、单层撤销、提交锁和 Suno。BGM 按 dialog ID 写回,SFX 继续走现有 `setGenerateDialog`,两条路径不得互换。
- 非目标:本次只规划视图归并,不实现 SFX V2 的 ElevenLabs、中译英、自动时长、30 秒、Loop、一键优化或预设,不修改任何后端、External v1、Schema、计费或需求原文,也不新建配置驱动的 composer 框架。
- 实施状态:已恢复共享音频 composer,独立完整 BGM composer 及其测试文件已删除,原覆盖完整迁入总 composer。Prompt / 预设 / controller / 总 composer `121/121`、surface 与 submission workflow `72/72` 通过,typecheck、变更文件 ESLint、Prettier、编码检查和差异检查通过;没有修改后端、契约或需求原文,也没有实现 SFX V2 独有功能。
## 2026-08-06 SFX 生成优化 V2.0 T0 设计与迁移口径
- 权威入口:SFX V2 的可编码规则已完整融合到 `docs/【编辑器】画板音乐生成入口设计-2026-06-18.md`。实现、审查、测试和发布只以该 tracked 权威设计、本条决策和共享实施计划为依据,不依赖团队通过 Git 无法取得的本地资料。
- 共享视图边界不变:`audio-sound-effect` 与 `audio-background-music` 继续共用 `ImageCanvasAudioGenerationComposerView`,通过 `isSoundEffect` 分流;SFX 和 BGM 的 Prompt 模型、controller、预设 wrapper、锁和提交契约分别维护,不新建独立页面或第二套音频系统。
- 后端入口边界:正式生成原地演进 `server-rs/crates/shared-contracts/src/assets.rs` 的现有音频 DTO 与 `server-rs/crates/api-server/src/vector_engine_audio_generation/generation.rs` 的现有 handler,保留既有 `/api/editor/audios/*/generations` 路由、queue / inline 分流、计费和队列边界;不得新建平行 DTO、正式生成 BFF、handler 或第二套路由。只有 SFX Prompt 优化内部路由、Worker 翻译 service 和 ElevenLabs adapter 是新增能力。
- provider 迁移:新编辑器 SFX 任务固定使用 ElevenLabs `eleven_text_to_sound_v2`,不提供模型选择、Vidu fallback 或 `audio1.0` alias。Vidu builder / 轮询仅保留给历史素材和其它未迁移调用方;历史素材可读,重绘新任务使用 ElevenLabs。
- Prompt 真相:`prompt` 表示用户可见且确认的 canonical `userPrompt``actual_prompt` 表示 Worker 严格验收后实际提交给 ElevenLabs 的英文 `actualPrompt`。两者均以 2048 Unicode code points 为上限,只删除首尾 Unicode `White_Space`,不做其它规范化、默认 Prompt 回退或静默截断。
- 可见交互:SFX 固定 40 个事件预设 + 12 个补充要求。空 Prompt 直接写入;非空 Prompt 末尾已是 Unicode 标点时直接追加,否则使用中文逗号 `,` 分隔。允许重复,不保留选中态,不去重或截断;点击预设清除旧快照。
- Prompt 助手:一键优化使用 `gpt-5.6-luna`、`reasoning_effort = medium`Worker 正式英文化使用同模型、`reasoning_effort = low`。两类候选上限均为 2048 Unicode code points,因此分别固定 completion tokens 总预算 `2048 × 4 = 8192`;该预算由隐藏 reasoning tokens 与可见输出 tokens 共享,不包含输入 Prompt tokens,也不是可见正文保证。当前 VectorEngine OpenAI Chat wire 固定发送 `max_completion_tokens = 8192`,内部历史字段名 `max_output_tokens` 不是业务语义。预算不按实际输入长度动态缩小;两者均不发送 temperature 或 function tools,只接受完整 `response.text` 中唯一 JSON object 的严格 envelope。翻译除要求 `isEnglish = true` 外,程序侧还要求候选至少含一个 Latin alphabetic code point,且所有 alphabetic code point 都属于 Latin Script;日文假名、韩文、西里尔、希腊和阿拉伯等非 Latin 字母均失败。优化只有一个业务语义轮,`finish_reason = length` 直接失败;翻译首轮成功响应但候选不合格或 `length` 时使用同一 `userPrompt`、同一 `8192` 上限唯一重试,第二轮 `length` 最终失败,`content_filter` 和 transport 最终失败不开启第二业务语义轮。翻译最终失败时 ElevenLabs 请求数必须为 0。
- 撤销与锁:一键优化成功产生一层 canonical Prompt 交换快照;优化失败清除本次临时快照,不恢复更早快照。AI 操作和正式提交使用 dialog ID、账号 + 项目 scope、同步 operation ID 与 `AbortController`;只锁当前 SFX dialog。正式提交在第一个 `await` 前冻结 Prompt / duration / LoopAPI 接受后结束 `submitting`、进入现有 `queued/generating` 占位,不把接受任务写成生成已完成。
- 时长与 Loop:首次打开默认自动时长模式,预置最近手动值 `5s`、Loop false;手动范围 `0.5-30s`UI 步进 `0.1s`。自动模式发送 `duration_seconds = null`,禁用 slider 但保留最近手动值。Loop 是独立 API 布尔参数;系统不根据 Prompt 推断、同步或校验 LoopPrompt 文本与 Loop 开关不建立业务一致性门禁。
- ElevenLabs 契约:`POST /v1/sound-generation`body 固定 `text / model_id / duration_seconds / loop / prompt_influence=0.3`query 固定 `output_format=mp3_44100_128``xi-api-key` 只在服务端 header 注入。provider POST 不自动重试,浏览器正式 POST 不 unsafe retry,队列 `max_attempts = 1`,一个平台 job 最多一次 ElevenLabs POST。成功响应复用现有 `MAX_GENERATED_AUDIO_BYTES = 40 MiB` 做 Content-Length 预检和 `limit + 1` 流式读取,验证 MIME 与真实 MP3,并探测有限正实际时长;实际时长仅受独立技术异常上限 `600s` 约束,不与请求最大 `30s` 比较,`30.5s-600s` 的合法结果可接受。平台使用 operation / queue job ID 作为 `taskId`,不伪造 provider task ID。
- 结果真相:服务端重建 SFX V2 `generation_inputs_json`,写入 `userPrompt / actualPrompt / model / durationMode / requestedDurationSeconds / actualDurationSeconds / loop`;实际英文 Prompt、实际时长、model 和 Loop 不信任客户端自报。SFX V2 完成响应的 `durationSeconds` 使用同一 MP3 探测值;本条作为后出的 SFX 专项决策,仅在该完成响应上覆盖 2026-07-28“音频生成响应不得新增 `durationSeconds`”的通用口径,不新增资源 / 素材正式时长列,也不把该值写入 `EditorAsset`、`CanvasLayer`、layout 或图片序列字段。信息弹窗展示用户 Prompt、实际英文 Prompt、模型、实际时长、Loop 和平台 Task ID;历史 Vidu 数据不误标英文 Prompt。
- 计费、External v1 与 schema:新模型键 `eleven_text_to_sound_v2` 保持 5 泥点 / 次,后端入队时冻结价格为真相。External v1 的 duration 演进为可选 / nullable `0.5-30 number`Loop 缺省 false`model` 的 omitted / null / 空串 / 纯空白 / 首尾空白包围的新模型 / 显式新模型统一 canonicalize 为 `eleven_text_to_sound_v2`,并产生相同幂等 payload。显式旧 `audio1.0` 和未知非空值返回 `400 BAD_REQUEST`,且必须为零入队、零预扣、零 LLM、零 providerRust、OpenAPI、幂等重放与最终响应必须在 T5 同批变更。本次不修改 SpacetimeDB schema,复用现有 `prompt`、`actual_prompt`、`generation_inputs_json` 和画布 layout。
- 共享计划:脱敏 T1–T6 任务、当前基线、测试矩阵、旧 Vidu 队列 drain、发布与回滚门禁记录在 `docs/project-memory/plans/【实施计划】SFX生成优化V2.0任务拆解-2026-08-06.md`。T0 只验收设计、决策、共享计划、差距归属和安全记录,不要求当前 Vidu V1 代码、OpenAPI 或实际 API 已与 SFX V2 设计一致;实现差距归入 T1–T5,T6 统一验收。
- 安全与状态:机器本地未跟踪资料不得进入提交,也不得被仓库文档链接、引用或作为团队证据源;ElevenLabs Key 只允许从服务端私密环境配置读取,不进入浏览器、日志、fixture、共享文档或 Git。T0 已通过,可以进入 T1–T5 实现;后续仍不得把秘密值、个人本地资料或不可审计记录写入仓库。
## 2026-08-06 SFX 生成优化 V2.0 T2 助手与 Worker 翻译 service
- 一键优化:新增登录态内部路由 `POST /api/editor/audios/sound-effects/prompts/optimizations`,复用 T1 的最小请求 / 响应 DTO、现有编辑器 `LlmClient`、标准成功 / 错误 envelope 和 route tracking。请求固定 Luna、OpenAI Chat、Medium、completion tokens 总预算 `8192`,当前 VectorEngine Chat wire 只发送 `max_completion_tokens=8192`,不发送 temperature 或 function tools;路由独立使用 `32 KiB` body limit,不增加功能级限流器,也不进入 External v1。
- 优化验收:内部五字段 envelope 必须从完整 `response.text` 直接反序列化,允许外围 JSON whitespace,拒绝代码块、前后解释、多个 JSON、额外 / 重复字段、错误类型、tool call 和未完成 finish reason。候选只删除首尾 Unicode `White_Space`,要求 `1-2048` code points、至少一个 Han code point,并严格执行 `true / true / false / false`;失败 HTTP 响应不携带候选、内部 envelope 或上游回显正文。
- 翻译 service:在现有 `vector_engine_audio_generation` 内新增仅 crate 内部生成流水线可见、没有同步 HTTP 路由的翻译 service。每个业务语义轮固定 Luna、OpenAI Chat、Low、completion tokens 总预算 `8192`,当前 VectorEngine Chat wire 只发送 `max_completion_tokens=8192`,不发送 temperature 或 tools;候选严格执行五字段结构、`true / true / true / false`、`1-2048` code points、至少一个 Latin alphabetic code point,且所有 alphabetic code point 都属于 Latin Script。Han、假名、韩文、西里尔、希腊和阿拉伯字母均失败,Common / Inherited 数字、标点、空白和符号允许。
- 轮次与错误:首轮成功返回但结构、判断、Script、长度或 `finish_reason=length` 不合格时,只以同一 canonical `userPrompt` 开启唯一第二业务语义轮;不得读取或传递首轮候选。首轮 `content_filter` 和 transport / timeout / 上游最终失败直接结束;`LlmClient` 内部 transport retry 仍属于当前业务轮。第二轮任何失败均最终失败。service 错误展示只提供 `translation_invalid / translation_upstream_failed` 分类和安全中文消息,不保存或输出未通过候选。
- 阶段边界:T2 没有调用 ElevenLabs、创建额外任务、扣费、修改队列 payload、持久化 `actual_prompt` 或变更 External v1 / OpenAPI / SpacetimeDB schema。T5 接入正式 Worker 时必须移除 T2 的 staged dead-code 豁免,并把翻译结果作为唯一 `actualPrompt` 进入 providerT2 单独合入仍不是可发布切点。
## 2026-08-07 SFX 生成优化 V2.0 T4 前端 controller 与参数 UI
- 状态模型:SFX 使用独立于 BGM 的纯状态模型和 dialog-scoped controller。优化与提交均在第一个 `await` 前同步 claim operation;账号、项目、dialog、mode 与 `AbortController` 共同判定响应归属。关闭、删除、mode / scope 切换后的旧响应不能写回新面板;同一按钮双击只有第一个 operation 生效。
- Prompt 与撤销:计数、优化、预设、撤销和提交统一复用 T1 的 Unicode `White_Space` canonicalizer 与 `1-2048` code point 规则。优化开始时以当前 canonical Prompt 替换旧快照,成功转为单层交换快照,失败清除本次临时快照且不恢复更早快照;预设写入清快照,手动编辑优化结果后仍可在两个 canonical 版本间反复交换。
- 预设视图:BGM 预设跑马灯抽出无业务语义的音频内核,保留单一可访问控件队列、无缝滚动、hover、触摸、页面可见性和 reduced-motion 行为;BGM / SFX 各自保留 wrapper、预设模型和业务 class。SFX wrapper 展示固定 `40 + 12` 预设,不保存展开、滚动或 hover 状态。
- 参数与布局:SFX 首次打开为自动时长、预置手动值 `5s`、Loop false;手动 slider 为 `0.5-30s`、步进 `0.1s`,自动模式禁用 slider 但保留最近手动值。layout 恢复 `soundDurationMode / soundDurationSeconds / soundLoop`,历史 Vidu dialog 与改造入口统一打开固定 `eleven_text_to_sound_v2` / `ElevenLabs` 面板。前端显示新模型 `5` 泥点兜底,正式价格仍以后端 T5 入队冻结值为真相。
- 锁与阶段边界:优化、提交或既有生成态只锁当前 SFX dialog 的输入、预设、滚动、参数、撤销和生成。提交 claim 同步冻结 canonical Prompt、时长模式、最近手动值与 Loop;T4 不改变正式请求的 nullable duration / Loop 映射,不接 Worker 翻译或 ElevenLabs,不修改服务端动态定价、计费、OSS、持久化详情、External v1、OpenAPI 或 SpacetimeDB schema。T4 必须与 T5 同一发布列车,不能单独发布。
## 2026-08-07 SFX 生成优化 V2.0 T5 正式生成与 External v1
- 正式执行链:站内与 External 请求在定价、预扣和 enqueue 前统一收敛为 canonical userPrompt、`model = eleven_text_to_sound_v2`、nullable 小数 duration 与 Loop;队列载荷不包含 actualPrompt。Worker 在既有冻结计费上下文内顺序执行 Luna 英文化、单次 ElevenLabs POST、MP3 校验 / 实际时长探测、OSS 和项目资源 / 账号素材 / 画布完成态写回,任一失败进入既有退款边界。queue 使用 job IDinline 在 provider 前生成平台 Task ID,不伪造 provider task ID。
- 权威结果:服务端只保留 Agent 身份关联字段并重建 SFX V2 `generation_inputs_json`,统一写入 userPrompt、actualPrompt、固定模型、duration mode、请求 / 实际时长和 Loop;客户端自报的实际英文 Prompt、实际时长、模型和 Loop 均被覆盖。信息弹窗展示中英 Prompt、实际时长、Loop、模型和完整平台 Task ID;重绘优先恢复 V2 metadata,自动模式恢复默认最近手动值 `5s`,不把实际输出时长当作手动请求值。历史 Vidu 素材仍按旧字段只读,并以新模型重绘。
- 定价兼容:默认配置、api-server 与 SpacetimeDB 值校验同时要求保留 `audio1.0` 和新增 `eleven_text_to_sound_v2`。已存在的 SpacetimeDB 定价快照仅缺新键时,api-server 从当前受控默认 / override 补入该键后读取;其它缺失模型仍失败。该兼容不修改 schema、不在读取时写库,下一次后台保存自然持久化完整矩阵;队列计费、响应和资产成本继续使用入队冻结价格。
- External v1Rust DTO / handler、OpenAPI、幂等 canonical payload、compact result 与仓库 Agent Skill 同批演进。model 的省略 / null / 空串 / 纯 Unicode White_Space / 包围空白新模型 / 显式新模型统一入队;旧模型和未知非空值在 enqueue 前返回 `400`。完成结果增加实际 `durationSeconds` 与 Loop,继续隐藏 provider、userPrompt 和 actualPrompt,只暴露稳定结果引用。
- 阶段状态:T1–T5 已完成,可以进入 T6;T6 仍需汇总 mock LLM / ElevenLabs / OSS 失败矩阵、计费退款、端到端等值、BGM 回归、API smoke、旧 Vidu 队列 drain 和发布 / 回滚门禁。T5 未执行真实 LLM、ElevenLabs 或其它付费请求,且没有 SpacetimeDB schema、migration 或 bindings 变更。
## 2026-08-07 SFX Prompt 边界 canonicalization 改为 ECMAScript trim
- 决策:SFX 的 `userPrompt` 与 `actualPrompt` 从首尾 Unicode `White_Space` 规则改为 ECMAScript `String.trim()` 语义。TypeScript 直接使用 `String.trim()`;Rust 以等值边界字符集合实现,不能使用语义不同的 Rust `str::trim()`。因此首尾 `U+FEFF` 删除、首尾 `U+0085` 保留,内部空白、内部 `U+FEFF`、`U+200B`、组合字符和 ZWJ emoji 继续保持原样。
- 范围:只影响 SFX Prompt 的输入、优化候选、Worker 翻译候选、正式请求、metadata 校验与 ElevenLabs bodyBGM Prompt 以及 External v1 `model` 的 Unicode `White_Space` canonicalization 不变。
- 验证:共享 fixture 锁定 ECMAScript 全部首尾删除字符、`U+FEFF` 边界删除与内部保留、`U+0085` 边界保留、内部空白和 `1 / 2048 / 2049` code point;前后端必须共同消费该 fixture。
## 2026-08-07 SFX 生成优化 V2.0 T6 Worker 组合门禁
- 正式编排:SFX Worker 以同一个内部编排函数串联现有计费、翻译、ElevenLabs、OSS、asset object / bind 候选准备和原子项目资源 / 账号素材 / 画布 / job 提交。生产 adapter 继续调用正式实现,测试 adapter 只替换外部边界;禁止另写与生产分叉的“测试专用业务流程”。
- 失败与退款:余额不足时 Worker future 不得被 pollLLM / ElevenLabs / OSS / 写回均为零;预扣后的翻译、provider、MP3、OSS 或写回失败全部一次退款。组合矩阵必须证明每个 job 的 ElevenLabs POST 最多一次、翻译最终失败 provider 为零、成功只扣费一次。
- 分类:内部稳定 reason code 固定为 `translation_invalid / translation_upstream_failed / translation_budget_exhausted / elevenlabs_http_failed / invalid_audio / duration_probe_failed / oss_failed / writeback_failed`。MIME、空 body 和大小归 `invalid_audio`MP3 识别、帧读取和时长门禁归 `duration_probe_failed`。普通用户继续只读稳定短文案,不暴露 endpoint、上游正文或凭据。
- 跨入口:画布 Agent `generate-sound-effect` 与站内 / External v1 共用 canonical Prompt、固定模型、nullable `0.5-30` 小数时长和 Loop;省略 duration 为手动 `5s`,显式 null 为自动。SFX 参数解析必须保留该 null,不能被通用 null-default 兼容层改写。最终仍进入相同 `editor_sound_effect_generation` queue payload,不新增 Agent 专属链路。
- 发布边界:T6 工程实施和 mock / loopback 门禁不等于真实 provider 或生产验收。发布前关闭 SFX 入队,使用显式 `--server` / `--server-url` 只读查询 `external_generation_job` 中 pending / running 的 `editor_sound_effect_generation`,清零后按 api-server / Worker → Web 顺序部署并灰度;禁止 `--root-dir`、删除任务伪造 drain 或自动回退 Vidu。本次没有 SpacetimeDB schema、migration 或 bindings 变更。
## 2026-08-06 编辑器生成结果使用 durable receipt 与统一原子提交
- 背景:图片、改图、去背景、图集 / UI 多产物、角色动作、视频、音效和背景音乐在 OSS 结果可用后,仍分段 confirm object、创建 project resource / account asset、保存 canvas 和 complete job。任一中间失败都会留下部分业务事实;只把 `external_generation_job` 当 operation journal 又无法覆盖无 job 的 inline,也无法独立证明某批 resource/asset/canvas 已作为一笔提交完成。
- 决策:新增私有 `editor_generation_operation` durable commit receiptqueue 与 inline 共用。`persist_editor_generation_result_and_return` 在一次 `try_with_tx` 内提交可选 asset object、全部 resource / asset / binding、可选 canvas V2 CAS、queue job 终态和 receipt。job 仍是队列、lease、计费和通知真相,receipt 只是提交凭证,不复制大快照或形成平行 read model。worker 成功走统一 procedure 后不再单独 complete job。
- 身份与重放:queue 以 job ID 为 operation IDinline 以稳定 request ID 为 operation IDProvider task ID 只做审计。operation fingerprint 绑定规范请求,commit SHA-256 对完整提交输入的稳定 BSATN 编码做 domain-separated 哈希,另外绑定逐 slot 候选、布局与 job completion,不使用 Rust `Debug` 文本充当持久协议。receipt 存在且所有权威事实一致时才返回 `AlreadyApplied`;不重复事件、不刷新时间、不推进 canvas revision。receipt 缺失但稳定 resource/asset/binding 已存在必须失败关闭;事务前已单独确认的 object 只在全部字段精确相等时允许复用。
- 并发、时间与 OSS 边界:canvas 冲突只刷新 project 重算布局,不重跑 Provider / OSS`completed_at_micros` 必须为正数,候选原时间字段与它一起绑定 commit SHA-256,重放不重新取时;job 终态与事件使用 SpacetimeDB `ctx.timestamp`。OSS `PUT / HEAD` 仍在数据库事务外,事务失败可以留下无引用 object,不声称跨 OSS exactly-once。
- queue 结果与 CAS 重试补充:普通画布 queue 只持久化 source/warning 元数据,Editor Agent 和 External API 分别只写入各自裁剪后的结果,最终 JSON 不得超过 512 KiB。消费者身份必须在 worker 从完整 claimed job 构造调用上下文时固化,不能从已裁剪的 summary 兼容快照反推。CAS 冲突最多刷新布局一次,只允许 revision/layers 和 layout `updated_at_micros` 随最新 project 变化,避免回拨并发用户更新时间;items、job payload 与 `completed_at_micros` 保持不变。每个 prepared commit 的传输未知结果最多原样重放两次,不重跑 Provider / OSS。
- receipt 只保存 queue result 的 SHA-256,不复制最多 512 KiB 的 payload;重放时从已完成 job 回读权威 payload 并核对摘要。事务边界即使没有 asset_object candidate,也必须统一核对 resource/asset/binding 的 object ID/key/owner,并要求 canvas layout 与全部 project resource 属于同一 project。
- 事务内还要先查同 `operation_id` 的 `external_generation_job`:存在则首次/重放都强制完整 completion guard,不存在才允许 inline。resource/asset 的尺寸、媒体引用、task、kind 与生成元数据按 item 交叉验证,音频 binding 使用 operation 限定 tuple 和显式 kind 映射。省略 candidate 的已登记 object 在 receipt 重放时仍回读 owner/key/task/kind/媒体身份。
- queue 跨记录绑定继续失败关闭:job `source_entity_id` 必须就是结果唯一 project,所有 `source_resource_id` 必须已存在且属于同 owner / project。Provider 已成功但原子持久化确定失败时,当前 worker/lease 验证、当前计费 attempt 退款和 job 失败终态由同一 SpacetimeDB 事务结算;不在 api-server 先独立退款。
- compact result 裁剪不得丢失消费 DTO 必填字段或正式素材定位信息:角色动作/视频保留 `ok`,音效/BGM 保留 `prompt`External 角色动作与视频还保留稳定 `assetId`,不复制大型生成 payload。account asset 的 `source_resource_id` 与 project resource 一样验证候选/已登记来源的 owner,并在有项目上下文时验证 project。
- External v1 的二次 allowlist 裁剪同样保留 `prompt / actualPrompt`,契约验收以 `serialize_atomic_editor_generation_job_result` 最终 JSON 为准,不只测上游 builder。图标/UI 正常与 source-only fallback 同时保留 `ok / prompt / actualPrompt`fallback 的尺寸/model/价格也从本次生成上下文显式携带,不依赖可选 project resource。Editor Agent 图片生成/修改 DTO 允许 compact payload 不携带 `provider`。inline 八类 provider 生成的已成功 billing guard 延迟到 owner handler 完成 durable receipt 提交才 disarmprocedure 发出前的明确失败/取消退款,发出后回包前的传输不确定或取消保留扣款。
- 影响范围:所有现役编辑器生成类型、`spacetime-module` / `spacetime-client` 结果提交契约、queue worker 终态写回、schema / migration / generated bindings 与对应故障注入测试。完美像素保留现有专用原子 procedure;手动图集拆分保留现有批量事务,其 canvas completion 并入批量事务另行收口。
- 关联:`docs/technical/【后端架构】编辑器生成结果原子提交与幂等重放方案-2026-08-06.md`、Issue #134。
## 2026-08-07 确定性派生配方与改造 capability 分离
- 决策:`generationInputs` 是持久化配方 / 来源账本,不直接代表“允许改造”。完美像素、裁扩、所有手动与自动图集切片、手动去背景分别写 `image.perfect-pixel`、`image.crop-expand`、`spritesheet.split`、`image.remove-background`,固定 `fields: []`;有正式来源行时只保存服务端权威 `references[id="source"]`,没有正式行时为空。这四个 action 不进入改造 allowlist,历史 `pixel-art-snap-*` 同样失败关闭;整张生成图集继续保留生成 action,自动抠图仍是生成流程内部后处理。
- V2 水合统一按 `version/action/fields[].id/references[].id` 严格识别,保留有限数字、布尔值与无标签引用;出现 V2 标记但结构无效时不得降级 legacy。完美像素账本复用相同 V2 白名单,外层仍为 version 1,并继续原形接受旧 legacy 请求以维持 exact retry fingerprint。
- 安全边界:站内已迁移队列只保留客户端 references 的安全槽位 `id`,真实 `title/label/refType/refId` 全部按 owner-scoped 记录重建;External API 和直接不可信写入仍删除整段 references。队列幂等比较忽略该冗余展示槽位,但继续严格比较实际媒体来源与其它参数。
- 不改 SpacetimeDB schema、路由或 External OpenAPI;不回填历史记录。
## 2026-08-08 游戏场景接入 V2 改造配方
- 背景:`72f268e0` 新增 `assetKind = scene` 和场景专用生成入口,但场景生成输入仍是依赖中文标题的 legacy 快照,无法通过现役 V2 action allowlist 稳定恢复“改造”。
- 决策:新增稳定 action `scene.generate`,字段 ID 固定为 `prompt/stylePreset/customStyle/model/aspectRatio/imageSize`,参考槽 ID 固定为 `reference`。新产物由服务端按结构化场景请求重建权威 V2 fields,并从真实 `referenceImageSrcs` 生成安全引用槽位,后续继续沿用 owner-scoped provenance 重建;前端 action decoder 恢复场景 composer,并让请求与持久快照共用规范化参数。
- capability 边界:`assetKind` 只表示素材类别,不直接授予“改造”。`scene.generate` 显式进入已知与可改造 action allowlistV2 上线前的场景仅在 `assetKind === scene` 且 legacy 字段包含“画面内容”时兼容恢复并告警,不把“画面内容”加入全局 legacy 标题路由,其他类别同名字段继续拒绝改造。
- 影响范围:图片画布场景提交、生成输入解码、改造入口、场景 composer 恢复、api-server 场景配方重建与对应前后端测试;不修改 SpacetimeDB schema、migration、bindings、External v1 路由或 OpenAPI。
- 关联文档:`docs/technical/【技术方案】图片画布游戏场景生成链路-2026-08-04.md`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【编辑器】生成类面板Lovart统一改造方案-2026-06-17.md`。
## 2026-08-08 AGC Vite 纳入统一用户端口段
- 背景:Linux 主开发栈已按用户分配 `100` 端口段,但后加入的 AGC Tauri 壳仍固定监听全机共享的 `3080`。同机任一用户的旧客户端都会阻塞其它用户,且 marker 中出现的动态 API 端口无法解决 Vite 本身的跨用户冲突。
- 决策:端口段正式增加第六个槽位 `agc-vite = start + 5`,端口段最小长度同步改为 `6`。Linux AGC 首选该槽位并只在当前用户段内漂移;Windows / macOS 保留 `3080` 兼容首选并统一探测漂移,不读取 Linux 系统注册表。
- 一致性:`start-tauri-dev.mjs` 是端口选择权威,最终端口通过 Tauri CLI `--config` 覆盖 `build.devUrl`,通过 `GENARRATIVE_AGC_VITE_PORT` 传给 `beforeDevCommand` 和配套后端端口解析器,并通过 Vite CLI `--port` 启动严格监听。父启动器选定端口后,子启动器只允许严格使用同一端口,配套后端漂移必须跳过该预留端口,竞态占用必须失败关闭。
- 安全边界:动态端口不恢复旧 Vite 复用。无法证明 worktree 归属的监听器仍不复用、不主动终止;同用户多 worktree 通过段内漂移并行,不通过共享未知服务并行。
- 验证:公共端口映射、Linux 默认槽位与段内漂移、非 Linux 兼容漂移、Tauri 动态配置、启动前预检、进程树收束、AGC typecheck / 配置门禁、编码检查和差异检查必须通过。
## 2026-08-10 AGC 默认使用 Codex CLI 作为节点 Agent
- 决策:AGC AppData 配置新增 `agentMode=codex_cli|provider`,默认切到 `codex_cli`,原 HTTP Provider 实现、配置和显式回退能力保持不变。External Runner 和现有 manifest DAG 不分叉;每个节点请求在现有 Provider lifecycle 外壳内选择执行器。
- 安全:Codex CLI 只负责结构化推理。它在空临时目录、ephemeral、忽略用户配置、read-only、never approval、禁用 shell tool 的边界内运行;项目读写、命令、MCP、Canvas、权限、revision、验证、receipt 和 reconciliation 仍由 AGC Runtime 执行。Codex 认证留在用户级 CLI,任何凭据和认证文件都不进入项目事实。
- 恢复:执行模式和 CLI 身份进入 Provider 配置指纹;模式或 CLI 变化会使旧 retry/handoff 作废并沿现有恢复合同处理,禁止跨模式复用成功交接或自动重放未知结果。
- 关联:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` V1.51,以及 `docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md` 的“2026-08-10 Codex CLI Agent 执行模式”。
## 2026-08-10 AGC 默认切换为 Codex app-server 长期节点 Agent
- 决策:新增 `agentMode=codex_app_server` 并设为默认,保留 V1.51 的一次性 `codex_cli` 与原 `provider`。External Runner 内按有效 Responses 凭据/路由及 `projectId/agentId/sessionId/runId` 为每个权威节点隔离长期 app-server;稳定节点 run 对应独立进程和 ephemeral thread,节点请求对应串行 turn,但 AGC lifecycle、handoff、manifest、Agent DB 和 finalization 仍是唯一 durable owner。单节点 stdio/进程失败只能影响本节点,不得把其它并发 Agent 一并置为 reconciliation。
- 配置:有无 AGC LLM Key 都只支持 `openai_responses`;非空 Key 映射后 base URL 与逐 Agent model/effort 生效,Key 只经专用环境变量;空 Key 只桥接用户 Codex `auth.json`,移除继承环境 Key。`stream=true` 复用现有 durable final-reply delta`webSearchEnabled=true`、`openai_chat / anthropic` 必须显式切换 `provider`,不得静默忽略已有 LLM 配置。
- 安全与恢复:app-server 使用隔离临时 `CODEX_HOME` 与 OS HOME,只桥接认证,不加载用户 MCP/config/skills/hooks;启动前关闭 web/multi-agent/shell/browser/plugin/image 等原生能力,固定 read-only、network off、never approvalAGC 是唯一 ToolHost。取消覆盖 turn-start 回包前窗口并只发送单 turn interrupt;已开始 turn 的连接/终态未知直接标记 reconciliation,明确 failed/interrupted 不自动重试。模式与 durable 指纹取同一配置快照。
- 资源与退出:pool 按实际凭据快照/base URL/API kind/CLI 版本和节点 run 身份隔离;空 AppData Key 只允许桥接一次性读取的有界 `auth.json` 快照,并用同一字节快照生成池指纹,宿主 `CODEX_API_KEY` 对 app-server 与一次性 CLI 都必须移除。节点进程与 thread 均有上限并只淘汰 inactive LRUstdout NDJSON 与 stderr 无换行记录均有硬上限,stderr 原文不得写入错误或日志,只记录固定分类、总字节数、SHA-256 与可取得的退出状态。Runner 正常、强制、watchdog 退出显式关池;Linux child 绑定 parent-death signal,避免 Runner 被强杀后遗留带凭据孤儿进程。
- DirectProject 会话恢复补充:Codex thread 继续使用 `ephemeral=true`,不保存或恢复 Codex 原生 thread。项目对话 `.agent/conversations/project.jsonl` 是唯一聊天事实源;仅当 app-server 连接没有可用的项目 thread(通常是进程重启或 thread 被淘汰)时,AGC 才读取全部 `user`、`assistant`、`tool` 行,按原顺序渲染为简单的 `user:` / `assistant:` / `tool:` 文本后,再追加本次新 user 请求发送给新 thread;已有 thread 的普通消息仍只发送新 user。app-server 意外中断时,已收到的 partial 文本作为普通 `assistant` 消息追加,并在末尾写入 `unexpected interrupt happened here`;断开处理与下一次发送均可重复追加,但使用普通消息 `messageId` 幂等。项目打开不再根据“只有 user 没有 assistant”自动重发旧请求;Direct 不新增 retry 入口。Runtime Agent 的恢复合同保持独立,不消费项目 Direct 对话历史。
- 兼容迁移:已有 AppData 未写 `agentMode` 时,仅当全局及逐 Agent 都是 `openai_responses` 才迁入 app-server;任何 `openai_chat / anthropic` 路由保持 `provider`,防止项目自动恢复先于用户改配置而批量失败。新安装仍默认 app-server;已确认兼容 Responses 的旧端点可由用户显式切换且继续使用原 model/base URL/API Key。
- 关联:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` V1.52。
## 2026-08-10 Supervisor steer 改为 LLM 判定后的条件中断
- 决策:根 Project Supervisor 收到运行中消息后继续原 Run,先由独立 LLM 生成非终态回复并判断 `interruptCurrentProvider`。状态询问、解释和不冲突补充默认不中断;只有明确停止、改向或在途方案会过期时才可请求中断。过程回复持久进入原 conversation,但不得调用终态 `respond_to_user`。
- 时序:steer durable 入队后不先通知 Runner。`false` 或判定失败才调用只唤醒的 `runtime.steer``true` 直接调用 `runtime.interrupt_for_steer_decision`。后者必须读取已持久化判定,并只中断 `appliedSteerCursor < steer.sequence` 的旧 Provider;新规划 Provider、工具和外部副作用不可被误杀。
- 失败:判定调用、协议解析或持久化失败时公开回复“继续当前任务”,在下一安全边界应用 steer,绝不退化为默认中断。External Runner 与本地进程内执行保持同一语义。
- 关联:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md` V1.53,以及 `docs/project-memory/shared-memory/pitfalls.md` 的“Supervisor steer 不能只有内部排队事件”。
## 2026-08-10 普通图片废弃 assetKind 的迁移与写入门禁
- 语义:普通静态图片的持久化 `asset_kind` 为 `NULL`,旧值 `image` 不再是合法业务类型。登录态 API 和 SpacetimeDB storage 的项目资源 / 账号素材创建边界统一将 trim 后精确等于 `image` 的输入归一为空;其它值不借本次迁移扩大或收紧既有校验。
- 迁移:生产维护窗口内按 `asset → project-resource → showcase → canvas` 执行受 operator 鉴权、分批 dry-run、批次 SHA-256 绑定和 apply 后零残留复核的清理。project-resource 清行前验证同工程迁移并把状态纳入批次 hash;只有 layout version 0 的 legacy 画布可以缺 migrationstructured 画布缺记录必须在任何资源写入前失败关闭。清行后能保持原 status 不变量时立即重签,从而覆盖 rolled_back 布局没有显式字段、摘要却依赖资源旧值的情况,仍需显式字段清理的过渡态保留旧凭证。canvas 的权威扫描、批次 hash 和原子 patch 同时覆盖双份 legacy shadow、`editor_canvas_layer` 与 `editor_canvas_generation_dialog.dialog_json`;写入前再按 active / backfilled / rolled_back 原状态验证旧凭证、structured revision/hash/integrity 与对应语义等价 / 重入不变量。只有旧凭证有效且差异仅来自受控资源清零及精确 `image` 删除时才写入,写后从全部 structured 权威行重建 layout,再用同一状态守卫验证并更新摘要。该过程不推进业务 revision、不改 migration 状态或任何时间戳。旧 migration 摘要中的 `image` 通过清理专用兼容 canonicalizer 复算,正常保存白名单仍拒绝该值。
- 持久化边界:除项目资源 / 账号素材创建入口外,legacy 画布保存从 layer 提取 `assetKind` 时必须区分“字段不存在”和“字段存在但归一为 `NULL`”;后者仍删除布局字段并触发持久化。项目资源 metadata 落表前再次归一 stored / incoming 值,防止 legacy V2 保存重新写回 `image`。
## 2026-08-10 AGC 普通 release 固定连接 dev API
- 传输边界:dev 公网入口不接受 Tauri WebView 的跨域 OPTIONS 预检,因此 release 使用 `tauri-plugin-http` 原生 transport。插件的 npm 依赖只归属 AGC 子包及其 lockfile,根 H5 package 与根 lockfile 不得引入任何 Tauri guest 依赖。插件 capability 与前端 URL 解析双重限制为 `https://dev.genarrative.world/api/*`,不为 WebView CSP 增加远程 `connect-src`,也不开放任意 HTTP(S) 目标。
- 会话边界:插件默认 Cookie Store 持久化 refresh Cookie;访问 Token 继续保存在现有客户端存储并通过 Authorization header 发送,不把 Token、Cookie 或登录正文写入日志、配置文件或仓库。
## 2026-08-10 AGC monorepo 构建强制复用单一 React runtime
- 根因:AGC 子目录存在独立 `node_modules` 时,AGC 源码会解析子目录 React,而仓库共享组件解析根目录 React;登录页不依赖共享 Hooks,进入首页后才触发 `Cannot read properties of null (reading 'useCallback')` 并白屏。
- 决策:AGC Vite 配置必须对 `react` 与 `react-dom` 启用 `resolve.dedupe`release 和本地构建统一复用仓库根 React runtime。登录后内容保留错误边界,渲染异常必须显示可恢复提示,不能再次退化为无提示白屏。
## 2026-08-10 AGC 打开现有 Godot 项目
- 项目双根决策(2026-08-14 更新):项目组只保留通用“打开项目 / 新建项目”,不再提供独立 Godot 入口。用户选择目录始终是工作区根,也是 `.agent`、Session、Runner、沙箱、通用文件工具和外围资料的唯一授权根;实际 Godot 根由根目录或一层直接子目录中的普通文件 `project.godot` 唯一确定,并以工作区相对 `godotProjectRoot` 记录,根目录使用 `.`。不得把 Runtime 根切换成 Godot 子目录,也不得复制工程或建立第二套工作区。
- 发现与歧义决策:根目录命中优先;根未命中时只检查一层直接子目录,唯一命中才通过,多个命中在任何 `.agent` 写入前失败关闭。候选目录与工程文件拒绝符号链接和 Windows reparse point,二层及更深不递归。未来只有 Godot 专属命令显式使用经过校验的相对 Godot cwd。
- 元数据决策:首次导入只在工作区根创建并保留 `.agent/manifest.json`、`.agent/agent.db`、`.agent/logs/` 与 `.agent/runtime/`;不得创建默认 Web 原型的 `game/`、`assets/`、`memory/`、`exports/`。已有有效 `.agent` 项目继续复用身份;缺失或错误的可推导 `godotProjectRoot` 只在 Godot 打开边界按唯一文件布局校准,歧义时不改写。
- Windows 锁文件决策:提升权限进程新建 `.agent/.manifest.json.lock` 时,Windows 可能把 owner 设为 `Administrators`。仅在固定锁路径已取得不共享独占句柄并确认是普通、非 reparse、单链接文件后,才初始化为当前 `TokenUser`;随后再次复核句柄并执行原有 owner/DACL 校验,不放宽既有异常对象的安全规则。
- 运行决策:Godot 项目提交给 Project Supervisor 时使用 `standard` Run Profile,避免触发 Web 专用 `game/index.html`、HTTP preview 与自主 Web 完成门。Godot 编辑器启动和内嵌运行预览不在本切片范围。
## 2026-08-15 Jenkins 容器预览部署使用独立控制面
- 决策:多人内网容器预览不把操作表单塞进 Jenkins 页面,也不让 SPA 直接操作 Docker。独立 `preview-deployer` SPA 通过同源 Axum 代理触发固定 `shared/Genarrative-Preview-Deployer` Job;浏览器只持有控制面 HttpOnly 会话,Jenkins service account 和 API Token 只存在服务端环境。
- 部署入口只使用内网 `http://192.168.35.82/build/`,不配置公网域名;预览 Web 端口固定为 `8400..8499`,卸载后立即释放租约,运行状态由页面刷新时的实时 Web 探针更新。
- Jenkins 的 Compose 编排、Dockerfile 入口和执行脚本固定取自受保护的 master 控制器 checkout,目标分支只作为应用源码构建上下文;控制 Job 只授予受信任开发者和专用服务账号。
- 实例与端口:分支规范化后形成稳定 `deploymentId`,同一分支换 commit 复用实例和 Web 端口;不同分支使用独立 Compose project。Web 端口在全局文件锁内从 `8400..8499` 分配,状态表与宿主监听同时空闲才可占用,卸载后释放。SpacetimeDB 与 OTLP 不映射宿主端口,Jenkins 通过受控 Compose 网络发布模块;页面只展示 Web 内网地址。
- 来源与卸载:部署只接受 `SOURCE_BRANCH` 和可选 `COMMIT_HASH`Jenkins 必须证明 commit 属于目标分支。卸载只接受受控状态中存在的 `deploymentId`,客户端不能传 Jenkins URL、Job、Compose project、容器名或端口。状态通过固定 `preview-result.json` artifact 返回,不解析或向浏览器暴露完整 console。
- 关联文档:`docs/technical/【开发运维】Jenkins容器预览部署控制面技术方案-2026-08-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
## 2026-08-17 预览发布记录使用 Jenkins 构建编号并有限保留
- 决策:内部稳定 `deploymentId` 继续绑定分支、Compose project 和端口租约;页面/API 记录 ID 在 Jenkins 分配执行器后改为构建编号,排队阶段为“待分配”。卸载通过构建编号找到内部实例,再向固定 Job 传内部 ID。
- 清理:失败或取消且不存在可卸载实例的记录保留 7 天;成功卸载的内部审计记录保留 30 天;仍可卸载的失败记录永久保留到人工卸载。服务启动、读取列表和创建部署时执行清理并原子落盘。
- 链接:服务内部仍通过 Jenkins loopback 轮询;只向浏览器返回由 `GENARRATIVE_PREVIEW_DEPLOYER_JENKINS_PUBLIC_BASE_URL` 构造的局域网构建详情地址,禁止回传 loopback URL。
## 2026-08-16 AGC 直连回合的 revision 与项目版本同步
- 根因:直连 Runtime 登记游戏代码并写入首个版本时复用了陶泥儿美术生成后的旧 project revision。外层工作台按 revision 合并 manifest,同 revision 的不同快照必须失败关闭,因此磁盘已有 `game/index.html`、`game/style.css`、`game/game.js` 和版本记录时,资源管理仍可能显示游戏代码 0 项、项目版本 0 项。
- 决策:每次直连 Codex 回合只有在代码文件存在、完整陶泥儿美术包有效且五类素材进入实际 Canvas 渲染后,才在项目写锁内推进一次 durable project revision,并以新 revision 创建首个 `initial-*` 或后续 `agent-*` 正式版本。后续版本绑定上一正式版本为父版本,继续复用原游戏代码资产 ID,禁止通过重复登记制造平行资产。
- 已有项目编辑:direct app-server 继续禁用 shell / unified exec,但系统提示词必须在仓库长文档之前带入当前三个游戏代码文件的有界脱敏快照,供原生 file-change 精确匹配。回合前后绑定三个文件的内容指纹;没有真实文件变化且 manifest 已同步时,不推进 revision、不追加版本,不能把“无法读取所以未修改”的回复登记成正式修订。
- 验收:连续两次产物同步必须得到单调 revision、`initial-* -> agent-*` 父子版本链、稳定的 3 个代码资产 ID;真实客户端外层资源管理必须随新 revision 显示游戏代码和项目版本。
## 2026-08-17 AGC 直连阶段反馈与资源预览首屏预取
- 直连 Runtime 的美术生成、代码生成和版本登记仍保持单次原子回合,不拆回 Supervisor 或 harness。为避免用户等待数分钟只看到“思考中”,Runtime 在每个用户可理解的安全阶段复用 `game-creator-agent-progress`:需求接收、陶泥儿美术包检查、规范图、背景图、图集及切片、代码生成、版本登记和预览刷新;普通直连工作台仅接受当前项目的事件并显示 `message`,不把内部执行协议、路径或凭据暴露到聊天。
- 资源画布不能把 `IntersectionObserver` 作为首屏唯一预览触发器。工作台进入资源管理时主动预取最多 12 个可预览的非音频、非版本、非占位资源;保留现有按可见性懒加载、队列优先级、并发上限、缓存上限、权限失败提示与详情/播放升级机制。这样不会一次读取大项目全部资源,但陶泥儿标准三张 PNG 和游戏代码能在正常首屏项目中无需打开详情直接显示。
- 验收:定向 hook 测试证明预取会直接调用本地 preview command 并进入 loadedAppSurface 证明 direct 提交即时显示接收文案并消费阶段事件;2026-08-17 在当前 checkout 的普通 AGC 客户端打开 `gameagent-3291e57c`,未打开任何资源详情即观察到三份游戏代码摘要和三张陶泥儿 PNG(规范图、16:9 背景、核心图集)缩略图。
## 2026-08-17 AGC 开发态默认单窗口启动
- 决策:`npm run agc` 及普通 debug 启动只打开标题为“陶泥儿”的 `client` 客户端窗口,不再由 Tauri setup 自动创建 Agent 聊天开发窗口。普通创作、直连 Codex Runtime 和项目工作台行为均不受影响。
- 守卫:原生壳配置检查必须拒绝恢复 `open_developer_window(app.handle())?` 自动调用;真实开发 smoke 以普通客户端单窗口为准。
## 2026-08-17 直连 Codex 使用 LLM 自主试玩替代固定美术产物门
- 直连 Runtime 的确定性门禁只保护工作区权限、凭据隔离、平台生成幂等与账本、文件事务、图片下载/PNG 解码、平台来源身份和同 Canvas 关系。`grid-2x2`、固定四切片、固定文件名及固定 `drawImage` 次数降为陶泥儿标准推荐路径,不再阻断所有合理的游戏产物。
- Codex 是唯一执行主体。客户端在同一 Codex thread 内最多追加两次结构化浏览器证据/整改 turn;受限 Chromium 负责 desktop/mobile 页面、Canvas、控制台/网络、截图和有限真实交互探针,禁止恢复 Supervisor、专业 Agent 或 harness,也不把固定玩法状态机当成 direct 完成合同。
- 完成登记前至少要有 `game/index.html` 和一个已登记、真实、平台来源的图片被源码引用;游戏质量与视觉实际使用由 Codex 根据真实试玩证据自行判断,最终回复必须说明检查文件、试玩动作、双视口观察、素材使用、修复和剩余风险。
## 2026-08-17 AGC 直连平台资源失败诊断
- 普通客户端使用陶泥儿平台账号会话时,直连 Runtime 的只读画布恢复继续以 External Editor 形状构造请求,再统一经 `resolve_platform_editor_api_route` 映射到 `/api/editor/...` 与 `/api/assets/...`;不得把平台 access token 直接送到未映射的 `/api/external/v1/...` 路由,也不得为排障改用或落盘开发者 API Key。
- 直连回合的请求准备、平台美术准备、Codex 代码生成、真实浏览器试玩和版本登记失败,统一投影为 `direct-codex-failure:v1`:仅含稳定阶段、脱敏摘要、是否可重试和下一步建议。每次失败尽力写入项目 `.agent/runtime/direct-codex-diagnostics/<nonce>/failure.json`,不保存 token、Cookie、完整 URL、绝对路径或原始服务端正文;写诊断失败不得遮蔽原失败。
- 普通客户端必须展示上述安全摘要与建议,不能把可解释的资源恢复失败降级成“执行失败,请稍后重试”。平台账号失效提示重新登录;资源身份冲突、多个同源图集或透明图集明确要求先在资源画布核对,而非盲目重复生成或扣费。
- 已有同一画布、身份可信且可解码的规范图与背景图时,历史核心图集的只读恢复只是可选增强:未找到该图集不得阻断直连 Codex 生成、浏览器试玩或版本登记,也不得触发重复付费生成;最终源码仍须实际引用至少一个已登记的平台图片。
- 透明后处理失败但平台已保留源图时,只有同一画布身份的只读恢复成功后才清理对应 `agentId/runId` 生成账本;恢复失败、身份不唯一或结果未知继续保留 `accepted` / `operationId` 供对账,禁止因清理过早而重复扣费。
## 2026-08-18 AGC 登录服务器选择
- AGC 登录页提供 `release``https://www.genarrative.world`)、`dev``https://dev.genarrative.world`)和 `custom` 三种服务器选择;选择持久化在客户端本地存储,登录、验证码、刷新和原生平台会话安装统一使用当前选择。
- custom 只接受纯 HTTPS origin`localhost` / loopback 的 HTTP 也允许用于本机服务,禁止把路径、查询参数、凭据或非本机明文 HTTP 地址作为服务器地址。
- Tauri release 的 HTTP capability scope 必须覆盖 release、dev、custom HTTPS 以及 loopback HTTP`check:native-shells` 以该精确 allowlist 作为源码门禁;否则前端选择虽能保存,plugin-http 仍会在请求层拒绝登录。
- 直连 Codex 的本机 External Editor API Key 必须按服务器 origin 独立存储。登录服务器切换后禁止复用另一 origin 的历史 Key 或 base URL;否则会出现登录走新服务器、平台资源生成仍请求旧服务器的漂移。
## 2026-08-18 AGC 登录网络错误与 Web Build 门禁对齐
- 网络 transport 的原始错误(例如浏览器 `Load failed`、URL、底层连接文本)不得直接进入登录页;客户端按超时、拒绝连接、地址解析和 TLS/证书四类可操作原因归一化,其余情况使用统一服务不可达提示。
- 本地 `master` 推送门禁 `scripts/check-repository-ci.sh` 必须在 lint 后运行 Jenkins Web Build 所覆盖的 AGC AppSurface 套件,再执行 build、content 和 diff 检查,避免登录 UI 回归只在远端构建阶段暴露;完整 Vitest 仍由 Jenkins 生产构建执行。
- Gitea `repository-checks` 因此必须和 Frontend / Native jobs 一样先执行 AGC 子包的 lockfile 安装;根目录 `npm ci` 不包含 `@tauri-apps/plugin-http` 等子包依赖,不能用预构建镜像缓存假定它们已存在。
- 验证:`npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts -t "shows a clear login service error|explains a refused login connection|keeps the stored token when startup auth check cannot reach the service"`、`npm run check:git-hooks`、`npm run check:encoding`、`git diff --check`。
## 2026-08-19 AGC 画板恢复测试显式认证模式
- 背景:普通 Debug 客户端已默认走平台账号路由;旧画板恢复 fixture 只写 `editorApi` 配置,却未安装测试会话或任务级开发者凭据。请求会在到达 loopback mock 前以 `authentication-required` 返回,而 fixture 随后无限等待 `accept`,使 Native shell CI 无界卡住。
- 决策:断言 External Editor `external-v1` 路由的测试必须通过 task-local 测试凭据显式进入开发者路径;断言普通客户端恢复路径的测试必须安装可自动恢复的测试平台会话并断言 `/api/runtime/external-generation/jobs/*`。所有等待 mock 请求的 fixture 必须使用有界 accept deadline,不得用无期限 `join` 掩盖请求前失败。
- 验证:`cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml canvas_generation_tests:: -- --test-threads=1`。
- 2026-08-19 追加:同一默认认证切换也覆盖资源编辑和 autonomous main-loop fixture。`resource_editor` 的 External Editor 视频提交/轮询/服务身份恢复测试同样使用 task-local 凭据;平台账号语义测试使用隔离测试会话。main-loop 的视觉任务配置测试不再通过旧 `editorApi` 文件伪造登录态。所有 loopback listener 在 accept 时设有 5 秒 deadline,并把 accepted stream 恢复为 blocking,避免 Windows `WouldBlock(10035)` 或请求未发出时无限等待。
- 定向验证:`project::asset_canvas::generation::tests::` 14/14、`project::resource_editor::tests::` 36/36、`agent::runtime_driver::main_loop_tests::` 48/48、`agent::runtime_protocol::autonomous_completion_contract_tests::` 107/107 通过。此前一次 Windows 全量 Native Rust 为 1811 passed、108 failed、15 ignored;失败集合仍包含 Provider/mock 调度与既有专业链断言。HEAD 基线独立复现 `tests::project::generate_platform_art_asset_downloads_and_registers_external_image` 的同一登录态缺失,故不能把全量结果伪报为本次 fixture 修复引入;本次新增认证/accept deadline 相关用例均已隔离通过,最后两个 autonomous completion fixture 的认证迁移已单独通过,完整套件未在该两行测试改动后重新执行。
## 2026-08-20 AGC 本机开发者凭据目录 ACL 自动收紧
- 缺失当前服务器对应的本机开发者凭据时,客户端仍必须在请求远端创建 Key 前完成私有目录准备。既有 `~/.config/genarrative` 若 owner 已严格匹配当前进程 `TokenUser`,客户端自动把 DACL 收紧为禁止继承且仅当前用户 Full Control,用户不再需要手工执行 PowerShell ACL 修复。
- 自动收紧不等于接管:owner 不匹配、链接、reparse point、非目录或无法安全写入 DACL 时继续在远端请求前失败关闭;客户端不得删除、移动、覆盖或读取旧凭据内容,也不得因收紧失败自动创建远端 Key。
- Windows 回归测试必须构造“owner 为当前用户但仍继承 ACL”的既有目录,先证明严格校验失败,再通过正式目录准备入口收紧并复核私有 DACL。
## 2026-08-20 AGC 图片精修候选与最终图事务
- 只有可栅格编辑的图片资源拥有持续精修草稿;同一 `sourceAssetId` 只恢复一个活动 refine 草稿。生成成功只把 PNG 写入草稿私有 `draft-media` 并追加候选图层,不自动修改 manifest,也不自动关闭画布。
- 每次生成在提交远端任务前冻结并持久化 `sourceLayerId` 与有边界的 `placeholder`。任务侧栏和画布占位共同消费 generation record;成功候选复用冻结落点,失败只终结当前任务,重启恢复不得重新提交或重复扣费。
- “设为最终图”是唯一正式资源切换入口:refine 保持原 asset ID,把正式文件切换到 `assets/canvas/<name>--<commitId>.png`,并用候选图层 ID 与媒体 SHA-256 交叉验证 staging、ledger 和 transaction journal。manifest 继续是当前最终图权威。
- 最终图事务完成或恢复后,草稿必须回到 `editing`,清空 `pendingCommit`,保留其它候选与 generation records,并在 `lastCommit.sourceLayerId / mediaSha256` 记录当前最终候选;不能把持续精修草稿永久停在 `committed`。
- 资源详情保持非模态,不能卸载资源工具栏或背景画板。图片卡展示尺寸来自安全预览元数据,受 `220x180`、最小短边 `96` 和 `1:2..2:1` 约束;碰撞、世界范围和依赖连线共同消费同一实际矩形。
## 2026-08-21 Game Agent 精修来源、批量导入与失败任务归档
- refine 主来源不能仅凭已有 Editor Resource ID 直接复用;历史 Game Agent 私有 kind 必须基于本地正式图片重新登记为 External v1 canonical kind,并使用绑定服务身份、owner、Editor Project、源 SHA256 和 canonical kind 的本地私有缓存避免重复上传。不得扩大 External v1 快速编辑白名单或向 edit DTO 补发 `assetKind`。
- Tauri 图片导入采用原生多选与 Rust 批量事务:安全读取、媒体安装、图层绑定和一次 draft revision 推进必须作为一个可恢复单元,前端只 hydrate 权威 draft,不再依赖隐藏 input 加后续 autosave 完成正式绑定。
- 生成失败任务的“删除”固定为归档私有 ledger 并移出 draft 公开投影;只允许明确 `failed`,结果未知和 `reconciliation-required` 必须继续留在恢复队列。任务侧栏折叠是会话 UI 状态,不进入业务持久化。
## 2026-08-21 Game Agent 图片生成恢复与正式图来源身份
- 图片 generation 恢复是任务级后台工作:关键提交事务恢复和草稿 hydrate 完成后,画布立即进入 editing`recoverImages` 继续恢复原 operation 并更新任务投影,但不得锁住整张画布。
- 精修候选设为最终图保持入口资产 ID。入口 manifest 已有有效 `source.resourceId` 时原样保留;只有未登记来源才使用 `local-asset:<assetId>`。提交与恢复回读使用同一解析规则。
- Game Agent 任务列表视觉复用现役美术画布的右上角独立按钮、白色面板、活动/完成双 Tab 和状态图标;失败归档是 Game Agent 的业务扩展。阻断性画布错误通过 body portal 覆盖整个 Tauri WebView。
## 2026-08-21 Game Agent 资源自由画板使用稳定隐藏边界
- 依赖画板的导航范围由当前项目全部资源的权威世界 extent 决定,并在屏幕坐标外扩 96px 安全留白;搜索过滤、详情卡和临时可见性只影响展示,不缩小导航边界。
- 滚轮、指针平移、缩放、复位和容器 resize 统一经过 viewport constraint。最小缩放为全量资源边界 fit scale;内容小于视口时居中,内容较大时边缘不能越过安全留白。该边界是会话级导航约束,不持久化为第二份布局真相。
## 2026-08-21 Direct Codex 美术资源提交后实时刷新
- Direct Codex 的进度事件只表达加载文案,不作为资源事务真相;规范图、背景图、核心图集及已付费源图恢复只有在本地文件和 manifest 登记成功后,才发送现有 `game-creator-manifest-invalidated`,身份固定为 `direct-codex-art`。
- App 继续复用按项目 single-flight 的 `refreshManifest`。单张资源提交事件负责生成中的即时投影;Direct Codex 命令无论成功、失败或超时 reject 都做一次最终 manifest 对账,失败路径不启动 preview,已提交资源不得被后续代码生成失败遮蔽。
- UI Editor 融合保留资源工作台 toolbar 和页面状态:UI 资源与普通资源一样先开详情卡,点击“编辑资源”后才进入 UI Editor;编辑器打开时使用单列全宽 stage,并禁止从父 toolbar 绕过未保存返回确认。
## 2026-08-22 Game Agent 精修最终图使用稳定运行入口
- 图片精修的 manifest 继续指向不可变正式版本 `assets/canvas/<name>--<commitId>.png`;游戏源码已引用的原路径(例如 `assets/direct-game-background.png`)是稳定运行入口,不要求代码改写。
- “设为最终图”提交、幂等重放和事务恢复都会校验不可变版本的字节、SHA-256、尺寸后刷新稳定入口。稳定入口缺失或损坏时,以不可变版本为恢复来源。
- 旧事务 journal 尚无稳定入口字段时,后续精修必须从该事务已校验的 `manifest.before.json` 迁移原入口路径,不能因为当前 manifest 已指向 `assets/canvas/**` 而丢失游戏运行入口。
## 2026-08-21 JavaScript 工程统一为 npm workspaces
- 决策:根、Admin、AGC、Desktop、Mobile、Preview Deployer、三个 `packages/*` 和 Spine validator 统一进入显式 npm workspaces;固定 `packageManager=npm@10.9.7`CI 镜像显式安装并校验同版 npm。Jenkins Web Build 不能假定系统 npm 已同步,每个独立 `bash -lc` 都必须 source `scripts/jenkins-prepare-npm-env.sh`,由该入口在 Jenkins 用户的版本隔离目录持久准备 npm `10.9.7`。仓库只提交根 `package-lock.json`,安装、CI、Jenkins 和容器缓存都只从根执行一次 `npm ci`。
- 依赖边界:每个 workspace manifest 拥有自身直接依赖,根不再为子 App 重复声明。内部私有包使用匹配版本的普通 semver `0.1.0`,由 npm 自动链接;当前 npm 不接受 `workspace:*`。npm 默认 hoist,因此依赖所有权按 manifest 和 lock 的 workspace entry 检查,不能按统一 `node_modules` 或 lock 全局包条目判断。
- 原生边界:根 H5 与 Desktop manifest 继续禁止 Tauri JS guestAGC workspace 可以声明;统一 lock 出现 AGC guest 是合法聚合结果。Expo 沿用默认 npm monorepo 支持。AGC Cubone bundle、TypeScript、Tauri CLI 与 Windows Codex sidecar 都必须兼容根提升位置,不得依赖子 App 固定 `node_modules` 层级。
- 锁与平台:删除 AGC 和 Spine 子 lock;统一根 lock 必须保留 optional、bundled 和跨平台二进制节点。Linux 干净安装不能替代 Windows AGC sidecar、Android Expo/EAS 或可用 macOS/iOS runner 的平台构建证据。
- 权威方案:`docs/technical/【技术方案】npm-workspaces统一依赖边界-2026-08-21.md`。
## 2026-08-22 完整容器 SpacetimeDB 内存上限统一为 2 GiB
- 决策:`deploy/container/docker-compose.loadtest.yml` 的 SpacetimeDB `mem_limit` 从旧压测采样值 `896m` 调整为 `2g`,与分支预览 override 一致;CPU、page pool、API、worker、Nginx 与 Collector 配额保持不变。
- 依据:当前完整模块首次 publish / init 的进程 RSS 会超过 `896m`cgroup 会直接 OOM kill `spacetimedb-standalone`,客户端表现为上传连接提前关闭,后续重试连接拒绝。提高 ping 或 publish 重试次数不能修复内存上限。
- 边界:这是本地/预发完整容器的模块实例化门槛,不修改生产服务资源合同;门禁同时锁定基础 Compose 与预览 override 均为 `2g`。
## 2026-08-22 Jenkins 预览只向 API 运行镜像内置固定运行时配置
- 决策:预览 `.env.local` 与 secrets 权威源固定为 Jenkins 宿主受控凭据目录;目录 / 文件由 Jenkins 运行账号所有且权限分别为 `0700` / `0600`,缺失、链接、非普通文件、owner 异常或权限过宽时构建失败关闭。
- 构建边界:只通过两个 BuildKit secret mount 把固定宿主副本提供给 `api-runtime` stage,并安装为 `/srv/genarrative/.env.local`、`/srv/genarrative/.env.secrets.local` (`genarrative:genarrative`, `0400`)。固定宿主副本不进 Git、build context、日志或 artifact,不进入 Web / Nginx、SpacetimeDB 或其它镜像;仓库工作区 `.env.local` 不得替代它们。容器显式运行 env 优先覆盖内置值。
- 更新与分发:任一固定源文件更新后必须重建并替换 API 与 worker 镜像,只重启容器无效。镜像可读者必然可提取内置运行时配置,因此只允许留在当前受信任内网 Docker 主机,禁止 push、`docker save` 或作为 artifact 导出到跨信任边界的 registry、主机或存储。
## 2026-08-22 AGC Tauri 命令调用可达性失败关闭
- 决策:`check-config.mjs` 的 App 调用扫描必须识别现役精确形态:裸 `invoke`、`directInvoke`、素材画布的 `invokeInput` / `invokeAuthenticatedInput` wrapper,以及对象字段 `.invoke`;不以包含 `invoke` 的任意名称、动态命令变量、注释、字符串、模板或正则文本作为可达证据。
- allowlist 边界:前端源码已调用的命令不得继续保留在 explicit native-only allowlist。allowlist 只承载确实由原生窗口或原生侧流程触发、App 源码不直接调用的 handler;源码调用与 allowlist 必须互斥。
- 门禁:逐文件使用仓库锁定的 TypeScript AST 解析,设置文件数量、单文件 / 总源码长度、命令长度和调用数量上限。回归测试同时锁定直接、wrapper、对象字段的正例与诱饵 / 动态 / 畸形输入的反例,并证明删除真实 wrapper 调用后 handler 可达性检查失败,不能由错误 allowlist 继续误绿。
## 2026-08-22 AGC 素材画布生成恢复与提交边界
- 凭据失败:`configuration-missing` / `authentication-required` 不是永久业务失败。尚无远端副作用时保留原 generation、commit 与 idempotency 身份并按 `context-preparing` / `prepared` 恢复;已有 operation 时只能进入 reconciliation。旧 `failed` 账本仅按这两个错误码和已有副作用证据白名单迁移,确定性业务失败继续终态。
- 提交边界:恢复先按项目、草稿和终态预过滤,再解析凭据;上下文准备完成后、首次可计费 POST 前必须重新校验冻结的平台账号会话。草稿一旦 cancelled,不再公开投影、下载后 staging 或提交资产,私有账本保留最后一份 durable 证据。
- staging 原子恢复:稳定 staging token 必须同时校验媒体类型、摘要、尺寸与已有文件。图片先落盘或 metadata 先落盘的同身份半提交允许补齐后幂等重放;任一身份或内容冲突失败关闭并保留现场,不覆盖残片。
## 2026-08-22 AGC 素材画布模态框焦点合同
- 素材生成确认、离开确认和旧服务身份确认沿用现有独立 modal,不在当前面板下方追加内容。modal 打开后焦点必须进入对话框,并同时隔离工具栏、画布视口、缩放/小地图、状态区和并存操作层,使背景从键盘焦点顺序与 accessibility tree 中退出。
- `Tab` / `Shift+Tab` 必须在当前 modal 内双向循环;非异步 pending 状态允许 `Escape` 安全关闭。关闭后优先恢复到原触发器,自动弹出的 modal 则回退到可操作的工具栏入口,不能把焦点遗留在已卸载节点或被隔离背景中。
## 2026-08-23 Game Agent 资源分页与精修生成并发合同
- 资源管理固定展示 `设计文档 -> 美术资源 -> 音乐音效 -> 游戏代码 -> 项目版本` 五个栏目;空栏目仍可从 Dock 打开空画布。资源卡、Dock、复位和下一页按钮上的普通滚轮继续切页,Ctrl/Meta + 滚轮继续缩放;只有显式标记为原生滚动区域的控件隔离画布 wheel。资源详情为非模态浮层,打开时背景画布 listener 保持可用。
- 资源卡在 pointerdown 即建立 capture,栏目切换、滚轮切页、排序切换和卸载统一取消拖拽并清理预览;媒体播放等交互控件不得启动坐标写入。导航 extent 按全部卡片计算 `minX/minY/maxX/maxY`,负坐标必须进入 fit bounds 并把非零原点传给 viewport 约束。
- 图片生成成功时先持久化并回读候选媒体、候选图层和公开 generation,再发布私有 `candidate-ready`。前端不得用生成返回的整份草稿 hydrate 覆盖生成期间的本地编辑;只合并候选图层和 generation 权威事实,并排在已有保存队列之后用最新本地 layers、viewport、selection 和 background 保存草稿,再在同一 FIFO 内执行独立幂等候选确认。提交、导入、生成、归档和放弃草稿等 revision-sensitive 操作必须先等待该确认屏障;重新打开含 `candidate-ready` 的权威草稿时,在开放编辑前对当前图层 ID 执行幂等确认,以覆盖候选落盘后、前端确认前退出的窗口。
- `ImageCanvasProjectPort.acknowledgeCandidateLayers`、`ImageCanvasAssetPort.importLocalImages` 与 `ImageCanvasGenerationPort.archiveFailedGeneration` 都是必选 Host Port;不支持的宿主必须返回结构化 `unsupported-capability`,共享 UI 不以方法缺失推断能力。快速编辑卡使用实测尺寸在图层上下方自动翻转,并钳制到 viewport 四边。
## 2026-08-23 AGC 素材画布失败结算使用可重放中间态
- 失败结算的 `expectedDraftRevision` CAS、结算意图和公开投影必须在同一草稿锁边界内串行化;显式失败结算和生成流程内部错误都先持久化 `failure-settlement-pending` 或 `reconciliation-settlement-pending`,不能直接跨文件发布终态。
- pending 恢复不依赖平台登录或 External API Key。恢复按当前权威草稿幂等补齐 generation 投影和 staging revision,再把私有 ledger 发布为 `failed` 或 `reconciliation-required`;公开投影已经存在时只完成账本,不重复增加草稿 revision。
- 回归必须覆盖 pending ledger 写入后、公开草稿写入前,公开草稿写入后、staging revision 写入前,以及 staging revision 写入后、终态 ledger 写入前三种重启切点。
## 2026-08-23 AGC 本地资源与 External Editor 账号绑定分离
- 权威边界:本地项目 ID、manifest asset、正式本地文件和内容摘要属于设备上的本地项目;`canvasProjectId / resourceId / assetObjectId / objectKey` 属于具体 External Editor 服务 principal。manifest 中现有远端字段继续保留生成来源,不再承担“当前账号可编辑句柄”,本轮不修改共享 manifest schema。
- 私有投影:统一在 `.agent/runtime/external-editor-bindings/` 持久化 active binding。项目 binding 按服务 origin、平台 userId 或 Developer Key 摘要以及本地 `projectId` 分区;资源 binding 再绑定远端项目、本地 asset ID、源 SHA-256、媒体类型和 canonical asset kind。sidecar 不保存 Token、Key、Cookie、Authorization、签名上传表单、媒体正文或绝对路径。
- binding 完整性与并发:lookup key 与稳定 payload 内容指纹分开校验,远端 project/folder/resource/object ID、object key 和尺寸的任一改写都必须失败关闭。同一 `本地项目 + principal` 的首次 binding 建立串行化覆盖复读、远端创建与安装,并发生成不得各自创建画布或留下孤儿项目。
- binding schema 迁移:当前 project/resource sidecar 显式为 v2 且 payload fingerprint 必填。旧 v1 文档仅能由独立 `deny_unknown_fields` wire 结构命中,在 key、principal、local project/source、remote 引用、object key、时间和尺寸等现役不变量通过后计算指纹并原子回写 v2;未知字段或身份 / source / 路径篡改均失败关闭且不回写。
- 切号语义:账号 B 打开账号 A 曾生成资源的同一本地项目时,不访问、迁移或覆盖 A 的私有画布;B 从本地正式文件在自己的远端项目重新上传、confirm、登记并形成独立 binding。切回 A 时复用 A binding。项目标题只用于远端展示,不能作为同名项目的权威关联;项目改名不换 binding。
- Runner 切号权威:Runner 协议 v7 规定 GUI owner 锁每次取得都创建新的随机 owner epoch,每次平台会话变更先推进 durable session revision claim。Runner 只能通过与该 claim 完全匹配的 `runner.attach_gui_owner` 替换会话,因此新 GUI 即使从较低 `authGeneration` 开始也能取代旧进程权威。claim 失配时立即清空 Runner 平台会话并阻断 Runtime,旧 `platform.session.install/clear` 只失败关闭而不再变更权威;GUI 与 Runner 同步失败时必须隔离或停止旧 Runner。
- renderer / native 会话提交:renderer auth generation 只负责 UI 转换和迟到读取失效,native install / clear 使用独立只增 generation 并在同一串行队列执行。登录、refresh、当前用户查询与 native commit 共用请求前冻结的 API originrefresh singleflight 按 origin 分区;入队前冻结 `user + token + origin`。候选账号只在 native 成功且仍属当前 auth generation 时提交;期望权威显式表示为 account 或 `null`。迟到 native 完成必须以更高 generation 对账回当前期望权威,对账失败清空 renderer committed 会话与 Token。
- stale refresh 决策:queued commit 的 expected generation 过期时,early return 先恢复 renderer 当前 committed / desired Token。旧账号的 refresh 失败在当前 owner 已变更时只返回 `stale`,不发送会让 `AuthenticatedClient` 清空新账号的 failed 结果,也不执行新账号 native clear。
- 在途边界:资源编辑、素材画布生成和 Agent 美术生成的 `prepared / accepted / running` 账本继续锁定发起 principal。账号切换后只能停止请求并保留原 operation 供切回或对账,不得把在途副作用迁移到新账号,也不得重建正文或重复 POST。只有尚未提交远端生成的新操作可以为当前账号建立新的项目和资源 binding。
- 在途会话复验:提交返回 `operationId` 后先持久化 accepted 身份,再在每次 poll、download 和本地 commit 前校验冻结会话;同步本地 commit 必须持有冻结会话租约,使切号与安装线性化。Tauri 手工生成入口也必须使用稳定的 durable operation slot,不能退化成临时幂等键。切号后不得继续旧账号网络或把旧账号结果安装到本地项目。
- 账本 owner 线性化:资源编辑与素材画布的 owner 绑定、服务身份指纹 / 挑战和确认写入,均在同一冻结 platform session 租约下完成,且账本已有 owner 必须精确匹配 `userId + api origin`。Developer Key 账本保持 `owner=null`;请求 / 确认挑战的直接命令调用也不具备跨 owner 写入权限。
- 恢复投影:资源恢复列表持有当前 session 租约扫描,远端账本只对精确 owner `userId + api origin` 可见;未绑 owner 的 Developer / legacy 远端账本与 owner 不完整账本均失败关闭,纯本地编辑仍可见。renderer auth generation 变更同步作废恢复 read epoch,清空列表、挑战和动作状态,迟到读取不得恢复旧账号投影。
- Direct 审计对账:图集只读恢复已完成 file + manifest 登记后,`asset.register` 追加审计一旦成功或结果未知,后续登记 / binding 失败都保留 file、manifest 和已落盘 audit,统一进入 `reconciliation-required`。禁止为了伪造原子性而回滚 manifest、删文件、重试未知审计或重新生成。
- 账本迁移:资源 canonical 身份从历史 manifest `resourceId` 收口到本地 asset ID 时,旧账本只在 source asset、路径和 SHA-256 一致时可白名单恢复;新请求仍只信任本地 canonical 身份。
- 集成边界:`resource_editor`、素材画布参考准备、`canvas.asset_generate` 的 art-spec 派生和直连只读恢复统一消费当前 principal bindingPR 176 rebase 后必须删除或整合其局部 canonical cache,不能保留第二套账号身份系统。External v1 的项目、素材目录和项目资源创建接口新增可选 `Idempotency-Key` 请求头并同步 OpenAPI;客户端用 binding key 派生稳定值,服务端按 `owner + 接口命名空间 + key` 生成稳定 ID,同键同正文返回原记录、同键异正文冲突。
- 验收:至少覆盖 A 生成到本地后切 B 重登记、B 请求零 A 远端 ID、重启后复用 B、切回 A 复用 A、两个同名本地项目隔离、项目改名不漂移、源 SHA 或 kind 变化重登记、A 在途任务切 B 零网络,以及 sidecar 零凭据和身份篡改失败关闭。
## 2026-08-23 AI 游戏运行视窗按预览文档实际尺寸自适应
- 背景:项目开发工作台的中央运行视窗尺寸小于部分生成游戏的页面布局高度时,滚动条来自 loopback iframe 内部;宿主只隐藏 overflow 会直接裁掉标题、Canvas 或控制区,不能满足完整试玩。
- 决策:客户端本地 preview server 为 UTF-8 HTML 注入固定同源尺寸桥;注入器按真实 HTML tokenizer 边界保守处理注释异常结束、DOCTYPE 引号、script escaped / double-escaped、raw-text、template、plaintext、foreign content 与重复 `src`,省略结束标签时只在已证明安全的文档位置注入。桥通过根节点 `ResizeObserver`、页面 load、窗口 resize 与字体就绪重新测量;页面可见时以 `500ms` 低频兜底探测至多 `512` 个元素边界,探测截断时不采用可能低估的部分样本,并排除随 viewport 同步变化的布局自反馈。它不订阅整页 DOM 突变,并只在尺寸元组真实变化时上报文档与浏览上下文宽高。宿主只接受当前 iframe source 与当前授权 loopback origin 的固定版本消息,按实际内容和可用容器计算最大为 `1` 的等比缩放并居中显示;宿主把最近一次合法上报的 viewport 与正式内容尺寸分开保存,首次收到自身 fit 切换产生的新 viewport 测量时只推进观察值、不反向改写 fit,viewport 稳定后的真实内容增减仍可重新适配。容器 resize 期间保留当前内容尺寸和已观察 viewport,只按新的可用空间连续重算缩放,避免拖动窗口时在原生尺寸与 fit 之间闪烁;preview URL 变化时才清空两者并重新测量。陈旧 viewport、重复内容尺寸和首次宿主回灌均不更新状态。运行视窗不再提供 iframe 横纵滚动条,内容适配不改游戏文件、manifest、PreviewRegistry 或运行业务状态,非 UTF-8 HTML 保持原样。
- 验证:前端组件测试锁定容器 resize 时 iframe 不恢复原生尺寸;纯函数覆盖无需缩放、纵向超高缩放、宿主首次应用 viewport 时保持当前 fit、容器 resize 后保持当前 fit、稳定 viewport 下内容增高 / 缩短、重复内容尺寸去重、过期 viewport 与非法消息;Rust preview server 测试锁定尺寸去重、无全页 MutationObserver、低频有界探测、截断保护、固定 body 与 viewport 耦合布局不振荡、真实 HTML 上下文注入、注释异常结束、DOCTYPE 引号、script escaped / double-escaped、raw-text / template / plaintext / foreign content、省略结束标签、大小写结束标签、重复 `src` 和幂等注入;再以 Issue #250 附件的 `min-height: 100vh` 页面在桌面最小窗口和更高窗口人工确认完整画面、无循环缩放、拖动窗口时无原生尺寸闪切、动态内容变化后仍适配、无纵向滚动条且指针 / 键盘交互仍可用。
## 2026-08-23 Direct Codex 显式重生成与切片一等资源
- 决策:`taonier_prepare_game_art` 使用 `reuse-or-create | regenerate` 两态合同;旧调用缺省复用,只有用户显式重做或换风格才允许重生成。`regenerate` 只绕过本地完整包复用,不绕过未决 External Editor operation;旧账本 prompt 与本次 prompt 不一致时必须进入结果未知/对账,零新 POST。
- 重生成前置门:`regenerate` 只要求旧规范图和背景图可下载、可解码、来源一致且有可信登记,以便完整恢复两项旧字节和 manifest entry;历史主图集、私有回执、公开清单或 canonical 切片可以缺失。八个严格路径及受管顶层 asset identity 必须按真实状态逐项冻结为 `Present/Some` 或 `Missing/None`,不能把缺失状态伪造成空内容。只有规范图或背景图缺失/无效时才提示先用 `reuse-or-create` 修复基础素材。
- 整包事务:`regenerate` 在任何付费阶段前持久化绑定意图摘要与稳定 `clientTurnId` 的客户端私有 v4 workflow;状态固定为 `resetting / in-progress / compensating / completed`,专用 `direct-codex-art` 跨进程执行锁覆盖整个付费生命周期。每个已安装阶段立即持久化旧字节、旧 manifest entry 与新结果双 CAS 锚点,后续阶段失败时可跨进程重启继续补偿,但不删除已完成阶段账本。`completed` 必须持久化有界且脱敏的完整工具结果,同一 `clientTurnId` 的完成回包丢失只等值重放该结果、零新 POST。新的显式用户回合先以 `completed -> resetting` 持久化目标身份,再清理旧账本并转为 `in-progress`,不删 workflow。只有零阶段账本、零替换锚点的孤立 `in-progress` 空壳允许新回合原子接管;未知版本、旧 v2/v3 及其余冲突全部失败关闭,旧字段不得通过 serde 缺省静默升级。
- 恢复入口:通用恢复扫描与 Direct 回合启动前置恢复都必须发现 `resetting`、`compensating` 和带替换锚点的 `in-progress`,并在专用执行锁内清阶段、补偿和中性化。补偿恢复旧文件并清除本地 replacement CAS 锚点,但保留已 `prepared / accepted` 的阶段账本、原 `Idempotency-Key / operationId`;同冻结意图续跑必须复用原请求身份,未知账本在文件 mutation 前失败关闭。冻结意图一致时,新进程 invocation 可接管未完成阶段;`completed` 以原始外层 `clientTurnId` 等值回放,不受模型 brief 重采样影响。App 在 Direct 调用前幂等持久化原始 User 消息与稳定回合 ID,Tauri 在成功返回及 `completed` 事件前以同一回合 ID 幂等持久化 assistant 终态,项目重开只续跑最近一条真正未回答的合法原始回合。
- 资源投影:工具返回主包路径、已登记切片路径、安全 `resources` 身份,并分开保留普通 warning 与 slice warning。标准核心图集首次创建和重生成都必须严格提交恰好四张 canonical 切片;alpha、可见像素、规范像素唯一、Canvas resource/asset identity 唯一任一不满足即失败。旧项目补登记与已有完整登记都必须由客户端私有回执交叉验证,不能把可编辑公开清单或顶层 manifest 中的自述身份单独升级为权威源;部分登记要么按私有回执事务补全,要么明确 warning。规范图只作 reference,不再计为运行态平台素材。
- 隐私投影:成功结果中的普通 warning 与 slice warning 也必须逐条经过宿主路径、凭据、URL 脱敏及长度限制,不能只保护错误分支。
- 权限边界:开放的是 `regenerate / registered resources / playtest` 等产品语义,不是原始最高权限。`regenerate` 只由当前请求最新一条原始 User 消息授权并绑定客户端稳定 `clientTurnId`;模型参数、MCP 自动批准和缺失 clientTurnId 都失败关闭。授权输入先对完整原文做 Unicode NFKC 与撇号规范化,随后整串必须完整匹配审核过的独立立即执行指令,只允许句号/感叹号收尾;不得剥离引号、方括号或代码片段,动作前后也不得携带 brief、条件、否定、选择、确认、费用、延迟或其它文本。复杂风格需求先单独描述,再由下一条独立确认消息授权,不能用开放式 deny 词表推断付费同意。同一进程重复水合相同 stable turn 时,“回合仍在运行”只作为非终态占用提示,不得以该 turn 的稳定 assistant messageId 持久化并覆盖原执行结果。DirectProject 的 cwd、sandbox writable root 与文件批准根只允许 canonical 且非 symlink/reparse point 的真实 `game/`canonical 项目根的原生 OS 路径字节和权威 manifest `projectId` 经域标签及独立长度前缀编码后共同绑定连接池与 thread 身份;项目根、`assets/`、`.agent/` 不可写,网络关闭,命令、MCP 扩权和额外权限批准全部拒绝。受控 `agc_tools` 只在客户端内部从同一真实 `game/` cwd 反查已校验的 canonical 项目根,不把项目根加入 Codex writable roots。Codex 不获得任意 Tauri invoke、Token/Key/Cookie`resources` 也只投影稳定身份与相对路径,不返回 prompt、provider route、URL 或绝对路径。
- Direct 恢复 claim:同一 App 实例重复水合相同 stable turn 并收到“仍在运行”时,必须释放该 `projectPath + clientTurnId` 的恢复 claim,且不得写稳定 assistant 终态。后续显式刷新对话可按原身份重新读取或续跑;不新增无界自动重试。
- 严格图集崩溃收口:workflow 在严格图集调用前先持久化 `strictSpritesheetPending` 并冻结底层严格事务覆盖的九项旧合同身份;旧路径可精确冻结为缺失。Provider 完成结果先绑定原 retained stage ledger。恢复在同一项目锁内对账严格事务;只有新九项合同、规范图/背景图替换锚点与 retained spritesheet result 三者一致才补写 `completed`,旧九项合同才允许补偿。旧合同判定、写 `compensating`、恢复两项素材与登记、回读和清锚点必须在同一项目锁内,重启已有 `compensating` 也重新判定;第三种混合、漂移或 foreign result 状态进入 reconciliation。不能在主图集与四切片已整体提交后仍按两文件 rollback 制造混合包;若中断前阶段告警尚未进入 durable completed result,恢复结果追加“原阶段告警无法完整重放”的明确 warning,不静默清空。
- Direct 对话恢复从新到旧扫描全部合法 User 回合,遇到较新已回答回合继续向前,不得丢失更早未回答回合。成功返回时 Rust 已先持久化 assistant,前端冗余 append 失败也不得重跑 Provider;普通错误终态的显式 append 失败后,恢复 claim 必须保持到 React fallback writer 对同一稳定 assistant messageId 的写入明确成功或失败,不能在 writer 尚在途时按旧 `/history` 快照重跑。fallback 成功后释放 claimfallback 失败时跳过该 writer 的无界迟到重试并释放 claim,后续显式 `/history` 才可复用原稳定 `clientTurnId`。终态收敛后删除 claim,避免长会话无界增长。
- 正式资源提交结算遵守同一顺序:阶段三 commit 成功后先持久化 `asset-commit-settlement-pending`,恢复器幂等补齐 `asset-durable-committed` 公开投影与 staging revision,再发布私有终态;公开投影已经存在时不得重复增加草稿 revision。恢复必须把私有回执与阶段三 commit ledger、transaction journal、manifest 资产和事件 payload 的完整身份绑定,任一错配都保留 pending 并失败关闭。回归同时覆盖三个 durable write cut,以及私有回执、commit ledger、journal 错配。
## 2026-08-24 AGC Direct 抠图语义工具
- 决策:将 External v1 `/api/external/v1/editor/images/background-removals` 通过 `agc_remove_background` 加入受控 `agc_tools`。工具只接受当前 manifest 的图片 `sourceLocalAssetId` 与结果名称;客户端负责正式 resourceId、画布/素材目录、稳定 operation/idempotency 身份、权限和错误脱敏,不向 Codex 暴露内部 BgFilter worker、凭据或任意 API。
- 约束:异步结果只投影有界队列状态,不允许模型自行构造源 URL 或在不确定提交后更换请求身份;External v1 负责 API Key、幂等接收与统一 operation 查询,客户端不得绕过该契约。
## 2026-08-24 资源详情动作、空态滚动与最终图多步恢复
- 角色资源详情的“生成动画”和“编辑资源”使用同一显式动作样式类,不再以 `first-child` 决定哪个业务按钮获得样式。
- 资源总览只有在至少存在一个资源、进入栏目分页画布时才挂载 paged 与 dependency 交互壳;完全空项目保留五分区展览和纵向滚动。
- viewport 按“排序模式 + 栏目”隔离保存;在“按依赖 / 按类型”之间来回切换,或离开资源管理进入运行视图后返回时,必须恢复对应组合的平移与缩放,不得通过自动点击复位或复用另一模式的 viewport 覆盖用户视角。只有该组合首次获得可测量容器尺寸或用户显式点击复位时才重新适配内容。
- 资源画布的导航范围与复位适配范围分离:导航范围继续保留最小世界尺寸和负坐标可达性;复位只按当前栏目资源卡真实包围盒计算,使用 `16px` 留白并允许在共享上限内放大,使至少一个轴贴合可用视口。
- 最终图恢复的 supersede 证明按相邻 transaction 的 after/before manifest 与 project revision 快照逐笔遍历,直到精确到达当前状态。三次及以上连续提交的早期事务不再因缺少“直连当前事务”而误报对账;任一中间账本、快照、正式文件或当前资产身份不完整时仍失败关闭。
## 2026-08-24 AGC 资源自由画板顶部移除手动新建入口
- 背景:无源视频、音效、背景音乐和 UI 设计已经有 Agent/编辑器权威生成链路,资源自由画板顶部继续并列手动新建按钮会形成第二套普通用户入口,并挤占画布级操作空间。
- 决策:资源自由画板顶部移除“生成视频”“生成音效”“生成背景音乐”和“新增 UI 设计”;保留播放、未完成编辑恢复、排序、复位,以及已有资源详情中的编辑、图片“生成动画”和精修图片“修改”。底层生成、恢复和事务能力不因入口移除而退役。
- 验证:运行项目开发工作台与资源实时集成定向测试、AGC typecheck、编码检查和 `git diff --check`,并在桌面视口确认顶部无上述四个入口且画布级动作仍可见。
## 2026-08-24 DirectProject 原生 Codex 工具解锁与薄 Runtime
- 现行边界:只在 `DirectProject` 解锁 Codex 原生文件/搜索/命令、图片查看和 Skill`ToolHost`/`DirectHome` 仍是只读、无 MCP 的被动合同。
- AGC 工具:`agc_tools` 是唯一注入的外部工具桥,负责平台美术、资源、去背景、浏览器试玩和受控搜索;不把 legacy Runtime action、durable delegation 或 `platform-agent-harness` 变成 Codex 的第二持久化权威。
- 安全:DirectProject 使用真实 `game/` writable root、`approvalPolicy=never`,原生命令网络保持关闭,联网资料继续走受控 `agc_web_search`Codex 子 Agent、Apps、插件、hooks、图片生成、Goals、Workspace Dependencies、Tool Suggestion 与未审计浏览器/电脑控制继续关闭。配置了 AGC LLM Key 或可解析的 `OPENAI_API_KEY` 登录态时,真实 provider 凭据只留在 AGC 本地代理;前者仍走已配置上游,后者只走 OpenAI 官方 API,Codex 仅获得连接级随机代理令牌。无法安全代理的 OAuth `auth.json` 继续关闭原生 shell/unified exec。app-server 使用隔离 `CODEX_HOME`shell 用 `shell_environment_policy` glob 排除 provider key、proxy、loopback bridge 和受控开关。
- 上下文:Direct 系统提示词只保留身份、cwd、边界和 Skill 索引;不再预注入项目源码快照、项目提示词或 Skill 正文。浏览器工具回传结构化事实,不强制固定三次整改循环;Codex 自行解释证据并决定是否继续。sandbox writableRoots 不提供 deny-read`.agent`/`../assets` 的不可读约束需靠行为合同和真实 smoke 验证。
## 2026-08-27 GDD 修改后历史 receipt 不得污染当前审批恢复
- 现象:GDD“修改”已成功生成下一版本且当前 pending 身份正确,但 hydrate 持续返回 `recoveryPending=true`,审批卡显示“审批状态正在恢复”。
- 原因:恢复扫描会重放全部历史 approval receipt;旧版本 receipt 仍拿当前单例 approval pending 做 identity 比对。修改后当前 pending 已属于新版本,旧 receipt 的 identity 不同是正常状态,却被误记为投影缺口。
- 决策:receipt 的 index、Markdown、audit、submit observation、session 等投影继续允许全量恢复;approval pending 只由 lineage 最新 GDD 的 receipt 读取、更新和清理。历史 receipt 不得检查或改写当前 pending,也不得因此提升 `recoveryPending`。
- 审批意见消息按 receipt 的 `rootRunId` 解析到原 Supervisor task 所属会话恢复;不会按当前 active session 重新路由。已存在于归档会话的幂等消息允许重放且不新增消息,缺失消息仍保持恢复失败,不静默写入其他会话。
- 验证:沿用现有审批恢复与 planning submit 定向测试;未新增独立测试,避免为非代表性 fixture 引入额外状态构造。
## 2026-08-27 审批修订以最新用户意见更新 GDD 决定快照
- `decisions` 表示当前 GDD 版本的决定快照,不再作为新提交必须逐项复制的 session 历史前缀。审批修订可以修改、推翻、删除或新增决定;Runtime 只校验结构、身份、CAS、版本和原型验证项双射,不做自然语言修改范围门禁。
- 新增 `answerSource=user_revision`,用于标记来自审批修改意见的当前决定,按 `round=0` 记录;`default` 仍只表示未提问的默认建议,澄清来源仍使用 `user_option` / `user_freeform`。
- planning Prompt 约束为:以当前 GDD 为基线,仅修改用户意见明确涉及的内容及保持内部一致性所必需的派生内容,未涉及内容保持不变;意见与旧决定冲突时以最新意见为准。
## 2026-08-24 AGC UI 原型桥接与自主 UI workflow
- 决策:`ui-prototype` 图片与 `UI` JSON 编辑资源保持两种正式类型。Agent 通过受控 `ui.workflow.run` 按 `prepare -> recognize -> status -> finalize` 创建页面资源、关联源图、持久化 UI State 和 manifest 阶段;`recognize` 直接复用 UI Editor 的 provider-backed 结构识别、多树合并与组件绑定命令,按 `reference-ready -> structure-ready -> merge-ready -> binding-ready` 逐阶段写入并推进项目 revision。页面可显式关联已登记图片/图标和字体,图片/图标按 5 项一批绑定,字体安全元数据进入绑定上下文且未知引用失败关闭。Runtime 回执携带 `revisionAdvanceCount`Provider 未配置、请求失败、工具调用缺失、结果不匹配、未产出可渲染组件或仍有待审节点时保留最近真实阶段,禁止用 deterministic seed 冒充语义处理完成。
- 客户端:画布点击 `ui-prototype` 先幂等桥接到 `UI` JSON,并立即刷新 manifest;关联查找按 canonical resource identity 且优先已完成 workflow 资源。全部页面完成后,工作台自动打开首个页面的 UI 编辑器 `visual-binding` 最终阶段。
- 完成门:`finalize` 必须为每个页面提供 `game/` 下真实 UTF-8 应用文件并安装当前 UI State revision 标记;缺少结构、组件、页面或标记时拒绝完成。详细输入、阶段与恢复契约见 [`docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md`](../../【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md)。
- 验证:前端 bridge 6/6、资源实时集成 19/19、AppSurface 410/410、AGC typecheck、Rust workflow 定向测试覆盖 provider 前的 reference 阶段与真实调用失败关闭、Rust bridge 1/1、编码、格式和 diff 门禁通过;认证登录与真实 Provider 生成的桌面端 E2E 尚未具备可用会话,保持未验证。
## 2026-08-25 AGC DirectProject 直连路径与 Developer Key 身份隔离
- DirectProject 的 Codex cwd 固定为真实 `game/` 目录时,原生文件工具和 patch 必须使用 cwd 相对路径(`index.html`、`style.css`、`game.js`);`game/...` 仅用于 AGC manifest、回执和客户端投影,不能作为 cwd 内原生 patch 路径,以避免 `writing outside of the project`。
- 直连 Runtime 已取得 Developer Key 时,资源编辑的 `remote_credentials` 是该操作的完整身份边界;其中冻结平台快照为空表示 Developer 模式,禁止再从进程全局 GUI 登录态补回账号快照。平台账号模式仍只使用同一组凭据捕获的快照。
- 回归覆盖 Direct 系统提示路径合同和 Developer Key / GUI 快照隔离;未触碰用户项目 `.agent` 锁、账本或凭据。
## 2026-08-26 网站与客户端共享基础组件库
- 决策:无业务 UI chrome 统一放入 `packages/shared/src/components`,通过 `@genarrative/shared/components` 导出;组件只接受 React props、原生 DOM props、短文案、图标节点和回调,不读取账号、钱包、请求客户端、store、Tauri API 或业务实体。
- 样式边界:共享样式位于 `packages/shared/src/components/styles.css`,选择器使用 `.genarrative-ui-*` 前缀并消费 `packages/shared/src/theme.css` 的 `--platform-*` token。网站与客户端各自保留页面壳、路由、业务和玩法视觉,不导入网站总 CSS;账户 DTO 适配器只从 `@genarrative/shared/components/account` 单独导出,不进入通用组件 barrel。
- 展示页:网站 `/components``/design-system` 兼容别名)展示所有公共组件的变体、状态、Token 和移动端布局,使用本地静态示例,不经过账号 Gate 或调用业务 API。
## 2026-08-27 共享组件采用 shadcn open-code 渐进迁移
- 决策:共享 Web UI 采用 shadcn 的源码归属项目模式,新增 canonical source 放入 `packages/shared/src/components/ui`,统一通过 `packages/shared/src/lib/utils.ts` 的 `cn` 与 CVA 组织变体;不引入 MUI / Ant Design 全量组件,也不一次性重写现有业务 common。
- 首步:`Button`、`Modal`、`SegmentedTabs`、`Switch`、`Input`、`Textarea`、`Badge`、`Card` 已迁移到 `components/ui` canonical source,保留 `@genarrative/shared/components` 的旧 API 作为兼容适配;Button/Input/Badge 用 CVADialog/Tabs/Switch 按需使用 RadixTailwind 4 继续消费现有 `--platform-*` token 与 `.genarrative-ui-*` 样式。原生 Select 暂不迁移。
- 边界:网站与 Tauri WebView 可消费该 DOM 源码,移动端 React Native 不导入这套组件与 CSSRadix primitive 仅在 Dialog、Tabs、Switch 等具体组件迁移时按需加入。
## 2026-08-28 共享平台 chrome 与基础语义件继续扩展
- 决策:无业务依赖的 `PlatformAsyncStatePanel`、`PlatformFilterToolbar`、`PlatformIconBadge`、`PlatformInfoBlock`、`PlatformNavigableListItem`、`PlatformRuntimeStatusToast`、`PlatformStatGrid` 实现统一收口到 `packages/shared/src/components``src/components/common` 仅保留兼容导出,展示页和部分实际业务页面开始直接消费共享 barrel。
- 基础件:`components/ui` 新增语义 `Label`、原生 `Checkbox`、`Skeleton` 和语义 `Table` 组合件;继续沿用项目自有 shadcn open-code 源码,不引入额外运行时依赖。
- 样式:筛选工具栏与可导航列表行的 hover/active/focus/disabled 反馈、移动端断点和独立宿主所需 chrome 放入共享样式;共享主题仍只消费 `--platform-*` token,不依赖网站总 CSS。
- 验证:相关共享/兼容组件 10 个测试文件共 36 个断言、类型检查、编码检查、定向 ESLint、`git diff --check` 和生产构建均通过。
## 2026-08-31 共享开关组件与业务页面直引迁移
- 决策:新增 `packages/shared/src/components/PlatformToggleRow.tsx`,统一承接白底整行 checkbox / status 开关的语义、状态胶囊和禁用态;`src/components/common/PlatformToggleRow.tsx` 仅保留兼容导出,展示页改为直接从共享 barrel 引入。
- 迁移:`Match3DResultView`、`PuzzleResultView`、`VisualNovelResultView`、`SquareHoleResultView`、`RpgCreationResultViewImpl`、`RpgCreationResultActionBar`、`RpgCreationAssetDebugPanel`、`CustomWorldCreationHub`、`BabyObjectMatchWorkspace`、`AccountModal`、`CreationAgentWorkspace` 直接消费共享 `Platform*` chrome,玩法专属资源 / 媒体 / 上传 / 弹窗组件继续留在业务层。
- 验证:共享 PlatformToggleRow 定向测试、相关前端类型检查、编码检查和 `git diff --check` 通过;未改变业务行为或后端契约。
## 2026-08-31 第二批共享组件直引
- 决策:新增 `PlatformBackActionButton` canonical 返回动作组件,统一 compact / regular 尺寸、返回图标和 platform / editorDark surface`src/components/common/PlatformBackActionButton.tsx` 仅保留兼容出口。
- 迁移:`LoginScreen`、`BindPhoneScreen`、`CustomWorldEntityCatalog` 将已有共享 `Platform*` chrome 直接从 `@genarrative/shared/components` 引入;展示页新增返回动作示例并保留整行开关示例。
- 边界:媒体、上传、资源换签、业务弹窗等带副作用组件继续留在网站业务层。
## 2026-08-28 AGC 自主构建放开编排约束
- `autonomous-game-build` 中,manifest `dependencies` 只作为上下文,不阻塞 ready;代码、设计、美术、音频和发布任务允许并行启动,child 不依赖固定回执顺序或固定 run 身份才能推进。
- 任务最终状态不再提前绑定平台画布、preview、static smoke 或发布产物检查;这些内容不参与该档位的完成判定,也不会因缺失而重置已完成任务。父 run 在任务图进入终态后直接收束并回复。
- 本档位仍沿用现有项目根和工具权限边界;本次调整只解除流程编排与平台产物验收前置,不新增第二套任务系统。
## 2026-09-02 DirectProject replay 取舍与 thread 原子判定
- DirectProject 的全量 replay、简单 `user:` / `assistant:` / `tool:` 前缀和普通 assistant partial(末尾 `unexpected interrupt happened here`)都是有意的当前产品合同:分别保证 AGC JSONL 事实源无损重建、保持 prompt 形状稳定且不引入 envelope breaking change、让模型明确知道上次输出在中断处结束。后续若调整任一项,必须先更新恢复合同与兼容策略。
- replay 与 thread 创建必须使用同一临界区结果。`turn_gate` 内的 `thread_for()` 原子返回 `(CodexThreadLease, created)`;只有 `created=true` 时才读取项目 JSONL 并构造历史 prompt,复用已有 thread 时只发送当前 user,避免并发首请求重复注入历史。
## 2026-09-03 DirectProject replay 有界滑动窗口
- DirectProject 继续以 `.agent/conversations/project.jsonl` 作为不可变、append-only 唯一事实源;窗口只生成本次恢复请求的派生 prompt,不写回 JSONL,不创建 Runtime compaction summary 或 sidecar。
- 新建/恢复 ephemeral Codex thread 时,replay 使用 `contextWindowTokens`、`autoCompactTokenLimit`、本次 `maxOutputTokens` 与 4096 安全余量计算预算,从最新记录向前选择连续完整的 `user` / `assistant` / `tool` 行;超预算旧前缀被省略,单条记录不截断,当前 user request 始终保留。
- 发生省略时在 prompt 开头加入普通 `system: Earlier conversation history was omitted due to context budget.` 提示;当前请求本身超过硬上下文预算则直接失败。该策略是 Direct 专用滑动窗口,不复用 Runtime Agent 的摘要、tail 或 session compaction 生命周期。
## 2026-09-05 共享 CanvasWorld 统一超采样与资源页迁移
- 背景:资源总览页曾在业务层重复实现 world 的渲染与尺寸逻辑,需与网站 / Tauri 共用的 `@genarrative/image-canvas-react` 统一。
- 决策:`CanvasWorld` 的公开契约为逻辑 `viewport`,最终视觉比例由 `viewport.scale` 定义。渲染细节由共享包封装,调用方不得自行换算。所有调用方继续使用共享默认正方形 `CANVAS_WORLD_SIZE = 12000`,不引入 `worldWidth/worldHeight` 等矩形 API。资源页 `navigationBounds` 只保留布局、fit、依赖线 geometry 和 data 属性用途,不再驱动 world DOM 尺寸。非缩放描边只应用于资源依赖关系线等几何 overlay,静态卡片图标不继承统一 SVG 描边规则;位图维持浏览器默认插值,不全局启用 `pixelated`。
- 影响范围:资源总览迁移为直接渲染共享 `CanvasWorld`,卡片控件通过共享 inverse-scale CSS 变量保持屏幕级尺寸;Asset Canvas、UI Editor 和依赖线继续消费相同 viewport 坐标与事件换算,不改变业务状态或持久化合同。
- 验证方式:调用方测试以预期逻辑 `{ x, y, scale }` 调用公开 helper `canvasViewportToWorldTransform`,再与实际 world 的 `style.transform` 比较;不手写渲染换算,不以缩放按钮文案替代 viewport 断言。运行共享包与 AGC 定向类型 / 测试、资源页集成测试、`npm run check:encoding` 和 `git diff --check`,覆盖 SVG 描边、拖拽坐标、关系线端点和默认 world 尺寸。
## 2026-09-07 DirectProject 用户消息由 AGC 预写并过滤 Codex 回显
- `.agent/conversations/project.jsonl` 中的 DirectProject 用户消息由 AGC 在 `turn/start` 前以 `direct-codex:{clientTurnId}:user` 幂等追加;写入失败时禁止发起 Codex turn,失败或中断也保留该 user item。
- Codex app-server 回显的 `userMessage` / `role=user` item 不是第二个历史来源。AGC 只处理其观察和关联,不再把该 echo 追加到项目历史;Codex 的 assistant、tool 和其它有效 response item 仍按现有 append-only 规则落盘。
- 本地 AGC user-item 写入必须使用允许 user item 的内部入口,Codex raw item 写入使用过滤入口,避免“过滤回显”反过来阻断预写。相同 `clientTurnId` 只能复用相同规范化 prompt,内容冲突必须失败关闭。
## 2026-08-31 AGC 错误报告与诊断上传
- AGC 采用 IDEA 风格的当前进程错误池:按 fingerprint 合并 React / window / Promise / Tauri / Agent 错误,重启后不恢复,不使用 run 或 run_id。
- 应用级日志持续写入 Tauri AppData 并滚动;报告系统完全忽略项目 `.agent/logs`、源码、prompt、配置、项目产物和截图。用户只可补充文字描述。
- 用户点击独立“报告问题”面板并确认后,批量提交当前进程事件和可取消的脱敏应用日志;失败只允许当前进程手动再次提交。
- 上传接口为登录态 `/api/error-reports`,后台新增 error-reports Tab、专用文件化诊断包、状态与受控下载;管理员查看/下载进入审计链路。
- `/bug-report` 仅作为打开该面板的快捷入口,追加简短提示,不再生成包含项目、run 或截图口径的缺陷模板。
- 2026-08-31 追加:事件 DTO 精简为 `eventId/fingerprint/source/message/stack/occurredAt/count`,提交请求携带 `submissionId` 做幂等。归档固定为 `events.jsonl`,服务端使用 `agc/error-reports/v1/{batchId}.zip` 私有 OSS key;元数据只保留 batch、用户、状态、大小、SHA-256 和 OSS key,事件正文/说明/日志从归档读取。OSS 不可用或上传失败时不写数据库,客户端可重新提交。
- 2026-09-01 追加:`application.log` 不再写结构化错误事件;Rust `app_log!` 和 WebView console 都写入普通文本 raw log,结构化事件仅保留在当前进程内,提交时才生成 ZIP 内的 `events.jsonl`。
- 2026-09-01 review 收口:错误报告修复详情请求竞态、下载 anchor 生命周期、客户端采集脱敏/指纹降级与 4xx 噪声、用户级幂等隔离、`agc` 私有 OSS 前缀越权、日志读取链接检查、ZIP 同名日志和元数据/归档清理一致性;同步在 `review.txt` 标注仍需产品/运维决定的架构项。
- 2026-09-01 追加:错误报告不依赖 api-server 本地文件、目录锁或同步文件 I/O;ZIP 只在请求内存构建后上传 OSS。管理员详情路由不属于 External OpenAPI;不存在返回 404,归档损坏返回 500。
- 2026-09-01 追加:错误报告不落本地文件;请求内存构建 ZIP 后直接上传 OSS,成功后写入 SpacetimeDB `error_report` 元数据。OSS key 固定为 `agc/error-reports/v1/{batchId}.zip`,不含日期;同一用户 `userId + submissionId` 幂等。管理员查询 DB,详情/下载按 object key 读取 OSS;每日清理先删 OSS,再删 DB,失败留待下次重试。
- 2026-09-01 review minor 修复:错误报告每日清理改为完整分页扫描,DB 删除失败显式返回以便下周期重试;OSS 读取按 `Content-Length` 与流式累计执行大小上限;管理员归档解析限制解压后 `events.jsonl` 为 24 MiB / 100 条事件。
- 2026-09-02 review breaking 项落地:错误报告 create/update procedure 删除 `user_id`、`now_micros` 输入字段,分别改用 `ctx.sender()`、`ctx.timestamp`;首个 fingerprint/source 强制 512 字符上限,备注超过 2,000 字显式拒绝;内部 OSS 对象键收紧为 `agc/error-reports/v1/`,凭据脱敏覆盖空白/分隔符变体。发布需同步 module、spacetime-client bindings 与 api-server。
- 2026-09-02 客户端错误报告提交成功与本地队列 ack 解耦:POST 成功即显示提交成功并关闭,ack 使用有限重试且失败只记录日志;同一批 eventId 在进程内复用稳定 submissionId,避免 ack 失败后重新提交产生重复报告。
- 2026-09-02 review 后续收口:错误报告创建增加服务 identity 门禁与单 identity 每小时 100 次配额;开发期将已验证登录用户 `user_id` 由 api-server 注入 procedure 并由 module 校验;module 收紧固定 OSS key、SHA-256、归档大小和计数;管理员 PATCH 返回轻量元数据并在详情弹窗提供备注编辑器;列表无过滤查询使用 `created_at` BTree 索引。
## 2026-09-01 UI 编辑器代码导出与填充预览边界
- UI 编辑器导出的 `ui/generated-*.js` 是派生本地产物。代码生成只写文件,绝不推进项目 revision、UI State revision、manifest 阶段或 Runtime 验证门;写入失败只返回生成错误,不能把生成文件写入冒充项目 mutation。
- 生成文件名保留可读清洗前缀,并追加 asset ID 的 SHA-256 摘要前缀以避免不同 ID 碰撞;不迁移既有旧路径,调用方需在采用新命名后使用新返回路径。
- Radial90 的前端预览与 Rust 导出统一使用角点映射和顺时针起始角规则,顺时针填充从角点前一条边开始,避免两端渲染偏移。
## 2026-09-03 策划会话 Runtime V2 P1 内核落地
- 新增 `PlanningSessionRuntime V2` 内核入口:`start_planning_session_v2`、`continue_planning_session_v2`、`hydrate_planning_session_v2`。P1 只负责单 Agent 会话生命周期、Provider 流式/普通调用、消息持久化、回合幂等、单项目单活跃回合、耗时和失败恢复,不接入 GDD 解析、审批或旧 Supervisor。
- V2 会话快照落在 `.agent/planning-v2/session.json`,完整消息落在 `.agent/planning-v2/conversation.jsonl`。同一 `clientTurnId` 已有成功 assistant 记录时直接重放;若只有 error 记录则允许沿用同一用户意图重试,不重复追加用户消息。进程退出后 hydrate 发现 `planning` 会投影为 `provider_failed/RECOVERY_REQUIRED`,不伪造成功或自动重试。
- Provider 调用复用现有 `platform-llm` 的 API-kind、流式解析、超时和重试配置;当前策略快照固定 `mode=gdd`、`questionLimit=8`、`tools=[]`、`skills=[]`。GDD 业务规则留给 P2,生产 UI 接入留给 P3。
## 2026-09-03 策划会话 Runtime V2 P2 产物闭环
- V2 新增 `GddPlanningPolicy`Provider 输出只接受合法 question 或完整 GDD JSONquestion 在 `questionCount < questionLimit` 时保存并展示,第 8 个问题仍可展示,第 8 个之后再次返回 question 时只允许一次内部强制出稿重试,额外 question 不落盘、不进入用户等待态。
- V2 GDD 使用独立 `plan-gdd.v2`,只保留项目身份、版本、游戏内容、决定、原型验证项和指纹,不带旧 Supervisor/Run/delegation 字段。每个版本写入 `.agent/planning-v2/gdd.vN.json`,同步更新 V2 index 和 `game/fast_gdd.md`;旧版本不可覆盖。
- 新增 `decide_planning_artifact_v2` 与 `plan-approval.v2`。批准、修改、退回均绑定当前 Session、artifact、version 和 fingerprint;修改/退回必须有意见,修改后 Session 回到 `revision_requested`,下一轮由同一 V2 Session 继续。`answerSource` 缺失或未知值回退,不成为单独阻断项。
## 2026-09-03 策划会话 Runtime V2 P3 入口与 UI 接入开始
- 正式 AGC“做方案”入口在 `planningStartMode` 下直接调用 `start_planning_session_v2`、`continue_planning_session_v2` 和 `decide_planning_artifact_v2`,不再为新策划回合创建 Supervisor root、child Run 或 delegation。
- 前端以适配层复用现有聊天区、澄清输入卡、GDD 审批卡和阶段进度条;V2 hydrate 返回 `conversation` 消息,用于页面刷新和重启后恢复可见历史。
- V2 会话使用独立 `planning-session-v2-stream` 事件,旧 Supervisor Runtime 轮询、专业 Agent 轮询和旧 Runtime 事件不会介入 V2 会话。
- 没有 V2 authority 的项目仍按旧读取路径打开;旧链路封存、未完成旧会话强制失败和旧入口彻底关闭仍留在 P5。
## 2026-09-05 策划会话 Runtime V2 P4 收口
- Planning V2 的 P4 灰度与回归验收已通过人工校验:正常提问/回答/GDD/批准链路、GDD 修改后再次批准链路、Provider 失败恢复、非法输出失败边界、重启恢复、前端工作台展示以及编码/差异门禁均完成验证。
- P4 收口不代表旧链路退役;旧 `project-supervisor-plan` / `project-planning` 源码和入口仍保留,旧活跃会话封存、迟到结果隔离、旧入口关闭和只读历史展示统一留在后续 P5。
- P5 收缩为最小退役:旧 `project-supervisor-plan` caller 统一立即返回退役错误,不启动旧 Runtime/Provider;不扩展 V1 `PlanSessionV1` schema,不做旧数据迁移,旧文件继续保留只读,V2 只读取 `.agent/planning-v2`。
## 2026-09-05 修正 Planning V2 流式交互契约
- Planning V2 不要求把 Provider 的文本 stream delta 逐条投影为用户可见对话。V2 的用户交互是结构化工具调用结果:`plan_ask_question` 渲染澄清选项卡,`plan_submit_gdd` 渲染 GDD 输出和审批卡;中间纯文本不是用户对话内容。
- `planning-session-v2-stream` 若继续存在,只能作为内部状态/兼容事件能力,不构成实时逐 delta 的功能契约;Provider 是否使用流式传输不影响 V2 的业务验收。
- 文档措辞约束:凡出现“流式响应”“流式事件”或 `text_delta`,均须注明其属于 Provider adapter/Runtime 内部实现能力;不得将其描述为前端必须逐条接收的用户可见消息。V2 的唯一用户交互结果是 `plan_ask_question` 和 `plan_submit_gdd` 的结构化工具结果,Provider 完成前是否产生多个 delta 不参与验收。
## 2026-08-29 AGC 官方 LLM 代理与 Windows 私有路径修复
## 2026-08-29 AGC 官方 LLM 代理与 Windows 私有路径修复
- AGC 正式发行版不再让用户配置 Provider、Base URL 或 API Key。客户端只携带登录 access token 调用 `api-server``api-server` 按 access token 的 owner 查询 `llm_router_account`,解密服务端密文后调用固定 `https://router.genarrative.world/v1`。模型目录由后台 owner 管理并持久化到 `agc_model_catalog`,客户端仅显示别名,在对话框右下角选择稳定标识,服务端映射实际模型名;设置页不承载模型选择或方案管理。真实 Router Key 不进入聊天、manifest、trace、日志、项目文件、Codex argv/环境变量或普通 IPC payload。
- 注册成功后视为账号已有余额;当前不实现真实扣费,LLM 代理在 Router 成功返回后再记泥点,扣费失败只记录日志,不影响已经成功的响应。注册入口和首次 LLM 请求都会幂等确保账号 Router Keyapi-server 只通过 New API 管理员 Token 执行正式“创建用户 → 查询用户 ID → 设置分组 → 登录 → 创建无限额度 token → 签发 API Key”流程并加密落库,不再生成或使用任何 Router fallback token。远端结果不确定时写入 reconciliation 标记;本地签发落库失败可安全重试,不制造第二个账号。客户端不会退回手工 Key;真实管理员 Token、注册和生产 Router 联通仍待受控部署 smoke。
- `external_api_key` 继续复用一次性明文返回和 hash/prefix 元数据链路,仅承载普通外部 OpenAPI/MCP Key。LLM Router 的 `llm-router` 用途、`llm:responses` scope、加密密文、Router account id 和固定路由元数据统一保存在 `llm_router_account`。登出、切换账号或服务器只清理进程内 access token/Provider Proxy,不删除其它账号或设备的本地/远端 Key;Router 确定返回 401/403 时标记当前账号 Key revoked。
- 后台只允许管理员通过专用 API Key 查询接口按 owner、公开用户编号、keyId、精确 prefix、名称、时间、状态和 purpose 筛选;未给出 owner/keyId/prefix 时拒绝无界扫描,永不返回 `key_hash`、密文或原始表行。通用 `external_api_key` 表浏览被拒绝。
- Windows 私有路径严格拒绝 reparse/symlink、非普通对象、路径类型冲突和候选路径冲突。只有本次调用新建的目录/临时文件可在普通进程内初始化 owner;owner 已正确但仅继承 ACL 不合规时,正式 prepare 入口先完成归属校验,再通过当前用户私有、禁止继承、单一 ACE 的 DACL 收紧。用户通过原生选择器明确选中的项目根/文件,或 AGC managed 路径,在发现 owner/DACL 权限不足时由一次性 UAC helper 将普通对象接管为当前 TokenUser 并复核;取消/失败保持失败关闭,未经过正式选择或项目根入口的内部路径不得触发任意提权。
- 规划、Runtime sidecar、UI workflow、资源桥、Skill 隔离目录和图片读取统一经过私有路径准备,并在原子写入后复核类型、owner/DACL 与文件身份。真实 Windows UAC、foreign owner 修复、继承 DACL 收紧、注册后 Router 签发和生产 `/v1/responses` 联通仍需在受控实机/部署环境验证。
## 2026-08-31 LLM Router 独立账号与后置扣费修订
- 每个 Genarrative 用户在认证成功后都必须幂等准备独立 Router 账号:api-server 使用管理员 Token 创建随机密码普通用户,查询用户 ID,设置用户 `group=taonier`,登录、创建或复用固定标识 `agc_auto_generate` 的无限额度 TokenToken/API Key 使用 `default` 分组;发现旧 Token 为其它分组时先更新为 `default`)并签发 API Key。Router 账号用户名、随机密码、access token(如需)和 API Key 作为一个服务端加密 bundle 保存到 `llm_router_account.credential_ciphertext`,脱敏账号信息和 API Key 核心字段保存到 `llm_router_account`;客户端和普通用户永远不可见 Router Key。管理员 Token 仅存在 api-server 私有配置,不写入数据库或日志;Router 凭据只来源于这条正式账号流程。
- 该账号 provisioning 使用持久 saga 状态:远端注册、登录、token 或 Key 签发结果不确定时进入 `unknown` / `reconciliation_required`,禁止重复注册;远端 Key 已确定签发但本地 `llm_router_account` 写入失败时保持 `key_issued`,后续使用确定 key id 重试落库。Router 确定返回 401/403 时撤销当前 Key 并把账号状态置为 `retryable`,复用已保存的账号密码重新签发替代 Key。
- AGC 调用固定为客户端 access token -> api-server -> Router。计费读取账号 `used_quota`,每 50000 quota 扣 1 泥点,美元数值乘 10、不乘汇率。首次模型调用前以当前累计额度完整建立免追扣基线,之后调用前后同步;扣钱包、写 `llm_router_consume` 流水与推进已结算额度同事务完成。小数和余额不足未支付部分继续累计,失败或重复同步不推进已结算额度,不使用本地 WAL 或余数队列。完整合同见 `docs/technical/【技术方案】LLM累计额度结算-2026-09-05.md`。
- AGC 状态面收口:Tauri `check_game_creator_llm_config`、`/llm-status` 与 `/llm-routes` 只返回账号凭据状态、官方路由锁定状态和运行参数;不序列化 Router 地址、模型、协议名或任何密钥/凭据字段,内部固定路由仅留在运行时配置与服务端代理中。
## 2026-09-01 LLM Router provisioning 环境隔离与测试门禁
- api-server 读取 `GENARRATIVE_ENV`;只有 `production` 才允许固定官方 Router 控制面,生产缺管理员 Token 在启动时告警并在新账号 provisioning 时拒绝。`development`、`test`、`container` 等非生产环境默认只允许 loopback Router,避免测试调用线上用户服务创建真实账号、冲突用户名或消耗额度。
- 删除进程级 Router fallback Key 语义。测试如需模拟已完成账号,只能注入显式 owner-scoped、loopback 的“已 provisioning”fixture;没有 fixture 必须走 `external_api_key` / `llm_router_account` 查询与正式 provisioning,不能直接访问上游。新生成的 pending 凭据不再被误判为可复用远端账号。
## 2026-09-01 LLM Router 公共实例与独立数据库的稳定账号恢复
- Router 为公共实例、各部署数据库独立时,Router 用户密码改为由固定版本 provisioning secret、Router 控制面 origin 和 owner 稳定推导;所有能操作同一 Router 的部署必须使用同一 secret。该 secret 当前按临时过渡方案固定在 api-server 服务端实现,客户端、数据库明文、日志和普通请求不接触;后续再迁移到部署密钥管理并保留 `credential_version`。
- 数据库没有本地 `llm_router_account` / `external_api_key` 行时,先用稳定用户名/密码查询并登录远端账号;只有确认用户不存在才注册。登录成功后先按固定名称查询 Router token,存在则复用,不存在才创建,避免不同部署因本地数据库为空而重复创建远端账号或 token。注册返回冲突时必须重新查询,不得盲目重试。
## 2026-09-01 Router 账号可恢复标识与跨开发库复用
- Router 用户名固定为 `agc_user_` 加 11 位 URL-safe SHA-256 短码,短码由完整 owner `user_id` 稳定派生,以满足 New API `username` 20 字符上限;完整 owner `user_id` 同步写入 New API 用户 `remark`Router 统计可据此直接回溯对应的 Genarrative 用户,即使某个部署的本地数据库被重置。
- Router 用户密码只由 owner user id 与固定版本 provisioning secret 稳定派生,不绑定 route origin;所有连接同一公共 Router 的开发/生产部署都能计算同一密码。
- Router 用户下用于 AGC 的 Token 固定标识为 `agc_auto_generate`。各部署登录后先按该标识查询并复用 Token,再通过 Token Key 接口取得同一把 API Key;不存在时才创建 Token,避免每次 provisioning 新建 Key。
## 2026-09-01 Router 订阅按账号准备续期
- 每次 api-server 准备或复用用户 Router API Key 时,在账号登录成功后、Token/API Key 流程继续前查询 New API 管理订阅接口。固定套餐为 `plan_id=1`:没有 active 订阅、订阅已过期或 `end_time - now <= 24h` 时创建一条管理员订阅;剩余超过 24 小时则复用现有订阅。`end_time` 按 New API 合同解释为 Unix 秒。
- 检查锚点固定为认证后的账号准备、显式 Router Key 准备和 LLM 请求解析凭据路径,不放入 Responses 流式 chunk;同一 api-server 进程继续复用 owner 级 provisioning mutex,跨实例重复订阅幂等性依赖 Router 端后续约束或部署侧串行化。
- 订阅查询/创建只使用 api-server 私有管理员 Token;管理员 Token 缺失时保留启动 Warning 并跳过续期检查,不能伪造客户端凭据或把 Router Key 暴露给客户端。
## 2026-09-02 LLM Router 零泥点前置门禁
- AGC LLM 对话入口在解析 Router 凭据和访问上游前先读取用户 `wallet_balance`。余额为 `0` 时直接返回 `409 MUD_POINTS_INSUFFICIENT`,客户端显示“泥点余额不足”;不创建、续期或使用 Router 账号。余额读取失败同样失败关闭,返回“泥点余额暂时不可用”。
- 余额大于 `0` 的请求继续走 Router,成功后仍按 best-effort 后置结算;退款占用、冻结或扣费时余额不足的处理继续由钱包事务和既有结算规则负责。
## 2026-08-29 DirectProject 受控联网搜索默认与边界
- 正式产品本次只覆盖 `DirectProject` 单 Codex Agent。`Provider`、`ToolHost`、`DirectHome` 不是 Agent,也不是本次联网主链路;不新增全路由联网或工具桥。唯一受控联网工具为 `agc_tools.agc_web_search`,链路固定为 Codex MCP 工具目录 -> 客户端 loopback `DirectToolBridge` -> 有界 Bing RSS HTTPS -> 过滤 / 脱敏 -> MCP 结果回传。
- Codex app-server 的 `web_search=\"disabled\"` 安全校验保持不变。`llm.webSearchEnabled` 在 DirectProject 只控制受控 AGC 工具暴露与执行;状态面必须同时显示受控联网状态和“Codex 原生 web_search 关闭”,不能混称为 Provider 原生搜索。
- 配置契约提升为 `game-creator-config.v2`:新模板默认 `stream=true`、受控搜索开启;无版本旧 DirectProject 配置仅在省略 `webSearchEnabled` 时把历史默认补为开启,旧配置显式 `false` 不覆盖,v2 显式 `false` 同样保留;Provider / Anthropic 未提供搜索覆盖时保持关闭。本地覆盖配置只补 schema 版本,不凭不完整 overlay 推断或写入 `agentMode` / 搜索布尔值;未知版本失败关闭。
- 搜索结果始终是不可信外部输入,只可作为资料;工具 schema、参数、客户端权限、Agent 身份、系统规则和工具协议不得由网页内容修改。搜索结果与状态投影不携带 API Key、请求头、宿主绝对路径或 Provider 原始错误正文。
- 验证锁定:`configuration`、`direct_tools_mcp`、`direct_tool_bridge`、`codex_app_server` 定向 Rust 测试,以及前端状态格式化 / AppSurface 测试;真实 Provider 登录态与真实公网搜索仍需单独现场 smoke。
## 2026-09-08 策划 V1 链路源码整体退役(四不写)
- 策划会话 Runtime V2 全量接管“做方案”入口后,旧 V1 链路(`project-supervisor-plan` 根 Run、`project-planning` 子 Agent、`plan.submit_gdd` 工具、Fast GDD 审批门禁与恢复车道)按四不写原则整体删除源码,不保留兼容实现、墓碑注释或防御性测试。
- 删除范围:`runtime_protocol` 六个 V1 模块与四条 V1 提示词、prompt manifest planning 目录与生成常量、`capabilities.planning` 配置开关、CLI `--swarm-chat --plan` 与 `--plan-gdd-status/--plan-gdd-decide`、provider_retry 的 planning session binding、provider_action_batch 的 V1 提交批次校验、main_loop/pending_recovery/recovery_scan/acceptance_graph 的 plan 根特判、agent_db 三条 V1 专用持久车道(`agent.runtime.plan.provider_usage`、`agent.runtime.plan.gdd_decided`、`agent.runtime.plan_submit_gdd.committed`)及预留配额、前端旧 V1 读模型、`scripts/agent-swarm-test-chat.mjs --plan` 与 `test:plan*` 脚本。
- 保留项:V2 与 V1 共用的 GDD 数据模型收敛到 `runtime_protocol/planning_gdd_model.rs``.agent/planning/` 旧文件不迁移、不删除,旧 `fast_gdd.md` 仍可只读打开;前端 `PROJECT_SUPERVISOR_PLAN_SOURCE` 字符串与 `GddApprovalCard.tsx``PlanGddSurface`)是 V2 现役的适配/展示面,不属于退役对象。
- 旧 sidecar 中带已删字段(`clarification`、`continuation`、`requestId`、`turnId`、`pendingApproval` 等)的记录会因 `deny_unknown_fields` 拒绝反序列化,这是退役语义的一部分,不做迁移。
- 测试基线说明:收尾时测试套件存在 25 个既有失败(mock LLM 时序敏感类,HEAD worktree 对照验证与本次无关),后续清理时不要误记到本次退役头上。
## 2026-09-08 Planning V2 正常 run 优先的恢复旁路
- 恢复只利用已经落盘的合法 question、GDD 和 approval receipt;不调用 Provider、不要求模型额外输出恢复字段、不设置复杂状态机或自动重试。
- 正常 run 进行时不读取或写入其 Planning V2 文件,也不增加文件锁或等待;无活跃 run 时恢复只做一次非阻塞锁尝试,竞争即退出,交给既有轮询。
- question 恢复为成功结果,approval 重放复用 receipt 原始 decisionId。
## 2026-09-08 Planning V2 用户输入与异步投影身份
- 问题回答携带被回答卡片的 questionId,在已有回合锁内核对 Session 和当前问题;自由文本回答同样绑定问题,已完成回合保留幂等重放。此身份匹配服务于用户提交,不增加恢复门禁或模型输出要求。
- hydrate 结果(包括空结果)写入前端状态前同时核对请求序列和当前项目路径;过期结果直接丢弃,不重试、不阻塞正常 run。
## 2026-09-09 项目写锁残留回收与启动诊断
- 决策:`.agent/project.lock` 记录 `processStartedAt`PID 存活时用启动身份区分“原持有者仍在”与“PID 被复用”,身份不一致才回收。旧锁无该字段时用“进程启动时间晚于锁 `createdAt` + 5 秒容差”推断,锁创建时间未知时不做该推断。空锁 / 坏锁(崩溃停在 `create_new` 与落盘之间)宽限期 30 秒,无法判定存活时保持 600 秒,mtime 不可读时按未知年龄不回收。活持有者始终不回收。
- 决策:回收判据与删除基于同一次读到的锁文件快照——payload 只解析一次,删除前重新核对字节,只有内容仍是判定时的内容才 unlink;文件已消失或被替换时重试 `create_new`,不把并发回收当成错误。
- 决策:启动诊断日志用 `StartupLogSlot`,优先用已生效的配置目录(含 `--config-dir`),否则退到平台配置根(Windows APPDATA、macOS Application Support、其它平台 `XDG_CONFIG_HOME` / `~/.config`),配置目录就绪后再切换;`startup.*.failed` 与 `show_startup_error_dialog` 必须可达,日志路径未知时也必须给出用户可见提示;Windows 启动失败弹系统消息框,其它平台写 stderr。
- 边界:`agent-runner.lock` / `agent-runner.gui-owner.lock` 是 OS 独占句柄锁,进程退出即释放,残留文件不阻塞下次启动;不要把它们当成项目写锁的同类残留处理。
- 边界:锁文件里的 PID 若超出平台进程号空间(Unix `pid_t` 是有符号 32 位、Windows 是 32 位,均恒大于 0),它不可能属于任何活进程,按“持有者不存在”直接回收,不再落回 600 秒保守分支。
- 验证:`project_lock_recovery` 11 条与 `diagnostic_log` 7 条定向测试通过,真实二进制双实例复现“第二个实例写 `startup.runner.owner-lock.failed` 并弹出可见提示”。
## 2026-09-10 Direct 写通道纳入统一项目锁等待窗口并补齐持锁方可诊断
- 背景:Issue #318。`agc_write_file` 是用户直接触发、失败即整轮无法落盘的项目写入通道,却用零等待取锁,任何重叠都在 24-42ms 内被投影成“项目正在被其他写操作占用”;同一形状已在 2026-07-22 由 `file.write / file.patch / file.delete` 用有界等待修过,本项目技术方案的 2026-08-13 一节也已规定这类争用结果“统一投影为争用并进入既有有界等待”。现场取证还缺 `commandId / pid / createdAt / ownerIsSelf`,无法回答“谁在持锁”,加上 `create_new` 把 ACL 拒绝、delete-pending 和真实跨进程争用压成同一句话,排障被引向“残留锁”。
- 决策:① Direct 写路径改用 `acquire_game_creator_agent_runtime_project_write_lock_with_wait`,与其它写入口同语义,同一轮并行写按同一把锁串行。①′ 这条等待是同步轮询(最多约 10 秒),handler 是 async,因此写路径经 `spawn_blocking` 走阻塞线程池:直接在 handler 里同步等待会占住 tokio worker,争用窗口内并行写多个文件时会波及共享同一 runtime 的只读端点与 UI 命令,破坏 Issue #318 现场“只读工具全部正常”的诊断特征。② 争用错误前缀逐字不变并追加持锁方身份;`create_new` 失败拆成可重试(进入有界等待)/ 权限拒绝(失败关闭,文案不含争用前缀)/ 其它三类,**重试性只看错误码**Windows 的 `ACCESS_DENIED(5)` 与删除拆链窗口在错误码上不可区分,一律先按可重试处理,等满预算且目标仍不存在时才由 `exhausted_projection` 改判成权限拒绝(单次试探不改判);`sharing violation(32)` 与 `lock violation(33)` 恒定归可重试。③ 等待预算耗尽时按 `project.write_lock.wait_exhausted` 记录持锁方快照、等待时长与 `projection=`contention / permission_denied),Unix 上明确判定的权限拒绝按 `project.write_lock.permission_denied` 记录;这条日志与终态改判都只在真的等过(`max_attempts > 1`)时发生,hydrate 的单次试探既不写日志也不改判。④ 重试与否改由类型决定:`acquire_project_write_lock_failure` 返回 `ProjectWriteLockFailure::{Retryable, Terminal}`,有界等待按 `is_retryable()` 分流,`acquire_project_write_lock` 只是它的文案包装;`PROJECT_WRITE_LOCK_CONTENTION_PREFIX` 同时收口 `provider_recovery.rs` / `planning_session_v2.rs` / `direct_runtime.rs` 三处手写文案。⑤ 平台判据的每一环都要带平台:终态改判也曾只比 `ErrorKind`,而 errno 5 在 Windows 是 `ACCESS_DENIED`、在 Linux 是 `EIO`CI 直接把它判成 `contention`;现由 `Retryable { platform, path, source }` 携带平台。
- 为什么不按“目标是否存在”当场分类:目标被删除时目录项先消失、删除挂起随后才结束,`create_new` 会在这个拆链窗口里返回 `ACCESS_DENIED(5)` 而 `exists()` 已经报 false(本机 6 万次建锁 / 删锁竞争实测 396-538 例命中该组合)。按一次 metadata 观察判成权限拒绝,等待层会立刻失败关闭,等于把 Issue #318 的“毫秒级直接失败”换成更误导的 ACL 文案;这也是对 2026-08-13 已定口径“这些结果统一投影为争用并进入既有有界等待”的回归。
- 复用既有实现:回收判据沿用 2026-09-09 的 `ProjectWriteLockSnapshot` + `project_write_lock_reclaim_decision` + 字节 CAS 删除,不新增第二套回收机制;本次只给快照补 `commandId` 与 `describe_holder()`,供错误文案和日志使用。
- 不做什么:不放宽 `.agent/project.lock` 的项目级串行化语义,不引入可重入项目锁,不改“同一调用链禁止二次获取 `.agent/project.lock`”的既有约定,不改 AGC 多进程拓扑,不改 `pendingOperations` 语义,也不改“活持有者始终不回收”的既有判据。其它仍用零等待取锁的入口(`command.exec / project.verify / memory / conversation / task / checkpoint / 预览 / UI 编辑器 / 资源编辑器 / Tauri 命令`)不在本次范围。
- 验证方式:`project_lock_recovery` 追加持锁方身份与权限拒绝两条;`direct_tool_bridge` 追加同进程重叠写等待、同轮并行写、有界等待不占 runtime worker(默认 `current_thread` runtime 加心跳任务,同步阻塞会立刻让心跳停摆)、ACL 拒绝不投影成争用四条;`project/write_lock` 追加“重试性只由错误码决定”与“终态改判三条件”两条平台无关用例(平台作参数传入,Linux CI 覆盖 Windows 分支)。Windows 本机定向结果:`project_write_lock` 18 条、`bridge_write_file` 4 条、`parent_wake` 16 条、`waits_across` 3 条全过;CI`71e9ad313`)四个 job 全绿,其中 Native shell tests 的首轮失败正是第 ⑤ 条平台判据缺陷。
- 关联文档:`docs/project-memory/shared-memory/pitfalls.md`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`、Issue #318。