Stands up the Binary Format companion's wire-format-fuzzer charter item as a
core adversarial byte-decode harness, and lands the three robustness fixes it
drove out. The canonical decoders are a trust boundary (a hostile bundle, a
bit-rot chunk, a mismatched implementation), so every byte string must decode
to a clean Err -- never panic, over-allocate, or loop unboundedly -- and any
accepted string must re-encode to itself (canonical decode is injective).
- epiphany-core/src/fuzz.rs (new): run_decode_fuzz mutates a once-built corpus
(random, substitution, truncation, trailing garbage, length-prefix
corruption, wrong-type payload, genuine-v0-form) against Score::decode_canonical,
the versioned seam (v1 + the frozen v0 migration), and a per-value decoder;
asserts no-panic + injective decode over ~40K inputs/run. Two seeds, plus
deterministic prefix-rejection sanity tests.
- Strict-canonical decode (the fuzzer's first finding): decode reconstructed via
normalizing constructors (RationalTime reduces, BTreeSet/BTreeMap re-sort, a
CanonicalF64/ReferencePitch/Tempo normalizes via new), so distinct byte
strings could map to one value. Fixed complete-by-construction: Score::
decode_canonical and the CanonicalValue macro re-encode and reject any input
not already its canonical form; decode_v0_score does the same against the
frozen v0 wire form (encode_v0_score promoted to production), so major-0
snapshots are injective too.
- Bounded collection count (Reader::count): reject any count/length exceeding
the bytes remaining. A garbage u32 count was a soft-DoS -- decoders looped
element-by-element toward EOF (e.g. misparsing v1 bytes as v0), ~100ms per
adversarial input; the bound also caps Vec/set allocation and gives a ~2600x
fuzz speedup (422s -> 0.16s). Codec round-trips confirm no valid data has
zero-byte-element collections, so the bound never rejects a real encoding.
Full gate green (workspace tests, clippy -D warnings, fmt, rustdoc -D warnings).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NEs4aYiu8MXjdYdMxw8PTd