AGC 发布前检查平台包体上限,并把上限单一来源
Project CI / AI game creator shell Rust smoke (push) Successful in 1m55s
Project CI / AI game creator shell Rust crates (push) Successful in 1m46s
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / Backend tests (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 / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Successful in 1m55s
Project CI / AI game creator shell Rust crates (push) Successful in 1m46s
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / Backend tests (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 / Native shell tests (push) Has been cancelled
- shared-contracts 新增 GAME_DISTRIBUTION_MAX_PACKAGE_BYTES(200 MiB),服务端 module-game-distribution 保留同值常量并在注释里指明;api-server 新增 publish_package_limit_matches_the_shared_contract 守卫测试把两处锁一致 - AGC project/export 新增 ensure_within_platform_package_limit:读包阶段预检,超限返回「发行包 X MiB 超过平台上限 200 MiB;请精简资源后重新导出再发布」,不再让 200–512 MiB 的包白读盘/暂存后吃服务端 413 - 用例:project::export::npm_export_tests::publish_precheck_rejects_packages_over_the_platform_limit(边界=上限放行、超限文案、本地导出上限确实更宽) - 里程碑 C1 与 pitfalls 记录「本地导出上限 ≠ 平台发布上限」这一口径
This commit is contained in:
@@ -301,6 +301,22 @@ pub(crate) async fn export_local_project_package_for_publish_at(
|
||||
export_local_project_package_at(root)
|
||||
}
|
||||
|
||||
/// 发布前的客户端预检:包体不得超过平台上限。
|
||||
///
|
||||
/// 服务端仍会独立校验;这里先判一次是为了给作者可操作错误(含实际体积与上限),
|
||||
/// 而不是让他把包传上去再吃一个 413。上限单一来源在 `shared-contracts`。
|
||||
fn ensure_within_platform_package_limit(package_bytes: u64) -> Result<(), String> {
|
||||
let limit = shared_contracts::game_distribution::GAME_DISTRIBUTION_MAX_PACKAGE_BYTES;
|
||||
if package_bytes > limit {
|
||||
return Err(format!(
|
||||
"发行包 {:.1} MiB 超过平台上限 {} MiB;请精简资源后重新导出再发布。",
|
||||
package_bytes as f64 / (1024.0 * 1024.0),
|
||||
limit / (1024 * 1024)
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn read_local_project_export_package_at(
|
||||
root: &Path,
|
||||
package_relative_path: &str,
|
||||
@@ -322,6 +338,7 @@ pub(crate) fn read_local_project_export_package_at(
|
||||
if metadata.len() == 0 || metadata.len() > MAX_PROJECT_EXPORT_PACKAGE_BYTES {
|
||||
return Err("发行包大小超出本地发布上限".to_string());
|
||||
}
|
||||
ensure_within_platform_package_limit(metadata.len())?;
|
||||
let package_bytes =
|
||||
fs::read(&package_path).map_err(|error| format!("读取发行包失败:{error}"))?;
|
||||
if package_bytes.len() as u64 != metadata.len() {
|
||||
@@ -643,6 +660,22 @@ pub(crate) fn normalize_export_package_entry_path(relative_path: &str) -> Result
|
||||
mod npm_export_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn publish_precheck_rejects_packages_over_the_platform_limit() {
|
||||
let limit = shared_contracts::game_distribution::GAME_DISTRIBUTION_MAX_PACKAGE_BYTES;
|
||||
// 边界:正好等于上限放行,差一个字节也放行(服务端同样按 `>` 判定)。
|
||||
ensure_within_platform_package_limit(limit).unwrap();
|
||||
ensure_within_platform_package_limit(limit - 1).unwrap();
|
||||
// 超限:错误文案必须给出实际体积与上限,作者才能据此精简。
|
||||
let error = ensure_within_platform_package_limit(limit + 1).expect_err("over limit");
|
||||
assert!(error.contains("超过平台上限"), "{error}");
|
||||
assert!(error.contains("200 MiB"), "{error}");
|
||||
assert!(error.contains("MiB"), "{error}");
|
||||
// 本地导出上限更宽松(512 MiB):超平台的包必须在读包阶段就被拦下,
|
||||
// 不能等到上传后由服务端 413。
|
||||
assert!(limit < MAX_PROJECT_EXPORT_PACKAGE_BYTES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn npm_package_contains_only_dist_and_publish_readme() {
|
||||
let base = PathBuf::from(std::env::var("HOME").unwrap()).join("data/tmp");
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
|
||||
### 行为与验收
|
||||
|
||||
- [ ] AGC 从已构建 dist 生成根入口为 `index.html` 的真实包,一次提交动作完成检查、资料确认、上传和送审;状态及失败原因与服务端回读一致。
|
||||
- [ ] AGC 从已构建 dist 生成根入口为 `index.html` 的真实包,一次提交动作完成检查、资料确认、上传和送审;状态及失败原因与服务端回读一致。(2026-09-29 补客户端侧检查口径:此前客户端只有本地导出上限 **512 MiB**,比平台上限宽一倍多——300 MiB 的包会一路读盘/暂存到建版本时才吃服务端 413。现在上限单一来源落在 `shared_contracts::game_distribution::GAME_DISTRIBUTION_MAX_PACKAGE_BYTES`(200 MiB),AGC 读包阶段先做 `ensure_within_platform_package_limit` 预检并给出「发行包 X MiB 超过平台上限 200 MiB;请精简资源后重新导出再发布」;服务端 `module-game-distribution` 仍保留自己的常量,由 api-server 的 `publish_package_limit_matches_the_shared_contract` 把两者锁成一致(改一处不改另一处直接变红)。AGS 侧用例 `project::export::npm_export_tests::publish_precheck_rejects_packages_over_the_platform_limit` 覆盖边界(=上限放行)与超限文案。仍未验收:AGC GUI 里真实一键发布与状态/失败原因回读一致。)
|
||||
- [ ] AGC 发布面板隐藏发行包技术摘要;打开时基于有界、脱敏的项目上下文免费生成一句话简介与白名单分类,失败保留本地兜底且不阻断发布;作者始终可以直接编辑生成结果。(2026-09-29 补客户端侧口径:「脱敏」此前只在服务端有界——`buildPublishProjectContext()` 会把素材的 `localPath` 原样写进提示词,登记来源不受本地约束时绝对路径(含用户目录)就会外发。现在素材只带**文件名**(`contextSafeAssetLabel` 取最后一段并限长 80),上下文整体仍截到 6,000 字符;`gameDistributionPublishPanel.test.tsx` 新增「发布资料建议只带脱敏且有界的项目上下文」并用 40 个绝对路径素材 + 超长版本说明验证。变异验证:把该处改回 `asset.localPath` 原样拼接后,用例立即以 `expected ... to not include 'C:\Users'` 变红。GUI 打开面板、真实 Provider 生成与失败兜底仍待客户端验收。)
|
||||
- [ ] AGC 发布封面支持基于项目上下文生成,复用现役图片生成与泥点扣费链路;生成结果登记为当前账号平台素材后自动作为 `coverAssetId`,不二次上传。
|
||||
- [x] 网页可选 ZIP、提交封面和必需资料,进入相同上传/校验/审核流程;任一客户端可以查看同账号游戏状态,更新沿用相同 `gameId`。
|
||||
|
||||
@@ -6139,3 +6139,9 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- **实测**:冷启动时 `Invoke-WebRequest /games` 花了 **20,171 ms**(`/` 约 2,146 ms),随后连续两次 4,208 ms / 10,119 ms,预热完成后 **14 ms**。原因是 Vite dev 按需编译该路由的模块图,首个请求最贵。
|
||||
- **处理**:脚本在浏览器导航前先 `fetch` 预热本轮要用的 `/games`、`/games/detail?id=…`、`/games/play?id=…`(120s 上限,失败只告警),并把首个 `goto` 超时提到 120s;冷启动重跑 → **24/24 PASS**。另外三个 web 脚本原本就用 `waitUntil: 'commit', timeout: 120_000`,属同一类防护,别再去掉。
|
||||
- **判据**:遇到"首个 goto 超时 + API 断言全过"的形态,先用 `curl`/`Invoke-WebRequest` 量一次同 URL 的耗时;20s 级冷启动说明是 Vite 编译,不是页面回归。
|
||||
|
||||
## 2026-09-29 客户端"本地导出上限"不等于"平台发布上限"
|
||||
|
||||
- **现象/风险**:AGC 里 `MAX_PROJECT_EXPORT_PACKAGE_BYTES = 512 MiB` 管的是"本地导出/暂存的体积安全线",而平台发布上限是 **200 MiB**(`module-game-distribution` 的 `MAX_PACKAGE_BYTES`,2026-09-23 决策从 100 MiB 放宽而来)。两者不是一个概念,拿前者当发布前检查会让 200–512 MiB 的包一路读盘 + 暂存,最后在建版本时吃服务端 413,作者白等一轮。
|
||||
- **现状(2026-09-29 已修)**:上限单一来源落到 `shared_contracts::game_distribution::GAME_DISTRIBUTION_MAX_PACKAGE_BYTES`;AGC 在读包阶段就做 `ensure_within_platform_package_limit()` 预检并给出「发行包 X MiB 超过平台上限 200 MiB;请精简资源后重新导出再发布」。服务端领域 crate 不依赖 `shared-contracts`,仍保留自己的常量,由 api-server 的 `publish_package_limit_matches_the_shared_contract` 锁成一致。
|
||||
- **判据**:任何"客户端先检查、服务端再校验"的额度都要问一句"这两处是同一个数吗、谁保证不漂移";本地安全线(防呆)与平台业务额度(可对外承诺)要分开命名,别混用。
|
||||
|
||||
@@ -2511,6 +2511,16 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publish_package_limit_matches_the_shared_contract() {
|
||||
// 客户端(AGC 发布前检查)读的是 `shared-contracts` 里的同一份上限;这里把服务端
|
||||
// 领域常量与它锁在一起,避免两边各改一处后静默漂移。
|
||||
assert_eq!(
|
||||
MAX_PACKAGE_BYTES,
|
||||
shared_contracts::game_distribution::GAME_DISTRIBUTION_MAX_PACKAGE_BYTES,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_request_body_limit_covers_max_package_bytes() {
|
||||
// 口径约束:发行包路由的请求体放行量必须覆盖包体上限,否则合法包会在
|
||||
|
||||
@@ -9,6 +9,10 @@ use sha2::{Digest, Sha256};
|
||||
/// 发行包体积上限。反代放行量与路由请求体上限都从它派生:Nginx
|
||||
/// `client_max_body_size`、Pingora `MAX_API_BODY_BYTES` 必须同步放宽,否则合法包会在
|
||||
/// 到达 `api-server` 之前被拒。
|
||||
///
|
||||
/// 客户端发布前检查读的是 `shared_contracts::game_distribution::
|
||||
/// GAME_DISTRIBUTION_MAX_PACKAGE_BYTES`;本 crate 不依赖 `shared-contracts`,两处数字
|
||||
/// 由 api-server 的守卫测试锁成一致(改一处不改另一处会直接变红)。
|
||||
pub const MAX_PACKAGE_BYTES: u64 = 200 * 1024 * 1024;
|
||||
/// 展开总量上限保持压缩包上限的 2.5 倍余量:包体本身基本不可再压时展开量约等于包体,
|
||||
/// 纯文本 / JSON 资源占比高的包仍要有足够空间。
|
||||
|
||||
@@ -8,6 +8,12 @@ use serde::{Deserialize, Serialize};
|
||||
pub const GAME_DISTRIBUTION_CATEGORIES: [&str; 7] =
|
||||
["休闲", "益智", "动作", "冒险", "模拟", "策略", "其他"];
|
||||
|
||||
/// 发行包体积上限(字节)。
|
||||
///
|
||||
/// 服务端校验与客户端「发布前检查」共用这一份:两边各写一个数字迟早会漂移,
|
||||
/// 客户端要么放行服务端必拒的包(白等一次上传),要么拦掉服务端能收的包。
|
||||
pub const GAME_DISTRIBUTION_MAX_PACKAGE_BYTES: u64 = 200 * 1024 * 1024;
|
||||
|
||||
/// 发布页免费生成简介与分类的输入。
|
||||
///
|
||||
/// 只传经过裁剪的项目摘要,不传本地绝对路径、聊天记录、凭据或完整 manifest。
|
||||
|
||||
Reference in New Issue
Block a user