diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 0c6d227a4..70ecdb6e0 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -6545,3 +6545,12 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/` - **CPU 边界**:发行网关是公开无鉴权端点,压缩按请求实时算。release 构建实测 level 6 为 1.3 MiB→10 ms、8 MiB→59 ms、64 MiB(单文件上限)→522 ms 纯 CPU,因此在 `RELEASE_COMPRESSION_FAST_ABOVE_BYTES`(2 MiB)以上改用 level 1(zlib 端实测 level 1 约为 level 6 的 1/3 耗时、压缩比只差约 3%)。若后续要再做减法,优先把压缩结果按 `(对象键, 资源路径)` 缓存,而不是放宽级别。 - **验证**:`cargo test -p api-server --bin api-server -- game_distribution`(新增 ETag 作用域、`If-None-Match` 列表/弱校验命中、`gzip;q=0` 拒绝、文本压缩与二进制/小文件不压、304 无正文、`*` 对包内缺失路径仍 404 等用例);`npx vitest run packages/shared/src/components/PlatformGameLoadingSurface.test.tsx`、`src/components/game-distribution/GameDistributionPages.test.tsx`、`apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx`;`npm run check:game-distribution-ops-rollback-e2e` 47 项通过(含压缩/Vary/ETag/304/不接受 gzip 四条新断言)。真实栈同链路复跑:`phaser.min.js` 1,375,976 B → 353,336 B(gzip,4 Mbps 下 2724 ms → 774 ms),用户端游戏画面 3298 ms → 1543 ms、后台试玩 4587 ms → 2694 ms。 - **关联**:`packages/shared/src/components/PlatformGameLoadingSurface.tsx`、`src/components/game-distribution/GamePlayPage.tsx`、`apps/admin-web/src/pages/AdminGameDistributionReviewPage.tsx`、`server-rs/crates/api-server/src/modules/game_distribution.rs`、`deploy/nginx/README.md`。 + +## 2026-10-06 合并 master 后 schema guard 的基线盲区:本地全绿、CI 红(表字段相对顺序) + +- **现象**:合并 master(merge 提交 `eeb101458`,PR base `9f4c7d76`)后本地整套门禁全绿,CI 的 **Backend tests** 与 **Repository checks** 却都在 `check:spacetime-schema` 红:`表 game_distribution_game 的第 26 个字段从 price_mud_points 变为 fork_authorization,疑似字段顺序被调整`。 +- **原因**:schema guard 对**表的字段相对顺序**是按 index 逐位比对的(`baseTable.fields[index]` vs `currentTable.fields[index]`);而**不显式给基线时它取 `git merge-base HEAD origin/master`**——merge 提交还没建时那还是**旧 merge-base**,于是「两侧各自在表尾追加」被并成「我们的列插到了 master 的列前面」这件事本地完全看不见;merge 提交一建,基线变成 PR base,CI 立刻红。 +- **处理(现行口径)**:合并 master 后**先建 merge 提交**,再用 PR base 复跑 `SPACETIME_SCHEMA_BASE_REF= npm run check:spacetime-schema`。列顺序规则:**master 先追加的列保持原 index,我们的追加排它之后**(`price_mud_points` 第 26 位、`fork_authorization` 表尾);对应的 `SpacetimeType` 快照(`GameDistributionGameSnapshot`)按同一条纪律排,避免只改表不改快照。 +- **别踩**:`migration.rs` 白名单、DTO parity 成员、nginx SPA 路由、Pingora 路由清单都是**集合/无序**判定(各脚本内部 `Set`/`difference`),只有 **SpacetimeDB 表字段顺序**(以及同步生成的 `module_bindings/*` 里的 wire 顺序)对相对顺序敏感;改列序后必须重跑 `spacetime generate` 并只回写受影响文件。 +- **判据/取证**:`SPACETIME_SCHEMA_BASE_REF=9f4c7d76 npm run check:spacetime-schema`(exit 0)、`cargo check --all-targets` 0、`cargo test -p api-server game_distribution`(101 passed)、`cargo test -p spacetime-module`(294 passed)。 +- **关联**:`server-rs/crates/spacetime-module/src/game_distribution.rs`(game 表与 game 快照的列序注释)、`server-rs/crates/spacetime-client/src/module_bindings/game_distribution_game_{type,snapshot_type}.rs`、`scripts/check-spacetime-schema-guard.mjs`、`scripts/check-repository-ci.sh`(CI 侧基线传法)。 diff --git a/server-rs/crates/spacetime-client/src/module_bindings/game_distribution_game_snapshot_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/game_distribution_game_snapshot_type.rs index 296b150c2..3eb397958 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/game_distribution_game_snapshot_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/game_distribution_game_snapshot_type.rs @@ -31,8 +31,8 @@ pub struct GameDistributionGameSnapshot { pub local_project_id: Option, pub cover_object_key: Option, pub screenshots_json: Option, - pub fork_authorization: String, pub price_mud_points: u64, + pub fork_authorization: String, } impl __sdk::InModule for GameDistributionGameSnapshot { diff --git a/server-rs/crates/spacetime-client/src/module_bindings/game_distribution_game_type.rs b/server-rs/crates/spacetime-client/src/module_bindings/game_distribution_game_type.rs index cb361ae1c..657279b3a 100644 --- a/server-rs/crates/spacetime-client/src/module_bindings/game_distribution_game_type.rs +++ b/server-rs/crates/spacetime-client/src/module_bindings/game_distribution_game_type.rs @@ -32,8 +32,8 @@ pub struct GameDistributionGame { pub cover_object_key: Option, pub screenshots_json: Option, pub deleted_at: Option<__sdk::Timestamp>, - pub fork_authorization: String, pub price_mud_points: u64, + pub fork_authorization: String, } impl __sdk::InModule for GameDistributionGame { @@ -69,8 +69,8 @@ pub struct GameDistributionGameCols { pub cover_object_key: __sdk::__query_builder::Col>, pub screenshots_json: __sdk::__query_builder::Col>, pub deleted_at: __sdk::__query_builder::Col>, - pub fork_authorization: __sdk::__query_builder::Col, pub price_mud_points: __sdk::__query_builder::Col, + pub fork_authorization: __sdk::__query_builder::Col, } impl __sdk::__query_builder::HasCols for GameDistributionGame { @@ -114,8 +114,8 @@ impl __sdk::__query_builder::HasCols for GameDistributionGame { cover_object_key: __sdk::__query_builder::Col::new(table_name, "cover_object_key"), screenshots_json: __sdk::__query_builder::Col::new(table_name, "screenshots_json"), deleted_at: __sdk::__query_builder::Col::new(table_name, "deleted_at"), - fork_authorization: __sdk::__query_builder::Col::new(table_name, "fork_authorization"), price_mud_points: __sdk::__query_builder::Col::new(table_name, "price_mud_points"), + fork_authorization: __sdk::__query_builder::Col::new(table_name, "fork_authorization"), } } } diff --git a/server-rs/crates/spacetime-module/src/game_distribution.rs b/server-rs/crates/spacetime-module/src/game_distribution.rs index 8415299a4..47992f662 100644 --- a/server-rs/crates/spacetime-module/src/game_distribution.rs +++ b/server-rs/crates/spacetime-module/src/game_distribution.rs @@ -765,14 +765,20 @@ pub struct GameDistributionGame { /// 版本行、发行包对象与冻结资料保持不可变,删除只改游戏行的可见性状态。 #[default(None::)] pub(crate) deleted_at: Option, - /// 作品级共创(Fork)授权档位:`forbidden` / `nonCommercial` / `full`。 - /// 只允许单向提升开放度;旧行按默认「禁止共创」解释。 - #[default("forbidden")] - pub(crate) fork_authorization: String, /// 买断制价格(泥点):`0` 表示免费。只在审核通过时随版本冻结资料整体生效, - /// 因此未通过审核的价格不会改变当前公开价格。必须保持在结构体末尾。 + /// 因此未通过审核的价格不会改变当前公开价格。 + /// + /// **列序是 wire format 的一部分**:这一列是 master 侧先追加的(第 26 位),序号必须保持不变; + /// 本分支后追加的 `fork_authorization` 必须排它**之后**——`check:spacetime-schema` 只允许在 + /// 结构体末尾追加,任何相对顺序变化都会被判成字段重排(合并时正是踩了这一条)。 #[default(0u64)] pub(crate) price_mud_points: u64, + /// 作品级共创(Fork)授权档位:`forbidden` / `nonCommercial` / `full`。 + /// 只允许单向提升开放度;旧行按默认「禁止共创」解释。 + /// + /// 本分支较 master 后追加,因此留在表尾(见上一列的注释)。 + #[default("forbidden")] + pub(crate) fork_authorization: String, } /// 作品之间的改编(Fork)血缘:一个作品最多有一个父,主键即子作品 ID。 @@ -1572,10 +1578,13 @@ pub struct GameDistributionGameSnapshot { pub local_project_id: Option, pub cover_object_key: Option, pub screenshots_json: Option, + /// 买断制价格(泥点);`0` 表示免费。 + /// + /// 与表同一条纪律:这一列是 master 侧先追加的,序号保持不变;本分支的 + /// `fork_authorization` 追加在它**之后**(客户端绑定与之同步生成)。 + pub price_mud_points: u64, /// 作品级共创授权档位;旧行由表默认值补齐为 `forbidden`。 pub fork_authorization: String, - /// 买断制价格(泥点);`0` 表示免费。 - pub price_mud_points: u64, } #[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)]