Merge branch 'master' into showcase-liked
This commit is contained in:
@@ -1,5 +1,135 @@
|
||||
# 决策记录
|
||||
|
||||
## 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,同时保留有界 tombstone:seen 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 全部回读相等才提交 ledger;manifest 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。
|
||||
- 服务身份:新生成账本统一写 `service-origin-v1`,绑定规范化 External base URL 的服务指纹,Developer API Key 只负责授权而不拥有 operation;确认面板只展示去除路径与凭据的服务 origin。升级前 Key-bound 指纹可被当前 Key 精确验证时自动迁移;对素材画布生成账本,无法验证时在任何网络动作前显示脱敏服务 origin 并要求用户确认带有期限且绑定账本快照的挑战值,确认后已受理任务只恢复原 GET,prepared 任务只精确重放冻结 POST。全类型资源编辑复用同一身份分类,旧指纹无法验证时保留原 operation 进入对账,不自动接管或重放。
|
||||
- 前端一致性: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 客户端远端资源编辑固定使用 External v1
|
||||
|
||||
- 产品边界:主站网页画布继续使用登录态 `/api/editor/*`、`/api/assets/*` 与 `/api/runtime/external-generation/jobs/*`;AI 游戏创作 Tauri 客户端没有网页画布宿主,图片、图片引用、视频、音效和背景音乐等远端媒体编辑固定使用 `/api/external/v1/*`。两条入口继续复用相同请求 DTO、owner 归属、统一生成队列和正式资产结果,不新建平行生成服务。
|
||||
- 凭据边界:客户端沿用发布 AppData 私有运行时配置中的 `editorApi.baseUrl/apiKey`,不实现登录后自动签发 Developer API Key,不把站内 Access Token 传给 Tauri 生成命令,也不把 API Key 打包进仓库、传入 WebView、写入项目、账本、日志或普通错误。Key 缺失投影安全配置错误,`401/403` 投影 External 凭据无效或权限不足,不再描述为站内登录失效。
|
||||
- 路由边界:图片编辑、视频、音效和 BGM 分别使用 `/api/external/v1/editor/images/edits`、`/api/external/v1/editor/videos/generations`、`/api/external/v1/editor/audios/sound-effects/generations` 与 `/api/external/v1/editor/audios/background-music/generations`;上传、确认、轮询和换签固定使用 External v1 对应端点。SVG、UTF-8 文档、代码、Agent 回执与项目版本仍是本地派生,不制造无意义的远端请求。
|
||||
- 可靠性边界:External POST 固定携带原稳定 `Idempotency-Key` 并只接受 `202 + operationId`;`prepared` 只重放原正文和原键,`accepted/running` 只查询 `/api/external/v1/generations/{operationId}`。账本使用 `service-origin-v1` 绑定规范化 External base URL 的服务指纹,不绑定或保存明文 Key;升级前 Key-bound 素材画布生成账本无法用当前 Key 验证时,必须由用户显式确认面板中展示的当前 origin 才能恢复冻结请求;全类型资源编辑的旧指纹无法验证时保留原 operation 对账。升级前已保存的站内 endpoint 只保留为待对账状态,禁止拿 External Key 自动重放。本地参考媒体继续严格执行 ticket → OSS form → confirm,结果只消费稳定 `objectKey/resource/asset`,旧资源保留且派生资源追加。
|
||||
- 关联:`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` 属于机器 subtype,create 只允许 Runtime 权威目录中的 `game-art / icon-spec / ui-prototype / art-spritesheet` 并显示中文用途,refine 继承源 kind 且锁定。未知历史 kind 只按原值保存,不在精修中迁移或改写。工具与保存设置显式分为两行,主保存动作不可压缩。
|
||||
- 路径展示:业务内部继续持有和校验绝对项目路径,普通工作区状态只投影项目名称;显式目录选择、非空目录确认和开发诊断仍可按权限边界使用真实路径。该变化不修改项目身份或 Tauri command 参数。
|
||||
- 验证:共享 chrome 与 Tauri Surface 定向测试 `20/20` 通过,覆盖可访问性、pressed/expanded/disabled、工具组/分隔符、生成/失败/保存/取消和非裁剪 CSS;AppSurface 横屏合同 `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 event,emit 失败不回滚已提交 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 只含纯 TypeScript;React 包只依赖 core、React/ReactDOM 和注入端口。project、asset、generation、completion 四组 ports 不知道 auth、wallet、payment、routing、`editorProjectClient` 或 Tauri `invoke/listen`。网站账号、计费、云端项目、素材库、上传、生成、媒体换签与业务面板继续留在网站 adapter/renderer 边界。
|
||||
- 宿主构建:根站与 AI 游戏创作壳分别 alias 同一 `packages/` 源码,Vite 去重 React/ReactDOM;Tauri 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 句柄锁内 CAS;JSON 最大 `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`。
|
||||
|
||||
## 2026-08-07 game-chat 使用单主路径按需补齐美术
|
||||
|
||||
- 背景:原 game-chat 把 Supervisor 的意图判断之后又硬接为 `design-director / code-director / art-* / code-prototype / preview-*` 固定图。`code-director` 代替程序主 Agent 判断素材缺口,会让“把现有美术资源应用到游戏中”被错误翻译成先生成美术,且美术回执无法天然回到同一个代码 Run 完成接入。
|
||||
@@ -8,12 +138,36 @@
|
||||
- 验证方式:覆盖路由后只启动 `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 夹取与零 CAS;SVG 回归覆盖单 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 证据,运行完整前端 / Rust / 运维 CI,并以真实 `1280×800` 浏览器测量证明页面级无溢出。开发页的真实登录门禁不得为验收绕过,工作台内部以 AppSurface 与 CSS 合同测试复核;美术编辑继续等待画板回写、血缘登记、新资源自动选中与邻近布局闭环。
|
||||
- 决策:阶段七不新增平行功能,只补齐视频原生控件和资源读取策略失败空态的 AppSurface 证据;美术编辑继续等待画板回写、血缘登记、新资源自动选中与邻近布局闭环。
|
||||
- 影响范围:`apps/ai-game-creator-shell` 资源管理测试、跨平台 CI / 运维测试脚本、工作台 PRD、AI 游戏创作实施计划和共享项目记忆;不修改 SpacetimeDB schema、manifest 业务合同或资源布局 sidecar。
|
||||
- 验证方式:根目录全量 Vitest `171` 个文件、`2155 passed / 5 skipped`;追加 lint、build、Rust workspace test / check、schema、原生壳、内容 / 编码和生产运维门禁;应用内浏览器在 `1280×800` 下 document/body client 与 scroll 尺寸相等。
|
||||
- 验证方式:根目录全量 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 追加不可变记录
|
||||
@@ -154,7 +308,7 @@
|
||||
> 状态:其中资源卡 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 环,并返回资源局部连接索引。
|
||||
- 决策:dependency 模式由 Tauri Rust 只读命令从当前 manifest、资源卡身份和有界 `.agent/agent.db` 审计构建稳定 `ProjectResourceGraph` read model,前端只归一化 DTO、测量卡片坐标并用原生 SVG 渲染。资产 `source.referenceResourceIds` 只在唯一匹配另一资产 `source.resourceId` 后形成橙色实线;任务依赖按任务对聚合,禁止资源笛卡尔积。本条最初把聚合任务流绘制为灰色虚线主线与分支,该显示口径已由 2026-08-10 最新决定替代为只参与布局、不进入 SVG。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,卡片坐标和持久化布局不变。
|
||||
@@ -327,7 +481,7 @@
|
||||
|
||||
- 背景:项目开发工作台当前只在 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 不取消。冲突返回最新完整布局且零写入,前端载入最新值、丢弃基于旧快照排队的手动拖动并要求重新操作;资源协调最多追加两次冲突重试,普通失败恢复最近可信布局。写入复用项目安全路径、链接校验、容量上限、恢复副本与原子替换,损坏或身份冲突不能被空布局覆盖。
|
||||
- 并发与失败: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 副作用。2026-08-05 阶段四把完整资源输入签名加入 `projectPath + projectId + mode` scope identity:资源投影或可信依赖深度变化立即建立新 epoch,旧读写继续由后端 CAS 收束,但不占用新 scope 的前端单写者槽且迟到响应被丢弃;同一签名 scope 内的资源协调仍使用 FIFO 和前一笔权威 revision。冲突返回最新完整布局且零写入,资源协调最多追加两次冲突重试,普通失败恢复最近可信布局。写入复用项目安全路径、链接校验、容量上限、恢复副本与原子替换,损坏或身份冲突不能被空布局覆盖。
|
||||
- 业务边界:布局是本地工作台 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`。
|
||||
@@ -437,7 +591,7 @@
|
||||
- 决策:在现有 `apps/ai-game-creator-shell` 项目开发入口内扩展单一工作台,不新建平行客户端。首版从当前 manifest、导入附件和 Agent 状态派生界面,提供资源 / 运行切换、资源排列与聚焦、审批弹层和底部状态栏;真实游戏通过现有 localhost 预览 server 直接载入客户端内受限运行容器,不再调用系统外部浏览器。未具备正式写回契约的拖拽布局、版本资源替换、数值微调、泥点累计、Agent.md 和 Skill 管理不得在前端伪造成功。
|
||||
- 横屏窗口:当前独立 App 只交付横屏桌面工作台,`client` 默认与最小窗口固定为 `1280×800`。工作台按壳内剩余视口排布并收紧四周留白;消息区与 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×800` 最小横屏与目标桌面视口布局。
|
||||
- 验证方式:运行 AI game creator shell 定向测试与 typecheck、`npm run ai-game-creator-shell:check`、`npm run check:encoding`、`git diff --check`。
|
||||
- 关联文档:`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
|
||||
|
||||
## 2026-07-17 AI 游戏创作 V1.31 使用同一父 run 收束静态与隔离协作
|
||||
@@ -1460,6 +1614,7 @@
|
||||
- 2026-06-19 桌面壳外链打开 helper 共用:Tauri WebView 外域拦截和 HostBridge `app.openExternalUrl` 都必须复用 `open_normalized_desktop_external_url` 执行系统外链打开动作;HostBridge 分支仍先用 `normalize_external_url` 保留 payload 错误语义并把 opener 错误回传给 H5,WebView 拦截保持 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 剪贴板复制表达,避免旧壳或裁剪壳露出不可用入口。
|
||||
@@ -6278,7 +6433,7 @@
|
||||
- 异步契约:External v1 的图片生成、图片编辑、图标图集、UI 素材提取、角色动画、视频、音效和背景音乐八类 POST 固定持久化入 `external_generation_job` 并返回 HTTP `202 + operationId/statusUrl/pollAfterMs`;不受站内 `GENARRATIVE_EXTERNAL_GENERATION_MODE=inline` 影响。每次逻辑生成必须携带稳定 `Idempotency-Key`;提交结果未知时复用原 endpoint、原始正文和原键恢复 POST,轮询超时时保留已有 `operationId` 并只继续 GET,不得换键重提。
|
||||
- 发布窗口兼容与幂等恢复:AI 游戏创作桌面客户端严格按 HTTP 状态分流生成首响应;旧服务 `200` 只作为已经完成且含可下载媒体的同步结果消费,旧图集允许从顶层 `spritesheetImageSrc` 换签下载且无效值不得遮蔽可用 `objectKey`;新服务 `202` 必须取得 `operationId` 后轮询,轮询间隔按 OpenAPI 限制在 `250..=5000ms`,其他 2xx 失败关闭。Runtime 在 POST 前原子持久化 endpoint、精确请求体字节、请求 SHA-256 与稳定幂等键,`202` 后先原子追加 `operationId` 并回读一致再查询;`accepted` 账本只恢复 GET,`prepared` 只允许在身份、配置指纹与请求 SHA 校验通过后使用账本中的原 endpoint、原始正文和同一键恢复 POST。该 POST 是 External v1 服务端幂等合同下的同一逻辑提交恢复,不放宽通用 ToolHost 未知副作用禁重放规则。恢复得到 `202` 后升级同一账本并继续 GET,旧 `200` 走兼容完成路径;再次 transport 失败仍保留同一账本。显式“继续/恢复”复用原 action、pending、provider batch 和生成账本身份,不创建 successor 请求或新键。生成 POST 使用独立三十五分钟等待预算;game-chat 的两次串行生成纳入父 run `4200` 秒软预算与从 root `bound_at` 起算的 `4500` 秒绝对硬截止,但截止时若 `canvas.asset_generate` 已进入 executing,客户端与预览照常退出,Runtime 保留 pending action、provider batch、生成账本与 `needs-reconciliation`。轮询超时保留 `accepted + operationId` 并在恢复时继续 GET;旧 `200` 结果损坏、`202` 缺 operationId、状态损坏、透明派生失败或外部完成后的本地提交失败仍进入对账边界。非阻断 general warning 继续消费结果并与 `sliceWarning` 分别展示。权威 External v1 OpenAPI 仍只声明新异步 `202`,不把部署过渡兼容公开成正式双协议。
|
||||
- 查询与结果:新增 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` 等响应不能证明原请求未入队,不得删除账本。其它非成功状态一律保留账本进入对账。账本路径解析、扫描和删除逐级拒绝符号链接,非法控制路径失败关闭。
|
||||
- 客户端 durable 查询约束:私有生成账本绑定归一化 base URL 的服务身份指纹,服务地址不一致时不恢复 POST 或查询旧 operation;Developer API Key 只负责授权,轮换不改变已有 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`。
|
||||
@@ -6629,6 +6784,19 @@
|
||||
- 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-06 客户端素材画布客户登录态生成与开发者 External 通道隔离
|
||||
|
||||
- 生成入口决策:客户端共享素材画布只实现首版图片生成与精修,通过共享 `ImageCanvasGenerationPort` 复用现有编辑器项目、统一生成队列、泥点预扣/退款和资产系统,不复制网站 `useImageCanvasGenerationWorkflow`,不建立第二套生成、扣费或发布系统。普通客户使用当前平台登录态调用 `/api/editor/*`、`/api/assets/*` 与 `/api/runtime/external-generation/jobs/*`;`/api/external/v1` 与开发者 API Key 继续只服务第三方 Agent、CLI 和受控内部验收。
|
||||
- 安全与计费决策:客户画布不显示或要求填写 Base URL / API Key,不读取仓库根目录 `.env*`,也不把供应商 Key 打包进客户端。后端从登录身份解析 owner 并归属泥点扣费;客户 Access Token 只作为单次 IPC 输入存在内存,不写账本、草稿、manifest、事件、日志或错误。
|
||||
- 确认决策:编辑 prompt、尺寸、比例、素材类型与参考图只改本地 UI;最终“确认生成”之前零远端请求。一次已确认意图固定 `intentId + generationId + 远端 idempotencyKey + commitId + commit idempotencyKey`,未知结果、重启和重复点击只能沿原身份恢复。
|
||||
- 持久化决策:prompt、精确请求快照、不依赖凭据的调用模式/API 身份指纹、远端 key、operationId 和远端稳定引用只写 `.agent/workbench/asset-canvas/generations/` 私有账本;参考图上传只持久化恢复所需的稳定 bucket/objectKey,direct-upload ticket 的 host、formFields、policy、signature 和临时 Authorization 只存在内存。登录 Token 刷新不得触发 `configuration-changed`。公开草稿、manifest 与事件只投影安全阶段和本地资产身份。远端 taskId 不得写入本地 manifest taskId。
|
||||
- 恢复决策:`accepted/running` 只 GET 原 operation,提交未知只用原请求字节和原 key 对账;远端完成后以固定 staging token 下载,再进入阶段三 journal/ledger。应用打开严格先恢复本地正式资产事务,再恢复 generation ledger,因此下载后提交中断只恢复原本地事务,不重新生成或下载。
|
||||
- 显现决策:`generation-accepted -> generation-running -> remote-completed -> media-downloaded -> asset-durable-committed -> manifest-projected -> layout-ready -> selected` 不可压成 completed;停止 UI 等待只失效聚焦意图,迟到完成仍可安全投影 manifest,但不能抢焦点。
|
||||
- 精修身份决策:正式本地资源通过 `referenceResourceIds` 引用原资源;External `sourceImageSrc` 只使用 objectKey、项目 resourceId 或素材 assetId,不能把 assetObjectId 当成媒体引用。`sourceResourceId` 只允许真实 External resourceId,不能使用 `local-asset:*` 或 objectKey 冒充。
|
||||
- 最终共享边界:Tauri 撤销/重做改为直接消费共享 `useCanvasHistory`;共享 history 统一恢复 viewport、selection、图层位置和缩放边界。Tauri Pointer 代码只负责把宿主事件接到共享 selection/viewport/transform/renderer 算法,不再维护第二套 history 栈。
|
||||
- 事务发布前恢复:首个快照、全部快照、journal 后但 ledger 前都是正式故障点。无 ledger 的 transaction 仅在受控目录内容、正式文件不存在且 manifest/revision 精确保持 before 时清理并返回 rolled-back;未知条目或权威状态变化必须失败关闭。清理后允许原 commit/idempotency 身份重放,最终仍只产生一个 manifest asset。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs`、`packages/image-canvas-core/src/ports.ts`、`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`。
|
||||
|
||||
## 2026-08-03 图片画布生成产物统一“改造”契约
|
||||
|
||||
- 背景:画布生成结果统一显示“改造”,但部分动画 / 音频结果无法恢复面板;另一些非生成型派生结果继承最近生成输入,产生错误可执行动作。中文标题和素材类别被同时当成显示文案、参数键和路由键,改名后容易漂移。
|
||||
@@ -6859,6 +7027,18 @@
|
||||
- 分类:内部稳定 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-10 客户端全类型资源编辑恢复身份与交互门禁
|
||||
|
||||
- 资源编辑 operation 以私有账本中的原 endpoint、请求字节、幂等键和 `operationId` 为唯一恢复身份;账本同时冻结源 asset、媒体类型、资源 kind、生产任务和源版本快照。旧账本只从当前 manifest、已完成任务产物、版本数组及受控扩展名兼容恢复,不能信任 UI 路径或前端重新构造的来源。
|
||||
- Developer API Key 允许轮换,当前 `service-origin-v1` 配置身份绑定规范化 External base URL 的服务指纹,确认 UI 只展示服务 origin。升级前包含 Key 的生成指纹在当前 Key 可精确验证原服务时自动迁移;无法验证时保留原 operation 进入对账,不自动接管或重放。素材画布生成恢复另提供受挑战值保护的显式 origin 确认。operation 已建立后遇到 401/403/404、超时或临时网关错误都保留原 operation;恢复命令仅接收项目与 operation 身份,不接收 Key 或新请求正文。
|
||||
- pending 扫描上限覆盖目录全部实际条目。媒体提交前重新校验源摘要;视频后续派生使用 committed ledger 的稳定 `objectKey`,不把 `assetObjectId` 当远端输入;版本 journal 重放返回原子版本,禁止重复追加。
|
||||
- refine 草稿恢复以正式 sidecar 为权威,按项目、意图、源素材和 active 状态有界唯一发现;唯一命中保留 `draftId` 并换新 `sessionId`,多候选失败关闭。文本/SVG/Agent 回执在 Provider 调用前持久化 request-issued,成功正文先写 durable handoff 再解析和 staging;issued 后无可信 handoff 只对账,有 handoff 只消费原响应。
|
||||
- 旧 Key-bound 资源编辑账本采用快照绑定的显式服务 origin 确认,确认前零网络动作;已受理 operation 遇到 401/403 保留原 remote phase,换 Key 后只 GET。asset journal 已证明目标 asset/media 与 target revision 后,后续合法 manifest/revision 前进不阻止补齐 ledger。
|
||||
- 画布生成中冻结 wheel、指针、缩放和方向键状态改变并聚焦“停止等待”;普通 wheel 二维平移,Shift 纵向滚轮横移,Ctrl/Command wheel 缩放。Shift 指针序列只执行一次选择切换,草稿保存失败后退出仍必须提供保留/放弃确认。
|
||||
- 只有 `editing` 生命周期允许新生成;其它状态与 modal 同时锁闭背景交互,生成 handler 必须再次读取当前 lifecycle 失败关闭。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs`、`apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx`、`packages/image-canvas-core/src/interaction.ts`。
|
||||
|
||||
## 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 已作为一笔提交完成。
|
||||
@@ -6954,4 +7134,12 @@
|
||||
- 元数据决策:首次导入只创建并保留项目根下的 `.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 必须在发布前证明工具目录可被接受。
|
||||
- HTTP 传输边界:根 H5 包不得依赖 Tauri guest 插件;AGC 独立包保留 `@tauri-apps/plugin-http`。Rust 插件显式关闭默认特性,只启用 `charset`、`cookies`、`http2` 和 `rustls-tls`,避免 `reqwest/system-proxy` 通过 Cargo feature union 把画布、Provider、Runtime 与本地回环夹具统一接入 OS 自动系统代理;如未来产品要求正式客户端继承系统代理,必须按各客户端明确设计并单独完成跨平台验证。
|
||||
- 运行决策:Godot 项目提交给 Project Supervisor 时使用 `standard` Run Profile,避免触发 Web 专用 `game/index.html`、HTTP preview 与自主 Web 完成门。Godot 编辑器启动和内嵌运行预览不在本切片范围。
|
||||
|
||||
## 2026-08-11 Game Agent 通用 Goal Contract 与动态验收图
|
||||
|
||||
- 决策:固定规则只负责安全、事实完整性和完成协议。根 Project Supervisor 必须先用 `agent.goal_contract` 把自己对当前用户最终意图的理解冻结为结构化 outcome、硬约束、偏好、禁止假设、开放问题和 acceptance nodes;同一根 Run 不允许改写。该合同摘要进入共享黑板,权威 JSON 绑定可信 root Run Profile、source task SHA-256 与 fingerprint。
|
||||
- 决策:根合同和 Acceptance Graph 自动进入所有后代上下文,但不扩大局部工具、路径、写范围、expectedArtifacts 或委派权限。isolated child 必须同时看到其持久化的局部验收、产物和 writeScopes。专业 Agent 只交付局部结果与证据,不能更新根图或宣布总目标完成。
|
||||
- 决策:只有同一可信根 Supervisor 能调用 `agent.acceptance_update`,且该控制面动作必须独占一轮,不能夹带 `plan_update`、legacy plan 或回复。requiredEvidence 采用 `tool:<Runtime 工具名>`,合同冻结前必须命中 Runtime 允许的持久证据工具集合并拒绝拼错、控制面和纯协调工具;动态 MCP catalog 不得冻结为不可变 requiredEvidence。passed 节点必须引用当前根任务树中对应工具的真实成功动作回执;回执同时记录动作执行边界的 `projectRevisionBefore / projectRevisionAfter`,非 mutation evidence 必须满足 before、after 与验收时 current revision 完全相同,mutation evidence 的 after 必须等于 current revision,旧 revision 或执行后延迟落账的回执不可重放。failed、not-observed、缺失节点以及落后当前 project revision 的整图确认状态均阻断普通完成、finalization 与恢复。项目 revision 变化后由 Supervisor 只更新受影响节点并确认当前图,未提交的 passed 节点保持不变;Runtime 不替 Supervisor 推断影响范围、选择具体 Agent 或实现方式。
|
||||
- 决策:不可变 Goal Contract 的根 Run 收到 steer 时,必须按旧 rootRunId 串行化整个转换,并在持锁后重新确认该旧根仍是 Session 当前权威 Run,避免不同 steerId 并发创建多个 replacement。随后为绑定旧 rootRunId 的全部非终态静态、ready 和 isolated 后代写入取消栅栏、打断 Provider 并逐个收束,再终止旧根;所有项目修改入口在看到取消栅栏后立即失败关闭。Runtime 必须确认旧树所有成员都已进入终态或 `needs-reconciliation`,超时则保持等待并拒绝启动 replacement;只有旧树停稳后,才在同一 Session、source 和 Run Profile 启动新根 Run 重新理解完整目标。
|
||||
|
||||
@@ -611,6 +611,14 @@ npm run check:server-rs-ddd
|
||||
- 页面交互 smoke
|
||||
- 移动端视口检查
|
||||
|
||||
### 提交前 TypeScript 自动格式化
|
||||
|
||||
仓库级 Git `pre-commit` hook 通过 `lint-staged`,只对当前已暂存的 `*.ts`、`*.tsx` 文件运行 Prettier 自动格式化,并把格式化结果更新到本次提交的暂存区;未暂存的其他文件不进入格式化范围。格式化或暂存恢复失败时提交会中止,应先处理失败原因并重新检查 staged diff,不能等 CI 再暴露格式问题。
|
||||
|
||||
部分暂存同一 TS / TSX 文件时,`lint-staged` 会临时隐藏该文件未暂存的改动,以暂存快照执行格式化,随后恢复未暂存内容。因此提交前后都应分别检查 `git diff --cached` 和 `git diff`,确认格式化后的暂存内容属于本次提交,未暂存工作没有被误带入;若恢复产生冲突,先人工整理暂存边界再重新提交。
|
||||
|
||||
`git commit --no-verify` 会绕过该 hook,只允许在已明确原因的紧急场景使用。绕过时仍须对本次暂存的 TS / TSX 文件手动执行等价的 Prettier 格式化、重新暂存并核对 staged diff;`--no-verify` 不代表可以跳过格式化或其他提交门禁。
|
||||
|
||||
前端原则:
|
||||
|
||||
- 移动端优先,再兼容网页端。
|
||||
|
||||
@@ -14,6 +14,77 @@
|
||||
- 关联:相关文件、文档、提交或 Issue
|
||||
```
|
||||
|
||||
## 资源管理第二轮修复后不能继续用第一轮文档和弱测试作为验收合同
|
||||
|
||||
- 现象:代码已经改成分区内 SVG plane 和卡内媒体,文档仍要求单全局 Overlay 或中央大图;CSS 正则和浅层 AppSurface 测试保持绿色,但真实 Tauri WebView 仍会默认缩放、主动预览请求饥饿、过滤后媒体继续播放或超深布局反复提交非法坐标。
|
||||
- 原因:第一轮编码时同步编写的 PRD / 技术方案被后续代码修复绕过,第二轮只改实现和局部测试,没有把新验证结论回写正式合同。React 合成 wheel 事件、单例滚动 ref、只按数量限制的 base64 缓存、跨 scope 共用的活动读取计数、无优先级有界队列和前后端不同坐标边界又分别跨越浏览器、会话状态与 IPC 边界,浅层文本断言无法证明运行时行为。
|
||||
- 处理:每轮验证后按“当前代码 + 最新决策 + 真实运行证据”同步修订 PRD、技术方案、决策记录和回归测试。原生可取消事件要直接断言 `defaultPrevented`;队列验证主动请求替换预取;预览 data URL 只作临时传输并转为可撤销 Blob URL,LRU 同时限制项目数和总字节;项目 / mode 切换推进 epoch,旧 `finally` 不得扣减新 scope;媒体状态同时核对可见集合;滚动分别按内外 scope 保存;坐标合同由共享 TS 与 Rust 同边界维护;暂时 / 永久错误在模型中显式分类。
|
||||
- 验证:运行定向 hook / AppSurface / 纯布局 / Rust 边界测试,再执行类型检查、编码检查和 `git diff --check`。
|
||||
- 关联:`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCardPreviews.ts`、`apps/ai-game-creator-shell/src/view/project-development/resourceCanvasLayoutModel.ts`、`apps/ai-game-creator-shell/src-tauri/src/project/resource_layout.rs`、`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`。
|
||||
|
||||
## 预览读取不能像放弃旧 CAS 回调一样直接重置并发槽
|
||||
|
||||
- 现象:A 项目的 3 个预览 IPC 仍挂起时切到 B,前端把活动计数归零并立即再发 3 个;界面不会被 A 的迟到结果污染,但原生同时保留 6 个读取。连续项目 / mode / Hook / 窗口切换会继续叠加整文件缓冲、base64 临时字符串和 WebView IPC 载荷,绕过 `64 MiB` 终态缓存预算。
|
||||
- 原因:布局 CAS 已经发出后只能依靠 revision / 系统锁仲裁,前端放弃回调是正确语义;只读预览却有明确的物理内存和文件读取成本,可以协作取消。把 scope epoch 的逻辑隔离误当成底层取消,又让每个 scope 自行拥有 3 个物理槽,实际并发就不再全局有界。
|
||||
- 处理:前端继续用 epoch、优先级队列、LRU 和完整资源身份隔离展示状态,但每次挂载 / scope 变化生成不复用的 `scopeId`,每次 IPC 生成唯一 `requestId`。三个安全读取命令共用 Tauri 进程级 3 permit 管理器;切换和卸载调用窄 scope 取消命令,等待 permit 与固定块读取都检查取消。permit 和 request/scope 清理 guard 必须移入真正的 `spawn_blocking` 读取闭包;WebView 卸载或调用方 abort 只会丢弃外层等待,不能让仍在运行的 blocking 读取提前释放物理槽或从取消 registry 消失。取消任务在 base64 前退出,所有终态清理活动 request / scope registry。seen request tombstone 最多保留 `8192` 项;非活动 cancelled scope tombstone 的预算为 `1024` 项,活动取消 scope 为防复活必须临时钉住并在结束后重新收敛。不得为了追求整个 registry 字面清零而删除防重放 / 防复活记录,也不得让已经结束的 scope 长期占用预算外记录。不得放宽原有项目边界、登记、权限、链接、签名、大小、漂移或安全 SVG 门禁。
|
||||
- 验证:先让旧 scope 占满 3 个 permit,再连续执行 A → B → A、mode、Hook 和多窗口切换;断言原生活动峰值始终 `<= 3`,旧等待任务不打开文件,旧在途任务在最近检查点释放,新 scope 随后启动,取消任务不编码 data URL / 不创建 Blob URL,最终活动 request / scope registry 为零。阻塞读取进入后主动 abort 外层 future,必须证明旧 blocking 任务仍占 permit、仍可按 scope 取消且新请求不能提前启动。另分别证明 seen request `8192` 项的硬上限、cancelled scope 超预算时不淘汰活动记录,以及任一活动 scope 结束后非活动 tombstone 立即收敛到 `1024` 项预算。前端再断言旧 `then / catch / finally` 和取消 ACK 均不写新 scope;内部取消类别只允许精确匹配,不能因真实错误正文恰好包含该标识而静默吞错。
|
||||
- 关联:`apps/ai-game-creator-shell/src/view/project-development/useProjectResourceCardPreviews.ts`、`apps/ai-game-creator-shell/src-tauri/src/resource_preview_scheduler.rs`、`apps/ai-game-creator-shell/src-tauri/src/resource_inspect.rs`、`apps/ai-game-creator-shell/src-tauri/src/image_inspect.rs`。
|
||||
|
||||
## 依赖线与资源卡不在同一 transform 层时会在滚动和缩放中分离
|
||||
|
||||
- 现象:静止时依赖线似乎对齐,触摸板缩放或连续滚动后线段会追赶、漂移或忽隐忽现;某一资源分区的长线还可能出现在相邻分区。
|
||||
- 原因:资源卡位于各自可滚动、可缩放的 section plane,全局 SVG 却是外层兄弟节点;通过 `getBoundingClientRect`、RAF 和 React state 重建屏幕端点无法与浏览器合成层 transform 原子同步。把四个 viewport 做成一个 clipPath 并集也不具备“每条线属于哪个分区”的所有权语义。
|
||||
- 处理:让每个固定分区在自己的 `.game-resource-plane` 中拥有独立 SVG,卡片与路径都直接使用布局逻辑坐标并共享父级 CSS scale / 原生 scroll;viewport 原生 overflow 负责本区裁剪。DOM 测量只换算本区逻辑 viewport,用于完整路径、incoming / outgoing 继续线和两端离屏隐藏,不参与端点身份或主路径坐标。每区 observer 和 RAF 各至多一个,卸载时清理。
|
||||
- 验证:同时挂载至少两个分区和各自同类型关系,断言每条边只存在于对应分区 SVG;只滚动其中一分区,另一分区的逻辑 viewport 与 path 不变。另覆盖缩放后 SVG / 卡片仍在同一 plane、双向离屏继续线、两端离屏隐藏、marker、自环以及 mode / 项目切换清理。
|
||||
- 关联:`apps/ai-game-creator-shell/src/view/project-development/ResourceDependencyOverlay.tsx`、`apps/ai-game-creator-shell/tests/ResourceDependencyOverlay.test.ts`、`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`。
|
||||
## 正式保存不能等待 React state 才取得草稿 CAS 的新 revision
|
||||
|
||||
- 现象:用户刚完成编辑就点击“保存到项目”,自动草稿保存已经成功,但正式提交仍携带旧 `expectedDraftRevision`,于是单窗口也得到 draft revision conflict;快速连续保存时还可能使用不同 commitId 重复 staging。
|
||||
- 原因:`setDraft(result.value)` 的 React state 提交晚于当前 Promise 链,正式保存若从闭包或下一次 render 读取 revision,会把 UI 调度时序误当成持久化顺序。相同问题也会出现在选择变化未标脏、父组件每次 render 新建 scope 对象而重复恢复、项目切换后旧 generation 回调继续写 notice。
|
||||
- 处理:草稿保存队列在 CAS 成功后同步更新 `draftRef.current` 并直接返回权威 draft;正式提交继续使用该返回值的 revision。scope 按 project/draft/intent/source 原始字段稳定化,所有导入、保存、生成和事件回调捕获当前 epoch,选择变化属于草稿合同并必须标脏。首次正式保存冻结 commitId/idempotencyKey,未知结果只重放原请求。
|
||||
- 验证:用 deferred Promise 证明草稿 CAS 完成后正式 commit 使用新 revision;相同 scope 值重渲染不重复 recover/load;锁定图层选择进入草稿更新;项目切换后迟到 generation/commit/event 均不改变新会话。
|
||||
- 关联:`apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx`、`apps/ai-game-creator-shell/tests/assetCanvasSurface.test.tsx`。
|
||||
|
||||
## 共享画布不能用全局 DOM 查询或宿主整包 CSS 作为隐式依赖
|
||||
|
||||
- 现象:页面挂两个画布时,第二个画布点击小地图会移动第一个画布;反复挂载后 wheel 触发多次。Tauri 单独引入网站 `index.css` 时还会带入账号、项目页和历史业务样式,或因仓库外源码解析到第二份 React 而出现 Hook 错误。
|
||||
- 原因:`document.querySelector`、body 级 portal、未清理的 listener/observer/animation frame 和不受作用域约束的 CSS 都把组件实例与网站宿主当成全局单例;Tauri Vite 默认根目录又不等于仓库根,React 解析路径可能分叉。
|
||||
- 处理:共享小地图先从当前 viewport 查询,只有文档中唯一候选时才兼容旧单实例形态;wheel、ResizeObserver 和 animation frame 在 effect cleanup 中逐项释放。portal 支持实例 root,默认 body 只作兼容。共享 CSS 全部限定在 `.genarrative-image-canvas`,两宿主 alias 同一 `packages/` 源码并 dedupe React/ReactDOM,Tauri `fs.allow` 覆盖 repo root,禁止导入主站完整 `index.css`。
|
||||
- 验证:共享 React 测试重复 mount/unmount 后 wheel add/remove 数量相等、ResizeObserver 精确 disconnect,并挂两个含各自小地图的 viewport,确认只更新目标实例;网站与 Tauri 壳分别 typecheck/build。
|
||||
- 关联:`packages/image-canvas-react/src/useImageCanvasViewportControls.ts`、`packages/image-canvas-react/src/CanvasPortal.tsx`、根目录与 `apps/ai-game-creator-shell` 的 Vite 配置。
|
||||
|
||||
## 桌面工作台不要让 Supervisor 内容高度挤掉输入区和 Agent Dock
|
||||
|
||||
- 现象:`1280×800` 或更矮窗口中,Supervisor 的消息、Runtime 状态和错误正文共同按内容高度增长,聊天输入被推到栏外;底部 Dock 使用固定宽度卡片时还会在中等宽度造成页面级横向溢出。中央画布的生成卡和状态栏若同时固定高度并隐藏 overflow,进度、失败、重试、保存或取消动作会被裁掉。
|
||||
- 原因:四区工作台没有把“主区内部滚动”和“页面级滚动”分开;Supervisor 的长状态没有独立上限,Dock 卡片不能收缩,宿主又用全局按钮/固定行高样式覆盖共享 chrome。
|
||||
- 处理:桌面工作台使用 `100dvh` 两行网格,第一行 `minmax(0, 1fr)` 承载中央区与 Supervisor,第二行承载 Dock;消息和 Runtime 分别内部滚动,composer 保持最后一行并设置明确层级。Dock 卡片使用可收缩 flex 与文本省略。画布 dialog、进度/失败卡和状态栏设置 `min-height: 0`、受限最大高度与内部滚动;宿主不再覆盖所有按钮,只为主要动作和布局提供 token 化薄样式。
|
||||
- 验证:AppSurface CSS 合同检查 `100dvh`、Supervisor composer、Runtime 内滚动、Dock 常驻/可收缩;素材画布测试检查生成 dialog、operation card、状态栏和窄屏保存动作不会被裁剪。真实入口在 `1280×800` 测量 document/body client 与 scroll 一致;登录门禁不可为视觉测试绕过。
|
||||
- 关联:`apps/ai-game-creator-shell/src/styles.css`、`apps/ai-game-creator-shell/src/features/asset-canvas/assetCanvasSurface.css`、`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`、`apps/ai-game-creator-shell/tests/assetCanvasSurface.test.tsx`。
|
||||
|
||||
## 素材保存区不要把机器 subtype 当普通文本框(2026-08-06)
|
||||
|
||||
- 现象:保存区同时显示“画布素材”和裸 `asset` 文本框,普通用户无法判断两者用途;自由修改 kind 会形成无法稳定参与类型布局、Agent 合同和替换兼容性的 subtype。工具栏与保存设置挤在同一行时,主要保存按钮还会被压缩或裁切。
|
||||
- 原因:把 Host Port 的 `name / assetKind / mediaType` DTO 直接映射成同层输入控件,没有区分用户命名、机器分类和编码格式,也没有为中央区域的真实容器宽度保留主操作列。
|
||||
- 处理:名称保留编辑;create kind 使用 Runtime 权威四项目录和中文标签,refine 从源 manifest 继承并锁定,未知历史值只透传;格式继续使用有限枚举。工具动作和保存设置显式上下分行,保存列使用 `max-content + nowrap`,窄容器时按钮独占整行。普通工作区状态只显示项目名称,不把绝对路径作为默认辅助文案。
|
||||
- 验证:Surface 测试断言四项用途、默认值、精修未知 kind 锁定、最终 commit 参数和保存按钮 CSS;AppSurface 断言普通界面找不到绝对路径,内部 Tauri 调用仍使用原完整路径。
|
||||
- 关联:`apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx`、`apps/ai-game-creator-shell/src/features/asset-canvas/assetCanvasSurface.css`、`apps/ai-game-creator-shell/src/features/project-workspace/`。
|
||||
|
||||
## 正式素材提交不能把多文件写入或 Tauri 事件误当成一次原子动作
|
||||
|
||||
- 现象:图片已经落到 `assets/` 但 manifest 没有资产,或 manifest 已追加而 project revision/草稿仍是旧值;进程在 emit 前后退出后,用户重试又得到第二份图片、第二个 asset 或重复选中。
|
||||
- 原因:文件系统只保证单文件原子替换,不能让最终图片、manifest、`.agent/runtime/project-revision.json`、commit ledger 和草稿跨文件物理原子;Tauri event 也没有跨崩溃 exactly-once。若先写副作用再临时生成幂等身份,或只凭目标文件存在推断成功,就无法区分未提交、已提交未回包和部分提交。
|
||||
- 处理:第一次保存前冻结 `commitId + idempotencyKey + eventId + requestFingerprint`,在项目 write lock 内先写 prepared journal 和 before/after 摘要,再按最终图片、manifest/revision 逻辑原子更新、回读、ledger/草稿提交推进,释放锁后最后 emit。恢复只按 journal stage、精确字节摘要和 ledger 前向完成/安全回滚;矛盾状态进入 reconciliation-required。事件采用至少一次,监听方按 eventId 和 project revision 去重。
|
||||
- 验证:分别在 prepared、图片安装、manifest 安装、revision 安装、ledger 提交、emit 和投递标记后强杀;确认只有唯一 `canvas-<commitId>`、revision 最多推进一次、源资产与血缘正确,响应丢失后返回 already-committed,矛盾 fixture 不自动重试。
|
||||
- 关联:`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`。
|
||||
|
||||
## 素材保存成功不等于迟到结果仍有权抢占当前焦点
|
||||
|
||||
- 现象:用户等待生成/保存时切到另一个项目、run、另一份素材草稿,或主动选择其它资源、修改搜索条件;旧请求完成后界面却切回旧画布、清空筛选并自动选中新资源。
|
||||
- 原因:异步回调只检查“请求成功”或捕获的旧 `isMounted/projectId`,没有绑定中央状态 session、draft/intent、selection epoch 和 query epoch;manifest 投影这一数据事实又被错误地与“当前应自动聚焦”的用户意图合并处理。
|
||||
- 处理:保存开始捕获 `projectPath + projectId + centerKind + sessionId + draftId + intent + selectionEpoch + queryEpoch`,响应时从当前 ref/store 完整复核。manifest 可以按精确项目身份更新当前上下文或后台缓存,但自动切状态、选择、滚动和聚焦必须等当前 mode 布局 ready 且全部焦点守卫仍相等。新资源被搜索/筛选隐藏时保留条件与选择,提示“新资源已保存,当前筛选条件下不可见”,只提供显式清除/定位动作。
|
||||
- 验证:使用 deferred commit/layout Promise,依次在请求后切项目、切 run/overview、新开 session、改选择和改筛选;断言 manifest 只更新对应项目,新资源仍进入投影/布局,但所有失效守卫都不切中央状态、不改选择、不清查询。条件未变化且资源可见时才自动定位。
|
||||
- 关联:`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`、`docs/prd/【AI游戏创作】项目开发工作台PRD-2026-07-20.md`。
|
||||
|
||||
## 派生 Debug 会让完整配置经应用状态递归进入日志
|
||||
|
||||
- 现象:配置和状态当前没有直接日志调用,但新增一行 `debug!(?state, ...)` 或 `format!("{config:?}")` 就能把 JWT、后台口令、支付私钥、OSS / provider key 与 SpacetimeDB token 一次性写入日志及 OTel 留存面。
|
||||
@@ -71,6 +142,14 @@
|
||||
- 验证:用真实 scheduler 恢复确定性 v1 主 Run;把旧 scheduler 美术 child 置为 running,断言 `canvas.asset_generate`、`memory.write`、`task.create`、`task.update` 和 `agent.run_status` 均被拒绝;再持久化其历史 `game/**` writeScope isolated 后代,断言恢复执行写操作仍失败且项目未变。对当前合法美术 child 同样验证 memory/manifest 零写入,再让它带在途外部生成命中硬截止,断言状态进入 `needs-reconciliation` 且 pending/batch/外部生成账本原样保留。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_start.rs`、`runtime_driver/game_chat_fast_path.rs`、`runtime_driver/main_loop.rs`、`runtime_tools/file_ops.rs`。
|
||||
|
||||
## 委派幂等与 child 写入测试不能和真实后台 worker 抢状态
|
||||
|
||||
- 现象:测试刚建立 static delivery,自动 parent-wake 就抢先恢复并终结父 Run,使随后同 action 重放被“当前 durable task 仍为 running”拒绝;受限美术 child 测试也可能在后台 worker 抢先终态后,让本应允许的 `assets/**` 写入误报 verification failure。
|
||||
- 原因:测试 fixture 同时手工推进 journal/manifest,又允许真实后台 future 执行同一父子 Run;单测运行时序决定谁最后写入。若为让测试通过而把 active durable task 门禁整体移动到 existing-child 分支之后,该分支仍可能补建 delivery 或投影 Ready,反而允许终态/过期父 Run 发生修复性写入。
|
||||
- 处理:保留生产门禁顺序,父 Run 终态后的迟到 delivery 继续只允许 suppressed。需要断言回执、claim 与同 action 重放时,测试持有父 Agent execution lane,断言结束后释放再执行 wake;直接测试 child 写工具时持有目标 Agent lane,再把 child 持久推进到 `running`。所有 lane 均由 RAII 释放,不能依赖后台 future 的调度时机。
|
||||
- 验证:覆盖父 Run active 时 claimed delivery 的同 action 重放返回 existing、不同 action 的重复缺口仍被拒绝、父终态后的新委派/迟到 delivery 继续失败关闭或 suppressed、合法运行中 child 只可写 `assets/**`。macOS 直接拼接 `std::env::temp_dir()` 的仓库安全测试还应先规范化临时根,避免 `/var -> /private/var` 被误当成项目内符号链接。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/delegation.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop_tests.rs`、`apps/ai-game-creator-shell/src-tauri/src/repository_context.rs`。
|
||||
|
||||
## 执行锁移交给未确认启动的异步 future 会制造永久 queued
|
||||
|
||||
- 现象:父 Supervisor 与 Runner 一直显示运行中、heartbeat 正常,专业 Agent 已有 `background_task.queued` 和 `autonomous_ready_task.scheduled`,对应执行锁也被 Runner 持有,但该 child 永远没有 running journal、`turn.started` 或后续 Runtime event;其它同批 Agent 可能已经完成。
|
||||
@@ -111,6 +190,30 @@
|
||||
- 验证:单测覆盖 CAS 边界(满了返回失败且计数不越界、上限为 0 时任何进入都失败),并由独立用例覆盖 guard 离开作用域后的计数归还。预算耗尽路径只断言 `504`,不得通过另一个测试也会修改的进程级 static before/after 来推断“未入队”,也不得用串行锁或 `--test-threads=1` 掩盖隔离问题。
|
||||
- 关联:`server-rs/crates/api-server/src/editor_project.rs`(`try_enter_bounded_queue`、`EditorPixelArtSnapQueueGuard`)。
|
||||
|
||||
## dependency 不能复用 type 的紧凑间距或让窄层始终顶部对齐
|
||||
|
||||
- 现象:相邻卡片间的橙色引用只剩一个箭头,看起来像长度异常;同一个菱形 / 分叉关系中,上半组线很短而下半组线绕很远。
|
||||
- 原因:type 模式的 `16px` 紧凑行列间距不足以同时容纳 marker 安全距离和可辨认线身;分层布局若只按每层 index 从簇顶向下排,单节点层无法与多节点层的垂直中心对齐。
|
||||
- 处理:dependency 自动坐标使用独立 `48px` 列间距和 `40px` 行间距,type 继续使用 `16px`。相关簇记录最大层行数,每层起始 y 增加 `(maxRows - layerRows) * dependencySlotHeight / 2` 的确定性偏移;平局仍用稳定资源 ID,手动坐标仍原样占位。不能通过裁短长线、偏移真实端点或压缩 Rust depth 伪造一致长度。
|
||||
- 验证:纯模型锁定 type / dependency 间距隔离、四节点菱形的首尾单节点层居中、同层稳定顺序、历史手动坐标和 4096 项性能;Overlay 使用相邻 dependency 槽位证明箭头前保留可辨认线身。
|
||||
- 关联:`apps/ai-game-creator-shell/src/view/project-development/resourceCanvasLayoutModel.ts`、`ResourceDependencyOverlay.tsx`、`resourceCanvasLayoutModel.test.ts`。
|
||||
|
||||
## 依赖聚类不能把聚合 task-flow 展开为资源两两边
|
||||
|
||||
- 现象:为了让 task-flow 的两端资源靠近,若对每个 source × target 构造边,资源多的任务流会迅速放大内存、排序工作和虚假关系;同一图输入还可能随着成员枚举顺序出现不稳定排列。
|
||||
- 原因:task-flow 的业务语义是任务对的聚合流,不是资源间的完整笛卡尔依赖;dependency depth 也已经由 Rust SCC read model 权威计算,前端不能用布局边重建业务方向。
|
||||
- 处理:布局分组把每条 flow 作为一个临时流节点,仅与其 source / target 成员相连;中位数扫描读取流另一端成员现有 rank 的中位值。遍历保持迭代式,扫描轮数固定,所有初始序和最终平局都以稳定资源 ID 收口。用于自动重派生的拓扑签名先按固定分类过滤并规范化稳定 ID,再生成固定大小摘要,不能把显示名、卡片大小或浏览器几何加入签名。跨分类 reference 与跨分类-only task-flow 不进入前端布局;所有 task-flow 都不进入 SVG 或画布关系说明。`producerMappingTruncated` 时继续只消费现有同类型精确引用,不重建 task-flow。
|
||||
- 验证:纯模型覆盖多入、多出、聚合 task-flow、环、4096 链和重复输入坐标一致;Hook 覆盖仅改邻接、深度不变仍重派生自动坐标。不得把搜索后的可见集传入聚类。
|
||||
- 关联:`apps/ai-game-creator-shell/src/view/project-development/resourceCanvasLayoutModel.ts`、`useProjectResourceCanvasLayout.ts`、`resourceDependencyGraphModel.ts`。
|
||||
|
||||
## 四分区 SVG 不能把跨类型业务关系当成可绘制几何
|
||||
|
||||
- 现象:跨分类资源位于彼此独立滚动和裁剪的 viewport;若仍绘制一条全局 SVG 路径,只会在两个分区中留下没有完整上下文的断线,滚动时还会看似随机出现或消失。同一卡片多边若都锚在中心点,也会让合法的同类型线叠成一束。
|
||||
- 原因:Rust read model 的业务关系范围大于资源管理画布的展示合同;四分区视图没有跨标题栏的合法连线走廊。几何层直接遍历全部 reference edge 等于把业务真相误当成全部可视关系;单中心端口又忽略了边的稳定身份与对端顺序。
|
||||
- 处理:保留 Rust 图与权威深度;布局拓扑按资源分类过滤 reference 和 task-flow 超边切片,关系说明与 SVG 则只消费同类型精确引用。相同分区内按对端坐标、稳定边 ID 为同侧精确边分配有界端口。不要通过改变端点、隐藏同类型合法精确边或生成资源笛卡尔积来换取整洁。
|
||||
- 验证:同时覆盖跨分类精确引用与跨分类-only flow 不聚类 / 不绘制、全部 task-flow 零 SVG / 零画布关系说明、同侧多边端口不重合且重复输入路径一致、同类环 / 自环和 4096 项回归。
|
||||
- 关联:`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`ResourceDependencyOverlay.tsx`、`resourceCanvasLayoutModel.ts`。
|
||||
|
||||
## Linux 生产脚本门禁不能假设本地也是 GNU userland
|
||||
|
||||
- 现象:macOS 本地运行维护页、生产 API 部署和 Rust 产物门禁时,依次出现 `mv: illegal option -- T`、`mapfile: command not found`、`/usr/bin/cp` / `/usr/bin/chmod` 不存在,以及 `.rlib` 明明含有 `.o` 却报告“没有可扫描成员”;安全修复计划还会把 `/var/folders` 到 `/private/var/folders` 的系统别名误判为用户符号链接。
|
||||
@@ -119,12 +222,20 @@
|
||||
- 验证:运行 `npm run check:maintenance-page`、`npm run check:production-api-deploy`、`npm run check:server-rs-ddd`、`npm run test -- scripts/spacetime-repair-editor-canvas-resources.test.ts`,并在 Linux CI 保留同一生产脚本语义。
|
||||
- 关联:`scripts/deploy/maintenance-on.sh`、`scripts/check-maintenance-page.mjs`、`scripts/check-production-api-deploy.mjs`、`scripts/deploy/production-api-deploy.sh`、`scripts/check-module-runtime-artifact.mjs`、`scripts/spacetime-repair-editor-canvas-resources.mjs`。
|
||||
|
||||
## 分区内部滚动不能只重测分区原点
|
||||
|
||||
- 现象:若滚动时只重测分区原点,线会停在旧位置或穿过标题栏;若进一步把“卡片完整位于 viewport”当作关系挂载条件,同一合法关系会在卡片刚触边时突然消失、滚回又出现,箭头也可能恰好落在 clip 外而只剩一截线。
|
||||
- 原因:全局 SVG 与 section plane 不共享 transform / scroll,`getBoundingClientRect + RAF + state` 重建屏幕端点只能异步追赶浏览器合成层;卡片可见性又是显示裁剪状态,不是关系身份。SVG marker 贴卡或贴裁剪边界时还可能只剩主 path。
|
||||
- 处理:每个 section plane 自己持有 SVG,让路径和卡片直接使用同一逻辑坐标与父级 scale / scroll;每区只以一个 Observer 和 RAF 维护逻辑 viewport。精确引用源端或目标端单独离屏时分别绘制 outgoing / incoming 边界继续线,两端离屏才隐藏;目标锚点预留固定箭头间隙,marker 使用 `userSpaceOnUse` 且允许 overflow;自环整体外移避免箭头压卡。搜索隐藏端点仍属于业务可见性过滤,不能与 viewport 裁剪混用。
|
||||
- 验证:覆盖同帧多次 scroll 只调度一次 RAF、部分离屏时 outgoing / incoming 正确切换、两端离屏隐藏、缩放后路径与卡片仍处于同一 plane、箭头可见、分区互不串线,以及每区单 observer 与卸载清理。搜索隐藏任一精确端点时整条橙线隐藏;task-flow 始终不渲染。
|
||||
- 关联:`apps/ai-game-creator-shell/src/view/project-development/ResourceDependencyOverlay.tsx`、`apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts`。
|
||||
|
||||
## External Editor taskId 不能当作本地 manifest taskId
|
||||
|
||||
- 现象:画布资产之间已有橙色精确引用线,但依赖任务之间没有灰色 task flow;测试用 `design-foundation` 之类字符串时正常,真实生成返回 `task-1` 后失败。
|
||||
- 现象:Rust read model、资源详情或 dependency 聚类中缺少本应存在的 task-flow;测试用 `design-foundation` 之类字符串时正常,真实生成返回 `task-1` 后失败。画布不显示灰色 task-flow 是当前产品决定,不能再用是否出现虚线判断 producer 映射是否正确。
|
||||
- 原因:`GameCreationAppAssetSource.taskId` 保存的是 External Editor 生成任务身份,命名空间与本地 `.agent/manifest.json` 的 Agent/task 身份不同;前端用 `taskById.get(source.taskId)` 会让真实画布资产全部失去 producer。
|
||||
- 处理:资源依赖图的 Tauri Rust read model 从有界 `.agent/agent.db` 读取 `agent.runtime.canvas.asset_generate`,以 `assetId -> agentId` 映射 producer,并要求 `agentId` 存在于当前 manifest。记录缺失、多个不同有效 Agent 冲突或读取已截断时失败关闭 producer assignment、task flow 与对应 `cyclicTaskIds`,不回退 `source.taskId`。精确 `asset-reference` 仍只依赖 manifest 中外部 resourceId 的唯一匹配;Rust 独立返回的 `dependencyDepths` 继续作为 manifest / reference read model 权威结果,前端只过滤未知资源、负数、非整数和非安全整数,不得因 producer 截断把它整体清空。
|
||||
- 验证:Rust fixture 把 `source.taskId` 固定为 `task-1 / task-2`,只有审计提供 `art-director / design-foundation` 后才生成 task flow;移除或截断审计后橙色引用保留、灰色任务流消失,合法深度仍为 `asset:spec=0 / asset:ui=1`。AppSurface 使用截断生产数据形状证明深度 `0 / 1 / 2` 真实到达卡片布局,并且不会把已有自动坐标持久化成扁平布局。
|
||||
- 验证:Rust fixture 把 `source.taskId` 固定为 `task-1 / task-2`,只有审计提供 `art-director / design-foundation` 后才生成 task-flow read model;移除或截断审计后该 flow 消失但橙色引用保留,合法深度仍为 `asset:spec=0 / asset:ui=1`。前端始终断言 task-flow 零 SVG;AppSurface 使用截断生产数据形状证明深度 `0 / 1 / 2` 真实到达卡片布局,并且不会把已有自动坐标持久化成扁平布局。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/resource_dependency_graph.rs`、`apps/ai-game-creator-shell/src/view/project-development/resourceDependencyGraphModel.ts`、`docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md`。
|
||||
|
||||
## 依赖图未就绪时不能先初始化资源布局
|
||||
@@ -4202,15 +4313,13 @@
|
||||
- 处理:先以 CAS 单独 commit `queued -> executing`,成功后才调 ToolHost;调用返回后再 commit observation。恢复见到 executing 或 ToolHost 返回 Unknown 时只能进入 reconciliation,不得自动重执行。重复 resume 不得继续增 revision 或重复 event。
|
||||
- 验证:在“ToolHost 已调用、observation commit 失败”处注入故障,序列化快照并用新 engine 重载;断言重复 resume 后 ToolHost 计数仍为 1,且只有显式 reconcile observation 才恢复 running。
|
||||
|
||||
## Runtime pending 恢复不能让大型 async frame 共用默认 worker 栈(2026-08-03)
|
||||
## Runtime 后台执行不能让大型 async frame 共用默认 worker 栈(2026-08-03)
|
||||
|
||||
- 现象:Supervisor collaboration durable isolated spawn 恢复测试在默认 Tokio worker 栈下稳定 `stack overflow`;单独运行同样失败,提高 `RUST_MIN_STACK` 后通过。
|
||||
- 原因:不是业务递归。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、队列继续推进且父任务取消不遗留后台子任务。
|
||||
- 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`。
|
||||
- 现象:Supervisor collaboration durable isolated spawn 恢复测试或普通 `agent.delegate` 后台委派测试在默认 Tokio worker 栈下稳定 `stack overflow`;单独运行同样失败,提高 `RUST_MIN_STACK` 后通过。
|
||||
- 原因:不是业务递归。debug 构建中 pending action continuation、后台 task queue、Agent 主循环,以及 Provider、Codex CLI、Codex app-server 组合模式分发的最大分支状态都会形成大型 async poll frame;恢复路径直接进入下一层状态机、普通后台任务把完整主循环放回默认 worker,或组合 future 进入泛型 helper,都会超过默认栈。
|
||||
- 处理:整个 pending continuation、它进入的后台主循环,以及完成、取消或失败后 drain 同 Agent 后续队列时,都必须跨越独立 Tokio task 轮询边界,使上层 poll 先退栈后再轮询下一层状态机。传入边界的 future 必须先装箱;若泛型 helper 直接持有大型 future,即使随后 `spawn`,调用方 async frame 仍会把它保留在默认 worker 栈上。普通后台任务、静态委派子任务和 manifest ready-task 的首次执行统一复用 16 MiB 专用 Runtime worker,并在 worker 已启动后交接 Agent 任务锁;worker 创建或交接失败要持久化当前 run 失败。Provider 物理请求必须在持久重试 helper 与非持久压缩路径构造完整请求后、进入下层泛型 control/lifecycle helper 前装箱,不能等到底层 helper 才装箱。pending 边界继续保留结构化取消语义,父 continuation 被丢弃时同步 abort 子任务。不得逐个扩大 queue worker 栈,也不得增大 CI 的 `RUST_MIN_STACK` 掩盖问题,否则生产路径仍可能崩溃。
|
||||
- 验证:失败用例必须在未设置 `RUST_MIN_STACK` 时通过;同时覆盖普通后台委派、policy batch 全组、拒绝 pending 后重规划并 drain 下一任务,以及 pending/cancellation 回归,证明任务锁只交接一次、恢复不重复生成 isolated spawn、队列继续推进且父任务取消不遗留后台子任务。另需运行 `background_agent_runtime_can_delegate_task_to_other_agent`、`provider_retry_`、`provider_handoff_`、`response_stream_` 与 Native shell 完整门禁,全部以默认 worker 栈通过。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_start.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_queue.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/pending_execution.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/provider_retry.rs`。
|
||||
|
||||
## Provider 可扩展不能用一个全局 protocol 枚举代替实例隔离
|
||||
|
||||
@@ -4280,7 +4389,7 @@
|
||||
- 原因:把“客户端没有收到结果”误判为“服务端没有受理”,又没有持久保留逻辑请求的幂等键和服务端返回的 `operationId`。托管 MCP 若绕过 External REST router 直接调用 worker 或 SpacetimeDB,也会形成第二套去重与状态语义。
|
||||
- 处理:一次逻辑生成只分配一个稳定幂等键。桌面 Runtime 在 POST 前先把 endpoint、精确请求体字节、SHA-256 和幂等键原子写入私有生成账本并回读一致;收到 `202 + operationId` 后先把账本升级为 `accepted` 再轮询。`accepted` 只恢复 GET;`prepared` 或提交响应丢失时,只允许校验账本身份、配置指纹和请求 SHA 后,以账本保存的原 endpoint、原始正文与同一键恢复同一逻辑 POST,不得重建画布上下文、重组正文或换键。恢复 `202` 后继续 GET,恢复再次 transport 失败仍保留原账本;轮询超时只保留既有 operation 并恢复 GET。game-chat 的 4500 秒硬截止可以结束本轮、关闭预览和客户端,但 executing 的 `canvas.asset_generate` 必须保留 pending action、provider batch 与生成账本;旧 `200` 图集的 `spritesheetResource` 允许为空,此时只在顶层 `spritesheetImageSrc` 是有效下载引用时优先使用,否则回退可用 `objectKey`。`202` 缺 operationId、状态损坏与 `postprocess-failed-source-preserved` 仍进入对账边界;其它 non-blocking warning 继续消费成功结果并单独展示。旧 `200` 兼容不改变权威 External v1 的异步契约。MCP 生成工具必须把 `idempotencyKey` 映射到同一 REST header,并复用同一 External router、owner 和任务账本。这是 External v1 的专用幂等恢复,不是通用副作用自动重放。
|
||||
- 补充:不能把“accepted 分支里没有生成 POST”误当成 GET-only 恢复。若读取账本前仍重做项目/素材目录准备、输出路径预检或请求正文构造,恢复仍可能创建远端资源或在查询 operation 前失败。恢复必须直接使用 durable snapshot;清理必须最后删除 pending 身份锚点,活动 orphan 不得自动删除。完整恢复 future 还要在默认 Tokio worker 栈下验证,不能靠测试环境调大 `RUST_MIN_STACK` 掩盖栈溢出。
|
||||
- 加固:durable snapshot 必须绑定不含明文凭据的 base URL/API Key 配置指纹,配置漂移时恢复 POST 和 GET 都必须阻断。accepted operation 明确 failed 也不能在 observation 持久化前删账本。旧 `200` durable result 只保留允许字段与安全 objectKey/相对路径,签名 URL、query/fragment 和未知字段不落盘。只有首次提交直接返回契约明确的 `400 / 401 / 403` 才可证明未入队并清理 prepared 账本;首次结果已经未知后,恢复请求的临时鉴权错误、超时、冲突、限流、网关错误及其它意外状态均保留同一账本。账本根目录、扫描和删除必须通过受控路径解析逐级拒绝符号链接,不能让项目内链接把清理目标指向项目外。
|
||||
- 加固:durable snapshot 必须绑定不含明文凭据的规范 base URL 服务身份指纹;服务地址漂移时恢复 POST 和 GET 都必须阻断,Developer API Key 轮换则必须继续原 operation。accepted operation 明确 failed 也不能在 observation 持久化前删账本。旧 `200` durable result 只保留允许字段与安全 objectKey/相对路径,签名 URL、query/fragment 和未知字段不落盘。只有首次提交直接返回契约明确的 `400 / 401 / 403` 才可证明未入队并清理 prepared 账本;首次结果已经未知后,恢复请求的临时鉴权错误、超时、冲突、限流、网关错误及其它意外状态均保留同一账本。账本根目录、扫描和删除必须通过受控路径解析逐级拒绝符号链接,不能让项目内链接把清理目标指向项目外。
|
||||
- 代理 DNS:Clash 等透明代理可能把公网对象存储域名解析到 RFC 2544 的 `198.18.0.0/15` fake-IP。下载器只对已通过鉴权 `objectKey` 或受控 legacy path 换签得到的 URL 接受“全部地址均位于该 benchmark 段”的窄例外;直接 URL、其它本机/私网地址、公私混合解析和重定向仍必须失败关闭,不能为了兼容代理整体移除 SSRF 校验。
|
||||
- 验证:覆盖“服务端已入队但提交响应丢失”后两次 POST 的 endpoint、正文 bytes 与 `Idempotency-Key` 完全相同,原键重试仍返回同一 operation,最终只出现一份 completed result 和一次计费 / 写回;恢复再次 transport 失败或临时鉴权失败仍保留同一账本;换 owner 不可见;MCP 与 REST 对同一 owner、同一请求和同一键必须命中同一 operation。
|
||||
- 关联:`server-rs/crates/api-server/src/external_generation.rs`、`server-rs/crates/api-server/src/external_mcp.rs`、`docs/【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md`。
|
||||
@@ -4353,6 +4462,20 @@
|
||||
- 处理:焦点状态机只比较稳定 `resourceId`:`null -> id` 与 `idA -> idB` 聚焦详情,`idA -> idA` 保持当前 active element。显式收起 / Escape 才恢复原卡片与滚动;后台删除清理 focused / matching selected ID 并聚焦搜索框;项目或运行视图切换清空 trigger / restore。媒体预览副作用依赖稳定 ID、路径和类别,不因同 ID 对象重建先卸载控件。
|
||||
- 验证:媒体控件获得焦点后用同 ID 新 manifest 重渲染并断言 active element 不变;删除资源后断言详情关闭、选中清理且搜索框获得焦点;既有收起、Escape、项目切换和运行切换测试继续通过。
|
||||
|
||||
## manifest 与 revision 必须作为同一一致快照发布(2026-08-05)
|
||||
|
||||
- 现象:旧 manifest 的 React effect 在正式素材提交后才读取项目 revision,可能把“旧内容 + 新 revision”发给父级;若它先到,真正的 commit manifest 会被误判为同 revision 分叉并失败关闭。
|
||||
- 原因:manifest 和 mutation revision 分开读取,却把其中任意时刻的两个值拼成一个权威快照;单独比较 callback 到达顺序无法修复这种身份错配。
|
||||
- 处理:普通 Supervisor 投影固定执行“revision 前读 -> manifest -> revision 后读”,两次 revision 相同才发布,漂移时有界重试。素材 command/event 直接使用事务返回的完整 manifest 与对应 revision。父级按 `projectPath + projectId` 单调接受更高 revision,同 revision 只允许内容一致的重复,低 revision 和分叉都不覆盖。
|
||||
- 验证:分别覆盖 command/event 两种先后、成功后旧轮询和同 revision 不同 manifest;不能只用 eventId 去重而跳过 revision 防倒灌。
|
||||
|
||||
## 新资源自动聚焦不能把投影、布局和 DOM 当成同一时刻(2026-08-05)
|
||||
|
||||
- 现象:保存回调已经带回 manifest,但新卡片可能尚无 dependency/type 坐标或尚未提交 DOM;立即选择会得到空画布、错误滚动,迟到回调还会抢走用户后来选择的资源。
|
||||
- 原因:把 durable commit、资源投影、关系图 ready、两份布局协调和 React DOM commit 压成一个“保存成功”布尔值,缺少保存尝试身份和用户意图 generation。
|
||||
- 处理:保存开始记录 `saveAttemptId + sessionId + draftId + commitId + focusGeneration`。自动定位依次等待资源投影存在、dependency/type 两份布局 settled 且都有位置、搜索条件可见和稳定 `data-resource-id` DOM 存在;按 commitId 只执行一次。切项目、切 mode、改选择/搜索、取消或开始新 flow 都推进 generation;迟到结果仍可合并权威 manifest,但不能改变选择。隐藏时保留搜索,只由显式“清除搜索并定位”建立新 generation。
|
||||
- 验证:覆盖 manifest 已更新但布局未完成、DOM 后只聚焦一次、搜索隐藏、保存中切项目/改选择和连续保存;测试不得用 reload 或重开项目绕过阶段边界。
|
||||
|
||||
## 不要用自然语言精确 `.replace()` 维护 Runtime Prompt
|
||||
|
||||
- 现象:Prompt 文案稍作改写、增删空格或调整段落后,替换静默失效,代码中出现难以审阅的链式 `.replace()`。
|
||||
@@ -4443,6 +4566,52 @@
|
||||
- 处理:全部全局 sink 测试共用一把 test-only 串行锁,并由 RAII guard 在 `Drop` 中无条件清空;测试统一使用 `manifest_invalidation_sink_isolation_` 前缀。relay fixture 对 accept 和 payload 分别使用非阻塞轮询与总 deadline,不使用固定 sleep;生产 loopback、token、连接 / 写入超时和 payload 大小校验保持不变。
|
||||
- 验证:用 `--test-threads=2` 重复运行统一 filter,覆盖正常 relay、无事件 accept 超时、不完整 payload 超时、panic 展开清理,以及 GUI owner attach 配置与 guard 清理。
|
||||
|
||||
## 远端图片 completed 不能冒充本地资源创建成功(2026-08-05)
|
||||
|
||||
- 现象:External operation 已返回 completed,但稳定引用缺失、下载失败、正式资产事务中断或 manifest 已提交而 UI 事件丢失时,界面仍可能提前显示“资源创建成功”,重复回调还可能再次下载、写文件或登记资源。
|
||||
- 原因:把远端生成、媒体传输、本地 durability、manifest 投影、布局和选择压成一个 completed 布尔值;同时把 External idempotencyKey、operationId 或 taskId 暴露到公开草稿,导致恢复逻辑从非权威状态重建请求或误绑本地 task graph。
|
||||
- 处理:使用私有 generation ledger 保存原请求、External 身份、稳定远端引用、固定 staging token 与本地 commit 身份;公开面只投影不可逆阶段。启动时先恢复阶段三事务,再恢复原 generation;重复 completed 先检查远端引用、staging 和 committed ledger,只有 `committed | already-committed` 才进入 manifest 投影。用户取消等待只推进 focus generation,不删除账本或伪装远端取消。
|
||||
- 精修补充:`sourceImageSrc` 是可下载的稳定媒体引用,`sourceResourceId` 是资源身份,二者不能因为都可表现为字符串就填同一个 objectKey。本地 `local-asset:*` 只保留在本地 manifest 血缘;没有真实 External resourceId 时省略 `sourceResourceId`。
|
||||
- 验证:覆盖确认前零调用、同 key 连点、accepted 重启 GET-only、重复 completed、取消后迟到、下载后本地事务恢复、事件丢失、切项目/改选择、旧轮询隔离、实时布局与选择、精修血缘及敏感字段零泄漏。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/asset_canvas/generation.rs`、`apps/ai-game-creator-shell/src/features/asset-canvas/AssetCanvasSurface.tsx`、`docs/technical/【技术方案】客户端素材创作无限画布阶段一合同-2026-08-05.md`。
|
||||
|
||||
## 可恢复生成账本不能持久化 direct-upload ticket(2026-08-05)
|
||||
|
||||
- 现象:为支持参考图上传中断恢复,把完整 upload ticket 放进 generation ledger;账本随之包含 Provider host、formFields、policy、signature 或临时 Authorization,项目目录泄露即可复用临时凭证。
|
||||
- 原因:把“恢复所需的稳定远端身份”和“仅供一次上传的临时授权材料”当成同一种持久状态。原子 sidecar 只能保证写入完整,不能让敏感字段变安全。
|
||||
- 处理:ticket 结构不实现 Serialize/Deserialize,host/formFields 只在本次内存调用中使用。账本在上传前只保存稳定 bucket/objectKey;重启先用这组身份调用 object confirm,确认成功后只保留 objectKey/assetObjectId 并清掉上传中间态。账本测试必须直接序列化完整 ledger,扫描 Provider URL、Authorization、policy、signature、API Key 和 ticket 字段名。
|
||||
- 验证:运行 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml private_generation_ledger_never_serializes_upload_credentials_or_provider_url`,并继续检查公开草稿、manifest、事件和普通错误不含 prompt、operationId、Key、绝对路径或媒体正文。
|
||||
|
||||
## 客户端内部用途目录不能直接作为 legacyPrefix(2026-08-10)
|
||||
|
||||
- 现象:本地图片精修或视频、音频等全类型资源编辑点击生成后立即提示参考资源上传失败;私有账本的 `uploadBucket/uploadObjectKey/operationId/requestBodyJson` 全为空,服务端也没有 OSS、confirm、生成或扣费记录。
|
||||
- 原因:direct-upload ticket 的 `legacyPrefix` 不是任意业务目录,而是 `platform-oss::LegacyAssetPrefix` 的权威白名单值。把 `asset-canvas-references` 或 `resource-editor-references` 直接放在该字段会被 api-server 在签名之前以 `400` 拒绝;客户端若把票据、OSS 和 confirm 全折叠成一个错误码,还会掩盖真正失败阶段。
|
||||
- 处理:客户端编辑器统一使用合法私有 `legacyPrefix=generated-character-drafts`,把业务用途放入 `pathSegments`:图片画布为 `editor/asset-canvas-references/<projectId>/<draftId>/<generationId>`,全类型资源编辑为 `editor/resource-editor-references/<projectId>/<operationId>`。仍严格执行 ticket → OSS form POST → object confirm,只有 confirm 返回自洽稳定 `objectKey/assetObjectId` 后才允许提交生成;不要为内部目录扩白名单或新建上传接口。图片路径按本地校验、票据、对象上传、对象确认分别使用安全错误码,票据材料继续只驻留内存。
|
||||
- 验证:客户端端到端测试必须断言 confirm 早于生成 POST、请求使用精确前缀与 pathSegments、账本只持久化稳定对象身份;票据失败时断言 `operationId/requestBodyJson` 为空且 manifest 只有源资产。api-server 测试应断言生成的 key 位于 `generated-character-drafts/editor/...` 且 access 为 private。
|
||||
- 关联:`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`。
|
||||
|
||||
## Tauri 有平台登录 Token 不代表应调用主站画布 API(2026-08-10)
|
||||
|
||||
- 现象:客户端素材画布和全类型资源编辑从 WebView 读取平台 Access Token,把它传给 Tauri command,再调用 `/api/editor/*`、`/api/assets/*` 和 `/api/runtime/external-generation/jobs/*`;代码同时保留 External 分支,导致真实 UI、Runtime 和测试使用不同路径,发布客户端还错误依赖网页画布登录态。
|
||||
- 原因:把“客户端壳有账号登录能力”误当成“客户端画布属于主站网页宿主”。主站和 Tauri 虽复用相同请求 DTO 与后端生成服务,但对外边界不同:主站使用站内认证路由,Tauri 远端媒体能力使用 Developer API Key 和 External v1 路由。
|
||||
- 处理:Tauri 前端不读取、透传或持久化站内 Access Token;Rust 只从发布 AppData 私有 `editorApi.baseUrl/apiKey` 解析 External 凭据。图片、视频、音效、BGM 的项目/素材库、上传、确认、生成、轮询与换签全部留在 `/api/external/v1`,不访问内部 job 查询或账号/profile 接口。账本只绑定 External 配置身份指纹,升级前遗留的站内 endpoint 必须进入待对账状态,不能拿 External Key 自动重放。Key 缺失或无权限只返回安全配置错误,不打印 Key、Authorization、Provider 正文或私有路径。
|
||||
- 验证:前端测试断言 command input 不含 `accessToken/apiKey`;Rust mock 服务器拒绝任何 `/api/editor/*`、`/api/assets/*` 和 `/api/runtime/external-generation/jobs/*` 请求,并覆盖 External `202`、原 operation 轮询、换签、非破坏性本地提交和账本零凭据。主站路由与 OpenAPI 未发生契约变化时不得为了客户端切换修改后端接口。
|
||||
- 关联:`apps/ai-game-creator-shell/src/features/asset-canvas/tauriImageCanvasHostAdapter.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`。
|
||||
|
||||
## prepared journal 之前同样存在正式事务崩溃窗口(2026-08-05)
|
||||
|
||||
- 现象:事务依次安装 before/after 快照后才写 journal;若进程在首个快照、全部快照或 journal 已写但 ledger 未写时退出,重启扫描看到 transaction 目录却无法进入原先只覆盖 prepared 之后的恢复状态机,可能留下孤儿目录或阻塞项目后续提交。
|
||||
- 原因:把 `prepared` 当成事务的第一个可观察持久阶段,忽略了构造 prepared 证据本身也由多次原子文件安装组成。
|
||||
- 处理:把首个快照、全部快照和 journal 后/ledger 前加入故障矩阵。无 ledger 时只允许清理受控快照与本模块临时文件;若 journal 已存在,还必须证明正式目标不存在、manifest 和 project revision 精确等于 before。未知文件、正式文件存在或权威状态漂移全部失败关闭,不能递归猜测清理。清理后同步 transaction 父目录,并允许同 commit/idempotency 身份安全重放。
|
||||
- 验证:故障矩阵逐阶段恢复;额外用同一幂等身份在快照残留清理后提交两次,必须得到一次 committed、一次 already-committed,manifest 仍只有一个 canvas asset。
|
||||
|
||||
## 宿主事件接线不能顺手复制共享 history 栈(2026-08-05)
|
||||
|
||||
- 现象:Tauri Surface 已复用共享 viewport/transform/renderer 数学,却另外维护 undo/redo refs、快照克隆和恢复逻辑;网站共享 hook 后续增加内容安全或字段恢复时,两端会静默分叉。
|
||||
- 原因:把 Pointer 事件接线、宿主生命周期胶水和可复用 history 算法放在同一组件中,误以为没有复制整个画布目录就已经满足共享源码边界。
|
||||
- 处理:两宿主直接消费共享 `useCanvasHistory`;共享 snapshot 统一覆盖 viewport、selection、图层位置和 width/height,宿主只声明本地媒体是否允许安全移除/重做。Tauri 仍可保留 Pointer capture/epoch/host callback 接线,但选择、平移、缩放、变换、renderer 和 history 状态机不得在宿主重写。
|
||||
- 验证:主站 history 定向测试覆盖 resize undo/redo;Tauri 新建、导入、编辑、撤销重做和 durable commit 用例必须在同一共享 hook 下通过。
|
||||
|
||||
## 编辑器生成不能把传输重试、参考图截断和客户端 provenance 当成独立小问题(2026-08-05)
|
||||
|
||||
- 现象:生成 POST 首次已经入队但响应丢失时,客户端自动重试产生第二个任务;第 6 张或更多参考图仍显示在 UI / 元数据里,却没有送给 provider;直接构造请求还能把任意资源 ID 写成最终素材引用。
|
||||
@@ -4541,3 +4710,26 @@
|
||||
- 原因: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`,不能在项目自动恢复时批量失败。
|
||||
|
||||
## 模拟 Provider 的隔离 AppData 测试必须显式固定执行模式(2026-08-11)
|
||||
|
||||
- 现象:测试已经写入本地 mock `baseUrl / apiKey / model`,却收不到任何 HTTP 请求,日志反而显示 Codex app-server 启动或退出;Native shell 全量中多个后台 Agent 用例一起超时。
|
||||
- 原因:新安装和没有迁移上下文的隔离 AppData 默认使用 `codex_app_server`。只写 `agentLlm` 不能表达测试要走 HTTP Provider;直接切换 runtime config dir 的 fixture 也不会经过会自动补 `agentMode` 的测试 helper。
|
||||
- 处理:任何要断言模拟 HTTP Provider 请求的配置都必须显式写 `agentMode=provider`。测试 helper 可以统一补齐,但直接写隔离 AppData 的 fixture 仍须在自身 JSON 中声明,不能依赖仓库 `.env`、用户 AppData 或历史迁移。
|
||||
- 验证:先单跑失败用例确认请求命中 mock server,再执行完整 `npm run check:native-shells`;日志中不得出现该用例启动 Codex CLI/app-server,所有 Provider/MCP 请求数量和顺序按 fixture 闭合。
|
||||
|
||||
## Tauri 生成与资源编辑恢复不能依赖 UI 快照、旧 Key 指纹或队列首项(2026-08-11)
|
||||
|
||||
- 现象:应用重启后,任务视频、项目版本或 refine 草稿无法恢复;轮换 Developer API Key 后已有 operation 被误判为配置变化,已受理任务一次 401/403 还可能永久进入对账;文本 Provider 已成功但尚未 staging 时崩溃会重复调用。目录中放入大量无关文件还能绕过 pending 扫描上限。一条远端已明确失败的老 operation 会持续占据队列首项,挡住后续已受理或已下载任务;manifest 已写而 project revision 未写时,又可能被误标为 committed,或者 journal 已证明提交后因项目继续合法修改而无法补 ledger。durable committed 后遗留 staging 可能因一次删除失败而被误报为提交失败,也可能在正式媒体或 manifest 身份已经漂移时被直接删除;manifest 已有派生子版本而 journal 缺失,或旧 journal 没有 revision 身份时,也可能被猜成已经提交。派生视频再次编辑时若把 `assetObjectId` 当远端引用,生成会失败或指向错误身份。
|
||||
- 原因:早期账本只保存显示层资源 ID,恢复时又依赖当前页面资源对象;refine `draftId` 只在组件 Map;配置指纹混入 Key 并把认证错误写成状态机终态;Provider 正文从内存直接进入解析/staging;扫描计数只在识别出 pending JSON 后递增;本地登记 ID 与 External generation 接受的稳定 `objectKey` 没有分层。恢复 UI 只选排序后第一项,而账本又没有远端终态失败/归档阶段;资产提交恢复把整个历史 after manifest 当作永久相等条件,没有区分目标事务事实与后续合法提交;旧 version journal 只保存 base/target 数值,不能证明完整 project revision before/after 身份。
|
||||
- 处理:新账本冻结完整源快照,旧账本从权威 manifest、完成任务和版本记录有界恢复;refine 从正式 sidecar 按项目、意图、源素材和 active 状态唯一发现。服务身份用 `service-origin-v1` 哈希规范化 External base URL,确认 UI 只展示去除路径与凭据的服务 origin;旧 Key-bound 指纹由快照绑定的显式挑战迁移,确认前零网络动作,已受理任务换 Key 后只 GET 原 operation。Provider 调用前先持久化 request-issued,成功正文再写 durable handoff 后解析/staging;issued 无 handoff 只能对账。扫描在读取每个目录条目时先计数,任何文件都消耗预算。提交前复验源摘要,远端请求只使用账本已确认的稳定 `objectKey`,恢复始终复用原 operation 和请求字节。
|
||||
- 队列与事务:独立恢复面板必须展示后端权威队列的所有 operation,读取失败不能伪装为空。`remote-failed` 不再重放,只能显式标为 `archived` 并保留账本;`reconciliation-required` 不能归档。派生 asset 使用 `prepared -> media-installed -> manifest-written -> revision-written -> committed` journal,只对可证明状态前向恢复;尚未证明目标写入时严格核对 before/after,已证明目标 asset/media 与 target revision 后允许 manifest/revision 被后续合法提交继续推进,并补齐同一 ledger。committed 后只有 staging 与正式媒体摘要一致、manifest 按 ID 或路径唯一精确匹配 journal asset 时才尽力清理;删除 I/O 失败保持 durable committed,身份或媒体漂移保留 staging 并进入对账。version journal 同样冻结 project revision before/after 身份;manifest 已有子版本但 journal 缺失,或旧 journal 面对已推进 revision 无法补证时都失败关闭。
|
||||
- 验证:覆盖跨进程唯一 refine 草稿发现和多候选失败关闭、文本 Provider 成功到 staging 崩溃后零重复调用、任务视频/版本旧账本恢复、所有目录条目上限、Key 轮换与旧 Key 无法验证时的显式确认、Accepted 后 401/403 再换 Key 只 GET 原 operation、远端明确失败只归档且零新网络/扣费、三条乱序恢复队列、项目切换迟到结果、asset transaction 各崩溃阶段、revision 后项目继续合法修改仍补齐 ledger、committed 后 staging 清理成功/删除 I/O 失败/媒体或 manifest 漂移保留、源摘要漂移拒绝、committed 视频二次派生,以及 manifest 子版本缺 journal、旧 version journal 无法证明 revision 推进与 version journal exactly-once。
|
||||
|
||||
## 无限画布延迟草稿与零位移不能制造新状态(2026-08-11)
|
||||
|
||||
- 现象:r5 的 `loadDraft` 比 r6 更晚回包时会把草稿回退;pointerdown 后没有任何移动,pointerup 仍增加一条空 undo 并触发 CAS 保存;Tauri 自行维护 Shift toggle 后,Shift 单击唯一选中图层会意外清空选择。同一 `canvas.failed` 视图还可能把草稿保存或提交故障显示成生成重试。
|
||||
- 原因:延迟回包只与发起时 revision 比较,没有在落地时复核当前最高 revision;指针按下就 capture history,而不是等首次真实几何变化;宿主复制了共享 selection 规则;失败状态没有携带发生故障的 operation 类别。
|
||||
- 处理:generation progress、保存队列、生成/提交回包和延迟 `loadDraft` 统一用当前 scope、触发最低 revision 与回包当下草稿的单调门禁;同 revision 只允许完整相等回包。Tauri 指针和键盘选择复用 `resolveLayerPointerSelection`。pointerdown 只冻结快照,首次真实 move/resize/pan 才 capture 一次;零位移、未变选择和锁定图层不增加 undo、documentVersion 或草稿保存。
|
||||
- 失败边界:`canvas.failed` 必须携带 `generation / draft-save / asset-commit / recovery / cancellation`,只有 `generation` 失败显示“返回修改/重新确认”。保存/CAS 只重试或重载,提交/恢复只安全恢复或对账,取消故障只保留草稿继续编辑;初始恢复失败也不得进入生成重试。
|
||||
- 验证:用 deferred Promise 覆盖 r5/r6 逆序、保存与 progress 交错和 scope 切换;同时覆盖 Shift 单选自身、多选拖动、指针完整序列、零位移、首次有效移动只一条 history,以及五类失败的可访问名称与按钮集。
|
||||
|
||||
Reference in New Issue
Block a user