- §Decision 1's invariant walked clean per surface (facade write seam / key decode / wire ingress); the grep audit found no length-routed, default-derivation, or option-typed constructor; §Decisions 2/4/5/6/7 verified line by line; the §Decision 8 litmus walked with one named, dispositioned residue (the schema gate's width enumeration rides a novel-width member's admission ADR — the ADR's own literal text, not the defect class) - the Key::derive decomposition-time reading confirmed (the fold into the explicit Algorithm::derive; Key::from(Digest) the only Key-from-digest route) - inline fixes: put_known_as's mangled duplicate doc sentences removed; the CHANGELOG's [0.2.0] section reordered newest-first (alkcall's convention) - stamps: wave 14 completed in the implementation plan; AGENTS.md's status line updated (the superseded-shape warning retired) and its fuzzing paragraph's key_encoding target description aligned to ADR-020's shrunk surface - the review task completed with the gate record in its notes Verification: cargo test default 156/54/7/2; cargo test --all-features server-less 341/125/7/3 (clean skips) and against the live pglo-poc harness (the pg columns ran for real); cargo clippy --all-targets -- -D warnings and --all-features --all-targets -- -D warnings; cargo fmt --check; cargo doc --no-deps; the fuzz corpus-replay gate 7/7; cargo publish --dry-run --allow-dirty (0.2.0 packages)
16 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| review-wave-14 | Wave 14 review — the family-wire gate (the invariant is the thing the gate pushes against) | completed |
|
moderate | low | project | review |
|
Description
The wave-14 review gate. Review-003's F-2.3 named the failure mode this gate exists to correct: the wave-13 gate validated the bytes against the record while nothing validated the record against the invariant. ADR-020 §Decision 1 is that invariant, stated as record precisely so this gate has something to push against. The gate reads the landed surface against the invariant first, then the ADR's decisions line by line:
- The invariant (§Decision 1) — every address-forming or
address-matching surface requires the algorithm explicitly, never
an option, never a hidden default, never length-implied. Walk all
three: the facade's write seam (
put_as— the algorithm is a required parameter; no default constructor remains at any level,Digest::deriveandKey::deriveincluded), the key decode (Key::from_bytes— tag validated, per-tag length checked), the wire ingress (tag-prefixed key hex — the tag is in the payload, length is a check). Grep-audit: no length-routed or default-derivation constructor survives in the public API. - The wire form (§Decision 2) — tag-prefixed key hex everywhere;
bare hex dead; the schema gate
^[0-9a-f]{42}$|^[0-9a-f]{66}$; gate and ingress agree (S-5); theINVALID_DIGESTtexts describe the family-explicit contract. - The put handler (§Decision 4) — the offer's key + len both
mandatory; the tee hasher parameterized per the offered key's
algorithm, always opening at stream start; the store arm routes
put_as; the post-check arm deleted (one claim-check moment, pre-EOF); the response echoes the key ({token, key}); renewal/ confirm/have semantics unchanged. - The facade (§Decision 5) — one seam;
put,put_git_object,from_git_oid_hex,Digest::deriveabsent from the public API (andKey::deriveper the decomposition-time invariant reading — the gate confirms or contests that reading explicitly); the put.rs:360 threading honored; the unknown-length arm retained with the identity-family restart-reuse form. - Reads unchanged (§Decision 6) — no read signature moved
(
get/stat/list/has/delete/read_range); the asymmetry sentence carried in the ADR is reflected, not re-litigated. - The op set closed (§Decision 7) — five ops, no family-suffixed variants; the correction is payload-parameterization only.
- The litmus (§Decision 8) — walk it: a hypothetical VFS registry member's entire ask of the landed surface is a registry byte under its own ADR — no wire change, no facade change, no new method. Any per-consumer touch found is the defect class re-entering; name it.
- The pinning test — green, both family members, the bare-hex negative included; the would-have-failed-on-wave-13 justification on record.
- The record corrections — no doc advertises a deleted shape or
the superseded posture (the grep sweep); the CHANGELOG's
[0.2.0]entry current; the version at 0.2.0. - The full verification block green —
cargo test(default),cargo test --all-featuresserver-less and against the live pg harness (ALKBLOBS_PG_HOST=localhost,ALKBLOBS_PG_PORT=15432,ALKBLOBS_PG_USER=postgres,ALKBLOBS_PG_PASSWORD=poc,ALKBLOBS_PG_DB=blobs), both clippy variants,cargo fmt --check,cargo doc --no-deps, the fuzz corpus-replay gate (thekey_encodingtarget's shrunken surface). - Publish readiness —
cargo publish --dry-run --allow-dirtygreen for 0.2.0 (publication runs manually, coordinator-side — the gate does not publish). - The stamps — wave 14 marked completed in docs/plans/implementation.md; AGENTS.md's status line updated (the ADR-020 wave landed; the superseded-shape warning retired — the code no longer carries the wave-13 shapes).
- Residue named — anything the round surfaced without a home gets one (an OQ, a task, or an explicit not-ours), not a shrug.
Acceptance Criteria
- The invariant walk recorded per surface (facade / key decode / wire ingress), the grep audit clean
- §Decision 2/4/5 verified line by line against the landed code
- §Decision 6/7 verified (reads untouched; the op set closed)
- The §Decision 8 litmus walked and recorded
- The
Key::derivedecomposition-time reading confirmed or contested explicitly (contested → the operator decides before the stamp) - The pinning test green with its justification on record
- The record corrections verified (the docs grep sweep clean; CHANGELOG/0.2.0 current)
- Full verification block green, including the fuzz corpus-replay gate
cargo publish --dry-run --allow-dirtygreen for 0.2.0- docs/plans/implementation.md wave 14 stamped completed; AGENTS.md's status line updated
- Any residue named with a home
References
- docs/architecture/decisions/020-family-explicit-wire-payloads.md (the gate's contract — §Decision 1 first)
- docs/reviews/003-family-wire-gap-review.md (F-2.3 — the failure mode this gate's shape corrects)
- docs/plans/implementation.md (wave 14 charter; the review-gates section)
- tasks/wire-key-hex-ingress.md; facade-put-as-seam.md; ops-put-handler-key-claims.md; pinning-tests-family-wire.md; record-corrections-adr-020.md
- AGENTS.md (the verification block; conventions 5 and 12)
Notes
The invariant (§Decision 1) — walked clean per surface.
- Facade write seam:
put_as(algorithm, len: Option<u64>, stream)(src/store/put.rs:132) andput_in_batch(batch, algorithm, len, stream)(src/store/put.rs:181) are the only public write methods;put_known_as(pub(crate)) andput_unknowntake the threadedAlgorithmat every arm, the fleet arms included — the put.rs:360 re-hardcode is gone (put_unknownpasses its parameter tofleet_put_pglo_unknown, pinned byfleet_pglo_unknown_arm_honors_the_threaded_algorithm, src/store/fleet.rs:2583). Nomatches!onAlgorithmanywhere in src/store/ or src/ops/. - Key decode:
Key::from_bytes(src/keys.rs:92) — the tag validated viaAlgorithm::try_from_byte, the digest length checked per tag (digest_bytes.len() != algorithm.digest_len()), never inferred. The only other constructor surface isDigest::from_parts(pub(crate), the from_bytes internals). - Wire ingress:
digest_ingress(src/ops/wire.rs:82) — explicit lowercase-charset gate (S-5: the ingress accepts nothing the[0-9a-f]schema gate rejects), hex decode, thenKey::from_bytesverbatim. Bare 40/64-hex dies at the schema gate (^[0-9a-f]{42}$|^[0-9a-f]{66}$, wire.rs:168). - Grep audit: zero hits for
from_git_oid_hex/put_git_object/Key::derive/Digest::derive/Option<Algorithm>across src/, tests/, fuzz/shared/src/; no== 40/== 64length-routed checks in the ops/store/keys/hashing surfaces. The facade's remaining.put(...)call sites are all the engine-levelBackendtrait seam (opaque byte keys) — no facade-levelputsurvives.
§Decision 2 verified: tag-prefixed key hex across stat/have/
delete/fetch/put-offer (the shared digest_wire_schema/
digests_wire_schema and the put offer's key field all gate on
DIGEST_WIRE_PATTERN); the INVALID_DIGEST declared texts
(src/ops/specs.rs:52/152/299, fetch.rs, wire.rs:97) all state
"tag-prefixed key hex (lowercase, 42 or 66 chars)" — the
family-explicit contract, no blob-family hex-length language.
§Decision 4 verified: the offer schema requires namespace, key,
len (src/ops/put.rs:80-88); put_establish and put_handle both
refuse a missing key/len pre-ingress (post-schema-unreachable guards);
BlobHasher::new(offered_key.algorithm(), offered_len) opens
unconditionally at stream start (put.rs:346) — no
offered_len.is_some() gating survives; the store arm routes
put_as(offered_key.algorithm(), Some(offered_len), reader)
(put.rs:331); finish_put carries no cross-check arm and no
offered-digest parameters — the pre-EOF cross-check (put.rs:386-394)
is the one claim-check moment, running before the facade's EOF (the
kill-before-commit + DIGEST_MISMATCH settle); the response is
{token, key} with tag-prefixed hex::encode(key.as_bytes())
(put.rs:503-506); renewal/confirm/have semantics untouched (their ops
files changed only their doc vocabulary).
§Decision 5 verified: one seam; the deleted names absent from the
public API (grep-clean above); the unknown-length arm retained —
preamble members restart over preamble + staged bytes, the
identity-family restart-reuse pinned by
unknown_length_identity_family_put_lands_the_known_length_key
(src/store/tests.rs:1756); the seam-guard transformation pinned
(put_as_blob_algorithm_lands_under_the_canonical_derivation,
tests/git_interop.rs:478 — the old put's behavior is the seam's
blob arm); put_in_batch takes the algorithm and routes put_as.
§Decision 6 verified: get/stat/list/has/delete/
read_range signatures unchanged (src/store/read.rs, sweep.rs) —
reads name an existing address, the asymmetry carried, not
re-litigated.
§Decision 7 verified: five ops — blobs/stat, blobs/have,
blobs/delete (specs.rs), blobs/fetch
(channels/blobs/sub), blobs/put (channels/blobs/pub) — the
harness stubs are test-only; no family-suffixed op names exist.
§Decision 8 litmus — walked; one residue named. A new registry
member's ask of the landed surface: an Algorithm variant +
registry byte + digest_len/derive arms under its own ADR (the
registry itself — the admitted ask); Key::from_bytes,
digest_ingress, put_as, and every read op go generic with zero
changes. The one enumerated touch: DIGEST_WIRE_PATTERN
(src/ops/wire.rs:168) hardcodes the widths 42|66 (and
digest_ingress's message text repeats them) — a member reusing the
20/32-digest widths takes zero wire change; a member with a novel
digest width needs a one-line pattern extension alongside
digest_len's new arm. Disposition (named, not the defect class
re-entering): this is ADR-020 §Decision 2's own literal text (the
ADR pins ^[0-9a-f]{42}$|^[0-9a-f]{66}$ verbatim), so the landed
shape is the record — and the extension rides the member's
admission ADR in the same ceremony as digest_len's arm, a bounded
one-line touch in one file, not a per-consumer scoping or a
consumer-named shape. If a member with a novel width ever files, its
ADR carries that line; no separate OQ opened (the admission ceremony
is the home).
The Key::derive reading — confirmed, not contested. §Decision 1's
invariant ("never a hidden default at an address-forming boundary")
decides the Key-level form along with §Decision 5's Digest::derive
sentence: both silent canonical wrappers fold into the explicit
Algorithm::derive(algorithm, content, len) (src/hashing.rs:157),
and Key::from(Digest) (src/keys.rs:161) remains as the only
Key-from-digest route — fully explicit, nothing hidden. No caller,
test, or doc needed the deleted forms (the sweep was mechanical); the
reading is consistent and the operator's confirmation is not required
to close the wave.
The pinning test — green with its justification on record.
a_facade_published_family_entry_is_wire_readable_end_to_end
(src/ops/tests.rs:2034), parameterized over both family members
(GitObjectSha1 tag 0x03/42-char, GitObjectSha256 tag
0x04/66-char, from one shared loose preimage — the cross-family
vector in-crate): facade put_as → oid-identical digest asserted,
wire stat (ADR-018 §Decision 3's stored-preimage length), wire have
(present: [true]), ranged fetch with slice-digest verification, a
wire-offered put landing under the claimed oid (echoed key ==
offer, custody, token), the landed entry then serving stat + whole
fetch. The bare-hex negative rides the malformed-wire-sweep battery
(schema-gate INVALID_INPUT for the put and stat shapes) and the
ingress-guard test (handler-direct INVALID_DIGEST). The recorded
justification: on the wave-13 surface the have leg answered
present: false (the ingress length-routed the bare oid onto a
blob-family key) and the test's tag-prefixed offer failed the 40/64
schema gate before any decode — the test could not have passed.
The record corrections — verified. The repo-wide grep sweep for
the deleted names as current-surface claims: every remaining hit is a
historical record (ADRs, reviews, open-questions, the plan's wave
text) or a deletion note; src/ops/ docs, the README, fuzz/README.md,
tests/conformance/README.md all describe put_as + tag-prefixed key
hex. CHANGELOG's [0.2.0] entry present with both breaks and the
ADR-020 ref (the review moved it above [0.1.0] for the
newest-first ordering alkcall's file follows — the one inline
correction); Cargo.toml at 0.2.0.
Inline fixes taken by this gate (minor, reasonable):
put_known_as's doc comment carried a mangled duplication of its
closing sentences (src/store/put.rs) — deduplicated; the CHANGELOG's
[0.2.0] section sat below [0.1.0] — reordered newest-first. Also
the gate's own stamps (AGENTS.md status line + the fuzzing
paragraph's stale target description; the plan's wave-14 stamp).
Full verification block — green, re-run at this gate (post
inline-fixes): cargo test default (156 unit / 54 conformance /
7 git-interop / 2 readme), cargo test --all-features server-less
(341 / 125 / 7 / 3, clean skips) and against the live pglo-poc
harness (341 / 125 / 7 / 3 — the pg columns ran for real, 88.67s and
96.17s wall times), cargo clippy --all-targets -- -D warnings and
cargo clippy --all-features --all-targets -- -D warnings, cargo fmt --check, cargo doc --no-deps, the fuzz corpus-replay gate
(7/7), cargo publish --dry-run --allow-dirty (0.2.0 packages,
upload aborted per dry run).
Residue named: only the §Decision 8 width-enumeration note above (dispositioned into the member-admission ceremony). Nothing else the round surfaced lacked a home.
Summary
The wave-14 gate reads green against ADR-020's invariant. §Decision 1
walked clean per surface — the facade's one put_as/
put_in_batch write seam with the derivation threaded through every
internal arm (the put.rs:360 re-hardcode fixed and fleet-pinned), the
key decode's tag-validated per-tag-length check, and the wire
ingress's tag-prefixed-key-hex contract behind the 42/66 schema gate
(the S-5 agreement pinned by test); the grep audit found no
length-routed, default-derivation, or option-typed constructor
surfacing anywhere. §Decisions 2/4/5 verified line by line (mandatory
{namespace, key, len} claims, the tee hasher opened unconditionally
at stream start under the offered key's algorithm, the store arm
routing put_as, exactly one pre-EOF claim-check moment with the
post-check arm deleted, the {token, key} response); §Decision 6/7
verified (read signatures untouched, the five-op set closed); the
§Decision 8 litmus walked with one named, dispositioned residue (the
schema gate's width enumeration rides a novel-width member's
admission ADR — ADR-020's own literal text, not the defect class).
The Key::derive decomposition-time reading is confirmed: both
silent constructors fold into the explicit Algorithm::derive,
Key::from(Digest) the only Key-from-digest route. The pinning test
is green for both family members with the wave-13 failure mode
recorded (the have leg's present: false was the wall). Record
corrections verified repo-wide; CHANGELOG/0.2.0 current (section
reordered newest-first inline). The full verification block —
including the live pg harness columns and the fuzz corpus replay —
plus the 0.2.0 publish dry-run are green. Wave 14 stamped completed;
AGENTS.md's status line retired the superseded-shape warning.