fix(游戏共创): 血缘摘要的父作者名改为读时联账号表兜底(修真实库 parentAuthorName 为 null)
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
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
真实库 `GET /games/{A111}` 的血缘摘要里 `parentTitle` 有值而 `parentAuthorName` 为 `null`,详情页溯源条
「父作品 · 作者名」开天窗。根因核实:`game_distribution_lineage_snapshot` 直接用了发布时冻结的
`parent.author_name`,而老数据 / 程序化创建的作品上该列常为 `None`——公开投影与族谱节点早已改成**读时联
`user_account`**,只有血缘摘要没跟上(同一处语义没同步)。
- 血缘摘要改用 `game_distribution_lineage_author_name`(与族谱节点**同一实现**):账号显示名优先 →
账号不可得/昵称为空白时退回冻结作者名 → 两者都为空白则 `None`(不占位、不回退空串、不编造),
与公开详情同口径;父作品不可用时仍走原有的 `fork_lineage_visible_identity` 三态裁剪,不泄露已删作品身份。
- 新增结构回归测试 `lineage_summary_resolves_parent_author_name_at_read_time`:血缘快照必须调用账号兜底
函数,并**不得**再直接出现 `parent.author_name`(宿主起不了真库,行为证据留给真实栈复验)。
- parity 新增登记 `lineage_payload`(构建器 17→**18**):把「builder 发出的键 == DTO 字段」钉住,并要求
`parentAuthorName` **恒发该键**(值为 `null` 也要发——缺键与 `null` 在前端是两种渲染分支,不能用
optional 表达)。
- 文档 §3.4:写明读时兜底的口径与优先级;并明确**不新增 `rootAuthorName`**——已只读核实前端
`GameDetailPage` 只消费 `parentAuthorName`(`CoCreationLineageStrip` 用的是族谱节点的 `authorName`),
根侧只发 `rootTitle`,不加字段以免契约膨胀。
- **无字段形状变更**(Rust DTO 与 TS 均未增删字段,故无 TS/绑定改动)。
门禁:wasm build 0;`cargo check --all-targets` 0;`cargo test -p spacetime-module` **306 passed / 1 ignored**;
`cargo test -p module-game-distribution` 134 passed;`cargo test -p api-server game_distribution` 107 passed;
DTO parity 0(66 组 / **18** 构建器 / 15 手拼类型);`check:spacetime-schema` 0(98 表);
`check:encoding` 0(5546 files);`cargo fmt --all --manifest-path server-rs/Cargo.toml -- --check` 0;
`git diff --check` 0。
未在真实库手动改任何数据(按要求);修完请另一个 session 用真实数据复验 `parentAuthorName`。
This commit is contained in:
@@ -507,6 +507,8 @@ A 路线里有一个必须提前知道的互斥点:`create_npm_scaffold` 的
|
||||
- 共创信息(授权徽章 + 代际 + 溯源 + 已被改编次数)通过它的 `infoCards` 槽位渲染,落在资料侧栏,等价于原设计里 `.game-info-card.game-fork-card` 的位置;
|
||||
- 共享组件会把 `value` 再包一层内联 flex 的 `span`,卡内样式统一用 `.game-detail-info-card .game-fork-*` 前缀保证优先级;
|
||||
- 父作品被软删除时后端 `parentTitle` 为 `null`,卡片降级为「第 N 代作品 · 原作品已不可用」,不猜测、不回显已删内容。
|
||||
- **`lineage.parentAuthorName` 是读时联账号表的结果(2026-10-06 修)**:账号显示名优先,账号不可得或昵称为空白时退回发布时冻结的作者名,两者都为空则 `null`(不占位、不伪造)。**不得**只读游戏行的冻结列 `author_name`——老数据与程序化创建的作品上它常为 `None`,真实库里就出现过「`parentTitle` 有值而 `parentAuthorName` 为 `null`」,导致溯源条「父作品 · 作者名」开天窗;口径与公开投影 / 族谱节点共用 `resolve_lineage_author_name`。
|
||||
- **不提供 `rootAuthorName`**:前端溯源条只展示父作者(`GameDetailPage` 只消费 `parentAuthorName`),根侧只发 `rootTitle`;除非将来确实要展示根作者,否则不加字段以免契约膨胀。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -312,6 +312,15 @@ const RESPONSE_BUILDERS = [
|
||||
'visibility',
|
||||
],
|
||||
},
|
||||
{
|
||||
// 公开详情里的血缘摘要(`GET /games/{gameId}` 的 `lineage`)。TS 里 `parentAuthorName` 是可选的
|
||||
// (表达「历史响应可能缺键」),但这条路径**必须恒发该键**(值为 `null` 也要发):它是溯源条
|
||||
// 「父作品 · 作者名」的唯一来源,「缺键」与「null」在前端是两种渲染分支,不能靠 optional 表达。
|
||||
// 这条登记同时把「builder 发出的键 == DTO 字段」钉住,防止再出现「加了字段忘了发」。
|
||||
fn: 'lineage_payload',
|
||||
ts: 'GameDistributionLineage',
|
||||
mustEmit: ['parentAuthorName'],
|
||||
},
|
||||
];
|
||||
|
||||
function camelCase(value) {
|
||||
|
||||
@@ -7606,11 +7606,18 @@ fn game_distribution_lineage_snapshot(
|
||||
.game_id()
|
||||
.find(&lineage.parent_game_id)?;
|
||||
let parent_available = parent.deleted_at.is_none();
|
||||
// 父作品作者名与公开投影/族谱节点同一口径:**账号显示名优先**,账号不可得或昵称为空时退回
|
||||
// 发布时冻结的 `author_name`,两者都为空白则 `None`(不占位)。
|
||||
//
|
||||
// 这里必须**读时联 `user_account`**:冻结值在老数据与程序化创建的作品上常常是 `None`(真实库里
|
||||
// 就出现过 `parentTitle` 有值而 `parentAuthorName` 为 `null`),而溯源条要展示「父作品 · 作者名」。
|
||||
// 规则复用 `game_distribution_lineage_author_name`(与族谱节点同一实现),不在本函数里再写一遍。
|
||||
let parent_author_fallback = game_distribution_lineage_author_name(ctx, &parent);
|
||||
let (parent_title, parent_author_name) =
|
||||
module_game_distribution::fork_lineage_visible_identity(
|
||||
parent_available,
|
||||
parent.title.as_str(),
|
||||
parent.author_name.as_deref(),
|
||||
parent_author_fallback.as_deref(),
|
||||
);
|
||||
// 根作品与父作品同一口径:标题同样只在根行可用时外发;根行缺失(历史数据)退回父标题,
|
||||
// 且同样受父的可用性约束。规则复用同一个纯函数,不在 module 里再写一遍。
|
||||
@@ -9037,4 +9044,25 @@ mod tests {
|
||||
);
|
||||
assert!(totals.contains("pub play_count: u64"));
|
||||
}
|
||||
|
||||
/// 公开详情的**血缘摘要**必须读时联账号表取父作者名。
|
||||
///
|
||||
/// 真实库缺陷:`parentTitle` 有值而 `parentAuthorName` 为 `null`(详情页溯源条要展示
|
||||
/// 「父作品 · 作者名」,于是开天窗)。根因是这里直接用了发布时冻结的 `parent.author_name`,
|
||||
/// 而老数据 / 程序化创建的作品上该列常为 `None`——公开投影与族谱节点早已改成读时联
|
||||
/// `user_account`(账号昵称优先 → 退回冻结值 → 空白为 `None`),只有血缘摘要没跟上。
|
||||
/// 归一规则复用族谱节点那一份实现(`game_distribution_lineage_author_name`),不在这里重写。
|
||||
#[test]
|
||||
fn lineage_summary_resolves_parent_author_name_at_read_time() {
|
||||
let source = include_str!("game_distribution.rs");
|
||||
let body = function_body(source, "fn game_distribution_lineage_snapshot(");
|
||||
assert!(
|
||||
body.contains("game_distribution_lineage_author_name(ctx, &parent)"),
|
||||
"血缘摘要必须用「账号兜底」的作者名:{body}"
|
||||
);
|
||||
assert!(
|
||||
!body.contains("parent.author_name"),
|
||||
"不得再直接读冻结列 `parent.author_name`(老数据常为 None,会重现空作者名缺陷):{body}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user