fix(release): 完成官方发布身份与 legacy CAS 归属保护
bat-rust / Build and test Rust (push) Canceled after 0s
bat-rust / Build and test Go API (push) Canceled after 0s

This commit is contained in:
2026-09-13 15:34:42 +08:00
parent 5bae90cb14
commit 37d49c9793
11 changed files with 416 additions and 67 deletions
@@ -239,7 +239,7 @@ trusted 和 release/TextUnit/provider/run provenance`translation.tasks` 优
8. 远端无变化且本地已有资源时执行 download manifest audit,检查路径、size、BLAKE3 和 ZIP 结构。
9. 远端变化、本地 audit 发现 repair_needed,首次空目录运行,或缺少 `current` 原子发布指针时,进入下载/发布流程。
10. 下载先写入 `<output>/.staging/<id>`;若已有 active release,会先 seed staging 以复用已验证文件;若 version-state 中存在同一版本的失败 staging,则优先复用该 staging 并跳过 active seed,避免旧 active 覆盖已下载的新文件。新 staging 还会扫描已发布 release 的下载 manifest,按规范化 destination 查找候选并重新验证 size、BLAKE3 和 ZIP 结构;硬链接失败时回退到临时文件复制和原子 rename,历史 release 保持不可变。
11. 下载、manifest、本地 BLAKE3、ZIP 和官方 `.hash` 校验完成后写入新的 snapshot,并在 staging 中写入 `official-launcher-bootstrap.json`(若本轮启用 `--auto-discover`)。
11. 下载、manifest、本地 BLAKE3、ZIP 和官方 `.hash` 校验完成后,在 staging 中写入独立的 `official-distribution-publication.json` 发布事实和新的 snapshot,并写入 `official-launcher-bootstrap.json`(若本轮启用 `--auto-discover`)。publication 文件关联 official release ID、完整 mapping identity、manifest content identity 和 entry count;缺少或不匹配时 release 不可作为 distribution-ready。
12. 将 staging rename 为 `<output>/versions/<id>`,再原子替换 `<output>/current` symlink 指向该 versioned 目录。
13. 发布完成后先对比上一完整 release 和当前 release 的 `official-download-manifest.json`,写出 `official-resource-changes.json``crowdin-translation-handoff.json`。同一 destination 只有 size 或 BLAKE3 变化才算 modified;新增+变更资源进入解析/翻译 handoff,删除资源只进入差异记录。当前只预留 Crowdin 本地 handoff,不发外部 API 请求。
14. 随后刷新 active release 下的 `official-parse-cache.json``official-textunit-index.json`,并从 Added/Modified 资源、parse cache 与 TextUnit 明细索引派生 `official-textunit-tasks.json``crowdin-textunit-queue.json` 和版本化的 `translation-tasks.sqlite`up-to-date 轮询在已有有效解析缓存、TextUnit 明细索引和 TextUnit 队列时只读取摘要,不重复解析,重新同步队列时保留已有 worker 状态。历史 release 复用只允许不可变资源 payload/sidecar 硬链接;download manifest、snapshot、parse/textunit cache、queue、handoff、bootstrap、CAS reuse references 以及 `translation-tasks.sqlite`、WAL/SHM 都必须独立复制,不能共享可变 inode。
@@ -32,6 +32,7 @@
current -> versions/<id> # 原子 symlink,生产读侧
versions/<id>/ # 已发布 versioned release= resource_root
official-download-manifest.json
official-distribution-publication.json # 独立发布事实:release、mapping、manifest identity、entry count
official-parse-cache.json # 校验后派生解析缓存,不是汉化产物
official-textunit-index.json # TextUnit 明细与解析错误索引,不是汉化产物
official-textunit-tasks.json # 翻译任务候选派生队列,不发 Crowdin 网络请求
@@ -137,6 +138,15 @@ reference、无法确认 ownership 的对象均保留;只删除重新验证后
跨文件系统时复制到 staging 内的临时文件并原子 rename,旧 `versions/<id>` 目录
保持不可变。
新 official release 在完整下载、manifest、文件和 ZIP 校验完成后,才会在 versioned
目录中原子写入 `official-distribution-publication.json`。该文件独立记录
`official_release_id`、完整 distribution mapping identity、manifest content identity
manifest 文件 BLAKE3)和 `entry_count`。普通 manifest 读写、release status 查询和
`release.distribution` 不会重建或刷新它;如果 manifest 在发布后变化、publication
文件缺失或两者 identity 不一致,该 release 的 `distribution_integrity_status` 不是
`valid`,不能被 distribution 读侧选择。没有该文件的历史 release 仍可被状态/清理逻辑
识别为 `legacy`,但不会被当作 distribution-ready。
从 CAS 物化资源时,`official-cas-reuse-references.json` 首次创建时生成并持久化
`ownership_id`,记录每个获取的对象引用;文件带版本字段且允许重复 object ID。
没有 `ownership_id` 的旧清单首次 cleanup 按 output-root scope、release ID、稳定 source
@@ -246,8 +256,20 @@ localized 或历史 release 时,调用 `release.distribution` 选择 Rust 已
| `bytes` | 文件大小 |
| `blake3` | 本地 BLAKE3 hex |
manifest 还持久化以下 distribution 查询字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `distribution_mapping_identity` | string | 完整 URL、destination、size、BLAKE3 映射的确定性 identity |
| `destination_index` | map destination → URL | 单 destination 分发的轻量定位索引 |
**权威清单**:拉取闭环写入的 manifest`bat-api` / RPC `resource.manifest` 以此为应有集合,再以磁盘校验 Present。
`official-distribution-publication.json` 是发布事实而不是 manifest 派生缓存。单条
distribution 查询只读取该文件、当前 manifest 的内容 identity 和
`destination_index`,再校验目标文件的 size/BLAKE3;不会为了定位一个 destination
重做完整 mapping canonicalization 或遍历其他资源。
---
## 5. 发现与 seed URL 规则(L2
@@ -229,6 +229,7 @@ cargo run -p bat-infrastructure --example official_pull_plan -- \
- `<output>/official-bootstrap-cache.json``--auto-discover``GameMainConfig` 解析缓存。launcher metadata 与 remote manifest 文件列表 digest 都未变时复用缓存;任一变化时才通过官方 HTTP 按 manifest 下载必要 `resources.assets` 或旧版 game zip 到临时目录解析。
- `<output>/official-version-state.json`:资源发布根目录的持久版本状态,包含当前已完成版本、正在拉取版本、上一个可用版本和失败版本。
- `<output>/current/official-download-manifest.json`:本地下载强校验清单,记录 URL、相对路径、size、BLAKE3、deterministic distribution mapping identity 和 destination index。
- `<output>/current/official-distribution-publication.json`:新 official release 完整校验后写入的独立发布事实,记录 official release ID、mapping identity、manifest content identity 和 entry count;它缺失或与 manifest 不一致时 release 不可分发。普通查询不会自动补写该文件。
- `<output>/current/official-cas-reuse-references.json`:当前 release 获取的 CAS 引用清单,首次创建时包含持久化 `ownership_id`;每个复用项占一条记录,release 清理或孤儿 staging GC 时据此按 ownership/ordinal 递减引用。旧无 identity 清单使用 output-root 的 `.cas-owner-scope` 完成 generation-aware legacy cleanup;该文件不会被复制到新 release。
- `<output>/current/official-resource-changes.json`:当前 release 相对上一完整 release 的资源差异,记录新增、变更、删除以及解析/翻译候选计数。
- `<output>/current/crowdin-translation-handoff.json`:为后续 Crowdin worker 预留的本地队列,只包含新增+变更资源;它不是 Crowdin API 调用结果。
+4 -2
View File
@@ -178,7 +178,7 @@ SQLite `ResourceRepository`,索引不存在时返回 `ok=true` 且
|---|---|---|---|
| `release.status` | 已实现 | `null` | official/localized current、source relation、match、历史 release 和 manifest/artifact/distribution integrity 统一视图。 |
| `release.list` | 已实现 | `{ "channel": "official" }``{ "channel": "localized" }`,可省略 | 对应 namespace 的历史 release 摘要,包含 stable ID、created/published、current pointer、`rollback_available`、lifecycle、`stale`/`damaged`/`referenced`/`unknown`、legacy 和诊断。 |
| `release.distribution` | 已实现 | `{ "channel": "official", "release_id": "...", "destination": "...", "offset": 0, "limit": 1000 }`,均可省略 | Rust 选择的 verified `resource_root` 和 download manifest entries;有 `destination` 时是 single-entry lookup,响应固定 `total=1, offset=0, limit=1, entries.length=1`,使用 persisted source mapping identity/destination index,只校验该实际文件的 bytes/BLAKE3,不重新执行全量映射或资源 audit;无 `destination` 时保留管理查询分页语义。localized 使用发布时生成的实际字节 metadata,不复用 official size/hash;默认 channel 为 official,选择失败返回 `available=false`,不跨 channel fallback。 |
| `release.distribution` | 已实现 | `{ "channel": "official", "release_id": "...", "destination": "...", "offset": 0, "limit": 1000 }`,均可省略 | Rust 选择具有独立 `official-distribution-publication.json` 且 identity 与当前 manifest 一致的 verified `resource_root`;有 `destination` 时是 single-entry lookup,响应固定 `total=1, offset=0, limit=1, entries.length=1`,使用 published mapping identity/destination index,只校验该实际文件的 bytes/BLAKE3,不重新执行全量映射或资源 audit;无 `destination` 时保留管理查询分页语义。localized 还必须匹配 source official 的 published identity,并使用发布时生成的实际字节 metadata,不复用 official size/hash;默认 channel 为 official,选择失败返回 `available=false`,不跨 channel fallback。 |
| `release.cleanup` | 已实现 | dry-run `{ "execute": false }`;执行 `{ "execute": true, "plan_id": "..." }` | cleanup plan、candidate/retain reasons、blocking references 和 removed paths;执行前会重新生成并比对 `plan_id`。 |
`release.status``release.list``release.distribution` 只读现有 official/localized
@@ -186,7 +186,9 @@ state、current、manifest、文件系统和 CAS/reference 元数据,不创建
分发选择使用发布后的轻量 manifest 和文件 size/单文件 BLAKE3 校验,不在 HTTP 热路径
重新执行完整 release auditlocalized 还要求 distribution manifest 的 destination/URL
集合与 source official manifest 一致,并使用发布时记录的实际 localized bytes/hash。
默认官方分发行为不变。
publication 文件缺失、manifest content identity 变化或 source mapping identity 不一致时,
即使单个目标文件本身完整,也返回 `available=false`;旧 release 可被列出并标记
`legacy`,但不会被 distribution 读侧自动重建 publication metadata。默认官方分发行为不变。
`release.cleanup` 只删除 Rust 能证明是普通目录且未被 current、rollback、staging、
source、state、manifest、CAS 或未知 ownership 引用的历史项,不修改 current,也不承担
rollback 或 repair。
+7 -2
View File
@@ -1,6 +1,6 @@
# 当前实现缺口清单
- **更新时间**2026-09-12
- **更新时间**2026-09-13
- **文档角色**:只记录尚未完成、仍需验证或仍需设计的工作,不重复维护完整实现状态。
- **当前事实**:以源码、测试、稳定契约和 `CURRENT_STATUS.md` 为准。
- **Go 进度**`GO_STATUS.md`
@@ -137,7 +137,12 @@ source/localized mapping identity 和 destination index`release.distribution(
是单条 lookup,返回 exactly one entrysource identity mismatch 会阻断 localized
distribution,完整 identity 校验只在 publish/status/audit 路径执行;分发读取保留 path
ownership、symlink 和文件完整性检查;`release.cleanup execute` 与 official sync 共用
`.official-sync.lock`本轮已关闭两个 Release/CAS P2。其他 P2 尚未由本轮处理:ResourceRepository
`.official-sync.lock`新 official release 还持久化独立的
`official-distribution-publication.json`,把 release ID、完整 mapping identity、manifest
content identity 和 entry count 绑定到发布事实;publication 缺失或 manifest 变化时
official/localized distribution 均被阻断,普通查询不会自动重建。legacy release 仍可
列出和清理,但不视为 distribution-ready。本轮已关闭两个 Release/CAS P2。其他 P2
尚未由本轮处理:ResourceRepository
更完整的查询/权限/损坏恢复、模糊 TM、bat.sock peer credential/perms、FFI 生命周期、
资源大小/限额与更强的持久化 fsync 语义仍按后续专项推进。