合并最新master到M0A-2开发分支
Project CI / Repository checks (pull_request) Successful in 1m21s
Project CI / Frontend tests (pull_request) Successful in 3m5s
Project CI / Backend tests (pull_request) Successful in 4m10s
Project CI / Native shell tests (pull_request) Failing after 10m35s

同步 origin/master 至 3c457feb4c

保留 M0 固定 owner 验证、本人 smoke、试玩执行者与动态 Canvas 谱系合同

通过 114 项完成合同回归、动态 Canvas 精确回归及格式编码检查
This commit is contained in:
2026-08-11 09:12:10 +00:00
103 changed files with 7551 additions and 1455 deletions
@@ -6922,3 +6922,55 @@
- 一致性:`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 完美像素最终持久化逻辑分辨率 PNG
- 背景:SpriteFusion Pixel Snapper 上游在网格采样后直接输出 `(列切线数 - 1) × (行切线数 - 1)` 的逻辑图,每个检测单元恰好对应一个输出像素。Genarrative 首版在此后增加了 nearest 输入尺寸恢复;当输入宽高不能被逻辑列数、行数整除时,最近邻只能把同一逻辑像素分配到 `floor / ceil` 数量不等的目标列或行,最终物理像素块宽窄不一。已有 `128 → 64` 的整数倍测试没有覆盖该问题。
- 决策:生成请求勾选 `style="pixelArt"` 与已有图片手动 `POST /api/editor/images/pixel-art-snaps` 共用同一输出语义:snapper 直接编码并持久化唯一的逻辑分辨率 PNG,不再 nearest 恢复到源图、RGBA 输入、业务交付或 generation dialog 占位尺寸。成功输出宽高固定为 `(columns.len() - 1) × (rows.len() - 1)`,允许与输入、交付和占位尺寸不同;响应、project resource、账号素材和结果 layer 一律记录最终 PNG 的实际宽高。
- 保留边界:普通图片和角色在规整前执行的 Lanczos 交付尺寸归一继续保留;角色 / 图标的平底网格分析源与透明 RGBA 采样源仍必须同尺寸,Alpha 覆盖、Alpha 加权 RGB、二值 Alpha、P30 步长估算、确定性采样、输入上限、deadline、strict 无网格拒绝和失败降级尺寸守卫全部不变。这些约束保护输入坐标系、资源安全或失败路径,不构成成功输出与输入同尺寸的承诺。
- 持久化边界:数量增量保持不变。普通图片只保存一张最终逻辑主图;角色保留一张 provider 原图与一张最终透明逻辑主图;图标保留一张 provider 原图、一张最终透明逻辑图集和原有成功切片;手动完美像素只保存一张最终逻辑 PNG。不得另外保存输入尺寸恢复版、像素化前后双份主图、预览、诊断或报告,不修改 asset kind、队列类型、数据库 schema、路由或请求 / 响应字段形状。
- 跨版本重放:手动入口算法指纹升为 `perfect-pixel-v2`。同一稳定 operation 已有结果时,candidate object key 相同才继续既有 exact replay;key 不同或既有稳定资源缺 key 时,必须在 preflight 与 OSS PUT 前返回 `409 + operationResultAlreadyExists=true`,由客户端 GET 权威项目收口,不得冒充本次请求已经设置 `resultPersistenceStarted`。preflight 到最终提交之间仍无数据库 reservation,滚动发布必须排空旧算法实例,不能把该护栏解释为消除了并发 TOCTOU。
- 历史边界:本条覆盖 2026-07-28 首发决策中“逻辑结果 nearest 恢复交付尺寸 / 逻辑图不持久化”和 2026-07-30 手动入口中“右侧新增同尺寸 PNG / 不保存逻辑低分辨率图”的旧口径;旧条目作为历史记录保留,不回写改造。
- 关联文档:`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`
## 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 被强杀后遗留带凭据孤儿进程。
- 兼容迁移:已有 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 AGC 普通 release 固定连接 dev API
- 决策:普通 AGC Tauri release 的平台认证与客户端 API 固定访问 `https://dev.genarrative.world/api/*`;本地 `npm run agc` 继续使用 Vite `/api` 代理,普通网页构建继续使用同源相对路径,game-chat release 继续绕过平台登录。
- 传输边界: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 项目
- 项目根决策:项目首页与项目组提供同一“打开 Godot 项目”能力;被选目录需包含普通文件 `project.godot`,选中目录本身即为文件工具、命令与 Agent Runtime 的唯一项目根,不复制工程或建立第二套工作区。
- 元数据决策:首次导入只创建并保留项目根下的 `.agent/manifest.json``.agent/agent.db``.agent/logs/``.agent/runtime/`;不得创建默认 Web 原型的 `game/``assets/``memory/``exports/`。已有有效 `.agent` 项目只读复用身份,不覆盖 manifest。
- Windows 锁文件决策:提升权限进程新建 `.agent/.manifest.json.lock` 时,Windows 可能把 owner 设为 `Administrators`。仅在固定锁路径已取得不共享独占句柄并确认是普通、非 reparse、单链接文件后,才初始化为当前 `TokenUser`;随后再次复核句柄并执行原有 owner/DACL 校验,不放宽既有异常对象的安全规则。
- Provider Schema 决策:`agent.route_manifest.missingAssetSlots` 不再广告 OpenAI-compatible 代理拒绝的 `uniqueItems`Runtime 继续排序去重,Schema 子集门禁新增该关键字,真实 Provider smoke 必须在发布前证明工具目录可被接受。
- 运行决策:Godot 项目提交给 Project Supervisor 时使用 `standard` Run Profile,避免触发 Web 专用 `game/index.html`、HTTP preview 与自主 Web 完成门。Godot 编辑器启动和内嵌运行预览不在本切片范围。
+65 -1
View File
@@ -147,6 +147,23 @@
- 验证:用 deferred graph Promise 断言终态前 Tauri layout read/update 调用均为 0;图就绪后首次坐标直接按最终深度生成,旧 scope 迟到结果无效,手动坐标不变且相同自动布局不增加 revision。
- 关联:`apps/ai-game-creator-shell/src/view/project-development/index.tsx``apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCanvasLayout.ts``apps/ai-game-creator-shell/src-tauri/src/project/resource_dependency_graph.rs`
## 等价 Runtime 投影刷新不能清空资源依赖图(2026-08-10)
- 现象:专业 Agent 运行期间,资源画布中的卡片按轮询节奏整批消失并立即恢复;停止产生新的 Runtime 时间戳后闪烁减弱或消失。
- 原因:专业 Agent 轮询会重建结果数组;资源内容虽然相同,前端图读取 effect 仍因数组引用变化重新执行,并先把图和布局置空。布局位置暂时缺失时,所有资源卡都会返回 `null`
- 处理:用包含项目与资源输入的语义 scope key 稳定图请求参数,等价输入不重复读取;同一项目的资源集合确实变化时,异步刷新期间保留上一个已解析图和布局,只有首次加载或切换项目才启用空图屏障。刷新失败继续展示旧快照,不能用瞬态失败清空画布。
- 验证:AppSurface 先用全新但内容相同的 Agent 结果数组 rerender,断言图读取仍只有一次且原卡片 DOM 保持连接;再增加真实资源并延迟第二次图响应,断言旧卡片在刷新窗口持续挂载,新图返回后新增卡片正常出现。
- 关联:`apps/ai-game-creator-shell/src/view/project-development/index.tsx``apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`
## Supervisor steer 不能只有内部排队事件(2026-08-10)
- 现象:自主制作期间继续向项目总控发消息,用户消息已进入同一 Run,当前 Provider 也被中断并重新规划,但普通工作台短暂的提交状态消失后一直没有回复,直到整轮制作最终收束。
- 原因:steer 只持久化用户消息与内部 `steer.queued` 事件;公开事件投影又明确排除 `steer.*`。普通工作台提交后会用后端 conversation 覆盖本地消息,因此仅追加临时前端气泡也无法稳定跨刷新显示。
- 处理:根 Project Supervisor 的 steer 进入 durable `queued` 后,先写“正在判断、当前任务继续”的公开确认,再由独立 `steer-decision` LLM turn 返回自然语言回复和 `interruptCurrentProvider`。状态询问、解释和不冲突补充默认不中断;明确停止、改向或会使在途方案过期时才允许请求中断。判定和回复按 `run + steer` 持久幂等,刷新后仍可见;判定失败时继续当前任务,并在下一安全边界应用 steer。
- 并发边界:steer 入队、Runner `runtime.steer` 通知都不得直接触发 Provider interrupt。Codex app-server 的判定使用独立节点,不能等待主节点 turn 锁;判定为 true 后也只能中断 `appliedSteerCursor < steer.sequence` 的旧 Provider 请求,已经消费该 steer 后启动的新请求不可被误杀。已经开始的工具和外部动作不强杀,完成 observation 后再消费 steer。
- 验证:真实 mock LLM 回归必须覆盖状态询问回复且 `interruptCurrentProvider=false`;持久重放只保留一条语义回复;steer 入队后旧 Provider 继续运行,判定为 true 后才中断;新规划 Provider 的 cursor 已包含该 steer 时即使旧判定为 true 也不能中断。前端同秒多条消息保持“用户补充 → 判断提示/语义回复”的关联顺序。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/interaction.rs``apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/steering.rs``apps/ai-game-creator-shell/src-tauri/src/runner/dispatch.rs``apps/ai-game-creator-shell/src/features/agent-runtime/model.ts`
## Jenkins 异步备份不能用 nohup 脱离作业
- 现象:Stdb Publish 成功,上传日志只留下“已获取进程锁 / 上传已有备份 / 目标对象”,没有成功或可捕获错误;本地 tar.gz 和 `uploadStatus=deferred` manifest 每次发布后继续增长。
@@ -4135,6 +4152,7 @@
- 现象:延迟重试携带比既有行更旧的调用方时间,回填图片序列字段时若无条件写入,会使 `updated_at` 倒退,导致基于时间戳的同步看不到更新或排序错误。
- 处理:同源图片序列字段只允许 `None → Some`,非空冲突失败关闭;发生回填时 `updated_at = max(existing.updated_at, request_timestamp)`。legacy 音频 repair 不派生资源级图片序列或通用时长,重放继续精确匹配。
## 历史钱包消费不能从最近流水或通用订单快照推算
- 现象:后台用户详情要展示累计花费时,直接复用只返回最近 50 条的 `list_profile_wallet_ledger`,或在充值订单每行使用的通用钱包快照里扫描该用户全部流水。
@@ -4202,7 +4220,9 @@
- 原因:不是业务递归。debug 构建中 pending action continuation、后台 task queue 和 Agent 主循环各自形成大型 async poll frame;恢复路径在同一次 poll 调用链直接进入下一层状态机,累计超过 worker 默认栈。
- 处理:整个 pending continuation、它进入的后台主循环,以及完成、取消或失败后 drain 同 Agent 后续队列时,都必须跨越独立 Tokio task 轮询边界,使上层 poll 先退栈后再轮询下一层状态机。传入边界的 future 必须先装箱;若泛型 helper 直接持有大型 future,即使随后 `spawn`,调用方 async frame 仍会把它保留在默认 worker 栈上。边界必须保留结构化取消语义;当前使用 boxed future 与 `JoinSet`,父 continuation 被丢弃时同步 abort 子任务。不得只增大 CI 的 `RUST_MIN_STACK`,否则生产默认栈仍可能崩溃。
- 验证:失败用例必须在未设置 `RUST_MIN_STACK` 时通过;同时覆盖 policy batch 全组、拒绝 pending 后重规划并 drain 下一任务,以及 pending/cancellation 回归,证明恢复不重复生成 isolated spawn、队列继续推进且父任务取消不遗留后台子任务。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/pending_execution.rs`
- 2026-08-10 补充:Provider、Codex CLI 与 Codex app-server 合并到同一个模式分发后,即使本轮实际选择普通 Provider,未装箱的组合 future 仍携带最大分支状态;委派子任务完成后回流父 Agent 的既有回归会在默认 Tokio worker 栈稳定溢出,单独运行同样失败,扩大 `RUST_MIN_STACK` 才通过。持久重试 helper 与非持久压缩路径都必须在构造完整物理 Provider request 后、进入下层泛型 control/lifecycle helper 前装箱;不要逐个扩大 queue worker 栈,也不要等到底层 helper 内部再装箱已经进入调用方 frame 的泛型 future
- 2026-08-10 验证:未设置 `RUST_MIN_STACK` 时运行 `background_agent_runtime_can_delegate_task_to_other_agent`,并追加 `provider_retry_``provider_handoff_``response_stream_` 与 Native shell 完整门禁;测试只能以默认 worker 栈通过,不能把 CI 环境变量当修复。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/pending_execution.rs``apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/provider_retry.rs`
## Provider 可扩展不能用一个全局 protocol 枚举代替实例隔离
@@ -4455,6 +4475,7 @@
- 原因:正式 SFX handler 把计费、翻译、provider、持久化和写回内联在一个 future 中;分层测试只能证明单个 helper,不能证明组合顺序和“失败后不继续”。同时若把 mock 流程另写一遍,它本身又可能与生产逻辑漂移。
- 处理:抽出单一 Worker 编排函数和计费 / stage adapter。生产 adapter 代理现有正式实现:OSS 后只准备 asset object / binding 候选,项目资源、账号素材、画布和 job 终态通过同一原子提交落库;测试 adapter 逐段记录调用与注入失败。组合矩阵同时断言 charge / refund、LLM / provider / OSS / writeback 计数、稳定 reason code 和权威值等值。ElevenLabs 二进制、MIME、大小、timeout 和 MP3 仍由 loopback adapter 测试负责,组合 mock 不替代协议测试。
- 验证:自动 / 手动时长 × Loop 四组合成功;余额不足;翻译、HTTP、无效音频、时长探测、OSS PUT / HEAD、asset confirm / bind、项目资源、账号素材和画布写回逐点失败;所有 job provider POST `<= 1`,预扣后失败 refund `= 1`
## 生成结果的稳定 ID 和 job 终态都不能代替 durable receipt2026-08-06
- 现象:Provider / OSS 已成功,但项目资源、账号素材、binding、画布和 job 只完成一部分;不确定结果重放时,有时又复制一批素材或重复推进 canvas revision。inline 路径在进程重启后尤其无法判断前一次提交是否整笔完成。
@@ -4482,3 +4503,46 @@
- 原因:`normalize_generated_asset_folder_id` 只处理 `project`、旧 `folder-*` 和默认目录 ID 的兼容映射,不读取 SpacetimeDB;真正的 `require_owned_asset_folder` 位于生成结果持久化末端。把“失败会退款”误当成副作用补偿,漏掉退款不能撤销 provider 请求与 OSS PUT。
- 处理:所有付费编辑器生成在队列 enqueue 前和 worker / inline 执行前复用只读 `preflight_editor_generation_target_and_return`,按认证 owner 校验可选项目及归一化目录;读取失败和归属不匹配一律失败关闭。helper 返回 canonical 项目与目录并覆写后续入队 / worker / 原子准备使用的 payload,不能校验 trim 后的项目却持久化原始空白值。角色图片、角色动作、图标 spritesheet 与 UI 提取省略目录时按实际默认目录预检;默认目录允许尚未创建,自定义目录必须存在且 owned。预检不替代最终 procedure 复验,也不保证跨外部调用的目录锁定。
- 验证:源码顺序回归必须覆盖图片生成、图片修改、图标 spritesheet、UI 设计图提取、视频、角色动作、SFX 与 BGM 的 enqueue / direct 两层,证明纯本地格式和 `data:` / `blob:` 稳定引用门禁先执行,canonical target 在预检后写回 payload,远端引用解析、generation input rebuild、扣费、入队、provider 与 OSS 均留在预检之后;模块侧扫描证明预检只调用 runtime identity、项目、目录只读校验且不含 insert / update / delete,并覆盖带空白项目、`project`、旧 `folder-*`、默认目录 ID、自定义目录与 `None` 归一化。
## 非整除 nearest 会让逻辑像素块宽窄不一(2026-08-10)
- 现象:像素规整后的图片虽然保持了源图宽高,放大观察却能看到相邻逻辑块占用的物理列数或行数不同,表现为部分块更宽、部分块更窄;整数倍样例看起来正常,换一张网格数不能整除输入尺寸的图才复现。
- 原因:逻辑图宽高为检测后的列数、行数。把 `C × R` 的逻辑图用 nearest 恢复到 `W × H` 时,只要 `W % C != 0``H % R != 0`,目标栅格就只能在不同逻辑像素间分配 `floor / ceil` 数量的列或行;nearest 能避免混色,却不能让非整数缩放后的块严格等大。只用 `128 × 128 → 64 × 64 → 128 × 128` 这类整数倍测试会掩盖问题。
- 处理:最终资产直接编码一格一像素的逻辑分辨率 PNG,不再执行输入 / 交付尺寸 nearest 恢复,也不要求成功输出与源图或占位尺寸相等。普通图片和角色的前置 Lanczos 交付尺寸归一仍用于确定检测输入;平底网格源与透明 RGBA 源仍必须同尺寸,不能把“取消输出同尺寸”误解为放开两个内部采样坐标系。
- 验证:使用至少一组逻辑列数或行数不能整除输入尺寸的图片,断言输出宽高等于切线数减一而不是输入宽高;同时核对响应、project resource、账号素材和结果 layer 都记录最终 PNG 实际尺寸,且只持久化一个最终 PNG,没有输入尺寸恢复版、诊断图或额外资源。失败降级用例继续验证 Alpha / 交付尺寸守卫,不应因成功输出改为逻辑分辨率而删除。
- 关联:`server-rs/crates/platform-image/src/pixel_art_snapper.rs``server-rs/crates/api-server/src/editor_project.rs``docs/technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md`
## Codex CLI 节点不能把进程终态当成 Runtime 提交证据(2026-08-10
- 现象:`codex exec` 已启动或退出码为 `0`,但 JSONL 没有 `turn.completed`;或者已出现 `thread.started`,Runner 随后退出,重启时误以为节点已完成。GUI 进程的 PATH、安装或登录状态与交互终端不同时,还可能在默认模式下静默落回 HTTP Provider。
- 原因:CLI 进程、Provider lifecycle、retry/handoff 和 Runtime finalization 是连续但不同的 durable owner;本地进程退出不能代替现有账本的原子提交与回读。模式切换若不进入配置指纹,还会把另一执行模式的 retry/handoff 当成当前结果。
- 处理:Codex CLI 必须只作为新的节点推理适配器,继续经过原 Runtime 的 lifecycle、retry、handoff、receipt、revision、verification 和 reconciliation。只接受完整 JSONL `turn.completed` 与可验证的最终消息;`started``completed`、超时、异常退出或输出破损都继续走原失败/核对边界。`agentMode`、CLI 版本和影响输出的固定参数必须纳入配置指纹;CLI 不可用时显式报错,不得静默切回 Provider。
- 验证:确定性回归覆盖 stdin prompt、空临时 cwd、read-only/no-shell/ephemeral 参数、structured tool call 转换、缺失终态、超时进程组回收和 stderr 不泄漏;真实 smoke 必须显式 opt-in,并分开报告 CLI 协议成功与本机认证/网络结果。
## 模拟 Provider 的测试不能继承生产默认 Agent 模式(2026-08-11
- 现象:生产默认切到 `codex_app_server` 后,HTTP mock Provider 测试不再收到请求并超时;只验证 retry/handoff identity 的纯单测还会在未安装 Codex CLI 的 CI Runner 上直接失败。本机安装了 Codex 时,相关遗漏可能被掩盖。
- 原因:测试只构造 `agentLlm` 或直接调用读取全局配置的 identity helper,没有显式固定 `agentMode`;缺省配置按正式产品合同选择 `codex_app_server`,测试因此意外依赖本机 CLI 和认证环境。
- 处理:测试若验证 HTTP Provider 协议或 mock 请求,必须在测试配置中显式写入 `agentMode: provider`;只验证 Provider retry/handoff 数据结构的纯单测应调用显式接收模式的 identity helper。不得把生产默认模式改回 Provider,也不得仅为单测向通用 CI 镜像安装 Codex CLI。
- 验证:在 PATH 不含 Codex CLI 的环境运行 response-stream identity、MCP Runtime 和平台素材 mock 回归;同时保留独立的 Codex CLI/app-server 可用性与协议测试,防止 Provider 测试替代正式模式覆盖。
## 空 MCP 覆盖不会清除 Codex 用户配置中的 MCP2026-08-10
- 现象:以 `codex app-server -c 'mcp_servers={}'` 启动后,`thread/start` 仍发出用户配置中各 MCP server 的 startup 事件;若直接把这种进程当 AGC 节点 Agent,会出现 Codex 与 AGC 两套 ToolHost、副作用和审批边界。
- 原因:Codex `-c` 对 table 做配置合并,空 table 不是“删除已有所有条目”。长期 app-server 与一次性 `codex exec --ignore-user-config` 的配置隔离能力不同,不能照搬参数后假设用户配置已清空。
- 处理:为 AGC app-server 创建权限受限的临时 `CODEX_HOME`,只桥接已有 `auth.json`,不带入用户 `config.toml`、MCP、skills、hooks 或项目 rulesmodel/provider/effort 全部由 AGC 显式传入。server→client 请求一律拒绝,原生工具 item 一律协议失败。临时 HOME 和认证桥接不得写入项目、日志或持久账本。
- 验证:协议 smoke 必须观察 `thread/start` 后没有 MCP startup 事件;fake server 还要断言 API Key 不在 argv、初始化只一次、结构化输出回到 AGC ToolHost。只看到 `initialize` 成功不能证明安全隔离成立。
## 只隔离 CODEX_HOME 仍会加载用户 Skill,且 Codex 原生工具默认不全关闭(2026-08-10)
- 现象:app-server 虽然使用临时 `CODEX_HOME` 和 read-only turn,仍可能发现 `$HOME/.agents/skills`,并默认提供 cached web search、multi-agent 及其它稳定原生能力;等 `item/completed` 后再拒绝已经太晚,工具调用和额外模型成本可能已发生。
- 原因:Codex 的用户 Skill 发现根是 OS HOME,不是 `CODEX_HOME``dynamicTools=[]` 也只清空宿主动态工具,不会移除 Codex 内建工具。read-only/network off 是副作用防线,不等于从模型工具目录删除能力。
- 处理:同时隔离 `HOME / USERPROFILE / APPDATA / LOCALAPPDATA`,并在临时 workspace 创建空 `.git` 作为仓库发现边界,防止继续向父目录(例如 `/tmp`)发现 `.codex/.agents`;启动前设置 `web_search="disabled"``agents.enabled=false`,并关闭 shell/unified exec/browser/plugin/image/workspace dependency 等原生 feature;接收 `item/started` 时只允许消息、计划、推理和压缩等被动 item,其余立即 interrupt。配置中的 `webSearchEnabled=true` 必须失败关闭并提示切 `provider`
- 验证:fake app-server 检查 argv 不含 Key、专用 Key 只在环境、继承 `CODEX_API_KEY` 被移除、HOME 指向临时目录、web/multi-agent/shell 关闭;另覆盖 turn-start 回包前 drop 最终只发一次对应 interrupt。
## 多 Agent 共享一个 Codex app-server 会放大单点终态丢失(2026-08-10)
- 现象:多个节点最初已有 `started -> completed`,随后一个 app-server stdio 连接关闭,同一秒多个仍在途节点一起进入 `needs-reconciliation`;单看 threadId 不同会误以为节点已经进程隔离。
- 原因:pool 只按 LLM 凭据和路由复用进程,节点身份只用于进程内 thread map。任一 stdout framing、子进程退出或连接故障都会 drain 整个进程的 pending/turn router,使所有共享节点同时失去可信终态。
- 处理:pool key 必须包含 `projectId + agentId + sessionId + runId`,每个权威节点直接持有独立 app-server 子进程;同节点 turn 还要串行,不能向同一 thread 并发 `turn/start`。只发送当前 CLI schema 定义的字段;stderr 使用有界内存尾部并先脱敏再进入 Runner 诊断。
- 验证:至少两个节点并发各跑多轮,确认存在两个 app-server PID;终止其中一个后只有对应节点进入 reconciliation,另一个仍能收到 `turn/completed`。旧 AppData 缺 `agentMode` 且含非 Responses 路由时必须保留 `provider`,不能在项目自动恢复时批量失败。
@@ -47,6 +47,7 @@
- 新增 Markdown 文档时,文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`;只在任务需要时重命名历史文档,避免无关大 diff。
- 涉及中文文本时注意 UTF-8 编码和乱码排查。
- 涉及后端时遵循 DDD 分层,不把业务真相下沉到前端或临时兼容层。
- 对已明确退役且不存在现役调用方、公开契约、持久化数据、活跃实例或迁移要求的对象,不新增历史兼容代码、维持退役行为的防御性兼容测试、墓碑注释或墓碑文档;权威文档直接更新为当前状态。公开 API、持久化数据、SpacetimeDB schema、跨版本重放、活跃实例和正式迁移仍须保留最小化、白名单化的必要兼容及契约或迁移测试,并在迁移完成后同步删除。
- 运行时日志禁止对完整配置、应用状态或 provider client 做递归 `Debug` 输出;当前 `AppConfig``AppState``AppStateInner``SpacetimeClientConfig``SpacetimeClient` 必须保持封闭的手写安全摘要,并用唯一哨兵测试锁定顶层与可独立格式化路径。其它仍使用派生 `Debug` 的历史 provider 类型不得新增整对象日志调用,后续按类型独立脱敏。新增字段默认不进入摘要,确需排障时只增加枚举、数值、布尔值或是否配置等非敏感字段。
- `packages/shared` 用于前后端 DTO、公开契约及跨页面复用的无业务真相 UI 组件和纯工具;不得把领域规则、后端副作用或正式状态放入其中。
- 修改 `/api/external/v1` 的路由、HTTP 方法、请求 / 响应 DTO、请求头、状态码、鉴权或异步语义时,必须同批更新 `docs/openapi/genarrative-external-v1.openapi.json` 和对应契约测试;Rust 实现与 OpenAPI 未对齐时不得完成、提交或发布。