Merge remote-tracking branch 'origin/master' into refactor/extract-dep-from-ref-inputer
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 21s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 20s
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 21s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 20s
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust crates (pull_request) Has been cancelled
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / AI game creator shell Rust smoke (pull_request) Has been cancelled
# Conflicts: # docs/project-memory/shared-memory/decision-log.md
This commit is contained in:
@@ -38,6 +38,6 @@ Parent Milestone: `【里程碑】AGC项目定时快照上传-2026-09-17.md`
|
||||
## 风险与回滚
|
||||
|
||||
- 上传体积与带宽:首轮全量可能很大,先设单文件与单次同步总量上限并把超限项记入跳过清单;不静默截断。
|
||||
- 数据出境边界:只上传项目目录内普通文件,排除 `.agent/runtime`、`.agent/logs`、`.git`、构建产物与临时文件;凭据类文件不在白名单内。
|
||||
- 数据出境边界(2026-09-22 修订):只上传项目目录内普通文件。`.agent` 承载项目身份与 Agent 状态,整目录上传(含 conversations、logs、runtime、checkpoint、`agent.db`);`.git`、构建产物、临时文件与凭据类文件仍不在白名单内。该修订只作用于快照同步,项目索引与 checkpoint 继续排除整个 `.agent`。
|
||||
- 服务端未配置 bucket 时客户端必须失败关闭,不能把本地索引推进成"已同步",否则后续同步会漏传。
|
||||
- 回滚:客户端可停用触发接线(保留模块与测试)即可回到无上传行为;服务端路由与配置项可单独移除,不影响既有 OSS 前缀与错误报告链路。
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# 【实施计划】游戏分发阶段A领域合同
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| Milestone | `docs/project-memory/plans/【里程碑】游戏分发目录详情与在线游玩-2026-09-18.md` 阶段 A |
|
||||
| Status | in_progress |
|
||||
| Owner | Codex |
|
||||
|
||||
## 修改边界
|
||||
|
||||
- 允许修改:`module-game-distribution` 新领域 crate、workspace 声明、Rust shared-contracts DTO、领域单测及文档证据。
|
||||
- 明确不修改:发行域名与 CDN、旧 public work/runtime、AGC native 上传命令、网页发布表单。
|
||||
|
||||
## 行为范围
|
||||
|
||||
- 定义游戏身份、版本状态、包摘要、owner 权限、幂等 key 冲突和 publication revision CAS。
|
||||
- 领域服务先以纯内存仓储验证状态合同,再接入已冻结的 SpacetimeDB 游戏/版本/幂等收据表;不把内存仓储作为生产事实源。
|
||||
- 阶段 A 只提供身份、版本、真实包确认和状态回读基础,不提供“发布成功”或公开可玩状态,不绕过人工审核。
|
||||
|
||||
## 验证命令
|
||||
|
||||
1. `cargo test -p module-game-distribution --manifest-path server-rs/Cargo.toml`
|
||||
2. `cargo fmt --all --manifest-path server-rs/Cargo.toml -- --check`
|
||||
3. `npm run check:encoding`
|
||||
4. `git diff --check`
|
||||
|
||||
## 风险与回滚点
|
||||
|
||||
- API 接入前端前必须完成 SpacetimeDB facade 和 schema 门禁;任何失败都保留旧版本数据,不覆盖已有业务表。
|
||||
- 内存仓储只用于行为合同测试,不得被前端当作正式数据源。
|
||||
|
||||
## 已完成证据(当前切片)
|
||||
|
||||
- `module-game-distribution` 已落地游戏/版本状态机、owner 校验、幂等摘要冲突、上传确认、验证失败重试、审核 CAS、撤回和管理员暂停的纯领域服务与内存测试仓储。
|
||||
- 同一 crate 已加入不执行上传代码的 ZIP 包校验:根 `index.html`、路径穿越/大小写冲突、符号链接、加密文件、敏感文件、嵌套压缩包、单文件/总展开量/压缩比上限和逐文件 SHA-256 清单;并新增按白名单内容类型取单个发行资源的读取层。
|
||||
- Rust/TypeScript 跨端 DTO、SpacetimeDB 三张持久表、migration 白名单、生成 bindings、typed facade 和游戏分发 HTTP 路由已接入,覆盖创建、上传、送审、审核、下架、公开目录与详情。
|
||||
- `api-server` 新增发行网关 `GET /api/game-distribution/releases/{gameId}/{assetPath}`:只服务当前已公开版本,未知扩展名 404,附带 nosniff / CORP / HTML CSP,拒绝带 Cookie 请求,并按对象键做有界包缓存。
|
||||
- 重复发布复用游戏身份:游戏表末尾新增可空 `local_project_id`,`create_game_distribution_game` 在 owner + local_project_id 命中时复用既有 `gameId` 并只新增版本;`localProjectId` 经 shared-contracts(Rust/TS)透传,AGC 发布链路写入并在缺失时于发请求前失败关闭,api-server 对短标识做路径分隔符与控制字符校验。
|
||||
- 后台新增 `#game-distribution` 游戏审核页:待审列表、通过(要求 HTTPS 发行入口)、拒绝(要求理由)、幂等键与列表刷新;对应 `editor-showcase` Tab 权限映射、API client 与页面测试已加入。
|
||||
- 路由级测试证明发行网关、公开目录与登录发布路由确实挂载:带 Cookie 的发行请求 403、未知扩展名 404 且不触达对象存储、无 Bearer 的发布请求 401、目录在无数据库时返回 502 而不是 404。
|
||||
- 自动化证据:`cargo test -p module-game-distribution`(11 passed)、`cargo test -p api-server` 游戏分发模块(9 passed)与全量 `cargo test -p api-server`(1069 passed / 6 ignored)、`cargo test -p shared-contracts`、admin-web Vitest(149 passed,含新增游戏审核页与 client 用例)、前端游戏定向 Vitest(15 passed)、AGC `typecheck` 与 `local_project_export` Rust 测试(7 passed)、Vite build。
|
||||
- 版本回读、撤回与管理员安全下架闭环:新增 SpacetimeDB `get_game_distribution_version_and_return` / `cancel_game_distribution_version_and_return`(幂等收据 + 公开修订号 CAS)、`spacetime-client` façade、`GET /api/game-distribution/versions/{versionId}`(作者,未知与非 owner 一律 404)、`GET /admin/api/game-distribution/versions/{versionId}`(管理员)与 `POST /api/game-distribution/versions/{versionId}/cancel`(作者,`Idempotency-Key` + `expectedPublicationRevision`);版本私有投影新增服务端派生的 `recoveryAction`(upload/submit/wait/none/reupload/fix_package/fix_metadata)。后台游戏审核页补齐「安全下架」入口(原因输入 + 二次确认弹窗 + 幂等键)。
|
||||
- 幂等响应语义修正:写操作 procedure 现在把「命中既有幂等收据」如实回传为 `replayed: true`(此前所有写操作都固定返回 false),覆盖创建游戏/版本、确认包、上传失败、送审、审核、撤回、下架与暂停。
|
||||
- 网页端恢复与撤回:`/games/publish` 在创建版本后写入带 owner 的发布草稿,窗口关闭或上传中断后同一账号可见「继续上传/继续送审」并复用原 `versionId`,换账号只忽略不读取;`/games/mine` 对可撤回状态提供二次确认的「撤回审核」,成功后刷新状态。
|
||||
- AGC 发布真实链路回归测试:新增 `apps/ai-game-creator-shell/tests/gameDistributionPublishLive.test.ts`(默认跳过,设置 `GENARRATIVE_AGC_PUBLISH_E2E_BASE_URL` 指向运行中的 api-server 才执行)。它不 mock 请求层,而是走真实的 `clientApi`/`clientHttp` 调用 `publishLocalProjectGame`,覆盖本地导出包读取、创建游戏、同 `localProjectId` 复用游戏身份、真实 ZIP 上传、送审、版本回读与 `my-games` 聚合,把「AGC 一键发布」从请求组装单测提升到真实后端链路证据。
|
||||
- AGC 客户端回归修复:导出试玩包后自动打开的发布面板是焦点陷阱模态,会让既有 `appSurface` 导出快捷操作用例静默失效;用例改为先断言面板出现、关闭后再继续,`appSurface.test.ts` 552 项(535 passed / 17 skipped)恢复全绿,并顺手补上该面板此前缺失的行为断言。同一轮修掉 `gameDistributionPublish.test.ts` 的模块 mock 缺少 `getClientServerBaseUrl` 导出的既有失败。
|
||||
- 发行来源(每游戏独立 origin)部署工件:新增 `deploy/nginx/genarrative-release-origin.conf` 与 `scripts/check-release-origin-config.mjs`(`npm run check:release-origin-config`)。模板按命名捕获 `game_id` 把 `https://<gameId>.games.<域>/` 映射到该游戏的 `index.html`、其余路径映射到发行网关前缀,边缘拒绝并清空 Cookie,不代理平台 API/后台/SPA;门禁逐条校验模板约束、交叉检查发行网关仍在设置 `nosniff`/CORP/CORS/CSP 与 Cookie 403,并在本机渲染临时配置跑 `nginx -t`。
|
||||
- AGC 发布面板组件测试:新增 `apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx`,覆盖打开时预填资料、发行包摘要、提交入参(项目路径/发行包/资料/幂等键)、连续点击只提交一次、服务端失败保留面板与无 Tauri 宿主/缺包时失败关闭。
|
||||
- 视觉与交互巡检修复:真实栈 + headless Chromium 逐页巡检 `/games`、`/games/detail`、`/games/play`、`/games/mine`、`/games/publish`(桌面 1440×900 与移动 390×844)。修掉两处:① 详情页「游玩方式」只读公开投影里恒为空的 `version.controls`,作者声明的 `inputModes` 被忽略,现在优先展示「键盘 · 鼠标 · 触屏」再退回自由文本;② 桌面端 hero 过高导致首屏看不到任何游戏卡片,改为仅桌面压缩 hero 高度,首排卡片进入 900px 视口。
|
||||
- 容量与限额边界(阶段 D 证据):新增真实栈脚本覆盖 5 类包——① 99.0 MiB(两个 50/49 MiB 文件 + `index.html`,均在单文件/展开量/压缩比限制内)上传 200、耗时 11.8s、api-server RSS 81.6→378.0 MB(峰值 +296 MB)后回落 83.2 MB,随后送审 202 进入 `pending_review`;② 声明的 `packageBytes` 为 101 MiB 时在创建版本即被拒 413 `PAYLOAD_TOO_LARGE`("发行包大小超出限制",不落版本);③ 声明合法但请求体 101 MiB 时被请求体限制拒绝 413(0.03s、内存零增长、版本保持 `awaiting_upload`/`upload`,可原版本重传);④ 60 MiB 全零文件压到 61 KB(压缩比约 1000)拒绝 422 `PACKAGE_VALIDATION_FAILED`(0.01s,`upload_failed`/`reupload`);⑤ 单文件 65 MiB 与 10,001 个文件同样 422(0.05s / 0.02s)。失败包都不出现在公开目录。
|
||||
- 发行包 PUT 受控重试:`platform-oss` 新增 `put_internal_object_with_retry`(复用既有 `oss_error_is_retryable` 分类:传输/超时/connect、408、429、5xx 与 400+RequestTimeout 可重试,确定性 4xx 不重试;body 只转一次 `Bytes` 供各 attempt 复用),发行包上传接入 3 次尝试 + 250/500ms 退避。触发原因:本机实测 99 MiB 单次 PUT 三次里出现过一次 `UPSTREAM_ERROR`(`请求 OSS 失败:error sending request`),客户端需要白传整包。
|
||||
- 网页端「为既有游戏发布新版本」:发布页新增 `updateGameId` 更新模式(按作者中心的最近版本回读冻结资料与公开修订号预填,提交时跳过创建游戏、直接在既有 `gameId` 下创建不可变新版本),作者中心新增「发布新版本」入口,壳层支持 `/games/publish?game=<gameId>`。浏览器真实链路:从 `/games/mine` 点「发布新版本」→ `/games/publish?game=…` 预填并显示「为《…》发布新版本 v2」→ 选包提交 → 该 `gameId` 下出现 `versions [(2, pending_review), (1, published)]`,公开入口仍指向并返回 v1 内容。此前网页端只能新建游戏,更新无法沿用 `gameId`,与里程碑「更新沿用相同 gameId」不符。
|
||||
- 全生命周期回归(真实栈,11 步):待审公开不可见(目录不返回 + 详情 404)→ 管理员通过 v1 后目录/详情/网关可玩 V1 → v2 待审期间网关仍是 V1 → v2 被拒后仍是 V1 → 重复批准 `rejected` 版本返回 409(`rejected` 是终态,必须新建版本,与主规范一致)→ 新建 v3 通过后公开入口切换为 V3 → 作者回读被拒版本为 `rejected`/`fix_metadata` → 作者下架后目录/详情/网关全部关闭。同一轮修掉 5 条既有的壳层导航用例失败(游戏分发新增「游戏」页签与移动底栏后,测试仍断言旧导航集合)。
|
||||
- 边界、越权与隔离运行时证据(真实栈):① 跨作者越权——作者 B 对作者 A 的版本上传 403、送审 403、撤回 404、回读 404,且 A 的版本状态未被改变;② 过期 CAS——管理员用错误 `publicationRevision` 批准返回 409,正确修订号才通过;③ 包边界——符号链接条目、`.env` 凭据文件、嵌套 ZIP 分别返回 422 `PACKAGE_VALIDATION_FAILED`,版本落到 `upload_failed`/`reupload`;④ 私有对象直取——按对象键直接 GET 私有 OSS 地址返回 **403**,发行文件只能经网关读取。
|
||||
- 浏览器沙箱隔离实测:发布一个自检探测包(内联脚本主动探测并 `postMessage` 回传结果),在 `/games/play` 里点「开始游戏」后收到 `{ origin: "http://127.0.0.1:10001", cookie: "throw:SecurityError", storage: "throw:SecurityError", parentDom: "throw:SecurityError", externalFetch: "throw:TypeError" }`——主站 Cookie、localStorage、父页面 DOM 全部不可访问,外站 fetch 被 CSP `connect-src 'self'` 阻断。键盘可达抽查:桌面端 Tab 顺序覆盖导航 → 搜索 → 下载客户端 → 账户,游戏分发自己的控件(发布游戏/我的游戏/分类页签/游戏卡片)聚焦时都有可见的浏览器默认焦点环。同一探测包还验证了音频链路:沙箱内 `fetch('assets/beep.wav')` 返回 `status:200 type:audio/wav`,WebAudio `decodeAudioData` 成功解出 `0.40s / 48000Hz`(`AudioContext.state = suspended`,与浏览器“首次播放需帧内用户手势”的策略一致,玩家进入游戏后的第一次交互即提供该手势)。游戏内真实触屏输入也已验证(见下一条)。
|
||||
- 上传中断与进程重启恢复(真实栈,两次独立实验):60 MiB 包 PUT 进行到 ~2.5s 时 `SIGKILL` api-server,客户端拿到 `RemoteDisconnected`(等价于响应丢失);重启后该版本仍是 `awaiting_upload`/`upload`,没有半确认状态。① 用不同字节重传返回 422 `PACKAGE_VALIDATION_FAILED`(`InvalidArchive`)并转 `upload_failed`/`reupload`;② 用固定时间戳构造的确定性 60 MiB 包重传**相同字节**返回 200(7.64s),版本转 `uploaded`/`submit`,随后送审 202 进入 `pending_review`。两次实验中游戏都未出现在公开目录。
|
||||
- 发布开关(回滚/事故能力):新增 `game-distribution:publish` 灰度 gate(复用现役灰度配置机制与后台入口)。默认开放;`enabled=true` 时按白名单/用户标签/灰度百分比放行,`rolloutPercent=0` 且无白名单即全部关闭。关闭时作者写入与管理员批准返回 `503 GAME_DISTRIBUTION_PUBLISH_DISABLED`,而目录、详情、版本回读、发行网关、`/my-games`、审核队列读取、拒绝审核与安全下架继续可用;开关读取失败按关闭处理。路由级测试覆盖「默认放行 / 全关拦截 / 读取不受影响 / 白名单放行」,真实栈验证同口径:关闭后作者写入 503、目录/详情/网关/my-games 全 200、管理员批准 503 而拒绝 200、白名单用户可发布、重新开放后恢复。
|
||||
- 游戏内真实触屏输入(本机 Chromium + CDP `Input.dispatchTouchEvent`,390×844 视口):在 `/games/play` 点「开始游戏」后,向 iframe 可视区域坐标派发 `touchStart`/`touchEnd`;`sandbox="allow-scripts"` 且 `ready` 的沙箱内探测包回传 `{touchstart: 1, touchend: 1, pointerdown: 1, pointerTypes: ["touch"], click: 1, target: "pad", touches: 1}`,证明真实触摸事件经平台进入游戏容器并命中目标元素。注意:触摸模拟必须在 iframe 文档创建之前启用,否则文档不会注册 touch 事件支持(首次实测只收到 pointerdown,重载后 touchstart/touchend 才出现)。
|
||||
- 可观测性(阶段 D「上传失败、校验耗时、审核积压、撤销传播」):`api-server` 的游戏分发模块补齐结构化事件,均带 `request_id`(可 join 访问日志)与 `operation`,不记录 Token、signed URL、完整文件内容或本地路径。事件与字段:`package_confirmed`(game_id/version_id/package_bytes/file_count/sha256_prefix/oss_put_skipped/elapsed_ms)、`package_rejected`(code/reason/uploaded_bytes/elapsed_ms)、`version_submitted`、`version_cancelled`、`review_backlog_listed`(pending_versions/limit)、`review_decided`(decision/admin_user_id/publication_revision)、`game_unpublished`(visibility/publication_revision/active_version_id)、`game_suspended`(admin/reason/publication_revision)、`publish_switch_blocked`/`publish_switch_unavailable`、发行网关 `release_rejected`(debug 级:reason=cookie_present/unsupported_extension/not_public/no_active_version/version_not_published)。真实栈复跑一次完整链路后,8 类事件各出现 1-2 次,字段与耗时均可用(样例:`package_rejected … reason=InvalidArchive uploaded_bytes=9 elapsed_ms=6`、`game_unpublished … visibility=unpublished publication_revision=2 active_version_id=""`)。
|
||||
- 工程门禁:`npm run check:spacetime-schema`(147 tables)、`npm run check:server-rs-ddd`、`cargo fmt --all -- --check`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`。
|
||||
|
||||
## 运行时证据(2026-09-20,真实本地栈)
|
||||
|
||||
`npm run dev` 在无冲突端口段启动(spacetime `127.0.0.1:10004`、api-server `127.0.0.1:10001`、真实 OSS bucket `xushi-dev`),用 HTTP 全链路脚本验证:
|
||||
|
||||
- 管理员登录 + 用户口令注册/登录均走真实路由。
|
||||
- 创建游戏后,用相同 `localProjectId` 再次创建返回**同一个 `gameId`**(去重在真实数据库上生效)。
|
||||
- 真实 ZIP(根 `index.html` + `assets/app.js`)上传私有 OSS,服务端重算 SHA-256 / 字节数 / 文件数并确认 `uploaded`。
|
||||
- 送审返回 `202` 与 `pending_review`;此时公开目录、公开详情都不返回该游戏。
|
||||
- 管理员用 HTTPS `entryUrl` 审核通过后,目录与详情出现该游戏并带当前版本 `entryUrl`。
|
||||
- 发行网关返回 `text/html` 与 `text/javascript`;带 `Cookie` 的发行请求 403,未知扩展名 404。
|
||||
- 作者下架后公开详情回到 404。
|
||||
- 更新链路:v2 送审期间与 v2 被拒后,公开入口始终停留在 v1 且 v1 内容仍可读取;v3 通过审核后才替换公开版本。
|
||||
- 真实浏览器(headless Chromium)加载发行网关的 `index.html`:文档渲染、同源 `assets/app.js` 在现役 CSP 下执行成功(`booted=1`、`window.__gd=1`),控制台只剩 favicon 404。
|
||||
- 浏览器打开 `http://127.0.0.1:10000/games` 能看到已发布游戏卡片,`/games/detail?id=<id>` 渲染标题与「立即玩」。
|
||||
- 平台页面内嵌游玩:浏览器打开 `/games/play?id=<id>`,点「开始游戏」后 iframe 以 `sandbox="allow-scripts"` 挂载发行网关地址,`game-player-frame--ready` 置位,控制台无 CSP/CORP 拦截,api-server 访问日志显示该版本 `assets/app.js` 返回 200。
|
||||
- 非生产环境允许 http 回环 `entryUrl`(与前端 `normalizeGameEntryUrl` 同口径),生产仍只接受 HTTPS;这样本地无需 TLS 即可验证内嵌游玩。
|
||||
- 作者中心 `/games/mine`:SpacetimeDB 新增 `list_owner_game_distribution_games_and_return`(owner 只从认证主体派生,每游戏最多回 10 个最近版本),`api-server` 暴露 `GET /api/game-distribution/my-games`;浏览器里用真实作者 token 打开该页,能看到「已公开」状态与「下架」按钮,点击后状态变为「未公开」、按钮消失,公开目录同步不再返回该游戏。
|
||||
- 网页端发布入口 `/games/publish`:浏览器里用真实文件选择框上传 ZIP(根 `index.html` + `assets/app.js`),api-server 访问日志记录 `POST /api/game-distribution/games` 200 → `POST /api/game-distribution/games/{gameId}/versions` 200 → `PUT /api/game-distribution/versions/{versionId}/package` 200 → `POST /api/game-distribution/versions/{versionId}/submit` 202;随后同一账号在 `/games/mine` 看到该游戏为「未公开 / 版本 v1 · 审核中」,公开目录不返回它。
|
||||
|
||||
- 版本回读/撤回真实链路(`npm run dev`,api-server `127.0.0.1:10001`、SpacetimeDB `127.0.0.1:10004`):作者送审后 `GET /versions/{id}` 返回 `pending_review` + `recoveryAction=wait`;其他账号与未知版本都是 404;过期修订号撤回 409;正常撤回 200 且状态变 `cancelled`、`recoveryAction=none`;同 key 同请求重放返回 `replayed=true`,同 key 不同摘要 409;撤回后公开目录不返回该游戏。
|
||||
- 主链路回归(同一真实栈):正式发布 `replayed=false`、重复送审 `replayed=true`;待审期间目录不可见;管理员读版本 200、审核通过后状态 `published` 且目录与详情返回 `currentVersion.entryUrl`;发行网关 `index.html` 200、带 Cookie 403、未知扩展名 404。
|
||||
- AGC 发布真实链路(同一真实栈):`GENARRATIVE_AGC_PUBLISH_E2E_BASE_URL=http://127.0.0.1:10001 npx vitest run apps/ai-game-creator-shell/tests/gameDistributionPublishLive.test.ts` 通过——AGC 发布函数依次完成创建游戏、创建版本、上传 ZIP、送审,返回 `pending_review`;版本回读为 `pending_review`/`wait`;不带旧幂等键的第二次发布复用同一 `gameId` 并生成 v2;`/my/games` 的 `latestVersion` 指向 v2。
|
||||
- 非法包真实链路(同一真实栈):缺根 `index.html` 且含 `../` 条目的 ZIP 上传返回 422(包校验错误),版本进入 `upload_failed` 且 `recoveryAction=reupload`,此时送审被拒(409)、公开目录不返回该游戏,作者仍可撤回该版本重新出包。
|
||||
- 发行来源真实边缘验证(同一真实栈 + 本机 nginx 1.28.3,模板渲染到 `~/data/tmp` 后监听高位端口):`Host: <gameId>.games.example.com` 时根路径 200 `text/html`(游戏 `index.html`)、`/assets/app.js` 200 `text/javascript`;带 `Cookie` 403;`/api/auth/me` 404(平台命名空间未暴露);未知 gameId 404;http 301 到 https;响应头经边缘透传后仍是 `nosniff` + CORP `cross-origin` + 无凭据 CORS + HTML CSP + `Cache-Control: public, max-age=60, must-revalidate`。
|
||||
- 移动视口真实游玩:headless Chromium 以 `390x844` 打开已发布游戏 `/games/play?id=<id>`,页面显示「横屏设计,旋转设备」提示与移动端底部导航,点击「开始游戏」后 iframe 以 `sandbox="allow-scripts"` + `allow="fullscreen"` 挂载发行网关地址并 `ready`,`index.html` 与 `assets/app.js` 均返回 200,控制台无 CSP/CORP 报错;截图存于本轮验证记录(不入库)。
|
||||
本轮同时修掉三个真实缺陷:Vite dev 代理缺少 `/api/game-distribution` 前缀(本地全部 404);详情页在「已发布但没有 controls」时误显示「暂未发布可玩版本」;发行网关的 `Cross-Origin-Resource-Policy: same-origin` 会让 opaque origin 沙箱内的游戏加载不了自己的脚本(改为 `cross-origin` + 无凭据 CORS,详见 `pitfalls.md`)。
|
||||
|
||||
- 资料冻结与展示闭环(封面 + 截图):游戏表末尾新增可空 `cover_object_key` / `screenshots_json`,版本表末尾新增可空 `metadata_json`;创建版本时 api-server 校验「必需封面、≤6 张截图、素材属于当前作者且 `content_type` 为 `image/`」,并从素材记录派生对象键生成冻结快照,审核通过时整体生效到游戏行。
|
||||
- 公开素材读授权:只有 `published` 且存在有效 `active_version_id` 的游戏,其封面/截图素材才在 `/api/assets/read-url` 获得匿名读授权;其余素材仍按 owner 校验。
|
||||
- 作者续发复用:版本回读(作者本人)与审核回读(管理员)的版本投影新增 `frozenMetadata`,带回 `coverAssetId` / `screenshots[].assetId`;`/games/publish` 更新模式据此预填封面与截图,不要求作者为沿用封面重新上传;快照缺素材 ID 的旧版本明确要求重新选择封面。公开投影仍只暴露对象键。
|
||||
- 网页发布资料入口:`/games/publish` 新增「封面与截图」区(封面必需、截图 ≤6、可逐张移除、上传中禁用提交),复用平台图片直传 + confirm 通道;本地校验与服务端口径对齐(缺封面/超 6 张在发请求前拦截)。
|
||||
- 网页展示:游戏广场卡片与详情页 hero 用 `useResolvedAssetReadUrl` 换签展示真实封面,详情页在存在截图时给出可点击缩略图条(封面 + 截图,选中态 + 键盘可达),换签失败或无素材时静默回退原有渐变占位,不出现空框。
|
||||
- 本切片验证:`cargo check -p api-server -p spacetime-module -p spacetime-client`;`cargo test -p api-server game_distribution`(17 passed,含新增 `version_detail_payload_exposes_frozen_metadata_to_owner`);`npm run typecheck`;`npx vitest run src/components/game-distribution src/services/gameDistributionClient.test.ts`(46 passed);改动文件 `eslint --max-warnings 0` 与 `npm run check:encoding`。
|
||||
|
||||
- AGC 发布面板资料入口:新增 `apps/ai-game-creator-shell/src/services/assetDirectUpload.ts`(凭证 → 直传 → confirm,直传固定走 Tauri HTTP 插件并校验目标主机必须是平台素材存储),面板支持封面必选 + 截图 ≤6、本地预览、逐张移除、上传中禁用发布;缺封面时在创建游戏前失败关闭,服务端「封面」类错误原样展示;同一文件重复提交复用素材 ID 不重复直传。为支持直传,`src-tauri/capabilities/main.json` 的 http 作用域新增 `https://*.aliyuncs.com/*`(配合客户端主机白名单,避免把本地文件发给任意主机)。
|
||||
- AGC 测试证据:`npx vitest run apps/ai-game-creator-shell/tests/assetDirectUpload.test.ts apps/ai-game-creator-shell/tests/gameDistributionPublish.test.ts apps/ai-game-creator-shell/tests/gameDistributionPublishPanel.test.tsx apps/ai-game-creator-shell/tests/clientApi.test.ts`(27 passed);`npm --prefix apps/ai-game-creator-shell run typecheck`;改动文件 `eslint --max-warnings 0`。
|
||||
|
||||
- 真实本地栈媒体验证(2026-09-20,`npm run dev:api-server` + `dev:web`,api-server `127.0.0.1:12401`、SpacetimeDB `127.0.0.1:12402`、真实 dev OSS 桶):`E2E_ADMIN_USER=… E2E_ADMIN_PASSWORD=… npm run check:game-distribution-media-e2e`(`scripts/check-game-distribution-media-e2e.mjs`)27 项全部通过——真实直传封面与 2 张截图素材、缺封面 400、截图 7 张 400、不存在素材 400(创建游戏与创建版本两处)、他人素材 403、创建游戏/版本/上传/送审(202 + `pending_review`)、作者回读拿到 `frozenMetadata` 且素材 ID 与顺序一致、待审期间公开目录不含该游戏且封面匿名读返回 404、管理员审核通过后公开投影带封面与截图对象键且不含素材 ID、匿名 `read-url` 对封面与截图都返回签名地址、发行网关 `index.html` 与 `assets/app.js` 均 200 且带 nosniff。
|
||||
- 真实浏览器展示验证(同一栈,headless Chromium `390x844` 与 `1280x900`):`/games` 卡片渲染真实封面签名地址(无占位回退),`/games/detail?id=…` hero 显示封面、缩略图条渲染「封面 + 2 张截图」,点击第 3 张后 `aria-pressed` 与 hero 图片同步切换;移动端布局正常。截图存于 `~/data/tmp/gd3/`(不入库)。
|
||||
- 网页发布页真实上传验证(同一栈 + headless Chromium):在 `/games/publish` 里用作者会话填写资料、选择真实 PNG 封面与 ZIP 并提交,页面返回「已提交审核」;随后 `my-games` 显示该游戏 `pending_review`,版本回读的 `frozenMetadata.coverAssetId` / `coverObjectKey` 正是本次浏览器上传的素材,证明网页端封面直传(凭证 → OSS → confirm)与冻结链路真实可用。
|
||||
- 发布资料入口交互打磨:原生 file 控件在上传后清空 value 时会显示「未选择任何文件」,与「已选择」提示互相矛盾;网页发布页与 AGC 面板都改成透明 input 覆盖自定义胶囊按钮(点击命中原生控件,文案显示「选择/更换封面图片」「添加截图」),并在验证中用 `elementFromPoint` 确认点击命中的是 `input[type=file]`。
|
||||
- AGC 直传能力作用域静态验证:用 `tauri-plugin-http` 同一套 `urlpattern` 解析逻辑验证 `capabilities/main.json` 新增的 `https://*.aliyuncs.com/*` 命中 dev 桶(`xushi-dev.oss-cn-beijing.aliyuncs.com/…`)与生产桶(`genarrative-assets.oss-cn-shanghai.aliyuncs.com/…`)、拒绝 `evil.example.com`;直传失败(含作用域拒绝/网络不可达)统一转成中文可操作文案,并有单测覆盖。桌面端真实执行仍需在装有 AGC 的机器上跑一次。
|
||||
- 创建游戏同步校验素材归属:此前 `POST /api/game-distribution/games` 只校验「封面必填 / 截图 ≤6 / ID 非空」,素材是否存在与是否属于当前作者要等到创建版本才失败,游戏行会先落一个无效素材 ID;现在创建游戏与创建版本都走 `resolve_owned_game_media`,不存在返回 400、他人素材返回 403。
|
||||
|
||||
- 合并 master(`81e0c41f1`,含 `429f991bd` 定向回滚、Godot 原生绑定迁移与策划 Agent 模型控件):回滚提交把混入 master 的游戏分发代码整体删掉,其中 25 个文件在本次合并里属于“双方都改动同一区域之外”的自动删除,直接合并会静默丢功能。处理方式是按“分支补回功能、master 保留迁移”逐类归位:模块注册(`spacetime-module/src/active.rs`)、api-server 路由与权限映射、后台审核页、AGC 发布命令与 payload 类型、`SelectionStage` 的 games 阶段、`ProjectSupervisorView` 的 `overlay` 挂载点、主站路由/标题、Cargo 依赖与锁、vite/vitest 代理与 tailwind source 全部取回分支版本;master 的 Godot C++ 迁移(删除旧 vendored GDExtension、新的 composer 控件样式)保留。合并后用“按文件对比 game-distribution 引用数不得下降”的脚本复核 85 个相关文件,无残留丢失。
|
||||
- 顺带修掉 master 自带的一处红灯:`src/config/viteProxyConfig.test.ts` 断言 `/api/creation-entry` 会被代理,但 master 的 `vite.config.ts` 并无该代理项,且 api-server 已把 `/api/creation-entry/config` 列入 retired 路由测试(dev 中间件按退役路径返回 404)。测试改为断言“退役路径不进入代理”,与 `isRetiredApiPath` 事实一致。
|
||||
|
||||
- 第二次合并 master(`81e0c41f1` → `500835407`,304 个提交:DirectProject 聊天容器重构、Project Supervisor 退役、策划附件导入、CI 隔离编译缓存等):冲突 9 个文件,其中真正需要集成决策的是 AGC 侧——master 把 `ProjectSupervisorView` / `SupervisorChatOnlyView` 整体退役(含我们此前挂 overlay 的挂载点),并且前端不再调用 `export_local_project_package`(`check-config.mjs` 把它登记为 native-only)。处理方式:接受 master 的退役与删除;把发布入口重新接到新架构上——`DirectProjectChatHeader` 新增「发布到游戏广场」按钮(没有回调时不渲染、忙态禁用),`DirectProjectChatView` 透传 `onRequestGamePublish`,`App.tsx` 用工作台壳持有试玩包导出与 `GameDistributionPublishPanel`(沿用 `project.export_package` 权限确认队列),并把该命令从 `check-config.mjs` 的 native-only 清单移回 App invoke。
|
||||
- 同轮修掉 master 自带的红灯断言:`src/config/viteProxyConfig.test.ts` 曾断言 `/api/creation-entry` 会被代理,但配置无该代理项且 api-server 已把 `/api/creation-entry/config` 列为退役路由,测试改为断言退役路径不进入代理。
|
||||
- 覆盖率与门禁(合并后):全量 `npm test` 392 文件 / 4371 用例通过(appSurface 重构后 215 用例)、两端与 admin-web typecheck、`cargo check`(api-server / spacetime-module / spacetime-client)、`cargo test`(api-server 游戏分发 17、module-game-distribution 11)、`check:encoding`、`check:doc-index`、`check:rustfmt`、SpacetimeDB schema guard(对比 `origin/master`)。
|
||||
|
||||
- 第三次合并 master(`500835407` → `fdc14404b`:DirectProject 回合三态、投影 memo、宿主崩溃后的回合收口、策划对话布局修复、AGC release 每日调度):git 自动合并无冲突,但产生了**静默拼接缺陷**——新加的聊天头 CSS 被并进了 master 策划态分组选择器中间,导致 `.project-chat-topbar-status` 在策划态丢失 `font-size`(`chatDialogFrameLayout` 用例抓到)。修复方式是按 master 原文重建分组规则、把发布入口规则独立成块,并把重复的状态规则删掉。
|
||||
- 合并后复核:全量 `npm test` 393 文件 / 4374 用例通过,root / AGC / admin-web 三端 typecheck 通过,发布入口(聊天头「发布到游戏广场」→ 试玩包导出 → 发布面板)在新回合三态下保持接线。
|
||||
|
||||
- 发布入口灰度下发:`GET /api/runtime/frontend-config` 新增 `gameDistributionPublishEnabled`,复用既有 `is_game_distribution_publish_enabled_for_user`(未配置 `game-distribution:publish` 或 `enabled=false` 时对已登录作者默认开放,显式收紧后只放行白名单/灰度命中,匿名恒为 false),避免前端入口与写入口出现两套判据。网页端 `PlatformEntryActiveFlowShell` 据此隐藏「发布游戏 / 发布新版本」入口,`/games/publish` 直接访问时渲染「发布功能正在灰度中」并提供重新检查;AGC 端 `readGamePublishAvailability` 同样读该字段,只有命中才把发布回调交给 DirectProject 聊天头。
|
||||
- 灰度验证:`cargo test -p api-server frontend_runtime_config`(6 passed,含新增的 `frontend_runtime_config_game_distribution_publish_is_scoped_to_authenticated_gate`:无 gate 行 → 登录作者 true/匿名 false;`enabled=true` 无白名单 → false;白名单命中 → true;`deny_user_ids` → false;`enabled=false` → true;`rolloutPercent=100` → true)、网页发布页 15 用例(含灰度未命中隐藏表单与「重新检查」放行)、平台壳 18 用例(含广场入口按灰度隐藏/显示)、AGC 发布服务 6 用例(含字段缺失与读取失败按不开放处理)。
|
||||
|
||||
## 尚未完成
|
||||
|
||||
- 真实独立发行域名、通配 TLS 与 CDN 仍属部署侧:边缘模板与门禁已就绪,本地已用真实 nginx 验证按主机映射、Cookie 403 与命名空间隔离,但仍需在真实域名/证书下跑一次“审核通过 → 游玩 → 换版 → 下架”并确认 CDN TTL 不超过 60 秒窗口。
|
||||
- 版本回读、撤回与管理员安全下架已实现;主规范 HTTP 表中不再有待落地路由。容量与限额边界、发行包 PUT 重试已有真实栈证据;仍未做的是 CDN purge 失败行为、回滚演练与清理策略(不删除仍被公开版本引用的对象)的上线验收。
|
||||
- 生产调用依赖已初始化的 editor generation runtime service identity;未初始化时 procedure 拒绝写入,不会退回 API 进程内存状态。
|
||||
@@ -17,7 +17,7 @@ AGC 在项目打开期间按周期把用户项目增量上传到 OSS `agc-dev`
|
||||
- 目标 bucket 配置:`GENARRATIVE_AGC_PROJECT_SNAPSHOT_OSS_*`,默认 `agc-dev`。
|
||||
- 契约:`shared-contracts::agc_project_snapshots` 新增请求/响应 DTO 与项目 ID、相对路径、摘要校验函数。
|
||||
- 客户端增量索引:`<AppData>/project-snapshots/<projectId>/index.json`,按用户身份判等,换号后按冷启动全量重算。
|
||||
- 排除口径:复用 `should_skip_project_snapshot_path`(整个 `.agent`、`.git`、构建与依赖目录、凭据目录、敏感后缀、符号链接与重解析点)。
|
||||
- 排除口径:快照同步使用 `should_skip_project_snapshot_sync_path`(2026-09-22 起)。`.agent` 承载项目身份与 Agent 状态,整目录同步;`.git`、构建与依赖目录、凭据目录、敏感后缀、符号链接与重解析点仍然排除。项目索引、checkpoint、Agent 上下文与 git 检查继续沿用 `should_skip_project_snapshot_path` 的整个 `.agent` 排除口径。
|
||||
|
||||
## 不做
|
||||
|
||||
@@ -32,7 +32,7 @@ AGC 在项目打开期间按周期把用户项目增量上传到 OSS `agc-dev`
|
||||
1. 首次同步上传项目内全部符合条件的普通文件;再次同步在无改动时上传 0 个文件。
|
||||
2. 只修改一个文件时,差异集合恰好包含一个修改项;删除一个文件时上传集合为空且清单中不再包含该文件。
|
||||
3. `(字节数, 修改时间)` 未变的文件复用已存摘要,不重复读取内容计算摘要。
|
||||
4. 排除规则命中项(`.agent/runtime`、`.agent/logs`、`.git`、`node_modules`、构建产物、临时文件、符号链接)与超限文件进入跳过清单,不进入上传集合。
|
||||
4. 项目内 `.agent` 的全部普通文件(`manifest.json`、`agent.db` 与其 WAL/SHM、`.manifest.json.lock`、`project.lock`、conversations、logs、runtime、checkpoint、workbench)进入上传集合;`.git`、`node_modules`、构建产物、凭据目录与敏感后缀仍不进入;超限文件进入跳过或延后清单,不静默丢弃。
|
||||
5. 任一次同步失败(非鉴权类)不推进本地索引,下一次触发重算并重试;鉴权/权限类失败不自动重试。
|
||||
6. 同一项目的并发触发串行执行,不产生两路重复上传。
|
||||
7. 工作区窗口关闭与应用退出都会触发一次同步,且关闭路径不因同步失败而阻塞退出超过超时上限。
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
# 【里程碑】游戏分发、发布与在线游玩
|
||||
|
||||
| 字段 | 值 |
|
||||
| --- | --- |
|
||||
| Version | 0.2 |
|
||||
| Status | accepted(用户“继续”确认按既定假设进入阶段 A;A 未验收) |
|
||||
| Date | 2026-09-18 |
|
||||
| Parent Spec | `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` |
|
||||
|
||||
## 交付与评审边界
|
||||
|
||||
本文件拆分主规范“AGC 游戏分发与在线游玩合同”的完整业务:真实上传、持久化发行、审核、隔离托管、AGC 发布、网页上传与游玩、上线运维。当前只授权阶段 A 实现,阶段 B/C/D 仍为 `proposed`,没有已上线/已验收结论。
|
||||
|
||||
先完成主规范与本文件评审,再为一个获准阶段创建单独实施计划;未经该阶段验收,不进入依赖它的阶段。里程碑只规定行为与证据,不预先写代码步骤。
|
||||
|
||||
| 阶段 | 行为切片 | 依赖 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| A | 真实包、游戏身份与可恢复发行 | 主规范评审及持久化合同冻结 | accepted(实现中) |
|
||||
| B | 人工审核、公开投影与隔离托管 | A 验收;发行站点/私有存储/审核策略确认 | proposed |
|
||||
| C | AGC 与网页发布、现代游戏目录和跨端游玩 | B 验收;导航与交互稿评审 | proposed |
|
||||
| D | 端到端验收、容量与撤销、上线准备 | C 验收;实际部署资源可用 | proposed |
|
||||
|
||||
## 实现进度(2026-09-20,未验收)
|
||||
|
||||
以下是已落地的实现与本地运行时证据,**不等于阶段验收**:B/C 的隔离托管、域名与生产验收仍缺少真实发行域名、TLS/CDN 与生产账号。
|
||||
|
||||
- 阶段 A:真实 ZIP 上传、游戏身份、owner/幂等/CAS、状态机、DTO 与 schema 门禁已完成;真实 SpacetimeDB + 私有 OSS 的创建/上传/确认/重启恢复已有证据。
|
||||
- 阶段 B:人工审核(后台列表、通过需 HTTPS 入口、拒绝需理由)、公开投影、发行网关(按公开版本服务、扩展名白名单、`nosniff`/CORP/CSP、带 Cookie 403)与作者下架已实现;每游戏独立来源已有可执行工件 `deploy/nginx/genarrative-release-origin.conf` 与门禁 `npm run check:release-origin-config`,并已在本机用真实 nginx + 真实网关验证按主机映射、Cookie 403 与平台命名空间 404;生产域名、通配证书与 CDN TTL 仍需上线环境确认。
|
||||
- 阶段 C:AGC 客户端「发布到平台」面板与发布链路(dist 归一化根 `index.html`、摘要/字节数声明、幂等键、`localProjectId` 复用)已实现并有请求组装与 Rust 导出测试;网页 `/games/publish` 走同一服务端管道,本地已用真实文件选择验证;AGC GUI 自身的端到端发布仍待客户端环境验收。目录(关键词/分类/设备筛选、滚动与筛选恢复)、详情、游玩页(主动作后加载、超时重试、旋转提示、全屏、移动端门槛)与作者中心(状态、驳回理由、撤回、下架)已实现;本地已在桌面与 `390x844` 移动视口真实游玩。
|
||||
- 阶段 D:容量/额度、重启恢复、CDN 撤销与回滚演练尚未开始,依赖生产资源。
|
||||
|
||||
细节与命令级证据见[实施计划【游戏分发阶段A领域合同】](【实施计划】游戏分发阶段A领域合同-2026-09-19.md)的「已完成证据」「运行时证据」「尚未完成」。
|
||||
|
||||
## 共通范围与不做项
|
||||
|
||||
- 正式状态来自后端,素材仍复用平台上传与归属能力;前端和 AGC 不另建公开游戏状态、owner 事实或审核结果。
|
||||
- 真实发行包具有不可变版本和 SHA-256,AGC dist 归一化为发行根 `index.html`;网页 ZIP 与 AGC 共用一条服务管道。
|
||||
- 不恢复退役玩法 API、公开作品表或专属 runtime;不把私有项目源码镜像公开。
|
||||
- 首版不包含原生/Wasm 游戏、任意外网依赖、服务端进程、多人联机、云存档、评论/评分/关注、排行榜、推荐算法和收益结算。
|
||||
- 本文件只协调本业务;不顺带改造图片编辑器、Agent Runtime 执行模型或无关项目数据。
|
||||
|
||||
## 阶段 A:真实包、身份与可恢复发行
|
||||
|
||||
### 前置条件
|
||||
|
||||
- 主规范的包类型、限额、身份/幂等/CAS、状态机和内部 API 已评审。
|
||||
- 游戏、版本、审核与操作账本的完整字段、索引、唯一约束、受信服务身份及清理策略已经冻结;若涉及已有表破坏性变更,另有已确认迁移计划。
|
||||
|
||||
### 行为与验收
|
||||
|
||||
- [ ] 登录用户创建服务端分配的游戏,owner 不能由请求伪造;其他账号不能读取私有版本、上传、提交或撤销。
|
||||
- [ ] 服务端接收真实 ZIP 字节,重算摘要/字节数并建立展开清单;只有 metadata 的请求不能获得已上传或已发布状态。
|
||||
- [ ] 缺入口、越界/重复/大小写冲突路径、符号链接、压缩炸弹、敏感内容和额度超限均失败关闭,原私有对象和公开状态保持一致。
|
||||
- [ ] 一份版本只接受一份已确认内容;同 key 同请求重放无重复游戏/版本,不同请求冲突;同版本并发上传不混写。
|
||||
- [ ] 校验可异步恢复,响应丢失、服务进程退出和客户端重试均回到原版本;确定失败和未知结果在响应中可区分。
|
||||
- [ ] 正常及失败状态、私有查询和错误 envelope 在 Rust 与 TypeScript DTO 中一致;新增 schema、迁移、表目录与绑定一致。
|
||||
|
||||
### 证据要求
|
||||
|
||||
- 自动化:领域状态机、owner、幂等/CAS、包读取与错误路径、DTO 和 schema 定向测试。
|
||||
- 运行时:真实 SpacetimeDB 与私有对象存储完成上传/回读/重启恢复;`/healthz` 正常。
|
||||
- 边界:服务端记录的 ZIP 摘要与测试上传字节一致;未审核目录不能匿名读取;无凭据或本地路径泄漏。
|
||||
|
||||
## 阶段 B:审核、公开投影与隔离托管
|
||||
|
||||
### 前置条件
|
||||
|
||||
- A 已验收;人工审核角色、资料检查标准与拒绝/封禁行为已确认。
|
||||
- 已确定不同可注册站点的发行域名、每游戏独立 origin、私有存储和网关能力;同源临时路径不能作为验收替代。
|
||||
|
||||
### 行为与验收
|
||||
|
||||
- [ ] 自动校验通过只进入待审,管理员可查看真实待审游戏并批准/拒绝;审核记录可追溯且普通作者不能提交审核动作。
|
||||
- [ ] 新游戏审核通过并核验发行文件可读后才公开;更新待审或失败不改变旧版资料、URL 与可玩性。
|
||||
- [ ] 审核激活与下架使用 `publicationRevision` CAS;过期审核、重复批准、并发更新和下架不会恢复本应关闭的游戏。
|
||||
- [ ] 游客目录、详情和启动接口只返回已公开投影;未公开和已下架状态均不可见,不返回私有快照地址。
|
||||
- [ ] 每游戏在独立 HTTPS origin 上,iframe sandbox、网关 CSP/CORS/MIME/禁止 Worker 等策略与主规范一致。
|
||||
- [ ] 实际 npm/Vite 模块和同包资源在 opaque sandbox 下可载入;外站 fetch/WebSocket、平台 Cookie/storage/DOM、顶层跳转、弹窗和敏感权限被阻断。
|
||||
- [ ] 作者下架及管理员安全下架会关闭新启动和发行读取;不能绕过网关直取公开 OSS 对象;撤销传播符合最大缓存窗口。
|
||||
|
||||
### 证据要求
|
||||
|
||||
- 自动化:审核权限、版本切换事务、公开投影与入口 allowlist、响应头和缓存策略测试。
|
||||
- 运行时:真实独立域名内运行至少一个代表性游戏;真实审核、更新失败、并发下架和旧 URL 回读证据。
|
||||
- 边界:外部请求阻断、匿名私有对象拒绝、跨游戏 origin 隔离及 60 秒以内缓存撤销(以获批值为准)。
|
||||
|
||||
## 阶段 C:双端发布与现代游戏体验
|
||||
|
||||
### 前置条件
|
||||
|
||||
- B 已验收;网页根入口、桌面/移动导航、暖色主题交互稿及首版设备范围已评审。
|
||||
- AGC 现有 npm 构建/导出能提供完整 dist,不需要把源码同步管道改作发行管道。
|
||||
|
||||
### 行为与验收
|
||||
|
||||
- [ ] AGC 从已构建 dist 生成根入口为 `index.html` 的真实包,一次提交动作完成检查、资料确认、上传和送审;状态及失败原因与服务端回读一致。
|
||||
- [ ] 网页可选 ZIP、提交封面和必需资料,进入相同上传/校验/审核流程;任一客户端可以查看同账号游戏状态,更新沿用相同 `gameId`。
|
||||
- [ ] 上传中断、双击、登录失效、窗口关闭后恢复原操作;换账号不能恢复前账号私有状态;待审不能显示为已发布。
|
||||
- [ ] 目录支持真实数据、关键词/分类/设备筛选、空/错/加载态;详情提供明确主动作,搜索与返回恢复上下文。
|
||||
- [ ] 游客从目录/分享链接进入详情再启动真实已发布游戏;开始操作后才加载 iframe,加载失败可重试,退出不会自动重启游戏。
|
||||
- [ ] 桌面与移动导航、详情、上传/发布面板、旋转提示、全屏、安全区和焦点可达符合主规范;不适配移动端的游戏有明确门槛。
|
||||
- [ ] 视觉沿用现有 warm token,通用交互复用共享组件;没有固定假统计、本地演示兜底或同源 iframe 放宽。
|
||||
|
||||
### 证据要求
|
||||
|
||||
- 自动化:AGC 打包与操作恢复定向 Rust 测试、两端客户端/组件/路由/状态测试及类型检查。
|
||||
- 运行时:AGC 一次真实发布、网页一次真实 ZIP 上传,分别审核后从桌面和手机游玩;浏览器覆盖游戏模块、素材、音频、触屏及横竖屏。
|
||||
- 边界:未构建/失效 dist、资料缺失、换账号、迟到响应、审核拒绝、非移动游戏及真实空态。
|
||||
|
||||
## 阶段 D:端到端、运维与上线准备
|
||||
|
||||
### 前置条件
|
||||
|
||||
- C 已验收;生产域名/TLS、CDN、私有存储、审核账号及保留/清理周期可用且已确认。
|
||||
|
||||
### 行为与验收
|
||||
|
||||
- [ ] 真实环境中完整跑通“首次上传 → 校验 → 审核 → 公开 → 游客游玩 → 更新待审旧版在线 → 新版切换 → 下架撤销”。
|
||||
- [ ] 100 MiB 包与获批文件数/展开量边界有可复核耗时、内存和失败证据;校验不会执行上传代码,服务资源有界。
|
||||
- [ ] 校验执行器重启可恢复,审核积压与失败可观测,清理不删除仍被公开版本引用的文件。
|
||||
- [ ] CDN purge 失败时仍在获批缓存 TTL 内拒绝新资源;明确已下载脚本无法远程抹除的边界。
|
||||
- [ ] 发布/回滚步骤保留当前公开版本,能关闭新提交和新版本激活;部署路由、缓存、响应头、日志脱敏和告警完成检查。
|
||||
- [ ] 主规范逐条证据矩阵齐全,未验证项明确列出;有任何核心路径未验证时不标记上线完成。
|
||||
|
||||
### 证据要求
|
||||
|
||||
- 自动化:范围匹配前后端、schema/契约、编码与文档索引门禁,部署配置检查。
|
||||
- 运行时:真实发行域名、真实存储、真实账号和两类客户端的完整链路证据,桌面及移动视口记录。
|
||||
- 边界:压力/额度、重启恢复、缓存撤销、误删防护和回滚演练。
|
||||
|
||||
## 当前待审项与下一门禁
|
||||
|
||||
主规范仍待确认人工审核策略、导航、首版能力/额度、对象保留、发行域名及运营责任;当前没有主规范与里程碑通过评审的记录,也没有任何阶段验收证据。先评审这些决策,再冻结 A 的持久化与 DTO 明细并创建仅覆盖 A 的实施计划。不得把该文档状态改为 accepted 以代替评审。
|
||||
|
||||
所有阶段完成并验收后,稳定事实回写主规范,删除本临时里程碑与各阶段实施计划;过程记录不进入产品代码、用户文案或长期共享记忆。
|
||||
@@ -25,6 +25,7 @@
|
||||
- [ ] 未单独配置快照目标时仍使用 agc-dev,只复用资源存储凭据;显式快照目标保持有效,不迁移现存对象。
|
||||
- [ ] 后台列表按部署渠道查询,显示项目名/ID、用户(昵称 + 陶泥号)、同步时间、文件数、体积和完整性,支持刷新与游标分页(每页 20/50/100 + 上一页/下一页)。
|
||||
- [ ] ZIP 按清单还原相对路径;不含对象存储摘要目录;空文件可上传与导出。
|
||||
- [ ] `.agent` 承载项目身份与 Agent 状态,整目录随快照上传,并在后台 ZIP 中按原相对路径还原;归档因此可用于还原项目身份与 AGC 对话历史。凭据、版本库与构建产物仍不进快照。
|
||||
- [ ] 清单名称/完整性变化在无内容差异时也提交,partial 可恢复 ready,历史缺字段不冒充 ready。
|
||||
- [ ] 缺失、损坏、越界路径和非完整清单失败关闭;历史未声明完整性的清单明确标记,允许导出已有文件但不称为完整工程。
|
||||
- [ ] 无后台权限不能读取项目或 ZIP;不泄漏凭据;ZIP 构建有体积、并发和临时文件清理边界。
|
||||
@@ -39,6 +40,10 @@
|
||||
| 层次 | 结果 |
|
||||
| --- | --- |
|
||||
| 客户端 Rust `project_snapshot` | 22 通过、1 忽略(写入式真实上传 smoke 未运行);含排队退出等待回归 |
|
||||
| `.agent` 全量上传口径(2026-09-22 修订) | `project_snapshot` 定向 24 通过、1 忽略;新增扫描纳入 `.agent` 与策略单测,覆盖 `.agent` 内凭据、版本库、`.env*` 继续排除,以及项目索引 / checkpoint 口径不变 |
|
||||
| `.agent` 真实样本复核 | 对 15 个本地 AppData 项目的 487 个 `.agent` 普通文件按新规则复算,0 个仍落在排除集;该复算是脚本复刻规则,不是 Rust 运行时证据 |
|
||||
| `.agent` 客户端链路运行时 smoke(2026-09-22) | 用真实项目副本(`.agent` 164 个文件 / 5.61 MiB,项目 `gameagent-agentsmoke1`)走真实差异引擎 → 本地 api-server → 真实 OSS:首轮 `synced`、`uploaded=179` / `18,991,590` 字节、跳过与失败均为 0;紧接着第二轮 `no-op` / 上传 0 个文件。`uploaded=179` 恰好等于原口径的 15 个项目文件加 164 个 `.agent` 文件 |
|
||||
| `.agent` 存储与后台归档(2026-09-22) | 只读 GET 真实 OSS 清单对象(`agc/project-snapshots/v1/<user>/gameagent-agentsmoke1/manifest.json`,26,356 字节):`files=179`、其中 `.agent/**` 164 条、`pendingFiles=0`、合计 18,991,590 字节;按清单里的 `大小-摘要` 构造对象键 HEAD 命中 `.agent/manifest.json`(18,319)、`.agent/agent.db`(9,353)、`.agent/conversations/project.jsonl`(1,422,226) 与 `game/package.json`(227)。后台 `download` 返回的 ZIP 解压出 179 条目、164 条在 `.agent/` 下,抽样 6 个文件(含 `agent.db`、会话、运行态文件)与本地 SHA-256 完全一致;正在运行的 dev api-server 列表显示同一项目 `files=179 status=ready` |
|
||||
| 客户端前端生命周期与启动器 | 3 文件、10 测试通过;完整 AGC typecheck、skill-pack、check-config 通过 |
|
||||
| 后台页面、API client、路由与样式 | 44 测试通过;admin-web typecheck/build 通过 |
|
||||
| 后端快照与权限 | 14 定向测试通过;未认证路由矩阵与页签映射 2 测试通过 |
|
||||
@@ -53,3 +58,5 @@
|
||||
`npm run dev:api-server -- --api-port 4198 --bgfilter-worker-port 4199 --api-timeout-seconds 600` 编译成功,但当前工作区配置的本地数据库 `xushi-p4wfr` 在 `127.0.0.1:3101` 返回 404,启动认证投影无法完成,故 `/healthz` 及完整 HTTP smoke 未通过。仅本任务启动的 API/worker 已停止;没有清库、迁移数据库、改 `.env` 或替换其它项目的验证目标。
|
||||
|
||||
尚未替换安装版、构建新客户端发布包或部署;提交推送按用户本轮授权执行。当前条款已有上述自动化与只读存储证据,最终验收仍等待当前工作区数据库就绪后的 HTTP 联调,以及新客户端的隔离实机自动上传验证;完成后再关闭里程碑并清理两份计划。
|
||||
|
||||
`.agent` 全量口径已在上表取得真实上传、真实 OSS 清单与后台 ZIP 三层运行时证据。两点边界仍需记住:① 本次 smoke 用的是真实项目的可丢弃副本,其 `projectId` 改成 `gameagent-agentsmoke1`、走 dev 专用账号上传,历史真实项目在下一个同步周期前仍是旧口径清单(不含 `.agent`);② 本地 `api-server.exe` 构建于 2026-09-21 14:06,早于渠道分区提交 `4951b71d7`(2026-09-21 16:57),因此本次对象键仍是 `agc/project-snapshots/v1/` 布局、`GET /admin/api/project-snapshots/channels` 在本地返回 404;升级到 v2 键布局需要重建并重启本地 api-server。
|
||||
|
||||
Reference in New Issue
Block a user