The hash registry extends per ADR-018: bytes 0x03/0x04 (git-object sha-1/sha-256), preimage-identity hashing over git's loose-object form as the stored preimage — the digest byte-identical to git's oid, every kind included. Re-derived from poc-o-git-objects, ADR-stamped, not merged. - Registry + Algorithm members after the blob members (total order rides the registry bytes, 0x02 < 0x03 < 0x04); the derivation arms (derive's len parameter inert for the family — documented, test-pinned); the BlobHasher no-preamble arms (finish pinned equal to derive); from_git_oid_hex's contract pinned blob-family-only. - keys.rs: the extended physical layout round-trips in total order; rejects-never-guesses unchanged (0x05 is the unknown-tag example). - put path: put_git_object(algorithm, len, stream) public — family members only (KeyInvalid otherwise), known-length only, the inherited Verification/Io mappings — via put_known_as; the unknown-length drivers take their derivation from callers. - fleet threading (ADR-018 §7): every hasher site derivation-parameterized (put_known_as; fleet_put_pglo_known/ _unknown); put_git_object under a fleet declaration lands through the joint entry+pin tx / pg-lo consolidation / pin-first staged forms — pinned by two env-driven tests with exact-count sweep outcomes (kv joint form byte-exact + dedup row-alone; consolidation form with LO pages landing and dying exactly). - git_interop: the family tests green against the real git CLI (tree/commit/tag byte-exact, dedup, the blob-tag negative, the sha-256 sibling), the seam guard (blob algorithms reject), the preimage-length stat assertion; in-module empty-tree known-answer vectors. - Conformance: the INV-1/2/6 reference rows gain family legs (round-trip incl. the staged form, dedup-by-oid, the stored-preimage stat semantic); backlog pins + README inventory updated. - kind-decision delta (ADR-018 §2): behavior unchanged, texts honest — the ops module doc's digest-wire-form paragraph, digest_ingress's doc, the specs' headers. - fuzz: the key_encoding generator + shared battery extended to the four-member registry (family keys, known-answer vectors, the len inertness pin, 0x05 as the invalid tag); committed corpus regenerated per policy (0x03 was an invalid-tag seed — 117 rewritten, 67 added); idempotent after landing. Verification: cargo test (default, 153+54+7+2), cargo test --all-features server-less (skip-clean) and against the live pglo-poc harness (336+125+7+3), cargo clippy --all-targets both feature variants (-D warnings), cargo fmt --check, cargo doc --no-deps, Cargo test --manifest-path fuzz/shared/Cargo.toml (the corpus-replay gate). Task: tasks/c1-git-object-family-impl.md (status completed, notes + summary recorded).
alkblobs fuzzing
libFuzzer targets for alkblobs' parse surfaces (see docs/plans/implementation.md §Wave 11 for the family pattern and target inventory).
Layout
fuzz_targets/— one binary per target; thinfuzz_target!wrappers.shared/— the invariant logic, as a plain library so normalcargo test(stable toolchain) can replay the committed corpora through the same invariants (fuzz/shared/src/*.rs#[cfg(test)]modules; quinn's CI pattern). The fuzz binaries are nightly-only; the shared crate is stable-clean. It consumes alkblobs' public API only and must never become a dependency of the root crate.corpus/<target>/— committed hand-made seeds (regenerate withpython3 fuzz/gen_fuzz_seeds.pyfrom the repo root — must be a no-op diff on the committed set) plus promoted campaign crash inputs (seed-900+ numbering). Onlyseed-*files are tracked; grown corpora (SHA-named units), artifacts, and coverage output are gitignored and are not merged back into the committed seeds.run-detached.sh— mandatory runner for agent sessions: wrapscargo fuzz runinsetsid+nohup+ log redirection so an OOM in a target can never take down the agent host.json.dict— JSON token dictionary for the structured targets.gen_fuzz_seeds.py— deterministic seed generator (quiche pattern).
Targets
| Target | Drives | Invariants |
|---|---|---|
key_encoding |
Key::from_bytes / Key::as_bytes (src/keys.rs), Digest::from_git_oid_hex, Algorithm::try_from_byte / byte / digest_len, Key::derive / Digest::derive / Algorithm::derive (src/hashing.rs) — entire public API |
no-panic on any byte sequence and any string (lossy-decoded and arbitrary-structured alike); every from_bytes failure exactly Error::KeyInvalid, every from_git_oid_hex failure exactly Error::KeyInvalid (rejects, never guesses — truncated, over-long, reserved/unknown tags, tag/digest-length mismatches, wrong-length, uppercase and non-hex hex); round-trip closure (accepted keys re-encode byte-identically, derived keys decode back); double-invocation determinism on both parsers; the derived Ord sort equals the as_bytes byte sort over arbitrary accepted-key collections with reparses staying accepted (ADR-002's sort-stability clause); hex posture: lowercase-only acceptance, length selects the algorithm (40 → sha-1, 64 → sha-256, every other length rejected), decoded length == digest_len; registry agreement (byte(try_from_byte(b)) == b for every accepted byte, reserved 0x00 and all unknown bytes reject — full byte-space sweep); derive determinism with the git-computed known-answer vectors pinned |
ops_ingress |
fetch_ranges_ingress + FetchRange/RangedBounds (the ranged-fetch bounds ingress, review-001 S-2 — via the #[doc(hidden)] fuzz/test re-exports of the exact production function), PinToken::from_hex (the renewal token ingress, review-001 S-5, src/ops/tokens.rs) |
ranges: no-panic on any Value (raw-byte serde_json parses plus arbitrary-derived JsonValues); the absent-field contract (no ranges field → Ok(None), the whole-form fetch); item-shape rejects (non-array ranges; items missing offset/len or carrying non-u64 shapes — negatives, floats, strings, nulls, nested) → Err with the stable domain text, never a panic, never Ok; bound arithmetic (checked_add overflow of the running sum → Err; a sum over max_total_bytes → Err carrying the wire numbers; a sum within the bound → Ok(Some) with every range's (offset, len) preserved exactly and in order); boundary exactness (total == bound accepts, bound + 1 rejects); the count bound is not this ingress's duty (an over-count array within the bytes bound parses Ok — the maxItems gate lives at the schema, alkcall's; pinned honestly, not asserted away); determinism. tokens: no-panic on any string (lossy-decoded and arbitrary-derived alike); length-before-decode (any length ≠ 64 → None, the 1 MiB canary pins the rejection); non-hex 64-char → None; case-insensitive decode agreement with the wire schema's pattern (upper == lower == Some, same bytes); round-trip closure (to_hex ∘ from_hex on accepted tokens); determinism |
Commands
cargo fuzz build and cargo fuzz run must be executed with fuzz/
(or deeper) as the working directory so rustup selects the pinned
nightly toolchain — the detached runner handles that itself.
# build (nightly, pinned by rust-toolchain.toml inside fuzz/; run from fuzz/)
cargo fuzz build
# agent sessions: detached campaign (never foreground; CWD-independent)
FUZZ_RUNTIME_SECS=600 fuzz/run-detached.sh <target> [extra libfuzzer args...]
# corpus replay through the invariants (stable toolchain, no nightly)
cargo test --manifest-path fuzz/shared/Cargo.toml
# coverage
cargo fuzz coverage <target>
The fuzz workspace is excluded from the main workspace (exclude in
the root Cargo.toml); it pins its own nightly toolchain via
rust-toolchain.toml and does not affect the root workspace's stable
toolchain or MSRV.