Merge branch 'master' into fix/empty-input
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m46s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m35s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 6m15s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 6m49s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m46s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 3m35s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 6m15s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 6m49s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# 后台模板上传实施计划
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| Version | 1.0 |
|
||||
| Status | in-progress |
|
||||
| Date | 2026-09-21 |
|
||||
| Milestone Spec | `docs/project-memory/plans/【里程碑】后台模板上传-2026-09-21.md` |
|
||||
|
||||
## 修改边界与顺序
|
||||
|
||||
1. **领域规则(`module-assets/template_library.rs`)**:新增「导入准备」函数——按 ID 合并清单条目(新增默认上架、已存在就地更新并保留 `enabled` 与未知字段),校验 ID / 版本 / entry / runtime / 显示字段上限,产出待提交的清单与 `template.json` 字节;不接触存储。
|
||||
2. **存储(复用 `platform-oss/template_library.rs`)**:只使用既有 `begin_publish` / `read_index` / `put_immutable` / `commit_index` / `finish`;如缺少「读单个对象作元数据基线」的能力,再补最小只读方法,不改锁与提交语义。
|
||||
3. **契约(`shared-contracts/admin.rs` + `apps/admin-web/src/api/adminApiTypes.ts`)**:新增导入请求(manifest)与导入结果 DTO;错误体沿用现有 `AppError` + 逐项原因结构。
|
||||
4. **接口(`api-server/admin_templates.rs` + `modules/admin.rs`)**:新增 multipart handler,按「解析 manifest → 校验每个 ZIP / 封面 → 取锁 → CAS → 写内容寻址对象并回读 → 提交清单 → 释放锁」顺序实现;路由套 `require_admin_auth` 与 256 MiB body 上限,并同步路由契约测试与页签权限矩阵测试。
|
||||
5. **后台页面(`AdminAgcTemplatesPage.tsx` + `adminApiClient.ts`)**:新增「上传模板」入口与弹窗(多选 ZIP、逐行元数据、批量提交、逐行错误、写入确认),沿用既有 `useAdminWriteConfirm` 与刷新语义。
|
||||
6. **文档**:主规范新增「后台模板上传」章节;决策记录补一条;主规范中「本轮只允许…不上传 ZIP」改为指向新章节。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
cargo test --locked -p module-assets --manifest-path server-rs/Cargo.toml -- template_library
|
||||
cargo test --locked -p api-server --manifest-path server-rs/Cargo.toml -- agc_template
|
||||
cargo fmt --all --manifest-path server-rs/Cargo.toml -- --check
|
||||
npm run admin-web:typecheck
|
||||
npx vitest run apps/admin-web/src/pages/AdminAgcTemplatesPage.test.tsx apps/admin-web/src/api/adminApiClient.test.ts
|
||||
npm run check:encoding
|
||||
npm run check:doc-index
|
||||
git diff --check
|
||||
```
|
||||
|
||||
接口 smoke:`npm run dev:api-server`(本地 8082)+ `npm run dev:admin-web`,先用未授权/无页签权限请求确认失败关闭,再以隔离存储替身或用户显式确认的 dev bucket 做一次真实导入。
|
||||
|
||||
## 时间盒与风险
|
||||
|
||||
- **风险:真实发布不可逆**。导入会写公共 bucket 且不提供删除,因此默认只在本地用替身验证;对 dev bucket 的写验证必须由用户显式确认,测试用模板需可在事后下架。
|
||||
- **风险:ZIP 原字节发布与 CLI 确定性打包不一致**。同一模板可能被两条路径写成不同字节;由「同 ID 同版本字节必须一致」的门禁兜住,导入失败时提示改用 CLI 或递增版本。
|
||||
- **风险:大文件内存**。ZIP(≤64 MiB)与封面在内存中校验,单批上限 20;需要更大模板时走 CLI。
|
||||
- **风险:批量部分写入**。所有对象在清单提交前写入且不删除;中途失败时清单不变、已写对象成为未被引用的历史对象,由后续同键复用。
|
||||
- **回退**:下线路由与页面入口即可停止使用;已发布内容按既有 CLI / 后台下架流程处理,历史对象保留。
|
||||
@@ -0,0 +1,69 @@
|
||||
# 后台模板上传
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| Version | 1.0 |
|
||||
| Status | implemented-awaiting-runtime-acceptance(接口、页面与定向用例已交付;真实 dev bucket 写入 smoke 未执行) |
|
||||
| Date | 2026-09-21 |
|
||||
| Parent Spec | `docs/technical/【技术方案】AGC模板库与模板建项-2026-09-17.md` |
|
||||
|
||||
## 目标与范围
|
||||
|
||||
管理员在后台直接上传模板并发布到公共模板库,不必走本地 CLI;一批可以带多个模板(批量上传),一批只做一次发布锁和一次清单提交。
|
||||
|
||||
- 新增 `POST /admin/api/agc-templates/import`(`multipart/form-data`):`manifest` 文本字段 + `zip_<index>` / `cover_<index>` 文件字段,字段下标与 manifest 条目顺序一一对应。
|
||||
- manifest:`{ expectedRevision, templates: [{ id, title, summary, tags, runtime, engine, engineVersion, entry, templateVersion }] }`;`expectedRevision` 是当前清单字节 SHA-256,用于锁内 CAS。
|
||||
- 限制:单批最多 20 个模板;单个 ZIP ≤ 64 MiB;单张封面 ≤ 5 MiB;请求体 ≤ 200 MiB;`id` / `templateVersion` / `entry` 走既有标识符与相对路径白名单;`runtime` 仅接受 `html / unity / godot / cocos`。存储层为 `application/zip` 单独放宽单对象上限到 64 MiB(图片与元数据仍是 5 MiB)。
|
||||
- 语义:一批**全有或全无**——任一模板校验失败都在写入前整批拒绝并逐项给出原因;通过后在同一把发布锁内写入全部内容寻址对象、逐个回读校验,最后提交一次清单。
|
||||
- 新 ID 默认上架;已存在 ID 的导入就地更新该条目(保留 `enabled` 状态、所属分组与未知扩展字段,包含下架条目);同一 ID 同一 `templateVersion` 但 ZIP 字节不同时拒绝,提示递增 `templateVersion`。
|
||||
- ZIP 由服务端校验后按原字节发布(不重新打包、不解压落盘):合法 zip、无符号链接、无绝对路径 / `..` / 盘符条目、必须包含声明的 `entry`、至少一个文件,条目数与解压后体积受上限保护。
|
||||
- 封面**每个模板必填**(与 CLI 源布局 `v1/<id>/cover.*` 一致),仅接受真实 PNG / JPEG / WebP,上限与编辑路径相同(5 MiB、单边 4096、1600 万像素);上传按字节嗅探格式,不信任 multipart 声明的 content-type。SVG 仍只可能来自 CLI 历史发布,编辑与上传都不产生新的 SVG 封面。
|
||||
- 后台「模板管理」页新增上传入口:可多选 ZIP、逐行编辑元数据(含可选封面)、批量提交、逐行显示校验错误;沿用现有写入确认、防重复提交与刷新语义。
|
||||
|
||||
## 不做
|
||||
|
||||
- 不做模板删除、版本回滚、下架条目清理、历史对象回收。
|
||||
- 不改 ZIP 内容、不做服务端重新打包(CLI 仍是确定性打包与 `--only` 定向发布的入口)。
|
||||
- 不做审核流、不做从 URL 拉取、不做 CLI 源布局目录(`v1/<id>/{meta.json,project/**,cover.*}`)的自动打包上传。
|
||||
- 不新增 SpacetimeDB 表或 schema。
|
||||
|
||||
## 合同与依赖
|
||||
|
||||
- 存储与锁:复用 `platform-oss` 的 `TemplateLibraryStore` / `TemplatePublishSession`(`begin_publish` / `read_index` / `put_immutable` / `commit_index` / `finish`)与既有版本控制前置检查;不新增第二套发布协议。
|
||||
- 领域规则:`module-assets/template_library.rs` 承担清单合并与字段校验(保留未知字段、下架状态、两组间 ID 唯一),沿用既有标识符 / 相对路径 / 摘要规则。
|
||||
- DTO:`shared-contracts/admin.rs` 新增导入结果类型;前端类型在 `apps/admin-web/src/api/adminApiTypes.ts`。
|
||||
- 鉴权:沿用后台认证与 `agc-templates` 页签权限;路由挂载在 `modules/admin.rs`。
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. 权限与路由:未授权读取/写入均失败关闭;无 `agc-templates` 页签权限不可导入;路由契约测试覆盖新路径与方法。
|
||||
2. 校验失败关闭:manifest 非法、ID/版本/entry 越界、runtime 不在白名单、ZIP 非法或含越界/符号链接条目、缺少 entry、封面超限或非真实图片,全部在**任何写入之前**整批拒绝,响应逐项给出模板 ID 与原因。
|
||||
3. 批量原子性:一批 3 个模板(含 1 个新 ID、1 个新版本、1 个下架 ID)在锁内一次提交;中途任一环节失败时清单与历史对象不变,返回可诊断错误。
|
||||
4. 版本一致性:同 ID 同版本、ZIP 字节不同必须拒绝并要求递增版本;同 ID 同版本、字节完全一致视为幂等复用,不报错。
|
||||
5. CAS 与锁:`expectedRevision` 过期返回 409;锁被占用返回 409;清单写入结果不明时保留锁并返回 503,不自动重试。
|
||||
6. 保留语义:既有条目的展示字段按上传内容更新,`enabled` 分组、其它条目、未知扩展字段与历史对象不变;下架条目仍留在 `inactiveTemplates`。
|
||||
7. 内容寻址与回读:ZIP / 封面 / template.json 以自身字节摘要寻址,写入后逐项回读校验,清单最后提交且返回最新快照。
|
||||
8. 界面:桌面与窄屏都能完成多选 ZIP、逐行元数据编辑、批量提交与错误展示;保存中防重复提交,409 引导刷新后重试。
|
||||
9. 检查:`cargo test`(module-assets / platform-oss / api-server 定向)、`npm run admin-web:typecheck`、后台页面 vitest、`npm run check:encoding`、`npm run check:doc-index`、`cargo fmt --check`、`git diff --check` 全部通过。
|
||||
|
||||
## 依赖
|
||||
|
||||
- 已交付的后台模板管理链路(`GET/PUT /admin/api/agc-templates`、页面、锁与内容寻址发布协议)。
|
||||
- 模板库 OSS 写凭据(`GENARRATIVE_AGC_TEMPLATE_LIBRARY_OSS_ACCESS_KEY_ID/SECRET` 或成套 `ALIYUN_OSS_*`);无凭据时导入返回 503 且页面明确不可用。
|
||||
- 真实 dev bucket 的写入验证需要用户显式确认(见实施计划「验证」)。
|
||||
|
||||
## 实现与证据(2026-09-21)
|
||||
|
||||
| 验收项 | 证据 |
|
||||
| --- | --- |
|
||||
| 1 权限与路由 | 路由契约测试覆盖 `POST /admin/api/agc-templates/import`;页签权限矩阵用例覆盖 GET / PUT / POST 三条路径(`admin::tests::agc_template_routes_require_the_template_tab_permission`) |
|
||||
| 2 校验失败关闭(写入前整批拒绝) | `template_import_archive_validation_fails_closed`(缺 entry / 越界路径 / 符号链接)、`template_import_plan_rejects_unknown_and_unreferenced_fields`(未引用字段、缺 ZIP 字段、字段前缀、未知 manifest 字段、非法 zip) |
|
||||
| 3 批量原子性 | `module-assets` 的 `template_import_appends_new_entries_with_metadata`(3 条口径:新 ID 入 `templates`、下架条目留在 `inactiveTemplates`、未知字段保留);发布编排在锁内写完全部对象后才提交一次清单 |
|
||||
| 4 版本一致性 | `template_import_rejects_same_version_with_different_bytes`(同句文案)、`template_import_reuses_identical_bytes_for_the_same_version`(`reused`) |
|
||||
| 5 CAS 与锁 | `import_templates` 复用 `check_revision`(409)与 `TemplatePublishSession`(`commit_index` 不确定即留锁 → 503);存储层既有用例覆盖锁与不确定写入 |
|
||||
| 6 保留语义 | 领域用例断言 `enabled` 分组、`extension` 未知字段与 `metadataKey` 保持不变 |
|
||||
| 7 内容寻址与回读 | `template_import_plan_uses_content_addressed_keys_and_sniffed_cover`;`put_immutable` 逐个回读校验 |
|
||||
| 8 界面 | 页面用例:多选 ZIP 生成行、封面匹配齐才可提交、确认后提交 manifest 与文件、成功后刷新列表、409 刷新引导、400 展示真实原因(共 40 项 vitest 通过) |
|
||||
| 9 检查 | `cargo test`(module-assets 11、platform-oss 12、api-server 定向 4)、`npm run admin-web:typecheck`、`check:encoding`、`check:doc-index`(193 份)、`cargo fmt --check`、`git diff --check` 全部通过 |
|
||||
|
||||
仍未执行:真实 `agc-dev` bucket 的批量写入 smoke(需要用户显式确认;建议用两个 `smoke-import-<时间戳>` 模板验证一批多模板 + 一次清单提交,验证后下架保留历史对象)。
|
||||
@@ -1,5 +1,16 @@
|
||||
# 决策记录
|
||||
|
||||
## 2026-09-21 后台模板上传:成品 ZIP + 每模板封面,批量全有或全无
|
||||
|
||||
- 背景:模板发布此前只有本地 CLI(源目录 + 确定性打包 + `--only`),后台上传需要一条不依赖本地仓库的通道,并支持一次提交多个模板。
|
||||
- 决策(输入形态):后台上传**成品 ZIP**(库内字节原样发布,不重新打包),每个模板必须同时给一张 PNG/JPEG/WebP 封面(与 CLI 源布局 `v1/<id>/cover.*` 一致),`id/title/templateVersion/runtime/entry` 由页面逐行确认;简介、标签与引擎上传后用编辑接口补齐。服务端按字节嗅探封面格式,不信任 multipart 声明的 content-type。
|
||||
- 决策(批量语义):一批 = 一把发布锁 + 一次清单提交,**全有或全无**;任一模板的 manifest/归档/封面不合法都在写入前整批拒绝并逐项给出原因。单批上限 20 个模板、单包 64 MiB、单封面 5 MiB、请求体 200 MiB。
|
||||
- 决策(校验与安全):归档只校验不落盘——合法 zip、无符号链接、无绝对路径 / `..` / 盘符条目、必须包含声明的 `entry`、条目数 ≤ 4096 且解压后 ≤ 512 MiB。
|
||||
- 决策(版本与保留):新 ID 默认上架;已存在 ID 就地更新并保留 `enabled` 分组、其它条目与未知扩展字段;同一 ID 同一 `templateVersion` 的 ZIP 字节不同时拒绝并要求递增版本,字节一致时按内容复用(`reusedObjects`)。
|
||||
- 决策(存储契约):`platform-oss` 的 `put_immutable` 为 `application/zip` 单独放宽到 64 MiB(此前只有 json/图片的 5 MiB),图片与元数据上限不变。
|
||||
- 影响范围:`server-rs/crates/{shared-contracts/api-server/module-assets/platform-oss}`、`apps/admin-web/src/{api,pages,styles}`、模板库技术方案与 `docs/project-memory/plans/【里程碑】后台模板上传-2026-09-21.md`。
|
||||
- 验证方式:`cargo test`(module-assets 11、platform-oss 12、api-server 定向 4)、`npm run admin-web:typecheck`、后台页面与模型 40 项 vitest、`check:encoding`、`check:doc-index`、`git diff --check`;真实 dev bucket 写入验证需用户显式确认。
|
||||
|
||||
## 2026-09-21 模板库线上产物对齐仓库源:递增版本重发 + 说明文档纳入发布
|
||||
|
||||
- 背景:`agc-dev` 上的 `templates/` 产物停在 2026-09-17 发布的那一版,仓库源在那之后改过(`5e4ff54a9` 删掉模板内嵌 `package-lock.json`、`game.js` / `main.js` 等内容调整),9 个模板里 7 个的 ZIP 与线上不一致;dry-run 被「同一 `templateVersion` 的 ZIP 不得变」门禁拒绝,发布器因此无法把仓库状态发上去。
|
||||
|
||||
@@ -5425,6 +5425,14 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- 处理:Windows 专用 Tauri 配置设置 `bundle.useLocalToolsDir: true`,把工具缓存到 `src-tauri/target/.tauri/NSIS`;Jenkins 预检验证实际用户、项目工具目录可写,并在构建失败时打印实际缓存路径和绝对路径执行结果。
|
||||
- 验证:不要把 PATH 中 `makensis` 可发现当作 Tauri bundler 工具可执行的充分证据;需要在 Windows Agent 上检查 `target/.tauri/NSIS/makensis.exe`、ACL、EDR/Defender 和直接 `-VERSION` 结果。
|
||||
|
||||
## Tauri NSIS 工具链必须在打包前预置并重试(2026-09-21)
|
||||
|
||||
- 现象:AGC Windows 发布构建已完成 Rust release,Tauri 依次打印 `Downloading .../nsis-3.11.zip`、`Info extracting NSIS`、`Downloading .../nsis_tauri_utils.dll` 之后,直接以 ``failed to bundle project `io: unexpected end of file` `` 失败(退出码 1),安装包不会产出。
|
||||
- 原因:tauri-bundler 的 `download_and_verify` 现场从 GitHub 取 NSIS 工具链,只有一次机会、没有重试;响应体被截断即报 `io: unexpected end of file`,看起来像打包错误其实是网络问题。Checkout 阶段的 `git clean -fdx` 每次都会清掉 `target/.tauri`,所以每个构建都要重新下载,在受限网络下必然反复失败。
|
||||
- 处理:新增 `apps/ai-game-creator-shell/scripts/nsis-toolset.mjs` 与 `ensure-nsis-toolset.mjs`,在 `buildRelease`(Windows 目标且需要打包时)与 Jenkins `Tauri NSIS toolchain` 阶段按固定 SHA1 预置 `target/.tauri/NSIS`:原始归档带 4 次重试,缓存在工作区外的 `%ProgramData%\genarrative\tauri-nsis-cache`(可用 `AGC_TAURI_NSIS_CACHE_DIR` 覆盖),镜像开关沿用 bundler 的 `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR_TEMPLATE` / `TAURI_BUNDLER_TOOLS_GITHUB_MIRROR`。该阶段同时执行 `makensis.exe -VERSION`,把 2026-09-02 记录的缓存目录不可执行问题也提前到编译之前暴露。Checkout 阶段改为 `git clean -fdx -e apps/ai-game-creator-shell/src-tauri/target/.tauri`:Tauri 的工具缓存位于工作区内,裸 `git clean -fdx` 会连它一起删,排除后同一节点的稳态构建不再需要联网,只有冷缓存(新节点、工作区重建)才下载。
|
||||
- 验证:`node --test apps/ai-game-creator-shell/scripts/nsis-toolset.test.mjs`(已就绪零下载复用、缓存离线还原、失败重试、哈希不符与归档越界失败关闭)与 `node --test apps/ai-game-creator-shell/scripts/build-release.test.mjs`;真实节点上该阶段必须早于 Rust 编译失败关闭。
|
||||
- 注意:不要改回 `bundle.useLocalToolsDir: false` 去用 `%LOCALAPPDATA%`,也不要依赖 PATH 里预装的 `makensis`;升级 `@tauri-apps/cli` 时同步核对归档 URL、SHA1 与必需文件清单。
|
||||
|
||||
## AGC 登录态续期必须同步本地运行时
|
||||
|
||||
- 模型目录 HTTP 请求与 DirectProject 的 Rust/app-server 使用同一账号,但凭据分别保存在 WebView 与 Rust / Runner;续期应复用 `requestPlatformSessionRefresh` 完成用户核验及本地会话安装,不能只写 localStorage。
|
||||
|
||||
Reference in New Issue
Block a user