补齐游戏分发发布回滚窗口与发行网关缓存取证
Project CI / AI game creator shell Rust crates (push) Successful in 1m31s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m59s
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell 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 crates (push) Successful in 1m31s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m59s
Project CI / Frontend tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell 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
- 新增 scripts/check-game-distribution-ops-rollback-e2e.mjs:关闭/开放发布开关、关投稿保在线、管理员批准被拦、拒绝审核与安全下架仍可用、换版保留旧版本、发行网关响应头与 60 秒缓存窗口、日志脱敏、边缘 /games/<gameId>/ 路由映射(40 项 PASS) - package.json 注册 npm run check:game-distribution-ops-rollback-e2e - 里程碑阶段 D 第 4、5 条勾选并补「本轮核对(阶段 D 发布回滚窗口与发行网关缓存)」,含变异验证结果 - pitfalls 记录发布开关用例要连拒绝审核与安全下架一起验,以及本机 check:production-health-patrol 必然 FAILED
This commit is contained in:
@@ -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 上限档与发包边界、真实栈两段式重启恢复(含失败可观测与公开版本不被误删)已在本地真实栈取证;CDN 撤销窗口、发布/回滚演练、真实发行域名与生产账号验收仍依赖生产资源。
|
||||
- 阶段 D:**2026-09-28 更新**:200 MiB 上限档与发包边界、真实栈两段式重启恢复(含失败可观测与公开版本不被误删)、发布开关回滚窗口与发行网关缓存/响应头/日志脱敏都已在本地真实栈取证;真实环境完整链路(D1)与主规范证据矩阵(D6)仍未完成,生产 systemd 巡检与 CDN 只能部署后复核。
|
||||
|
||||
细节与命令级证据见[实施计划【游戏分发阶段A领域合同】](【实施计划】游戏分发阶段A领域合同-2026-09-19.md)的「已完成证据」「运行时证据」「尚未完成」。
|
||||
|
||||
@@ -122,8 +122,8 @@
|
||||
- [ ] 真实环境中完整跑通“首次上传 → 校验 → 审核 → 公开 → 游客游玩 → 更新待审旧版在线 → 新版切换 → 下架撤销”。
|
||||
- [x] 200 MiB 包(现行上限,见 2026-09-23 决策记录)与获批文件数/展开量边界有可复核耗时、内存和失败证据;校验不会执行上传代码,服务资源有界(2026-09-28 在本地真实栈按新上限复跑,见文末阶段 D 核对)。
|
||||
- [x] 校验执行器重启可恢复,审核积压与失败可观测,清理不删除仍被公开版本引用的文件(2026-09-28 在本地真实栈按两段式重启复跑,见文末阶段 D 核对)。
|
||||
- [ ] CDN purge 失败时仍在获批缓存 TTL 内拒绝新资源;明确已下载脚本无法远程抹除的边界。
|
||||
- [ ] 发布/回滚步骤保留当前公开版本,能关闭新提交和新版本激活;部署路由、缓存、响应头、日志脱敏和告警完成检查。
|
||||
- [x] CDN purge 失败时仍在获批缓存 TTL 内拒绝新资源;明确已下载脚本无法远程抹除的边界(2026-09-28 在本地真实栈取证源站拒绝与 60 秒 TTL 上限,见文末阶段 D 核对;真实 CDN 未接入,purge 场景本身属部署后复核)。
|
||||
- [x] 发布/回滚步骤保留当前公开版本,能关闭新提交和新版本激活;部署路由、缓存、响应头、日志脱敏和告警完成检查(2026-09-28 在本地真实栈取证发布开关、路由模板、响应头与日志脱敏,见文末阶段 D 核对;真实 systemd 巡检运行属部署后复核)。
|
||||
- [ ] 主规范逐条证据矩阵齐全,未验证项明确列出;有任何核心路径未验证时不标记上线完成。
|
||||
|
||||
### 证据要求
|
||||
@@ -245,3 +245,17 @@
|
||||
- **失败可观测**:再传一个结构损坏的包返回 **422 `PACKAGE_VALIDATION_FAILED` + `reason=InvalidArchive`**(作者当场可诊断);同一版本回读落到 `upload_failed`;后台游戏列表出现 `statuses=upload_failed,pending_review,published`。失败原因同时落库:`spacetime sql --server http://127.0.0.1:3101 xushi-p4wfr "SELECT version_id, status, last_error_code FROM game_distribution_version WHERE status = 'upload_failed'"` 返回 `(some = "PACKAGE_VALIDATION_FAILED")`。
|
||||
- **不误删公开版本**:v2 待审、v3 失败之后,`GET /api/game-distribution/releases/<gameId>/index.html` 仍 200 且正文仍含 `V1-OK`,公开版本内容没有被后续待审/失败版本清理掉。
|
||||
- **明确缺口(未当成已验收)**:版本回读 DTO 目前只暴露 `status`,不暴露 `last_error_code` / `last_error_message`;作者或运维事后回看只能看到 `upload_failed`,具体原因只有失败当次的 422 envelope 与库内字段。把它做进回读 DTO 需要改 `GameDistributionVersionSnapshot` / `GameDistributionAdminVersionSnapshot` 的 ABI 并重发模块与绑定,本轮未做,列为后续事项。
|
||||
|
||||
## 本轮核对(2026-09-28,阶段 D 发布回滚窗口与发行网关缓存)
|
||||
|
||||
- 已勾选(阶段 D 第 4 条:撤销后新资源仍被拒绝且边界明确)——本轮按本地可证范围补齐
|
||||
- 新增 `scripts/check-game-distribution-ops-rollback-e2e.mjs`(`npm run check:game-distribution-ops-rollback-e2e`,本地真实栈 + 真实 OSS,**40 项 PASS**)。管理员安全下架后,`GET /api/game-distribution/games/<gameId>` 与发行入口 `/api/game-distribution/releases/<gameId>/index.html` 对**新的**请求立刻返回 `404`,恢复后重新服务同一公开版本;因此“CDN purge 失败”不会让源站继续放行新资源,旧内容只可能留在声明过的边缘缓存窗口里。
|
||||
- 缓存窗口与文档一致:发行网关对所有发行资源下发 `Cache-Control: public, max-age=60, must-revalidate`,脚本同时断言运维文档里的 60 秒上限与“已下载脚本无法远程抹除”边界(`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`)。平台当前没有接 CDN,purge 场景本身留待部署后复核。
|
||||
- 响应头:HTML 下发收紧 CSP(`default-src 'none'` + `frame-src/object-src/base-uri 'none'`),CSS/JS 等非 HTML 不下发 CSP;两者都带 `X-Content-Type-Options: nosniff`、`Referrer-Policy: no-referrer`、`Cross-Origin-Resource-Policy: cross-origin` 与无凭据 `Access-Control-Allow-Origin: *`。
|
||||
- 已勾选(阶段 D 第 5 条:保留当前公开版本、关闭新提交与新版本激活,部署路由/缓存/响应头/日志脱敏/告警检查)——本轮补齐本地可执行部分
|
||||
- **关投稿、保在线**:`game-distribution:publish` 灰度关闭后,创建游戏、创建版本、上传包、送审、撤回、作者下架与管理员**批准**新版本全部 `503 GAME_DISTRIBUTION_PUBLISH_DISABLED`;同一窗口内目录、详情、`/my-games`、审核队列读取与发行入口继续可用,`publicationRevision` 与当前公开版本不变。拒绝审核与管理员安全下架 / 恢复始终可用(已在关闭窗口内实测,且恢复后仍服务原公开版本)。
|
||||
- **换版保留旧版本**:开关恢复后管理员激活新版本,发行入口改为新内容,旧公开版本在作者版本列表里保留为 `revoked` 记录(可追溯、对象未删)。
|
||||
- **部署路由**:脚本断言三份边缘模板(`deploy/nginx/genarrative.conf`、`deploy/nginx/genarrative-dev-http.conf`、`deploy/container/nginx.conf`)都把 `^/games/(?<game_id>game_[0-9a-f]{32})(?<game_path>/.*)?$` 映射到发行网关并 `proxy_set_header Cookie ""`;`npm run check:nginx-spa-routes` → `OK (12 SPA routes, 3 Nginx templates)`。
|
||||
- **日志脱敏**:运行期 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 巡检运行留待服务器复核。
|
||||
|
||||
@@ -6086,3 +6086,15 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- **根因**:`.env.local` 里配置的是 `GENARRATIVE_ADMIN_USERNAME=admin` / `GENARRATIVE_ADMIN_PASSWORD=123456`,超管登录直接比对 `state.admin_runtime()`(即进程启动时的环境变量),不走库里的后台账号。上一次 api-server 若是被显式带上 `codex-admin / CodexLocal-4198!` 起起来的,重启回到 `.env.local` 后这组账号就会 401。
|
||||
- **做法**:两段式 E2E 的两次启动必须用同一组管理员凭据(本轮统一改成 `.env.local` 的 `admin / 123456`,实测 `admin` → 200、`codex-admin` → 401);遇到 401 先用 `Invoke-WebRequest http://127.0.0.1:8082/admin/api/login` 探一次凭据,再判断是否是产品回归。
|
||||
- **附注**:同一个 `E2E_STATE_FILE` 只能 resume 一次。resume 段会额外建一个坏包版本,第二次跑「后台只看到两个版本」这类断言会因为多出 `upload_failed` 版本而失败;需要重跑就重新执行 prepare。同理,prepare 依赖 `game-distribution:publish` 灰度对该作者开启,脚本自己会先 PUT 特征开关。
|
||||
|
||||
## 2026-09-28 发布开关回滚窗口要连管理员拒绝/安全下架一起验,否则会把“关投稿”做成“关后台”
|
||||
|
||||
- **现象**:只按“关闭 `game-distribution:publish` 后写入口 503”写用例,会漏掉两个方向的问题——① 关闭窗口里管理员**批准**新版本也必须被拦住,否则回滚窗口形同虚设;② 拒绝审核与管理员安全下架 / 恢复在关闭窗口里必须仍然可用,否则运营在最需要救火时会发现自己也进不去。
|
||||
- **做法**:`scripts/check-game-distribution-ops-rollback-e2e.mjs` 把这两类方向写进同一条用例:7 条写入(含管理员批准)断言 `503 GAME_DISTRIBUTION_PUBLISH_DISABLED`,同时断言目录/详情/`/my-games`/审核队列/发行入口 200 且 `publicationRevision` 不变,并在关闭窗口内实测拒绝审核与管理员下架 → 恢复。改 `ensure_publish_enabled` 的调用位置(例如给下架也加上开关检查)会立刻打红。
|
||||
- **变异验证**:把 `ensure_publish_enabled` 临时改成直接 `Ok(())`,脚本 14 项转红;这类“开关语义”用例必须做一次变异验证,否则无法区分“开关生效”与“断言恒真”。
|
||||
|
||||
## 2026-09-28 `check:production-health-patrol` 在 Windows 本机必然 FAILED,别当成回归
|
||||
|
||||
- **现象**:本机跑 `npm run check:production-health-patrol` 输出 `[check:production-health-patrol] FAILED`,理由是「nginx gateway mode 巡检应成功。预期退出码 0,实际 2」,巡检 JSON 里 6 条 `service:*` 全是 `服务状态异常: spawn systemctl ENOENT`。同一轮的 `api:/healthz`、`bgfilter:/readyz`、`spacetimedb:/v1/ping`、`public:/` 都是 200。
|
||||
- **根因**:这个 harness 用桩 `systemctl` 驱动巡检脚本,Windows 上 `spawn systemctl` 直接 `ENOENT`,服务态检查不可能通过;它验证的是 Linux + systemd 的生产形态。
|
||||
- **做法**:本机只跑 `npm run check:production-health-patrol-env`(本轮 OK)确认巡检变量口径;`check:production-health-patrol` 留给服务器/CI 复核,别据此判定巡检脚本本身坏了。
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
"check:game-distribution-web-a11y-e2e": "node scripts/check-game-distribution-web-a11y-e2e.mjs",
|
||||
"check:game-distribution-package-limits-e2e": "node scripts/check-game-distribution-package-limits-e2e.mjs",
|
||||
"check:game-distribution-validation-restart-e2e": "node scripts/check-game-distribution-validation-restart-e2e.mjs",
|
||||
"check:game-distribution-ops-rollback-e2e": "node scripts/check-game-distribution-ops-rollback-e2e.mjs",
|
||||
"check:production-ops": "node scripts/check-production-ops-guardrails.mjs",
|
||||
"check:preview-deployer": "node scripts/check-preview-deployer.mjs",
|
||||
"check:maintenance-page": "node scripts/check-maintenance-page.mjs",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user