【付费游玩】游戏买断制泥点付费规范:主规范合同、里程碑与实施计划(#634) #635

Merged
suzmii merged 29 commits from feat/game-purchase into master 2026-10-06 15:16:01 +08:00
Member

关联

本次改动(切片 1:规范先行)

  • 主规范《平台入口与玩法链路》解除“不做交易”排除,新增「游戏买断制泥点付费与播放鉴权合同」章节。
  • 新增里程碑与实施计划,冻结范围、不在范围内、验收标准与实现顺序。

方案摘要

  • 定价:价格作为版本冻结资料,随审核通过生效到游戏行;0 为免费,付费走同一条发布/审核链路;展示资料编辑不能直接改价。
  • 数据:game_distribution_game 末尾追加 price_mud_points;新增 game_distribution_purchase((user_id, game_id) 唯一);泥点流水新增 game_purchase 来源类型。
  • 购买:Idempotency-Key 加唯一约束,扣费与购买记录同事务,余额不足失败关闭。
  • 播放鉴权:付费游戏不暴露公开 /games/<gameId>/;服务端签发绑定 gameId 加用户加短时效的播放会话(复用后台待审预览的 token 前缀模式);未购买直连返回 404,作者与管理员免购买。
  • 后台:审核详情与待审列表展示价格;审核员试玩待审版本不限次数、不扣泥点(沿用现有预览会话语义)。

前后对照

  • 之前:游戏 published 即对所有游客免费可玩,游戏与版本没有任何价格字段,钱包与游戏分发零关联。
  • 之后:作者可选买断制定价;未购买用户详情可见但不可游玩;后台审核可见价格并可无限试玩。

待确认

  1. 价格上限初值 1_000_000。
  2. 免购买游玩是否仅限作者本人与现有管理员身份。

验证(本切片)

  • npm run check:doc-index 通过(244 份 Markdown)
  • npm run check:encoding 通过(5268 文件)
  • git diff --check 通过

后续实现按实施计划顺序推进,并在本 PR 内持续推送。

## 关联 - Issue #634 ## 本次改动(切片 1:规范先行) - 主规范《平台入口与玩法链路》解除“不做交易”排除,新增「游戏买断制泥点付费与播放鉴权合同」章节。 - 新增里程碑与实施计划,冻结范围、不在范围内、验收标准与实现顺序。 ## 方案摘要 - 定价:价格作为版本冻结资料,随审核通过生效到游戏行;`0` 为免费,付费走同一条发布/审核链路;展示资料编辑不能直接改价。 - 数据:`game_distribution_game` 末尾追加 `price_mud_points`;新增 `game_distribution_purchase`(`(user_id, game_id)` 唯一);泥点流水新增 `game_purchase` 来源类型。 - 购买:`Idempotency-Key` 加唯一约束,扣费与购买记录同事务,余额不足失败关闭。 - 播放鉴权:付费游戏不暴露公开 `/games/<gameId>/`;服务端签发绑定 `gameId` 加用户加短时效的播放会话(复用后台待审预览的 token 前缀模式);未购买直连返回 404,作者与管理员免购买。 - 后台:审核详情与待审列表展示价格;审核员试玩待审版本不限次数、不扣泥点(沿用现有预览会话语义)。 ## 前后对照 - 之前:游戏 `published` 即对所有游客免费可玩,游戏与版本没有任何价格字段,钱包与游戏分发零关联。 - 之后:作者可选买断制定价;未购买用户详情可见但不可游玩;后台审核可见价格并可无限试玩。 ## 待确认 1. 价格上限初值 `1_000_000`。 2. 免购买游玩是否仅限作者本人与现有管理员身份。 ## 验证(本切片) - `npm run check:doc-index` 通过(244 份 Markdown) - `npm run check:encoding` 通过(5268 文件) - `git diff --check` 通过 后续实现按实施计划顺序推进,并在本 PR 内持续推送。
suzmii added the Kind/Feature label 2026-10-05 16:36:38 +08:00
suzmii self-assigned this 2026-10-05 16:36:38 +08:00
suzmii added 1 commit 2026-10-05 16:36:38 +08:00
新增游戏买断制泥点付费规范:主规范合同、里程碑与实施计划
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
7d24b0ac50
- 主规范《平台入口与玩法链路》解除“不做交易”排除,新增“游戏买断制泥点付费与播放鉴权合同”章节,明确定价冻结、买断所有权、播放鉴权、后台审核展示与幂等边界
- 新增【里程碑】游戏买断制泥点付费与播放鉴权-2026-10-05.md:范围、不在范围内、依赖、验收标准与证据要求
- 新增【实施计划】游戏买断制泥点付费与播放鉴权-2026-10-05.md:修改边界、实现顺序、验证命令与回滚点
- docs/README.md 登记新合同、里程碑与实施计划入口
suzmii added 1 commit 2026-10-05 16:47:34 +08:00
后台游戏审核页展示买断制泥点价格
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
4d7cd98dc6
- adminApiTypes:AdminGameDistributionReviewEntry 与 AdminGameDistributionGameDetail 新增 priceMudPoints: number 字段
- 审核页待审列表新增「价格」列:priceMudPoints 为 0、缺失或旧响应时显示「免费」,否则显示「N 泥点」
- 审核详情资料区新增「价格」项,与列表共用同一格式化口径,避免出现 NaN
- 同步审核表 nth-child 列宽定义:新增价格列 96px,提交时间与审核列顺移,窄屏媒体查询一并覆盖第 6 列
- 补齐 AdminGameDistributionReviewPage 测试:付费 30 泥点、免费与缺字段回落各一条,并在详情用例断言价格展示
suzmii added 1 commit 2026-10-05 16:49:57 +08:00
审核详情价格改用待审版本冻结价
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
d27e204f21
- applyFrozenGameMetadata 叠加冻结资料中的数值型 priceMudPoints:价格随版本冻结,审核员要看到待审版本将要生效的价格,而不是游戏行当前已公开价格
- 冻结资料缺 priceMudPoints 或为旧响应时保留 game.priceMudPoints,详情资料区仍渲染 detail.game.priceMudPoints
- 新增两条详情用例:游戏行 0 泥点但冻结 30 泥点时展示「30 泥点」;旧版本冻结资料无价格时回落「免费」且不出现 NaN
suzmii added 1 commit 2026-10-05 16:59:54 +08:00
网页端接入游戏买断制泥点付费
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
7b38ef9623
- packages/shared 游戏契约补买断价与购买态:GameDistributionGame 增 priceMudPoints/purchased,版本摘要 entryUrl 可为 null
- packages/shared 版本请求增 priceMudPoints,私有版本回读增同名字段;新增购买请求、购买记录、购买响应与播放会话 DTO
- packages/shared 钱包流水来源类型增 game_purchase,网页与共享账单文案增「购买游戏」标签
- 游戏分发客户端新增 purchaseGame(Idempotency-Key + 期望价格)与 createGamePlaySession
- 发布页新增「付费方式」免费/买断制表单,定价校验与服务端 0..=1000000 口径一致,价格随版本请求提交
- 详情页展示定价、付费未购买时主动作改为「N 泥点购买」,复用泥点确认弹窗展示余额,成功后刷新钱包并回到可玩状态,余额不足给出充值入口
- 游玩页付费作品先签发播放会话再挂载 iframe,免费作品链路不变;会话被拒时不挂载 iframe 也不上报游玩
- 发行入口守卫接受播放会话前缀路径,拒绝其它非发行路径
- 定向契约测试登记 4 组购买/播放会话 DTO,补发布定价、未购买不可玩、购买确认与播放会话挂载用例
suzmii added 1 commit 2026-10-05 17:03:06 +08:00
新增游戏买断制泥点付费领域规则、SpacetimeDB schema、钱包来源与 Rust DTO
Project CI / Backend tests (pull_request) Failing after 19s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 4m10s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 4m18s
Project CI / Repository checks (pull_request) Failing after 26s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 5m5s
Project CI / Frontend tests (pull_request) Successful in 3m48s
Project CI / AI game creator shell web tests (pull_request) Successful in 4m4s
Project CI / Native shell tests (pull_request) Failing after 8m31s
80fd5a9fea
- server-rs/crates/module-game-distribution:新增价格校验 normalize_game_price_mud_points(0..=1000000,0 为免费)、购买资格 resolve_game_purchase_decision、GamePurchaseSnapshot 与 game_purchase_id(前缀 gdpurchase_);GameSnapshot/GameVersionSnapshot 增加 price_mud_points,审核通过时随版本价格生效;新增购买服务方法与纯内存测试。
- server-rs/crates/spacetime-module/src/game_distribution.rs:GameDistributionGame 末尾追加 #[default(0u64)] price_mud_points;新增私有表 game_distribution_purchase(purchase_id 主键 + user_id/game_id btree 索引);新增 purchase_game_distribution_game_and_return(同事务校验可见性/价格 CAS/未购买并原子扣费、幂等收据 action=purchase)与只读 get_game_distribution_purchase_and_return;approve 在同一事务内把冻结资料 priceMudPoints 生效到游戏行;公开游戏投影带出 priceMudPoints。
- server-rs/crates/spacetime-module/src/runtime/active/profile.rs:新增 consume_profile_wallet_points_for_game_purchase 复用通用钱包写入路径(来源类型 game_purchase、流水 ID game_purchase:{userId}:{gameId}、metadata_json 记 kind/gameId);profile_wallet_balance 提升为 pub(crate)。
- server-rs/crates/spacetime-module/src/migration.rs:迁移表白名单登记 game_distribution_purchase。
- server-rs/crates/module-runtime/src/domain.rs:RuntimeProfileWalletLedgerSourceType 追加 GamePurchase 变体与 game_purchase 文案,未改 profile_wallet_ledger 表结构。
- server-rs/crates/shared-contracts:新增 PROFILE_WALLET_LEDGER_SOURCE_TYPE_GAME_PURCHASE;公开游戏摘要/详情增加 priceMudPoints 与 purchased;GameDistributionPrivateVersion 与 GameDistributionCreateVersionRequest 增加 priceMudPoints;新增 GameDistributionPurchaseRequest/GameDistributionPurchase/GameDistributionPurchaseResponse/GameDistributionPlaySessionResponse。
- server-rs/crates/spacetime-client:新增购买与回读 typed facade、GameDistributionPurchaseRecord 与 row mapper,GameDistributionGameRecord 带出 price_mud_points,钱包来源 mapper 支持 GamePurchase。
- docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md:登记 price_mud_points、game_distribution_purchase 与 game_purchase 流水来源。
- 重新生成 SpacetimeDB Rust 绑定(购买表/过程与价格字段)。
suzmii added 1 commit 2026-10-05 17:34:09 +08:00
实现游戏买断制泥点付费的 api-server HTTP 层、公开投影与播放鉴权
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
c732a3f851
- server-rs/crates/api-server/src/runtime_profile.rs:钱包流水来源类型补 GamePurchase 分支并导入 game_purchase 常量,修复 non-exhaustive match 编译错误。
- server-rs/crates/api-server/src/modules/game_distribution.rs:创建版本接受并校验 priceMudPoints(复用 normalize_game_price_mud_points,越界 400),随版本冻结资料一起落库。
- server-rs/crates/api-server/src/modules/game_distribution.rs:新增 POST /api/game-distribution/games/{gameId}/purchase(require_bearer_auth + 必填 Idempotency-Key,请求体 { expectedPriceMudPoints }),经 facade 原子扣费并返回 GameDistributionPurchaseResponse。
- server-rs/crates/api-server/src/modules/game_distribution.rs:购买错误映射——余额不足 400 INSUFFICIENT_MUD_POINTS、价格已变化 409、免费游戏 400、游戏未公开或不存在 404、缺幂等键 400。
- server-rs/crates/api-server/src/modules/game_distribution.rs:新增 POST /api/game-distribution/games/{gameId}/play-session;免费作品直接回既有公开入口,付费作品接受管理员令牌(沿用现有 admin 鉴权,免购买)与用户令牌,校验已购买 / 作者本人,未登录 401、未购买 403,签发绑定 gameId + userId 的 2 小时会话。
- server-rs/crates/api-server/src/modules/game_distribution.rs:新增 GET /api/game-distribution/play-sessions/{token}[/{assetPath}] 网关,无登录中间件、凭令牌读取,能解析平台刷新会话 Cookie 时 403,令牌过期 / 不存在、游戏下架或封禁、缺少有效公开版本一律 404,复用发行包读取与 CSP/MIME/nosniff/CORP 并全程 no-store。
- server-rs/crates/api-server/src/modules/game_distribution.rs:公开发行网关 releases/{gameId} 在当前公开版本 price_mud_points > 0 时返回 404,付费作品只能走播放会话路径。
- server-rs/crates/api-server/src/modules/game_distribution.rs:game_payload 发出 priceMudPoints 与 purchased 默认值;public_game_payload 按查看者解析 purchased,并对未购买且非作者 / 非管理员的付费作品把 currentVersion.entryUrl 置 null;公开详情接口可选鉴权读取当前用户,坏令牌按匿名、购买态读取失败按未购买。
- server-rs/crates/api-server/src/modules/game_distribution.rs:private_version_payload 发出该版本冻结价(历史缺字段为 0);作者 / 管理员版本详情的 game payload 按「冻结价优先、否则游戏行价」。
- server-rs/crates/api-server/src/state.rs:新增付费游戏播放会话的进程内存储与读写方法,读取时清理过期会话。
- server-rs/crates/api-server/src/admin.rs:抽出 resolve_admin_session,新增 try_authenticate_admin_from_headers 供同时接受管理员 / 用户令牌的端点复用,require_admin_auth 的校验与鉴权行为不变。
- server-rs/crates/spacetime-client/src/active.rs:补出 GameDistributionPurchaseRecordInput 的 re-export。
- docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md:登记购买、播放会话、公开详情入口隐藏与公开发行网关的 HTTP 契约。
- 补齐 api-server game_distribution 定向测试:价格校验 400、公开投影入口门禁(未购买 / 已购买 / 作者 / 管理员 / 免费)、冻结价回读、购买错误映射、购买路由鉴权与幂等键、播放会话网关 Cookie / 未知与过期令牌 / 路由挂载。
suzmii added 1 commit 2026-10-05 17:38:39 +08:00
修复游戏详情匿名读取导致已购买用户看不到游玩入口
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
532c847620
- gameDistributionClient.getGame 改用「可选鉴权」:已登录时带 Authorization: Bearer,未登录按匿名请求且不补票、不因 401 影响全局登录态
- 公开详情响应随查看者变化,读取加 cache: no-store,避免缓存串用他人购买态
- listGames 与评价等公开列表继续走跳过鉴权的匿名读取,行为不变
- 详情页在登录身份变化时重读公开详情,登录后不会再停在匿名的未购买结论
- 游玩页补充播放会话 401 的可读提示(未登录进付费作品)
- 补测试:详情已登录带 bearer / 未登录匿名且只发一次请求、列表不带 bearer、付费已购买显示立即玩并可进入游玩页、已购买且公开入口仍在时仍走播放会话、未登录直接进游玩页不挂载 iframe
suzmii added 1 commit 2026-10-05 17:47:42 +08:00
修复付费作品作者与管理员在详情页被误判为未购买,并去掉后台审核价格的客户端回退
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
054709db65
- GameDetailPage:动作判定改为「服务端是否授予可玩入口」优先(canPlay = currentVersion.entryUrl 存在或 purchased 为 true),作者本人与管理员虽 purchased=false 但入口已下发时显示「立即玩」而不是「N 泥点购买」

- GameDistributionPages.test:新增「付费未购买但服务端已下发公开入口(作者/管理员)时显示立即玩」用例,保留既有付费未购买显示购买、付费已购买显示立即玩用例

- AdminGameDistributionReviewPage:删除 applyFrozenGameMetadata 中 priceMudPoints 的客户端回退,价格单一口径沿用服务端 version_detail_payload 写好的 game.priceMudPoints

- AdminGameDistributionReviewPage.test:详情价格用例改为断言后端下发的版本冻结价(不再本地叠加冻结资料价格),历史版本无价格按免费的用例保持不变
suzmii added 1 commit 2026-10-05 17:53:58 +08:00
修复付费游戏播放会话在边缘转发 Cookie 导致 403 不可玩
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 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
d40df89e2c
- nginx:三份模板在通用 /api location 之前新增 location ^~ /api/game-distribution/play-sessions/,代理头与通用 /api 一致并清空 Cookie
- nginx:^~ 保证该前缀不被正则 location ~ ^/api(?:/|$) 抢先;只匹配带尾斜杠的前缀,创建会话端点继续走通用 /api 并保留 Cookie
- pingora:新增 RouteDecision::PlaySessionGateway,走 api 上游并同样套用 api 限流分组、大小上限与维护闸
- pingora:抽出 route_clears_cookie,发行入口 ReleaseGateway 与播放会话入口在上游代理阶段统一清空 Cookie
- pingora:classify_path 在通用 /api 分支之前命中播放会话前缀,并新增播放会话路由、Cookie 清除与保护等级用例
- 路由矩阵:新增 play_sessions_gateway 用例,声明清空 Cookie 与 protectionClass api
- 门禁 check:nginx-spa-routes:新增播放会话前缀断言——三份模板存在 ^~ location、块内清空 Cookie、代理头齐全且排在通用 /api location 之前
- 门禁 check:pingora-route-parity:新增断言——平台内容网关用例必须清空 Cookie 且不得复用通用 /api location,Rust 播放会话分支必须排在通用 /api 之前并由 route_clears_cookie 清理
- 门禁 check:pingora-gateway-smoke:新增真实网关用例——播放会话前缀转发到 api 上游并清空 Cookie、创建会话端点保留 Cookie
- dev:vite.config.ts 在 /api/game-distribution 规则之前新增同名前缀代理并清除 Cookie
- 文档:同步 Pingora 试点文档、本地开发运维文档、deploy/nginx/README 与 shared-memory 决策/踩坑记录
suzmii added 1 commit 2026-10-05 18:00:10 +08:00
修复游戏买断制付费的作者豁免、价格口径与契约可空性
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
d1bfe4c08b
- H1:购买事务在扣费前判定 `game.owner_user_id == user_id` 并失败关闭,领域 `resolve_game_purchase_decision` 新增 `is_owner` 标记返回 `OwnerCannotPurchase`(文案「作者本人无需购买」);api-server `map_purchase_error` 映射为 400 `GAME_PURCHASE_OWNER_EXEMPT`;购买路由按角色显式拒绝管理员令牌(403 `GAME_PURCHASE_ADMIN_NOT_ALLOWED`),管理员免购买不扣费。

- M1:`version_detail_payload` 的历史版本价格改为 `frozen_version_price_mud_points(version).unwrap_or(0)`,与待审列表及审核通过后生效的游戏行价格统一;同步把既有断言改为「历史版本缺 `priceMudPoints` 按免费(0)」。

- M2:`shared-contracts::GameDistributionVersionSummary.entry_url` 改为 `#[serde(default)] Option<String>`,与公开投影对未购买者下发的 `entryUrl: null` 一致。

- M3/L5:SpacetimeDB 购买事务改为复用 `module_game_distribution::resolve_game_purchase_decision`(新增 `game_distribution_visibility` 与 `resolve_game_distribution_purchase_decision` 包装),可购买性口径统一为「活动版本存在且 `status = published`」(复用 `public_game_distribution_version`),删除事务内联判定。

- 测试:内存领域作者自购拒绝;SpacetimeDB 购买判定五分支(免费 / 未公开 / 版本未公开 / 已拥有 / 作者自购)、事务源扫描(先判定后扣费、公开投影口径)、审核通过时历史版本价格生效为 0;api-server 管理员令牌 403、公开投影 `entryUrl: null` 可被 Rust DTO 解析、错误映射补作者豁免。

- `scripts/check-game-distribution-dto-parity.mjs` 增加可空性一致性比对(TS `| null` ⟺ Rust `Option`,允许 `?` 表示可省略),已用负例验证能拦住同类漂移。

- 同步更新 docs/【后端架构】server-rs与SpacetimeDB数据契约:登记作者豁免 / 管理员 403 / 可玩性口径 / 历史价格按 0;顺带修复 `module-game-distribution` 与 `spacetime-module` 既有未格式化行。
suzmii added 1 commit 2026-10-05 18:08:43 +08:00
新增游戏买断制泥点付费真实本地栈 E2E 检查脚本
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
1e13563a5e
- 新增 scripts/check-game-distribution-purchase-e2e.mjs:覆盖付费游戏发布与审核、未购买用户无可播放入口且直连 404、购买扣泥点与幂等重放、播放会话可玩与重复进入不扣费、免费游戏回归、余额不足失败无副作用
- 脚本端口只读 .app/dev-stack.json 的实际地址,E2E_START_STACK=1 时自行拉起并收束本地 dev 栈
- 追加播放会话网关取证:伪造令牌 404、带平台 refresh Cookie 直连 403、经 dev 代理按前缀清空 Cookie 后仍可播放
- package.json 增加 check:game-distribution-purchase-e2e 别名
suzmii added 1 commit 2026-10-05 18:12:17 +08:00
同步游戏买断制泥点付费与播放鉴权文档闭环
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
f7d3652878
主规范路由表新增 POST /games/{gameId}/purchase 行,写明幂等键、请求体与余额不足/作者自购/管理员令牌/价格变化错误口径
主规范路由表新增 POST /games/{gameId}/play-session 行,写明付费需登录、免费回公开入口与 { playUrl, expiresAt } 响应
主规范路由表新增 GET /game-distribution/play-sessions/{token}[/{assetPath}] 行,写明凭令牌、不读 Cookie、no-store、失效一律 404
主规范路由表更新 GET /games/{gameId} 行,补充 priceMudPoints 与 purchased,付费未购买时 currentVersion.entryUrl 为 null
主规范路由表更新发行网关行,当前公开版本 price_mud_points > 0 时统一 404
主规范合同 Status 由 proposed 改为 implemented(本地验证通过,待用户验收;未部署)
主规范收口价格上限 1_000_000 与免购买范围(作者本人 + 后台管理员令牌),删除正文悬置措辞并注明日期
主规范补充播放会话进程内会话、始终解析当前公开版本两条实现约束,记录后台待审预览前缀未清 Cookie 的同类风险
里程碑 Status 由 proposed 改为 implemented-awaiting-runtime-acceptance
docs/README.md 该合同/里程碑/实施计划条目描述改为已实现、本机真实栈 E2E 通过、待用户验收、未部署
suzmii added 1 commit 2026-10-05 18:17:22 +08:00
补齐买断制付费验收脚本的免购买、并发与边界用例
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
9662b56fa6
- 作者本人免购买:详情有入口但 purchased=false、自购 400 GAME_PURCHASE_OWNER_EXEMPT 且余额与流水不变、可直接创建播放会话
- 管理员免购买:管理员令牌可直接创建播放会话并可玩;购买路径记录真实返回码(当前为用户鉴权前置 401,403 GAME_PURCHASE_ADMIN_NOT_ALLOWED 需带 admin 角色的用户令牌)
- 真并发购买:同一未购买账号同时发两个不同幂等键,断言无 5xx、只扣一次、流水与购买记录各 1 条、详情 purchased=true
- 审核员试玩待审付费版本:admin preview session 入口与包内资源同包可用、可反复创建不限次、钱包与购买记录不变
- 定价边界:priceMudPoints=1000001 被 400 拒绝且不落版本,0 与 1000000 可通过
- 下架后失效:旧播放会话入口与包内资源 404、公开详情与新建会话都关闭
- 购买记录/版本条数用 spacetime sql 直读本地表交叉验证,CLI 不可用时降级为 WARN
- 脚本头部写明这是人工验收脚本、不在 CI 自动门禁内,且需要 E2E_ADMIN_USER/E2E_ADMIN_PASSWORD
suzmii added 1 commit 2026-10-05 18:19:43 +08:00
回写游戏买断制泥点付费真实栈验收证据并删除实施计划
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
c32097ab18
主规范合同章「验收标准与证据」表逐行回写真实本地栈 E2E 与单元/组件门禁结果,缺证据的覆盖边界如实标注未覆盖
主规范合同章补充说明运行时证据来自人工验收脚本 check:game-distribution-purchase-e2e(76 PASS / 0 FAIL / 1 WARN),不在 CI 自动门禁内
主规范合同章「实现约束与已知风险」新增 E2E 人工脚本不在 CI 覆盖、上线前私有桶 E2E_REQUIRE_PRIVATE_BUCKET 复核两条
主规范合同章表头删除已废弃的实施计划链接
里程碑验收复选框勾选已满足项并补证据说明,新增「未覆盖边界」保持未勾选(管理员安全下架会话失效、多账号/多游戏混合并发)并注明原因
里程碑「证据要求」补充已执行的人工验收结果与唯一 WARN 说明,Status 保持 implemented-awaiting-runtime-acceptance
删除 docs/project-memory/plans/【实施计划】游戏买断制泥点付费与播放鉴权-2026-10-05.md 并同步 docs/README.md 条目为已实现、E2E 76 PASS、待用户验收、未部署
suzmii added 1 commit 2026-10-05 18:34:51 +08:00
合并 origin/master(21c79f54a)到 feat/game-purchase
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Failing after 2m28s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Failing after 3m52s
Project CI / AI game creator shell Rust crates (pull_request) Failing after 5m23s
Project CI / Repository checks (pull_request) Failing after 56s
Project CI / Frontend tests (pull_request) Successful in 3m7s
Project CI / Native shell tests (pull_request) Failing after 7m11s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m47s
Project CI / Backend tests (pull_request) Successful in 9m28s
3145ef472e
- 冲突 docs/project-memory/shared-memory/decision-log.md:两侧都是在「# 决策记录」顶部新增条目,保留双份——本分支「2026-10-05 播放会话前缀在三处入口清空 Cookie」在前,master「2026-10-03 首页自动建项与 AI 项目命名解耦(Issue 599)」在后。
- 冲突 docs/project-memory/shared-memory/pitfalls.md:同为顶部新增条目,保留双份——本分支「2026-10-05 付费游戏播放会话前缀落在 /api/*」在前,master「2026-10-04 生成任务账本 taskType 维度与 v1 兼容」「2026-10-03 后台异步回填被进项目覆盖」在后。
- 其余逐处核对:master 本次只改了 apps/ai-game-creator-shell 与 docs(首页建项与 AI 命名解耦、AGC 标题栏项目标签、相关计划/记忆文档),与买断制付费功能文件无交集,全部吸收;master 未触碰 SpacetimeDB schema、钱包流水来源类型、游戏分发契约、边缘路由与生成绑定。
- 合并后回归核对:GameDistributionGame.price_mud_points 仍在结构体末尾且带默认值 0;migration 白名单仍含 game_distribution_purchase;RuntimeProfileWalletLedgerSourceType::GamePurchase(game_purchase)仍在。
suzmii added 1 commit 2026-10-05 19:54:35 +08:00
修复 AGC 壳版本请求缺少买断制价格字段导致的编译失败
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
8d8e74ca60
apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs:构造 GameDistributionCreateVersionRequest 时补 price_mud_points: 0;AGC 发布面板没有买断制定价入口,显式按免费提交,与升级前行为一致。
docs/project-memory/shared-memory/pitfalls.md:新增排障记录——shared-contracts 请求 DTO 加字段后必须同时编译 AGC 壳(独立 cargo workspace),#[serde(default)] 不能救 Rust 结构体字面量,并写明正确的本地检查命令与既有的 CI lane。
suzmii added 1 commit 2026-10-05 20:00:48 +08:00
修复买断制购买分支遗留的 Rust 格式偏差
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
b1e40d8b68
- server-rs/crates/pingora-gateway/src/main.rs:播放会话代理测试中的 assert! 断言按 rustfmt 收成单行。
- server-rs/crates/spacetime-client/src/active.rs:游戏分发购买相关 use 导入列表按 rustfmt 重新断行。
- server-rs/crates/spacetime-client/src/active/mapper.rs:游戏分发记录类型与映射函数的 pub use 导入列表按 rustfmt 重新断行(两处)。
- server-rs/crates/spacetime-client/src/active/mapper/game_distribution.rs:购买结果映射的 result.purchase.map 链式调用按 rustfmt 换行。
- 仅格式变动,不改变行为;改动前 npm run check:rustfmt 在上一提交 8d8e74ca6 报这 4 个文件,改动后转绿(master 96859666f 上同为绿)。
suzmii added 1 commit 2026-10-05 20:04:54 +08:00
文档:AGC 发布面板补齐买断制定价口径
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
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 / Repository checks (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
93de2a0d6c
- 主规范「入口与边界」改为网页与 AGC 两个发布入口一致支持定价,并写明给已上线作品发新版本的预填规则
- 主规范「定价与冻结」补充发布新版本必须预填并保持现有价格、不允许因默认免费静默改价,定价 UI 由 packages/shared 共享组件承载
- 主规范「契约与迁移」补记 AGC 版本请求 priceMudPoints 字段与 #[serde(default)] 仅作旧客户端兼容的口径
- 主规范验收表新增「定价(AGC 入口)」行,AGC 侧证据标注待补及其来源
- 里程碑范围补上 AGC 发布面板定价,删除「不在范围内」的 AGC 后续切片条目
- 里程碑验收标准新增 AGC 入口复选框(保持未勾选并注明等待实现证据)
- 排障记录 pitfalls 把 AGC 按免费提交改为历史口径 + 现行口径,保留「改请求 DTO 必须同时编译 AGC 壳」的排障经验
- docs/README.md 合同/里程碑条目同步为两个入口一致并标注 AGC 证据待补
suzmii added 1 commit 2026-10-05 20:21:19 +08:00
AGC 发布面板接入买断制定价并抽取共享定价组件
Project CI / Backend tests (pull_request) Failing after 18s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m0s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 6m47s
Project CI / Repository checks (pull_request) Failing after 56s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m28s
Project CI / Frontend tests (pull_request) Successful in 4m18s
Project CI / AI game creator shell web tests (pull_request) Successful in 4m7s
Project CI / Native shell tests (pull_request) Successful in 9m27s
ce737f4e8b
packages/shared/src/components:新增 PlatformGamePricingField(.tsx/.css/.test.tsx)与 platformGamePricingModel(上限常量与解析),并从 components/index.ts 导出;组件只表达付费方式单选、买断价输入、上限提示、禁用态与错误行,校验由使用方持有。
src/components/common/PlatformGamePricingField.tsx:按既有的 common 适配约定新增网页端再导出。
src/components/game-distribution/GamePublishPage.tsx:把内联的付费方式 fieldset 与买断价输入替换为共享组件,文案、校验、禁用逻辑与既有用例保持不变。
src/components/game-distribution/gamePublishMetadata.ts:买断价上限与解析改为复用共享常量与共享函数,不再各写一份。
apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx:首次发布与更新两条路径都加入共享定价组件,默认免费;提交前本地校验并透传价格。
apps/ai-game-creator-shell/src/services/gameDistributionPublish.ts:发布命令载荷透传 priceMudPoints,并做 0..=1000000 兜底校验;发布草稿回读新增 priceMudPoints 字段。
apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs:Tauri 命令新增可选 price_mud_points(旧前端不传按免费,向后兼容)并替换硬编码的 0;回读作者作品时解析 priceMudPoints,按「最近版本冻结价优先、回退游戏行当前价」预填,避免已上线付费作品被静默改成免费;发布摘要纳入价格,只改价格也会换幂等键,不会重放上一次收据。
apps/ai-game-creator-shell/tests:补充买断价透传、本地校验拦截、更新模式价格预填与免费兜底用例。
packages/shared/src/components/PlatformGamePricingField.test.tsx:覆盖免费/买断制切换、价格输入、上限提示、禁用态与错误行。
suzmii added 1 commit 2026-10-06 00:23:57 +08:00
文档:修正买断制里程碑的 AGC 定价证据、预填口径与 E2E 证据摘要
Project CI / Backend tests (pull_request) Failing after 13s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m13s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m27s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m25s
Project CI / Repository checks (pull_request) Failing after 36s
Project CI / Frontend tests (pull_request) Successful in 3m7s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m9s
Project CI / Native shell tests (pull_request) Successful in 9m1s
55a76ae78d
- 里程碑 AGC 定价验收项拆成「前端/Rust 已覆盖(附用例与单测路径与行号)」与「AGC 真实栈发布未覆盖」,checkbox 保持未勾选并写明未勾选原因
- 里程碑新增「验收证据摘要(真实栈 E2E)」:记录验收命令、脚本内 76 处 check 断言、最近一次人工运行 76 PASS / 0 FAIL / 1 WARN、唯一 WARN 为管理员令牌购买分支在现役登录链路不可达、WARN 为环境跳过语义、脚本是人工验收脚本且不在 CI,并删除易变的耗时秒数
- 里程碑把预填口径改为「最新版本冻结价优先(可能是 pending/rejected 版本价),缺失或为 0 才回落作品行当前价」,并注明 AGC 壳、网页与后端三处实现位置
- docs/README.md 买断制条目改为与事实一致:两个发布入口一致共用共享组件、AGC 定价前端用例与 Rust 预填单测已覆盖、AGC 真实栈发布未覆盖、真实栈 E2E 由人工脚本覆盖且不在 CI、待用户验收、未部署
suzmii added 2 commits 2026-10-06 01:33:26 +08:00
- 逐块合并 server-rs/crates/api-server/src/modules/game_distribution.rs:以 master 的 ReleaseAssetResponseInput / 5 参 release_asset_response_with_cache / ETag / 304 / gzip 结构为准,并在其上保留本分支的付费 404 守卫(price_mud_points > 0 仍在 release_package_bytes 读包之前返回);播放会话资源响应改用同一结构(etag None、cache_control no-store、沿用 accept-encoding 协商)。
- 合并 src/components/game-distribution/GamePlayPage.tsx:接入 master 的共享 PlatformGameLoadingSurface 与 PLATFORM_GAME_LOADING_TIMEOUT_MS,删除本地重复常量 GAME_PLAY_STARTUP_TIMEOUT_MS 与裸 div,买断制播放会话准备态改由共享加载面承载。
- 合并 src/components/game-distribution/GameDetailPage.tsx:同时保留 master 的 onOpenCreator 作者插槽与本分支的买断制购买弹窗、onOpenRecharge 充值入口。
- 合并 vite.config.ts:保留 master 的 /api/creators 代理,并保留 play-sessions 前缀清 Cookie 规则(顺序仍在通用 /api/game-distribution 之前)。
- 合并 deploy/nginx/README.md:保留 master 的 SPA allowlist 门禁口径(含 /creators、/creators/connections)与本分支的播放会话前缀章节;三份 nginx 模板的 ^~ play-sessions location 与清 Cookie 原样保留。
- 合并 apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx:保留 master 的加载面/超时用例与本分支的审核价格(冻结价优先、历史按 0)用例。
- 合并 src/components/game-distribution/GameDistributionPages.test.tsx:保留双方 mock,并新增「付费作品在会话签发期间显示共享加载面」用例。
- 合并 docs/【玩法创作】平台入口与玩法链路-2026-05-15.md(保留买断制合同并回填 master 的创作者主页与关注粉丝合同)、decision-log.md、pitfalls.md:追加双方条目,不改写任一侧正文。
- 保留 master 侧新增能力:release_asset_etag / if_none_match_matches / accepts_gzip_encoding / gzip_release_asset / release_asset_not_modified_response、SPA 加载面、创作者主页与关注粉丝(user_follow 表、creator 查询与 author_id 过滤)。
合并 origin/feat/game-purchase(55a76ae78)到 feat/game-purchase
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 6m38s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m40s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 3m41s
Project CI / Frontend tests (pull_request) Successful in 2m57s
Project CI / Backend tests (pull_request) Successful in 7m54s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m16s
Project CI / Repository checks (pull_request) Successful in 7m2s
Project CI / Native shell tests (pull_request) Successful in 9m35s
15bbb55949
- 远端 55a76ae78(文档:修正买断制里程碑的 AGC 定价证据、预填口径与 E2E 证据摘要;docs/README.md 索引行)的内容已随上一次合并进入本分支,本次合并只补齐拓扑关系,工作区内容零变化。
- 冲突解决口径:docs/README.md 由 ort 自动合并(远端索引行与 master 新增的创作者两行并存),无手工改动。
suzmii added 1 commit 2026-10-06 02:04:39 +08:00
文档:修正买断制合同章的定价预填口径、AGC 定价证据与后台落点
Project CI / AI game creator shell Rust lane 1/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
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
d18998b712
- docs/【玩法创作】平台入口与玩法链路-2026-05-15.md:583 入口预填规则由「预填当前价格与模式」改为「最新版本冻结价优先、缺失或为 0 时回落作品行当前价、都取不到按免费(0)」
- 同文件:595 定价与冻结段写明实际预填口径(最新版本冻结价可能是 pending_review/rejected 版本价)并补实现落点:AGC 壳 game_distribution_publish.rs:811、网页 GamePublishPage.tsx:164、后端 version_detail_payload
- 同文件:638 补 game_purchase 后台文案落点:api-server/src/admin.rs(wallet_ledger_source_type_to_string 与后台统计消耗白名单)已补齐,接口侧文案见 runtime_profile.rs,钱包消耗口径落在 spacetime-module runtime profile 统计
- 同文件:648 AGC 定价证据由「待补」改为已覆盖:发布面板 Vitest :333/342/802/819、请求层 Vitest :180/197、预填口径 Rust 单测 :1715;未覆盖项为 AGC 真实栈手点一次发布付费版本
- 同文件:658 运行时证据改为稳定口径:最近一次人工运行 76 PASS / 0 FAIL / 1 WARN 及唯一 WARN 说明,去掉 exit 0 / 耗时 / teardown 等易变且无留痕的表述
- 核对后确认无需改动:里程碑 front-matter Status 已是 implemented-awaiting-runtime-acceptance,AGC 定价 checkbox 保持未勾选且已列未覆盖原因;docs/README.md 由前一提交改好
suzmii added 1 commit 2026-10-06 02:18:45 +08:00
前端:修复买断制付费评审问题(flaky 用例、草稿恢复定价、契约口径、守卫与测试质量)
Project CI / Backend tests (pull_request) Has been cancelled
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 / 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
b4bf37671d
- GameDistributionPages.test.tsx:作者补查断言包进 waitFor,消除并发下的 0 调用 flaky
- GameDistributionPages.test.tsx:购买确认改为双击只发一次请求,按格式断言幂等键而非仅非空
- GameDistributionPages.test.tsx:新增 409「价格已变化」UI 分支与「旧响应缺价格且无入口」可操作说明用例
- GameDetailPage.tsx:禁用「立即玩」且无入口时补原因说明并用 aria-describedby 关联按钮
- GamePublishPage.tsx:草稿恢复按版本冻结价预填付费模式与价格,恢复提示写明「本次将沿用版本冻结价 N 泥点 / 免费」
- GamePublishPage.test.tsx:删除与共享组件重复的定价纯函数用例,改为草稿恢复定价(游戏行 999 / 版本冻结 240)网页独有用例
- gameDistributionGuards.ts:播放会话 token 段收窄为 [A-Za-z0-9-],拒绝 `..` 逃逸
- gameDistributionGuards.test.ts:补 UUID 令牌、`..`、超长 URL、控制字符与字母表外字符用例
- gameDistributionClient.test.ts:补 purchaseGame / createGamePlaySession 路径、幂等键、expectedPriceMudPoints 序列化与 401/403/409/余额不足错误码映射
- PlatformGamePricingField.tsx:错误行加 id,价格输入补 aria-invalid / aria-describedby
- PlatformGamePricingField.test.tsx:禁用态改用「买断制 + 禁用」以覆盖价格输入;新增错误关联 a11y 用例
- packages/shared 契约与 admin-web DTO:priceMudPoints / purchased 改为可选,与 Rust #[serde(default)] 口径对齐
- AdminGameDistributionReviewPage.tsx:详情价格按「版本冻结价优先、游戏行价兜底」取值(与服务端 version_detail_payload 同口径)
- AdminGameDistributionReviewPage.test.tsx:详情价格改发散值(game=999 / frozen=30,断言 30);价格列按列名定位、去掉全局 NaN 弱断言
- adminGameDistributionReview.css:价格列 96px nowrap 放宽为 120px 并允许换行,避免最大价被硬裁
- AGC 发布面板与发布服务:深相对路径改走 @genarrative/shared/components alias
- AGC 壳 game_distribution_publish.rs:修正预填价注释为「最近版本冻结价优先,可能仍待审/被拒」
suzmii added 1 commit 2026-10-06 02:22:10 +08:00
后台:撤回审核页价格客户端二次回退,改为哑渲染服务端字段
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
4d901eec65
- AdminGameDistributionReviewPage.tsx:删除新增的 resolveFrozenPriceMudPoints,恢复「价格不在这里二次叠加」的单一实现
  (服务端 version_detail_payload 已把版本冻结价优先、缺字段按 0 归一写进 game.priceMudPoints,M4 已明确客户端不得复刻该语义)
- AdminGameDistributionReviewPage.test.tsx:详情价格用例改为直接编码「客户端保持哑渲染」约束
  - 服务端下发 game.priceMudPoints = 30 → 详情显示「30 泥点」(列表侧由价格列用例覆盖同一字段)
  - 对抗用例:冻结资料故意传 frozenMetadata.priceMudPoints = 999,断言仍显示 30 且不出现「999 泥点」
suzmii added 1 commit 2026-10-06 02:26:50 +08:00
修复买断制付费评审问题:后台消耗统计、定价上限校验、付费入口自动化断言
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 6m43s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m17s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m20s
Project CI / Frontend tests (pull_request) Successful in 3m29s
Project CI / Backend tests (pull_request) Successful in 7m53s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m23s
Project CI / Native shell tests (pull_request) Successful in 7m48s
Project CI / Repository checks (pull_request) Successful in 5m36s
dcef9b62a8
- admin.rs:钱包流水来源映射补齐第 17 个变体(索引 16 → game_purchase),后台「消耗泥点」白名单新增 game_purchase,并抽出效果分类与单行累加函数
- profile.rs:消耗口径抽出 profile_wallet_ledger_source_counts_as_consumption,消费投影增量、历史花费重建、投影初始化三处一并纳入 GamePurchase
- module-game-distribution/errors.rs:价格上限文案改为插值 MAX_GAME_PRICE_MUD_POINTS,并新增文案断言
- 新增 scripts/check-game-distribution-price-limit-parity.mjs 并挂进 npm run lint(随 Repository checks 在 CI 生效):断言 TS / 服务端 Rust / AGC Rust 常量与网页侧别名四处同源
- api-server 发行网关抽出可注入 loader 的 serve_public_release_asset,付费 404 严格发生在读取包字节之前;播放会话准入抽出 resolve_paid_play_session_entitlement
- 新增 api-server 单测:付费发行路径 404 且零次读包字节、免费放行且读到包字节、未购买 403 与作者 / 已购买 / 管理员放行
- spacetime-module:把「先判定后扣费」从源码扫描改为 resolve_game_purchase_pre_charge_plan 纯函数断言,删除 include_str 源码切片用例
- E2E 脚本:平台同源断言缺配置时记 SKIP 并在报告末尾汇总 SKIP 清单,E2E_REQUIRE_PLATFORM_ORIGIN=1 时缺配置直接 FAIL,汇总行改为输出 PASS/FAIL/WARN/SKIP 计数
- 修正注释里不存在的路由 POST /api/game-distribution/play-sessions(真实为 POST /api/game-distribution/games/{gameId}/play-session)与「管理员令牌 403」口径(403 需带 admin 角色的用户令牌)
Author
Member

买断制付费(游戏买断制泥点付费)本轮评审结论与修复说明

本轮对买断制付费做了 3 路独立评审(后端 + 边缘网关 / 三端前端 + 契约 + 文档 / 客观门禁复跑),结论是:发现 1 个阻断、1 个高、若干中低;阻断已修(合并最新 origin/master),高与中按级修复,低风险项同批修掉。功能面——防绕过、购买原子性与幂等、播放会话、SpacetimeDB schema 与迁移、免费作品零回归——经评审 PASS。


1. 阻断(已修):分支基线过期,且 master 的发行链路优化会被静默回退

  • 分支落后 origin/master 22 个提交(自共同祖先起算),master 上已有「在线游玩加载体验」与「AGC 运行画面点选」两条已合并 PR 的产出。

  • 直接把 origin/master 合进本分支时实际产生 9 个冲突文件,其中 3 个是会被静默回退的功能代码:

    • src/components/game-distribution/GameDetailPage.tsx
    • src/components/game-distribution/GamePlayPage.tsx
    • vite.config.ts
    • 其余 6 个:src/components/game-distribution/GameDistributionPages.test.tsx、apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx、deploy/nginx/README.md、docs/project-memory/shared-memory/pitfalls.md、docs/project-memory/shared-memory/decision-log.md、docs/【玩法创作】平台入口与玩法链路-2026-05-15.md
  • 回退风险具体是:master 侧的发行网关 ETag / If-None-Match 304 / Content-Encoding: gzip(server-rs/crates/api-server/src/modules/game_distribution.rs)与共享加载面 PlatformGameLoadingSurface(packages/shared/src/components/PlatformGameLoadingSurface.tsx,网页游玩页与后台试玩共用)会在这三个文件上被旧版本覆盖。

  • 处理:已合并 origin/master,冲突逐块手工合并,保留 master 侧优化同时保留本分支的买断制改动。

  • 判定命令(CI 的 Resolve comparison base 门禁就是这一条):

    git merge-base --is-ancestor origin/master HEAD   # 退出码 0 = 已满足
    

2. 高(已修):game_purchase 未进入后台流水来源映射与统计白名单

  • 现象:game_purchase 这个钱包流水来源没有进入后台的来源映射与三处统计白名单,导致买断消耗在「Dashboard 消耗」「消费投影」「历史花费」里整类丢失——买断制上线后后台看不到这部分消耗。
  • 修复落点:
    • server-rs/crates/api-server/src/admin.rs:流水来源索引映射补上 16 => "game_purchase"(含 GamePurchase 字符串形态),把消费判定收口到单一助手 resolve_admin_dashboard_wallet_ledger_effect,统计累加收口到 accumulate_admin_dashboard_wallet_ledger_row。
    • server-rs/crates/spacetime-module/src/runtime/active/profile.rs:消费判定谓词统一为 profile_wallet_ledger_source_counts_as_consumption,历史花费汇总 sum_historical_profile_wallet_consumed_points 与它同源。
    • 买断制没有退还来源,因此正向流水不计入消耗(与冲正口径区分开),后台不再出现「负数当消耗」的错配。
  • 配套单测(名字即断言意图):
    • dashboard_wallet_ledger_effect_counts_game_purchase_as_consumption
    • dashboard_wallet_stats_accumulate_game_purchase_consumption
    • wallet_consumption_source_predicate_includes_game_purchase

3. 中(已修)

a. 网页发布页草稿恢复不预填价格
对已付费作品恢复草稿时会显示「免费」,存在静默改价风险。现改为按版本冻结价预填(该版本可能仍在 pending_review / rejected,因此预填值未必已生效),仅在冻结价缺失或为 0 时才回落作品行当前价,两条来源都取不到才按免费,并在 UI 上写明「沿用当前价格」。落点:src/components/game-distribution/GamePublishPage.tsx、文档 docs/【玩法创作】平台入口与玩法链路-2026-05-15.md;AGC 壳侧同口径实现为 resolve_publication_prefill_price(apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs)。

b. 价格上限 1_000_000 四处硬编码
上限同时存在于共享 TS 常量、服务端 Rust 常量、AGC 壳 Rust 常量与网页侧再导出别名,任一处单独改动都会造成「前端允许提交、后端拒绝」的口径分叉,且只在真实发布时才暴露。新增门禁脚本 scripts/check-game-distribution-price-limit-parity.mjs(npm run check:game-distribution-price-limit-parity),断言「3 处常量声明同源(共享 TS 常量、服务端 Rust 常量、AGC 壳 Rust 常量取同一个上限值)+ 网页侧别名必须是引用共享常量而不是另写字面量」,合计覆盖 4 个落点;已挂进 npm run lint 链,因此 Repository checks 会经 scripts/check-repository-ci.sh 跑到。上限错误文案也改为常量插值,由 cargo test -p module-game-distribution 断言。

c. 付费 404 与未购买 403 只有人工 E2E 证据
把公开发行包读取与播放会话鉴权入口抽成可注入依赖的形式,补 3 组单测(在 server-rs/crates/api-server/src/modules/game_distribution.rs):

  • paid_game_public_release_path_is_rejected_without_reading_package_bytes:付费作品走公开发行包直读路径必须被拒,且不得先读包体。
  • free_game_public_release_path_serves_the_package_asset:免费作品走同一条路径仍能正常读到包内资源(正向对照,证明上面那条不是「一律拒绝」)。
  • paid_play_session_entitlement_rejects_unpurchased_and_grants_owner_buyer_admin:未购买拒绝、作者/购买者/管理员放行。
    并做了变异验证:把付费判定短路后,对应用例会转红(证明断言不是恒真)。

d. E2E 平台同源断言静默跳过
scripts/check-game-distribution-purchase-e2e.mjs 缺主站地址时原先静默返回,会把「没验过」当成通过。现改为显式记 SKIP 并在报告末尾汇总(汇总行同时给出 PASS / FAIL / WARN / SKIP 四项计数与 SKIP 清单);设置 E2E_REQUIRE_PLATFORM_ORIGIN=1 时缺配置直接 FAIL,供发布前验收与 CI 化演练使用。

e. 前端契约口径统一
packages/shared/src/contracts/gameDistribution.ts 的价格/播放会话字段「可选 / 必需」口径与实现对齐,消除调用方各自兜底的写法。

f. 弱测试改写与契约用例补齐
改写若干条恒真或断言过弱的用例(GamePublishPage.test.tsx、GameDistributionPages.test.tsx、AdminGameDistributionReviewPage.test.tsx 等),并补上:

  • gameDistributionClient 契约用例(src/services/gameDistributionClient.test.ts):购买请求带路径、幂等键与 expectedPriceMudPoints;播放会话请求路径固定、playUrl / expiresAt 原样解析;本地标识缺失时失败关闭且不发出请求;余额不足映射成 400 + INSUFFICIENT_MUD_POINTS;播放会话被拒时同样抛 ApiClientError。
  • 409 改价 UI:服务端返回 409 价格已变化时提示已刷新并保留购买入口。
  • 双击只扣一次:purchaseGameMock 在同一购买请求窗口内只被调用一次。
  • 守卫用例(src/components/game-distribution/gameDistributionGuards.test.ts):.. 路径穿越、超长、控制字符。

4. 低(已修)

  • 注释/文档澄清(不是行为改动):location ^~ /api/game-distribution/play-sessions/ 本身就已覆盖 /play-sessions/<token> 与 /play-sessions/<token>/… 两种形态(都在该前缀内,都会被清 Cookie),这部分行为不变。改的是注释里引用了一条不存在的路由:三份 nginx 模板、deploy/nginx/README.md 与 vite.config.ts 原先写「创建会话是 POST /api/game-distribution/play-sessions」,实际 api-server 在该前缀下只注册 GET 入口与包内资源,创建会话是 POST /api/game-distribution/games/<gameId>/play-session;现已改为真实路径,scripts/check-nginx-spa-routes.mjs 同步。
  • play-session 守卫的 token 段字符集收窄为 [A-Za-z0-9-](令牌是 UUID v4):原先放开的 . 与 / 会让 …/play-sessions/../../x 被 new URL 归一成同源其它路径;正则同时显式允许尾部斜杠。
  • 共享定价字段的 a11y、后台价格列宽(apps/admin-web/src/pages/adminGameDistributionReview.css)已调整。
  • 后台审核页价格改为哑渲染服务端字段,去掉客户端二次回退(AdminGameDistributionReviewPage.tsx)。
  • AGC 壳的深相对路径已改为 alias:apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx 与 apps/ai-game-creator-shell/src/services/gameDistributionPublish.ts 都改为从 @genarrative/shared/components 导入共享定价组件与常量。
  • AGC 壳预填注释口径已更新(apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs):改为「取最新版本冻结价 → 该价缺失或为 0 时回落游戏行当前生效价 → 两条都取不到才按 0(免费)」,与网页侧同一口径。

5. 明确 PASS 的项(功能面)

  • 防绕过:评审逐条枚举了全部读包 / 换签入口(含公开发行网关、播放会话网关、后台预览、资产换签与 object key 白名单、进程内包缓存等),唯一字节原语的三处调用点全部先鉴权后读字节,付费作品拿不到包体。
  • 播放会话:UUIDv4(CSPRNG)会话 ID、2 小时 TTL、绑定 game + user、过期回收、不跨作品复用。
  • 购买事务:单事务内完成;确定性主键 + 钱包账本幂等 + 幂等收据三重防重复,CAS 校验发生在扣费之前。
  • schema 与迁移:price_mud_points 在表末尾追加并带默认值 #[default(0u64)],新表唯一性约束成立,迁移白名单与生成绑定同步。
  • 免费作品零回归:0 表示免费,与付费共用同一条发布/审核链路,免费链路行为不变。
  • DDD 红线:api-server 不越过 facade 直连表,spacetime-module 侧领域规则收口。
  • 日志:不含 token / 流水 ID 等敏感值。

6. 已知风险 / 未覆盖(如实列出)

  1. 管理员令牌购买分支在现役登录链路不可达:现役登录先判 401,该分支属纵深防御;已有单测覆盖,但没有端到端可达路径。
  2. 向后兼容疑点(待产品决策):老客户端给已付费作品发新版本、请求不带 priceMudPoints 时,服务端 #[serde(default)] 会落 0(即变成免费)。AGC 壳侧有预填兜底,服务端尚未兜底。是否需要服务端拒绝/沿用旧价,需要产品口径。
  3. AGC 真实栈未手点验证:AGC 壳内的发布预填链路只走了单测与门禁,没有在真实栈手工点一次发布。
  4. E2E 是人工脚本、不在 CI 自动门禁内:npm run check:game-distribution-purchase-e2e 需要真实 SpacetimeDB / api-server / OSS 与 E2E_ADMIN_USER / E2E_ADMIN_PASSWORD。CI 绿不代表付费链路的运行时行为已验证;上线前必须人工运行,并用 E2E_REQUIRE_PRIVATE_BUCKET=1 复核生产桶不允许匿名直取发行包。
  5. 管理员安全下架后的会话失效未单独跑:suspend 之后既有播放会话的失效未做单独立项验证。

7. 验证命令清单(供复核)

以下命令均已在 package.json / Cargo.toml / workflow 中核对存在,或在本次取证中实跑过。

# 基线门禁(等价于 CI 的 Resolve comparison base)
git merge-base --is-ancestor origin/master HEAD

# 仓库级门禁:lint 链内含 check:game-distribution-price-limit-parity、
# check:game-distribution-dto-parity、check:spacetime-schema、check:generated-bindings、check:rustfmt 等
npm run lint
bash scripts/check-repository-ci.sh     # 即 npm run check:repository-ci:lint + appSurface 用例 + build + git diff --check

# 前端
npm run test:ci:frontend
npx vitest run src/services/gameDistributionClient.test.ts \
  src/components/game-distribution/GameDistributionPages.test.tsx \
  src/components/game-distribution/gameDistributionGuards.test.ts \
  packages/shared/src/components/PlatformGamePricingField.test.tsx \
  apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx
npm run check:native-shells:agc-web

# 后端 / SpacetimeDB
cargo test --locked --workspace --exclude spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml
cargo test --locked -p spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml
cargo test -p module-game-distribution
cargo test -p api-server game_distribution

# 人工 E2E(不在 CI 自动门禁内,发布前必跑)
npm run check:game-distribution-purchase-e2e            # 缺主站时平台同源断言记 SKIP 并在末尾汇总
E2E_REQUIRE_PLATFORM_ORIGIN=1 npm run check:game-distribution-purchase-e2e
E2E_REQUIRE_PRIVATE_BUCKET=1 npm run check:game-distribution-media-e2e

8. 本轮关键提交

  • 合并 origin/master(22 个提交):959ed4fd5
  • 同步远端 PR 分支:15bbb5594
  • 文档:d18998b71
  • 前端:b4bf37671、4d901eec6
  • 后端与门禁:dcef9b62a
# 买断制付费(游戏买断制泥点付费)本轮评审结论与修复说明 本轮对买断制付费做了 **3 路独立评审**(后端 + 边缘网关 / 三端前端 + 契约 + 文档 / 客观门禁复跑),结论是:发现 **1 个阻断、1 个高、若干中低**;**阻断已修**(合并最新 `origin/master`),高与中按级修复,低风险项同批修掉。功能面——**防绕过、购买原子性与幂等、播放会话、SpacetimeDB schema 与迁移、免费作品零回归——经评审 PASS**。 --- ## 1. 阻断(已修):分支基线过期,且 master 的发行链路优化会被静默回退 - 分支落后 `origin/master` **22 个提交**(自共同祖先起算),`master` 上已有「在线游玩加载体验」与「AGC 运行画面点选」两条已合并 PR 的产出。 - 直接把 `origin/master` 合进本分支时**实际产生 9 个冲突文件**,其中 3 个是会被静默回退的功能代码: - `src/components/game-distribution/GameDetailPage.tsx` - `src/components/game-distribution/GamePlayPage.tsx` - `vite.config.ts` - 其余 6 个:`src/components/game-distribution/GameDistributionPages.test.tsx`、`apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx`、`deploy/nginx/README.md`、`docs/project-memory/shared-memory/pitfalls.md`、`docs/project-memory/shared-memory/decision-log.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md` - 回退风险具体是:`master` 侧的发行网关 **ETag / `If-None-Match` 304 / `Content-Encoding: gzip`**(`server-rs/crates/api-server/src/modules/game_distribution.rs`)与共享加载面 **`PlatformGameLoadingSurface`**(`packages/shared/src/components/PlatformGameLoadingSurface.tsx`,网页游玩页与后台试玩共用)会在这三个文件上被旧版本覆盖。 - 处理:已合并 `origin/master`,冲突**逐块手工合并**,保留 master 侧优化同时保留本分支的买断制改动。 - 判定命令(CI 的 `Resolve comparison base` 门禁就是这一条): ```bash git merge-base --is-ancestor origin/master HEAD # 退出码 0 = 已满足 ``` --- ## 2. 高(已修):`game_purchase` 未进入后台流水来源映射与统计白名单 - 现象:`game_purchase` 这个钱包流水来源没有进入后台的**来源映射**与**三处统计白名单**,导致买断消耗在「Dashboard 消耗」「消费投影」「历史花费」里**整类丢失**——买断制上线后后台看不到这部分消耗。 - 修复落点: - `server-rs/crates/api-server/src/admin.rs`:流水来源索引映射补上 `16 => "game_purchase"`(含 `GamePurchase` 字符串形态),把消费判定收口到单一助手 `resolve_admin_dashboard_wallet_ledger_effect`,统计累加收口到 `accumulate_admin_dashboard_wallet_ledger_row`。 - `server-rs/crates/spacetime-module/src/runtime/active/profile.rs`:消费判定谓词统一为 `profile_wallet_ledger_source_counts_as_consumption`,历史花费汇总 `sum_historical_profile_wallet_consumed_points` 与它同源。 - 买断制没有退还来源,因此**正向流水不计入**消耗(与冲正口径区分开),后台不再出现「负数当消耗」的错配。 - 配套单测(名字即断言意图): - `dashboard_wallet_ledger_effect_counts_game_purchase_as_consumption` - `dashboard_wallet_stats_accumulate_game_purchase_consumption` - `wallet_consumption_source_predicate_includes_game_purchase` --- ## 3. 中(已修) **a. 网页发布页草稿恢复不预填价格** 对已付费作品恢复草稿时会显示「免费」,存在**静默改价**风险。现改为按**版本冻结价**预填(该版本可能仍在 `pending_review` / `rejected`,因此预填值未必已生效),仅在冻结价缺失或为 `0` 时才回落作品行当前价,两条来源都取不到才按免费,并在 UI 上写明「沿用当前价格」。落点:`src/components/game-distribution/GamePublishPage.tsx`、文档 `docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`;AGC 壳侧同口径实现为 `resolve_publication_prefill_price`(`apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs`)。 **b. 价格上限 `1_000_000` 四处硬编码** 上限同时存在于共享 TS 常量、服务端 Rust 常量、AGC 壳 Rust 常量与网页侧再导出别名,任一处单独改动都会造成「前端允许提交、后端拒绝」的口径分叉,且只在真实发布时才暴露。新增门禁脚本 `scripts/check-game-distribution-price-limit-parity.mjs`(`npm run check:game-distribution-price-limit-parity`),断言「3 处常量声明同源(共享 TS 常量、服务端 Rust 常量、AGC 壳 Rust 常量取同一个上限值)+ 网页侧别名必须是引用共享常量而不是另写字面量」,合计覆盖 4 个落点;已挂进 `npm run lint` 链,因此 `Repository checks` 会经 `scripts/check-repository-ci.sh` 跑到。上限错误文案也改为常量插值,由 `cargo test -p module-game-distribution` 断言。 **c. 付费 404 与未购买 403 只有人工 E2E 证据** 把公开发行包读取与播放会话鉴权入口抽成可注入依赖的形式,补 3 组单测(在 `server-rs/crates/api-server/src/modules/game_distribution.rs`): - `paid_game_public_release_path_is_rejected_without_reading_package_bytes`:付费作品走公开发行包直读路径必须被拒,且**不得先读包体**。 - `free_game_public_release_path_serves_the_package_asset`:免费作品走同一条路径仍能正常读到包内资源(正向对照,证明上面那条不是「一律拒绝」)。 - `paid_play_session_entitlement_rejects_unpurchased_and_grants_owner_buyer_admin`:未购买拒绝、作者/购买者/管理员放行。 并做了**变异验证**:把付费判定短路后,对应用例会转红(证明断言不是恒真)。 **d. E2E 平台同源断言静默跳过** `scripts/check-game-distribution-purchase-e2e.mjs` 缺主站地址时原先静默返回,会把「没验过」当成通过。现改为显式记 `SKIP` 并在报告末尾汇总(汇总行同时给出 PASS / FAIL / WARN / SKIP 四项计数与 SKIP 清单);设置 `E2E_REQUIRE_PLATFORM_ORIGIN=1` 时缺配置直接 `FAIL`,供发布前验收与 CI 化演练使用。 **e. 前端契约口径统一** `packages/shared/src/contracts/gameDistribution.ts` 的价格/播放会话字段「可选 / 必需」口径与实现对齐,消除调用方各自兜底的写法。 **f. 弱测试改写与契约用例补齐** 改写若干条恒真或断言过弱的用例(`GamePublishPage.test.tsx`、`GameDistributionPages.test.tsx`、`AdminGameDistributionReviewPage.test.tsx` 等),并补上: - `gameDistributionClient` 契约用例(`src/services/gameDistributionClient.test.ts`):购买请求带路径、幂等键与 `expectedPriceMudPoints`;播放会话请求路径固定、`playUrl` / `expiresAt` 原样解析;本地标识缺失时**失败关闭且不发出请求**;余额不足映射成 `400` + `INSUFFICIENT_MUD_POINTS`;播放会话被拒时同样抛 `ApiClientError`。 - 409 改价 UI:`服务端返回 409 价格已变化时提示已刷新并保留购买入口`。 - 双击只扣一次:`purchaseGameMock` 在同一购买请求窗口内只被调用一次。 - 守卫用例(`src/components/game-distribution/gameDistributionGuards.test.ts`):`..` 路径穿越、超长、控制字符。 --- ## 4. 低(已修) - **注释/文档澄清(不是行为改动)**:`location ^~ /api/game-distribution/play-sessions/` 本身就已覆盖 `/play-sessions/<token>` 与 `/play-sessions/<token>/…` 两种形态(都在该前缀内,都会被清 Cookie),这部分行为不变。改的是**注释里引用了一条不存在的路由**:三份 nginx 模板、`deploy/nginx/README.md` 与 `vite.config.ts` 原先写「创建会话是 `POST /api/game-distribution/play-sessions`」,实际 api-server 在该前缀下只注册 GET 入口与包内资源,创建会话是 `POST /api/game-distribution/games/<gameId>/play-session`;现已改为真实路径,`scripts/check-nginx-spa-routes.mjs` 同步。 - play-session 守卫的 `token` 段字符集收窄为 `[A-Za-z0-9-]`(令牌是 UUID v4):原先放开的 `.` 与 `/` 会让 `…/play-sessions/../../x` 被 `new URL` 归一成同源其它路径;正则同时显式允许尾部斜杠。 - 共享定价字段的 a11y、后台价格列宽(`apps/admin-web/src/pages/adminGameDistributionReview.css`)已调整。 - 后台审核页价格改为**哑渲染服务端字段**,去掉客户端二次回退(`AdminGameDistributionReviewPage.tsx`)。 - AGC 壳的深相对路径已改为 alias:`apps/ai-game-creator-shell/src/components/game-distribution/GameDistributionPublishPanel.tsx` 与 `apps/ai-game-creator-shell/src/services/gameDistributionPublish.ts` 都改为从 `@genarrative/shared/components` 导入共享定价组件与常量。 - AGC 壳预填注释口径已更新(`apps/ai-game-creator-shell/src-tauri/src/game_distribution_publish.rs`):改为「取最新版本冻结价 → 该价缺失或为 `0` 时回落游戏行当前生效价 → 两条都取不到才按 `0`(免费)」,与网页侧同一口径。 --- ## 5. 明确 PASS 的项(功能面) - **防绕过**:评审逐条枚举了全部读包 / 换签入口(含公开发行网关、播放会话网关、后台预览、资产换签与 object key 白名单、进程内包缓存等),唯一字节原语的三处调用点全部**先鉴权后读字节**,付费作品拿不到包体。 - **播放会话**:`UUIDv4`(CSPRNG)会话 ID、2 小时 TTL、绑定 `game + user`、过期回收、**不跨作品复用**。 - **购买事务**:单事务内完成;确定性主键 + 钱包账本幂等 + 幂等收据**三重防重复**,CAS 校验发生在扣费之前。 - **schema 与迁移**:`price_mud_points` 在表末尾追加并带默认值 `#[default(0u64)]`,新表唯一性约束成立,迁移白名单与生成绑定同步。 - **免费作品零回归**:`0` 表示免费,与付费共用同一条发布/审核链路,免费链路行为不变。 - **DDD 红线**:`api-server` 不越过 facade 直连表,`spacetime-module` 侧领域规则收口。 - **日志**:不含 token / 流水 ID 等敏感值。 --- ## 6. 已知风险 / 未覆盖(如实列出) 1. **管理员令牌购买分支在现役登录链路不可达**:现役登录先判 `401`,该分支属纵深防御;已有单测覆盖,但**没有端到端可达路径**。 2. **向后兼容疑点(待产品决策)**:老客户端给已付费作品发新版本、请求不带 `priceMudPoints` 时,服务端 `#[serde(default)]` 会落 `0`(即变成免费)。AGC 壳侧有预填兜底,**服务端尚未兜底**。是否需要服务端拒绝/沿用旧价,需要产品口径。 3. **AGC 真实栈未手点验证**:AGC 壳内的发布预填链路只走了单测与门禁,**没有在真实栈手工点一次发布**。 4. **E2E 是人工脚本、不在 CI 自动门禁内**:`npm run check:game-distribution-purchase-e2e` 需要真实 SpacetimeDB / `api-server` / OSS 与 `E2E_ADMIN_USER` / `E2E_ADMIN_PASSWORD`。**CI 绿不代表付费链路的运行时行为已验证**;上线前必须人工运行,并用 `E2E_REQUIRE_PRIVATE_BUCKET=1` 复核生产桶不允许匿名直取发行包。 5. **管理员安全下架后的会话失效未单独跑**:`suspend` 之后既有播放会话的失效未做单独立项验证。 --- ## 7. 验证命令清单(供复核) 以下命令均已在 `package.json` / `Cargo.toml` / workflow 中核对存在,或在本次取证中实跑过。 ```bash # 基线门禁(等价于 CI 的 Resolve comparison base) git merge-base --is-ancestor origin/master HEAD # 仓库级门禁:lint 链内含 check:game-distribution-price-limit-parity、 # check:game-distribution-dto-parity、check:spacetime-schema、check:generated-bindings、check:rustfmt 等 npm run lint bash scripts/check-repository-ci.sh # 即 npm run check:repository-ci:lint + appSurface 用例 + build + git diff --check # 前端 npm run test:ci:frontend npx vitest run src/services/gameDistributionClient.test.ts \ src/components/game-distribution/GameDistributionPages.test.tsx \ src/components/game-distribution/gameDistributionGuards.test.ts \ packages/shared/src/components/PlatformGamePricingField.test.tsx \ apps/admin-web/src/pages/AdminGameDistributionReviewPage.test.tsx npm run check:native-shells:agc-web # 后端 / SpacetimeDB cargo test --locked --workspace --exclude spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml cargo test --locked -p spacetime-module --no-fail-fast --manifest-path server-rs/Cargo.toml cargo test -p module-game-distribution cargo test -p api-server game_distribution # 人工 E2E(不在 CI 自动门禁内,发布前必跑) npm run check:game-distribution-purchase-e2e # 缺主站时平台同源断言记 SKIP 并在末尾汇总 E2E_REQUIRE_PLATFORM_ORIGIN=1 npm run check:game-distribution-purchase-e2e E2E_REQUIRE_PRIVATE_BUCKET=1 npm run check:game-distribution-media-e2e ``` --- ## 8. 本轮关键提交 - 合并 `origin/master`(22 个提交):`959ed4fd5` - 同步远端 PR 分支:`15bbb5594` - 文档:`d18998b71` - 前端:`b4bf37671`、`4d901eec6` - 后端与门禁:`dcef9b62a`
suzmii added 1 commit 2026-10-06 11:09:41 +08:00
美化买断制定价字段:分组卡片、胶囊付费方式、带单位的价格输入
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m12s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m18s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m6s
Project CI / Repository checks (pull_request) Failing after 34s
Project CI / Frontend tests (pull_request) Successful in 3m26s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m34s
Project CI / Native shell tests (pull_request) Successful in 8m59s
7223c05e28
- packages/shared:`PlatformGamePricingField` 改用 `PlatformSubpanel` 承载分组外壳(描边卡片 + 14px 黑体标题 `titleVariant="strong"`),与网页 `game-publish-fieldset`、AGC `__publication` 同族观感。
- packages/shared:付费方式从裸原生 radio 改为「原生 radio(屏外裁切,保留单选组语义/方向键/.checked/label 关联)+ 共享 chip 外观 `getPlatformCategoryChipClassName`」(未选=浅底描边,选中=实心品牌填充反白)。
- packages/shared:买断价改用共享 `PlatformTextField`(size=md / density=compact),标签与输入分离(label htmlFor),输入右对齐等宽数字、右侧留出「泥点」单位位;错误态红边红底(对网页宿主 `platform-subpanel` 输入框 remap 的 `!important` 同权重例外)、禁用态降透明度。
- packages/shared:CSS 补齐分组间距与层次、44px 触控胶囊、窄屏(≤480px)两枚等宽胶囊、`focus-visible` 焦点环;文案、props、校验与提交逻辑一律未改。
- 未触碰 AGC 发布面板「项目版本」一组及其包装/样式,也未改 `GameDistributionPublishPanel.tsx`。
suzmii added 1 commit 2026-10-06 12:02:17 +08:00
项目版本改用平台派发的下一个版本号:AGC 只读展示 + 提交同一个值
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m9s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m23s
Project CI / Repository checks (pull_request) Failing after 33s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m39s
Project CI / Frontend tests (pull_request) Successful in 3m20s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m51s
Project CI / Native shell tests (pull_request) Successful in 9m1s
31a3d326b3
- packages/shared:定价字段提示句补 `id` 并挂进 `aria-describedby`(与错误行并列),恢复「最多 1000000 泥点」的程序化关联;补两条断言(有错/无误时描述里都含上限)。
- packages/shared:分组标题改用 `PlatformFieldLabel variant="field"`(12px/700/弱化色),与网页「支持设备 / 封面与截图」的 legend 同号;价格输入中和原生 number spinner(`appearance: textfield` + 隐藏 `::-webkit-inner-spin-button`),不再压住「泥点」单位,`inputMode="numeric"` 保留。
- packages/shared:非法态去掉组件级 `!important`,改由网页宿主 remap 的 `[aria-invalid='true']` 分支给出错误色(`src/index.css`,排在 `:focus` 之后),组件侧保留正常权重同名声明供 AGC 生效。
- AGC 壳 Rust(`game_distribution_publish.rs`):作者作品条目解析 `versions[]`;新增 `resolve_owner_next_version_number`(已有版本最大 `versionNumber + 1`,对齐平台 `resolve_version_number(None)`);发布状态回读下发 `nextVersionNumber`(首次发布 `1`;刷新成功的更新 `max+1`;`unavailable` 与「有绑定但没刷新成功」为 `None`);补单测两条。
- AGC 服务层(`gameDistributionPublish.ts`):`versionNumber` 改为必填,删除按清单 `versions[]` 条数的回落;回读结果类型补 `nextVersionNumber`。
- AGC 发布面板:删除本地派生的 `projectVersion`,`项目版本` 显示并提交同一个平台版本号;字段改为只读 + 禁用观感(未知时显示 `—` 占位);平台版本未知时提交按钮禁用并提示重试;「项目版本」不再可从 AGC 手填。
- AGC 样式:新增 `__version-field` / `__version-value`(与相邻字段同形 + 灰底只读观感),并更新版本标签注释为平台口径。
- 测试:AGC 面板三条新口径断言(显示值==提交值==平台下一个版本号;本地迭代计数与遗留字段不参与;平台数据不可得时显示占位/提交禁用/不调用发布)、非 Tauri 宿主用例改为断言禁用、服务层用例改为「只认调用方传入值 + 缺省失败关闭」、live 用例补显式版本号;陈旧注释同步(`game_publish_attempt.rs`、`project/manifest.rs`、`resourceProjectionModel.ts`)。
suzmii added 1 commit 2026-10-06 13:15:35 +08:00
合并 origin/master(52c83cc2a,248 个提交)到 feat/game-purchase
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m28s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m26s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m41s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m56s
Project CI / Backend tests (pull_request) Successful in 9m4s
Project CI / Repository checks (pull_request) Successful in 7m13s
Project CI / Native shell tests (pull_request) Successful in 10m16s
Project CI / Frontend tests (pull_request) Successful in 2m26s
f0cad9dddc
- 同步 master 侧 248 个提交:会员与泥点计费(PR #588 / #603)、创作者主页与关注粉丝(PR #638)、运行页刷新真重载(PR #640)、AGC 模板 templateVersion 递增(PR #643)等;确认 master 未触碰发行网关与 `api-server/src/modules/game_distribution.rs`。
- 冲突 1(server-rs/crates/module-runtime/src/domain.rs:1157-1162)`RuntimeProfileWalletLedgerSourceType` 两侧各自在末尾追加变体:保留 master 的 `MembershipUpgradeGrant` 为索引 16(线上已落库口径),本分支 `GamePurchase` 顺延为索引 17,并保留其「每账号每游戏一次扣费」注释。
- 冲突 2(server-rs/crates/module-runtime/src/domain.rs:1184-1188)`as_str()` 同时保留 `membership_upgrade_grant` 与 `game_purchase` 两个分支。
- 冲突 3(server-rs/crates/spacetime-client/src/active/mapper/runtime_profile.rs:95-101)bindings→领域枚举映射同时保留 `MembershipUpgradeGrant` 与 `GamePurchase` 两支,按新索引顺序排列。
- 冲突 4(server-rs/crates/spacetime-client/src/module_bindings/runtime_profile_wallet_ledger_source_type_type.rs:43-47)生成枚举按模块源码顺序合并为 …`MembershipUpgradeGrant`、`GamePurchase`;随后 `npm run spacetime:generate` 复核该文件与生成结果逐字节一致。
- 冲突 5(server-rs/crates/api-server/src/admin.rs:4849-4853)sats 索引映射改为 `16 => membership_upgrade_grant` 与 `17 => game_purchase` 两条并存。
- 冲突 6(server-rs/crates/api-server/src/admin.rs:6368-6461)索引断言并入 master 的 `[16] => membership_upgrade_grant`,并把 master 的「17 尚未映射」占位断言替换为 `[17] => game_purchase`;本分支的后台消耗统计两个用例原样保留。
- 冲突 7(server-rs/crates/api-server/src/runtime_profile.rs:219-226)来源文案格式化同时保留 `MembershipUpgradeGrant` 与 `GamePurchase` 两支,注释随 `GamePurchase` 保留。
- 冲突 8(docs/project-memory/shared-memory/decision-log.md:3-24)两条决策记录并列保留:本分支 2026-10-05「播放会话前缀在三处入口清空 Cookie」在前,master 2026-10-03「每日免费发放额为 0 时前端隐藏该池」在后。
- 保住 master 侧:发行响应 ETag/304/gzip(`release_asset_response_with_cache` 五参形态与 `release_asset_etag` / `if_none_match_matches` / `accepts_gzip_encoding` / `gzip_release_asset` / `release_asset_not_modified_response`)、共享加载面 `PlatformGameLoadingSurface` 及其使用方、三池钱包与会员档位目录改造。
- 保住本分支侧:付费作品 404 守卫(`price_mud_points > 0` 且位于 `load_package` 之前)、购买与播放会话网关路由、nginx×3 / Pingora / Vite 的播放会话 Cookie 清空与 `nginx-route-parity.matrix.json` 登记、viewer 感知公开详情、后台审核价格、`PlatformGamePricingField` 共享定价组件与两端接入、AGC 项目版本只读 + 平台派发下一版本号、schema 与迁移白名单、钱包来源 `game_purchase`。
- 验证:`git merge-base --is-ancestor origin/master HEAD` 返回 0;`check:encoding`、`check:doc-index`、`check:spacetime-schema`、`check:server-rs-ddd`、`check:rustfmt`、`check:npm-workspaces`、`check:game-distribution-dto-parity`、`check:game-distribution-price-limit-parity`、`check:pingora-route-parity`、`check:nginx-spa-routes`、根/admin-web/AGC typecheck、`git diff --check` 全绿。
- 测试:`cargo test -p module-game-distribution` 30 passed,`cargo test -p api-server game_distribution` 61 passed,`cargo test -p spacetime-module game_distribution` 7 passed;定向 vitest 28 个文件 252 个用例全通过。
- `npm run spacetime:generate` 除 11 个 procedure 文件的纯空白格式差异外无任何语义改动(`git diff -w` 为空),已保留 master 的生成产物形态,提交内无 schema/绑定语义变化。
suzmii merged commit be9f74f420 into master 2026-10-06 15:16:01 +08:00
suzmii deleted branch feat/game-purchase 2026-10-06 15:16:01 +08:00
Author
Member

Close #634

Close #634
Sign in to join this conversation.