ce614d59c16382f7943ea57bd033d85a91114dc2
5879 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ce614d59c1 |
feat(游戏共创): 主题详情 rootsTruncated 信号(memberCount 报真实可见成员数)
Project CI / AI game creator shell Rust crates (pull_request) Successful in 6m1s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 7m10s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m58s
Project CI / Backend tests (pull_request) Successful in 9m10s
Project CI / Frontend tests (pull_request) Successful in 2m58s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m32s
Project CI / Native shell tests (pull_request) Successful in 6m37s
Project CI / Repository checks (pull_request) Successful in 4m55s
裁定(与族谱 `truncated` 同约定):`memberCount` = **真实可见成员数**(不受展示上限污染),新增布尔 `rootsTruncated`,**仅当 `roots` 被 50 上限截断时为 true**。此前两个数被同一个 50 上限截断 ⇒ 可见成员 > 50 时静默截断,客户端既看不到全部树、也拿不到任何信号。 - 纯函数:`game_distribution_theme_roots_truncated(visible_member_count, returned_root_count)` (相等 / 回传更多 ⇒ false;后者是防御性口径:两组数字不同源时不得凭空报「还有更多」)。 - 契约:`GameDistributionThemeDetail` 加 `roots_truncated`(serde camelCase ⇒ `rootsTruncated`), TS 镜像同步;parity `public_theme_detail_payload.mustEmit` 6 键 → **7 键**。 - 事务:`member_count` 走可见成员清单的 `len()`(**不再**经 `page_theme_members` 与上限),`roots` 仍是 同一清单排序后取前 50,`rootsTruncated` 由纯函数算出(不写死「>50 就 true」——阈值与上限是两个 独立事实);列表入口共用同一计数函数 ⇒ 同一主题在列表与详情里不会报出两个 `memberCount`。 - api-server:`public_theme_detail_payload` 增加 `rootsTruncated`;形状单测 6 → 7 键,并加 `memberCount=120 / roots=1 / rootsTruncated=true` 用例,证明两个键口径独立。 - 结构断言同步修正:列表侧原「成员数与 roots 共用同一上限」改为「成员数数的是全部可见成员(断言 源码里不含切页函数与上限常量)」;详情侧改为「必须调用共享纯函数 + 不得再恒等 `roots.len()` + 该字段必须进入返回值」。 - 生成绑定:`module_bindings/game_distribution_theme_detail_result_type.rs` 手改后用临时 out-dir 重跑 `spacetime generate --lang rust` 逐字节校对一致。**wire format 变更 ⇒ 部署需重新 publish 模块** (客户端绑定已同步)。 - 文档:技术方案 §3.4 / §3.10.6 / §3.10.9(原「已知限制·三个备选待拍板」→ 已定口径)、里程碑 (依赖、接口表、验收标准、纯函数表、测试清单、待确认项)、以及数据契约文档里那句「主题详情的 roots 与列表的 memberCount 永远一致」(本改动已使其为假)。 门禁:wasm build 0;`cargo check --all-targets` 0;`cargo test -p api-server game_distribution` 86 passed; `cargo test -p module-game-distribution` **101 passed**;`cargo test -p spacetime-module` 286 passed / 1 ignored;DTO parity 0(58 组 / **15** 构建器 / 12 手拼类型);`check:spacetime-schema` 0(96 tables); `check:encoding` 0(5420 files);`cargo fmt --all -- --check` 0;`git diff --check` 0。 遗留(不在本仓可改范围,需前端 session 跟进):`src/services/gameDistributionClient.ts` 有自己一份主题 详情本地类型与 normalize,会丢弃 `rootsTruncated`(约 3 行小改),因此网页主题页暂时拿不到截断信号; admin-web 走共享契约类型,后台侧即时生效。 |
||
|
|
4a33397822 |
chore(游戏共创): 主题注释失真修正 + 后台主题 payload 纳入契约门禁
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m53s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m48s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m42s
Project CI / Backend tests (pull_request) Successful in 9m47s
Project CI / Frontend tests (pull_request) Successful in 4m4s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m56s
Project CI / Native shell tests (pull_request) Successful in 7m55s
Project CI / Repository checks (pull_request) Successful in 6m8s
1) 注释失真(独立复核 §B8):`module-game-distribution/src/theme.rs` 与 `spacetime-module/src/game_distribution.rs` 原先声称「api-server 与事务都调 `game_distribution_theme_text_violation`」,但全仓只有写入事务一处调用(HTTP 层没有预校验)。 选择**改注释**(单一判据来源就是事务),不给 HTTP 层再叠一层同名校验——否则「HTTP 挡住的」 与「落库挡住的」会重新长出两套判据。两处注释都改成明确写「只有事务调用 + api-server 不做预校验」。 2) 后台主题 payload 的契约门禁:`admin_theme_payload` / `admin_themes_payload` / `admin_theme_mutation_payload` / `admin_theme_member_payload` 原先既无 TS 类型也未登记 parity, 形状只有 api-server 单测钉着 → 后台 UI 落地时容易与实现漂移。 - `packages/shared/src/contracts/gameDistribution.ts` 新增 5 个 TS 类型 (`GameDistributionAdminTheme`、`…ListResponse`、`…MutationResponse`、`…MemberResponse`、 `…MemberRemovalResponse`),注释里写清两处刻意差异:后台主题条目比公开摘要多出 `sortOrder`/`status`/时间戳,且 `memberCount` 是**成员行总数**(含当前不可见成员), 公开投影里同名键是**当前可见**成员数;后台列表**没有** `nextCursor`。 - `api-server`:把 handler 里内联的成员移除响应抽成命名构建器 `admin_theme_member_removal_payload`——内联 `json!` 不在 parity 的证据范围内,抽出来这条 形状的漂移才会被门禁抓住。 - parity 登记:`TS_ONLY_TYPES` +5(这些形状服务端同样是逐字段手拼 JSON,没有 Rust 结构体可对, 要收口得先把响应改成结构化类型),`RESPONSE_BUILDERS` +5(响应键与 TS 字段逐键比对)。 门禁:`cargo test -p api-server game_distribution` 86 passed;`cargo test -p spacetime-module` 286 passed / 1 ignored;DTO parity 0(58 组类型 / **15** 个构建器 / **12** 个手拼类型); `check:encoding` 0(5420 files);`cargo fmt --all -- --check` 0;`git diff --check` 0。 |
||
|
|
e401963d3a |
fix(游戏共创): 非法游标回归稳定码 THEME_INVALID_CURSOR(契约码此前在 HTTP 面不可达)
Project CI / Backend tests (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
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
端到端实测(54 项里唯一红的那条):`GET /api/game-distribution/themes?cursor=abc` 回 400,但
`error.code = "BAD_REQUEST"`——技术方案 §3.4 与主题 e2e 脚本承诺的稳定码 `THEME_INVALID_CURSOR`
在 HTTP 面上拿不到,客户端只能去匹配中文文案。
根因:`map_spacetime_error` 的 `THEME_` 分支前置条件是「消息含 `THEME_`」,而模块侧游标解析只回
中文串「主题列表游标格式无效」⇒ 落兜底分支成 400 + 通用 `BAD_REQUEST`;契约常量与那条映射分支
因此是死代码。且 api-server 原有单测**明确要求**文案不含 `THEME_`(口径留了两套)。
口径二选一,选**让码可达**(模块产出前缀),删掉「要求不含 `THEME_`」的断言:
- `module-game-distribution/src/errors.rs`:新增 6 个主题码常量
`GAME_DISTRIBUTION_THEME_{NOT_FOUND,BAD_REQUEST,INVALID_CURSOR,IDEMPOTENCY_CONFLICT,MEMBER_NOT_ROOT,MEMBER_GAME_NOT_FOUND}_CODE`,
并把 `Display` 从字面量改为**复用同一批常量**(消灭「常量改了、`Display` 没改」这类只有真栈才发现的漂移)。
- `theme.rs`:`parse_game_distribution_theme_cursor` 产出 `THEME_INVALID_CURSOR: 主题列表游标格式无效`;
文档注释更新为「只保留 `FORK_` 这个排在 `THEME_` 分支之前的子串禁忌」(其余子串已抢不到)。
- `lib.rs`:导出新增的 6 个码常量。
- 测试:
· `domain.rs::theme_errors_are_prefixed_with_their_machine_code` 改为**枚举全部 6 个主题码**(含唯一
不走领域变体的游标路径——正是当初漏掉它的原因),并加「任何新增码都必须进这张表」的覆盖完整性断言;
· `theme.rs::invalid_cursor_message_stays_in_the_bad_request_bucket` 增加「必须以稳定码开头」;
· api-server 新增 `theme_error_codes_are_reachable_from_module_messages`:用**模块真实产出的消息**
逐码断言 `(状态码, 稳定码)` 可达,并比对模块常量与 `shared-contracts` 常量是同一组字符串;
· api-server `theme_list_invalid_cursor_maps_to_bad_request` 改为断言映射出 `THEME_INVALID_CURSOR`,
只把 `FORK_` 留在禁忌列表里(并写明理由)。
门禁:`cargo test -p module-game-distribution` **100 passed**;`cargo test -p api-server game_distribution`
**86 passed**;`cargo check --all-targets` 0;DTO parity 58 组 / 10 构建器;`check:encoding` 0;
`cargo fmt --all -- --check` 0;`git diff --check` 0。
|
||
|
|
909f3d5fa2 |
网页端共创主题:共创 Tab + 主题页(逐根内嵌作品树)+ 详情页主题入口 + 网关清单
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 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
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
- 客户端:新增 listThemes(游标分页,读 nextCursor)与 getTheme(404 由页面渲染成降级态);getGame 的返回类型增量 themes(匿名也返回;无主题时是空数组而不是缺键) - 共创 Tab:广场页新增「游戏 / 共创」页签,复用既有 game-category-tabs 结构与类名,切到 /games/themes - 主题列表页 GameThemesPage:卡片展示 name / summary / badge / memberCount;只有 nextCursor 非空才渲染「加载更多」并在点击时追加;空态与失败重试照既有写法 - 主题页 GameThemePage:标题 / 简介 / 角标 + 成员根清单;**逐根内嵌渲染作品树**——直接复用族谱页(GameLineagePage 新增 embedded 变体,只隐藏页面外壳:返回按钮 / 页头 / 脚注),树查询、可见性过滤、稳定排序与截断全部走 M3 那一份实现,不新建第二套树查询或树渲染(每根一次 /lineage 的 N+1 是技术方案 §3.10.4 已接受的取舍);已发布但可见成员为空 → 明确空态而不是错误;主题不存在 / 未发布 → 错误态 - 详情页:共创信息卡渲染所属主题角标(点击进主题页),第 N 代作品同样可见(服务端按根反查);无主题时不渲染空容器 - 路由登记:新增 /games/themes 与 /games/theme(详情用 ?id=),四处同批登记(STAGE_ROUTE_ENTRIES + SelectionStage 类型 + 壳层 lazy/回调/渲染分支 + 页面标题),并同步三份 nginx SPA allowlist 与 Pingora MAIN_SPA_PATHS:check:nginx-spa-routes(20 SPA routes / 3 模板)与 check:pingora-route-parity 均绿 - 测试:主题列表三例(渲染与卡片跳转 / 有游标时的加载更多与追加 / 空态与失败重试)、主题页三例(逐根两棵树且复用 lineage 查询 + 全屏入口参数 / 空成员空态 / 404 错误态)、详情页主题入口两例(渲染并可跳转 / 无主题不渲染容器)、路由与标题登记断言、壳层三例(页签切换 / 列表进主题页 / 主题页按 ?id= 渲染并复用族谱查询) |
||
|
|
58da0e67db |
test(游戏共创): 新增共创主题端到端验收脚本
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
- 新增 scripts/check-game-distribution-theme-e2e.mjs(约 1160 行):真库验收公开读 + 后台写 +
作品详情 themes 增量,复用 lineage 脚本的完整发布链路 helper(脚本头注释逐条标注出处),
覆盖 9 组共 54 条断言:
· 建主题→加根成员→公开读:公开列表只含 published(且不含 status 字段)、详情 roots 逐条与公开
目录同一投影(键集合 23=23)、memberCount 与 roots 长度一致
· 只允许根:第 1 代子作品当成员 → 409 THEME_MEMBER_NOT_ROOT;不存在的 gameId → 404 THEME_MEMBER_GAME_NOT_FOUND
· 跨主题多归属:同一个根加入两个主题,两条主题都能读到它,作品详情 themes 长度=2
· 作品详情 themes 口径:匿名与登录都发;**专门构造第 1 代衍生作品**(带 fork 声明 + 完整发布链路)
并断言它也返回根所属的两个主题(先取根再反查);无归属作品返回空数组
· 可见性:draft 主题公开 404(后台按 draft 过滤仍可见)、archived 公开 404 且作品详情 themes 同步消失、
改回 published 恢复;已发布但成员全不可见 → 200 + 空 roots + memberCount=0;
成员下架 → 从 roots 消失,重新公开(新版本审核通过)→ 同一行自动回到 roots 且仍只一行
· 分页:默认 20、limit=999 截断到 50、limit=20 游标翻页 3 页 54 条不重不漏、末页 nextCursor=null、
?cursor=abc → **400**(状态码符合契约)
· 后台权限:五条路由未登录 401;普通作者 token 403;成员 PUT/DELETE 免幂等键、重复 PUT 只留一行
且 createdAt 不变、DELETE 幂等(data 载荷逐字段相同,信封 meta.requestId 每请求不同属既有协议)
· 后台幂等:同键重放 replayed=true 且不产生第二个主题、同键换请求 409 THEME_IDEMPOTENCY_CONFLICT、
缺 Idempotency-Key → 400
· 三条公开路由匿名 200 且带 Cache-Control: no-store
- **实测 53 PASS / 1 FAIL**:唯一失败是已确认的产品缺口(仅报告、未改后端)——非法游标的状态码是
400 ✓,但稳定码 THEME_INVALID_CURSOR 不可达:api-server/...:5343-5346 为该码注册了 400 映射,
而模块侧只回纯文案「主题列表游标格式无效」(module-game-distribution/src/theme.rs:205-215),
不含分支前置所需的 `THEME_` 前缀,客户端实际拿到 code=BAD_REQUEST。脚本刻意保留这条红断言
(契约真值),并附 NOTE 说明成因与证据。
- package.json:新增 npm script check:game-distribution-theme-e2e
|
||
|
|
09a1cf5497 |
docs(游戏共创): 共创主题文档回填(§3.4 接口表 / §3.10 实现状态 / 里程碑 Status 与验收)
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m57s
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
Project CI / Backend tests (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 / Native shell tests (pull_request) Has been cancelled
服务端三块(`2fa201e0d` 模型与纯函数、`742723a58` 公开读、`033e3aa79` 后台写)已落地,本次把文档从「只落设计」补齐到「服务端已实现」,并写死一条已知限制。
- **技术方案 `§3.4`**:新增 5 条接口行(真实路由/鉴权/错误码/字段名均以 `git show` 核对代码为准,非猜测)——公开 `GET /api/game-distribution/themes`(匿名 + `no-store`;只含 `published`;limit 缺省 20 / 上限 50 / 超界截断;游标 `"{createdAtMicros}:{themeId}"`;非法游标 400;末页 `nextCursor: null`;`memberCount` = 公开可见成员数)、`GET /api/game-distribution/themes/{themeId}`(顶层扁平 `{themeId,name,summary,badge,memberCount,roots}`;`roots` 复用公开目录同一份投影;不存在/draft/archived → 404 不返回空壳;已发布空成员 → 200 + 空 `roots`),以及 `GET /games/{gameId}` 的 `themes` 增量(匿名与登录都发、空数组恒发;先取根再按根反查 ⇒ 第 N 代作品也能跳主题页);后台 5 条(`require_admin_auth` + `AuthenticatedAdmin`,未登录/失效 401 `UNAUTHORIZED`、非 admin 403;创建与编辑要求 `Idempotency-Key`,同键重放 `replayed: true`、同键换请求 409 `THEME_IDEMPOTENCY_CONFLICT`;成员增删**不要求**幂等键(确定性主键天然幂等,删不存在也 200);非根成员 409 `THEME_MEMBER_NOT_ROOT`;后台列表含 draft/archived、limit 缺省与上限 200、无游标;`theme_id` 服务端生成 `theme-{uuid}`)。
- **`§3.10`**:各小节「本轮实现 / 留白」标注更新为现状(服务端已实现;公开前端与后台 UI 仍留白);`§3.10.6`/`§3.10.9` 明确写下**已知限制**——主题详情 `roots` 沿用页上限 **50** 且 `memberCount == roots.len()`,可见成员 > 50 时会**静默截断且没有「还有更多」标志**,三个备选(去上限 / 给成员加游标 / `memberCount` 报真实可见数)待拍板,本轮不改契约。
- **`§7`**:第 3 条(共创主题)改为「设计已采纳 + 服务端已实现」,仍待拍板收敛为两条:① 主题级排序口径(公开列表当前按 `created_at` 倒序 + `themeId` 兜底;若要按运营 `sort_order` 展示需改游标格式并同步前端);② 上述 `roots` 50 上限行为。
- **里程碑文件**:`Status` 由 `proposed` → 「服务端已实现(附四个提交号);公开前端与后台管理 UI 待做」,Version 1.0 → 1.1,新增「已落地提交」行;实施清单 A–F 标注已落地提交、G 标未做(待前端)、H 分「已落地层 / 未做层」、I 补执行状态;**验收标准勾 17/19**(第 17 条公开前端、第 18 条 SPA 路由门禁留空并注「等前端」);文末写明「dev 栈端到端整链未跑(`scripts/check-game-distribution-theme-e2e.mjs` 未创建),依赖真实库存的运行时分支目前只到单元/结构/映射层」。
- **数据契约文档**:两张主题表小节的「写入路径属后续里程碑」改为现状(读/写 procedure 与事务名),表结构描述未动。
- **门禁**:`check:doc-index` 0(252 份 Markdown,current=121 / historical=15 / review=1);`check:encoding` 0(5414 files);`git diff --check` 0。
- **环境限制(标注,非绿)**:`check:generated-bindings` 本机 `EXIT=1`,原因是 `apps/ai-game-creator-shell/src-tauri/build.rs:167` 校验随包资源时缺 `bin/win-x64/agc_godot_editor.dll`(需先跑随包资源准备步骤)——纯环境前置,与本次只改 `docs/**` 无关;该检查的 AGC 段本地从来跑不了(CI 绿)。
|
||
|
|
033e3aa79a |
feat(游戏共创): 共创主题(三)后台写路径(5 条 admin 路由 + 事务 + 幂等)
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m59s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m47s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m43s
Project CI / Backend tests (pull_request) Successful in 9m27s
Project CI / Frontend tests (pull_request) Successful in 3m13s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m24s
Project CI / Repository checks (pull_request) Successful in 5m4s
Project CI / Native shell tests (pull_request) Successful in 6m50s
按 §3.10.8:后台 UI 不在本轮,本轮只落**接口与事务**。
- **鉴权(照仓库既有 admin 体系,不自造)**:5 条路由挂在既有 `/admin/api/game-distribution/*` 支,统一 `route_layer(require_admin_auth)`,handler 取 `Extension<AuthenticatedAdmin>`;未带 / 失效会话 → **401 `UNAUTHORIZED`**(与同组 games/reviews 路由同码同形),非 admin role → 403。模块侧第二层:写 procedure 与后台列表都要求受信服务身份(`require_editor_generation_runtime_service_identity`),只有 api-server 能调。
- **5 条路由**:`POST /themes`(创建;服务端生成 `theme-{uuid}`;要求 `Idempotency-Key`,摘要只覆盖客户端可见请求体,**不含重试时重新生成的 `theme_id`**)、`PUT /themes/{theme_id}`(整体覆盖 name/summary/badge/sortOrder/status,生效即刷新 `updated_at`,重放不写库不刷新)、`PUT /themes/{theme_id}/members/{root_game_id}`(**不要求幂等键**——确定性主键天然幂等;可带 `sortOrder`)、`DELETE …/members/{root_game_id}`(不要求幂等键;成员不存在也算 200,响应刻意不含「之前存不存在」→ 重复调用逐字节相同)、`GET /themes?limit=&status=`(**含 draft/archived**,status 白名单 `all|draft|published|archived`,limit 缺省与上限 200、超界截断、无游标)。
- **成员只允许根作品**:用 `game_distribution_theme_root_acceptable(has_lineage_row)`,非根 → **409 `THEME_MEMBER_NOT_ROOT`**;作品不存在 → 404 `THEME_MEMBER_GAME_NOT_FOUND`;主题不存在 → 404 `THEME_NOT_FOUND`。**不做「成员必须已公开」的前置校验**(运营可先挂草稿根,作品公开后自动出现在公开投影里);归档主题**不清理**成员行(与公开投影「跳过不删行」同一口径)。
- **领域错误码**:`module-game-distribution/src/errors.rs` 新增 5 个带码错误(`ThemeNotFound` / `ThemeBadRequest{reason}` / `ThemeIdempotencyConflict` / `ThemeMemberNotRoot{game_id}` / `ThemeMemberGameNotFound{game_id}`),Display 一律 `CODE: 文案`,并由测试钉住「码后必须跟冒号」;api-server 集中映射,不落 axum 默认 422 纯文本。
- **纯函数补充**:后台 status 过滤器白名单(`all` + 三态的单一实现)、文本上限常量(名称 40 / 简介 200 / 角标 16,按 UI 一行与卡片摘要的量级取,写进注释)与 `game_distribution_theme_text_violation`(trim 后判空名与超长,返回可拼进 `THEME_BAD_REQUEST:` 的原因)。
- **测试**:`theme.rs` +2 纯函数(status 过滤器白名单、文本违规判定)、`domain.rs` +1(错误码前缀)、spacetime-module +8 事务结构断言、client mapper +3、api-server +6(401 同码同形、创建/编辑/成员增删的形状与错误码、后台列表含 draft、重复 PUT 幂等、非根成员被拒)→ `cargo test -p api-server game_distribution` **85 passed**、`module-game-distribution` **100 passed**、`spacetime-module` **286 passed**、`spacetime-client` 37 passed。
- **门禁**:wasm build 0;`cargo check --all-targets` 0;`cargo test -p api-server game_distribution` 85 passed;`cargo test -p module-game-distribution` 100 passed;`cargo test -p spacetime-module` 286 passed / 1 ignored;`cargo test -p spacetime-client` 37 passed;DTO parity 58 组 / 10 构建器;`check:spacetime-schema` 0(96 tables);`check:encoding` 0(5414 files);`cargo fmt --all -- --check` 0;`git diff --check` 0。
- 生成绑定:新增 15 个 `module_bindings/*theme*/admin_theme*` 文件 + 入口声明(无重排);其余 202 个文件的 rustfmt 漂移按上一轮做法逐字节还原,未跑任何 git 写命令。
|
||
|
|
742723a58c |
feat(游戏共创): 共创主题(二)公开读路径(事务 + client + 两个公开路由 + 详情 themes 增量)
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m44s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m35s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m37s
Project CI / Backend tests (pull_request) Successful in 9m29s
Project CI / Frontend tests (pull_request) Successful in 3m7s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m7s
Project CI / Repository checks (pull_request) Successful in 6m48s
Project CI / Native shell tests (pull_request) Successful in 8m39s
- **事务**(`spacetime-module`):公共列表 `list_game_distribution_themes_tx`(匿名可读;先按 `published` 过滤、再排序切页——`created_at` 倒序 + `theme_id` 兜底;只对当页现算 `member_count`;不写库);主题详情 `get_game_distribution_theme_detail_tx`(不存在 / 未发布**同一句**「主题不存在」→ 404;`roots` 逐条复用公开目录同一份 `public_game_distribution_snapshot`;成员可见性委托 `game_distribution_theme_member_visible`,被滤掉的行**不删除**;已发布但可见成员为空 = Ok + 空 roots = 200);作品→主题 `list_game_distribution_theme_refs_for_root_tx`(入参任意代作品,先取**根**再按根反查成员、只保留 `published` 主题;排序复用 `sort_public_themes`)。**根解析只有一份实现**:抽成 `game_distribution_theme_root_of` 并让既有 `game_distribution_lineage_tx` 复用(测试钉住)。
- **client**:3 个新方法 + mapper 记录类型(`spacetime-client`)。
- **公开路由**(`api-server`):`GET /api/game-distribution/themes?limit=&cursor=`(匿名可读 + `no-store`;`limit` 缺省 20 / 上限 50 / 超界截断;非法游标 **400** 透传;末页 `nextCursor: null`)、`GET /api/game-distribution/themes/{theme_id}`(顶层扁平 `{themeId,name,summary,badge,memberCount,roots}`;不存在 / draft / archived → **404**,不回空壳、不带 `THEME_` 码)、既有公开作品详情追加 `themes`(匿名与登录**都**发、空数组恒发;`collected` 仅登录发的老口径不变;因按根反查,**第 N 代作品也能跳到主题页**)。
- **错误码**:新增 6 个 `THEME_*` 稳定码集中在 `map_spacetime_error` 映射(不落 axum 默认 422 纯文本);未登记码兜底 **400 `THEME_ERROR`**。
- **契约/parity**:5 条手拼响应构建器登记(`public_themes_payload` / `public_theme_detail_payload` / `public_theme_summary_payload` / `theme_reference_payload`,并给 `public_game_detail_payload` 加 `mustEmit: [themes]`)→ 58 组类型 + 10 个构建器一致。
- **测试 +13**:spacetime-module 4 条源码结构断言(根解析单一实现、先过滤再切页、投影不删行、详情只有两种失败且同一 404 文案、refs 走具名根索引 + 只保留 published)、api-server 7 条(路由挂载与 `no-store`、列表/详情响应形状与末页 null、`roots` 逐字节等于 `public_game_payload`、limit 缺省与截断、非法游标 400、404 与 6 个 THEME_ 码映射、作品详情 `themes` 恒发而 `collected` 仅登录)、client mapper 2 条。
- **门禁**:wasm build 0;`cargo check --all-targets` 0;`cargo test -p api-server game_distribution` **79 passed**;`cargo test -p module-game-distribution` **97 passed**;`cargo test -p spacetime-module` **278 passed** / 1 ignored;`cargo test -p spacetime-client` 34 passed;DTO parity **58 组 / 10 构建器**;`check:spacetime-schema` 0(96 tables);`check:encoding` 0(5399 files);`cargo fmt --all -- --check` 0;`git diff --check` 0。
已知限制(**已上报,待拍板**):主题详情的 `roots` 沿用页上限 50,`memberCount == roots.len()`,因此可见成员 > 50 时**静默截断且无「还有更多」标志**——设计文档未定义该行为,三个备选(去上限 / 加成员游标 / memberCount 报真实数)待定;本轮不改契约。
|
||
|
|
2fa201e0dc |
feat(游戏共创): 共创主题(一)数据模型与领域纯函数
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m30s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m12s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m44s
Project CI / Backend tests (pull_request) Has been cancelled
Project CI / Frontend tests (pull_request) Has been cancelled
Project CI / AI game creator shell web tests (pull_request) Has been cancelled
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
按 `docs/【技术方案】游戏共创与作品Fork-2026-10-03.md §3.10` 与里程碑《共创主题与作品树-2026-10-06》的清单 A/B/F 落地(本块**不含** procedure / 路由)。
- **表**:`game_distribution_theme`(`theme_id` 主键、`name`/`summary`/`badge`/`sort_order`/`status ∈ draft|published|archived`/`created_by_user_id`/`created_at`/`updated_at`)+ `game_distribution_theme_member`(确定性主键 `member_id = "{theme_id}:{root_game_id}"`,`theme_id`、`root_game_id` 各具名 btree 索引,`sort_order`、`created_at`);归档 / 下架只改公开投影,不删成员行。`migration.rs` 白名单登记两张表(随迁移导出/导入)。
- **生成绑定**:4 个 `game_distribution_theme*` 生成文件 + `module_bindings.rs` 入口(纯追加)。生成时**不覆盖工作树**(本仓共享工作树禁止 git 写命令):先 `spacetime generate --lang rust --out-dir <仓外临时目录>`,rustfmt 后只拷回本次相关文件,避免上一轮踩过的「rustfmt 漂移把别人文件一起改」。
- **领域纯函数**(`module-game-distribution/src/theme.rs`,风格照同目录 `collection.rs`):状态合法/公开可见判定、`game_distribution_theme_member_id`、成员可见性(**委托** `collection.rs` 的判定,不复制第二套)、`game_distribution_theme_root_acceptable(has_lineage_row)`、页大小(缺省 20 / 上限 50 / 0 取默认 / 超界截断)、游标编解码(`"{createdAtMicros}:{themeId}"`,Err 文案「主题列表游标格式无效」,已核对不含 404/409 关键词)、`sort_public_themes`(`created_at` 倒序 + `theme_id` 兜底)、`page_public_themes`(排序 → retain 游标之后 → 多取一条判 has_more;**先过滤再切页**的顺序语义写进文档注释)、`sort_theme_members`(`sort_order` 升序 + `member_id` 兜底)与 `page_theme_members`。
- **契约**:`shared-contracts` 新增 `GameDistributionThemeStatus` / `GameDistributionThemeReference` / `GameDistributionThemeSummary` / `GameDistributionThemeListResponse` / `GameDistributionThemeDetail` / `GameDistributionCreateThemeRequest` / `GameDistributionUpdateThemeRequest` / `GameDistributionUpsertThemeMemberRequest`,`GameDistributionGameSummary` 追加可选 `themes`(按 `collected` 先例只在详情路径下发);TS 镜像同步;parity 登记 **58 组**(响应构建器待路由落地后再登记,parity 要求函数真实存在)。
- **数据契约表目录**:补两张表小节,94 → **96**。
- **测试**:`theme.rs` 16 个单测(成员 id 确定性、状态三态与公开可见、成员可见性逐格与收藏判定等价、根判定、页大小边界、游标往返与非法输入、排序稳定性与同 `sort_order` 兜底、分页不重不漏)。
- **门禁**:wasm build 0;`cargo check --all-targets` 0;`cargo test -p module-game-distribution` **97 passed**;`cargo test -p spacetime-module` 274 passed / 1 ignored;DTO parity **58 组**;`check:spacetime-schema` 0(**96** tables);`check:server-rs-ddd` 0;`check:encoding` 0(5388 files);`cargo fmt --all -- --check` 0;`git diff --check` 0(8 个改动文件 0 删除行)。
|
||
|
|
063c04a1b3 |
docs(游戏共创): 共创主题设计定稿(§3.10 + 里程碑《共创主题与作品树-2026-10-06》)
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
需求形态(飞书):共创 Tab 下有多个「共创主题」,点进去是一棵(或一组)作品树;**主题由平台/运营命名,不由根作品决定**;作品在游戏 Tab 里仍作为独立作品展示,详情页有 Fork 入口并能看到 / 跳到对应主题页。本轮只落设计(不写业务代码),实施清单见里程碑文件。
设计要点与理由(详见 §3.10):
- 实体:`game_distribution_theme`(`theme_id` 主键、三态 `draft|published|archived`、`sort_order`、`badge`、`created_by_user_id`)+ `game_distribution_theme_member`(确定性 `member_id = "{theme_id}:{root_game_id}"`,`theme_id` / `root_game_id` 各具名 btree 索引)。
- **成员只允许根作品**:树查询 `/games/{id}/lineage` 按根聚合,否则同一棵树会在主题页里出现多次(去重/高亮打架);代价=要挂就挂它所属的根。跳到某一代节点属 UI 层(族谱页已有 `from` 高亮)。
- **允许跨主题多归属**:主题是运营叙事,「平台精选」「双人合作」会同时收录同一个根;确定性主键只保证**同一主题内**不重复。代价=作品详情要返回多值、运营需知悉。
- **树直接复用 `/games/{id}/lineage`**,主题侧**不重建**任何树查询/树整形(没有第二套可见性/排序可漂移);主题详情只返回成员根清单(N+1 本轮接受,批量树端点留白)。
- 可见性:公开侧只见 `published` 主题;成员只出现公开未删且有当前公开版本的根(**投影跳过但不删行**,重新公开自动回来);主题不存在/未发布 → **404**;已发布但可见成员为空 → **200 + 空成员列表**。
- 公开三接口:`GET /themes`(游标分页沿用默认 20/上限 50/非法 400/末页 null)、`GET /themes/{theme_id}`(+ `roots`)、作品详情 `themes` 增量(**先取该作品的根、再按根反查**,因此第 N 代作品也能跳到主题页)。
- 后台五接口(创建/编辑/增删成员/后台列表,均幂等),鉴权**复用既有 `require_admin_auth` + `AuthenticatedAdmin`**,不自造;后台 UI 不在本轮。
- 留白 5 项(各写不做理由与将来接法):主题封面图、slug/URL 别名、埋点统计、跳到某一代的高亮、批量树端点。
- 同步收敛:§6 里程碑表 M4 行指向新里程碑;§7 第 3 条「共创主题」从**待拍板**改为**已拍板采纳**,并把「主题级排序口径」留作唯一待产品确认项。
门禁:`check:doc-index` 0(252 份 md;新里程碑在 `plans/` 下免分类);`check:encoding` 0(5383 files);`git diff --check` 0。
注:§3.10 的前约 200 行因共享工作树/索引,被上一提交 `f703bc49d`(注释修正)一并带上;本提交补齐其余章节 + 里程碑文件。
|
||
|
|
f703bc49d4 |
docs(游戏共创): 修正收藏幂等注释与实现不符之处(换用户不是 409)
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
端到端实测(`scripts/check-game-distribution-collection-e2e.mjs`)发现注释与实现不一致,**行为本身无缺陷,改的是注释**: - `api-server/src/modules/game_distribution.rs:1389-1396`(`collection_request_digest` 文档注释):原写「同一个 key 撞到不同作品 / 不同用户」都被判成同键不同请求(409)。实际只有**换作品**是 409;**换用户**不会命中彼此收据(收据键 `(user_id, action, idempotency_key)` 本身含 `user_id`),两个用户各走各自的新请求、正常 200。已改写为「换作品 → 409;换用户 → 200 各自成功」,并注明这条注释曾写错。 - `api-server/src/modules/game_distribution.rs:1404-1408`(`collect_game` handler 幂等说明):同上(原文「同键不同请求(换作品 / 换用户)是 409」)。 - `api-server/src/modules/game_distribution.rs:7896-7897`(测试 `collection_request_digest_binds_user_and_game` 的描述):补上「换作品 → 409 / 换用户不会(收据按用户隔离)」;**断言未改**(它只断言摘要对两维度敏感,本来就与实现一致)。 - `docs/【技术方案】游戏共创与作品Fork-2026-10-03.md:339`(`§3.4` 接口表同一句话):同一口径修正。 - `module-game-distribution/src/collection.rs:173`:把易被误读为「客户端 Idempotency-Key」的注释改为明确的「主键派生必须对两个维度都敏感」,并说明与幂等收据的键无关。 `spacetime-module/src/game_distribution.rs:4717-4719` 的注释本来就与实测一致(「不同用户 / 不同作品即使共用同一个 Idempotency-Key 也不会互相命中」),未改。 门禁:`cargo check -p api-server --all-targets` 0(仅既有 1 warning);`cargo test -p api-server collection` 7 passed;`cargo fmt --all -- --check` 0;`check:encoding` 0(5382 files);`git diff --check` 0。 遗留(非本人文件,未改):`scripts/check-game-distribution-collection-e2e.mjs:32-34,537` 的说明文字仍在描述「api-server 注释与实测不一致」,现已一致,需该脚本作者同步(归另一会话)。 |
||
|
|
f09607d903 |
feat(游戏共创): 收录(收藏)列表补真分页(cursor + limit)
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
- 纯函数(`module-game-distribution/src/collection.rs`,与收藏另两条规则同文件):`encode/parse_game_distribution_collection_cursor`(`"{created_at_micros}:{collection_id}"`,复用仓库既有 `"{micros}:{id}"` 惯例;非法 → Err「收藏列表游标格式无效」→ 400)、`game_distribution_collection_page_limit`(0/缺省 → 20,超界 → 50)、`game_distribution_collection_page`(稳定全序排序 → 跳过游标之前 → 取 limit → 多取一条判 has_more,只对页内最后一条编码下一游标)
- 排序与顺序语义:`created_at` 倒序 + `collection_id` 升序兜底(主键、同用户内唯一 ⇒ 全序,翻页不重不漏);**先按可见性过滤、再排序切页**——游标位置必须落在已过滤序列上,否则被过滤的行会让下一页漏项(推理写进 tx 与纯函数注释);snapshot 只对最终页成员计算
- 模块:list 的 input 增 `limit` / `cursor`,result 增 `next_cursor`;api-server `my-collections` 加 `Query<MyCollectionsQuery>`,默认 20 / 上限 50(网格一屏 vs 单响应体积;与后台列表的 200 口径不必相等,理由写进注释),超界**截断**不报错,非法游标 400 透传不吞,日志带 `has_more`
- 契约:Rust DTO 增 `next_cursor`、TS 镜像同步、parity 登记(50 组一致)
- 文档:§3.4 的 `my-collections` 行补分页语义(默认/上限/游标格式/非法 400/末页 null/排序口径)
- 测试 13 条:纯函数 8(不足一页 → None;恰好整页不产生空游标;5 条 limit=2 连翻 3 页不重不漏;同 `created_at` 用 `collection_id` 兜底;limit=0 与超界;游标往返 + 7 种非法输入)、模块事务结构 2、api-server 3(默认与截断、`nextCursor` 透传、非法游标 → 400)
- 门禁:wasm build 0;`cargo check --all-targets` 0;`cargo test -p module-game-distribution` **81 passed**;`cargo test -p api-server game_distribution` **73 passed**;`cargo test -p spacetime-module` 274 passed / 1 ignored;DTO parity **50 组**;`check:spacetime-schema` 0(94 tables);`check:encoding` 0(5382 files);`cargo fmt --all -- --check` 0;`git diff --check` 0
|
||
|
|
eb7349ef3d |
test(游戏共创): 新增收藏(收录)端到端验收脚本
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
- 新增 scripts/check-game-distribution-collection-e2e.mjs(约 750 行):真实 HTTP 验收收藏链路,
复用 lineage 脚本的发布链路 helper(脚本头注释逐条标注出处),覆盖 9 组共 46 条断言:
· 幂等与「只一行」:两次不同键都成功(replayed=false);同键重放 replayed=true;两种情况都断言
my-collections 长度=1 且该作品只出现一次(无收藏计数接口,按工单允许的列表去重与长度证明)
· 同键不同请求:换作品 → 409(「幂等键对应的请求摘要不一致」);**换用户 → 200 新收藏**,与
api-server handler 注释「换用户也是 409」不一致(收据键含 user_id,模块侧注释与实测一致),
脚本按实测断言并在 NOTE 与报告里标注
· DELETE 幂等:已收藏 / 未收藏 / 从未收藏且未公开 三种情形都 200 {collected:false} 且不发 replayed
· 鉴权与缓存:三条路由未登录都 401 且带 Cache-Control: no-store(证明挂载而非 404/405);
已登录但缺 Idempotency-Key 的 PUT → 400
· 状态门:草稿 PUT → 409「作品状态不允许收藏(未公开或已软删除)」(含「状态」不含「不存在」,
不泄露未公开作品存在性);不存在的 gameId → 404「作品不存在」
· 下架 → 重新公开:收藏后下架读侧过滤掉该作品,重新公开(新版本审核通过)后又回到列表且仍只一行
(证明下架不删行、只在读侧过滤)
· 公开详情 collected:匿名 23 键无 collected;已登录 24 键恰好多 collected=true;
去掉该键后与匿名逐字段一致;公开目录同作品条目与匿名详情键集合与取值完全一致
· 跨用户隔离:A 收藏后 B 的列表不含该作品、B 的 collected=false
- package.json:新增 npm script check:game-distribution-collection-e2e
- 实测:本地 dev 栈(SpacetimeDB 3110 / api-server 8188)46 项 46 PASS / 0 FAIL / 0 SKIP
|
||
|
|
785152f70b |
网页端收藏:作品详情页收藏切换 + 我的收藏页签
Project CI / AI game creator shell Rust crates (pull_request) Successful in 5m50s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m38s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m38s
Project CI / Frontend tests (pull_request) Failing after 1m10s
Project CI / Backend tests (pull_request) Successful in 9m34s
Project CI / AI game creator shell web tests (pull_request) Successful in 4m3s
Project CI / Native shell tests (pull_request) Has been cancelled
Project CI / Repository checks (pull_request) Has been cancelled
- 客户端:新增 setGameCollection(PUT 带 Idempotency-Key;DELETE 不带)与 listMyCollections({games,nextCursor},条目形状与公开目录逐字一致,可复用同一套卡片);getGame 返回类型放宽为 GameDistributionGameWithCollection,collected 为可选
- 详情页:在动作区(「立即玩」旁,而不是共创信息卡——那张卡讲改编关系)新增收藏切换。三态严格区分:collected=true → 「已收藏」点击取消;collected=false → 「收藏」点击收藏;**缺字段(匿名 / 无效 token 被按匿名处理)绝不落成「未收藏」**,按钮 title 标注「登录后可收藏」,点击走既有登录门禁且不发请求
- 乐观更新 + 回滚:先切换本地态,失败回滚到服务端投影并用服务端 message 提示(role=status);成功以服务端返回的 collected 为准
- 广场卡片:卡片本身没有操作区,按「没有就别硬塞」不动
- 我的收藏:落在 /games/mine 的页签(「我的作品」/「我的收藏」)。理由:两者都是登录后的个人内容、该页已有登录门禁;且不需要新路由,避免牵动「路由表 + nginx×3 + Pingora allowlist」这组必须同批登记的清单。复用公开目录同一套卡片(shared 的 GameCard + GameCover),空态/失败重试/登录态照既有写法
- 分页按「将来会有 cursor」的形态写:读 nextCursor,非空才渲染「加载更多」并在点击时追加;服务端当前恒为 null,不伪造分页数据
- 文案统一「收藏 / 已收藏」(不用需求文档旧词「收录」)
- 测试:详情页收藏四例(已收藏→取消 / 未收藏→收藏 / 缺字段走门禁且零请求且不渲染已收藏 / 失败回滚+服务端文案);我的收藏两例(空态且默认页签不预取 / 列表渲染+卡片点击跳详情+无 cursor 不出现「加载更多」)
|
||
|
|
4b1f24a8fc |
feat(游戏共创): 收录(收藏)契约与文档同步(块 3/3)
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 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
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Has been cancelled
- `shared-contracts`:新增 `GameDistributionCollectionState { collected, replayed: Option<bool> }` 承载 PUT/DELETE 共用形状(PUT 才发 `replayed`);既有 `GameDistributionGameSummary` 增 `collected: Option<bool>`(公开详情登录时才带)——该类型只被 api-server 测试与 `GameDistributionListResponse` 引用,加字段不影响任何现有发送方
- `packages/shared` TS 镜像同步(`{ collected: boolean; replayed?: boolean }` 与 `GameDistributionGame.collected?: boolean`),并按既有约定登记进 `scripts/check-game-distribution-dto-parity.mjs`(PAIRS + 手拼响应构建器);注意 `json!` 字面量里不插注释行(parity 脚本抓顶层键时不剥注释,这个坑今天踩过一次)
- 技术方案:`§2.4` 入口矩阵补「收藏按钮 / 我的收藏(登录)与我的作品页同行风格」;`§3.4` 接口表补三条路由(含 404/409 语义、PUT 要求幂等键而 DELETE 不要求及理由、`my-collections` 只含公开作品且不删行)与公开详情 `collected` 的可见性口径;`§7` 把「收录/收藏」从范围外移除并标注已纳入落地
- 数据契约表目录:新增 `game_distribution_collection` 小节(主键确定性、两个索引、为什么不删行、为什么不建唯一索引),供 `check:spacetime-schema` 守卫通过(94 tables)
- 门禁:DTO parity 0(**49 组** Rust/TS 一致,较本轮前 +7);`check:spacetime-schema` 0(94 tables,对照新 merge-base `51cb05f4`);`check:encoding` 0;`git diff --check` 0
|
||
|
|
cc7d214136 |
feat(游戏共创): 收录(收藏)接口:三条路由 + 公开详情 collected(块 2/3)
- 新增 `collections` 路由组(整组 `require_bearer_auth` + `add_no_store_response_headers`,用户态读不会被任何缓存复用):
· `PUT /api/game-distribution/games/{game_id}/collection` → `{ collected: true, replayed }`,要求 `Idempotency-Key`(沿用既有 `idempotency_key(&headers)?`);同键重放 `replayed=true`,同键不同请求(摘要绑定 `(userId, gameId)`)→ 409,重复收藏(不同键)只留一行且成功
· `DELETE .../collection` → `{ collected: false }`,**不要求 Idempotency-Key**:按确定性主键删除本就幂等(不存在也算成功),没有「重放 vs 新意图」可区分;也**不要求作品仍公开**(下架后仍可清理)
· `GET /api/game-distribution/my-collections` → `{ games: [<public_game_payload>…], nextCursor: null }`,逐条复用既有公开目录的组装方式,只含当前公开可读作品
- 公开详情 `GET /games/{game_id}` 增量:**已认证**返回 `collected: true|false`;**匿名不返回该键**(选这种口径的理由:`false` 会把「未登录」说成「没收藏」,客户端无法区分就会渲染出错误的按钮态);携带无效/过期 token 时按**匿名**处理(沿用 `record_game_play` 的取舍——公开详情匿名可读,不该因此掉 401),因此该响应与纯匿名逐字节相同(单测断言)。实现复用既有 `auth::optional_access_token_from_headers`(`auth.rs:223`),**没有自造 extractor**;值来自新 procedure(真实投影),**未改动既有公开快照契约**
- **共享缓存核实(硬性项)**:公开详情所在的 `public_games` 路由组带 `add_no_store_response_headers`;`app::build_router` 只做 merge/背压/错误归一化/request_id/tracing/`attach_request_context`,**没有任何响应体缓存或预渲染**;本模块唯一进程内缓存是 `RELEASE_PACKAGE_CACHE`(键 = OSS 对象键、值 = 发行包字节),与 per-user JSON 无关。结论:**该路径无共享缓存,不需要绕开**
- 错误码:作品不存在 → 404;未公开/已软删除/无当前公开版本 → 409(文案「作品状态不允许收藏(未公开或已软删除)」刻意含「状态」而不含「已被删除」,以命中既有映射的 409 分支而非 404 分支)
- 测试(本文件内,新增 5 条):三条路由无 Bearer → 401 且 `no-store`;404/409/摘要不一致 409 的映射;摘要绑定 `(user, game)` 的幂等隔离;PUT/DELETE 的载荷形状;公开详情「匿名无 collected 且与目录负载逐字节相等、登录时 Some(true)/Some(false) 各出现且只多这一个键」
- 门禁:`cargo test -p api-server game_distribution` **70 passed**(含新增 5 条);`cargo check --all-targets` 0
|
||
|
|
77c30bb7b2 |
feat(游戏共创): 收录(收藏)数据模型与绑定(块 1/3)
- `spacetime-module`:新增表 `game_distribution_collection`(`collection_id` 主键、`user_id` / `game_id` 各具名 btree 索引、`created_at`)。**防重靠确定性主键** `collection_id = "{user_id}:{game_id}"`(与 `profile_save_archive.archive_id` 同一写法):同一 (用户, 作品) 在结构上不可能有第二行,不依赖「事务里先查后写」(那种写法只在单写者假设下成立,分片/并发时两个事务都可能先查到不存在再各写一行);正因主键即约束,**不需要**再建 `(user_id, game_id)` 唯一索引,两个索引只服务两个读取方向
- 下架(未公开/软删除)**不删除**收藏行:收藏是用户态事实,读侧投影时才按当前公开性过滤,作品重新公开后同一行自动回到列表(注释写明)
- 新增动作常量、4 个输入类型、3 个结果类型、3 个事务(collect / uncollect / list)+ 读态 helper + 4 个 procedure;`uncollect` 按确定性键删除且**不要求作品仍公开**(下架后拒绝取消会给用户留下清理不掉的脏行);`list` 走 `user_id` 索引 + 既有 `public_game_distribution_snapshot` 投影,只含当前公开可读作品
- 新增 `module-game-distribution/src/collection.rs`:两条纯函数(`game_distribution_collection_id`、`game_distribution_collection_visible`)+ 单测——后者用「同一行、四种可见性输入」直接钉住「已收藏 → 下架 → 列表不含 → 重新公开 → 列表又含」这条链路,不必起真库
- `migration.rs`:新表进白名单并补中文注释
- `spacetime-client`:record + 3 个 mapper + 4 个方法 + 2 个 RecordInput,并按既有做法只保留 `module_bindings/game_distribution*`(13 个新文件)与 `module_bindings.rs` 的生成改动、还原其余 rustfmt 漂移
- 门禁:wasm build 0;`cargo check --all-targets` 0;`cargo test -p module-game-distribution` 75 passed;`cargo test -p spacetime-module` 273 passed / 1 ignored(含 4 条新事务结构断言);`cargo fmt --all -- --check` 0
|
||
|
|
c3030eddba |
chore(游戏共创): 收尾清掉最后一条旧口径注释(成品包只作可玩参考 → 生成脚手架 + 参考副本)
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 / 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 / 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
|
||
|
|
f94946ef8e |
feat(游戏共创): 注册并处理 Fork 深链 genarrative://fork?gameId=…
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
冻结约定:`genarrative://fork?gameId=<gameId>`,同时接受 `genarrative://fork/<gameId>`。
方案(用官方两个插件,不手搓单实例与注册表):
- `tauri-plugin-single-instance`(开 `deep-link` feature)**第一个注册**:Windows/Linux 上深链是
「带 URL 启动第二个进程」,该插件把第二个实例的 argv 转交给已有实例,并(feature 生效时)
交给 deep-link 插件发事件——否则用户点第二次链接会再开一个窗口。macOS 上它用 Unix socket
做同样的收口(系统本身也只会有一个 .app 实例)。
- `tauri-plugin-deep-link`:`tauri.conf.json > plugins.deep-link.desktop.schemes = ["genarrative"]`。
· macOS:打包器按该配置往 `Info.plist` 注入 `CFBundleURLTypes`(插件 `config.rs` 里那句
`// Used in tauri-bundler` 是代码级证据),运行时由系统把 URL 交给已运行的 .app。
· Windows:运行时 `register_all()` 写 `HKCU\Software\Classes\genarrative`(`URL Protocol` 标记 +
`DefaultIcon` + `shell\open\command`,不需要管理员);安装器侧同样按该配置注册。
· Linux:运行时写用户级 `applications/<exe>-handler.desktop` 并调 xdg-mime(AGC 未以此平台分发,
但仍走同一条运行时路径)。
- 冷启动:插件在插件初始化阶段就解析 argv 并暂存,而应用 `setup` 晚于插件,因此
`initialize_fork_deep_link` **先取 `get_current()`**、再 `on_open_url` 订阅后续链接(漏了前者会
丢冷启动那一次)。
- 收到 URL 后:聚焦主窗口(unminimize + show + set_focus)→ 发 `game-fork-deep-link` 事件
`{ gameId | message }` → 渲染层把请求存进 store 并切回首页;**不自动下载**,用户按
「Fork 到我的项目」才走与手工输入完全相同的提交路径。正停在项目工作区时不强行切走
(避免把用户从进行中的项目里踢出来),请求留在 store 里等用户回到首页。
- 非法链接:解析出可读原因(空链接 / 非 genarrative 方案 / 非 fork 形式 / 缺 gameId / gameId 不合法)
一并聚焦窗口并交给渲染层展示,不做静默失败。
测试:`fork_game_id_from_deep_link` 的合法(两种形式、大小写、多余参数、game_id/id 别名)与
非法(空、错方案、错形式、缺 gameId、`../escape`、URL 编码斜杠、非 URL)各一组;`gameId` 形状
判据与取件路径共用(`is_safe_fork_game_id`);TS 侧覆盖载荷解析、订阅映射、深链预填后**不自动
下载**、非法链接展示原因且不请求。
|
||
|
|
96309c895c |
新增「原版与二创对比」页与路由登记(含 nginx×3 + Pingora allowlist)
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
- 新增路由 /games/compare(stage game-compare):STAGE_ROUTE_ENTRIES + SelectionStage 类型 + 壳层 lazy 引入/导航回调/渲染分支 + 页面标题,四处同批登记 - 三份 nginx SPA allowlist(deploy/nginx/genarrative.conf、deploy/nginx/genarrative-dev-http.conf、deploy/container/nginx.conf)与 Pingora MAIN_SPA_PATHS 同步放行 /games/compare(保持既有排序):check:nginx-spa-routes 18 SPA routes、check:pingora-route-parity 均绿 - 对比页左右两侧各自取数与降级:任一侧不可读(不存在/未公开/已软删除)或没有可玩版本时只降级那一侧,另一侧照常;显示标题、作者与代际;桌面左右并排、窄屏上下堆叠 - iframe 复用游玩页同一份嵌入:抽出 GamePlayFrame(sandbox=allow-scripts、allow=fullscreen、referrerPolicy=no-referrer、.game-player-frame),游玩页改为渲染它,不再存在第二套嵌入逻辑 - 入口:二创作品详情页在存在父作品时给出「与原版对比」;族谱节点仅在其父作品可见时给出同一入口 - 测试:对比页两侧渲染与 iframe 复用属性、单侧降级后另一侧照常;详情页入口(有/无父作品);族谱节点入口(父可见才出现);路由与标题登记;壳层「族谱节点→对比页」导航与对比 stage 渲染 - 顺手:按 eslint 自动修复两个文件的 import 排序 |
||
|
|
4cf00ac07d |
refactor(游戏共创): Fork 口径收敛为「把整个工程复制到你的项目里继续改造」
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
产品裁定:不再对外解释「可玩参考 + 素材 / 不能一键复刻工程 / 需自建合规工程」那套口径。 统一一句话:**Fork = 把整个工程复制一份到你的项目里继续改造(与 git fork 同理)**,动作名统一 叫 Fork。必要告知只保留一句:开启共创并上传工程时,整个工程(含素材与文档)会对他人可见, 且不可撤销。 - 入口(首页):`从平台作品 Fork` + 角标 `Fork`,说明压成一句「把平台上的作品整个工程复制到 你的项目里继续改造(像 git fork)。输入要 Fork 的作品 ID 或作品链接即可。」;按钮 `Fork 到我的项目`、加载态「正在 Fork:下载、校验并创建项目…」;成功态只留 「已创建项目《X》,Fork 自平台作品 <gameId>(来源已记录在 …fork-source.json)」, 删掉按取件形态分岔的长句。 - 发布面板:授权区只留一句「授权非「禁止共创」即允许他人 Fork 你的作品;开启共创并上传工程时, 整个工程(含素材与文档)会对他人可见,且不可撤销。」;确认项压成一句 「我已确认工程里没有不愿公开的内容,同意随本次发布公开整个工程(含素材与文档);一经发布 不可撤销。」,其后一行写「默认已勾选,可随时取消;不勾选则本次不上传工程源码。」 - 错误与进度文案:取件失败四态改为 Fork 措辞(`无法 Fork` / `该作品当前不能被 Fork(未公开、 已下架或没有公开版本)`),读取/下载/响应类错误改为 `读取 Fork 信息失败` / `下载 Fork 内容失败`, 超限提示改为「Fork 内容超过客户端下载上限,无法完成」;「未上传工程源码」提示改为 「他人 Fork 时只能拿到平台已发布的成品」;默认项目名前缀 `改编项目` → `Fork 项目`; 记录标签 `项目改编来源记录` → `项目 Fork 来源记录`。 - 实现事实(成品包形态的脚手架 + `reference/<gameId>/`)保留在 Rust 文档注释里,不再作为对外 表述;`collect_project_bundle_files` 等实现注释不动。 - 测试:入口用例改为断言一句话口径与「Fork 自平台作品」并断言旧的解释性表述已消失;面板用例 断言新的简短告知与「默认已勾选,可随时取消;不勾选则本次不上传工程源码」;错误文案断言同步。 |
||
|
|
928fb0e616 |
Fork 入口主路径改为唤起客户端(genarrative://fork),未失焦才降级为手动路径
- 新增 src/components/game-distribution/gameForkProtocol.ts 收口唤起:协议 genarrative://fork?gameId=<id>,用顶层 location.href 赋值(协议未注册时桌面浏览器只是「什么都不发生」,不会把页面导航走也不会被弹窗拦截,失败可观察),并导出降级等待窗口 FORK_LAUNCH_FALLBACK_DELAY_MS=1200 - 详情页点击「Fork 这个作品」直接唤起;失焦(window blur)或页面转入后台(visibilitychange=hidden)都视为客户端接手 → 只给状态反馈,不出降级面板 - 未失焦 → 经过降级窗口后展示手动面板(作品 ID / 完整作品链接 / 复制按钮 / 客户端指引 / 一句话告知) - 移动端与未知平台不尝试唤起(协议未注册时移动浏览器行为不可控),直接给手动路径 - 每条路径都有可观察反馈:正在唤起 / 已唤起 / 没有检测到客户端 / 当前设备无法直接唤起;定时器与失焦监听的清理覆盖卸载、重复点击、超时三条路径 - 顺手修掉同文件一个既有竞态:作者昵称补查的断言紧跟 findByRole(effect 尚未发出请求)导致偶发 0 次调用、让整份文件的门禁随机变红,改为 waitFor 等待 |
||
|
|
b6fe63b551 |
文案收敛:动作名统一 Fork,去掉「可玩参考 + 素材 / 不能重新发布」那套表述
- 动作名统一:「改造这个作品」→「Fork 这个作品」,「登录后可改造」→「登录后可 Fork」 - 面板指引改为「在陶泥儿客户端里打开「从平台作品开始创作」,粘贴下面的作品 ID 或作品链接。」 - 对外口径统一为「Fork = 把整个工程复制一份到你的项目里继续改造(与 git fork 同理)」,并保留一句话必要告知:整个工程(含素材与文档)复制后对他人可见,且不可撤销 - 删除「成品包只能可玩参考 + 素材」「不能直接再次发布」「参考产物在 reference/<作品ID>/」等展开说明(冻结口径 1) - 测试同步:断言新文案与「不可撤销」告知,测试名与注释不再出现「改造」 |
||
|
|
bdcefe91d1 |
Merge remote-tracking branch 'origin/master' into feat/game-fork
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m31s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m20s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m3s
Project CI / Frontend tests (pull_request) Successful in 2m54s
Project CI / Backend tests (pull_request) Successful in 7m36s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m26s
Project CI / Repository checks (pull_request) Successful in 6m25s
Project CI / Native shell tests (pull_request) Successful in 9m34s
# Conflicts: # deploy/container/nginx.conf # deploy/nginx/genarrative-dev-http.conf # deploy/nginx/genarrative.conf # server-rs/crates/api-server/src/modules/game_distribution.rs # src/components/game-distribution/GameDetailPage.tsx # src/components/game-distribution/GameDistributionPages.test.tsx # src/components/platform-entry/PlatformEntryActiveFlowShell.tsx |
||
|
|
51cb05f418 |
补充创作者主页与关注粉丝工程文档 (#638)
Project CI / AI game creator shell Rust crates (push) Successful in 5m56s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m36s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 7m37s
Project CI / Backend tests (push) Successful in 9m22s
Project CI / Frontend tests (push) Successful in 4m40s
Project CI / AI game creator shell web tests (push) Successful in 3m50s
Project CI / Repository checks (push) Successful in 6m30s
Project CI / Native shell tests (push) Successful in 8m32s
本 PR 补齐创作者主页与关注粉丝功能的产品合同和工程设计,供后续按里程碑实施;本次只修改文档。 桌面导航第四项进入自己的创作者主页,“我的”移到第五项。游戏详情可进入作者主页并关注/取消关注;自己或他人的关注、粉丝列表均公开可查看,点击用户头像或昵称可进入其主页。本人支持取消/重新关注、回关和移除粉丝,并明确两个关注方向独立。 工程设计包含关系表与 DTO、认证和事务边界、跨页状态同步、共享组件、深链及验收要求,拆分关系查询和页面交互两个里程碑。按用户要求,游戏列表沿用最多 48 项的现有限制,不增加游戏目录分页改造。 验证: - 已合入最新 origin/master(21c79f54a);共享决策记录冲突保留双方新增内容。 - npm run check:encoding 通过。 - npm run check:doc-index 通过。 - git diff --check 与暂存差异检查通过。 - 相对 master 仅六份文档发生变化;没有业务代码或 schema 修改,未执行业务测试或部署。 Reviewed-on: #638 Co-authored-by: Linghong <ink29535@proton.me> Co-committed-by: Linghong <ink29535@proton.me> |
||
|
|
96859666ff |
Merge pull request 'AGC 运行画面点选:canvas 玩法上识别 Phaser 4 / three.js 的已登记素材(#612)' (#624) from feat/agc-runtime-picking-engines into master
Project CI / AI game creator shell Rust crates (push) Successful in 5m29s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 7m5s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 6m28s
Project CI / Backend tests (push) Successful in 9m14s
Project CI / Frontend tests (push) Successful in 4m4s
Project CI / AI game creator shell web tests (push) Successful in 3m36s
Project CI / Repository checks (push) Successful in 7m37s
Project CI / Native shell tests (push) Successful in 10m32s
Reviewed-on: http://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/624 |
||
|
|
fa42cdb139 |
Merge pull request '修复在线游玩加载体验:加载期黑屏/空白与发行资源传输开销(#636)' (#639) from fix/game-play-loading into master
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Reviewed-on: http://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/639 |
||
|
|
df221f4eb9 |
收口评审 P2:点选条款限定引擎档、对象名上限入边界、素材 id 格式写入 skill
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m29s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m6s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m50s
Project CI / Frontend tests (pull_request) Successful in 3m26s
Project CI / Backend tests (pull_request) Successful in 7m48s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m3s
Project CI / Repository checks (pull_request) Successful in 7m15s
Project CI / Native shell tests (pull_request) Successful in 9m30s
- 功能说明第 8 条「命中区域裁剪在画布内」限定为引擎档,并说明 DOM 档仍是元素矩形;证据表对应行同步对齐 - 功能说明「边界与非目标」新增一条:引擎档对象名超过 40 字符按取不到处理(有意保留),宿主侧标签上限另为 120 - SKILL.md「运行画面点选契约」第 3 条补一句:素材 id 不含空白与路径分隔符(localAssetId 由客户端生成) - skill-pack 指纹同步(agc-web-game-development version 2026-08-26.42 与 sha256) |
||
|
|
364b1aa342 |
Merge remote-tracking branch 'origin/master' into feat/agc-runtime-picking-engines
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
- 冲突解决(3 处): - agc-game-production-workflow/SKILL.md:双方都保留——第 4 条用本分支版本(含「运行画面点选契约」指针),第 5 条用 master 的新构建验证口径 - agc-skills/manifest.json:先取 master 侧,再用 `agc:skill-pack:sync` 按合并后内容重新生成 version 与指纹(2026-08-26.41),`skill-pack:check` 通过 - pitfalls.md:两侧新增条目全部保留(按日期倒序:本分支 10-05 / 10-04 条目在前,master 的 10-04 / 10-03 条目紧随,未改动任一方内容) - 无冲突但人工核对:`agc-web-game-development/SKILL.md`(本分支「运行画面点选契约」小节 + master 内容都在)、 `index.tsx`(我们的 `handleRuntimeInspectSelection` 回调体逐字不变,master 新增内容都在) - 桥脚本 sha256 未变(9513020cfffa79128fb6355d44cdaabe666eefecbd96b7e807b15d2c4601bf76),真机证据对应字节不变 |
||
|
|
d7c4f82d7d |
修复自查发现的门禁与体验问题:格式、超时态语义、压缩 CPU 边界
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m48s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m3s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 6m33s
Project CI / Frontend tests (pull_request) Successful in 3m24s
Project CI / Backend tests (pull_request) Successful in 7m29s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m55s
Project CI / Repository checks (pull_request) Successful in 6m33s
Project CI / Native shell tests (pull_request) Successful in 9m0s
- 新增 Rust 测试补齐 cargo fmt;ESLint 按仓库约定收口:常量与解析函数拆到 platformGameLoadingSurfaceModel.ts,删除 GAME_PLAY_STARTUP_TIMEOUT_MS 常量别名(测试直接用共享常量) - 共享加载面新增 stalled:宿主判定等待不再推进时停掉进度动画并把 aria-busy 置回 false(去掉与 role=status 重复的 aria-live) - 后台试玩超时时传入 stalled,避免「一边转圈一边说超时」 - Accept-Encoding 权重参数名改为大小写不敏感(RFC 9110 §12.5.3),GZIP;Q=0 视为拒绝并补用例 - 发行资源压缩分级:≥2 MiB 改用 level 1(zlib 端实测约 1/3 耗时、压缩比差约 3%),小文件仍用 level 6 - 文档:主规范补压缩与条件请求契约(并写明 60 秒窗口与撤销窗口不放宽)、nginx README 修正 Pingora 压缩关闭时的口径、pitfalls 补 CPU 边界与措辞修正 |
||
|
|
126fb7bc06 |
test(游戏共创): 两个浏览器 e2e 的登录等待抗冷启动(Vite 首次编译)
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m3s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 5m53s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m31s
Project CI / Frontend tests (pull_request) Successful in 2m58s
Project CI / Backend tests (pull_request) Successful in 7m44s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m6s
Project CI / Repository checks (pull_request) Successful in 6m45s
Project CI / Native shell tests (pull_request) Successful in 9m32s
- rebase 到 origin/master 后的第一次实跑里,lineage 与(同源的)fork 脚本在浏览器段都卡在
`page.waitForSelector('.platform-account-entry', { timeout: 120_000 })` 并超时:
Vite dev server 重启后首次导航要现编译整张模块图,rebase 引入大量新代码时首屏会超过 120s。
证据:两次运行都在该行超时退出;随后用 Playwright 直接探针(同一 URL)页面正常渲染、
`.platform-account-entry` count=1、无 pageerror(仅两条未登录态 401 的资源错误),
紧接着再跑同一脚本即 99/99 全绿。
- 修法:登录等待改成 240s + state:attached,并在超时后重载一次再等(避免把「开发服务器还没编译完」
误判成页面坏了)。两处改动逐字相同,lineage 与 fork 脚本各一处。
- 实测(HEAD 3ae0d129e,固定端口栈 3110/8188):lineage 99/99、fork 48/48、project-bundle 72/72;
三条只读门禁 policy-parity / dto-parity / spacetime-schema 均 exit 0。
|
||
|
|
05cf5b9547 |
docs(游戏共创): 修掉 §3.5.2 里那处嵌套加粗
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
- `docs/【技术方案】游戏共创与作品Fork-2026-10-03.md:362`:`- **服务端复核(已落地,**不信任客户端**)**:` → `- **服务端复核(已落地、不信任客户端)**:`(去掉内层多出的一对 `**`) - 之前改不掉的原因搞清楚了:edit 工具的模糊匹配按**整行**比对,我先前传的是该行前缀(该行还有 300+ 字符),相似度自然只有六七成;这次把**整行**(429 字符)作为 old_string 即一次命中。渲染上本来就是连续加粗,本次只是源码观感与「别让人以为漏了个闭合标记」 - 门禁:check:doc-index 248 份 OK;check:encoding 5305 files OK;git diff --check 0 |
||
|
|
3ae0d129ef |
test(游戏共创): 一致性门禁把内容嗅探与 magic bytes 纳入比对(补最后一处规则空白)
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
- `scripts/check-project-bundle-policy-parity.mjs` 新增第 4 维:**凭据内容嗅探与嵌套包 magic bytes**。复核指出这两套「换名字也拦得住」的规则只靠常量表表达,过去门禁对 `sniff|PEM|AKIA|magic|secret` 零命中(只有看得见的规则才有守卫),本次补齐: · `SECRET_CONTENT_TEXT_EXTENSIONS` / `SECRET_CONTENT_SIGNATURES`(字符串数组集合相等)、`SECRET_CONTENT_MAX_SNIFF_BYTES` / `NESTED_ARCHIVE_SNIFF_BYTES`(整数相等)、`SECRET_AWS_ACCESS_KEY_PREFIX` / `PEM_PRIVATE_KEY_LINE_PREFIX` / `PEM_PRIVATE_KEY_MARKER`(字符串逐字相等)——新增 `rustStringConst` helper 抓 `&str` 常量 · `nested_archive_format` 里的 `b"..."` magic 字面量按「服务端 ⊆ 客户端」比对(客户端可以先拦、不能漏拦),并用新的 `byteStringLiterals` helper 抽取;两侧空集合一律 throw - **「故意改一侧会红」已验证(两处,均已原样恢复)**:① 从客户端 `BUNDLE_FORBIDDEN_PATH_CHARS` 去掉 `'?'`/`'*'` → 报两条「路径段禁止字符…客户端没有」;② 从客户端 `SECRET_CONTENT_SIGNATURES` 去掉 `ghp_` → 报「嗅探规则不一致:服务端 …=ghp_|github_pat_|…,客户端 …=github_pat_|…」 - 文档 §3.5.2 与 §5.2 同步门禁的四个覆盖维度(目录/前缀/后缀/全名 + 上限、路径形状、内容嗅探特征表、magic bytes),并把 §5.2 的门禁行从「P1-a」扩为「P1-a + 嗅探维度」 - 门禁:`check-project-bundle-policy-parity` 0(服务端 51 条规则全被客户端覆盖;嗅探:`ghp_/github_pat_/xoxa-/xoxb-/xoxp-` + `AKIA` + PEM 标记;magic:7z / PK / Rar! / gzip / ustar;窗口 512 字节两侧一致);`check:doc-index` 248 份 OK;`check:encoding` 5305 files OK;`git diff --check` 0 |
||
|
|
15024dab0a |
docs(游戏共创): M2b 证据表与里程碑改为冻结点的实测数字(含补强证据与已知留白)
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
- 技术方案 §5.2 三处过期数字按**自己去代码里数一遍**的结果改:校验器单测 16 → **19**(`project_bundle::tests`)、模块总测 69 → **73**、e2e 由「46 项 46 PASS」改为「**51 个 `check(` 调用点**」(复核另按 72 条断言计,差值来自同一 `check` 内的多条子断言);「`package.rs` 零改动」改为「已同批补强(读取层按声明封顶 + 压缩比分母改为条目压缩字节,+70/−4)」;客户端行补「打包器 **24 条单测**、上传前按 `max_archive_bytes` 预检、定向 `cargo test -- project_bundle` **24 passed**」 - 技术方案 §5.2 新增四行:**校验器对抗性补强(P0-a/b/c)**(凭据目录/前缀/后缀/全名 + 小文本内容嗅探、嵌套包扩展名并集 + magic 嗅探、读取层封顶、新错误变体 `CredentialDirectoryNotAllowed` / `SecretContentDetected`)、**执行级交叉证据(P1-b)**(dev-dependency 交叉测试)、**一致性门禁路径形状维度(P1-a)**(含「故意破坏会红」实测)、**已知留白** - 里程碑:`前置依赖 ②` 里「客户端预检属待办」改为**已完成**并给出代码位置(`write_project_bundle_zip` 内 `bytes.len() > limits.max_archive_bytes` 失败关闭);证据行与验收项重数、并补齐补强后的新增规则;新增「已知留白」条目(`.bz2`/`.xz`/`.zst`、`*.db`/`*.sql`、`.gcloud`/`.azure`/`.password-store`、CP437 条目名与 `external_attributes == 0`、峰值内存未量测、内容嗅探误报率无实测) - 本块在 rebase 之后完成,基线为新的 merge-base `21c79f54a` - 门禁:`check:doc-index` 248 份 OK;`check:encoding` 5305 files OK;`git diff --check` 0 |
||
|
|
df43ffd05a |
test(游戏共创): 工程源包 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
- scripts/check-game-distribution-project-bundle-e2e.mjs 新增 A7 对抗段(脚本内自带裸 ZIP 写手, 可逐字节控制条目名、unix mode 与 central directory 声明值),逐条断言 422 + details.reason: · A7-1 路径穿越:foo/../bar、/etc/passwd、C:/evil.txt、a\..\b、./x、a//b → InvalidPath · A7-2 路径形状:a/secret.、a/secret(尾随空格)、src/a?.ts、a*b.ts → InvalidPath · A7-3 大小写变体:Node_Modules/… → DependencyDirectoryNotAllowed;.GIT/HEAD → VersionControlDirectoryNotAllowed;.AGENT/x → LocalStateDirectoryNotAllowed · A7-4 符号链接条目(external attrs=0o120777)→ SymlinkNotAllowed · A7-5 嵌套包改名 deps.dat(zip magic)→ NestedArchiveNotAllowed · A7-6 凭据:.aws/credentials、.ssh/id_ecdsa → CredentialDirectoryNotAllowed;.htpasswd、service-account-prod.json、terraform.tfstate、app.p8 → SensitiveFileNotAllowed · A7-7 内容嗅探:无扩展名文件里的 PEM 私钥块 → SecretContentDetected · A7-8 声明说谎:STORE 条目声明 1 字节、实际 4096 字节 → ReadFailed(失败关闭;非内存量测) · A7 收尾:全部拒绝后该版本仍 bytes=0/sha256 空;对照包(.dat/文本/无扩展名/PNG)200 不被过度拦截 - 修正一处我此前的错误判断:`./x`/`a//b` 并非「被 zip crate 归一化后放行」,而是被校验器自身的 路径形状检查(空段/`.`)拒为 InvalidPath(zip-2.4.2 types.rs:537-555 只拒 NUL/根/`..` 逃逸, 且返回未归一化原串);脚本 NOTE 已按实测与源码改正,两条也改为严格 422 断言 - 实测:本地 dev 栈(SpacetimeDB 3110 / api-server 8188)72 项 72 PASS / 0 FAIL / 0 SKIP; 同栈回归 lineage-e2e 99/99(真实发行包上传,覆盖 package.rs 读取封顶改动)与 fork-authorization-e2e 48/48 |
||
|
|
88368d0418 |
test(游戏共创): 一致性门禁补「路径形状」维度,并同步校验器文档
- `scripts/check-project-bundle-policy-parity.mjs` 新增第 3 维:服务端 `normalize_archive_path` 的拒绝形状必须被客户端打包器的三个常量逐 token 覆盖——**哨兵段**(`.` / `..`)与**路径段结尾字符**(空格 / 点)分别用 `rustStringArray` / `rustCharArray` 抽取,**路径段禁止字符**同时抓 `.contains('x')` 与 `matches!(expr, 'a' | 'b')` 两种写法;再单独钉「两侧都必须显式拒 `/` 开头与 `\`」两条独立分支(它们靠常量表覆盖不到)。**空集合一律 throw**,沿用既有「抽不到不许当绿灯」的写法
- 修掉我自己在第一版里写错的两处(由补强过程中的实测发现,并已用临时副本验证修法):① `BUNDLE_FORBIDDEN_PATH_SEGMENTS` 是 `&[&str]` 而非 `&[char]`,须用 `rustStringArray`(`".."` 也无法用 char 字面量表达);② 反斜杠分支的正则写成了 4 个字面反斜杠,而源码是 `contains('\\')`(2 个),已改为 `/contains\('\\\\'\)/u`
- **「故意破坏会红」已验证**:临时从客户端 `BUNDLE_FORBIDDEN_PATH_CHARS` 去掉 `'?'` 与 `'*'` → 门禁立即报两条「路径段禁止字符:服务端有「?」/「*」,客户端没有」,随后已原样恢复
- 文档 `§3.5.2/§3.2.3`:校验器清单同步到新规则(新增目录/前缀/后缀/全名 + 内容嗅探及其取舍理由、嵌套包扩展名并集与 magic 嗅探、读取层封顶与压缩比口径更正、路径形状维度),并新增**产品口径「知情同意」**一条——清单是黑名单,`docs/`、`*.pdf`、`notes.txt`、截图等不在任何拒绝集内会原样外发,发布面板必须写明「整个项目目录(除少数排除项)会原样公开」
- 门禁:policy parity 0(服务端 51 条全被客户端覆盖 + 路径形状维度 OK);`check:doc-index` 248 份 OK;`check:encoding` OK;`git diff --check` 0
|
||
|
|
10d1fcbc5f |
fix(游戏共创): 工程源包校验器对抗性补强(P0-a/b/c + 执行级交叉证据)
对抗性复核(`local://verify-m2b.md`)确认三个真实缺口,本次按 P0 → P1-b 补齐;两端规则逐条对齐,parity 门禁仍绿。 - **P0-a 凭据/依赖识别**:任意层级目录新增 `.aws` / `.ssh` / `.kube` / `.docker` / `.gnupg` / `.terraform` / `.secrets`(服务端新增独立变体 `CredentialDirectoryNotAllowed`);文件名前缀新增 `credentials` / `id_ecdsa` / `id_dsa` / `terraform.tfstate` / `service-account`;后缀新增 `.jks` / `.keystore` / `.ppk` / `.p8` / `.kdbx` / `.der`;全名新增 `.htpasswd` / `.pgpass`。另外对**小体积文本条目**(≤256 KiB、扩展名在白名单或无扩展名)做内容特征扫描:PEM 私钥块、`AKIA`+16 位、`ghp_`/`github_pat_`/`xox*-`(服务端新增 `SecretContentDetected`);更宽的前缀启发(如 `sk-`)**故意不做**并在注释里写明理由(误报会直接阻断作者发布) - **P0-b 规模检查不再被声明值骗**:两侧校验器改为**读取层封顶**——`by_index(index)?.take(declared_size + 1)` + 预分配收紧到单文件硬上限 + 多读 1 字节即判溢出、短读同样失败;压缩比分母从整包字节改为**该条目自身的压缩字节**;**同一修法同步到发行包校验器**(`package.rs` 原有同款洞),常量注释改成与实现一致(明确它不是整包炸弹防线) - **P0-c 嵌套包识别**:扩展名并集 `.zip/.tar/.gz/.tgz/.7z/.rar/.jar/.whl/.nupkg` + **magic bytes 嗅探**(zip/7z/rar/gzip/tar-ustar),改名成 `.dat` 也拦。**一处有证据的偏离**:`by_index_raw` 拿到的是**压缩流**(zip 2.4.2 文档与实测一致),按它嗅探会漏拦,因此改为对**已按声明大小封顶读出的解压内容**判 magic;客户端命中即报错并点名路径与格式 - **P1-b 执行级交叉证据(走 dev-dependency 路,未退化到 fixture)**:客户端 crate 加 `[dev-dependencies] module-game-distribution`,新测试用真实打包器产出字节喂给服务端 `validate_project_bundle_zip` 断言接受(并逐项比对摘要/字节数/条目顺序),再手造含 `.env` 与 `node_modules` 的包断言服务端独立拒绝 - 客户端同时补上**路径形状检查**(`BUNDLE_FORBIDDEN_PATH_CHARS` / `_SUFFIX_CHARS` / `_SEGMENTS` + `bundle_entry_path_shape_error`),修掉「macOS/Linux 上 `src/a?.ts`、`x.` 客户端能打、服务端必 422」的不一致;客户端压缩比预检也改按条目压缩字节 - 测试:`cargo test -p module-game-distribution` **73 passed**(+4);AGC `cargo test -- project_bundle` **24 passed**(含交叉证据那条);**发行包回归**:`cargo test -p api-server game_distribution` **60 passed / 0 failed**(`package.rs` 改动无行为回归,新增「声明说谎 → ReadFailed」用例) - 门禁:`cargo check --all-targets` 0;两端 `cargo fmt --check` 0;policy parity 0(服务端 51 条全被客户端覆盖);DTO parity 0;schema 0;encoding 0;`git diff --check` 0 |
||
|
|
18d19d5d54 |
fix(游戏共创): 源码公开确认改回默认勾选(与「允许共创即开放源码」口径一致)
按产品裁定回落默认值:保留主动取消能力与全部知情告知,但确认项**默认已勾选**—— 「允许共创本身就意味着源码开放,不设独立开关」,默认不勾选等于第二个开关,与本面板 自己写着的那句「没有单独的源码可见性开关」自相矛盾。 - `projectBundleConfirmed` 初值 true;每次重新选择授权档位都重置为「非禁止档位即已勾选」, 避免把上一次的取消状态带到新的档位选择上(禁止共创时不显示该确认项,值本身无意义)。 - 告知文案一字不减(整个项目目录原样公开、含文档/截图/设计稿/笔记、只自动排除依赖/版本库/ 构建产物/常见凭据、一经发布不可撤销、无法收回已下载内容)。 - 新增一句提示说明默认值来源:「上面这项默认已勾选(平台按「允许共创即开放源码」的默认帮你 勾上,不是替你做决定——你可以随时取消)。」——避免作者以为平台偷偷替他决定。 - 未勾选时的后果提示保留,仍写清「不勾选则本次不上传工程源码……」。 测试(面板 23 条):默认「禁止共创」无确认项;切到非禁止档位后确认项出现且**已勾选**, 并逐条断言告知文案 + 「默认已勾选/可随时取消」;默认状态提交不携带 `includeProjectBundle` (交给原生按授权执行上传);作者主动取消后提交携带 `includeProjectBundle: false`(这是 「作者能退出」的证据);重新选择档位回到已勾选;切回「禁止共创」确认项消失。 |
||
|
|
7972089a5f |
fix(游戏共创): 工程源码公开改成作者的主动知情确认,文案如实告知外发范围
背景(对抗性复核 D.7):工程源包的拒绝规则是**黑名单**,作者的 `docs/`、`*.pdf`、`notes.txt`、 截图、设计稿、会议记录等不在任何拒绝集内,会被原样外发。也就是说「授权非禁止 + 上传工程源码」 = **整个项目目录(除少数排除项)对任何登录用户公开**。原来那句「同时公开工程源码……取消勾选则 只能被试玩」是免责声明式措辞,不足以构成知情同意。 文案(最终措辞): - 授权三态说明统一口径:「授权非「禁止共创」即允许他人改编(**没有单独的源码可见性开关**); 是否随本次发布把工程源码一起交出去,由下面的确认决定。上架后授权只能单向提升,不能撤销。」 - 确认项(仅授权非「禁止共创」且首次发布时出现): 「我已确认项目里没有不愿公开的内容,同意随本次发布公开**整个项目目录**:源码、素材与其中 其它文件(含文档、截图、设计稿、笔记等)都会对任何登录用户公开;只会自动排除依赖目录、 版本库、构建产物与常见凭据文件。一经发布**不可撤销**:即使之后把授权降级,也无法收回别人 已经下载的内容。」 - 紧随其后写清不勾选的后果:「不勾选则本次不上传工程源码:作品仍可正常发布,但他人只能拿到 已构建成品试玩与参考,不能直接重新发布。」 交互选择:**默认不勾选 + 不勾选就不上传**(不是禁用发布按钮)。 - 选它的理由:① 它把第 3 点做成了真正的「主动确认动作」;② 发布本身不被阻断——工程包缺失本来 就不影响发布(服务端与原生链路都按「可选资产」设计),禁用按钮反而会把「禁止共创 / 只想先发 出去」的作者一起挡住;③ 勾选状态只作用于本次版本上传,不引入平台侧的独立开关,授权语义不变。 - 注意:这一条**取代**了早先「授权非禁止时默认帮作者上传」的默认值——知情同意优先于默认帮忙; 若要把默认值改回勾选,只需把 `projectPackageConfirmed` 初值改成 true 并翻转对应断言。 测试:改写两条面板用例——授权开放后确认项出现且**默认未勾选**、九处告知文案逐条断言(整个项目 目录 / 含文档截图设计稿笔记 / 只自动排除四类 / 不可撤销 / 无法收回已下载内容 / 不勾选则不上传 / 没有单独的源码可见性开关)、未确认时显式传 `includeProjectBundle: false`(不上传但照常发布)、 确认后不带该字段(由原生按授权执行上传)。 |
||
|
|
843ecbf4a2 |
chore(游戏共创): 删掉打包器的 dead_code 豁免,并只保留真正被用到的范围分支
上传链路已在 `5dd9d1c5d` 接好,`#![allow(dead_code)]` 的豁免期结束: - 删掉模块级 `#![allow(dead_code)]`,注释改成写明调用方(发布链路的 `upload_project_bundle_for_version`,取件侧摘要校验共用 `sha256_hex`)。 - 移除豁免后只剩一条 dead code:`ProjectBundleScope::GameDirectory` 从未被生产代码构造 (发布链路一律上传整个项目——收到方要能直接打开并重新构建,只带 `game/` 会丢掉根 `assets/`)。 该变体承载的是独立能力(不同的遍历根 + 剪枝 + 「没有 game/ 目录」失败关闭),且已被两条用例 覆盖,因此按仓库做法标 `#[cfg(test)]`(连带的 match 分支同步 cfg),并在注释里写清「真有生产 调用方时去掉 cfg」;没有连带删除其它代码——模块里其余函数与常量都已被生产路径引用。 |
||
|
|
779bbd1157 |
docs(游戏共创): M2b 文档回填两处实现偏差 + 状态改已实现(含 M2b 证据表)
- 技术方案 §3.4:上行路由行按端到端实测补两条按实现为准的细节——① **非作者上传返回 404 而不是 403**(`load_owner_version_or_404` 把 owner 不匹配按「版本不存在」处理,与发行包上行族同口径,不泄露版本存在性、响应不含对象键);② **阶段门先判「已存在」再判档位**(`ensure_project_bundle_uploadable`:`project_bundle_bytes > 0` → 409 `PROJECT_BUNDLE_ALREADY_EXISTS`,再看 `status` → 409 `PROJECT_BUNDLE_UPLOAD_NOT_ALLOWED`),因此「已公开且已有工程包」返回 `ALREADY_EXISTS`,「已公开但无工程包」才返回 `UPLOAD_NOT_ALLOWED`。证据:`check-game-distribution-project-bundle-e2e` 的 A5 / A5b / A6 及其 NOTE - 技术方案 §3.5.2:AGC 侧「上传」「下载与建项」由「接线待做」改为**已落地**(发布时上传且失败不阻断、按 `source` 分支建项、来源记录 v2 兼容 v1、取件提示按形态措辞),并写明两端规则由 `npm run check-project-bundle-policy-parity` 机器门禁钉住一致 - 技术方案 §3.5.4:路线 B 由「服务端已实现、客户端接线待做」改为**两端均已落地** - 技术方案 §3.7:补 M2b 口径——共创写入仍复用 `game-distribution:publish`(未新增灰度键);内容下发与工程包上传当前只有 `tracing` 日志,三个结构化埋点事件**仍未接**(待产品定事件口径) - 技术方案新增 §5.2「M2b(工程源包)实施证据」表:三列与契约、校验器(16 条单测 / 上限镜像)、上行路由族(api-server 60 passed)、下行优先与缓存不串味、端到端 `check:game-distribution-project-bundle-e2e` **46/46**、两端规则 parity 门禁、客户端 vitest 1961 passed 与定向 cargo test 17+16;并显式登记「backfill 明确不做」「真实极端边界与结构化埋点待补」 - 里程碑「作品工程源包与一键改造」:Status → `implemented-local`;新增「实现进展(2026-10-05)」列出两端提交 SHA 与证据命令、以及四条未做项及原因;验收标准改为 ✅/⏳/⏭ 三段并有证据指向(阶段门顺序、非作者 404、半包清理等按实测更新);证据要求补上已落地的命令清单与「真机建项链路仍未跑」的括注 - 门禁:`check:doc-index` 248 份 OK;`check:encoding` 5304 files OK;`git diff --check` 0 |
||
|
|
09a11ac215 |
fix(游戏共创): 取件成功提示按两种取件形态如实措辞
M2b 之后取件有两种形态:工程源包解压即可编辑工程(没有 reference/ 参考副本),成品包才放在 reference/<gameId>/ 作可玩参考。原来的成功提示写死「参考产物在 reference/<gameId>/ 下」, 对工程源包形态是错的。改为两种情形都说明,并同步测试断言(仍保留「改编自 X」这条来源事实)。 |
||
|
|
1a4af39e56 |
feat(游戏共创): 取件按 source 分支建项,来源记录升到 v2(兼容 v1)
- 来源记录 v2:新增 `source`(`package` / `project`),当前写入 `agc-fork-source.v2`;
解析同时接受 v1——v1 没有该字段,按「成品包」解释(当时只有这一条取件通道),
因此老项目仍能正确读出「改编自哪个作品」,发布时照常带上声明。未知版本与非法取值仍按
「没有来源记录」失败关闭。
- 建项按取件形态分支(`create_project_from_platform_fork_at` 增加 `source` 入参,远程
`source` 取值在命令层映射):
· `project`(工程源包):先把包解到**项目根**再走标准初始化。源包自带
`package.json` / `vite.config.*`,初始化会跳过 npm 脚手架判据,只补 `.agent` 身份、
目录与 agent.db,得到一份能继续改、能重新构建的工程;不再生成 `reference/` 参考副本。
· `package`(成品包):维持原形态(合规脚手架 + `reference/<gameId>/` 可玩参考)。
- 解压仍走既有门禁(`extract_template_archive`:条目数/单文件上限、拒符号链接、条目路径
只允许项目内相对路径;ZIP 根 == 项目根与模板包同一合同)。
- 命令文档同步说明两种形态的差别(工程源包=解压即工程;成品包=可玩参考,不能直接再发布)。
测试:新增「工程源包建项得到可编辑工程」用例(源码逐字节不被脚手架覆盖、`.agent` 身份已补、
无 `reference/`、记录 source=project)与「v1 记录仍可读且按成品包解释」用例;
既有成品包建项用例补断言 source=package。
|
||
|
|
1fc78faff2 |
feat(游戏共创): 授权非禁止时随版本上传工程源包(M2b 上传链路,失败不阻断发布)
上传链路(复用既有分片续传,不另写一套): - `PackageUploadAsset` 收拢两条资产的差异(URL 资源段 `package` / `project-bundle`、文案、 偏移错误码 `PACKAGE_UPLOAD_OFFSET_MISMATCH` / `PROJECT_BUNDLE_UPLOAD_OFFSET_MISMATCH`、 是否有创建版本时预登记大小),`upload_staged_game_package` 改名 `upload_staged_version_asset`,发行包与工程源包共用同一段主循环与重试/续传语义。 - 发布流程:建游戏 → 建版本 → 上传发行包 → 〔授权非「禁止共创」且作者没取消时〕打包工程源包 并上传 → 送审。插入点必须在送审**之前**:服务端只允许 `awaiting_upload` / `upload_failed` 档位补传工程包,送审后即 409。 - 失败关闭与不阻断:工程源包打包/上传任何失败都只写进结果的 `projectBundleWarning` (「未上传工程源码,本作品只能被产物级改编:…」),发布本身照常成功;作者的作品仍可玩可发布。 - 进度复用既有 `game-package-upload-progress` 事件(phase 仍用 upload/verify,文案区分资产), 渲染层的阶段联合是闭集,不新增阶段名。 面板与渲染层: - 新增「同时公开工程源码」勾选:仅当授权非「禁止共创」且为首次发布时出现,默认勾选, 文案写清「允许他人下载并使用你的工程源文件(含素材),且不可撤销」; 取消勾选才显式传 `includeProjectBundle: false`,其余情况交给原生按「授权非禁止即上传」判断, 渲染层不重复实现同一套默认值。 - 成功页如实展示 `projectBundleWarning`,避免作者误以为源码已随版本公开。 测试:Rust 新增「两条资产的路由段/偏移错误码/预登记大小差异」用例,并更新既有 `upload_url` 用例; TS 新增勾选默认值、取消勾选显式 false、未上传提示三条,服务层新增「true/缺省不带字段、 false 才带」用例。 |
||
|
|
360cb9830a |
test(游戏共创): 新增工程源包(M2b)端到端验收脚本
- 新增 scripts/check-game-distribution-project-bundle-e2e.mjs:真实 HTTP 验收 M2b 上行族与下行优先 · A1 合法工程源包整包 PUT → 200;版本私有 payload 的 projectBundleBytes/projectBundleSha256 与上传一致且不含对象键 · A2 >8 MiB 不可压缩负载两片续传:chunkBytes=8388608,偏移 8388608→9438176,upload-state 权威偏移逐片一致,complete 后 bytes/sha256 一致 · A3 含 .env → 422 PROJECT_BUNDLE_VALIDATION_FAILED,且该版本仍 bytes=0 / sha256 空 · A4 二次上传 → 409 PROJECT_BUNDLE_ALREADY_EXISTS · A5 已公开且已有工程包再传 → 409 ALREADY_EXISTS(阶段门先判已存在);A5b 已公开但无工程包补传 → 409 PROJECT_BUNDLE_UPLOAD_NOT_ALLOWED · A6 未带 Bearer → 401;另一作者 token → 404(实现按不存在处理,脚本按实现断言并标注与工单 403 的差异) · B7/B8 发布后 fork-source source=project、sha256/bytes 一致、downloadPath 指向 project,下载校验 zip/length/sha256/解压条目 · B9 对照作品(无工程包)source 回落 package,/fork-source/project → 409 - package.json:新增 npm script check:game-distribution-project-bundle-e2e - 复用 lineage 脚本的发布链路与 helper(脚本头注释标注出处),未抽公共模块以免改动其它脚本 - 实测:本地 dev 栈 46 项 46 PASS / 0 FAIL / 0 SKIP |
||
|
|
524ca890c6 |
feat(游戏共创): 工程源包规则与服务端对齐,并加机器 parity 门禁
规则对齐(以服务端为权威,`module-game-distribution/src/project_bundle.rs`):
- 凭据/隐私两侧补齐:客户端新增 `.git-credentials`、`id_rsa*`、`id_ed25519*`、`*.map`,
并把 `.env` 放宽成服务端同形的 `starts_with(".env")`(`.envrc` 之类一并排掉)。
反向无需补:客户端没有服务端缺的凭据类规则,比对一次后两侧凭据集合完全一致。
- 新增被漏掉的嵌套压缩包规则:服务端对任意层级 `*.zip` 一律拒收(解包阶段不递归校验,
等于绕过整份清单),客户端原来会把它打进包里 → 现在同样排除。
- `.agent` 由「项目根首层」对齐到**任意层级**(服务端口径;平台保留名两边一致比更宽松重要)。
- 客户端额外项保留并逐项写明理由:`game/dist`、`game/build`(AGC 脚手架构建输出)、
`.godot`(编辑器缓存,模板包指南同规则)、`exports`/`memory`(AGC 生成物与 Agent 记忆)。
- 规模上限统一到服务端数值:压缩包 ≤ 200 MiB(新增)、展开 ≤ 500 MiB、单文件 ≤ 64 MiB、
条目 ≤ 10 000、单文件 ≤ 包体 × 100(新增)。客户端先拦,不再出现「传到一半被 422 拒掉」。
- 顺手补上服务端已有的「大小写折叠后重名条目」防线:客户端提前失败关闭。
可机器比对的常量:规则集拆成 `BUNDLE_EXCLUDED_*` 常量、上限拆成 `PROJECT_BUNDLE_MAX_*`,
新增 `scripts/check-project-bundle-policy-parity.mjs` 逐 token/逐数值比对两侧(服务端每条规则
必须在客户端存在,客户端额外项只打印不算失败;抽不到 token 直接报错,避免假绿),并照既有
DTO parity 的形态接到 `package.json` 的 `check:*` 与 `lint` 链。
测试:客户端 17 条(新增 `.git-credentials`/`id_rsa`/`*.map`/`.envrc`/`.zip`/任意层级 `.agent`、
压缩包上限、压缩比上限各自一条;合同常量锚点改为与服务端逐项相等)。
|
||
|
|
861dc0a676 |
feat(游戏共创): M2b 上行路由族 + 下行优先与缓存资产维度(块 C+D)
- 上行(作者、Bearer + 发布灰度,与发行包族逐条对齐):`PUT /versions/{id}/project-bundle`(整包一次上传,≤ 200 MiB)、`GET …/project-bundle/upload-state`、`PUT …/project-bundle/chunk`(`x-genarrative-upload-offset`,分片上限同发行包)、`POST …/project-bundle/complete`、`POST …/project-bundle/reset`;载体一律 `application/octet-stream`,分片边界与偏移语义只有一套
- 对象键:新增 `game_distribution_project_bundle_object_key(game_id, version_id)` = `…/{version_id}.project.zip`;发行包 helper **未改**(仍是 `.zip`)→ 同一(作品, 版本)的两份资产互不覆盖、也不会串用读取缓存
- 阶段门:新增**唯一**共享函数 `ensure_project_bundle_uploadable`,5 条上行路由全部调用、无重复实现。判定顺序:① 已确认(`project_bundle_bytes > 0`)→ 409 `PROJECT_BUNDLE_ALREADY_EXISTS`(必须先判:确认工程包不驱动版本状态机,已确认的版本可能仍停在 `awaiting_upload`);② `status` ∉ {`awaiting_upload`, `upload_failed`} → 409 `PROJECT_BUNDLE_UPLOAD_NOT_ALLOWED`(与模块事务同口径)
- `complete`:回读整包 → 与 HEAD 权威长度比对 → `validate_project_bundle_zip`(失败删半包对象 + 记上传失败 + 422 `PROJECT_BUNDLE_VALIDATION_FAILED`)→ `confirm_game_distribution_project_bundle`;幂等键与摘要口径照抄发行包 complete
- 下行优先:`fork_source_target` 选定资产 = 有工程包时 `Project`(优先)否则 `Package`;「字节数 > 0 **且**摘要非空」才算有工程包,半写行回落 `package`(失败关闭)。元数据 `source` / `sha256` / `bytes` / `downloadPath` 随所选资产;新增 `GET /games/{id}/fork-source/project`(同两层中间件、无 Query 提取器;没有工程包时 409 `FORK_SOURCE_NOT_AVAILABLE`,**绝不静默回落成品包**),响应头与成品包同形(文件名 `{gameId}-{versionId}-project.zip`)
- 缓存键加资产维度:`release_package_bytes` 泛化为 `release_asset_bytes(state, object_key, max_bytes)`(缓存键即对象键),发行包侧退化为薄封装——键字符串、上限、`Bytes` 值类型与 4 条 / 256 MiB 预算逐字节不变,既有缓存测试继续通过
- 顺着同一「不复制第二套」原则抽出/参数化的共享件:`fork_source_bundle_response`(两份资产共用响应构造)、`package_upload_offset(headers, asset)`、`require_octet_stream_content_type(headers, asset)`(按资产给错误文案,发行包文案不变)
- 补上块 A 遗漏的两处客户端登记(否则下行字段读不到):`spacetime_client` 对 `GameDistributionConfirmProjectBundleRecordInput` 的 re-export,以及 `GameDistributionForkSourceRecord` 新增 `project_bundle_sha256` / `project_bundle_bytes` 与对应映射
- 测试 6 条:5 条上行 + 1 条下行共 6 条路由未带 Bearer → 401(逐条);`ensure_project_bundle_uploadable` 全状态(含「已确认优先于阶段」)+ 7 档拒绝;`fork_source_target` 6 种资产组合(工程包优先 / 回落成品包 / 两种半写行 / 只有工程包 / 两份都缺 → 409);两个对象键互不相等 + `ReleasePackageCache` 双资产不串味;上限镜像(`MAX_PROJECT_BUNDLE_BYTES == MAX_PACKAGE_BYTES == shared_contracts::GAME_DISTRIBUTION_MAX_PACKAGE_BYTES`、分片放行量 > 分片大小);校验失败 422 映射
- 门禁:`cargo check --all-targets` 0;`cargo test -p api-server game_distribution` **60 passed**(基线 54;既有发行包族用例全绿 → 抽取共享件未改发行包行为);DTO parity 42 组 OK;`check:encoding` 5301 files OK;`git diff --check` 0;`cargo fmt --all -- --check` 0
|
||
|
|
d39257b608 |
docs(游戏共创): M2b 文档按实现收敛(块 E)
- 技术方案 §3.2.3:三列语义改为与实现一致;删掉原「保留一次补齐机会」的规则(**已公开版本的补传未实现**,本期不做),改为「只在 `awaiting_upload` / `upload_failed` 可写一次、写入后换内容 409、不做版本回溯」;补产品口径(不做独立「源码可见性」开关、作者不传即产物级改编、`nonCommercial` 约束力属平台规则层面);对象键改为实现值 `…/{version_id}.project.zip`
- 技术方案 §2.3:把「公开后可补传一次工程源包」的表述改为如实口径(公开后不接受补传,只能走试玩 + 素材)
- 技术方案 §3.3:version 行的 `project_bundle_*` 迁移规则改为「只在未公开时可写一次」
- 技术方案 §3.4:`PUT /versions/{versionId}/project-bundle` 从「规划」改为**已实现**(整包 + 4 条分片族路由、作者限定、阶段门、无既有工程包、服务端派生对象键);「登录用户」小节把取件接口从两个改为三个并说明 `source` 可能是 `project`(优先)或 `package`,新增 `/fork-source/project` 本体行(失败关闭、缓存含资产维度)
- 技术方案 §3.5.2:打包/服务端复核/上传/下载建项/优先级五条按实现重写——写明服务端 `validate_project_bundle_zip` 的完整拒绝清单与与发行包逐项相等的上限(200 MiB / 500 MiB / 64 MiB / 10 000 / 100,理由:共用同一上传链路且反代按 200 MiB 校准),以及「打包器已落地、上传与建项接线待做」
- 技术方案 §3.5.4:记录产品已采纳推荐口径(A 默认继续服务未上传工程包的作品、B 服务端已实现、C 不做)
- 里程碑「作品工程源包与一键改造」:Status → in-progress(服务端已落地 / 客户端接线待做)、Version 1.2;范围去掉「是否公开工程」选项(不存在独立开关);不在范围内新增「已公开版本补传」;两条前置依赖标注已落地并写清 512 MiB 与 200 MiB 的对齐口径(客户端须按服务端上限预检);验收标准改为带 ✅/⏳ 标注、删掉两条 backfill 验收、补阶段门与半写行回落等已实现项
- 门禁:`check:doc-index` 248 份 OK;`check:encoding` 5301 files OK;`git diff --check` 0
|
||
|
|
8ce4649081 |
feat(游戏共创): M2b 工程源包专属 zip 校验器(块 B)
- 新增 `server-rs/crates/module-game-distribution/src/project_bundle.rs`:`validate_project_bundle_zip(bytes) -> Result<ProjectBundleManifest, ProjectBundleError>`
· 复用 `crate::package::normalize_archive_path`(不复制路径安全逻辑),路径不安全统一归 `InvalidPath`
· 拒绝清单:空包 / 非 zip / 条目数 > 10 000 / 符号链接 / 加密条目 / 嵌套 `.zip` / 完全重复与大小写折叠重复路径 / `node_modules`(任意层级)/ `.git`、`.svn`(任意层级)/ `.agent`(任意层级:避免把上一个作品的改编来源记录带给下一个人)/ 根级 `dist`、`build`、`library`、`temp`、`local`(子目录同名允许,与模板包规则一致)/ 根级 `.idea`、`.vscode` / 凭据与隐私:`.env`、`.env*`、`*.pem`、`*.key`、`*.p12`、`*.pfx`、`.npmrc`、`.netrc`、`.git-credentials`、`id_rsa*`、`id_ed25519*`、`*.map` / 单文件 > 64 MiB / 累计 > 500 MiB / 单文件 > 包体 × 100
· 规模上限与发行包**逐项相等**(200 MiB / 500 MiB / 64 MiB / 10 000 / 100):两者共用同一条上传链路,反代与 Pingora 的放行量就是按 200 MiB 校准的,放宽就得同步放开多处部署配置;单测用 `assert_eq!` 钉住这条等价关系
· 不要求根 `index.html`(源码包没有入口约定),非空即可
- `lib.rs` 只追加 `mod project_bundle;` 与 `pub use project_bundle::{...}`(+6 行);`package.rs` **零改动**,发行包行为与测试不受影响
- 测试 16 条:合法源码包通过(故意不含 `index.html`)、上述每条拒绝规则各一条、「子目录里的 `dist/` 允许」反例、常量与发行包逐项相等、manifest 的 sha256/字节数与输入一致
- 门禁:`cargo test -p module-game-distribution` 69 passed(含本校验器 16 条);wasm build 0;`cargo fmt --all -- --check` 0
|
||
|
|
604ce7f3ec |
feat(游戏共创): M2b 数据模型——版本行三列工程源包 + 契约与绑定(块 A)
- `server-rs/crates/spacetime-module/src/game_distribution.rs`:`game_distribution_version` 表尾追加三列(明确默认值,遵循既有追加规则)——`project_bundle_object_key: Option<String>`(`#[default(None)]`)、`project_bundle_bytes: u64`(`#[default(0)]` = 未上传)、`project_bundle_sha256: Option<String>`(`#[default(None)]`);三列同步进 `GameDistributionVersionSnapshot`、版本快照构造与建版本时的初始化
- 同文件:`GameDistributionForkSourceSnapshot` 与扁平结果类型追加 `project_bundle_sha256` / `project_bundle_bytes`,下行据此判断走 `project` 还是回落 `package`(字节数与摘要成对,缺一视为没有工程包)
- 同文件:新增 `GAME_DISTRIBUTION_ACTION_UPLOAD_PROJECT_BUNDLE` 动作常量、输入 `GameDistributionConfirmProjectBundleInput`、事务 `confirm_game_distribution_project_bundle_tx` 与 procedure `confirm_game_distribution_project_bundle_and_return`。语义:可选资产、**不驱动版本状态机**;owner 校验 → 阶段门(`awaiting_upload` / `upload_failed`,与发行包确认同口径,已公开/已取消/已撤回一律拒绝)→ 同内容幂等重放 / 换内容冲突 → 写三列 + 幂等收据
- `spacetime-client`:`GameDistributionVersionRecord` 与 `map_version` 同步三列(生成绑定把 `project_bundle_sha256` 折成 `project_bundle_sha_256`,与 `package_sha_256` 同约定);新增 `GameDistributionConfirmProjectBundleRecordInput` 与 `confirm_game_distribution_project_bundle`(沿用 `map_game_distribution_game_result` 再取 version 的既有写法)
- 契约:`shared-contracts` 的 `GameDistributionPrivateVersion` 追加 `project_bundle_bytes`(必需,`0` = 未上传)与 `project_bundle_sha256`(可选);`packages/shared` TS 镜像同步;api-server `private_version_payload` 追加同名键——**对象键不下发**,只回摘要与字节数
- 生成物:重跑 `npm run spacetime:generate -- --rust-only`,只保留 `module_bindings/game_distribution*`(含 2 个新文件)与 `module_bindings.rs`,其余 `module_bindings/**` 的 rustfmt 漂移已 `git checkout --` 还原
- 文档:数据契约表目录 `game_distribution_version` 小节补三列语义与对象键不出服务端的口径
- 顺手记下一条门禁陷阱:`scripts/check-game-distribution-dto-parity.mjs` 抓 `json!` 顶层键时要求「键前一个非空字符是 `{` 或 `,`」且**不剥注释**,在字面量里插注释行会让紧随其后的键被判成缺失(已写进 `private_version_payload` 的文档注释)
- 门禁:wasm build 0;`cargo check --all-targets` 0;`cargo test -p module-game-distribution` 69 passed;`cargo test -p api-server game_distribution` 54 passed;DTO parity 42 组 OK;`check:spacetime-schema` 92 tables OK;`check:encoding` 5301 files OK;`git diff --check` 0;`cargo fmt --all -- --check` 0
|