# bat-api / Rust bat Contract Fixture Handoff 更新时间:2026-07-31 本文用于两个 Codex 窗口之间间接联调 `bat-api` 与 Rust `bat` 的跨语言 contract fixture。它只定义协作协议和验收标准,不包含已审核 fixture。 2026-07-31 更新:已审核归一化 fixture 已落入 `internal/api/testdata/contract/`,Go 侧通过 `internal/api/contract_fixture_test.go` 固化 mirror struct 验证。本文件保留为 后续重新生成或扩展 contract fixture 时的协作协议。 ## 最小上下文包 另一个窗口不需要知道本窗口的完整对话,只需要遵守以下上下文: - 本次联调对象是 Rust `bat` RPC / snapshot JSON 与 Go `bat-api` mirror struct 的 contract fixture。 - 联调不要求本地运行全量长期服务端 `bat`;允许 Rust 侧使用 fixture root 或临时目录走真实代码路径导出 JSON。 - fixture 审核前只能放在 `/tmp/bat-contract-fixture/`,不能直接提交到仓库。 - Go 侧已经实现 player-facing HTTP 鉴权、限流、访问日志、反代适配、OpenAPI 和 `/admin/` 预留;contract fixture 是剩余跨语言强契约工作。 - Go 侧当前相关代码入口: - `internal/api/rpc_release.go` - `internal/api/release_index.go` - `internal/api/responses.go` - `internal/backendrpc/` ## 背景 - Rust `bat` 是资源同步、状态发布和 `bat.sock` RPC 的权威实现。 - Go `bat-api` 是只读 HTTP bootstrap / 分发服务,消费 Rust RPC 输出和已发布资源目录。 - contract fixture 不能由任一侧手写猜测;必须由 Rust 侧真实输出,经归一化和用户审核后,再由 Go 侧固化测试。 ## 非目标 - 不引入真实玩家账号、登录、网关、鉴权绕过或游戏业务 API fixture。 - 不写入开发机绝对资源路径,例如 `/home/wanye/D/BlueArchive`。 - 不把当前某个真实版本号、日期、远程目录或本地目录写成长期契约。 - 不让 Go fixture 反向约束 Rust 内部实现;只约束对外 JSON contract。 ## 建议共享目录 联调前使用临时目录交换未审核产物: ```text /tmp/bat-contract-fixture/ rust/ catalog-status.available.raw.json catalog-status.unavailable.raw.json resource-manifest.page0.raw.json official-sync-snapshot.raw.json normalized/ catalog-status.available.json catalog-status.unavailable.json resource-manifest.page0.json official-sync-snapshot.json notes.md ``` 只有用户审核通过后,才允许把归一化 fixture 落入仓库,例如: ```text internal/api/testdata/contract/ ``` ## Rust 侧需要产出 Rust 窗口请基于当前真实代码生成或导出以下 JSON: 1. `catalog.status` available=true 响应。 2. `catalog.status` available=false 响应。 3. `resource.manifest` 第一页响应,至少包含 1 到 2 个 entries。 4. 对应 release 的 `official-sync-snapshot.json`。 输出应来自 Rust 代码路径,而不是手写 JSON。允许使用 fixture resource root 或临时目录,但不能依赖开发机真实资源目录。 ## 归一化规则 归一化只允许处理环境相关值,不改变 schema: - 绝对路径归一化为 `${RESOURCE_ROOT}` 或 `${STATE_DIR}`。 - 版本 id 归一化为 `${VERSION_ID}`。 - 时间戳可归一化为固定小整数或 `${COMPLETED_UNIX_SECONDS}`。 - 真实 URL host 保留;路径中若含具体 release token,可归一化为 `{addressables-root}` / `{manifest-path}`。 - 字段名、字段类型、字段层级、null / missing / array / number 语义不得修改。 ## Go 侧验证范围 Go 窗口读取归一化后的 JSON,验证: 1. `parseCatalogStatus` 能解析 `available=true`,并正确映射: - `app_version` - `bundle_version` - `connection_group_name` - `addressables_root` - `version.id` - `version.completed_unix_seconds` - `version.resource_root` - `launcher_metadata` - `game_main_config_bootstrap` 2. `parseCatalogStatus` 对 `available=false` 返回不可用而不是错误。 3. `resource.manifest` entry 字段能映射为 Go `ResourceManifestEntry`: - `url` - `destination` - `bytes` - `blake3` 4. 本地 snapshot fixture 与 RPC `catalog.status` 均使用 `game_main_config_bootstrap`。 5. `bat-api` bootstrap 和 launcher bootstrap 不泄露归一化前的开发机路径。 Go 侧审核通过后的落地建议: - `internal/api/testdata/contract/catalog-status.available.json` - `internal/api/testdata/contract/catalog-status.unavailable.json` - `internal/api/testdata/contract/resource-manifest.page0.json` - `internal/api/testdata/contract/official-sync-snapshot.json` - `internal/api/contract_fixture_test.go` 测试不应依赖 `/tmp/bat-contract-fixture/`;该目录只用于两窗口交接未审核产物。 ## 必须覆盖的 optional 语义 至少需要两组 Rust 输出或派生 fixture 覆盖: 1. optional 字段非空: - `launcher_metadata.game_lowest_version` - `launcher_metadata.game_start_exe_name` - `launcher_metadata.manifest_source` - `game_main_config_bootstrap.server_info_data_url` - `game_main_config_bootstrap.default_connection_group` 2. optional 字段为 null 或缺省: - Go mirror 不应崩溃。 - HTTP response 中按当前 Go struct `omitempty` 策略输出。 ## 用户审核点 落仓库前请用户审核: - 归一化是否过度改变 Rust 真实输出。 - fixture 是否意外绑定真实版本、日期、本机路径或私有部署路径。 - `game_main_config_bootstrap` 在 RPC / snapshot 中是否保持同一语义。 - optional 字段覆盖是否足够。 ## notes.md 模板 Rust 侧生成 `/tmp/bat-contract-fixture/notes.md` 时建议使用以下结构: ```markdown # bat contract fixture notes ## 生成命令 - catalog.status available=true: ... - catalog.status available=false: ... - resource.manifest page0: ... - official-sync-snapshot: ... ## 原始输出来源 - Rust commit / working tree: ... - 使用的 fixture root 或临时目录: ... - 是否依赖真实开发机资源目录: 否 ## 归一化 - `${RESOURCE_ROOT}`: ... - `${STATE_DIR}`: ... - `${VERSION_ID}`: ... - `${COMPLETED_UNIX_SECONDS}`: ... - URL 路径占位符: ... ## 需要用户审核 - ... ``` ## 完成判定 contract fixture 工作只有在以下条件同时满足时才算完成: 1. Rust 侧原始 JSON 来自真实 Rust 代码路径。 2. 归一化 JSON 经过用户审核。 3. Go 侧测试读取归一化 fixture 并验证 mirror struct / launcher bootstrap 行为。 4. Go 测试不依赖开发机资源目录、远程长期运行 `bat` 或 `/tmp` 中的交接目录。 5. 文档记录 fixture 覆盖的风险和仍未覆盖的字段。 ## 建议给另一个窗口的短指令 ```text 请读取 docs/reports/BAT_API_CONTRACT_FIXTURE_HANDOFF.md。 你负责 Rust bat 侧 contract fixture 原始输出: 1. catalog.status available=true 2. catalog.status available=false 3. resource.manifest page0 4. 对应 official-sync-snapshot.json 请输出到 /tmp/bat-contract-fixture/rust/,不要手写 JSON,不要引用开发机真实资源目录。 输出后在 /tmp/bat-contract-fixture/notes.md 说明生成命令、是否做过归一化、哪些字段需要用户审核。 ``` ## 当前状态 - Go `bat-api` 已具备消费 `launcher_metadata` / `game_main_config_bootstrap` 的 mirror struct。 - Go `bat-api` 已具备 player-facing HTTP 控制面、OpenAPI 和管理控制白名单。 - 已归一化的 Rust contract fixture 已落仓库: - `internal/api/testdata/contract/catalog-status.available.json` - `internal/api/testdata/contract/catalog-status.unavailable.json` - `internal/api/testdata/contract/resource-manifest.page0.json` - `internal/api/testdata/contract/official-sync-snapshot.json` - 原始交接产物仍位于 `/tmp/bat-contract-fixture/`;当前受本地 sandbox 限制,live socket daemon 无法启动,原始 JSON 通过临时 Rust 测试调用同一 dispatch/report 代码路径生成。 - Go contract 测试读取仓库内归一化 fixture,不依赖 `/tmp/bat-contract-fixture/`、开发机资源目录或远端长期运行的 `bat`。 - 仍未覆盖真实长期 daemon socket 的端到端调用和完整发布切换;该项需要在允许 live daemon / smoke 的隔离环境中单独验证。