Files
alkblobs/tasks/core-hashing-keys.md
T
glm-5.3-flash fef05b4ec5 feat(keys): the content-address layer — canonical hash derivation and key encoding (ADR-002)
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.
2026-10-11 18:20:12 +00:00

7.4 KiB
Raw Blame History

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
core-hashing-keys 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
  • Rejects: truncated keys, unknown algorithm bytes (incl. 0x00, 0xFF), wrong digest lengths → Error::KeyInvalid, never guesses
  • Total-order sort stability asserted (mixed-algorithm collection sorts deterministically)
  • Doc comments carry the one-way-door warning and the registry note (new algorithm = new ADR)
  • cargo test, cargo clippy --all-targets -- -D warnings, cargo fmt --check green

References

  • docs/architecture/hashing-and-keys.md
  • docs/architecture/decisions/002-canonical-hash-and-key-encoding.md
  • docs/research/poc-trait-dispatch-findings.md findings 4/5
  • AGENTS.md convention 5 (the one-way door)

Notes

  • 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.