发行包上限提升到 200 MiB 并支持 AGC 分片续传上传

- 发行包上限 100→200 MiB、展开总量 250→500 MiB,整包路由请求体上限继续从包上限派生;发行静态资源进程内缓存预算提到 256 MiB
- 反代放行量同步放宽到 210 MiB:Nginx 三份模板的 client_max_body_size、Pingora 网关默认值与 env 样例、路由对照矩阵
- platform-oss 新增内部对象追加写 append_internal_object(_with_retry),以 OSS 返回的 next-append-position 作为权威已收字节
- api-server 新增 upload-state / chunk / complete / reset 四条分片路由,抽出共享收口 confirm_validated_package;偏移不符返回 409 与权威偏移,校验失败删除半包并落 upload_failed
- AGC 新增原生上传器 game_package_upload.rs(内容寻址暂存、分片续传、受控重试、进度事件)与 prepare / upload 两条命令,退役整包回传命令
- 渲染进程改为 prepare → 创建游戏 → 创建版本 → 原生分片上传 → 送审,LocalProjectExportPackagePayload 整包类型退役
- 同时修正 live 用例无法指向本地栈的两处基础设施问题:平台基址按传入 URL 选择,桥接层把 jsdom realm 的 Headers / Blob / FormData 降级成 Node 原生值
- 测试:platform-oss 74、api-server game_distribution 23、AGC 原生 4、发布相关前端 20;live 用例补真实栈「中断 → 续传 → 确认」断言(分片偏移序列 [0, 8388608])
- 文档:玩法创作主规范的上传合同、运维与 Pingora 文档、决策记录、发行里程碑口径,以及新增的续传里程碑与实施计划
This commit is contained in:
kdletters
2026-09-23 19:04:45 +08:00
parent c38d07044a
commit c2c5e1ced5
26 changed files with 1779 additions and 169 deletions
@@ -1,5 +1,20 @@
# 决策记录
## 2026-09-23 自绘标题栏是窗口边框:弹层从它下方开始,焦点陷阱放行它
- 背景:AGC 打开任意一个 `ThemedModal` 弹窗(发布面板、发布进度、资源预览、账本、错误报告等)后,右上角「最小化 / 最大化 / 关闭」点击没有任何反应,标题栏拖拽也不能移动窗口;关掉弹窗立刻恢复。原因是标题栏在模态之外,而 `focus-trap-react` 在 document 捕获阶段监听 `mousedown`/`touchstart`/`click`,模态外的点击被 `preventDefault()` 且 `click` 直接 `stopImmediatePropagation()` —— React 的监听在更内层,事件到不了它,所以表现是「点了没反应」而不是报错。另有 `.app-update-overlay` 用 `inset: 0` 真的把标题栏盖住了。
- 决策:把自绘标题栏定为**窗口边框**,不属于弹层内容:① portal 到 body 的全屏弹层一律 `top: var(--window-chrome-height)`,禁止用 `inset: 0` 盖住标题栏;② `ThemedModal` 的焦点陷阱用 `allowOutsideClick` 只放行落在 `[data-window-chrome-bar]` 内的目标,工作区内容的点击继续被拦住;③ `WindowChrome` 的标题栏加 `data-window-chrome-bar` 标记,作为这条约定的唯一契约点。
- 影响范围:`apps/ai-game-creator-shell/src/components/modal/ThemedModal.tsx`、`apps/ai-game-creator-shell/src/components/WindowChrome.tsx`、`apps/ai-game-creator-shell/src/styles.css`(`:root` 注释、`.app-update-overlay`、`.game-publish-progress-overlay`)。
- 验证方式:`tests/themedModal.test.tsx`(标题栏点击放行、工作区点击仍被拦)、`tests/WindowChrome.test.tsx`(弹窗打开时三个窗口按钮仍调用原生窗口 API)、`tests/windowChromeOverlayContract.test.ts`(7 个全屏弹层都从标题栏下方开始)、`tests/gamePublishFeedback.test.tsx` 与 appSurface(208 passed);两处新增用例都做过「去掉修复即失败」的反向确认。`npm run --workspace apps/ai-game-creator-shell typecheck`、eslint、`npm run check:encoding`、`git diff --check` 通过。
## 2026-09-23 游戏发行包上限提升到 200 MiB(反代放行量与发行缓存同步)
- 背景:游戏广场发行包上限原为 100 MiB(`module-game-distribution` 的 `MAX_PACKAGE_BYTES` 与网页端 `GAME_PACKAGE_MAX_BYTES`),而 Nginx 三份模板与 Pingora 网关的通用 `/api` 放行量是 64 MiB。上限只改一层没有意义:包体超过 100 MiB 时先在反代层被 413,`api-server` 的 ZIP 校验根本不会执行。
- 决策:发行包上限 100 MiB → 200 MiB;展开总量 250 MiB → 500 MiB(保持 2.5 倍余量);单文件 64 MiB、最多 10,000 个文件、展开/压缩比 100 三条内容规则不变;发行包路由请求体上限继续从包上限派生(200 MiB + 1 KiB)。反代放行量统一放宽到 210 MiB:`deploy/nginx/genarrative.conf`、`deploy/nginx/genarrative-dev-http.conf`、`deploy/container/nginx.conf` 使用 `client_max_body_size 210m`,Pingora `DEFAULT_MAX_API_BODY_BYTES` 改为 `220200960` 并同步 `deploy/pingora/pingora-gateway.env.example`。发行静态资源进程内缓存字节预算 200 MiB → 256 MiB,让 200 MiB 档发行包仍能进缓存、且不独占整份预算。
- 边界:包内单个文件仍不得超过 64 MiB;线上 Pingora 环境文件若仍写 `67108864`,必须在重启网关前同步改值,否则发行包 PUT 会在网关层被 413。AGC 一键发布经 `@tauri-apps/plugin-http` 传整包字节,实际可发布体积还受该传输方式限制,200 MiB 档的客户端容量需要单独验证。
- 影响范围:`server-rs/crates/module-game-distribution/src/package.rs`、`server-rs/crates/api-server/src/modules/game_distribution.rs`、`server-rs/crates/pingora-gateway/src/main.rs`、`src/components/game-distribution/gameZipPackage.ts`、`deploy/{nginx,container,pingora}`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`、`docs/technical/【开发运维】Pingora独立网关试点-2026-06-11.md`。
- 验证方式:`cargo test -p module-game-distribution`(13 passed,其中 `accepts_package_above_the_previous_hundred_mib_limit` 用两个 50 MiB 存储型条目构造 100 MiB 出头的包;把上限临时改回 100 MiB 时该用例确实失败,证明它能守住新上限)、`cargo test -p api-server game_distribution`(20 passed,含新增的请求体上限覆盖包上限断言)、`cargo test -p pingora-gateway`(38 passed,含 `matches_nginx_route_parity_matrix`)、`npx vitest run src/components/game-distribution`(46 passed)、`cargo fmt --all -- --check`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check` 通过。`npm run check:pingora-route-parity` 仍在 dev-http / 容器模板缺少 `/games` 等 SPA 路由处失败,改动前同样失败,与本次口径无关。200 MiB 档真实栈容量证据(上传耗时、api-server 峰值内存、超限 413 口径)尚未复跑,发布前需按阶段 D 脚本重跑一轮。
## 2026-09-23 引用名不允许空白:素材 / Skill / 附件共用 `normalizeMentionName`
- 背景:自动评审发现 `buildContentFromTextTokens` 在前缀重叠时会多插一枚芯片——素材显示名 `hero` 与 `hero v2` 并存时,粘贴 `看 @hero v2 这一版` 得到 `[chip hero]` + `[chip hero-v2]`(短名先按 index 平局抢位,长名成了补到末尾的孤儿)。根因不是匹配算法,而是**引用名自己带空白**:token 的边界规则是「前后为空白或行首行尾」,`@hero␠` 在 `@hero v2` 内部也算一次合法命中。