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

145 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: core-hashing-keys
name: Hashing & keys — the canonical derivation and the one-way-door encoding
status: completed
depends_on: [core-error-model]
scope: narrow
risk: medium
impact: project
level: implementation
tags: [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
- [x] `Digest`/`Key`/algorithm enum per ADR-002; encoding byte-exact
(33/21 total lengths; 0x01/0x02 tags; 0x00 rejected)
- [x] Canonical derivation matches `git hash-object` byte-exact for
both algorithms (CLI-driven test, both a small and a multi-KiB
content case)
- [x] `from_git_oid_hex` round-trips external oids into pool keys
- [x] Rejects: truncated keys, unknown algorithm bytes (incl. 0x00,
0xFF), wrong digest lengths → `Error::KeyInvalid`, never guesses
- [x] Total-order sort stability asserted (mixed-algorithm collection
sorts deterministically)
- [x] Doc comments carry the one-way-door warning and the registry
note (new algorithm = new ADR)
- [x] `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`.