Merge remote-tracking branch 'origin/master' into feat/gptimage2to2.5
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m3s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m55s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 7m25s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 8m18s
Project CI / Backend tests (pull_request) Successful in 8m6s
Project CI / Native shell tests (pull_request) Successful in 9m16s
Project CI / Frontend tests (pull_request) Failing after 7m0s
Project CI / Repository checks (pull_request) Successful in 7m8s
Project CI / AI game creator shell web tests (pull_request) Failing after 5m16s

This commit is contained in:
2026-09-21 14:43:20 +08:00
138 changed files with 21864 additions and 350 deletions
@@ -0,0 +1,63 @@
# 后台模板管理
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | implemented-awaiting-runtime-acceptance(本地验收已过;按验收口径未对真实 OSS 写入) |
| Date | 2026-09-19 |
| Parent Spec | `docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md` |
## 目标与范围
后台管理员按权限查看并编辑 AGC 模板名称/简介/标签/封面,支持上架/下架。用户已明确选择此范围,不新增模板、ZIP 上传、版本发布、删除、部署或提交。
## 合同与依赖
OSS index 单一真相,active/inactive 两组;CLI/Rust 共用不可变对象及互斥发布锁;客户端沿用 active 读取。后台现有鉴权/权限/组件复用;不改 SpacetimeDB schema。
## 验收
- 页面、导航、权限和请求/响应合同一致,未授权无读取/写入。
- 编辑、封面、上下架真实通过存储适配器写回;ZIP与版本不变,其他模板及未知字段保留。
- 并发、旧revision、失败/未知写入和页面迟到响应失败关闭;CLI不重新上架下架项。
- 定向测试/类型/构建/编码/格式与文档检查通过;本地API健康检查与桌面/移动受控浏览器smoke,真实OSS不写。
## 验收证据(2026-09-21)
### 合同与权限
- 路由合同:`admin_templates::tests` 4 项(封面按真实字节与尺寸校验、两组合并快照且封面走可信 URL、尺寸/体积上限在发布前拒绝、**编辑必须携带精确快照 revision**)通过;`admin::tests::agc_template_routes_require_the_template_tab_permission` 断言只有 `agc-templates` tab 权限可读可写。
- 领域规则:`module-assets` 5 项(active/inactive 往返不丢字段、封面编辑保留嵌套 metadata 扩展、拒绝来自其它模板/包的 metadata、拒绝重复 ID 与不安全引用、Unicode 与标签上限按字符计)通过。
- 存储与锁:`platform-oss` 11 项(不可变对象冲突必须一致回读、断开后留锁不删不重试、结果未知不重试不释放、锁 owner 变化不删别人的锁、JS 侧锁与发布者互斥、未知/不安全 versioning 绝不 PUT、索引读取有界且公共读不带凭据)通过。
- CLI 与清单合并:`node --test scripts/agc-template-library-publish.test.mjs` 25 项通过,含「定向发布保留线上已有模板与库字段,只替换选中的 ID」「锁删除失败不能报告发布成功」「真实 CLI 拒绝清理历史对象且不发网络请求」。
### 本地 API(`npm run dev:api-server -- --api-port 8099`,SpacetimeDB `127.0.0.1:3102` / `genarrative-game-creator-dev`)
- `GET /healthz` → `200 {"ok":true,"service":"genarrative-api-server"}`;启动日志显示 AGC 项目快照 OSS 客户端启用(bucket `agc-dev`)。
- 未授权(无令牌):`GET /admin/api/agc-templates` → `401`、`PUT /admin/api/agc-templates/cocos-empty-2d` → `401`、`GET /admin/api/me` → `401`。读取与写入都在进入 handler 前失败关闭,没有副作用。
- `POST /admin/api/login`(owner)→ 会话 token,`tabPermissions` 含 `agc-templates`;`GET /admin/api/agc-templates` → `200`、`Cache-Control: no-store`、`writable=true`、`revision=ecc9d464ae7e4af9…`、9 个模板(4 个原有起步工程 + 5 个 Cocos 官方模板,封面 host 均为 `agc-dev.oss-rg-china-mainland.aliyuncs.com`)。
- **未执行任何写请求**:授权 PUT 与保存按钮都没触发,真实 OSS 未被写入;写路径由上述存储适配器与 CLI 用例覆盖。
### 受控浏览器(Chrome,admin-web 5199 → API 8099)
- 桌面:侧栏出现「模板管理」;页面含刷新、搜索(名称/ID/标签)、运行时(全部/Cocos/HTML)、上架状态(全部/已上架/已下架)与 9 行表格(封面、名称+ID+简介+标签、引擎版本、包大小、状态、编辑/下架)。
- 筛选:上架状态切到「已下架」→ 显示「没有符合筛选条件的模板」(当前 9 条全部已上架),inactive 投影为空符合预期。
- 编辑面板:预填名称/简介/标签、封面预览与「更换封面」入口;点击「取消」关闭,**未保存**。
- 移动端 390×844:`scrollWidth=390=innerWidth`(无横向溢出),卡片式布局 + 底部导航(Dashboard/服务总览/表查询/API 调试/埋点数据…),9 条模板仍可读。
- 会话获取方式:为不把口令写进命令文本,用 API 签发的会话 token 通过 CDP 注入浏览器 `localStorage` 后整页加载;结束后已清理 token 临时文件、剪贴板与视口覆盖。
### 静态检查
- `npm run check:encoding`、`npm run check:doc-index`、`npm run check:production-ops`、`cargo fmt --all -- --check`(两份 manifest)通过;admin-web `typecheck` 与 39 项 vitest、AGC 壳 `template_library` 18 项通过。
### 仍未覆盖
- 真实 OSS 的编辑/上下架写入与「其它模板、未知字段在真实 bucket 中原样保留」只由适配器用例与 CLI 用例证明,未对 `agc-dev` 执行写入(验收明确要求不写)。
- 桌面/移动真机(iOS/Android 浏览器)未测,只有 390×844 受控视口。
## 复审记录(2026-09-21)
- 写路径(`admin_templates::admin_update_agc_template` → `update_template`)顺序正确:先校验封面字节/尺寸,再取发布锁 → 读清单 → 校验 revision(不一致 `409 TEMPLATE_LIBRARY_CONFLICT`)→ 定位模板 → 写内容寻址的封面与 metadata → 提交 index → 释放锁;请求正文由独立任务持有,HTTP 断连不会在 index PUT 在途时提前解锁,异常分支保留「刷新核对 / 锁仍占用联系运维」文案。
- 后台 DTO 只携带名称/简介/标签/上下架/封面/expectedRevision,结构上无法改动 ZIP、版本或其它模板,符合「ZIP 与版本不变、其他模板与未知字段保留」;未知字段保留由 `module-assets` 的 metadata 往返用例证明。
- 封面公共域名在 api-server 侧是常量 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com/`,与「模板库固定位于 `agc-dev` bucket」的既有合同一致(客户端也只信任该主机,可用 `AGC_TEMPLATE_LIBRARY_BASE_URL` 覆盖但仍限于该主机);存储适配器在发现配置的 bucket/endpoint 不是该组合时直接失败关闭,因此不存在「读到别处清单」的路径。若将来模板库换 bucket,这三处必须一起改。
- 「页面迟到响应失败关闭」由 `readGeneration` 计数 + `AbortController` 实现(卸载、token 切换、写后重载都会作废旧响应);刷新按钮在读取中禁用,因此不存在用户可触发的并发同 token 刷新,该判据由既有「token 切换后忽略旧列表和未确认写操作」与 409 用例覆盖,无需新增用例。
@@ -71,6 +71,15 @@
- 影响范围:`apps/ai-game-creator-shell/src/features/resource-canvas/{ResourceCanvasAssetGenerationTasksPanelView.tsx,resourceCanvasAssetGenerationTasksSidebar.css}`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/tests/{resourceCanvasAssetGenerationTasksPanel.test.tsx,resourceCanvasAssetGenerationTasksSidebarStyle.test.ts,resourceCanvasGenerationTasksSidebarDismiss.test.tsx}`、PRD §3.10。未动 Rust、SpacetimeDB、`packages/`、共享弹窗组件。
- 已知未覆盖:真实客户端观感(右上角坐标相对画布顶边的落点、与运行表现层版本入口的间距)未在 Tauri 里目视确认;窄屏(≤480px)只有声明级断言。
## 2026-09-21 画布绑定前置查询收口为项目摘要,失败文案补因链
- 背景:dev 上「AI 生成图片」连续失败,卡片显示 `解析读取外部画布项目响应失败:error decoding response body`,每条恰好 `1 分 00 秒`;同批的远端资源编辑终态只提示「已明确失败」,用户看不到原因也看不到下一步。(同批「图标素材切片超过 64 个」已由本文件「图集切片上限:客户端结果门从 64 对齐到平台契约的 256」条目决策,这里不再重复。)
- 决策(项目列表视图):`GET /api/editor/projects` 与 `GET /api/external/v1/editor/projects` 共用同一套 `view` 取值与摘要投影(缺省 `full` 保持兼容,未知取值失败关闭);`summary` 只回传 `projectId / title / updatedAt / cover`,既不做内联媒体修复,也不带画布与全量资源。投影实现收敛到 `editor_project.rs` 一份,外部 API 与 MCP 复用同一份;AGC 画布绑定前置查询固定使用 `?view=summary`,它只需要 `projectId`。
- 决策(错误文案与终态出口):外部请求失败文案补 kind 语义与底层因链(`reqwest::Error` 的 `Display` 只有 kind,超时 / 正文截断 / 非法 JSON 显示成同一句话),且不拼接 URL;远端资源编辑终态文案带出稳定失败码并指向唯一出口「移出恢复队列」,上游原文继续不写入账本。
- 影响范围:`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/api-server/src/external_editor_api.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs`、`project/resource_editor.rs` 与对应夹具。
- 验证方式:`cargo test --locked -p api-server -- summary`(含站内 `view=summary` 跳过媒体修复、未知 view 返回 400 的新用例)、AGC 壳 `agent::generation::`(99 项)与 `project::resource_editor::`(59 项)、`cargo fmt --check`、`npm run check:encoding`、`git diff --check`。
## Unity 与 Godot 常用操作指导
两种编辑器的操作指导复用客户端审核 Skill pack:DirectProject 通过原生 Skill 或既有审核资源读取入口按需取得,Agent Runtime 的对应执行工具说明嵌入同源参考。指南不改变插件可用性、执行授权或 Runner 回执;只读说明不能证明编辑器已连接。常用示例与执行失败/部分修改、保存、撤销边界在同一参考中维护,避免提示词和文档各存一份代码。
@@ -14,6 +14,10 @@
Copy Artifact 插件在**非 SYSTEM 认证**下按「认证用户」判权:只有当被复制 Job 的 `CopyArtifactPermissionProperty`(仓库里由 Declarative 的 `copyArtifactPermission(...)` 维护)显式列出当前消费者,或者该 Job 对认证用户开放 Item.Read 时才放行;`ACL.SYSTEM2` 的定时构建会短路通过。因此会出现「定时调度一路成功、手动发布必挂」的现象(2026-09-21 手动发布 #6/#7 与同期的用户触发探测全部命中,定时调度 #104+ 正常)。`Genarrative-Agc-Global-Version-Issue` 生产权限模式的授权名单必须同时包含 `Genarrative-Scheduled-Revision-Trigger` 与 `Genarrative-Manual-Build-And-Deploy`;改完 `copyArtifactPermission` 后要先跑一次发号 Job 把 Job property 写回 Jenkins,只改仓库文件不生效。
## 手工发布目标不会自动映射成 AGC 更新渠道
`Genarrative-Manual-Build-And-Deploy` 的 `DEPLOY_TARGET=release` 只控制 Stdb / API / Web 全量发布,不会自动成为 AGC 的 `AGC_UPDATE_CHANNEL`。2026-09-21 的手工发布 #10 就因此让 Windows #107 与 macOS #16 使用默认 `dev`,把 `0.1.95` 上传到 `agc/dev-win`、`agc/dev-mac`,而 `agc/release-win/latest.json`、`agc/release-mac/latest.json` 保持 404。现行口径:手工入口按 `release -> release`、`development -> dev` 同时给 Windows 与 macOS AGC Build 传 `AGC_UPDATE_CHANNEL`;补发已烧号的同一版本时用相同 `AGC_RELEASE_VERSION` 直接重跑两条 AGC Job,不重新发号。OSS 发布对象是 `agc/<channel>-win|mac/`,不存在 `agc/release/` 这一层。
## 同一条链路两处上限不一致:平台合法产出被客户端整条丢弃
- 现象:客户端报「生成素材失败:platform-generation-result-unknown: 异步生成完成结果无法绑定到 operationId:External Editor 旧同步结果的图集切片超过 64 个」,而平台侧这次生成**其实已经成功并切完图**(任务账本耗时正常、`assetId` 为空、没有任何素材落盘,付费产物被丢)。
@@ -42,6 +46,22 @@ Copy Artifact 插件在**非 SYSTEM 认证**下按「认证用户」判权:只
`Genarrative-Agc-Windows-Build` 在阶段里用 `AGC_WINDOWS_PATH` 整体替换 PATH、不继承节点机器的 PATH,所以 Godot C++ 引导需要的 CMake 与 Python 必须显式写进这份白名单,装在机器 PATH 上并不生效。2026-09-21 的 #97–#99 连续失败都停在 `Get-Command cmake.exe`(#93–#96 是更早的手写 C ABI 在 MSVC C 模式下的对齐问题):节点只有 Visual Studio Build Tools(`C:\BuildTools`)自带的 CMake 3.31,缺 Python 3。修复后白名单包含 `C:\BuildTools\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin`、`C:\Python312`、`C:\Python312\Scripts`,preflight 校验 CMake ≥3.25、Python 3 和 Visual Studio 17 2022 生成器;把 `cmake.exe` 单独复制到别的目录会丢掉 `share/cmake-*/Modules`,不能替代加入安装目录。新节点的 Python 用 `python-3.12.10-amd64.exe /quiet InstallAllUsers=1 TargetDir=C:\Python312 PrependPath=1 Include_launcher=1 InstallLauncherAllUsers=1` 静默安装即可,CMake 不必另装。
## AGC 画布绑定前置查询不能取全量项目列表
- 现象:dev 上「AI 生成图片」连续失败,卡片显示 `解析读取外部画布项目响应失败:error decoding response body`,每条恰好 `1 分 00 秒`(三条同因,各自独立计时)。
- 原因:绑定前置的 `GET /api/(external/v1/)editor/projects` 缺省 `view=full`,会把账号下每个项目的画布与全量资源一起返回(19 个大项目的 fixture 就已超过 4 MiB);客户端这条请求只有 60 秒预算,卡在读正文时被 reqwest 总超时打断。而 `reqwest::Error` 的 `Display` 只打印 kind,超时、正文被截断和非法 JSON 显示成同一句话,现场看不出根因。
- 处理:只确认项目身份的消费者固定取 `view=summary`(站内与外部路由都支持,缺省 `full` 不变,未知取值失败关闭);摘要视图不做内联媒体修复、不带画布与全量资源;外部请求失败文案补 kind 语义与 source 因链,且不拼接 URL。
- 验证:站内路由用例断言 `view=summary` 不回传 `canvas / layers / resources` 且不触发媒体修复、`view=unknown` 返回 400;AGC 壳用例断言失败文案不再等于 `error decoding response body`、补出因链且不含绝对地址。
- 关联:`server-rs/crates/api-server/src/editor_project.rs`、`server-rs/crates/api-server/src/external_editor_api.rs`、`apps/ai-game-creator-shell/src-tauri/src/agent/generation/canvas_generation.rs`。
## 远端资源编辑终态必须指出唯一出口
- 现象:「生成背景音乐」再次提交 0.1 秒就失败,卡片只有 `remote-terminal-failed: 远端资源编辑已明确失败,不允许再次请求`,既没有原因也没有下一步。
- 原因:上一次同 `operationId` 的请求被平台确定性拒绝(HTTP 400 或任务 `failed`)后,账本落到 `remote-failed`,之后所有重试都在 `ensure_resource_edit_phase_resumable` 失败关闭;唯一出口是「待恢复资源编辑」里的移出恢复队列,但终态文案没有指向它。
- 处理:终态文案带出稳定失败码,并明确「先在待恢复资源编辑中把它移出恢复队列」;上游失败原文仍不写入账本(只存分类码),首次失败的原始拒绝说明继续由当次错误文案承担。
- 验证:`remote_failed_status_is_terminal_and_can_only_be_archived`、`submission_bad_request_is_terminal_while_gateway_failure_requires_reconciliation` 等资源编辑用例继续通过,账本序列化不含上游失败原文。
- 关联:`apps/ai-game-creator-shell/src-tauri/src/project/resource_editor.rs`。
## Tauri `--no-sign` 会连带跳过 updater 签名
AGC macOS 发布入口一度传入 `--no-sign`(目的是绕过没有 Apple 证书的代码签名),结果 Tauri 打印 `Warn Updater signing is skipped due to --no-sign flag.`,产物只有 `*​.app.tar.gz` 而没有 `.sig`,发布入口按设计在「缺少更新包签名」处失败关闭(2026-09-20 首次 Jenkins 实跑命中)。正确做法是不传 `--no-sign`,改为剥离 `APPLE_*` 凭据让 Tauri 跳过 Apple 签名——minisign 更新包签名与 Apple 代码签名这两个开关在 Tauri 里并不独立。Apple 签名状态要按 `codesign -dv` 实测记录,不能硬编码。
@@ -51,6 +51,8 @@ SpacetimeDB crate、SDK、CLI / standalone 与生成 bindings 按 `2.8.3` 对齐
## AGC DirectProject 与 UI workflow
- AGC 模板库包含 Creator 3.8.8 的四个官方 Cocos 模板;Cocos 建项复用原生导入,重建项目 UUID 并保留场景与资源。发布使用内容地址保留历史对象,并在确认 Bucket 从未开启版本控制后获取排他锁;`--only` 在锁内合并最新清单,清单写入结果不明时留锁,同版本 ZIP 变化拒绝发布。详细合同见 [AGC 模板库与模板建项](../../technical/【技术方案】AGC模板库与模板建项-2026-09-17.md)。
- AGC 的本地 `llm.customEnabled` 默认关闭,只能手动修改配置文件;开启后设置支持自定义 Responses 端点、读取 `/models`、勾选和预览 `visibleModels`。对话下拉只显示勾选项,LLM 请求经客户端凭据代理直连自定义上游;不会回退官方中转,平台资源服务仍使用账号权限。详见 AGC 后台模型别名与对话选择规范。
- DirectProject 对话先在完整历史中按回合/原始 item 身份关联,再分页渲染;每个回合只有一个呈现入口。有流按 item `seq` 交替文本和工具,无流采用历史正文;禁止位置猜配或同时展示累计回复与 item 正文。流写入单调归并,收尾等待落盘任务,不按磁盘“最后一段”猜最终回复位置。详见 AGC 实施计划的“DirectProject 回合展示唯一归属”。