同步平台入口主规范与排障记忆
- 平台入口与玩法链路:改写版本模型条款为按 versions 派生只读,并同步安全下架入口收敛到游戏管理页 - 平台入口与玩法链路:新增兼容与迁移、非目标两条 - pitfalls:追加随包 plugins feature 档位与 build.rs 假通过排障条目
This commit is contained in:
@@ -2,6 +2,42 @@
|
||||
|
||||
这里只记录对当前开发仍有用的症状、根因、排查方法和风险边界。同一事实保留一个当前口径;退役对象的专属过程与单轮测试结果由 Git 历史追溯。遇到旧路径或版本时,以现行代码和专题文档为准。
|
||||
|
||||
## 2026-10-03 AGC 随包 plugins 的 feature 档位必须与消费方一致,且门禁会因 build.rs 未重跑而假通过
|
||||
|
||||
- **现象**:Windows 本机 `npm run check:generated-bindings`(`npm run lint` 链内,`scripts/check-repository-ci.sh` 的 Repository checks 也走它)在 `build.rs:167:29` panic:`插件随包资源校验失败:随包插件存在未声明文件:.../src-tauri/resources/plugins/agc-godot-editor/native/gdextension/bin/win-x64/agc_godot_editor.dll(目标 x86_64-pc-windows-msvc 与当前 feature 组合不允许;请先执行随包资源准备步骤)`;树上换成 `agc-unity-editor/dotnet/publish/win-x64/Agc.Unity.Attach.exe` 时报同一类错。反向还有更隐蔽的形态:门禁 2 秒就 exit 0 说「通过」,但 tree 上其实带着编辑器产物。
|
||||
- **原因**:`apps/ai-game-creator-shell/src-tauri/resources/plugins/` 是 gitignored 但被 dev / 发布 / 门禁多流程共用的目录,它的**档位**(staging 里放了哪些编辑器产物)必须与本次 cargo 调用实际生效的 feature 组合一致。`apps/ai-game-creator-shell/src-tauri/Cargo.toml` 的 `[features] default =` 是空的,而 `scripts/check-generated-bindings.mjs` 对 AGC 用的是裸 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml export_bindings`(**不带任何 feature**);`build.rs` 按「当前 TARGET + 已启用 `CARGO_FEATURE_*`」构造 plugins 允许集合,于是为 windows 编辑器 feature 准备的 `origin: prepared` / `libraryStaging` 产物全成了「未声明文件」。
|
||||
- **关键坑(假通过)**:`resources/**` **不是**构建脚本声明的输入(设计如此,避免每次资源变化都重编),所以缓存命中的 `cargo test` **根本不会重跑 `build.rs`**,校验被整个跳过。实测:树上带着未声明的 unity 产物时,门禁仍以 2.33 秒 exit 0「通过」;`touch apps/ai-game-creator-shell/src-tauri/build.rs` 强制重跑后才暴露。
|
||||
- **处理(现行口径)**:校验 / 无 feature 的消费方先复位到 featureless:`npm run agc:bundled-resources:prepare -- --features=`。要带编辑器能力的本地 AGC:`npm run agc:bundled-resources:prepare -- --target x86_64-pc-windows-msvc --features=cocos-editor-execute,unity-editor-execute,godot-editor-execute`(`cocos-editor-bridge.dll` 的 native payload 另需 `cocos-editor-injection`)。要求门禁**真校验**过,先 `touch apps/ai-game-creator-shell/src-tauri/build.rs` 再跑门禁。
|
||||
- **判据/取证**:复位后强制重跑那次输出 `生成绑定校验通过:shared-contracts(1 个文件)` / `生成绑定校验通过:ai-game-creator-shell(104 个文件)` / `生成绑定校验通过:合计 105 个文件与 Rust 声明一致`(exit 0)。featureless 判据:`resources/plugins` 只有各插件 `plugin.json` + `src/`(cocos 另有 `panels/`),不存在 `Agc.Unity.Attach.exe` / `agc_godot_editor.dll` / `cocos-editor-bridge.dll`。
|
||||
- **边界**:这是同一工作树里多人共享的档位——featureless 是无 feature 构建与并发 cargo 运行的前提,带编辑器产物会让它们失败,反之亦然。`prepare-bundled-resources.mjs` 的原子替换要 rename `resources/plugins`,撞上外部目录句柄会 `EPERM` 并把 staging 留在 `resources/plugins-staging-<pid>-<hex>`(`.gitignore:61` 已声明该模式),确认无并发进程后重试即可。
|
||||
- **关联**:`apps/ai-game-creator-shell/src-tauri/build.rs`(`validate_staged_plugins` / `validate_prepared_payloads`)、`src-tauri/build_support/package-layout.json`、`apps/ai-game-creator-shell/scripts/{prepare-bundled-resources.mjs,cargo-features.mjs}`、`scripts/check-generated-bindings.mjs`;另见本文件「随包资源的写入方按产物来源分界」「AGC 随包资源的布局只能改声明文件」「构建期 staging 撞上不装 npm 依赖的 Linux 门禁」三条。
|
||||
|
||||
## 2026-10-03 工作树位于 `.worktrees/` 下时前端 Vite 被自己的忽略规则整棵排除:改代码不热更
|
||||
|
||||
- **现象**:在 `.worktrees/<name>/` 里的工作树改主站或后台前端源码,浏览器看不到任何更新;重启 Vite 也不生效(本轮有人重启 3 次才发现不是缓存问题)。
|
||||
- **原因**:根 `vite.config.ts`(`ignoredWatchGlobs`,约 461-468 行)与 `apps/admin-web/vite.config.ts`(约 19-24 行)的忽略列表都含 `'**/.worktrees/**'`。工作树本身就在 `.worktrees/` 目录下,该 glob 会匹配工作树内的**每一个文件**,等于把整个项目排除出 watch——不报错、不提示,只是永远不触发 HMR / 重建。
|
||||
- **影响面**:主站 web 与 admin-web 都中招(两者共用这条规则);`npm run dev:web` 走 `scripts/vite-cli.mjs`,它只是转发到 Vite 自带 bin、用的是同一份根配置,所以经 CLI 入口启动也一样。`apps/ai-game-creator-shell/vite.config.ts` 只忽略 `'**/src-tauri/target/**'`,**不受影响**。
|
||||
- **处理方向(本轮未改配置)**:忽略规则必须只排除**其它** worktree 而放行当前 root——例如按真实仓库根计算,形如只忽略 `<repoRoot>/.worktrees/*/` 且显式排除当前 root;或当项目 root 自身已落在 `.worktrees/` 内时不注入该条。不要保留无条件的 `'**/.worktrees/**'`。
|
||||
- **验证方向**:修改 `src/**` 一句话后,主站与 admin-web 各自应打印 HMR 更新(修改后立即生效),而不是只在重启后才生效;必要时打印生效的 watch 忽略集合确认不再覆盖当前 root。
|
||||
- **关联**:`vite.config.ts`、`apps/admin-web/vite.config.ts`、`apps/ai-game-creator-shell/vite.config.ts`、`scripts/vite-cli.mjs`。
|
||||
|
||||
## 2026-10-03 同一工作树并发拉起多份 dev 栈:`.app/dev-stack.json` 互相覆盖,启动兜底清扫会反杀健康栈
|
||||
|
||||
- **现象**:在同一工作树里再开一个 `npm run dev` 之后,AGC 侧报「后端归属校验失败」,或前端代理连到别的端口(「后端端口记错」);更严重的是新会话启动后,`8082` / `8083` 上原本健康的后端被清掉,旧会话随即报连接失败。
|
||||
- **原因**:`.app/dev-stack.json` 是**全工作树单文件**(`scripts/dev.mjs` 的 `resolveDevStackStatePath()` → `<repoRoot>/.app/dev-stack.json`,`scripts/dev-all.mjs` 与若干 e2e 脚本也读它),每个 `DevRunner` 都整份覆写快照,端口、SpacetimeDB data-dir 与 instance id 只保留最后写入者,于是两份并发栈互相覆盖实例信息。同时 `dev.mjs` 在启动/退出时会按身份兜底清扫 `stopWindowsWorktreeBackendProcesses`(`api-server.exe` 绝对路径 + SpacetimeDB `--data-dir`),这是**按工作树**而不是按会话匹配的:其它会话留下的半死栈一旦重启,就会把当前健康栈一并收走。
|
||||
- **处理(现行口径)**:同一工作树保持**单栈**;确需并发时用显式端口参数(`--api-port` / `--web-port` / `--admin-web-port` / `--spacetime-port` 等)错开,并接受状态文件只有一个「最后写入者」。清理残留必须按**端口 → PID → 命令行**确认归属,再杀该 PID 的整棵进程树;不要 `taskkill /IM node.exe`(会误伤其它会话与 IDE 的 Node 进程)。
|
||||
- **排查顺序**:先比对 `.app/dev-stack.json` 的 status / 端口与实际监听(`Get-NetTCPConnection -State Listen -LocalPort ...`)是否一致,再用 `Get-CimInstance Win32_Process` 按本工作树 `server-rs\target\debug\api-server.exe` 路径与 SpacetimeDB `--data-dir` 核对归属;不要因为 `/healthz` 返回 200 就认定后端属于当前会话。
|
||||
- **关联**:`scripts/dev.mjs`(`resolveDevStackStatePath` / `stopWindowsWorktreeBackendProcesses`)、`scripts/dev-windows-process.mjs`、`scripts/dev-all.mjs`、`scripts/check-game-distribution-ratings-e2e.mjs`;另见本文件「`npm run agc` 的 Ctrl+C 不能只依赖 shell 包装层与端口健康检查」与 `docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 2026-10-03 Windows 上会弹阻塞模态的启动失败用例挂死本机分片 runner
|
||||
|
||||
- **现象**:Windows 本机 `npm run ai-game-creator-shell:check:rust:shell -- --shard-index=3/4` 长时间无进展(可到 2400s 超时);单独跑那条用例同样超时——进程还在、CPU 不再增长、也没有子进程,形态很像「测试死锁」或「分片器坏了」。
|
||||
- **原因**:`apps/ai-game-creator-shell/src-tauri/src/main.rs:2042` 的 `startup_log_slot_fail_without_path_still_reports_instead_of_going_silent` 调 `StartupLogSlot::fail()`(约 1854 行),而 `fail()` 会走 `show_startup_error_dialog()`;Windows 实现(约 1740 行)用的是 `MessageBoxW(..., MB_OK | MB_ICONERROR | MB_SETFOREGROUND)`,是**阻塞模态**,没有人点「确定」就永不返回。`STARTUP_ERROR_DIALOG_SHOWN`(约 1534 行)只在同一个进程内保证「只弹一次」,对测试用例没有任何豁免。分片 runner 用 `--exact <名单> --test-threads=1` 串行执行,一条挂死就整片挂死。
|
||||
- **影响面**:Linux CI 走非 Windows 分支(约 1778 行)只写 stderr,**不受影响**;这是本机专属现象,不要据此判定 Rust 代码或分片规则有问题。
|
||||
- **处理(本机绕过)**:改用等价分块跑,而不是整片上阵——同一个测试二进制、同一 `--exact <名单>` 与 `--test-threads=1` argv、同一 TMPDIR 隔离,把这条阻塞用例排除或单独限定。
|
||||
- **判据/取证**:单独执行 `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell -- --exact startup_log_slot_fail_without_path_still_reports_instead_of_going_silent --test-threads=1` 本机同样挂住;对照非 Windows 分支只产生 stderr 文案。
|
||||
- **关联**:`apps/ai-game-creator-shell/src-tauri/src/main.rs`、`apps/ai-game-creator-shell/scripts/run-rust-shell-test-shards.mjs`;另见本文件「AGC 壳 Rust 套件按「一片一 job」拆分,且分片必须自校验覆盖」条中此前记为「本机跑到约 20 分钟后长时间无进展」的同一现象。
|
||||
|
||||
## 2026-10-03 AGC 栏目画布上传素材落「待归类」:kind 派生分类不等于入口栏目
|
||||
|
||||
- **现象**(Issue 359):在 AGC 资源栏目子画布(如「UI 交互」「角色与对象」)左下角工具栏点「上传」选图片 / 视频 / 代码类文件,提示条给出「已上传 1 个素材」,但当前栏目计数纹丝不动(仍「0 项」),素材出现在「待归类」。用户看到的是"上传成功了,可它就消失在这个页面里"。
|
||||
|
||||
@@ -94,15 +94,18 @@
|
||||
|
||||
### 身份、状态、审核与更新
|
||||
|
||||
- `gameId` 是服务端分配的稳定游戏身份;`ownerUserId` 只从当前认证主体派生。AGC 本地 `projectId` 只用于关联提示,不能证明云端游戏所有权。项目清单保存唯一用户发行版本 `projectVersion`,以及平台 `gameId`、最近 `versionId`、`publicationRevision` 和状态;网页上传和 AGC 发布使用相同游戏、版本与上传记录,不建立两套发行系统。
|
||||
- `gameId` 是服务端分配的稳定游戏身份;`ownerUserId` 只从当前认证主体派生。AGC 本地 `projectId` 只用于关联提示,不能证明云端游戏所有权。AGC 项目清单保存平台绑定(`gameId`、最近 `versionId`、`publicationRevision`、状态)与 AGC 工程内部版本记录 `versions[]`;AGC 发布面板展示的「项目版本」不是清单里可编辑的标量,而是由 `versions[]` 派生的只读标签(见下条)。网页上传和 AGC 发布使用相同游戏、版本与上传记录,不建立两套发行系统。
|
||||
- 同一作者用相同 `localProjectId` 再次发布时复用既有 `gameId`。一次具体上传/资料/审核快照由不可变 `versionId` 标识;同一个用户版本标签可以有多个提交实例,旧公开实例不原地修改。AGC 如果清单缺少发布记录,首次更新前按作者作品回读和 `localProjectId` 做一次性恢复。
|
||||
- `projectVersion`/`version_number` 是用户可见的正整数版本标签,不要求严格递增,允许回退和重复提交。AGC 发布面板直接编辑项目清单的 `projectVersion`,不再维护独立 `targetVersion`;服务端不传时保留旧客户端自动取最大值加一的兼容行为,传入时只校验 `versionNumber >= 1`。
|
||||
- AGC 发布面板的「项目版本」是 AGC 工程内部版本序数,唯一来源是 AGC 项目清单的 `versions[]`(AGC 工程内部版本记录,即资源总览「项目版本」栏目里的版本卡):标签等于当前存活的内部版本条数(即最新内部版本卡的「版本 N」),`versions` 为空时取 1。该标签**只读**,用户不能编辑,且**不由平台侧任何字段推导**——不得读取、回填或覆盖平台 `version_number`,也不得读取 `publicationRevision`。每次智能体修订追加一条内部版本,标签随之推进。
|
||||
- 平台侧 `versionNumber` 是用户可见的正整数版本标签,**不要求严格递增,允许回退和重复提交**;服务端不传时保留旧客户端自动取最大值加一的兼容行为,传入时只校验 `versionNumber >= 1`。该兼容口径只为网页端与历史客户端保留,AGC 不再用它维护"用户发行版本"字段。AGC 项目清单里的历史字段 `projectVersion` 属遗留位:仅为兼容旧清单可解析而保留,任何读写路径都不得再把它当作发行版本来源,AGC 也不再提供修改它的入口。
|
||||
- 同一 `gameId` 与同一 `versionNumber` 的再次提交创建新的 `versionId`;旧的 `published`/`activeVersionId` 继续服务,新的提交审核通过后才切换 `activeVersionId`。旧的 pending 提交可被新提交标记为 `cancelled`,审核队列只展示最新有效提交。`publicationRevision` 仍然严格 CAS 递增,但不限制用户版本号。
|
||||
- 游戏单独保存 `publicationRevision`、`activeVersionId` 和可见性 `unpublished | published | suspended`;正式可见性由服务端持久化事实决定。未通过审核时 `activeVersionId` 为空;`suspended` 是管理员安全下架,作者不能自行解除。
|
||||
- 版本状态为 `awaiting_upload → uploaded → validating → pending_review → published`。上传确定失败进入 `upload_failed`,验证失败进入 `validation_failed`,人工拒绝进入 `rejected`;尚未公开提交可以撤回为 `cancelled`,已公开实例可撤销为 `revoked`。同一用户版本标签的新内容必须创建新提交实例。
|
||||
- 首版建议人工审核。审核员检查游戏资料、真实桌面运行、声明移动适配、内容与外部请求被阻断的行为;自动包校验通过只进入 `pending_review`,不自动公开。审核记录保存审核者、目标 `versionId`、结论、理由和时间。后台只授权现有管理员身份,不让普通作者调用审核动作。
|
||||
- 更新送审时旧 `activeVersionId` 继续服务目录、详情与游玩。审核通过并完成对象可读验证后,一次事务切换当前公开版本和公开资料;新提交上传、校验、审核或对象安装失败均不改变旧版本。
|
||||
- AGC 发布面板在无绑定时显示“首次发布”,存在绑定时显示“更新游戏”,回填当前 `projectVersion`、线上版本状态和建议值。首版不做完整版本历史/回滚管理,但允许用户修改项目版本标签、重复提交同版本和回退到旧版本标签。
|
||||
- AGC 发布面板在无绑定时显示“首次发布”,存在绑定时显示“更新游戏”,回填**派生的项目版本标签、线上版本状态和建议值**;不再回填 `projectVersion`。面板同时逐字展示平台侧事实「线上最近提交 vN · 状态」(`N` 取该作品最近更新的版本行,与派生标签可以不同),两者并列展示、互不推导。首版不做完整版本历史/回滚管理;用户不再直接编辑版本标签,回退与重复提交仍由平台侧接受(例如 AGC 工程内部版本被截尾删除后派生标签会变小)。
|
||||
- 兼容与迁移:AGC 项目清单的 `versions` 只能追加,或按显式放行删除一个**后缀**(删除素材时连带删除引用该素材的版本);因此内部版本序数(派生标签)不保证单调递增,历史发布标签可能高于后续发布标签。平台必须继续接受回退标签,AGC 不得把回退在本地判成冲突。本地遗留字段 `projectVersion` 只做兼容解析,不参与任何派生。
|
||||
- 非目标:不新增服务端内部版本号字段、不新增或修改 SpacetimeDB 表/字段/procedure、不改 `publicationRevision` 的 CAS 语义、不做版本历史/对比/一键回滚 UI、不重写平台历史版本行的 `version_number`。
|
||||
|
||||
### 幂等、并发与恢复
|
||||
|
||||
@@ -134,7 +137,7 @@
|
||||
| `POST /games/{gameId}/unpublish` | owner | **已实现**:CAS 关闭公开游戏及其版本入口,不删除审核记录 |
|
||||
| `GET /admin/api/game-distribution/reviews` | 管理员 | **已实现**:分页获取待审版本;此行是完整后台路径 |
|
||||
| `POST /admin/api/game-distribution/versions/{versionId}/review` | 管理员 | **已实现**:批准由服务端按 gameId 派生平台同源发行路径 `/games/{gameId}/` 并执行公开版本 CAS;拒绝需理由;此行是完整后台路径 |
|
||||
| `POST /admin/api/game-distribution/games/{gameId}/suspend` | 管理员 | **已实现**:安全下架整个游戏并撤销发行访问,要求 `expectedPublicationRevision` CAS 与幂等键;后台游戏审核页提供带原因输入与二次确认的入口;此行是完整后台路径 |
|
||||
| `POST /admin/api/game-distribution/games/{gameId}/suspend` | 管理员 | **已实现**:安全下架整个游戏并撤销发行访问,要求 `expectedPublicationRevision` CAS 与幂等键;入口在后台「游戏管理」页,带原因输入与二次确认(审核页只做版本级通过/拒绝,不承载游戏级下架);此行是完整后台路径 |
|
||||
| `GET /admin/api/game-distribution/games` | 管理员 | **已实现**:后台作品管理列表,支持 `limit` / `keyword` / `owner` / `status`(`published`、`unpublished`、`suspended`、`deleted`,为空时排除软删行)/ `cursor` 过滤与游标分页,返回 `{ games, nextCursor }`;`status` 不在白名单时 400;此行是完整后台路径 |
|
||||
|
||||
除显式 `/admin/api/...` 外,表内路径均相对 `/api/game-distribution`。错误采用现有平台 envelope,覆盖 400 格式错误、401 未登录、403 owner/审核权限错误、404 不可见、409 幂等/状态/并发冲突、413 大小上限、422 包或资料校验失败、429 限流和明确的可重试 5xx;服务端响应不包含存储凭据和本地绝对路径。
|
||||
@@ -481,7 +484,7 @@
|
||||
- 审核员在一个详情工作台内看到发布者、游戏资料、待审版本包信息、审核状态和审核历史。
|
||||
- 审核员可以实际试玩当前待审 `versionId`,不能误播旧的公开版本或另一个待审版本。
|
||||
- 试玩保持发行网关的 opaque sandbox、Cookie 隔离、CSP、路径白名单和运行期 storage 兼容边界。
|
||||
- 通过、拒绝、安全下架继续使用现有 `publicationRevision`、认证管理员和幂等语义。
|
||||
- 通过、拒绝继续使用现有 `publicationRevision`、认证管理员和幂等语义;安全下架是游戏级动作,入口留在后台「游戏管理」页,不出现在「审核一个新版本」的语境里。
|
||||
|
||||
### 非目标
|
||||
|
||||
@@ -508,8 +511,8 @@
|
||||
|
||||
### 审核动作
|
||||
|
||||
- 详情页保留通过、拒绝、安全下架;列表页保留快捷动作。
|
||||
- 拒绝和下架继续要求理由;所有写操作携带 `publicationRevision` 和 `Idempotency-Key`。
|
||||
- 审核页(详情与待审列表)只保留版本级动作:通过、拒绝。安全下架作用于整个游戏(强制下线线上版本),入口在后台「游戏管理」页,不放在审核新版本的语境里。
|
||||
- 拒绝继续要求理由;拒绝与游戏管理页的安全下架等所有写操作携带 `publicationRevision` 和 `Idempotency-Key`。
|
||||
- 并发修订返回冲突时只提示刷新并重新读取,不覆盖其他审核员的决定。
|
||||
- 审核操作成功后刷新详情和待审列表,不能只修改前端本地状态。
|
||||
|
||||
@@ -528,7 +531,7 @@
|
||||
| 待审快照 | 修改 game 行资料后仍显示待审版本冻结资料 | 详情响应和页面已接入 `frozenMetadata`;真实数据验证待补 |
|
||||
| 版本隔离试玩 | 用两个版本验证预览 URL 不能串读,待审包资源全部可加载 | 预览会话路由和版本绑定已实现;真实审核试玩待补 |
|
||||
| 安全边界 | Cookie、过期 Token、越权 versionId、公开路径访问拒绝 | sandbox、Cookie 拒绝和短期 Token 已实现;HTTP 边界测试待补 |
|
||||
| 审核动作 | 通过/拒绝/下架、理由、CAS 冲突、幂等重放 | 既有审核动作保留,详情页已接入;回归测试沿用现有 6 项审核页面测试 |
|
||||
| 审核动作 | 审核页通过/拒绝、理由、CAS 冲突、幂等重放;游戏级安全下架在「游戏管理」页 | 审核页只保留版本级动作(含取消不写请求);游戏管理页保留「下架(原因必填)+ 二次确认」入口 |
|
||||
| 工程门禁 | admin-web 测试、api-server 定向测试、类型、编码、diff | admin-web typecheck、详情测试、api-server cargo check、Rust fmt、编码和 diff 通过 |
|
||||
|
||||
2026-10-02 已完成第一版审核详情工作台与待审版本预览接线:后台详情回读复用现有 game/version 投影,预览 Token 只绑定待审 versionId,预览 iframe 保持 `sandbox="allow-scripts"`。尚未完成真实管理员登录、待审包资源全量加载和 Token 越权 HTTP smoke。
|
||||
|
||||
Reference in New Issue
Block a user