docs(游戏共创): M2b 文档回填两处实现偏差 + 状态改已实现(含 M2b 证据表)
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (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 / 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

- 技术方案 §3.4:上行路由行按端到端实测补两条按实现为准的细节——① **非作者上传返回 404 而不是 403**(`load_owner_version_or_404` 把 owner 不匹配按「版本不存在」处理,与发行包上行族同口径,不泄露版本存在性、响应不含对象键);② **阶段门先判「已存在」再判档位**(`ensure_project_bundle_uploadable`:`project_bundle_bytes > 0` → 409 `PROJECT_BUNDLE_ALREADY_EXISTS`,再看 `status` → 409 `PROJECT_BUNDLE_UPLOAD_NOT_ALLOWED`),因此「已公开且已有工程包」返回 `ALREADY_EXISTS`,「已公开但无工程包」才返回 `UPLOAD_NOT_ALLOWED`。证据:`check-game-distribution-project-bundle-e2e` 的 A5 / A5b / A6 及其 NOTE
- 技术方案 §3.5.2:AGC 侧「上传」「下载与建项」由「接线待做」改为**已落地**(发布时上传且失败不阻断、按 `source` 分支建项、来源记录 v2 兼容 v1、取件提示按形态措辞),并写明两端规则由 `npm run check-project-bundle-policy-parity` 机器门禁钉住一致
- 技术方案 §3.5.4:路线 B 由「服务端已实现、客户端接线待做」改为**两端均已落地**
- 技术方案 §3.7:补 M2b 口径——共创写入仍复用 `game-distribution:publish`(未新增灰度键);内容下发与工程包上传当前只有 `tracing` 日志,三个结构化埋点事件**仍未接**(待产品定事件口径)
- 技术方案新增 §5.2「M2b(工程源包)实施证据」表:三列与契约、校验器(16 条单测 / 上限镜像)、上行路由族(api-server 60 passed)、下行优先与缓存不串味、端到端 `check:game-distribution-project-bundle-e2e` **46/46**、两端规则 parity 门禁、客户端 vitest 1961 passed 与定向 cargo test 17+16;并显式登记「backfill 明确不做」「真实极端边界与结构化埋点待补」
- 里程碑「作品工程源包与一键改造」:Status → `implemented-local`;新增「实现进展(2026-10-05)」列出两端提交 SHA 与证据命令、以及四条未做项及原因;验收标准改为 ✅/⏳/⏭ 三段并有证据指向(阶段门顺序、非作者 404、半包清理等按实测更新);证据要求补上已落地的命令清单与「真机建项链路仍未跑」的括注
- 门禁:`check:doc-index` 248 份 OK;`check:encoding` 5304 files OK;`git diff --check` 0
This commit is contained in:
2026-10-05 16:17:07 +08:00
parent da24dd5bee
commit bc06360d51
2 changed files with 42 additions and 17 deletions
@@ -3,7 +3,7 @@
| 字段 | 值 |
| ----------- | ----------------------------------------------------- |
| Version | 1.2 |
| Status | in-progress(**服务端已落地**:三列 + 工程包校验器 + 上行/下行路由族;AGC 侧确定性打包器已落地,**上传与建项接线待做**) |
| Status | implemented-local(**两端均已落地**:服务端三列 + 校验器 + 上行/下行路由族;AGC 侧打包器 + 上传 + 取件建项 + 来源记录 v2;端到端脚本 46/46、规则 parity 门禁 OK。未跑项见验收标准里的 ⏳) |
| Date | 2026-10-03(2026-10-04 标注为正解路径;2026-10-05 按实现更新范围与验收) |
| Parent Spec | `docs/【技术方案】游戏共创与作品Fork-2026-10-03.md`(口径见 §2.5、§3.2.3、§3.4、§3.5.1、§3.5.2、§3.5.4) |
@@ -40,23 +40,33 @@
- 客户端已有的「从包安装并建项」基座与包内容门禁可直接复用;本里程碑不新建第二套解压或建项路径。
- 决策:产品已同意引入工程源包,且**不设**独立的「源码可见性」授权位(口径见文首)。
## 实现进展(2026-10-05)
- 服务端:块 A `c6bd2c081`(三列 + 契约 + 绑定)、块 B `9403a47c0`(工程包校验器)、块 C+D `d061b2e37`(上行 5 条路由 + 下行优先 + 缓存资产维度);文档块 E `3aa145d91`。
- 客户端:`4d070dd1a`(规则对齐 + `check-project-bundle-policy-parity` 机器门禁)、`5dd9d1c5d`(发布时上传,失败不阻断)、`72f08c264`(取件按 `source` 分支建项 + 来源记录 v2 兼容 v1)、`da24dd5be`(取件提示按形态如实措辞);`ff763abe5`(端到端验收脚本)。
- 证据:`npm run check:game-distribution-project-bundle-e2e` **46/46**;`npm run check-project-bundle-policy-parity` OK;`cargo test -p module-game-distribution` 69 passed(含校验器 16);`cargo test -p api-server game_distribution` 60 passed;AGC `vitest` 1961 passed、定向 `cargo test` 17 + 16 passed。
- **未做(含原因)**:① 已公开版本的补传(backfill)——按产品口径不做,阶段门与发行包确认一致;② 真实 >200 MiB / 10 001 条目 / 极端半包续传边界——未测(成本高,靠常量单测钉住);③ AGC 真机取件→建项链路——未跑(需要真机客户端窗口);④ 结构化埋点事件——未接(待产品定事件口径)。
## 验收标准
> 标注:✅ = 服务端已完成并有单测/门禁证据(本地);⏳ = 待客户端接线或真实栈(dev 栈端到端)验证。
> 标注:✅ = 已落地并有证据(单测 / 门禁 / 端到端脚本,本地);⏳ = 仍待补(真机或极端边界);⏭ = 明确不做。
- [x] ✅ 工程包校验器拒绝清单齐备:绝对路径、`..`、盘符、反斜杠、通配符、符号链接、加密条目、嵌套 `.zip`、完全重复/大小写折叠重复路径、任意层级 `node_modules` / `.git` / `.svn` / `.agent`、根级 `dist` / `build` / `library` / `temp` / `local` / `.idea` / `.vscode`、凭据与隐私文件(`.env*`、`*.pem`、`*.key`、`*.p12`、`*.pfx`、`.npmrc`、`.netrc`、`.git-credentials`、`id_rsa*`、`id_ed25519*`、`*.map`)、单文件/累计/压缩比超限;合法源码包(**不含** `index.html`)通过。证据:`module-game-distribution` 16 条校验器单测。
- [x] ✅ 同一版本重复确认同一工程包不产生第二份对象(同内容幂等重放);换内容重传按冲突拒绝(409 `同一版本已存在不同的工程源包`)。
- [x] ✅ 阶段门:只有 `awaiting_upload` / `upload_failed` 可写;已上传、验证中、待审核、已拒绝、已公开、已撤回、已取消一律拒绝;已公开版本的**补传不做**(见「不在范围内」)。
- [x] ✅ 工程包校验器拒绝清单齐备:绝对路径、`..`、盘符、反斜杠、通配符、符号链接、加密条目、嵌套 `.zip`、完全重复/大小写折叠重复路径、任意层级 `node_modules` / `.git` / `.svn` / `.agent`、根级 `dist` / `build` / `library` / `temp` / `local` / `.idea` / `.vscode`、凭据与隐私文件(`.env*`、`*.pem`、`*.key`、`*.p12`、`*.pfx`、`.npmrc`、`.netrc`、`.git-credentials`、`id_rsa*`、`id_ed25519*`、`*.map`)、单文件/累计/压缩比超限;合法源码包(**不含** `index.html`)通过。证据:`module-game-distribution` 16 条校验器单测 + `check-project-bundle-policy-parity`(客户端打包器与它逐项对齐)。
- [x] ✅ 同一版本重复确认同一工程包不产生第二份对象(同内容幂等重放);换内容重传按冲突拒绝(409 `同一版本已存在不同的工程源包`)。证据:模块事务 + `check-game-distribution-project-bundle-e2e` A4。
- [x] ✅ 阶段门:只有 `awaiting_upload` / `upload_failed` 可写;已上传、验证中、待审核、已拒绝、已公开、已撤回、已取消一律拒绝。**顺序按实现**:「已存在工程包」先判(409 `PROJECT_BUNDLE_ALREADY_EXISTS`),故「已公开且已有工程包」返回 `ALREADY_EXISTS`;「已公开但无工程包」才返回 `UPLOAD_NOT_ALLOWED`。证据:e2e A5 / A5b + api-server 单测。
- [x] ✅ 非作者上传 → **404**(按「版本不存在」处理,与发行包上行族同口径,不泄露版本存在性),未带 Bearer → 401。证据:e2e A6 + 路由级 401 单测。
- [x] ✅ 上行整包 PUT(`application/octet-stream`)+ 服务端独立复核 + 失败 422 `PROJECT_BUNDLE_VALIDATION_FAILED` 且清理半包对象。证据:e2e(整包/校验失败/清理)+ 单测。
- [x] ✅ 下行优先级:同一版本同时有两种资产时 `fork-source` 返回 `source: "project"` 且 `downloadPath` 指向 `/fork-source/project`;只有成品包时回落 `package`;「字节数 > 0 但摘要为空」的半写行回落 `package`(失败关闭);两条下载路径共用同一个校验函数,请求 project 而实际没有工程包时 409 而非静默回落。
- [x] ✅ 两种资产的读取缓存不串味(缓存键即对象键,键名分别以 `.zip` / `.project.zip` 结尾)。
- [x] ✅ 未上传工程包不阻断发布与审核(上传是独立可选资产,不参与版本状态机);无工程包的作品仍能拿到成品包。
- [ ] ⏳ 上传中断后可按权威偏移续传(分片路由已实现,客户端接线与真实栈验证待做)。
- [ ] ⏳ 客户端在真实环境中完成源码工程形态建项:新项目可打开、可编辑源码、可试玩、可重跑构建并发布。
- [ ] ⏳ 通过源码路径发布的作品,其来源、代际与根与成品包路径完全一致(服务端共用同一套血缘,真机验证待做)。
- [ ] ⏳ 界面如实告知:未上传时改造能力止于「试玩 + 素材参考(不能直接重新发布)」。
- [x] ✅ 两种资产的读取缓存不串味(缓存键即对象键,键名分别以 `.zip` / `.project.zip` 结尾)。证据:api-server 单测(对象键互不相等 + `ReleasePackageCache` 双资产各自取回)。
- [x] ✅ 未上传工程包不阻断发布与审核(上传是独立可选资产,不参与版本状态机);无工程包的作品仍能拿到成品包;未上传时面板如实提示「只能被他人试玩与参考」。证据:客户端发布链路改动 + 面板文案测试(真机窗口仍未看,见下条)。
- [ ] ⏳ 上传中断后按权威偏移续传的**真实**验证:分片路由与客户端续传逻辑均已落地,但未跑真实断点/乱序场景。
- [ ] ⏳ AGC **真机**取件 → 建项链路:客户端按 `source` 分支建项与来源记录 v2 已落地并有单测,但未在真机窗口里跑过「取件 → 解压 → 建项 → 可编辑/可试玩/可发布」整链。
- [ ] ⏳ 通过源码路径发布的作品,其来源、代际与根与成品包路径完全一致(服务端本就用同一套血缘,真机验证待做)。
- [ ] ⏳ 真实极端边界:>200 MiB 压缩包、>10 001 条目、极端压缩比在**真实上传链路**上的表现(当前靠常量单测与门禁钉住,未在真实栈压测)。
- [x] ⏭ 已公开版本的**补传(backfill)**:按产品口径**不做**(阶段门与发行包确认一致,公开后不接受内容写入)。原验收项已从本节移除,改为本行显式记录。
## 证据要求
- 自动化:打包排除规则与体积上限的客户端定向测试、上传确认与冲突用例、服务端门禁与鉴权拒绝用例、来源优先级与标注用例、DTO 一致性与 schema 检查、编码与文档索引检查。
- 运行时:真实 api-server + 真实对象存储 + 真实客户端跑通「拿到源码 → 改核心逻辑 → 试玩 → 发布 → 溯源与代际正确」,附对比截图或录屏。
- 自动化(已落地的命令):`npm run check:game-distribution-project-bundle-e2e`(**46/46**)、`npm run check-project-bundle-policy-parity`(两端规则一致)、`cargo test -p module-game-distribution`(69 passed,含校验器 16)、`cargo test -p api-server game_distribution`(60 passed)、AGC `npx vitest run apps/ai-game-creator-shell/tests`(1961 passed)与定向 `cargo test -- fork|project_bundle`(17 + 16 passed)、DTO parity 与 schema/编码/文档索引门禁。
- 运行时:真实 api-server + 真实对象存储 + 真实客户端跑通「拿到源码 → 改核心逻辑 → 试玩 → 发布 → 溯源与代际正确」,附对比截图或录屏。**(取件 → 建项链路的真机窗口验证仍未跑,见验收标准 ⏳)**
- 边界:越权下载、无授权下载、来源已下架、摘要不符、超限包、解压失败、上传中断续传、账号切换后的迟到响应、同版本双来源优先级。
@@ -313,7 +313,7 @@ pub(crate) project_bundle_sha256: Option<String>,
| 方法 / 路径 | 说明 |
| --- | --- |
| `PUT /games/{gameId}/fork-authorization`(新) | body `{ expectedForkAuthorization, forkAuthorization }` + `Idempotency-Key`;只允许提升;返回最新 `forkAuthorization` 与 `replayed` |
| `PUT /versions/{versionId}/project-bundle`(**M2b 已实现**) | `application/octet-stream` 整包一次上传(≤ 200 MiB);另有分片族 `GET …/project-bundle/upload-state`、`PUT …/project-bundle/chunk`(偏移头 `x-genarrative-upload-offset`)、`POST …/project-bundle/complete`(服务端独立跑工程包 zip 门禁 + 算摘要 + 确认)、`POST …/project-bundle/reset`(丢弃未确认的暂存对象)。前置:调用者是该版本作者;版本处于 `awaiting_upload` / `upload_failed`;该版本**尚无**已确认的工程包(换内容 → 409)。对象键由服务端派生(`…/{version_id}.project.zip`),不接受客户端指定 |
| `PUT /versions/{versionId}/project-bundle`(**M2b 已实现**) | `application/octet-stream` 整包一次上传(≤ 200 MiB);另有分片族 `GET …/project-bundle/upload-state`、`PUT …/project-bundle/chunk`(偏移头 `x-genarrative-upload-offset`)、`POST …/project-bundle/complete`(服务端独立跑工程包 zip 门禁 + 算摘要 + 确认)、`POST …/project-bundle/reset`(丢弃未确认的暂存对象)。前置:调用者是该版本作者;版本处于 `awaiting_upload` / `upload_failed`;该版本**尚无**已确认的工程包(换内容 → 409)。对象键由服务端派生(`…/{version_id}.project.zip`),不接受客户端指定。**两个按实现为准的细节(端到端实测,A5/A5b/A6)**:① **非作者上传返回 `404` 而不是 `403`**——api-server 用 `load_owner_version_or_404` 把 owner 不匹配按「版本不存在」处理,与发行包上行族同口径,既不会泄露「这个版本存在但不属于你」,响应里也不含对象键;② **阶段门先判「已存在」再判版本档位**——`ensure_project_bundle_uploadable` 先看 `project_bundle_bytes > 0`(→ 409 `PROJECT_BUNDLE_ALREADY_EXISTS`,因为确认工程包不驱动版本状态机,已确认的版本可能仍停在 `awaiting_upload`),再看 `status`(→ 409 `PROJECT_BUNDLE_UPLOAD_NOT_ALLOWED`);因此「已公开**且已有**工程包」返回的是 `ALREADY_EXISTS`,只有「已公开**但还没有**工程包」才落到 `UPLOAD_NOT_ALLOWED` |
| `POST /games`(**既有,请求增量**) | 追加可选 `forkedFromGameId` / `forkedFromVersionId`;再追加可选 `forkAuthorization`(`forbidden` / `nonCommercial` / `full`,**缺省禁止共创**,非法取值整请求 400 + 平台信封),使作者**上架时**即可选择授权档位,不必事后提升 |
#### 登录用户(Bearer,**不叠加**发布灰度)
@@ -358,8 +358,8 @@ pub(crate) project_bundle_sha256: Option<String>,
- **打包(AGC,已落地)**:客户端 `project_bundle` 模块做**确定性打包**(条目排序 + 固定时间戳,同内容同摘要),排除 `.agent` / `.git` / `.svn` / `node_modules` 段(任意层级)与根 `dist` / `build` / `library` / `temp` / `local` / `.idea` / `.vscode`;客户端自身另有总量上限(512 MiB)。
- **服务端复核(已落地,**不信任客户端**)**:`module-game-distribution/src/project_bundle.rs` 的 `validate_project_bundle_zip` 独立校验上传内容——上限与发行包**逐项相等**(压缩包 200 MiB / 展开 500 MiB / 单文件 64 MiB / 条目 10 000 / 压缩比 100,理由是两者共用同一条上传链路,反代与 Pingora 的放行量就是按 200 MiB 校准的);额外拦凭据与隐私文件(`.env`、`.env*`、`*.pem`、`*.key`、`*.p12`、`*.pfx`、`.npmrc`、`.netrc`、`.git-credentials`、`id_rsa*`、`id_ed25519*`、`*.map`)、嵌套 `.zip`、符号链接、加密条目与不安全路径;**不要求**根 `index.html`(源码包没有入口约定)。
- **上传(AGC,接线待做)**:走 §3.4 的 `PUT /versions/{versionId}/project-bundle`(或分片族)。只在版本尚未公开时可写一次;**可选**——上传失败不影响发布与审核,但要在发布面板明确提示「未上传工程包,你的作品只能被他人试玩与参考(不能直接重新发布)」。是否有工程包**不改变**授权是否开放(见 §3.2.3 的产品口径)。
- **下载与建项(AGC,接线待做)**:`fork-source` 返回 `source: 'project'` 时走 `GET /games/{gameId}/fork-source/project` → 校验 `sha256` 与 `bytes` → 解压到 `<app_data>/forks/<gameId>/<versionId>/`(与模板安装同一套路径门禁与私有 DACL 写入)→ 建到用户工作区 → 写 `manifest.forkedFrom`。已落地的客户端半段只有确定性打包器。
- **上传(AGC,已落地)**:发布链路在「授权非 `forbidden`」时随版本上传工程源包(失败**不阻断**发布,按可重试处理);面板明确提示「未上传工程包,你的作品只能被他人试玩与参考(不能直接重新发布)」。是否有工程包**不改变**授权是否开放(见 §3.2.3 的产品口径)。客户端规则与服务端对齐,并有机器门禁 `npm run check-project-bundle-policy-parity` 钉住两端排除清单/上限一致。
- **下载与建项(AGC,已落地)**:`fork-source` 返回 `source: 'project'` 时走 `GET /games/{gameId}/fork-source/project`(`package` 时走原路径)→ 校验 `sha256` 与 `bytes` → 解压到 `<app_data>/forks/<gameId>/<versionId>/`(与模板安装同一套路径门禁与私有 DACL 写入)→ 按来源分支建项(源码形态可直接改源码/重跑构建;成品包形态是可玩参考副本)→ 写 `manifest.forkedFrom`。来源记录 `.agent/fork-source.json` 升到 **v2**(记录取件形态与对应摘要),**兼容 v1**(旧记录仍可读);取件成功提示按两种形态分别如实措辞。
- **优先级(已落地)**:同一版本同时存在工程源包与成品包时,`fork-source` 优先返回工程源包并标 `source: 'project'`;只有工程包缺失(或行上「字节数 > 0 但摘要为空」这种半写状态)才回落 `package`,客户端据此决定建项形态。
#### 3.5.3 发布时声明(两条路径共用)
@@ -370,7 +370,7 @@ AGC 发布链路(`game_distribution_publish.rs`)读取 `manifest.forkedFrom`
> 本节是**待拍板材料**,不是定稿合同;定稿前不得据此实现。结论来自 2026-10-04 的只读侦察(材料:`local://m2a-design.md`),它否掉了原方案「成品包铺进项目即可试玩与发布」的假设(事实依据见 §2.5 与 §3.5.1)。
**进展(2026-10-05)**:产品已采纳下面的推荐口径(**不做**独立的「源码可见性」开关;作者不传工程包即退化产物级改编;`nonCommercial` 的约束力是平台规则层面)。路线 **B 的服务端已实现**(§3.2.3 三列 + `module-game-distribution` 的工程包校验器 + 上行/下行路由族,见 §3.4),AGC 侧已落地确定性打包器,**上传与建项接线待做**;A 作为默认路径继续服务未上传工程包的作品;C 不做。
**进展(2026-10-05)**:产品已采纳下面的推荐口径(**不做**独立的「源码可见性」开关;作者不传工程包即退化产物级改编;`nonCommercial` 的约束力是平台规则层面)。路线 **B 两端均已落地**:服务端(§3.2.3 三列 + `module-game-distribution` 的工程包校验器 + 上行/下行路由族,见 §3.4)与 AGC 侧(确定性打包器、发布时上传、按 `source` 分支的取件建项、来源记录 v2),两端规则由 `npm run check-project-bundle-policy-parity` 机器门禁钉住一致;A 作为默认路径继续服务未上传工程包的作品;C 不做。
三条候选路线:
@@ -404,6 +404,7 @@ A 路线里有一个必须提前知道的互斥点:`create_npm_scaffold` 的
- **M1 实现口径**:共创写入复用现役 `game-distribution:publish` 开关(`ensure_publish_enabled`),不新增独立灰度键——本功能与发布能力同批上线,先少一个开关减少漂移面。独立键 `game-distribution:fork`(含 `/api/runtime/frontend-config` 的 `gameDistributionForkEnabled` 与前端入口门禁)作为后续增强保留,接入前需要同时改 `module-runtime`、`api-server` 与前端配置读取。
- 事件:`game_fork_source_downloaded`、`game_fork_declared`、`game_fork_authorization_updated`,含成功/失败分类;不发对象键与用户隐私字段。
- **M1 未接埋点**:上述事件在 M1 未实现(当前只有既有 `tracing` 日志),随 M2 的内容下发一起补,避免在无下载能力时先埋无意义事件。
- **M2b 口径(2026-10-05)**:共创写入仍复用 `game-distribution:publish`,未新增独立灰度键;**内容下发与工程包上传目前只有 `tracing` 日志**(带 `operation` 与 `request_id` 标签,便于按请求串联),上面三个结构化埋点事件**仍未接**——待产品确认事件口径与埋点表后再补,避免先埋一套随后要改的字段。
### 3.8 兼容与迁移
@@ -519,6 +520,20 @@ A 路线里有一个必须提前知道的互斥点:`create_npm_scaffold` 的
| **未知共创档位(HTTP 面合同)** | `PUT /api/game-distribution/games/{gameId}/fork-authorization` 载荷里目标或期望档位为未知值(如 `"allowed"`)时返回 **400 + 平台信封**(不再落到 axum 默认的 422 纯文本);新用例 `set_fork_authorization_maps_unknown_authorization_to_envelope_bad_request` 断言 `ok=false`、`data=null`、`error.code=BAD_REQUEST`、message 含「共创授权档位不合法」、带 `meta.apiVersion`,且不带框架的 `Failed to deserialize` 文案。领域层 `FORK_AUTHORIZATION_UNKNOWN` 分支保留(procedure 读到库里未知档位时仍可达) | ✅ 已验证(`cargo test -p api-server game_distribution`,45 passed) |
| **详情页结构对齐 #565** | 详情页改用共享 `GameDetailDisplay`,共创卡通过 `infoCards` 渲染;vitest 用例「展示共创授权、代际与溯源信息,且不提供改造入口」与「旧数据缺少授权与血缘字段时只按禁止共创渲染」通过 | ✅ 已验证 |
### 5.2 M2b(工程源包)实施证据 — 2026-10-05
| 项 | 证据 | 状态 |
| --- | --- | --- |
| 数据模型与契约 | 版本行三列(`project_bundle_object_key` / `_bytes` / `_sha256`)落地;`GameDistributionPrivateVersion` 两侧同步(对象键不下发);生成绑定只保留 `module_bindings/game_distribution*` 与 `module_bindings.rs`,其余漂移还原 | ✅ 已验证(DTO parity 42 组、`check:spacetime-schema` 92 tables、`cargo check --all-targets` 0) |
| 工程包校验器 | `module-game-distribution` 的 `validate_project_bundle_zip`:拒绝清单见 §3.5.2,上限与发行包**逐项相等**(测试用 `assert_eq!` 钉住);`package.rs` 零改动 | ✅ 已验证(16 条单测;`cargo test -p module-game-distribution` 69 passed) |
| 上行路由族 | 5 条路由(整包 / upload-state / chunk / complete / reset);对象键服务端派生且与发行包不同;唯一共享阶段门;`complete` 独立复核 zip + 422 映射 | ✅ 已验证(`cargo test -p api-server game_distribution` **60 passed**,含 6 条路由与阶段门/资产选择用例) |
| 下行优先与缓存 | `source` 取 `Project`(优先)/`Package`;半写行回落 `package`;`/fork-source/project` 无工程包时 409 而非静默回落;缓存键 = 对象键(资产维度) | ✅ 已验证(同一批 api-server 用例:对象键互不相等 + `ReleasePackageCache` 双资产不串味) |
| 端到端(真实本地栈 + 真实客户端链路) | `npm run check:game-distribution-project-bundle-e2e` → **46 项 46 PASS**(含 A5 已公开且已有工程包 → `ALREADY_EXISTS`、A5b 已公开但无工程包 → `UPLOAD_NOT_ALLOWED`、A6 非作者 → **404** 且响应不含对象键) | ✅ 已验证 |
| 两端规则一致(机器门禁) | `npm run check-project-bundle-policy-parity` → OK(客户端打包器与服务端校验器的排除清单/上限逐项对齐,防止两端口径漂移) | ✅ 已验证 |
| 客户端 | AGC 打包器(确定性打包)、发布时上传(失败不阻断)、按 `source` 分支的取件建项、来源记录 v2(兼容 v1);`npx vitest run apps/ai-game-creator-shell/tests` **1961 passed**;AGC 定向 `cargo test` 17 + 16 passed | ✅ 已验证 |
| 已公开版本的补传(backfill) | **未做**(按产品口径不做:阶段门与发行包确认一致,公开后不接受内容写入),文档三处已如实写明 | ⏭ 明确不做 |
| 真实边界(>200 MiB / 10 001 条目 / 半包续传的极端组合)与结构化埋点事件 | 未跑/未接:边界靠常量与单测钉住,结构化埋点(`game_fork_source_downloaded` 等)待产品定口径 | ⏳ 待补 |
---
## 6. 里程碑拆分(评审后逐个开实施计划)