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 用例覆盖。
### 受控浏览器(Chromeadmin-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 packDirectProject 通过原生 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: 异步生成完成结果无法绑定到 operationIdExternal 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 回合展示唯一归属”。
@@ -19,51 +19,73 @@ AGC 客户端接入公共 OSS 上的**游戏模板库**(真·游戏模板,
- 验证入口:后台灰度页面测试、`useTemplateLibrary.test.tsx`、AppSurface 实际挂载的 template 用例、原生 `template_library` 测试和后端 `frontend_runtime_config` 测试。退出开始使用既有平台会话代次立即撤销,建项返回、revision 读取和预览核验后的旧回调均不得导航或登记最近项目;同主体 token 轮换不误撤销原生身份。
- 本地隔离数据库已验证 Gate 经后台 API 保存后可重新读回,匿名运行时配置为 false;后台受控浏览器 smoke 验证桌面和 320px 布局及保存确认交互。线上 OSS 下载和正式安装包登录后的端到端操作不由这些测试替代。
## 后台模板管理
- 后台新增 `#agc-templates`「模板管理」页签,复用现有后台布局、列表、公共表单/独立弹窗及写入确认。提供名称/ID/标签搜索、运行时和上下架筛选,展示封面、名称、简介、标签、引擎/版本、包大小与上架状态。
- 本轮只允许编辑名称、简介、标签、封面和上架状态;不新增模板、不上传 ZIP、不修改 ID、运行时、引擎或模板版本,不删除包、历史对象或已建项目。
- 唯一数据真相仍是 OSS `templates/index.json``templates` 保存上架条目,新增可选 `inactiveTemplates` 保存下架条目,两个数组之间 ID 唯一。后台合并展示两组;AGC 客户端仍只读取 `templates`,刷新后不展示下架项。下架不是资源访问撤销,旧清单缓存和已下载项目不受影响。下架条目元数据位于公开清单,不承载私密草稿。
- 后台读取/写入分别为 `GET /admin/api/agc-templates``PUT /admin/api/agc-templates/{id}`,均经过现有后台认证及 `agc-templates` 页签权限。owner 默认可用,member 需显式分配该权限;不新增数据库表或 schema。部署时后台、API 与引用权限白名单的 SpacetimeDB 模块需同步更新,成员账号才可保存新页签权限。
- GET 返回 `{ revision, writable, templates }`:revision 是完整原始清单字节的 SHA-256;每条包含 `id/title/summary/tags/runtime/engine/engineVersion/templateVersion/enabled/coverUrl/zipSizeBytes`。不可用或格式错误返回可诊断错误,不能当作空模板库。
- PUT 请求 `{ expectedRevision, title, summary, tags, enabled, cover? }`,禁止未知字段。名称 trim 后 1–80 字符、简介最多 1000 字符、最多 16 个去重标签(每项 1–32 字符)。封面可选 `{ contentType, dataBase64 }`,仅接受真实 PNG/JPEG/WebP,解码前后校验,原始字节最多 5 MiB、单边最多 4096 像素且不超过 1600 万像素;原有 SVG 引用可继续展示。HTTP body 上限 8 MiB。返回与 GET 同形的最新快照。
- 写入复用同一 OSS 发布锁、版本控制前置检查及不确定清单写入留锁协议;在锁内读取最新清单并比较 expectedRevision,过期或锁忙返回 409,不自动重试写入。封面及更新后的 template.json 以内容摘要键写入并回读校验,再一次提交清单;ZIP、版本和未知元数据字段保留。清单响应不明返回 503 并留锁,后续需核对;任何 API 错误不回显凭据或上游正文。
- 页面保存/上下架前使用既有确认交互。编辑弹窗保持独立;保存中防重复提交,失败保留输入并展示错误,409 引导刷新后重新编辑;刷新、切页、换账号和晚到响应不能覆盖新页面状态。封面仅作为待保存草稿预览,成功后使用后端返回的正式地址。
- 模板 OSS 目标固定为当前 AGC 公开 bucket/endpoint,不能误用通用素材 Bucket。凭据优先成套读取 `GENARRATIVE_AGC_TEMPLATE_LIBRARY_OSS_ACCESS_KEY_ID/SECRET`,两项均未配置时可成套复用已有 `ALIYUN_OSS_ACCESS_KEY_ID/SECRET`;半配置不拼接回退且禁止写入。无可用写凭据时后台可只读,写接口返回 503,页面明确不可保存。
- CLI 与后台共享锁和清单合同。CLI 合并保留未选条目与 `inactiveTemplates`,更新下架模板仍保持下架;新增模板默认上架,CLI 不承担删除或上下架。显式发布选中 ID 时,展示字段按该模板源更新,后台编辑结果持续有效直到下一次显式发布该 ID。两端都必须保留另一组条目,不能因本地模板源较旧而抹掉后台记录。
- 验收包含权限、入口挂载、过滤、编辑与图片校验、上下架往返、未知字段保留、过期 revision/锁争用、上传失败与未知提交、CLI 对下架项的更新/保留,以及桌面/窄屏真实浏览器验证。真实 OSS 写入和生产部署不在本轮验证范围,使用隔离存储替身。
## OSS 契约
```text
templates/
index.json # 模板库清单,客户端唯一读取入口
.publish-lock.json # 发布互斥锁,不供客户端读取
v1/<templateId>/
template.json # 模板元数据(含文件级摘要)
template.zip # 模板正文,zip 根 == AGC 项目根(如 game/index.html
cover.(png|jpg|webp|svg) # 封面图(卡片展示,客户端 <img> 直接取
sha256/<zipSha256>/template.zip # 模板正文,zip 根 == AGC 项目根
sha256/<coverSha256>/cover.<ext> # 封面图
sha256/<metadataSha256>/template.json # 单模板元数据(含文件级摘要
```
`index.json`schema `agc-template-library.v1`):
| 字段 | 说明 |
| --- | --- |
| `schemaVersion` | 固定 `agc-template-library.v1`;破坏性变更换 schema,不原地改语义 |
| `library` / `libraryVersion` / `updatedAt` | 库标识、库格式版本、本次更新时间 |
| `templates[].id` | 稳定标识,`[a-z0-9][a-z0-9._-]{0,63}`,同时是目录名 |
| `templates[].title/summary/tags[]` | 展示与搜索/筛选用文案;`tags` 参与标签筛选与关键词命中 |
| `templates[].runtime` | `html` / `unity` / `godot` / `cocos` |
| `templates[].engine` / `engineVersion` | 引擎标识与版本(如 `phaser` 4.2.1、`three.js` 0.180.0 |
| `templates[].templateVersion` / `updatedAt` | 模板内容版本;客户端按它判断是否需要重新下载 |
| `templates[].entry` | 解压后的项目入口相对路径,如 `game/index.html` |
| `templates[].zipKey` / `zipSizeBytes` / `zipSha256` | 模板包对象键、字节数、SHA-256(下载后强校验) |
| `templates[].coverKey` / `coverWidth` / `coverHeight` / `coverSha256` | 封面对象键与尺寸/摘要 |
| `templates[].metadataKey` | 单模板元数据对象键(`template.json` |
| 字段 | 说明 |
| --------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `schemaVersion` | 固定 `agc-template-library.v1`;破坏性变更换 schema,不原地改语义 |
| `library` / `libraryVersion` / `updatedAt` | 库标识、库格式版本、本次更新时间 |
| `templates[].id` | 稳定标识,`[a-z0-9][a-z0-9._-]{0,63}`,同时是目录名 |
| `templates[].title/summary/tags[]` | 展示与搜索/筛选用文案;`tags` 参与标签筛选与关键词命中 |
| `templates[].runtime` | `html` / `unity` / `godot` / `cocos` |
| `templates[].engine` / `engineVersion` | 引擎标识与版本(如 `phaser` 4.2.1、`three.js` 0.180.0 |
| `templates[].templateVersion` / `updatedAt` | 模板内容版本;客户端按它判断是否需要重新下载 |
| `templates[].entry` | 解压后的项目入口相对路径,如 `game/index.html` |
| `templates[].zipKey` / `zipSizeBytes` / `zipSha256` | 模板包对象键、字节数、SHA-256(下载后强校验) |
| `templates[].coverKey` / `coverWidth` / `coverHeight` / `coverSha256` | 封面对象键与尺寸/摘要 |
| `templates[].metadataKey` | 单模板元数据对象键(`template.json` |
约束:
- 所有对象键必须落在 `templates/` 前缀内;客户端只用「受信任 OSS 主机 + 对象键」自行拼 URL,**不直接信任清单里的地址**。
- 任何一项校验失败(schema、标识符、sha256、尺寸、键前缀)都让整次清单读取失败,前端拿到的是全有或全无的清单。
- 模板源在仓库 `apps/ai-game-creator-shell/template-library/``v1/<id>/{meta.json, project/**, cover.(png|jpg|webp|svg)}``template.zip` **不落仓库**,由脚本按 `project/` 现场打包(条目排序、固定时间戳,同内容重复打包摘要一致)。
- 上传与校验由 [`scripts/agc-template-library-publish.mjs`](../../scripts/agc-template-library-publish.mjs) 完成:`--source apps/ai-game-creator-shell/template-library [--dry-run] [--prune]`,脚本生成 `template.json``index.json`、上传后回读 zip 摘要;`--prune` 清理该模板前缀下本次没有产出的旧对象(例如换封面扩展名后的残留)
- 当前模板:`blank-web`(空白网页)、`blank-2d-canvas`(空白二维画布)、`blank-3d-scene`(空白三维场景)、`phaser-2d-starter`Phaser 2D 起步工程)、`threejs-3d-starter`Three.js 3D 起步工程)
- 上传与校验由 [`scripts/agc-template-library-publish.mjs`](../../scripts/agc-template-library-publish.mjs) 完成:`--source apps/ai-game-creator-shell/template-library [--dry-run] [--only <id,id,...>]`。ZIP、封面和元数据分别以自身字节的 SHA-256 定位,只创建新对象或复用逐字节校验一致的已有对象;全部对象回读一致后才更新 `index.json`。失败不回收已上传对象,旧清单及其引用始终可读
- 只更新指定模板时使用 `--only <id,id,...>`,在发布锁内读取最新清单,只替换指定 ID,其余条目和未知扩展字段保留。首次清单 404 可由本次选择初始化;读取异常或清单非法时停止。全量发布也遵守相同锁与版本门禁
- 正式发布先通过 `GetBucketVersioning` 确认 Bucket 从未开启版本控制,再用 `x-oss-forbid-overwrite: true` 原子创建 `.publish-lock.json`;版本控制 Enabled、Suspended、检查无权限或无法判定时均在写入前停止。所有写同一清单的发布进程必须使用此锁,发布期间不得改变 Bucket 版本控制配置。锁没有自动过期或抢占机制,已被占用时直接失败,重新执行须重新获取锁并读取最新清单。
- 只释放本任务已明确获取且 owner 标识仍一致的锁。获取结果不明时不猜测删除。正文对象不可变,其写入失败可安全释放本任务的锁;清单 PUT 已发起后若遇到断连、超时或服务端 5xx 等不确定结果,必须保留锁并报错,防止旧在途请求晚于下一发布者写入。清单收到确定成功或确定拒绝响应后才进入正常解锁路径;不自动重发清单写入,也不凭一次 GET 猜测在途 PUT 已结束。遗留锁须在确认原请求及进程已终止或完成后由运维处理;释放失败必须报告,不伪装为发布成功。
- `--dry-run` 仅构造和读取合并计划,不读取凭据、不获取锁、不 PUT/DELETE。发布不删除历史对象,也不提供随发布清理的选项;旧客户端缓存和未完成下载可能仍引用旧键。
- 同一 ID、同一 `templateVersion` 的 ZIP 大小或摘要改变时,在上传正文前拒绝,要求更新模板版本;只把相同 ZIP 迁移到新键可保留版本。客户端现有 `<id>/<templateVersion>` 缓存行为不变。
- 当前模板:`blank-web`(空白网页)、`blank-2d-canvas`(空白二维画布)、`blank-3d-scene`(空白三维场景)、`phaser-2d-starter`Phaser 2D 起步工程)、`threejs-3d-starter`Three.js 3D 起步工程),以及 Cocos Creator 3.8.8 的 `cocos-empty-2d``cocos-empty-3d``cocos-empty-3d-hq``cocos-hello-world`
- Cocos 内容来自 Creator 3.8.8 随附的 `resources/templates/{empty-2d,empty,empty-quality,hello-3d-world}`,保留官方资源、`.meta`、设置和模板预设;补齐 `package.json.creator.version`,空模板以 `assets/.gitkeep` 保证资源目录进入 Git 和 ZIP。`entry``package.json`,不打包编辑器生成的缓存或用户项目数据。
- Cocos 建项在复制后按实际 `package.json.creator.version + assets/` 识别,复用既有 Cocos 导入流程,写入 `cocosProjectRoot: "."`;每个新项目重建 `package.json.uuid` 并写入所选项目名。只创建 `.agent` 管理目录,不生成 Web 占位入口;模板源与本机安装缓存不被改写。
- 客户端可用 `AGC_TEMPLATE_LIBRARY_BASE_URL` 覆盖库地址;只接受 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com`(拒绝其他主机、路径、http)。
## 客户端实现
### Rust`apps/ai-game-creator-shell/src-tauri/src/template_library.rs`
| 命令 | 行为 |
| --- | --- |
| `fetch_game_template_library` | 读 `templates/index.json`(≤4 MiB),校验后缓存到 `<app_data>/templates/index.json`;网络失败时回退本机缓存并在 `source``cache` |
| `download_game_template` | 取清单里对应条目,流式下载 zip(≤512 MiB),校验字节数与 SHA-256,解压到 `<app_data>/templates/installed/<id>/<version>/`,最后写 `installed.json` 作为安装完成的唯一标记 |
| `create_automatic_local_game_project_from_template` | 需要时先安装模板,然后在 `<app_data>/projects/` 下按既有自动工作区规则建目录:先复制模板文件,再走 `init_local_game_project_at``.agent` 清单与标准目录。根目录可用 `projectsRoot` 覆盖(必须来自本机目录选择器并通过私有路径门禁),未指定时仍是 `<app_data>/projects/`见 [`【实施计划】AGC项目创建目录可选-2026-09-17.md`](../project-memory/plans/【实施计划】AGC项目创建目录可选-2026-09-17.md) |
| 命令 | 行为 |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fetch_game_template_library` | 读 `templates/index.json`(≤4 MiB),校验后缓存到 `<app_data>/templates/index.json`;网络失败时回退本机缓存并在 `source``cache` |
| `download_game_template` | 取清单里对应条目,流式下载 zip(≤512 MiB),校验字节数与 SHA-256,解压到 `<app_data>/templates/installed/<id>/<version>/`,最后写 `installed.json` 作为安装完成的唯一标记 |
| `create_automatic_local_game_project_from_template` | 需要时先安装模板,然后在自动工作区根目录下按既有自动工作区规则建目录:先复制模板文件;Cocos 项目更新自身身份后走既有 Cocos 导入,其余沿现有 `init_local_game_project_at` 初始化。根目录默认是 `<app_data>/projects/`,用户可选 `projectsRoot` 覆盖(必须来自本机目录选择器并通过私有路径门禁),见 [`【实施计划】AGC项目创建目录可选-2026-09-17.md`](../project-memory/plans/【实施计划】AGC项目创建目录可选-2026-09-17.md) |
安全与健壮性:
@@ -86,6 +108,12 @@ templates/
## 验收与验证
并发发布验收必须覆盖真实发布编排的离线故障注入:正文写入或清单提交失败后旧清单引用保持一致;两个发布者互斥、后续重试从新清单合并;条件请求头正确签名;版本控制状态不安全时零写入;外部锁不被删除;同版本改 ZIP 被拒绝;已存在内容对象冲突及释放失败可诊断。普通打包和纯合并函数测试不能替代此门禁。OSS 协议依据:[PutObject](https://help.aliyun.com/zh/oss/developer-reference/putobject)、[GetBucketVersioning](https://help.aliyun.com/zh/oss/developer-reference/getbucketversioning) 与 [V1 签名](https://help.aliyun.com/zh/oss/developer-reference/include-signatures-in-the-authorization-header)。
Cocos 回归分别覆盖仓库模板和线上真实 ZIP 的安装、连续建项、独立 UUID、`cocosProjectRoot`、原文件保留与安装缓存不变;Web 模板继续走原有回归。此处验证的是模板下载与建项,不替代 Creator 内场景运行验收。Cocos 原生建项分流需要包含此实现的客户端,旧二进制须重新构建或更新。
`2026-09-19` 发布一致性验收:Node 发布回归 22 项通过,覆盖两个发布者竞争、正文/清单写入失败、迟到清单 PUT、锁归属、版本控制拒绝、V1 签名及真实 CLI 的无写入 dry-run。Rust 定向回归 15 项通过,新增内容地址的清单解析与 URL 保留校验;3 项线上用例本轮未重复执行,此前同日线上下载及原生建项已通过。只读 dry-run 保留线上九个模板并仅计划更新四个 Cocos 条目。格式、编码、文档索引、定向 ESLint 与 diff 检查通过。全部并发/故障写入证据来自离线替身,未执行真实 OSS 锁写入或发布,也未验证 Creator 内场景运行。
## 本地压测假数据注入(feature 控制)
模板库的数据源在 Rust 侧(清单校验、安装状态、下载与建项目都在这里),TS 只消费快照做渲染,所以假数据注入也放在 Rust 侧,走与真实完全一致的链路。
@@ -122,6 +150,10 @@ AGC_TEMPLATE_LIBRARY_SYNTHETIC_COUNT=300 AGC_DEV_CARGO_FEATURES=template-library
- 前端回归:1000 条渲染 + 已安装过滤(334)/标签过滤(50)/关键词过滤数量自洽,见 `apps/ai-game-creator-shell/tests/templateLibraryView.test.tsx`
```bash
# 模板内容、确定性打包与定向发布合并回归
node --test scripts/agc-template-library-publish.test.mjs
# 仅发布 Cocos 模板(先加 --dry-run 核对合并清单)
node scripts/agc-template-library-publish.mjs --source apps/ai-game-creator-shell/template-library --only cocos-empty-2d,cocos-empty-3d,cocos-empty-3d-hq,cocos-hello-world
# 模板库单测(清单校验、键安全、解压路径逃逸、安装与建项目)
cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell template_library
# 模板库真连检查(可选,需要网络):读线上清单、下载安装线上模板包并据此建项目
@@ -144,4 +176,4 @@ curl -s https://agc-dev.oss-rg-china-mainland.aliyuncs.com/templates/index.json
- 清单读不到且没有本机缓存:模板库页显示错误与重试,首页推荐位显示「模板库暂时没有可用的模板」。
- 版本落后:`installedVersion != templateVersion` 视为需要重新下载,点「使用模板」会先重下再建项目。
- 需要回退整条链路时,删除 `templates/` 前缀即可让客户端回到"空模板库";客户端代码路径不受影响
- 回退清单同样必须使用发布互斥协议并保留其引用的历史对象,不能删除整个 `templates/` 前缀来回退
File diff suppressed because one or more lines are too long