--- title: "Bincode v1 → v2 Migration — Risk Audit (Sprint 36)" description: "Audit of bincode usage in the server crate, risk assessment, and migration recommendation for ticket #636." type: architecture status: final ticket: "#636" author: "Tyre" created: 2026-04-19 updated: 2026-04-19 --- # Bincode Migration — Risk Audit **Ticket:** #636 (Migrate bincode v1.x to v2.x) **Advisory:** RUSTSEC-2025-0141 (bincode v1.3.3 unmaintained) **Author:** Tyre **TL;DR:** **bincode is an orphan dependency — nothing in the server crate actually calls it. Remove it outright. The "migration" is a four-line change.** --- ## 1. What the audit found Grep-audit of the **entire repository**, not just `server/src/`: ```bash grep -rn "use bincode\|bincode::" server/ tests/ tooling/ --include="*.rs" # → 0 hits ``` Cargo manifest references: ```bash grep -rn "bincode" server/ --include="*.toml" --include="*.lock" # → server/Cargo.toml:19 bincode = "1" # → server/Cargo.lock:329 [[package]] name = "bincode" version = "1.3.3" # → server/Cargo.lock:1311 " bincode"," — under settled-reach-server deps # → server/audit.toml:6 ignore = ["RUSTSEC-2025-0141"] ``` No Rust source file in **any** crate (`server/`, `tests/`, `tooling/`) contains the string `bincode`. The lockfile entry under `tooling/test-client` shows bincode transiting through `rmp-serde` or a sibling — **not** from direct use. **Conclusion:** `bincode = "1"` in `server/Cargo.toml` was added in anticipation of save-load / Rust-Rust sync (see `docs/workshops/v01-gap-analysis/round1-tyre.md`, `docs/workshops/save-load-architecture/workshop-brief.md`) but **the implementation path chose `rmp-serde` / MessagePack instead** (see `server/src/bridge/types.rs` — tests use `rmp_serde::to_vec_named` and `rmp_serde::from_slice`, line 1052 onward). Bincode is a dead dependency. ## 2. Recommended migration: DELETE, don't bump ### 2.1 The actual changes **server/Cargo.toml** — remove line 19: ```diff -bincode = "1" ``` **server/audit.toml** — remove the ignore (lines 5–9): ```diff -[advisories] -# RUSTSEC-2025-0141: bincode v1.3.3 is unmaintained. -# Migration to bincode v2 or an alternative is tracked in ticket #636. -# This ignore can be removed once #636 is resolved. -ignore = ["RUSTSEC-2025-0141"] ``` (If removing the whole `[advisories]` section leaves `audit.toml` empty, either delete the file or leave the file with just a header comment — check `.config/cargo-audit/` or the Makefile for how `cargo audit` is invoked.) **server/Cargo.lock** — regenerate by running `cargo check` in `server/`. Verify `bincode` no longer appears. ### 2.2 Verification ```bash # 1. No source regressions: grep -rn "bincode" server/ tests/ tooling/ --include="*.rs" # Expected: 0 hits. # 2. Clean build: cargo check --workspace --all-features # 3. Clean tests: cargo test --workspace # 4. Audit is green without the ignore: cargo audit # Expected: no RUSTSEC-2025-0141 mention. # 5. cargo-deny (once #726 lands): cargo deny check ``` ### 2.3 Risk assessment | Risk | Likelihood | Impact | Mitigation | |------------------------------------------------|------------|--------|------------| | Hidden `use bincode` I missed | Near-zero | Build break | Covered by §2.2 step 1 grep + `cargo check` | | Proc-macro or build.rs pulling bincode | Near-zero | Build break | No `build.rs` in server crate; no proc-macro deps use it | | Transitive need (some crate depends on it) | Zero | N/A | Transitive deps come through lockfile without a manifest entry | | Future save-load work expects it in manifest | Low | Re-add | If save-load lands with bincode later, re-add `bincode = "2"` then — fresh v2 install, no migration | **All four risks are trivially mitigated. Net risk: ~0.** --- ## 3. If the team decides to keep bincode — the v1 → v2 cheat sheet Included for completeness even though §2 is the recommendation. If save-load (#553, D-085) or a future Rust↔Rust server-sync feature decides to use bincode, adopt it fresh at v2 with these signature changes: ### 3.1 The core API difference **v1 (current, unmaintained):** ```rust // Relies on serde Serialize/Deserialize derives. let bytes: Vec = bincode::serialize(&value)?; let value: MyType = bincode::deserialize(&bytes)?; ``` **v2 (stable):** ```rust // New "Encode"/"Decode" derives, explicit config. use bincode::{config, encode_to_vec, decode_from_slice}; let cfg = config::standard(); let bytes: Vec = encode_to_vec(&value, cfg)?; let (value, _used): (MyType, usize) = decode_from_slice(&bytes, cfg)?; ``` **Derive change:** v2 introduced its own `#[derive(bincode::Encode, bincode::Decode)]` traits. If the type must stay serde-compatible (required for us — we use `rmp-serde` and `ron` side-by-side), use the compat shim: ```rust use bincode::serde::{encode_to_vec, decode_from_slice}; let bytes = encode_to_vec(&value, config::standard())?; let (value, _) = decode_from_slice::(&bytes, config::standard())?; ``` This keeps `#[derive(Serialize, Deserialize)]` as the only derives on the data types — no dual-derive required. That matters because the same types cross the MessagePack boundary via `rmp-serde`. ### 3.2 Config v2 makes encoding config explicit. `config::standard()` uses variable-int, little-endian — matches v1 default for our types (no floats in the save shape today, so endian parity is not critical). For perfectly-byte-identical output to v1, use `config::legacy()`. **Any new adoption should use `config::standard()`** — don't inherit v1 quirks. ### 3.3 Known gotchas (for future reference) - v2 does **not** auto-handle untagged serde enums in the compat layer (pre-v2.0.1); if we adopt it and hit an untagged enum, use `bincode::serde::Compat`. - v2's `decode_from_slice` returns the byte count consumed — v1 silently ignored trailing bytes. Useful for streaming multi-message frames; irrelevant for one-shot save files. - The `bincode::options()` builder from v1 (`with_fixint_encoding()` etc.) is gone — replaced by `config::Configuration`. - Binary format is **not** compatible across v1 ↔ v2. Any v1-written blob is unreadable by v2. (This is moot for us — we have none.) ### 3.4 Touch points if we were actually migrating None. Literally no source file imports or uses it. --- ## 4. For Dudley — execution checklist 1. Delete `bincode = "1"` from `server/Cargo.toml`. 2. Delete the `RUSTSEC-2025-0141` ignore block from `server/audit.toml`. 3. `cargo check --workspace` — regenerates `Cargo.lock`. 4. `cargo test --workspace` — must pass. 5. `cargo audit` — must not print RUSTSEC-2025-0141 anymore. 6. Commit: ``` fix(deps): remove unused bincode dependency (#636) RUSTSEC-2025-0141 no longer relevant — bincode was declared but never imported. Drop the crate and the audit ignore. Future save-load work that wants bincode should adopt v2 fresh. ``` **Estimated effort:** ~15 minutes including verification. ## 5. What this means for docs One doc to update: `docs/sprints/sprint-27/server.md` line 93 mentions the audit ignore. Either leave it (it's historical notes) or strike through. Not blocking. --- **Audit status:** Complete. Recommendation: remove bincode entirely. If the team prefers "migrate now, don't remove" (symbolic commitment to the migration path), say the word and I'll spec that instead — but it costs more with zero benefit given the usage survey.