合并 master:resume 入口与 ProjectDevelopmentView 各留双方新增
两处冲突都是双方各自新增、互不相干: - recovery_scan.rs:master 在 resume 入口加了 recover_direct_taonier_regeneration_workflow_at(硬 `?`),分支加了策划 GDD 审批投影恢复(分三路容错、不硬传播)。都留;master 那句放前面, 保持它原有语义不受分支块影响。 - WorkspaceLauncher.tsx:master 的 walletEntry 与分支的 planningStartMode 是同一个 JSX 元素上两个独立 prop,都留。 typecheck 干净,eslint rc=0,cargo check --all-targets 通过, appSurface 421/421,分支自有过滤器(plan_envelope_repair_tests 4、 native_agent_delegate 9、prompt::tests 32)全绿。clarification 仍是本机 既有的 1 条锁超时基线失败。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7632,8 +7632,8 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
|
||||
- 路由决策:普通 AGC 登录后直接调用固定官方 origin 下的网站现役 `/api/editor/*`、`/api/assets/*` 与 `/api/runtime/external-generation/jobs/*`,不再要求用户配置 External Editor Base URL 或 `tnr_sk_...`。普通 release 的官方 origin 继续服从 2026-08-10 的 dev API 固定规则,本地 dev 使用配套代理。`/api/external/v1` 的 Bearer Developer API Key 契约保持独立且不接受网站 Access Token,只供第三方 Agent/CLI、独立 standalone game-chat release 与显式高级自定义模式;服务端不为普通用户自动签发、代管或下发 Developer Key。
|
||||
- UI 与 standalone 例外:普通 Launcher、工作台、已认证 debug game-chat 和运行时配置页不展示或维护 External Editor URL/Key。独立 standalone game-chat release 仍绕过 `AuthenticatedClient`,只能在用户显式配置其隔离 AppData 的高级 External v1 URL/Key 后使用云端画板能力;凭据缺失时明确不可用,不得回退到普通登录态、内置 Key 或隐式公共账号。
|
||||
- 会话 CAS:WebView 登录、refresh、退出和换号通过窄 Tauri/Runner 协议同步 `userId + 短期 Access Token + authGeneration` 到 GUI/Runner 进程内存。generation 单调递增,只有不旧于当前值的安装/清除可以生效;每个业务请求在发出前冻结 `ownerUserId + authGeneration`,在响应解析、账本推进、媒体安装和正式项目提交前都重新核对。旧 generation 的迟到安装、响应或清除不得覆盖新账号,也不得把旧账号产物安装到新账号项目。
|
||||
- 凭据边界:Access Token、refresh Cookie、Authorization 和 authGeneration 不进入 AppData、项目、manifest、生成账本、Agent prompt、conversation、Agent DB、trace、日志或普通错误;durable 账本只保存非敏感 ownerUserId。Runner IPC 只允许专用 session install/clear 消息携带 Token,普通生成/恢复命令参数继续不得携带 Access Token 或 Developer Key。
|
||||
- 会话 CAS:GUI 取得 owner OS 锁时产生随机 `owner epoch`,并持久化只含 `owner epoch + session revision` 的私有 claim。WebView 登录、refresh、退出和换号先单调推进 durable session revision,再以 `runner.attach_gui_owner` 同步 `userId + 短期 Access Token + authGeneration`;Runner 只接受 epoch/revision 与 durable claim 完全一致的 attach。同一 epoch 内 generation 仍单调递增,只有不旧于当前值的安装/清除可以生效;新 GUI epoch 则可替换旧进程留下的更高 generation。每个业务请求在发出前冻结 `ownerUserId + authGeneration`,在响应解析、账本推进、媒体安装和正式项目提交前都重新核对。旧 epoch/revision/generation 的迟到 attach、响应或清除不得覆盖新账号,也不得把旧账号产物安装到新账号项目。
|
||||
- 凭据边界:Access Token、refresh Cookie、Authorization 和 authGeneration 不进入 AppData、durable claim、项目、manifest、生成账本、Agent prompt、conversation、Agent DB、trace、日志或普通错误;durable claim 只保存非敏感 epoch/revision,durable 账本只保存非敏感 ownerUserId。Token 只允许随与 durable claim 完全匹配的 `runner.attach_gui_owner` 进入 Runner 内存;旧 `platform.session.install/clear` 不再是授权入口,普通生成/恢复命令参数继续不得携带 Access Token 或 Developer Key。claim 缺失、不可读或失配时 Runner 必须立即清空平台会话并阻断 Runtime 请求;同步失败必须隔离或停止旧 Runner,不得在 GUI 已切换后继续使用旧账号权威。
|
||||
- 401/refresh:普通模式首次 `401` 进入当前 authGeneration 的全局单飞 refresh;其它请求等待同一 Promise,但各自 deadline、取消和账号归属独立。refresh 成功只在原 generation/owner 仍当前时以更高 generation CAS 发布新 Token,然后每个调用最多使用原 endpoint、原始请求字节和原 `Idempotency-Key` 重试一次。refresh 失败、账号已切换或重试仍为 `401` 时停止网络并保留原 operation;`403` 表示当前账号权限不足,不触发 refresh。高级 External v1 模式只允许用户修正自己的 Developer Key,并继续原 operation。
|
||||
- Durable 身份与换号:普通账本身份固定为 `official-platform-v1 + 官方 origin + ownerUserId`,高级账本身份固定为 External service origin,二者均不绑定可轮换 Token/Key。退出或换号会提升 generation、中止并脱离旧请求;新账号对旧 owner 的 `prepared/accepted/running` 账本必须零 POST、零 GET、零下载、零安装、零归档,只有重新登录同一 owner 后才能恢复。`401/403/404`、网关错误或轮询超时不能在 owner 不匹配时证明旧 operation 的终态。
|
||||
- 幂等与站内响应:`prepared` 只可精确重放账本冻结的原 endpoint、原始正文和原幂等键;`accepted/running` 只查询原 operationId。Access Token/Key 轮换、refresh、重启、退出或换号都不得创建替代 operation、修改正文或重新扣费。站内生成适配 `200 + queueState.operationId`、inline 完成和 `job` 状态包装;External v1 继续固定 `202 + operationId`,两者共享相同 owner、计费、稳定引用与本地事务门禁。
|
||||
@@ -14805,3 +14805,42 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
|
||||
- 素材生成确认、离开确认和旧服务身份确认沿用现有独立 modal,不在当前面板下方追加内容。modal 打开后焦点必须进入对话框,并同时隔离工具栏、画布视口、缩放/小地图、状态区和并存操作层,使背景从键盘焦点顺序与 accessibility tree 中退出。
|
||||
- `Tab` / `Shift+Tab` 必须在当前 modal 内双向循环;非异步 pending 状态允许 `Escape` 安全关闭。关闭后优先恢复到原触发器,自动弹出的 modal 则回退到可操作的工具栏入口,不能把焦点遗留在已卸载节点或被隔离背景中。
|
||||
|
||||
## 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 origin,refresh 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 binding;PR 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 回灌不更新状态,容器 resize 后重新以原生视口测量。运行视窗不再提供 iframe 横纵滚动条,内容适配不改游戏文件、manifest、PreviewRegistry 或运行业务状态,非 UTF-8 HTML 保持原样。
|
||||
- 验证:前端纯函数覆盖无需缩放、纵向超高缩放、首次缩放后的增高 / 缩短、重复内容尺寸去重、过期 viewport 与非法消息;Rust preview server 测试锁定尺寸去重、无全页 MutationObserver、低频有界探测、截断保护、固定 body 与 viewport 耦合布局不振荡、真实 HTML 上下文注入、注释异常结束、DOCTYPE 引号、script escaped / double-escaped、raw-text / template / plaintext / foreign content、省略结束标签、大小写结束标签、重复 `src` 和幂等注入;再以桌面最小窗口和更高窗口人工确认完整画面、动态内容变化后仍适配、无纵向滚动条且指针 / 键盘交互仍可用。
|
||||
|
||||
## 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 或绝对路径。
|
||||
- 验收边界:程序证明切片真实 alpha、可见像素、内容唯一、稳定身份和实际渲染;背景语义、无缝地面、实体含义与绘制尺寸继续由工具图片和双视口试玩验收,不把 prompt 描述外推为视觉事实。game-chat 的有效旧包语义替换仍是独立 replace route,不能复用 Direct 开关或伪装为 missing。
|
||||
- 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 成功后释放 claim;fallback 失败时跳过该 writer 的无界迟到重试并释放 claim,后续显式 `/history` 才可复用原稳定 `clientTurnId`。终态收敛后删除 claim,避免长会话无界增长。
|
||||
|
||||
@@ -4921,3 +4921,32 @@
|
||||
- 原因:当前完整模块 init 的 RSS 会超过基础 Compose 旧 `896m` cgroup 上限;内核 OOM kill SpacetimeDB,客户端只看到传输错误,容易被误判为网络竞态。
|
||||
- 处理:先查 kernel journal 的 `Memory cgroup out of memory` 和目标容器 ID,再把本地/预发完整容器 SpacetimeDB 上限统一为 `2g`;保留 page pool 限制。不要只增加 publish 重试,也不要把 `/healthz` 或首页改成数据库就绪探针。
|
||||
- 验证:用新空卷完成模块 publish、五服务启动和 Web/API smoke,并确认容器未 OOM、SpacetimeDB 与 API/Nginx 最终 healthy。
|
||||
|
||||
## 同一本地项目切换账号后不能继续信任 manifest 远端 ID(2026-08-23)
|
||||
|
||||
- 现象:本地资源文件仍存在,但账号 A 生成后切到账号 B,快速编辑、GIF/视频派生、art-spec 下游生成或直连恢复提示画布不存在、无权限或资源不属于当前项目;原请求重试仍失败。
|
||||
- 原因:本地 manifest 只有一组 `source.canvasProjectId / resourceId / assetObjectId`,旧实现把它同时当作历史来源和当前账号可编辑引用。新的 operation 虽已绑定 B,却会把 A 的 project/resource/object 身份配合 B Token 发出;另一部分链路又把 A 的画布 ID 与 B 按标题选出的项目严格比较。
|
||||
- 处理:manifest 远端字段只保留历史 provenance。所有远端编辑和派生先解析 `.agent/runtime/external-editor-bindings/` 中当前 principal 的项目/资源 binding;不存在时从本地正式文件按 asset ID、SHA-256、媒体类型和 canonical kind 在当前账号重登记。禁止按标题、manifest ID 或其他账号 committed ledger 自动采纳远端对象。A 的在途 operation 继续留在 A,B 只能开始自己的新操作。
|
||||
- 并发与升级陷阱:首次 binding 不能用无锁的“先查后建”,否则两个 generation 会各自创建远端项目/目录;进程锁只能压住当前存活客户端,远端创建还必须携带由 binding key 派生的稳定 `Idempotency-Key`,关闭“响应已返回但 sidecar 未落盘”时重启重复创建的窗口。canonical ID 变更也不能只改新请求指纹;必须用本地 asset、路径和内容摘要白名单恢复旧 accepted/committed 账本,避免升级后已付费结果永久无法恢复。
|
||||
- binding wire 升级陷阱:在仍标 v1 的 struct 上直接新增必填指纹,会让所有已有本地项目的 binding 反序列化失败,重现“画布项目丢失”。必须升为新 schema,用独立严格 legacy wire 只迁移完整通过现役不变量校验的旧文档;不能用 default 缺失字段或在身份校验前回写。
|
||||
- 在途切号陷阱:只在首次 POST 前校验 session 不足以锁定 principal。获得 `operationId` 后必须先落盘,后续每次 poll、download 和本地 commit 都要复验原 session;同步 commit 的“先校验再写入”仍有切号竞态,必须用会话租约把校验与本地安装线性化。手工 Tauri 入口若使用临时幂等键,也会在 202 后切号时丢失恢复身份。切号后继续用 A Token 轮询或安装 A 结果同样是账号边界缺失。
|
||||
- renderer / native 两阶段陷阱:只用一个 generation 同时表示 UI 转换和 native CAS,或在 Rust 确认前先替换 committed Token,会让迟到 install / clear 把新账号覆盖回旧账号。正确做法是 auth generation 与 native 只增 generation 分离,所有 native mutation 串行,入队前冻结 token + origin + user,并持续以显式 account / `null` 期望权威对账迟到完成。对账失败不能恢复未确认候选会话,而要清空 committed 会话与 Token。
|
||||
- auth origin 竞态陷阱:只在登录前持久化服务器选择不等于冻结事务 origin;请求 A 在途时若 UI 改为 B,返回的 A Token 可能被安装到 B origin。登录、hydrate 与 refresh 必须在首个请求前冻结 origin,让 HTTP 链与 native commit 共用该快照;同时按 origin 隔离 refresh singleflight,不能让 A 的 Promise 被 B 复用。
|
||||
- stale refresh 陷阱:请求在入队前写入候选 Token 后,若 queued commit 直接因 generation 过期返回却不恢复 renderer authority,本地请求会继续携带错账号 Token。另外,A refresh 失败晚于 B 登录成功时,若仍向全局发布 failed,`AuthenticatedClient` 会把 B 误登出。所有 stale early return 先恢复当前 committed / desired Token;旧 owner 的迟到 refresh failure 只返回 `stale`,不 clear、不发布 failed。
|
||||
- 账本写入租约陷阱:只在进入命令时读一次当前账号,仍可能在 owner 绑定、服务身份指纹 / 挑战或确认落盘前切到 B,从而向 A 账本写入 B 身份。这些写入必须持同一冻结 platform session 租约并精确校验 ledger owner;公开的 request / confirm 命令也必须自身完成 owner 门禁,不能依赖调用方曾经走过恢复流程。
|
||||
- 恢复列表泄漏陷阱:项目相同不代表账本对当前账号可见。如果列表只按 project / phase 扫描,切 B 后会展示 A 的 operation,迟到回包还可能把已清空的列表重新写回。扫描必须持当前 session 租约并按完整 owner `userId + api origin` 过滤;未绑 / 不完整的远端账本隐藏,纯本地编辑保留。renderer 还要用 auth generation 使恢复 read epoch 失效并清空相关操作状态。
|
||||
- 追加审计回滚陷阱:Direct 恢复中 `asset.register` 是 append-only 审计,返回错误不能证明 append 未持久。若 file / manifest 已落盘且审计成功或结果未知,删文件或回滚 manifest 会创造“审计已存在、资源却消失”的第二种不一致,重试还可能复制审计或重新扣费。应保留 file + manifest + audit 现场并标记 `reconciliation-required`;后续 binding 失败也使用同一语义。
|
||||
- Runner 跨 GUI 陷阱:WebView 的 `authGeneration` 会随 GUI 进程重启从较低值重新开始,但 busy Runner 可能仍持有旧 GUI 的高 generation。只用 generation CAS 会把新账号安装误判为过期;只把 OS owner 锁当作授权,或保留独立 `platform.session.install/clear` 入口,又会让 Runner 在单次 IPC 丢失后继续使用旧账号。Runner 协议 v7 由 owner 锁创建随机 epoch,每次会话变更先推进 durable revision claim,并且只允许与 claim 完全匹配的 `runner.attach_gui_owner` 安装会话。Runner 要持续比对 claim,失配立即清空平台会话并拒绝 Runtime 请求;GUI 同步失败还要隔离或停止旧 Runner,不能只向前端报错。
|
||||
- 验证:用真实平台会话 fixture 覆盖 A→B→重启→A,逐个断言 B 的 URL、请求体和稳定引用中没有 A 的 project/resource/object ID;另测同名 localProjectId 隔离、改名不漂移、源摘要变化、非 refine 参考、视频 committed objectKey、art-spec 派生以及在途 operation 切号零网络。Runner 回归还要覆盖高 generation 旧 epoch 被低 generation 新 epoch 正确替换、迟到旧 epoch/revision attach 失败关闭、claim 改写或同步失败后旧 Runner 零 Runtime 请求。Developer Key fixture 不能替代平台账号隔离证据。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/external_editor_bindings.rs`、`project/resource_editor.rs`、`project/asset_canvas/generation.rs`、`agent/generation/canvas_generation.rs`、`agent/direct_runtime.rs`。
|
||||
|
||||
## Direct 美术工具不能把“包存在”当成“本次已生成”(2026-08-23)
|
||||
|
||||
- 现象:用户明确要求重做美术或切换游戏主题,工具仍立即返回 `assets/art-spec.png`、`assets/direct-game-background.png`、`assets/art-spritesheet.png`;新需求没有 Provider operation,游戏继续使用旧图。切片虽然已经落盘,也可能不出现在资源管理或工具结果中。
|
||||
- 原因:旧 Direct 工具只有 `brief`,完整包校验成功后无条件短路;固定阶段账本恢复又未比较本次生成 prompt。切片只写文件和切片清单,未作为顶层 manifest asset 投影;工具桥只返回三条主路径并丢失切片与 warning。
|
||||
- 处理:显式重做使用 `mode=regenerate`,普通请求使用 `reuse-or-create`。重生成必须由当前最新 User 消息明确授权并绑定客户端稳定 `clientTurnId`。授权先对完整原文做 Unicode NFKC 与撇号规范化,随后整串必须完整匹配审核过的独立立即执行指令,只允许句号/感叹号收尾;不得剥离引号、方括号或代码片段,动作前后也不得携带 brief、条件、否定、选择、确认、费用、延迟或其它文本。风格需求先单独描述,再由下一条独立“请重新生成美术”消息确认;不要靠扩充 deny 同义词推断付费同意。同一调用完成回包丢失只从 `completed` 持久结果等值重放,不能因重试再次扣费。App 必须在 Direct 调用前落盘原始 User 消息和回合 ID,Tauri 必须在成功返回前幂等落盘同 ID assistant 终态;同进程重复水合若命中“回合仍在运行”,只能显示瞬时占用提示,不得以稳定 assistant messageId 写成终态并抢占原执行的成功回复。恢复扫描与启动前置恢复必须发现 `resetting / compensating / anchored in-progress` 并在专用锁内恢复,重开项目只续跑真正未回答的原身份。整条付费链必须持有专用跨进程执行锁;换新回合时先持久化 `resetting` 再清理旧阶段账本,不得通过删除 workflow 留出无主窗口。崩溃补偿只恢复旧文件并清 replacement CAS 锚点,已 `prepared / accepted` 阶段账本、原 `Idempotency-Key / operationId` 必须保留,同冻结意图续跑复用旧请求;未知账本在文件 mutation 前失败关闭。只有没有任何阶段账本和替换锚点的孤立 workflow 空壳可原子接管;旧 schema 和其余冲突失败关闭。遇到 prompt 或当前 art-spec 身份不一致的未决账本必须保留原 operation 并返回对账错误。Direct app-server 可写边界只限真实 canonical `game/`,canonical 项目根的原生 OS 路径字节与权威 manifest `projectId` 经域标签和独立长度前缀编码后共同绑定连接池和 thread 身份,不得写项目根、`assets/`、`.agent/`,也不得获得网络、命令、MCP 或权限扩权;受控工具如果需要项目级客户端状态,只能从同一真实 `game/` cwd 经相同校验内部反查项目根,不能扩大模型可写根。标准图集首次创建和重生成都要求四张透明、可见、像素及平台身份唯一的 canonical 切片;工具只回传通过私有回执、公开清单、源图和顶层登记交叉验证的 `slicePaths` 与安全 `resources`。部分/opaque/重复/缺回执切片必须告警,不能把公开清单或顶层自述身份当作 Canvas 权威。
|
||||
- 验收:不要把规范图当运行态素材,也不要用 prompt 证明图片内容。程序门检查透明/可见像素、唯一性、来源、登记和源码/双视口渲染;背景排除实体、无缝地面、管道或角色尺寸等仍需观察返回图与真实试玩截图。Direct 修复不能外推为 game-chat 已支持有效旧包强制替换。
|
||||
- 同进程恢复补充:命中“同一 stable turn 仍在运行”后除禁止写 assistant 终态外,还必须删除当前 App 实例的恢复 claim。这样原调用随后成功时显式刷新能读取其终态,随后失败时也能按相同 `clientTurnId` 再次续跑;不要靠重载 WebView 清理进程内 claim,也不要用无界定时轮询制造并发调用。
|
||||
- 严格图集崩溃补充:规范图和背景图的两文件 rollback 不覆盖严格图集事务已经整体修改的 `.agent/manifest.json`、私有回执、公开清单、主图集、四切片和切片清单。必须在严格调用前持久化 pending 及九项旧合同身份;重启恢复先对账底层严格事务,完整新合同直接收口完成,完整旧合同才补偿前两阶段,混合或漂移状态失败关闭。不要在严格提交成功后局部恢复前两张图。
|
||||
- 部分旧包补充:rollback 的规范图/背景图必须保存旧字节与旧 manifest entry,不能把这两项缺失隐式当成空内容;显式 `regenerate` 因此只在这两项可信可回滚时开放。历史主图集、私有回执、公开清单或 canonical 切片可以缺失,但八个严格路径与受管顶层 asset identity 必须逐项冻结其真实 `Present/Some` 或 `Missing/None` 状态,补偿也必须恢复相同存在性。不要因为旧美术包缺切片而阻断重生成,也不要把本轮新建的严格文件误记成旧文件。
|
||||
- 对话扫描与 claim 补充:历史中出现 `User A / User B / Assistant B` 时,B 已回答不代表 A 已回答,扫描必须继续寻找 A。成功 Direct 回复在 Rust 返回前已经落盘,前端冗余 append 失败不能据此重跑;普通错误回复的显式落盘失败时,恢复 claim 要保持到 React fallback writer 的同一 messageId append 明确收敛。writer 成功或明确失败后才释放;失败路径要停止该消息的自动迟到重试,再由显式 `/history` 复用原 stable turn。终态后及时删除 claim,避免 Set 无界增长。
|
||||
|
||||
Reference in New Issue
Block a user