Wave 1 task core-hashing-keys: the crate's declared one-way door,
implemented byte-exact to ADR-002.
- src/hashing.rs: Algorithm (exactly GitBlobSha256 canonical /
GitBlobSha1 tolerated; BLAKE3 absent), Digest (known-length
derivation H("blob <len>\0" + content) as primary constructor;
from_git_oid_hex interop constructor, lowercase-only acceptance),
and the hashing::registry constants (0x01/0x02, 0x00 reserved; new
algorithm = new ADR)
- src/keys.rs: typed Key wrapper; as_bytes/from_bytes are the only
backend-boundary conversions; 1 algorithm byte + digest bytes
(33/21 total); rejects-never-guesses validation -> Error::KeyInvalid
- tests/git_interop.rs: the load-bearing test — byte-exact derivation
against the `git hash-object` CLI, both algorithms, small and
75-KiB content; external-oid round-trip into pool keys; both oid
formats coexisting in one flat pool
- named design point (oid hex policy): lowercase-only acceptance,
recorded in the task Notes and on from_git_oid_hex
Verified: cargo test (15 passed), cargo clippy --all-targets -- -D
warnings, cargo fmt --check, cargo test --all-features — all green.
Hashing & keys — the canonical derivation and the one-way-door encoding
completed
core-error-model
narrow
medium
project
implementation
wave-1
keys
Description
Implement the content-address layer (src/keys.rs or keys/ +
hashing, one module per file per convention) per ADR-002 and
hashing-and-keys.md. This is the crate's declared one-way door — key
bytes persist in backend rows and filenames with no migration story —
so the encoding is implemented exactly to the ADR text and reviewed
line-by-line at the wave gate.
Digest — algorithm + digest pair. Algorithm enum with exactly
two members: GitBlobSha256 (canonical), GitBlobSha1 (tolerated
legacy). BLAKE3 is absent from the crate entirely (ADR-002 §Decision
class 3).
Canonical derivation: H("blob <len>\0" + content) — SHA-256
canonical, SHA-1 same derivation. The preamble is the algorithm's
domain separation, not a special case (ADR-002 §The preamble as
abstraction). Known-length derivation is the primary constructor;
the unknown-length path's derivation-over-staged-bytes restart is
wave 2 facade logic, not here.
Interop constructor: from_git_oid_hex shape (POC #1 finding 4)
— a pre-computed pair from an external git oid, both algorithms.
Key — the typed wrapper; as_bytes() / from_bytes() are the
only conversions across the backend boundary. Physical encoding per
ADR-002: 1 algorithm byte + digest bytes — 0x01 =
git-blob-sha-256 (32-byte digest; total 33), 0x02 = git-blob-sha-1
(20-byte digest; total 21). 0x00 is reserved and never assigned.
Validation: truncated keys and unknown algorithm bytes are
rejects (Error::KeyInvalid), never guesses (the POC's
length-checking test is the standard).
Sort stability: fixed-length enum-tagged keys sort in total
order (required for the sweep's live-set diffs) — asserted by test.
Registry constants: the algorithm bytes are the ADR-002
registry; new algorithms require a new ADR (doc comment states
this).
Interop test (the load-bearing test): byte-exact derivation
against the git hash-object CLI for both algorithms (POC #1
findings 4/5's standard — validated against git, not just hardcoded
vectors), plus the external-oid round-trip: from_git_oid_hex →
as_bytes → from_bytes → equal, and tagged-key coexistence of
both algorithms in one flat collection.
Named design point for the review gate: oid hex parsing policy —
git oids are lowercase hex; pin lowercase-only acceptance (reject
uppercase as malformed) or case-insensitive acceptance, with the
choice recorded. Lean lowercase-only (git's own output is always
lowercase; strictness at the boundary matches rejects-never-guesses)
unless implementation finds a concrete interop reason otherwise.
Deps: sha2, sha1 (RustCrypto), hex — added to the manifest by
this task.
Acceptance Criteria
Digest/Key/algorithm enum per ADR-002; encoding byte-exact
(33/21 total lengths; 0x01/0x02 tags; 0x00 rejected)
Canonical derivation matches git hash-object byte-exact for
both algorithms (CLI-driven test, both a small and a multi-KiB
content case)
from_git_oid_hex round-trips external oids into pool keys
Module layout: two files, one module per file — src/hashing.rs
(the hash side: Algorithm + Digest + the derivation + the
interop constructor) and src/keys.rs (the key side: the typed
Key wrapper + the physical encoding). The scaffold's stub doc on
hashing.rs was replaced by the full module doc. Both re-exported
from lib.rs (Algorithm, Digest, Key, mirroring the
Error re-export).
Named design point resolved — oid hex parsing policy:
lowercase-only acceptance.git hash-object's own output is
always lowercase; strictness at the boundary is the
rejects-never-guesses posture. Uppercase oids are rejected as
malformed (Error::KeyInvalid) — the check is a byte scan for
[0-9a-f] before decode (the hex crate's decode alone would
accept uppercase). Policy stated verbatim on
Digest::from_git_oid_hex and covered by test. If an external
interop consumer ever needs uppercase acceptance, that is a change
at this boundary with its own test.
Algorithm is declared GitBlobSha256 before GitBlobSha1 so
derived Ord matches registry-byte order; the sort-stability test
asserts derived order == as_bytes() byte order. Digest keeps
its fields private behind from_parts (pub(crate)), so every
digest's length stays pinned by its algorithm.
Registry constants live in hashing::registry (GIT_BLOB_SHA256
0x01, GIT_BLOB_SHA1 0x02, RESERVED 0x00) with the new-ADR doc
note. Algorithm::try_from_byte is public; Key::from_bytes is
the boundary consumer.
Deps pinned as the task scoped: sha2 0.10, sha1 0.10, hex 0.4 (RustCrypto + hex). The generic derivation helper is bound
over the RustCrypto digest trait so the derivation body has zero
per-algorithm special-casing; the match arms pick only the hasher.
No unwrap/expect/panic outside tests; library paths are
Result/Option only (error.rs's Error::KeyInvalid carries the
boundary rejections).
Summary
src/hashing.rs + src/keys.rs implement the content-address layer
per ADR-002, the crate's declared one-way door. Algorithm (exactly
GitBlobSha256 canonical / GitBlobSha1 tolerated; BLAKE3 absent),
Digest (known-length derivation H("blob <len>\0" + content) as
primary constructor; from_git_oid_hex interop constructor with
lowercase-only acceptance, both oid formats selected by length), and
Key (typed wrapper; as_bytes/from_bytes the only backend-boundary
conversions; 1 algorithm byte + digest bytes — 0x01+32, 0x02+20,
33/21 total) with rejects-never-guesses validation (Error::KeyInvalid
for truncated/unknown-tag/wrong-length/reserved-0x00; never a guess).
Registry constants in hashing::registry with the new-ADR doc note;
one-way-door warnings in both module docs. Tests: byte-exact
derivation against the live git hash-object CLI (the load-bearing
test — both algorithms × empty/short/binary-NUL/75-KiB, sha256 repos
via git init --object-format=sha256 per POC finding 4's harness
gotcha), external-oid round-trip into pool keys, both-oid-formats
coexistence in one flat pool, hardcoded git-computed vectors, encoding
byte-exactness, full rejection matrix, and derived-order == byte-order
sort stability on mixed-algorithm collections. Verified: cargo test
(15 passed), cargo clippy --all-targets -- -D warnings,
cargo fmt --check, and cargo test --all-features all green. Deps
added: sha1, sha2, hex.