补齐游戏分发主规范逐条证据矩阵
Project CI / AI game creator shell Rust crates (push) Successful in 1m32s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m52s
Project CI / Backend tests (push) Successful in 4m1s
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled

- 新增 docs/project-memory/plans/【证据矩阵】AGC游戏分发主规范逐条证据-2026-09-28.md:按主规范 9 节逐条映射证据与三档结论,HTTP 路由逐行核对,未取证项单列
- 里程碑阶段 D 第 6 条勾选,补「本轮核对(阶段 D 主规范逐条证据矩阵)」并更新阶段 D 现状
- 阶段 D 第 5 条注记告警现状:只有巡检 timer + 日志 operation,独立指标/告警规则未建
This commit is contained in:
kdletters
2026-09-28 21:50:48 +08:00
parent e8b76da809
commit d5dc95e822
2 changed files with 158 additions and 3 deletions
@@ -0,0 +1,147 @@
# 【证据矩阵】AGC 游戏分发主规范逐条证据
| 字段 | 值 |
| --- | --- |
| 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 |
| 用途 | 阶段 D 第 6 条要求的「主规范逐条证据矩阵」:把主规范每个条款映射到可复核的证据,并把未取证项单独列出 |
| 结论 | **未上线**。主规范合同大体已取证,仍有 6 类未取证项(见文末),因此不得把本业务标记为上线完成 |
## 怎么读这张表
- 结论只分三档:**已取证**(当天有真实运行记录或可复跑门禁)、**部分取证**(只覆盖条款的一部分,具体缺哪半写在证据里)、**未取证**(只有实现或计划,没有真实运行证据)。
- 「证据」尽量给命令或脚本名;历史取证记录都指向里程碑文末对应的「本轮核对」小节,里面有耗时、状态码与接口回读值。
- 本文件只归集证据,不授予阶段验收;阶段验收仍按里程碑的门禁(A 验收后才允许依赖它的阶段进入 accepted)。
- 本文件是临时过程材料,位于 `docs/project-memory/plans/`;主规范被评审通过后,稳定事实回写主规范与决策记录,本文件按里程碑收尾规则删除。
## 1. 交付目标与范围
| 条款 | 证据 | 结论 |
| --- | --- | --- |
| 完整闭环: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 一键提交仍未在客户端取证 | 部分取证 |
| 验收必须用真实上传 / 真实存储 / 真实审核状态 / 隔离发行,静态演示卡片与 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 条核对) | 已取证 |
## 2. 入口与产品体验
| 条款 | 证据 | 结论 |
| --- | --- | --- |
| 网页 `/` → `/games`;桌面导航「游戏/创作/项目/我的」,移动「游戏/我的」;移动端不再用欢迎遮罩阻断游戏浏览 | `check:game-distribution-web-a11y-e2e` 11 PASS 覆盖桌面左侧栏与移动底部导航切换;移动端仍会弹既有的「创作工具请用电脑端」提示但可关闭后正常浏览游玩(同脚本有记录) | 已取证 |
| 目录 `/games`、详情 `/games/detail?id=`、游玩 `/games/play?id=`、作者管理 `/games/mine`;不建第二套账号系统 | 四条路由在 nginx SPA 白名单内(`npm run check:nginx-spa-routes` → OK,12 routes / 3 templates);`check-game-distribution-web-e2e` 走 `/games/detail` 深链与返回恢复;账号仍用平台登录态 | 已取证 |
| 目录推荐位只消费后台已公开游戏,其余按发布时间;支持关键词/分类/设备筛选、写 URL、返回恢复、真实空态 | `check-game-distribution-web-e2e`:关键词命中/空态+「重置筛选」、分类与设备切换、返回后 URL 恢复 `keyword/category/device`(顺带修掉 StrictMode 清空关键词的真实缺陷) | 已取证 |
| 详情主动作「立即玩」,展示封面/标题/作者/短简介/设备/截图/操作方式;不虚构评分与玩家数;游客浏览与启动无需登录 | 同脚本:游客直接进详情并启动发行包内容(iframe 文本含包内标记);目录游玩次数直接取服务端 `playCount`(新游戏显示 0 次),无假统计 | 已取证 |
| 游玩页先给封面+开始按钮,点击后才加载;提供加载/超时/重试/返回/全屏;iframe load 不等于运行验证 | 同脚本:进游玩页**先无 iframe**,点「开始游戏」才挂载 `sandbox="allow-scripts"` 的 iframe;退出后 iframe 消失;详情接口被打断后出现「重新加载」入口;全屏在 a11y 脚本里实点后 `document.fullscreenElement` 生效 | 已取证 |
| 移动端全宽/底部动作/≥44px 点击区/safe-area/横竖屏与键盘焦点;仅桌面游戏在手机显示「请在电脑上游玩」;横屏提示不能作为启动前提 | `check:game-distribution-web-a11y-e2e` 覆盖 safe-area `env(safe-area-inset-*)`、键盘焦点+Enter 走完目录→详情→游玩→开始、390×844 无横向溢出;旋转提示与桌面专属门槛由网页游玩脚本覆盖 | 已取证 |
| 视觉延续 warm token 与共享组件;发布打开独立弹窗/抽屉;技术信息只在失败诊断时展示 | 阶段 C 第 9 条核对:四个页面直接用 `PlatformActionButton`/`PlatformStatusMessage`/`PlatformEmptyState`,`gameDistribution.css` 只用 `var(--platform-*)`;AGC 发布面板不展示 ZIP 路径/文件数/体积属 AGC 侧条款,见未取证清单 | 部分取证 |
## 3. 真实发行包与资料合同
| 条款 | 证据 | 结论 |
| --- | --- | --- |
| 1. AGC 取 `dist/` 归一化为根 `index.html`;网页 ZIP 同样要求根 `index.html`;不上传整个项目或源码快照 | 网页 ZIP 侧已取证(`check:game-distribution-web-publish-e2e` 真实选包提交);AGC `dist` 归一化有客户端实现与单元测试,但**没有**真实 AGC 一次提交的运行证据 | 部分取证 |
| 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`)但没有专门用例,**设备文件**没有专门拒绝实现(只区分常规文件与符号链接) | 已取证(两处子项见缺口) |
| 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` 的创建资料走真实接口被接受/拒绝 | 已取证 |
| 7. 原始 ZIP、未审核展开目录、审核资料为私有对象;公开版本不暴露源码镜像键、本地路径、凭据;运行文件只能由发行网关按白名单读取,不能绕过网关直取 OSS | `platform-oss` 对象级 ACL 修复后 `E2E_REQUIRE_PRIVATE_BUCKET=1 npm run check:game-distribution-media-e2e` 65 PASS,匿名直取直传封面与发行包都是 403;目录/详情响应文本无 `aliyuncs.com`/`Signature=` | 已取证 |
| 8. 发行网关路径/内容类型白名单/未知扩展名 404/`nosniff`/CORP/CORS/HTML CSP/Cookie 403/进程内有界缓存 | `check-game-distribution-media-e2e`、`check-game-distribution-sandbox-e2e`(22 PASS,真实 Chromium)、本轮 `check:game-distribution-ops-rollback-e2e` 都断言响应头;带 Cookie 403 在沙箱脚本里取证 | 已取证 |
| 9. 发行入口由服务端按 gameId 派生同源路径 `/games/{gameId}/`,不接受管理员填写或部署侧配置;派生失败即失败,客户端按当前 origin 解析 | 阶段 B 核对:审核通过后 `currentVersion.entryUrl=/games/<gameId>/`;`check-game-distribution-web-e2e` 里 iframe src 就是同源 `/games/<gameId>/`;三份 nginx 模板的映射由本轮脚本断言 | 已取证 |
## 4. 身份、状态、审核与更新
| 条款 | 证据 | 结论 |
| --- | --- | --- |
| `gameId` 服务端分配、`ownerUserId` 只从认证主体派生;AGC `localProjectId` 只是关联提示;两端共用同一游戏/版本/上传记录 | `check:game-distribution-owner-isolation` 21–23 PASS(伪造 owner 不生效、越权读写送审撤回全部拒绝);`localProjectId` 复用行为有客户端与领域测试,未在 AGC 真机取证 | 部分取证 |
| 相同作者相同 `localProjectId` 复用既有 `gameId` 只加版本;旧客户端缺该字段视为新建 | 领域/客户端实现 + 单元测试;未在真实 AGC 运行取证 | 部分取证 |
| 每次发行唯一 `versionId` + 递增 `versionNumber`;归属/摘要/已确认字节/送审资料冻结后不可变,改包或改资料必须新建版本 | 领域测试(`a_version_accepts_only_one_confirmed_package`、状态机用例)+ `check-game-distribution-upload-safety`:换 key 重复送审 409 且版本数仍为 1 | 已取证 |
| 游戏保存 `publicationRevision`/`activeVersionId`/可见性 `unpublished|published|suspended`;未过审 `activeVersionId` 为空;`suspended` 只能管理员操作 | 阶段 B 核对:CAS 过期批准/过期下架 409、下架后目录与网关不可见;本轮 ops 脚本实测安全下架/恢复只能由管理员做 | 已取证 |
| 版本状态机 `awaiting_upload → uploaded → validating → pending_review → published`,失败态与 `cancelled`/`revoked` | 领域测试 `version_state_machine_reaches_pending_review_only_after_package_validation`、`recovery_action_covers_every_version_status`;`check-game-distribution-validation-restart-e2e` 实测 `upload_failed`/`uploaded` | 已取证 |
| 首版人工审核:自动校验只进 `pending_review`,不自动公开;审核记录保存审核者/版本/结论/理由/时间;后台只授权管理员 | `check-game-distribution-media-e2e` 58 PASS 覆盖待审不可见、可批可拒、`reviewReason`/`reviewedAt` 可追溯、普通作者调审核接口 403 | 已取证 |
| 更新送审时旧 `activeVersionId` 继续服务;审核通过并核验对象可读后一次事务切换;上传/校验/审核/安装失败不改变旧版 | `check-game-distribution-media-e2e`:更新待审与被拒期间公开摘要与网关内容仍是旧版;本轮 ops 脚本:换版后旧版本保留为 `revoked` 记录 | 已取证 |
| 发布页自动填入标题与封面、复用上次资料;一次「提交发布」串联校验/上传/送审并显示真实阶段;等待审核不显示已发布;成功后给查看详情与复制链接 | `check:game-distribution-web-publish-e2e` 12 PASS(同账号看到待审、审核通过后广场出现、更新沿用同一 `gameId`);续发回填封面/截图未单独取证 | 部分取证 |
## 5. 幂等、并发与恢复
| 条款 | 证据 | 结论 |
| --- | --- | --- |
| 所有创建/提交/审核/撤销/下架带 `Idempotency-Key`;同 key 同请求重放,不同请求 409 `IDEMPOTENCY_CONFLICT`;至少保留 30 天 | 领域幂等测试 + `check-game-distribution-upload-safety`(同 key 重放返回同一条、换请求体 409);30 天保留期由库内 `expires_at` 承载,未做时间旅行验证 | 部分取证 |
| 一个版本只能确认一份 ZIP;中断重传同 `versionId`/摘要;不同摘要 409;同版本第二个写入 409 `UPLOAD_IN_PROGRESS`;未确认半包不进校验 | `check-game-distribution-upload-safety` 24 PASS + `check-game-distribution-upload-resume` 两段式(杀进程后按权威偏移续传) | 已取证 |
| 重复操作不得创建第二个游戏或版本;原生端持久保存操作 ID/目标/key,网页保存恢复标识并以服务端回读为准;相同 ZIP 不同资料修订允许新版本 | 网页侧:`check-game-distribution-web-publish-recovery-e2e` 10 PASS(双击只落一份、关窗后继续同一 versionId);原生端 AGC 侧未取证 | 部分取证 |
| 公开版本切换/作者下架/管理员审核必须带 `expectedPublicationRevision` 并在事务内 CAS;并发 409 `PUBLICATION_CONFLICT`,旧送审不能静默覆盖 | `check-game-distribution-media-e2e` 58 PASS:过期批准、过期下架、并发批准一次 200 一次 409;下架后用过旧 revision 批准仍 409,游戏不复活 | 已取证 |
| 网络中断/响应丢失先查原操作;`validating` 在途任务按版本身份幂等续作;登录失效保留草稿,换账号不能接管 | `check-game-distribution-validation-restart-e2e` 11 PASS(杀 api-server 后重确认可恢复、失败可观测、公开版本不被误删)+ recovery 脚本的登录失效/换账号分支 | 已取证 |
## 6. HTTP 与持久化边界
「已实现」路由逐行核对如下;证据列里的 `media`/`web`/`ops` 分别指 `check:game-distribution-media-e2e`、`check-game-distribution-web-*`、本轮新增的 `check:game-distribution-ops-rollback-e2e`。
| 路由 | 证据 | 结论 |
| --- | --- | --- |
| `GET /games` | media/web 脚本断言只出现公开游戏、待审与下架不可见;ops 脚本在开关关闭时仍 200 | 已取证 |
| `GET /games/{gameId}` | web 脚本深链详情;ops 脚本断言下架后 404 | 已取证 |
| `GET /game-distribution/releases/{gameId}[/{assetPath}]` | media/sandbox/ops 三个脚本断言内容、扩展名白名单、Cookie 403、未知扩展名 404 与响应头 | 已取证 |
| `GET /my/games` | web-publish 脚本回读 `pending_review`/`published`;ops 脚本在开关关闭时仍 200 | 已取证 |
| `POST /games` | owner-isolation + ops(开关关闭 503) | 已取证 |
| `POST /games/{gameId}/versions` | upload-safety/owner-isolation + ops(503) | 已取证 |
| `PUT /versions/{versionId}/package` | limits/upload-safety/upload-resume/validation-restart + ops(503) | 已取证 |
| `GET /versions/{versionId}` | owner-isolation(越权 404、未认证 401);ops 脚本用它断言 `upload_failed` 可回读 | 已取证 |
| `POST /versions/{versionId}/submit` | upload-safety(202 与 409 重放)+ ops(503) | 已取证 |
| `POST /versions/{versionId}/cancel` | owner-isolation(撤回成功)+ ops(503) | 已取证 |
| `POST /games/{gameId}/unpublish` | 阶段 B 核对:下架后目录/详情/网关关闭 + ops(503) | 已取证 |
| `GET /admin/api/game-distribution/reviews` | media(待审列表)+ ops(开关关闭仍 200,积压可查) | 已取证 |
| `POST /admin/api/game-distribution/versions/{versionId}/review` | media 58 PASS(批准需 CAS、拒绝需理由)+ ops(关闭时批准 503、拒绝仍 200) | 已取证 |
| `POST /admin/api/game-distribution/games/{gameId}/suspend` | 阶段 B + ops(关闭时仍可安全下架并恢复) | 已取证 |
| 错误 envelope 覆盖 400/401/403/404/409/413/422/429/5xx,不泄露存储凭据与本地路径 | 阶段 A 核对:成功与失败同一份 `{ok,data,error,meta}`,`meta.requestId`/`meta.operation` 两侧一致(顺带修掉错误 envelope 缺 requestId 的缺陷);limits 脚本实测 413/422;upload-safety 实测 409 | 已取证 |
| 领域规则进 `module-*`、事务进 `spacetime-module`、访问经 `spacetime-client`、HTTP 编排在 `api-server`、对象存储复用 `platform-*`、DTO 同步 `shared-contracts` 与 `packages/shared` | `check:game-distribution-dto-parity` 逐字段/逐变体比对 14 组 DTO 与手拼响应顶层键;`npm run lint` 里的 schema guard(85 表)与生成绑定校验通过 | 已取证 |
## 7. 发行路径、沙箱与网络能力
| 条款 | 证据 | 结论 |
| --- | --- | --- |
| 发行入口是同源 `/games/<gameId>/`,边缘原样映射;隔离靠 `sandbox="allow-scripts"` 不透明来源,不依赖独立域名 | 三份 nginx 模板映射由 ops 脚本断言;`check:nginx-spa-routes` OK;沙箱 E2E 22 PASS 证明 storage/cookie/父文档 DOM 不可得 | 已取证 |
| 主站只接受服务端派生路径,拒绝任意 URL/重定向/`javascript:`/`srcdoc`;边缘清空 Cookie,网关对带 Cookie 请求 403;发行请求不接收平台 Bearer,日志不携带认证数据 | 沙箱脚本断言 Cookie 403;ops 脚本断言三份模板 `proxy_set_header Cookie ""` 且运行期日志不含访问令牌/刷新 Cookie/OSS signed URL | 已取证 |
| iframe 仅 `allow-scripts`;禁止 `allow-same-origin`、顶层导航、弹窗、表单、下载、模态、相机/麦克风/剪贴板/定位;不启用 Service Worker | 沙箱 E2E 22 PASS 逐条断言(含 Worker、popup、top navigation、敏感权限被拒) | 已取证 |
| 网关强制 HTML CSP(默认拒绝、无 `unsafe-eval`、`connect-src` 仅本游戏源、`worker/frame/object/form-action` 为 `none`、`base-uri 'none'`);包内 meta 不能放宽 | `release_asset_response` 只对 HTML 追加 CSP,ops 脚本断言 CSP 关键字且非 HTML 不带 CSP;沙箱脚本实测浏览器按 CSP 拦截外站请求 | 已取证 |
| 允许同包 JSON/二进制素材,禁止外部 API/分析/广告/第三方 SDK;为 opaque sandbox 提供不带凭据 CORS,且只对获准发行文件生效 | 沙箱脚本:同包 ES module 与素材可加载、外站 fetch/WebSocket 被挡;CORS 头只在发行网关响应上出现(`Access-Control-Allow-Origin: *` 仅由 `release_asset_response` 下发) | 已取证 |
| 固定 MIME + `nosniff`,未知扩展名不返回 HTML;静态校验与人工审核不能替代运行时隔离 | 白名单 `release_asset_content_type` + media/ops 脚本断言;沙箱脚本在真实浏览器里验证 | 已取证 |
| 真实浏览器验收要覆盖 npm/Vite 模块、Phaser 素材、音频与触屏,并确认外站请求被阻断 | 模块加载、素材、开始/重试/退出、全屏、移动视口与旋转已取证;**音频实际播放**与**真实触屏输入**未单独取证,AGC 侧真实游戏未取证 | 部分取证 |
## 8. 下架、缓存与运维
| 条款 | 证据 | 结论 |
| --- | --- | --- |
| 下架或封禁后列表/详情/启动 API 立即停止返回;网关按游戏与版本拒绝新请求;原始对象不能绕过网关直取;封禁不能靠作者自行重发绕过,解除需管理员动作并重新审核 | 阶段 B 核对 + 本轮 ops 脚本(安全下架后详情与发行 404、恢复后重新服务);`platform-oss` 对象级 ACL 修复后匿名直取 403 | 已取证 |
| HTML/公开状态/启动 API 用 `no-store`;发行静态资源 TTL ≤60s,禁 `stale-while-revalidate`/`stale-if-error`/发行 Service Worker;下架主动 purge,60s 为最大撤销窗口,不把 purge 当唯一保障;已载入脚本不承诺远程抹除 | 本轮 ops 脚本断言 `Cache-Control: public, max-age=60, must-revalidate` 且与运维文档的 60 秒上限一致,并实测源站立刻拒绝新请求(不依赖 purge);`no-store` 与「无发行 Service Worker」未逐条取证,真实 CDN 未接入 | 部分取证 |
| 上线依赖:独立站点域名与通配 TLS、每游戏 host 路由、私有存储、CSP/CORS/MIME、CDN TTL/purge、审核运营入口、可恢复校验执行器;缺一不可上线 | 私有存储、CSP/CORS/MIME、审核运营入口、可恢复校验执行器都已取证;独立域名/通配证书已由 2026 决策用「同源路径 + 不透明来源沙箱」替代;CDN TTL/purge 与真实域名属部署后复核 | 部分取证 |
| 观察上传失败、校验耗时、审核积压、发行 4xx/5xx、撤销传播时间与容量;日志按游戏/版本/操作 ID 关联,不记录 Token、完整用户文件内容或 signed URL | 本轮 ops 脚本:运行期日志无访问令牌/刷新 Cookie/OSS signed URL,含本轮 gameId 与 `publish_switch_blocked`/`game_suspended` operation;阶段 D 发包核对有上传耗时与内存;审核积压有 `pending_versions`;**发行 4xx/5xx 与容量指标没有独立计数或告警规则** | 部分取证 |
| 原始失败/撤回包建议保留 7 天后清理,公开版本与审核记录保留周期上线前确定;清理必须先查引用,不能删除仍在服务的版本 | `check-game-distribution-validation-restart-e2e` 证明后续待审/失败不会误删在服务的公开版本;**没有任何清理任务实现**(仓库内无 game-distribution retention/cleanup),保留周期也仍在主规范「待评审决策」第 3 条 | 部分取证 |
| 回滚部署时关闭新提交与新版本激活,保留当前可玩版本与状态读取;数据库迁移不以删表回滚;安全事件靠服务端关闭发行权限,不依赖前端隐藏按钮 | 本轮 ops 脚本 40 PASS:关闭开关后 7 条写入(含管理员批准)503、读取与在游戏仍可用、拒绝审核与安全下架仍可用、恢复后换版成功;开关读取失败按关闭处理有实现与用例 | 已取证 |
## 9. 验收标准与证据(主规范表逐行)
| 主规范要求的证据 | 现状 | 结论 |
| --- | --- | --- |
| 真实包闭环:网页 ZIP 与 AGC dist 分别提交同一管道,服务端包 SHA-256/文件清单与发行回读一致;仅 metadata 请求被拒 | 网页 ZIP 侧已取证;AGC dist 侧只有实现与单元测试 | 部分取证 |
| 包与数据边界:缺入口、摘要不符、穿越、符号链接、压缩炸弹、超限、凭据路径及未授权对象均被拒且未公开 | `module-game-distribution` 16 passed + limits 11 PASS + upload-safety 24 PASS + owner-isolation | 已取证 |
| 权限与游客:匿名浏览/游玩成功,匿名写入 401,其他作者读写私有版本被拒,伪造 owner 无效 | owner-isolation 21–23 PASS + web 脚本游客游玩 | 已取证 |
| 幂等与恢复:双击、响应丢失、上传中断、同 key 不同内容、重启恢复、换账号迟到响应分别验证,不生成重复发行版本 | upload-safety / upload-resume / validation-restart / web-publish-recovery 四个脚本分别取证;换账号迟到响应由 recovery 脚本的账号切换分支覆盖 | 已取证 |
| 审核与并发:待审不公开;拒绝有理由;旧版在更新失败/待审期间在线;审核与下架并发 CAS 拒绝过期写入 | media 58 PASS 全覆盖 | 已取证 |
| 真正可玩:桌面与手机真实浏览器覆盖模块加载、素材、音频、触屏、横竖屏、开始/重试/退出与全屏;不以 iframe load 代替 | 模块/素材/开始/重试/退出/全屏/移动视口/旋转已取证;音频与真实触屏未单独取证,AGC 侧未取证 | 部分取证 |
| 隔离与撤销:真实生产构建下同源 iframe 内 Cookie/storage/DOM 不可访问、外网被 CSP 阻断、离开页面后代码不再运行、旧 URL 在缓存窗口后不能取得新资源 | 沙箱 22 PASS(前三项)+ ops 脚本(源站立刻拒绝新请求、TTL 上限 60s);真实生产构建与缓存窗口后的到期行为属部署后复核 | 部分取证 |
| 页面与视觉:warm token 下的目录/详情/发布/加载/空/失败/待审态,桌面与移动视口无遮挡,键盘可达 | a11y 11 PASS + web 脚本的筛选/空态/重试 + 阶段 C 第 9 条核对 | 已取证 |
| 工程门禁:定向前后端测试、两端类型检查、真实 SpacetimeDB/API smoke、schema/绑定检查、编码、文档索引与 diff 检查 | `npm run lint`(encoding / doc-index / npm-workspaces / git-hooks / rustfmt / schema guard 85 表 / generated-bindings / dto-parity / production-ops / preview-deployer / maintenance-page / eslint / typecheck)+ appSurface 192 passed + web/admin-web 构建;真实 smoke 由各 E2E 跑在真实 api-server 上 | 已取证 |
## 未取证清单(上线前必须补齐)
1. **AGC 客户端一次提交**:`dist` 归一化真实包、发布面板隐藏技术摘要、简介/分类免费生成、封面生成复用扣费链路;需要 AGC GUI 与真实 Provider(阶段 C 第 1–3 条)。
2. **真实环境完整链路(阶段 D 第 1 条)**:生产域名/TLS、生产账号、真实 CDN、部署后的 systemd 巡检与告警,跑通「首次上传 → 校验 → 审核 → 公开 → 游客游玩 → 更新待审旧版在线 → 新版切换 → 下架撤销」。
3. **CDN 与缓存到期的真实行为**:本地已证源站立刻拒绝新请求且 TTL 上限 60 秒;真实 CDN 接入后需复核 TTL ≤60s、下架 purge 与「缓存窗口过后旧 URL 取不到新资源」。
4. **条款级小缺口**:加密条目与嵌套 `.zip` 缺专门用例、**设备文件缺拒绝实现**、客户端对前导 `/assets`/绝对 URL 的可操作错误、发布页续发回填封面与截图、`no-store` 与「无发行 Service Worker」的逐条断言、音频实际播放与真实触屏输入。
5. **保留/清理任务**:失败与撤回包的 7 天保留、公开版本与审核记录保留周期、清理前的引用检查都还没有实现;保留周期本身仍是主规范待评审决策。
6. **告警规则**:目前只有巡检 timer + 日志 operation,没有针对上传失败、校验耗时、审核积压、发行 4xx/5xx、撤销传播时间与容量的独立指标或告警规则。
## 与主规范待评审决策的关系
主规范「待评审决策」五条仍未关闭(人工审核策略、导航与根入口、首版范围与额度、同源路径替代独立域名、以及「未评审前不建立 ready 实施计划」)。本矩阵只提供证据,不代替评审;在评审把合同从 proposed 推进到 accepted 之前,本业务不得宣布上线,这也正是阶段 D 第 6 条要求的「有任何核心路径未验证时不标记上线完成」。
@@ -27,7 +27,7 @@
- 阶段 A:真实 ZIP 上传、游戏身份、owner/幂等/CAS、状态机、DTO 与 schema 门禁已完成;真实 SpacetimeDB + 私有 OSS 的创建/上传/确认/重启恢复已有证据。**2026-09-28 更新**:阶段 A 的六条行为与验收已全部勾选(含跨账号越权、并发上传不混写、进程退出后按权威偏移续传、DTO/envelope 一致性),正式验收仍待 owner 评审。
- 阶段 B:人工审核(后台列表、通过需 HTTPS 入口、拒绝需理由)、公开投影、发行网关(按公开版本服务、扩展名白名单、`nosniff`/CORP/CSP、带 Cookie 403)与作者下架已实现;**2026-09-28 更新**:审核治理(待审可见、可批可拒、结论可追溯、作者不能提交审核)、「更新待审/被拒不改变在线旧版」、`publicationRevision` CAS(过期审核、并发激活、过期下架、陈旧审核不复活已下架游戏)与作者/管理员下架后目录/详情/发行读取全部关闭都已在本地真实栈取证(见文末阶段 B 核对);匿名直取发行包这一条已修:`platform-oss` 现在在内部 PUT / 分片追加 / 直传 policy 三处都下发对象级 ACL,严格模式下直传封面与发行包匿名直取都是 403(见 pitfalls 与 decision-log);opaque sandbox 的真实浏览器行为也已用 Chromium 探针包取证(22 项 PASS);每游戏独立来源已有可执行工件 `deploy/nginx/genarrative-release-origin.conf` 与门禁 `npm run check:release-origin-config`,并已在本机用真实 nginx + 真实网关验证按主机映射、Cookie 403 与平台命名空间 404;独立发行域名已由主规范 2026 决策用「平台同源路径 + sandbox 不透明来源」替代,不再是门禁;只剩「撤销传播符合最大缓存窗口」需要生产 CDN/TTL。
- 阶段 C:AGC 客户端「发布到平台」面板与发布链路(dist 归一化根 `index.html`、摘要/字节数声明、幂等键、`localProjectId` 复用)已实现并有请求组装与 Rust 导出测试;网页 `/games/publish` 走同一服务端管道,本地已用真实文件选择验证;AGC GUI 自身的端到端发布仍待客户端环境验收。目录(关键词/分类/设备筛选、滚动与筛选恢复)、详情、游玩页(主动作后加载、超时重试、旋转提示、全屏、移动端门槛)与作者中心(状态、驳回理由、撤回、下架)已实现;本地已在桌面与 `390x844` 移动视口真实游玩。
- 阶段 D:**2026-09-28 更新**:200 MiB 上限档与发包边界、真实栈两段式重启恢复(含失败可观测与公开版本不被误删)、发布开关回滚窗口与发行网关缓存/响应头/日志脱敏都已在本地真实栈取证;真实环境完整链路(D1)与主规范证据矩阵(D6)仍未完成,生产 systemd 巡检与 CDN 只能部署后复核。
- 阶段 D:**2026-09-28 更新**:200 MiB 上限档与发包边界、真实栈两段式重启恢复(含失败可观测与公开版本不被误删)、发布开关回滚窗口与发行网关缓存/响应头/日志脱敏、主规范逐条证据矩阵都已完成;真实环境完整链路(D1)仍未完成,生产 systemd 巡检、独立告警规则与 CDN 只能部署后复核。
细节与命令级证据见[实施计划【游戏分发阶段A领域合同】](【实施计划】游戏分发阶段A领域合同-2026-09-19.md)的「已完成证据」「运行时证据」「尚未完成」。
@@ -123,8 +123,8 @@
- [x] 200 MiB 包(现行上限,见 2026-09-23 决策记录)与获批文件数/展开量边界有可复核耗时、内存和失败证据;校验不会执行上传代码,服务资源有界(2026-09-28 在本地真实栈按新上限复跑,见文末阶段 D 核对)。
- [x] 校验执行器重启可恢复,审核积压与失败可观测,清理不删除仍被公开版本引用的文件(2026-09-28 在本地真实栈按两段式重启复跑,见文末阶段 D 核对)。
- [x] CDN purge 失败时仍在获批缓存 TTL 内拒绝新资源;明确已下载脚本无法远程抹除的边界(2026-09-28 在本地真实栈取证源站拒绝与 60 秒 TTL 上限,见文末阶段 D 核对;真实 CDN 未接入,purge 场景本身属部署后复核)。
- [x] 发布/回滚步骤保留当前公开版本,能关闭新提交和新版本激活;部署路由、缓存、响应头、日志脱敏和告警完成检查(2026-09-28 在本地真实栈取证发布开关、路由模板、响应头与日志脱敏,见文末阶段 D 核对;真实 systemd 巡检运行属部署后复核)。
- [ ] 主规范逐条证据矩阵齐全,未验证项明确列出;有任何核心路径未验证时不标记上线完成。
- [x] 发布/回滚步骤保留当前公开版本,能关闭新提交和新版本激活;部署路由、缓存、响应头、日志脱敏和告警完成检查(2026-09-28 在本地真实栈取证发布开关、路由模板、响应头与日志脱敏,见文末阶段 D 核对;告警目前只有巡检 timer + 日志 operation,独立指标/告警规则未建,真实 systemd 巡检运行属部署后复核)。
- [x] 主规范逐条证据矩阵齐全,未验证项明确列出;有任何核心路径未验证时不标记上线完成(2026-09-28 产出 [`【证据矩阵】AGC游戏分发主规范逐条证据-2026-09-28.md`](【证据矩阵】AGC游戏分发主规范逐条证据-2026-09-28.md),逐条给证据与结论,并把 6 类未取证项单独列出;结论仍是未上线)。
### 证据要求
@@ -259,3 +259,11 @@
- **日志脱敏**:运行期 api-server 日志不出现访问令牌、刷新 Cookie 与 OSS signed URL 凭据(正向对照:同一份日志里能查到本轮 gameId 与 `publish_switch_blocked` / `game_suspended` 两个 operation);三份边缘模板的 `log_format` 不含 `$http_authorization` / `$http_cookie` / `$arg_`。
- **变异验证**:临时把 `ensure_publish_enabled` 改成直接 `Ok(())` 后重跑,同一个脚本 **14 项转红**(7 条写入拦截 + 保在线/下架/恢复等下游断言),还原后重新全绿——说明这些断言确实在测发布开关,而不是恒真。
- **告警**:`npm run check:production-health-patrol-env` → OK(巡检环境的 nginx / pingora-direct 两种模式变量口径);`npm run check:production-health-patrol` 这台 Windows 机器没有 `systemctl` 桩,巡检 harness 报 `FAILED`(预期退出 0、实际 2),属本地限制,真实 systemd 巡检运行留待服务器复核。
## 本轮核对(2026-09-28,阶段 D 主规范逐条证据矩阵)
- 已勾选(阶段 D 第 6 条:主规范逐条证据矩阵齐全,未验证项明确列出,核心路径未验证时不标记上线完成)
- 新增 [`【证据矩阵】AGC游戏分发主规范逐条证据-2026-09-28.md`](【证据矩阵】AGC游戏分发主规范逐条证据-2026-09-28.md):把主规范「AGC 游戏分发与在线游玩合同」按节拆成 9 张表逐条映射证据——交付目标与范围 4 条、入口与产品体验 7 条、真实发行包与资料合同 9 条、身份/状态/审核/更新 7 条、幂等/并发/恢复 5 条、HTTP 边界 14 条路由 + envelope/分层 2 条、发行路径/沙箱/网络 6 条、下架/缓存/运维 5 条,以及主规范「验收标准与证据」表 9 行。
- 每条只给三档结论(已取证 / 部分取证 / 未取证)并写清缺哪半,例如「网页 ZIP 已取证、AGC dist 未取证」「音频播放与真实触屏未单独取证」「加密条目与嵌套 `.zip` 有实现无专门用例、设备文件缺拒绝实现」。
- 矩阵结论是**未上线**:未取证清单 6 类——AGC 客户端一次提交、真实环境完整链路(D1)、真实 CDN 与缓存到期行为、条款级小缺口、失败/撤回包保留清理任务(仓库内没有 game-distribution retention/cleanup 实现)、独立告警规则(目前只有巡检 timer + 日志 operation)。
- 这张矩阵同时是 D1 的输入:D1 要求的生产域名/TLS、生产账号、真实 CDN 与部署后巡检,正是矩阵「未取证清单」第 1–3、6 项。