收口 M3 复核遗留:文档如实口径、fork-source 状态说明、forkAuthorization 机器守卫、e2e 引用去行号

- 技术方案 §3.4:删掉「GET /games/{gameId} 追加 forkSourceAvailable」——实现里不存在该字段;改为如实口径:改造入口显隐只依据 forkAuthorization,真实可复刻形态由 /fork-source 的 source 回答,若 M2b 要区分「可源码级改造 / 只能参考」届时再加
- 实施计划(M1)§5:同步去掉 forkSourceAvailable 并注明「未实现、也不需要」,保留其余字段清单不动
- 技术方案 §2.6 时序图:说明字句从「GET …/fork-source 是待实现接口」改为「M2a 已实现(形状见 §3.4 登录用户表)」,并在图内加一行 Note 标注「图中字段名是 M2b 目标形状」;图属目标形态的免责声明保留
- parity 脚本:game_payload 增加 mustEmit ['forkAuthorization']——该字段 TS 侧可选(旧响应可省),但公开与作者响应必须发出,否则详情页授权徽章与改造入口会静默退化成「禁止共创」;public_game_payload 以 game_payload 为 base,因此一并受保护
- e2e 脚本头部:把全部行号引用(含已过期 2–4 行的那些)改为「文件 + 符号」引用(router()/lineage_read_or_not_found()/GameDistributionLineageNode/lineage.rs 常量与函数等),并删除「消费链路在未提交的 M2a WIP 里」这类会过期的措辞,改为「已在 M2a 落地」;只改注释,断言逻辑未动
This commit is contained in:
2026-10-05 04:40:07 +08:00
parent 6759e56454
commit f40050afb1
5 changed files with 50 additions and 40 deletions
@@ -52,7 +52,8 @@ fn sha256_hex(bytes: &[u8]) -> String {
/// 参考副本相对项目的路径:`reference/<gameId>/`。
///
/// 作品 ID 来自平台响应,只接受路径安全取值;拼接复用归档条目门禁(拒绝 `..`、盘符与
/// 绝对路径),保证参考副本永远落在项目内的子目录,不会逃出项目根。
/// 绝对路径),保证参考副本永远落在项目内的子目录,不会逃出项目根。这是参考目录的**唯一**
/// 推导处:调用方把它 join 到项目根,不再另起第二份拼接规则。
pub(crate) fn fork_reference_relative_path(game_id: &str) -> Result<PathBuf, String> {
let game_id = game_id.trim();
// 空标识必须显式拒绝:`safe_archive_relative_path` 会把空段丢掉,`reference/` 会被
@@ -71,14 +72,6 @@ pub(crate) fn fork_reference_relative_path(game_id: &str) -> Result<PathBuf, Str
Ok(relative)
}
/// 参考副本目录:`<project>/reference/<gameId>/`。
pub(crate) fn fork_reference_directory(
project_root: &Path,
game_id: &str,
) -> Result<PathBuf, String> {
Ok(project_root.join(fork_reference_relative_path(game_id)?))
}
/// 用平台作品的成品包建一个新项目。
///
/// 先按标准初始化生成合规脚手架,再把参考副本铺进 `<project>/reference/<gameId>/`,
@@ -215,16 +208,20 @@ mod tests {
}
#[test]
fn fork_reference_directory_stays_inside_the_project() {
let root = Path::new("C:/work/project");
fn fork_reference_relative_path_stays_inside_the_project() {
// 生产路径就是「把这唯一的相对路径 join 到项目根」,因此这里直接钉相对路径本身。
assert_eq!(
fork_reference_directory(root, "game_1").expect("safe id"),
root.join("reference").join("game_1")
fork_reference_relative_path("game_1").expect("safe id"),
Path::new("reference").join("game_1")
);
assert_eq!(
Path::new("C:/work/project").join(fork_reference_relative_path("game_1").unwrap()),
Path::new("C:/work/project/reference/game_1")
);
// 路径不安全的标识一律失败关闭,不拼出逃出项目根的路径。
for unsafe_id in ["", "..", "../escape", "a/b", "a\\b", "C:game", " "] {
assert!(
fork_reference_directory(root, unsafe_id).is_err(),
fork_reference_relative_path(unsafe_id).is_err(),
"should reject {unsafe_id:?}"
);
}