完善游戏审核试玩与客户端版本更新

后台审核详情复用公开游戏详情样式,补齐发布者、冻结资料、素材换签和待审 ZIP 试玩。

AGC 保存已发布作品绑定,支持同一 gameId 更新版本并由用户填写目标版本号。

保留 SpacetimeDB 表结构不变,增加版本号请求校验、幂等边界和本地清单字段。
This commit is contained in:
2026-10-03 12:35:57 +08:00
parent 783ca014ef
commit 44f02ee19b
42 changed files with 3721 additions and 488 deletions
@@ -0,0 +1,40 @@
# 【实施计划】AGC 已发布游戏版本更新
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】AGC已发布游戏版本更新-2026-10-02.md` |
| Status | proposed |
| Owner | Genarrative Agent |
## 修改边界
- `packages/shared/src/contracts/gameCreationApp.ts`:本地 `publication` 绑定,不混入本地编辑迭代 `versions[]`。
- `packages/shared/src/contracts/gameDistribution.ts` 与 Rust shared-contracts:创建版本请求增加可选 `versionNumber`。
- `apps/ai-game-creator-shell`:绑定回读、首次发布/更新 UI、目标版本输入、原 gameId 新版本发布、结果回写。
- `api-server`、`spacetime-client`、`spacetime-module`:把可选目标版本号传入现有创建版本 procedure,事务内比较最大版本号并拒绝冲突。
- 现有创建版本 procedure 输入/生成绑定需要同步,但不修改持久表字段。
## 实现顺序
1. 契约先补 `versionNumber?: number` 和本地 `publication`,明确版本号校验与兼容。
2. 创建版本事务在未传时自动递增;传入时必须大于现有最大版本号,并在同事务内校验。
3. AGC 发布绑定保存 `gameId`、最新版本、状态和 revision;按账号和 API origin 隔离绑定。
4. AGC 打开项目回读已有作品;旧项目通过 `localProjectId` 恢复关联。
5. 发布面板区分首次发布/更新,给出当前版本和可编辑目标版本输入。
6. Rust 发布 facade 在更新模式跳过创建 game,使用原 gameId 创建新 version;幂等账本把目标版本号纳入发布意图,不能仅以 ZIP 摘要合并不同发布。
7. 验证重复提交、响应丢失、切换账号、版本冲突、旧作品恢复,以及旧公开版本在新版本审核中继续可玩。
## 数据库影响
- 不新增表。
- 不修改 `version_number` 字段类型或重排字段。
- 修改创建版本 procedure 输入 DTO 与生成绑定;按当前 SpacetimeDB 门禁验证。
- 老客户端不传版本号仍自动递增,已有版本和历史数据不需要回填。
## 验证
- 领域/数据库创建版本测试:默认递增、指定更高版本、重复/较小/零/负数/非整数/越界输入、并发相同目标号。
- AGC 发布面板测试:首发、更新、默认目标版本、用户改号和恢复失败。
- AGC Rust 发布测试:跳过 create game、原 gameId、新 version、结果持久化、幂等意图区分。
- `npm run typecheck`、定向 Rust/Vitest、schema/生成绑定检查、编码与 diff。
- 真机发布:首次发布 → 重开项目 → 修改游戏 → 指定更高版本 → 更新送审 → 审核后原公开链接运行新包。
@@ -0,0 +1,52 @@
# 【实施计划】后台游戏审核详情与待审版本试玩
| 字段 | 值 |
| --- | --- |
| Milestone | `docs/project-memory/plans/【里程碑】后台游戏审核详情与待审版本试玩-2026-10-02.md` |
| Status | in-progress |
| Owner | Genarrative Agent |
## 修改边界
### 允许修改
- `server-rs/crates/api-server`:管理员版本详情/预览会话/待审版本资源读取,以及现有发行资源公共逻辑的安全复用。
- `server-rs/crates/shared-contracts`、`apps/admin-web/src/api`:审核详情、预览会话和资料字段 DTO。
- `apps/admin-web/src/pages/AdminGameDistributionReviewPage.tsx` 及其测试:详情工作台、试玩入口、审核动作刷新。
- 现有 admin 权限映射、发行 sandbox/CSP/storage 兼容逻辑的必要扩展。
- 当前玩法主规范、里程碑证据和实施计划。
### 明确不修改
- `spacetime-module` 持久表结构、`migration.rs`、SpacetimeDB schema 和现有生成绑定,除非调研发现当前字段不足并重新获得迁移确认。
- `/api/external/v1`、作者发布页面、公开游戏详情和用户评价数据模型。
- 审核员分派、批量审核、精选推荐和后台游戏资料编辑。
## 实现顺序
1. 固化现有详情响应的字段映射与权限边界,先补 admin DTO 和契约测试。
2. 定义版本绑定的短期预览会话与错误语义,复用发行 ZIP 路径白名单、大小限制、HTML 注入、CSP 和响应头。
3. 实现管理员待审版本预览资源读取,确认公开发行路径不能读取未公开版本。
4. 将审核列表升级为详情工作台:资料分组、发布者、版本摘要、封面/截图、试玩状态和审核动作。
5. 补齐正常、拒绝、过期、越权、Cookie、缺文件、资源 404、CAS 冲突和幂等重放测试。
6. 运行真实本地审核 smoke,确认打开的是待审 `versionId`,不是当前公开版本;完成后回写主规范并删除临时计划。
## 验证命令
1. `npm run typecheck`
2. `npm exec -- vitest run apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx apps/admin-web/src/api/adminApiClient.test.ts --root .`
3. `cargo test -p api-server game_distribution --manifest-path server-rs/Cargo.toml`
4. `cargo fmt --all --manifest-path server-rs/Cargo.toml -- --check`
5. `npm run check:encoding`
6. `npm run check:doc-index`
7. `git diff --check`
8. 本地 `npm run dev` + 管理员登录 + 待审版本详情/试玩 smoke。
## 风险与回滚点
- **预览越权**:Token 必须绑定 `versionId` 和过期时间;错误实现会把私有待审包变成可枚举资源。回滚点是关闭预览会话路由,不影响公开发行网关。
- **版本串播**:预览 URL 不能复用 `game.currentVersion.entryUrl`;详情和资源读取都必须携带并校验待审版本。回滚点是隐藏试玩按钮,保留资料审核。
- **sandbox 放宽**:不得添加 `allow-same-origin`;如 storage 兼容层回归,回滚新增注入逻辑而不放宽 sandbox。
- **资料漂移**:详情必须展示版本冻结快照;若快照缺失,只显示明确缺失状态,不静默用作者当前资料替代。
- **审核并发**:继续使用 `publicationRevision` 和幂等键;CAS 冲突只刷新,不自动重放决定。
- **API 兼容**:新字段可选,旧后台响应不能因缺少冻结资料而崩溃;预览接口失败不能影响公开游戏游玩。
@@ -0,0 +1,38 @@
# 【里程碑】AGC 已发布游戏版本更新
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | proposed |
| Date | 2026-10-02 |
| Parent Spec | `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md#AGC 游戏分发与在线游玩合同` |
## 目标
让 AGC 区分首次发布与已有作品更新:已有作品继续使用原 `gameId`,创建不可覆盖的新版本;发布时用户可以决定目标正整数版本号。
## 范围
- 本地项目清单保存平台作品绑定和最近版本状态。
- 打开项目时回读绑定作品;旧清单按 `localProjectId` 做一次性恢复。
- 发布面板显示首次发布/更新模式、当前版本和建议目标版本。
- 用户在发布时填写目标版本号,默认最大版本号加一。
- 服务端可选接收 `versionNumber`;旧客户端不传时继续自动递增。
- 同一 `gameId` 的新版本上传、校验、送审和结果回写。
## 不在范围内
- AGC 完整版本历史页、版本回滚、版本删除、版本对比。
- 覆盖已有版本号或原地替换发行包。
- 新增 SpacetimeDB 持久表或修改 `game_distribution_version.version_number` 类型。
- 改变公开 URL、审核状态机、发行网关或后台审核权限。
## 验收标准
- [ ] 首次发布完成后项目清单保存 `gameId`、`versionId`、版本号、状态和 `publicationRevision`。
- [ ] 重新打开同一项目显示“更新游戏”,不再显示“创建平台游戏”。
- [ ] 默认目标版本为最大版本号加一,用户可改为更大的正整数。
- [ ] 目标版本不大于已有最大版本号时服务端拒绝,不能覆盖旧版本。
- [ ] 更新使用同一 `gameId`,只增加一个新 `versionId`,公开地址保持不变。
- [ ] 旧项目没有发布绑定时能按作者和 `localProjectId` 恢复;恢复失败时明确回到首次发布。
- [ ] 上传响应丢失、重复提交、版本号冲突和切换账号不会创建重复游戏或错误接管作品。
@@ -0,0 +1,51 @@
# 【里程碑】后台游戏审核详情与待审版本试玩
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | in-progress |
| Date | 2026-10-02 |
| Parent Spec | `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md#后台游戏审核详情与待审版本试玩合同` |
## 目标
把后台游戏版本审核从“列表操作”补成可复核的审核详情工作台:审核员能确认发布者和冻结游戏资料,能试玩当前待审版本,并能在同一详情上下达审核决定。
## 范围
- 待审版本详情回读。
- 发布者、游戏资料、版本包摘要和冻结资料展示。
- 单个待审版本的短期隔离试玩会话。
- 试玩资源、Cookie、sandbox、CSP 和版本绑定边界。
- 通过、拒绝、安全下架动作与现有 CAS/幂等规则对齐。
- admin-web、api-server、shared-contracts 和相关测试的契约同步。
## 不在范围内
- 新增游戏审核、发布者、试玩会话或游戏资料持久化表。
- 审核员分派、SLA、批量审核和精选推荐。
- 后台修改游戏资料、重新构建或重新上传发行包。
- 公开发行接口、作者发布链路和 `/api/external/v1`。
## 依赖与前置条件
- 现有管理员版本详情接口继续返回游戏投影和版本冻结资料。
- 现有 SpacetimeDB `game_distribution_game` 与 `game_distribution_version` 字段满足详情展示。
- 现有发行 ZIP 校验、静态资源白名单和 HTML storage 兼容层可以抽取复用。
- 现有游戏审核权限映射作为第一版预览权限,不新增权限角色。
## 验收标准
- [ ] 审核详情显示发布者、完整游戏资料、版本摘要和审核状态。
- [ ] 详情使用待审版本冻结资料,不被后续 game 行资料修改污染。
- [ ] 审核员可以打开当前 `versionId` 试玩,且 JS/CSS/图片/音频等同包资源可加载。
- [ ] 预览 Token 绑定版本、短期有效、过期和越权读取均失败。
- [ ] 预览不读取平台 Cookie,不授予 `allow-same-origin`,保留发行 sandbox/CSP/storage 约束。
- [ ] 通过、拒绝、安全下架继续执行理由、CAS、幂等和刷新规则。
- [ ] 无 SpacetimeDB schema、migration、公开契约破坏性变化。
## 证据要求
- 自动化:admin-web 详情/动作测试、api-server 预览会话和权限测试、DTO/类型检查、编码和 diff 检查。
- 运行时:本地管理员打开待审版本,确认资料、封面/截图和待审包试玩;确认旧公开版本不会被误播。
- 边界:过期 Token、错误 versionId、Cookie、越权管理员、缺入口、资源 404、CAS 冲突和幂等重放。
@@ -94,14 +94,14 @@
### 身份、状态、审核与更新
- `gameId` 是服务端分配的稳定游戏身份;`ownerUserId` 只从当前认证主体派生。AGC 的本地 `projectId` 只能作为作者名下的关联提示,不能证明云端游戏所有权。网页上传和 AGC 发布使用相同游戏、版本与上传记录,不建立两套发行系统。
- 同一作者用相同 `localProjectId` 再次发布时复用既有 `gameId` 并只新增版本;游戏身份、版本号和服务端校验都不依赖客户端传来的路径或 ID 可信度。缺少 `localProjectId` 的旧客户端仍可发布,但会被视为新建游戏。
- 每次发行分配唯一 `versionId` 与游戏内递增 `versionNumber`。版本的游戏归属、包摘要、已确认字节和送审资料冻结后不可变;改包或改送审资料必须创建新版本。版本状态可以流转,内容不能原地覆盖。
- `gameId` 是服务端分配的稳定游戏身份;`ownerUserId` 只从当前认证主体派生。AGC 本地 `projectId` 只用于关联提示,不能证明云端游戏所有权。AGC 发布成功后必须把 `gameId`、最近 `versionId`、`versionNumber`、`publicationRevision` 和状态写入项目清单;网页上传和 AGC 发布使用相同游戏、版本与上传记录,不建立两套发行系统。
- 同一作者用相同 `localProjectId` 再次发布时复用既有 `gameId` 并只新增版本;AGC 如果清单缺少发布记录,首次更新前按作者作品回读和 `localProjectId` 做一次性恢复。缺少 `localProjectId` 的旧客户端仍可发布,但会被视为新建游戏。
- 每次发行分配唯一 `versionId`,`version_number` 已存在于版本表。服务端保留“未传版本号时自动取最大值加一”的旧客户端兼容行为;AGC 更新发布必须让用户填写正整数版本号,默认建议为当前最大版本号加一。版本号必须严格大于该游戏已有最大版本号,不能覆盖、重用或静默改写旧版本;允许跳号但发布前明确展示当前版本与目标版本。
- 游戏单独保存 `publicationRevision`、`activeVersionId` 和可见性 `unpublished | published | suspended`;正式可见性由服务端持久化事实决定。未通过审核时 `activeVersionId` 为空;`suspended` 是管理员安全下架,作者不能自行解除。
- 版本状态为 `awaiting_upload → uploaded → validating → pending_review → published`。上传确定失败进入 `upload_failed`,验证失败进入 `validation_failed`,人工拒绝进入 `rejected`;尚未公开版本可以撤回为 `cancelled`,已公开版本可撤销为 `revoked`。已成功上传的相同字节可重新校验;内容改变或审核拒绝后的修改必须新建版本。
- 首版建议人工审核。审核员检查游戏资料、真实桌面运行、声明移动适配、内容与外部请求被阻断的行为;自动包校验通过只进入 `pending_review`,不自动公开。审核记录保存审核者、目标版本、结论、理由和时间。后台只授权现有管理员身份,不让普通作者调用审核动作。
- 更新送审时旧 `activeVersionId` 继续服务目录、详情与游玩。审核通过并完成对象可读验证后,一次事务切换当前公开版本和公开资料;新版本上传、校验、审核或对象安装失败均不改变旧版本。
- 发布页先自动填入可用标题和封面,首次需要确认必需资料,此后复用上次资料;用户一次“提交发布”动作串联校验、上传和送审,并显示真实阶段。等待审核不能显示“已发布”;成功后提供查看详情和复制公开链接。
- AGC 发布面板在无绑定时显示“首次发布”,存在绑定时显示“更新游戏”,回填当前版本号、状态和建议目标版本号。首版不做完整版本历史/回滚管理,只提供目标版本号选择、当前状态和新版本发布。
### 幂等、并发与恢复
@@ -464,3 +464,75 @@
2026-10-01 按用户后续实现授权完成公开快照、共享契约、后端响应和共享评分文本接入。绑定以仓库固定 SpacetimeDB 2.8.3 生成,持久化表未变;只为列表最终返回游戏聚合评价。隔离库没有既有评分 fixture,本次复用既有 helper 临时创建两个账号、两款游戏和两条评价,未新增仓库 E2E 脚本或完整管理矩阵。
本地验证通过,尚待用户验收和生产部署。未跑完整仓库测试、完整评价/后台管理 E2E、真实手机、长标题/极大人数浏览器矩阵、浏览器登录改分和实际发行包游玩;返回刷新使用真实 API 改分验证,登录用户公开列表已在 API smoke 对照。
## 后台游戏审核详情与待审版本试玩合同
| 字段 | 值 |
| --- | --- |
| Version | 1.0 |
| Status | in-progress |
| Date | 2026-10-02 |
| 适用范围 | admin-web 游戏版本审核、作者/游戏资料回读、待审版本隔离试玩 |
| 数据事实源 | `game_distribution_game`、`game_distribution_version` 及现有版本冻结资料 |
### 目标
- 审核员在一个详情工作台内看到发布者、游戏资料、待审版本包信息、审核状态和审核历史。
- 审核员可以实际试玩当前待审 `versionId`,不能误播旧的公开版本或另一个待审版本。
- 试玩保持发行网关的 opaque sandbox、Cookie 隔离、CSP、路径白名单和运行期 storage 兼容边界。
- 通过、拒绝、安全下架继续使用现有 `publicationRevision`、认证管理员和幂等语义。
### 非目标
- 不新增平行游戏审核表、游戏资料表、发布者表或公开发行通道。
- 不把待审包提前写入公开发行网关路径,不改变未审核版本的可见性。
- 不在审核页提供游戏资料编辑、重新打包、重新上传或替作者发布。
- 不把管理员 Access Token 放进 iframe URL,也不授予 `allow-same-origin`。
### 详情数据与权限
- 审核列表使用现有版本审核列表;点击版本后回读现有管理员版本详情。
- 详情必须展示:发布者 ID/名称/头像、游戏标题、简介、详细介绍、分类、标签、操作方式、支持设备、输入方式、方向、封面、截图、版本号、包大小、文件数、SHA-256、提交时间、审核时间、审核理由和 `publicationRevision`。
- 展示的游戏资料优先使用该版本冻结的 `metadata_json`;不能用审核期间作者后来修改的 game 行资料替代待审快照。
- 发布者和游戏资料仅通过管理员受保护接口读取;公开目录不得因此增加作者私有字段或待审版本字段。
- 封面与截图通过现有后台素材换签接口读取 Object Key,不把私有 Object Key 当作浏览器直链。
### 待审版本试玩
- 管理员先创建绑定单个 `versionId` 的短期预览会话,再得到 `previewUrl` 与过期时间。
- 预览会话不持久化到 SpacetimeDB;Token 仅能读取绑定版本包,过期后不可继续读取。
- 预览资源读取必须拒绝平台 Cookie,继续返回 `nosniff`、跨来源静态资源头、最小权限 CSP,并复用发行 ZIP 路径和大小白名单。
- HTML 入口必须保留 `sandbox="allow-scripts"`,继续注入隔离的运行期 `localStorage/sessionStorage` 兼容层;不得添加 `allow-same-origin`。
- 试玩入口、JS、CSS、图片、音频和其它包内资源都必须来自同一个绑定的待审版本;读取失败显示明确资源/入口错误,不降级到公开版本。
### 审核动作
- 详情页保留通过、拒绝、安全下架;列表页保留快捷动作。
- 拒绝和下架继续要求理由;所有写操作携带 `publicationRevision` 和 `Idempotency-Key`。
- 并发修订返回冲突时只提示刷新并重新读取,不覆盖其他审核员的决定。
- 审核操作成功后刷新详情和待审列表,不能只修改前端本地状态。
### 契约与数据库边界
- 第一版不新增 SpacetimeDB 表、字段、migration 或生成绑定。
- 复用现有 `game_distribution_game`、`game_distribution_version`、`metadata_json`、审核人/时间/理由和 `publicationRevision` 字段。
- 预览会话属于 api-server 短期运行态;如果未来需要审核任务分派或完整试玩审计,另开规范评估是否增加持久化表。
- 管理员预览接口属于后台契约,必须同步 admin DTO、权限映射、API 测试和前端类型;不改变 `/api/external/v1`。
### 验收标准
| 条款 | 验收方式 | 证据 |
| --- | --- | --- |
| 发布者和资料 | 详情回读 + admin-web 页面检查 | admin-web 详情测试通过,显示作者、资料、版本摘要和冻结 JSON |
| 待审快照 | 修改 game 行资料后仍显示待审版本冻结资料 | 详情响应和页面已接入 `frozenMetadata`;真实数据验证待补 |
| 版本隔离试玩 | 用两个版本验证预览 URL 不能串读,待审包资源全部可加载 | 预览会话路由和版本绑定已实现;真实审核试玩待补 |
| 安全边界 | Cookie、过期 Token、越权 versionId、公开路径访问拒绝 | sandbox、Cookie 拒绝和短期 Token 已实现;HTTP 边界测试待补 |
| 审核动作 | 通过/拒绝/下架、理由、CAS 冲突、幂等重放 | 既有审核动作保留,详情页已接入;回归测试沿用现有 6 项审核页面测试 |
| 工程门禁 | 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。
### 未决问题
- 管理员权限继续复用现有游戏审核权限映射,还是单独增加 `game-review-preview` 动作权限;第一里程碑默认复用现有审核权限,不扩大权限模型。
- 预览 Token 的单次 HTML 读取与同包静态资源多次读取需要统一 TTL 和失效规则;第一里程碑使用短 TTL 的版本绑定会话。