同步统一发布身份与资料 PATCH 合同到权威文档
后端数据契约:`projectKey` 只在 `POST /games` 写定、路径端点拒绝它;删除 `(owner, project_key)` 兜底与凭 key 复活分支,复活只在 `POST /games`;摘要按原样序列化;统一发布对非默认 `fork` / `forkAuthorization` 失败关闭 400 后端数据契约与共创技术方案:共创授权提升并入 `update_game_distribution_game_metadata_and_return` 同一事务,独立 procedure `set_game_distribution_fork_authorization_and_return` 与输入 DTO 删除 玩法链路:`projectKey` 表改为路径端点「禁止」(400);`POST /games` 注明无版本号入参、服务端固定写 `1` 共享记忆新增当日决策条目,覆盖 #16/#18/#20/#32/#33/#34/#36
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
# 决策记录
|
||||
|
||||
## 2026-10-07 统一发布身份只从路径来;资料 PATCH 细粒度化并把共创授权并入同一事务
|
||||
|
||||
- 统一发布身份(#34/#36):`POST /games/{game_id}/versions/{version_number}` 的身份只从路径来,body 出现 `projectKey` 直接 400;模块 get-or-create 删除 `(owner, project_key)` 兜底查找,`project_key` 只写进本次新建的游戏行。软删复活只发生在 `POST /games`(同一派生 `gameId` 命中软删行时就地覆盖),复活不再读取或覆盖 `project_key`;路径端点不再有「凭 `projectKey` 认领 / 复活」分支,不存在或已软删一律 404。幂等摘要恢复为按请求原样序列化,删除 `publish_digest_payload` 归一 helper;`publish_game_distribution_version_tx` 去掉「校验 `game_id` 却丢掉 trim 结果」的分叉。
|
||||
- 统一发布共创(#33):入口收到 `fork` 声明或非默认 `forkAuthorization` 时失败关闭 400,不再 200 静默丢弃;支持它们需要先定「谁在什么时机能声明改编 / 档位继承与禁止收窄」并补 ADR。
|
||||
- 资料 PATCH(#16/#18/#20):`GameDistributionUpdateGameMetadataRequest` 除 CAS 外全部可选(缺省 / `null` = 不动,`description: ""` = 清空,`coverObjectKey` 不可清空);`GameMetadata` 仍为必填展示资料(#18),局部更新走独立更新 DTO。`update_game_distribution_game_metadata_and_return` 在同一事务内合并 `Some` 字段并裁决 `fork_authorization`(血缘终态 → 只升不降),删除独立提升 procedure `set_game_distribution_fork_authorization_and_return`、`GameDistributionSetForkAuthorizationInput` 与 `{Idempotency-Key}:fork` 第二事务。
|
||||
- 生成绑定(#32):用本地 SpacetimeDB CLI 2.8.3 整批重跑 codegen,把 31 个仍停留在 12 空格缩进的 `*_procedure.rs` 统一为 16 空格;纯缩进变化、无语义改动。
|
||||
- 影响范围:`server-rs/crates/{shared-contracts,spacetime-module,spacetime-client,api-server}`、`packages/shared/src/contracts/generated/GameDistributionUpdateGameMetadataRequest.ts`、`src/services/gameDistributionClient.ts`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【技术方案】游戏共创与作品Fork-2026-10-03.md`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。
|
||||
- 验证方式:`cargo test --locked -p spacetime-module`(315 passed)、`cargo test --locked -p api-server --bin api-server game_distribution`(118 passed)、`cargo check --locked -p spacetime-module -p api-server --all-targets`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-10-07 陶泥儿截图槽位改存 objectKey:发布成功后把本地路径就地改写,不再用空数组隐式沿用
|
||||
|
||||
- 背景:陶泥儿表单的 `screenshotPaths` 原语义是「空数组 = 整组沿用线上」,但空数组既分不清「真的没有截图」与「沿用」,也读不出上一版本具体是哪几张;沿用只能整组、不能逐槽,上一版本预览也无从按槽位取。
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,8 +7,8 @@
|
||||
|
||||
> **2026-10-07 发布面 clean cut 修订(以 `docs/adr/【ADR】游戏分发版本以versionNumber为对外身份-2026-10-07.md` 与《玩法创作》最新合同为准;本文以下按旧名的段落属历史记录):**
|
||||
>
|
||||
> - 发布写入口改为 `POST /api/game-distribution/games`(新作品 + 首版,`projectKey` 必填、首版号固定 `1`)与 `POST /api/game-distribution/games/{game_id}/versions/{version_number}`(追加版本,身份只在路径里);`POST /versions` 与 `POST /games/{game_id}/versions` 已删除。
|
||||
> - 共创授权 `forkAuthorization` **并入** `PATCH /api/game-distribution/my-games/{game_id}`:删除 `PUT /games/{game_id}/fork-authorization`、`GameDistributionSetForkAuthorizationRequest` 与 `expectedForkAuthorization` CAS(并发统一由该 PATCH 的 `expectedPublicationRevision` 承担)。母版只升不降、衍生作品终态 409 `FORK_AUTHORIZATION_INHERITED` 的领域规则不变。
|
||||
> - 发布写入口改为 `POST /api/game-distribution/games`(新作品 + 首版,`projectKey` 必填、无版本号入参、服务端固定写 `1`)与 `POST /api/game-distribution/games/{game_id}/versions/{version_number}`(追加版本,身份只在路径里;body 出现 `projectKey` 直接 400);`POST /versions` 与 `POST /games/{game_id}/versions` 已删除。
|
||||
> - 共创授权 `forkAuthorization` **并入** `PATCH /api/game-distribution/my-games/{game_id}`:删除 `PUT /games/{game_id}/fork-authorization`、`GameDistributionSetForkAuthorizationRequest` 与 `expectedForkAuthorization` CAS(并发统一由该 PATCH 的 `expectedPublicationRevision` 承担);档位与展示字段在同一 `update_game_distribution_game_metadata_and_return` 事务内裁决,已无调用方的独立提升 procedure `set_game_distribution_fork_authorization_and_return` 与 `GameDistributionSetForkAuthorizationInput` 一并删除。母版只升不降、衍生作品终态 409 `FORK_AUTHORIZATION_INHERITED` 的领域规则不变。
|
||||
> - 读面改名:`/derived`→`/forks`、`/lineage`→`/network`、`/contribution`→`/network/stats`、`/fork-source`(含 `/package` `/project`)→`/source`;对应 DTO / 记录类型同步改名(`GameDistributionForksResponse`、`GameDistributionNetworkNode`、`GameDistributionSource*`、`GameDistributionForkMetadata`)。
|
||||
> - 版本级上行族资产段名保持 `project-bundle`(不改名 `source`;GitHub 语义的 source 对应游戏级 `GET /games/{game_id}/source`)。
|
||||
> - 下文出现 `/lineage`、`/derived`、`/contribution`、`/fork-source`、`PUT …/fork-authorization`、`expectedForkAuthorization` 的段落保留为**历史设计记录**;新实现与新契约以修订后的路由与合并后的 PATCH 为准。
|
||||
|
||||
@@ -127,8 +127,8 @@
|
||||
| GET | `/api/game-distribution/games/{game_id}/source`(`/package` `/project`) | – | `GameDistributionSourceResponse` / 二进制 | Bearer(原路径改名) |
|
||||
| GET | `/api/game-distribution/games/{game_id}/network/stats` | – | `GameDistributionNetworkStatsResponse`(子树归集) | Bearer(原路径改名) |
|
||||
|
||||
- `POST /games` 首次发布只强制 `projectKey`,`game_id` 由 `derive_game_distribution_game_id(owner, project_key)` 派生;**首版号固定为 `1`**(无路径槽),带 `fork` 时是衍生首版。
|
||||
- 作品资料编辑走既有通用 `PATCH /my-games/{game_id}`(自带 `expectedPublicationRevision` CAS);共创授权 `forkAuthorization` 并入该 PATCH,删除单字段 `PUT /games/{game_id}/fork-authorization` 与 `GameDistributionSetForkAuthorizationRequest`。
|
||||
- `POST /games` 首次发布只强制 `projectKey`,`game_id` 由 `derive_game_distribution_game_id(owner, project_key)` 派生;**首版号无入参、由服务端固定写 `1`**(无路径槽、body 也不含版本号),带 `fork` 时是衍生首版。
|
||||
- 作品资料编辑走既有通用 `PATCH /my-games/{game_id}`(自带 `expectedPublicationRevision` CAS);共创授权 `forkAuthorization` 并入该 PATCH,删除单字段 `PUT /games/{game_id}/fork-authorization` 与其请求 DTO,以及后来已无调用方的独立提升 procedure `set_game_distribution_fork_authorization_and_return` 与 `GameDistributionSetForkAuthorizationInput`(档位与展示字段在同一资料事务内裁决)。
|
||||
- `PATCH …/versions/{version_number}` **未落地**(2026-10-07 决定):作品级资料编辑只需 `PATCH /my-games/{game_id}`,不为版本详情再开资料写路径,上表该行删除;`GameDistributionUpdateGameMetadataRequest` 仍是 `PATCH /my-games/{game_id}` 的请求体。
|
||||
- 版本级上行族资产段名保持 `project-bundle`(不改名 `source`):GitHub 语义的 source 指可下载来源,对应本表游戏级 `GET /games/{game_id}/source`;版本级 `project-bundle` 是作者上传的可编辑工程包,改名会造成同名不同层级(见 ADR 修订记录)。
|
||||
- 后台族继续按 `{version_id}`(`/admin/api/game-distribution/versions/{version_id}/…`),不参与本次换号。
|
||||
@@ -137,13 +137,13 @@
|
||||
### 路径即身份(body 不重复路径参数)
|
||||
|
||||
- 只要路径里有 `{game_id}` / `{version_number}`,请求体就**不得**再出现同名字段:handler 只从 `Path(…)` 取值,不存在「路径优先还是 body 优先」的校验分支。
|
||||
- `NewGameVersionRequest` 因此**去掉** `gameId` 与 `versionNumber`:`POST /games` 只带 `projectKey`(无路径槽,首版号固定 `1`);`POST /games/{game_id}/versions/{version_number}` 的身份全在路径。
|
||||
- `NewGameVersionRequest` 因此**去掉** `gameId` 与 `versionNumber`:`POST /games` 只带 `projectKey`(无路径槽,首版号由服务端固定写 `1`、没有版本号入参);`POST /games/{game_id}/versions/{version_number}` 的身份全在路径,body 里的 `projectKey` 直接拒绝。
|
||||
- 复核:game-distribution 其余带路径参数的写端点(`submit` / `cancel` / `purchase` / `reviews` / `theme members` / `admin review` / `fork-authorization`)请求体都不含路径身份字段;唯二重复的是本请求与 **待删** 的 legacy `GameDistributionCreateVersionRequest.versionNumber`。
|
||||
|
||||
### 版本身份(versionNumber 对外,version_id 内部主键)
|
||||
|
||||
- URL 中的版本一律用客户端冻结的正整数 `version_number` 标识;`version_id` 保留为 SpacetimeDB 主键与内部代理键,继续用于幂等收据 `outcome_version_id`、媒体命名空间、客户端草稿与生成绑定。
|
||||
- 不删列、不改主键、不搬迁数据:只为 `game_distribution_version` 新增 `(game_id, version_number)` btree 索引用于按号定位;唯一性沿用自然幂等键 `game_distribution_version_key(game_id, project_key, version_number)`,不由数据库唯一约束承担。
|
||||
- 不删列、不改主键、不搬迁数据:只为 `game_distribution_version` 新增 `(game_id, version_number)` btree 索引用于按号定位;自然幂等键只由 `(gameId, versionNumber)` 派生(`game_distribution_version_key(gameId, None, versionNumber)`),不由数据库唯一约束承担;`projectKey` 只体现在派生出的 `gameId` 里,不再入键、也不参与摘要。
|
||||
- 版本号在同一作品内唯一:重复提交同号同包摘要 = 重放(200 + `replayed`),同号异摘要 = `409 IDEMPOTENCY_CONFLICT`。放弃 master 旧口径「同一版本号可反复提交、每次生成新 `versionId`」。
|
||||
- 创建响应体继续返回 `versionId`,客户端把它存起来(收据 / 媒体命名空间 / 草稿)供后续引用;URL 一律用 `versionNumber`。
|
||||
- 后台与内部(`/admin/...`、幂等收据、媒体命名空间)继续按 `version_id`。
|
||||
@@ -157,10 +157,12 @@ AGC 不靠 `projectKey` 反查远端身份,而是把远端身份**存在本地
|
||||
| 接口 | `projectKey` | 说明 |
|
||||
| -------------------------------------------------- | ------------ | ------------------------------------------------------------- |
|
||||
| `POST /games` | **必填** | 没有 `gameId` 可参考,靠它派生作品身份;缺失 400 |
|
||||
| `POST /games/{game_id}/versions/{version_number}` | 可选 | 身份由路径 `game_id` 定;AGC 已绑定时根本不传,只在恢复时兜底 |
|
||||
| `POST /games/{game_id}/versions/{version_number}` | **禁止** | 身份由路径 `game_id` 定;body 里出现 `projectKey` 直接 400,不再有「凭 key 认领 / 复活别的作品」的兜底 |
|
||||
| `PATCH /games/{game_id}/versions/{version_number}` | 不需要 | 不换身份 |
|
||||
| `PATCH /my-games/{game_id}` | 不需要 | 资料编辑不换身份 |
|
||||
|
||||
路径端点不携带 `projectKey`,因此也不再有「请求 key 与路径 `gameId` 的确定性身份一致时就地复活软删作品」这条分支:软删作品经路径读写一律 404,复活只发生在 `POST /games`(同一派生 `gameId` 命中软删行时覆盖为全新作品)。AGC 无绑定恢复需要复活时,仍以「只传 `projectKey`」走 `POST /games`。
|
||||
|
||||
### GitHub 术语对齐
|
||||
|
||||
| 现名 | 语义 | GitHub 术语 | 新名 |
|
||||
|
||||
Reference in New Issue
Block a user