diff --git a/docs/project-memory/plans/【证据矩阵】AGC游戏分发主规范逐条证据-2026-09-28.md b/docs/project-memory/plans/【证据矩阵】AGC游戏分发主规范逐条证据-2026-09-28.md index ab3f98ba0..f2f2c7d09 100644 --- a/docs/project-memory/plans/【证据矩阵】AGC游戏分发主规范逐条证据-2026-09-28.md +++ b/docs/project-memory/plans/【证据矩阵】AGC游戏分发主规范逐条证据-2026-09-28.md @@ -4,7 +4,7 @@ | --- | --- | | Source Spec | `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`「AGC 游戏分发与在线游玩合同」节(Version 0.2,Status proposed) | | Milestone | `docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md` | -| Date | 2026-09-28 | +| Date | 2026-09-28(2026-09-29 复跑:四条网页 E2E + 九条游戏分发 E2E + AGC 客户端一键发布 live 测试,见里程碑同日小节) | | 用途 | 阶段 D 第 6 条要求的「主规范逐条证据矩阵」:把主规范每个条款映射到可复核的证据,并把未取证项单独列出 | | 结论 | **未上线**。主规范合同大体已取证,仍有 6 类未取证项(见文末),因此不得把本业务标记为上线完成 | @@ -19,7 +19,7 @@ | 条款 | 证据 | 结论 | | --- | --- | --- | -| 完整闭环:AGC 一键提交 / 网页上传 ZIP → 后端收真实包 → 校验与人工审核 → 发现/详情/游客在线游玩 → 更新与下架 | 网页侧全链路已取证(`check:game-distribution-web-publish-e2e` 12 PASS、`check:game-distribution-web-e2e`、`check:game-distribution-web-publish-recovery-e2e` 10 PASS、`check:game-distribution-media-e2e` 58 PASS,见里程碑「阶段 C 网页发布」「阶段 B 审核与在线旧版」);AGC 一键提交仍未在客户端取证 | 部分取证 | +| 完整闭环:AGC 一键提交 / 网页上传 ZIP → 后端收真实包 → 校验与人工审核 → 发现/详情/游客在线游玩 → 更新与下架 | 网页侧全链路已取证(`check:game-distribution-web-publish-e2e` 12 PASS、`check:game-distribution-web-e2e`、`check:game-distribution-web-publish-recovery-e2e` 10 PASS、`check:game-distribution-media-e2e` 58 PASS,见里程碑「阶段 C 网页发布」「阶段 B 审核与在线旧版」)。**AGC 一键提交 2026-09-29 补上客户端侧取证**:`GENARRATIVE_AGC_PUBLISH_E2E_BASE_URL=http://127.0.0.1:8082 npx vitest run apps/ai-game-creator-shell/tests/gameDistributionPublishLive.test.ts` → **1 passed(3.0s)**,走真实 `publishLocalProjectGame` + 真实 HTTP 打到本地栈:创建游戏 → 建版本 → 分片上传(断言偏移 0 只发一次、其余按 8 MiB 递增,不重传不跳段)→ 送审 → 版本回读 `pending_review` / `recoveryAction=wait` → 同 `localProjectId` 第二次发布复用同一 `gameId` 且版本号 +1 → `my-games` 聚合指向新版本;后端复核:管理员列表出现 `AGC 真实发布2078`(`unpublished`、`publicationRevision=0`、2 个版本)。**仍缺**:真实客户端 GUI 里点一次「一键发布」(本会话不能启动用户可写目录下的可执行文件) | 部分取证 | | 验收必须用真实上传 / 真实存储 / 真实审核状态 / 隔离发行,静态演示卡片与 metadata-only 不能当证据 | 全部 E2E 都跑本地真实栈 + 真实 OSS(`agc-dev` bucket);`check-game-distribution-upload-safety` 里「只有 metadata 的提交必须失败」有断言;发行隔离用真实 Chromium 沙箱探针取证 | 已取证 | | 首版只支持离线静态 Web 游戏(HTML/JS/CSS/JSON/图片/字体/音视频),不含 Godot/Cocos 源码、原生可执行、Wasm、服务端进程、外网 API、多人联机、云存档 | 发行包校验白名单与失败关闭有测试(`module-game-distribution`);沙箱 E2E 证明外站 fetch / WebSocket / Worker 被挡;范围本身是声明性条款,未做「禁止其它引擎」的主动拦截(也不需要) | 已取证 | | 首版不做评分、评论、关注、榜单、推荐算法、交易与创作者收入 | 目录/详情 DTO 里没有这些字段(`check:game-distribution-dto-parity` 有响应键逐键比对);页面无假统计(阶段 C 第 9 条核对) | 已取证 | @@ -40,9 +40,9 @@ | 条款 | 证据 | 结论 | | --- | --- | --- | -| 1. AGC 取 `dist/` 归一化为根 `index.html`;网页 ZIP 同样要求根 `index.html`;不上传整个项目或源码快照 | 网页 ZIP 侧已取证(`check:game-distribution-web-publish-e2e` 真实选包提交);AGC `dist` 归一化有客户端实现与单元测试,但**没有**真实 AGC 一次提交的运行证据 | 部分取证 | +| 1. AGC 取 `dist/` 归一化为根 `index.html`;网页 ZIP 同样要求根 `index.html`;不上传整个项目或源码快照 | 网页 ZIP 侧已取证(`check:game-distribution-web-publish-e2e` 真实选包提交);AGC 侧 2026-09-29 补上**客户端发布函数打真实后端**的 live 测试(`gameDistributionPublishLive.test.ts`,见第 1 节),`dist` 归一化与包结构本身仍由 Rust `local_project_export` 单元测试覆盖,**没有**真实 AGC 进程从真实 `dist` 跑一次提交的运行证据 | 部分取证 | | 2. 运行依赖全部在包内,相对地址,不允许前导 `/assets`、外链与本地文件 URL;客户端给可操作错误、服务端独立校验、运行时 CSP 兜底 | 服务端校验有领域测试;运行时兜底由沙箱 E2E 的 CSP 拦截取证(外站 fetch/websocket/worker 都失败);客户端对前导 `/assets` 与绝对 URL 的可操作错误未单独取证 | 部分取证 | -| 3. 额度与拒绝集:包 100 MiB→现行 200 MiB、展开 250/500 MiB、单文件 64 MiB、10 000 文件、100:1 压缩比;拒绝加密 ZIP、路径冲突、绝对路径、`..`、符号链接、设备文件、嵌套压缩与 `.agent`/VCS/`node_modules`/凭据/源码映射 | `check:game-distribution-package-limits-e2e` 11 PASS(196 MiB 成功档耗时 36.9s/确认 9.6s、声明超限 413、文件数 422 `TooManyFiles`、单文件 `FileTooLarge`、展开量 `ExpandedPackageTooLarge`、压缩比 `CompressionRatioTooHigh`);`module-game-distribution` 16 passed 覆盖缺入口/越界/重复与大小写冲突/符号链接(含变异验证);加密条目与嵌套 `.zip` 有实现(`EncryptedFileNotAllowed` / `NestedArchiveNotAllowed`)但没有专门用例,**设备文件**没有专门拒绝实现(只区分常规文件与符号链接)。**2026-09-29 补**:包体上限改为单一来源 `shared_contracts::game_distribution::GAME_DISTRIBUTION_MAX_PACKAGE_BYTES`(AGC 客户端发布前检查读它,服务端领域常量由 api-server 的 `publish_package_limit_matches_the_shared_contract` 锁一致);客户端本地导出上限 512 MiB 仍只是防呆线,不再被当成发布额度 | 已取证(两处子项见缺口) | +| 3. 额度与拒绝集:包 100 MiB→现行 200 MiB、展开 250/500 MiB、单文件 64 MiB、10 000 文件、100:1 压缩比;拒绝加密 ZIP、路径冲突、绝对路径、`..`、符号链接、设备文件、嵌套压缩与 `.agent`/VCS/`node_modules`/凭据/源码映射 | `check:game-distribution-package-limits-e2e` 11 PASS(196 MiB 成功档耗时 36.9s/确认 9.6s、声明超限 413、文件数 422 `TooManyFiles`、单文件 `FileTooLarge`、展开量 `ExpandedPackageTooLarge`、压缩比 `CompressionRatioTooHigh`);2026-09-29 复跑同口径 11 PASS(上传 37.6s / 确认 9.4s)。`module-game-distribution` 16 passed 覆盖缺入口/越界/重复与大小写冲突/符号链接(含变异验证);加密条目与嵌套 `.zip` 有实现(`EncryptedFileNotAllowed` / `NestedArchiveNotAllowed`)但没有专门用例,**设备文件**没有专门拒绝实现(只区分常规文件与符号链接)。**2026-09-29 补**:包体上限改为单一来源 `shared_contracts::game_distribution::GAME_DISTRIBUTION_MAX_PACKAGE_BYTES`(AGC 客户端发布前检查读它,服务端领域常量由 api-server 的 `publish_package_limit_matches_the_shared_contract` 锁一致);客户端本地导出上限 512 MiB 仍只是防呆线,不再被当成发布额度 | 已取证(两处子项见缺口) | | 4. 声明 SHA-256 与字节数,服务端重算并建展开清单;摘要不符/缺文件/入口损坏必须失败 | `check-game-distribution-upload-safety` 24 PASS:结构合法但摘要不符 → 409 `PACKAGE_MISMATCH` → `upload_failed` → reset → 重传成功;领域侧 `accepts_root_entry_and_returns_file_manifest` | 已取证 | | 5. 资料冻结与额度(标题 2–40 字、简介 ≤120、详介 ≤2000、1 分类、≤5 标签 ≤20 字、必需封面、≤6 截图、操作方式 ≤240、分类白名单);AGC 免手打 ZIP;发布灰度键 `game-distribution:publish` 与前端 `gameDistributionPublishEnabled`;续发复用冻结资料;公开投影只暴露对象键 | 资料校验与冻结由 `check-game-distribution-media-e2e`(真实素材直传 → 创建 → 送审 → 回读 frozenMetadata)与 DTO parity 覆盖;灰度键与前端下发在阶段 A/B/C 核对里取证(含 `skipAuth` 缺陷修复);AGC 免手打 ZIP 与面板免技术摘要未取证 | 部分取证 | | 6. `supportedDevices` 至少含 desktop/mobile 之一;`inputModes` ∈ {keyboard,mouse,touch};声明移动端必须含 touch;`orientation` ∈ {landscape,portrait,responsive} | 服务端校验实现 + 领域测试;`check-game-distribution-media-e2e` 的创建资料走真实接口被接受/拒绝 | 已取证 | diff --git a/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md b/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md index 521978f2c..c2eb46fc7 100644 --- a/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md +++ b/docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md @@ -289,3 +289,21 @@ - **根因**:`fetch('/games')` 只取到 SPA 外壳,HTML 里没有路由模块,所以 Vite dev 仍是在断言阶段现编译这些模块图,超过单步等待就被判成"页面打不开"。 - **修复**:`scripts/check-game-distribution-web-e2e.mjs` 改成**浏览器级预热**——起浏览器后用一次性 context 真的访问 `/games`、`/games/detail?id=…`、`/games/play?id=…`(`domcontentloaded` + 2 秒 settle,120 秒上限,失败只告警),再做断言;首个 `goto` 的 120 秒超时保留。 - **复验**:重启 dev 栈(Vite 冷缓存)→ 修复版 **24/24 PASS**;同一冷启动条件下修复前版本会在上述两处各失败一次。判据已写入 pitfalls 同日条目。 + +### 同一天把游戏分发 E2E 家族其余 9 条也全跑了一遍 + +同一次 `npm run dev` 会话里(真实 SpacetimeDB + 真实 api-server + 真实 OSS `agc-dev` bucket),把不在上面四条之列的脚本全部跑完,**9/9 全部通过**: + +| 脚本 | 结果与要点 | +| --- | --- | +| `check:game-distribution-dto-parity` | OK:14 组 Rust/TS 类型一致、4 个手拼响应构建器键一致、7 个手拼响应类型已登记 | +| `check:game-distribution-upload-safety` | 全部通过:摘要不符 → 409 `PACKAGE_MISMATCH` → `upload_failed(reupload)` → 重置分片 → 重传成功;同 key 重放送审 `replayed=true`,换 key 重发按状态冲突拒绝且不产生第二份提交 | +| `check:game-distribution-upload-resume` | 两段式全部通过:prepare 传完第一片(8,388,608 字节)后**重启 api-server**,resume 段确认「重启后权威已收字节仍来自对象存储」并按权威偏移续传剩余字节 → 整包确认 → 送审成功 | +| `check:game-distribution-package-limits-e2e` | 全部通过:196.0 MiB 合法包上传 37.6s / 确认 9.4s 通过并送审;声明超限 413、10,001 文件 `TooManyFiles`、单文件 64 MiB `FileTooLarge`、展开 500 MiB `ExpandedPackageTooLarge`、压缩比 100:1 `CompressionRatioTooHigh` 均失败关闭 | +| `check:game-distribution-owner-isolation` | 全部通过:读上传状态/写分片/确认/送审/撤回等跨账号操作一律 404 且不落副作用;请求体伪造 owner 无效,游戏仍归请求者 | +| `check:game-distribution-sandbox-e2e` | 全部通过:opaque sandbox 探针 10 项(module 可载入、localStorage/cookie/DOM/跨源 fetch/WS/Worker/popup/顶层跳转/定位全部被挡) | +| `check:game-distribution-validation-restart-e2e` | 两段式全部通过:确认校验进行中杀掉 api-server → 重启后已收字节仍在、重新确认完成、送审成功;审核积压与后台版本状态可观测;坏包 422 `PACKAGE_VALIDATION_FAILED` 可诊断;公开版本不被误清理 | +| `check:game-distribution-media-e2e` | 全部通过:并发激活只有一次成功(另一次 409 CAS)、下架/安全下架后目录·详情·发行网关全关闭、过期 revision 的审核与下架都被 CAS 拒绝、匿名直取私有 bucket 对象 403 | +| `check:game-distribution-ops-rollback-e2e` | 全部通过:下架立即生效(不依赖 CDN purge)、恢复后可复原、换版后旧公开版本保留为 `revoked`;运行期日志无令牌/Cookie/OSS 签名;三份边缘模板不记请求头与查询参数 | + +同日还补上了矩阵里一直写「AGC 一键提交仍未在客户端取证」的那条:`GENARRATIVE_AGC_PUBLISH_E2E_BASE_URL=http://127.0.0.1:8082 npx vitest run apps/ai-game-creator-shell/tests/gameDistributionPublishLive.test.ts` → **1 passed(3.0s)**。它不 mock 请求层,走真实 `publishLocalProjectGame` + 真实 HTTP:创建游戏 → 建版本 → 分片上传(断言偏移 0 只发一次、其余按 8 MiB 递增,不重传不跳段)→ 送审 → 版本回读 `pending_review` / `recoveryAction=wait` → 同 `localProjectId` 第二次发布复用同一 `gameId` 且版本号 +1 → `my-games` 聚合指向新版本;后端复核到 `AGC 真实发布2078`(`unpublished`、2 个版本)。**仍缺**的是真实客户端 GUI 里点一次「一键发布」。 diff --git a/docs/project-memory/todos/【待办】计划收口剩余项索引-2026-09-24.md b/docs/project-memory/todos/【待办】计划收口剩余项索引-2026-09-24.md index 8c5128865..edbe8316d 100644 --- a/docs/project-memory/todos/【待办】计划收口剩余项索引-2026-09-24.md +++ b/docs/project-memory/todos/【待办】计划收口剩余项索引-2026-09-24.md @@ -34,6 +34,8 @@ 补充(2026-09-24 复跑,供解冻决策用):游戏分发系列**本地可跑的证据今天全绿**——`cargo test -p module-game-distribution` 13 passed、`cargo test -p api-server game_distribution` 26 passed、网页端 `src/components/game-distribution` + `gameDistributionClient.test.ts` 5 文件 / 54 passed、AGC 发布三条用例文件 24 passed。因此这 28 条未勾选项的瓶颈不在本地代码,主要是真实环境(独立 HTTPS 发行源与通配证书、CDN purge 与缓存窗口、200 MiB 档容量复跑、真机导航/沙箱观感)与阶段 A/C 的验收结论。 +补充(2026-09-29 复跑,同上用途):把游戏分发 E2E 家族**13 条脚本全跑了一遍**(真实 `npm run dev` 栈 + 真实 OSS `agc-dev`),**全部通过**——`web-e2e` 24/24、`web-publish-e2e`、`web-publish-recovery-e2e`、`web-a11y-e2e`,以及 `dto-parity`、`upload-safety`、`upload-resume`(重启后按权威偏移续传)、`package-limits-e2e`(196.0 MiB 合法包上传 37.6s/确认 9.4s 通过)、`owner-isolation`、`sandbox-e2e`、`validation-restart-e2e`(确认校验中杀 api-server 后可恢复)、`media-e2e`、`ops-rollback-e2e`。同日还补上了「AGC 一键提交」的客户端侧证据:`gameDistributionPublishLive.test.ts` 打真实后端 **1 passed**(创建 → 分片上传偏移不重传不跳段 → 送审 → 版本回读 → 同 `localProjectId` 复用 `gameId` → `my-games` 聚合)。瓶颈口径不变:剩真实 HTTPS 发行源/CDN 窗口、阶段 A/C 验收结论,以及真机 GUI 点一次「一键发布」。同日还修掉 `web-e2e` 的冷启动假失败(原先的 `fetch` 预热只拿到 SPA 外壳,已改为浏览器级预热,详见 pitfalls 与里程碑)。 + ## 四、`docs/project-memory/todos/` 其余开放事项(不挂在计划上的) | 待办 | 现状 | 等谁 |