- put_as(algorithm, len: Option<u64>, stream) is the facade's sole public write surface; put, put_git_object, put_known, and the matches! family restriction are deleted outright (no shim, 0.x) - put_in_batch takes the algorithm parameter (batch, algorithm, len, stream); the one seam routes every member - Digest::derive, Key::derive, Digest::from_git_oid_hex deleted; explicit construction is Algorithm::derive / Key::from_bytes over the physical encoding - the put_unknown fleet arm honors its threaded algorithm parameter (the put.rs:360 re-hardcode), pinned by a live pg-lo fleet test - the unknown-length arm retained per §Decision 3; its identity-family form pinned (buffer + staged routes land the known-length key) - the seam-guard test transforms: blob algorithms are legal at the seam; put_as lands the canonical blob derivation (git vectors) - caller sweep: ~250 sites across src/, tests/, and the conformance columns; the ops put handler's store arm rides the seam's blob arm canonically until the put-handler task (§Decision 4) - fuzz key_encoding target: the from_git_oid_hex arm dies with the constructor; seed regeneration a no-op diff; corpus-replay gate green Verified: cargo test (default), cargo test --all-features server-less and against the live pglo-poc harness, both clippy variants (-D warnings), cargo fmt --check, cargo doc --no-deps, the fuzz corpus-replay gate.
14 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| facade-put-as-seam | The facade's one derivation-explicit put seam — put_as alone; the fossil surfaces deleted | completed |
|
broad | medium | project | implementation |
|
Description
ADR-020 §Decision 5's facade unification (the second seam of
§Decision 9's order; depends on the wire task because it deletes
Digest::from_git_oid_hex, whose last src/ callers the wire task
removes). The facade's write surface becomes put_as(algorithm, len: Option<u64>, stream) → (Key, Pin) alone:
putandput_git_objectare deleted outright — no shim (0.x, zero in-repo callers, one out-of-repo POC composition; every publish the family-named method survived would teach the approaching consumers the wrong shape). Thematches!restriction (src/store/put.rs:159-164) simply ceases to exist — convention 12 satisfied by construction.put_in_batchtakes the algorithm parameter —put_in_batch(batch, algorithm, len, stream); the member put runs the one seam.- The one seam's semantics (ADR-020 §Decision 5, verbatim
contract):
algorithmis a required input. Preamble-carrying members (the blob family) hash preamble-then-content — the known-length path'sBlobHasher::new(algorithm, len)preamble initialization as today. Identity-family members take the preimage verbatim withlenas the stored-length check (the no-preamble hasher arms as today). The unknown-length arm (None) is retained per §Decision 3: preamble-carrying members restart the derivation pass over preamble + staged bytes (today's shape); the identity-family restart-reuse is the permitted simple implementation — correct today, single-pass-capable if ever measured worth it. No canonical default is blessed anywhere. - The put.rs:360 re-hardcode fixed:
put_unknown's pg-lo consolidation arm passesAlgorithm::GitBlobSha256while ignoring its own threadedalgorithmparameter — the threading exists and is honored (the defect was invisible only because the sole caller happened to pass the constant). Digest::derivedeleted (src/hashing.rs:270 — the silent canonical constructor): explicit construction isAlgorithm::derive(algorithm, content, len).Key::derivefolds with it (src/keys.rs:50 — the same silent canonical constructor one wrapper up). The ADR's §Decision 5 sentence namesDigest::derive; §Decision 1's invariant decides the Key form — it is an address-forming surface with a hidden default ("never a hidden default"), and a Key-level re-wrap ofAlgorithm::deriveadds nothing overKey::from(Algorithm::derive( ...)). Its callers are tests plus one#[cfg(test)]helper (src/local.rs:899) — the sweep is cheap. Flag to the operator at the gate if this reading is contested: it is a decomposition-time application of the invariant, not a new decision.Digest::from_git_oid_hexdeleted (src/hashing.rs:315 — the length-routed constructor, the last length-implied address-forming surface): family-explicit construction from oid bytes isKey::from_bytesover the physical encoding.- The caller sweep — every in-repo caller of the deleted
surfaces: src/store/tests.rs, src/store/fleet.rs (the family fleet
tests'
put_git_objectcalls →put_as), src/store/construct.rs and src/store/sweep.rs and src/local.rs (Key::derivetest helpers), src/facade.rs and src/hashing.rs and src/keys.rs (Digest::derivein tests), tests/conformance/rows.rs (the family rows'put_git_object→put_as), tests/conformance/sqlite_column.rs (Key::derive), tests/git_interop.rs (put_git_object→put_as; the seam-guard testput_git_object_rejects_blob_algorithmstransforms — the blob algorithms are now legal at the seam; its replacement pins thatput_as(GitBlobSha256, ...)lands under the canonical blob derivation, i.e. the oldput's behavior is the seam's blob arm), src/ops/tests.rs (Digest::derivesites). - The public surface (src/lib.rs re-exports + the crate-root
//!docs):put_as/put_in_batchexported;put/put_git_object/from_git_oid_hexgone.put_as's rustdoc is contract text (store-api.md §Write path's shape — the full spec restatement is record-corrections'; the rustdoc must not contradict). - The fuzz
key_encodingtarget (fuzz/shared/src/key_encoding.rs): theDigest::from_git_oid_hexarm dies;Digest::derivesites becomeAlgorithm::derive; the target's doc comment's surface list updated. Seeds: runpython3 fuzz/gen_fuzz_seeds.pyand record the disposition — the generator may reference the deleted constructor's input shapes (the 40/64-hex oid batteries); per the corpus policy the committed seeds regenerate if the input shape changed, and a no-op diff is the expected outcome if they don't. The corpus-replay gate (cargo test --manifest-path fuzz/shared/Cargo.toml) must stay green either way.
Reads unchanged, by principle (ADR-020 §Decision 6): get/stat/
list/has/delete/read_range signatures are untouched — the
asymmetry is the design; this task does not re-audit the read path.
Acceptance Criteria
- The public write surface is
put_as+put_in_batch(with the algorithm parameter) alone;put,put_git_object,Digest::from_git_oid_hex,Digest::derive,Key::deriveare absent from the crate (grep-clean across src/, tests/, and the fuzz sidecar's compiled surface) put_ascovers every registered algorithm: the blob family preamble arm and the identity-family verbatim arm byte-identical to the pre-round behaviors (the conformance family rows and the git-interop CLI-validated tests green unchanged in substance)- The unknown-length arm retained:
put_as(algorithm, None, ...)for a preamble member restarts over preamble + staged bytes; for an identity member the restart-reuse form lands the same key — pinned by test - The put.rs:360 re-hardcode fixed — the threaded
algorithmis honored (pinned by a test that drives the pg-lo unknown arm under a non-default algorithm, or by the threading's direct assertion if the arm is env-gated) - The fuzz
key_encodingtarget compiles against the new surface; the seed disposition recorded; the corpus-replay gate green - Gates green: the full AGENTS block —
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— thepglo-poccontainer; server-less runs skip cleanly), both clippy variants,cargo fmt --check,cargo doc --no-deps, the fuzz corpus-replay gate
References
- docs/architecture/decisions/020-family-explicit-wire-payloads.md
(§Decision 5 — this task's contract; §Decision 1 — the invariant
that decides the
Key::derivefold; §Decision 3 — the retained unknown-length arm; §Decision 6 — reads unchanged) - docs/reviews/003-family-wire-gap-review.md (F-4; D-2, D-4)
- docs/architecture/store-api.md (the write path's WHAT — record corrections restates it)
- src/store/put.rs (put :98, put_git_object :153, the matches! :159, put_known :222, put_known_as :241, put_unknown :350, the :360 hardcode); src/hashing.rs (:270 Digest::derive, :315 from_git_oid_hex); src/keys.rs :50 (Key::derive); src/lib.rs; tests/conformance/rows.rs; tests/git_interop.rs; fuzz/shared/src/key_encoding.rs; fuzz/gen_fuzz_seeds.py
Notes
The seam's landed shape (src/store/put.rs): put_as(algorithm, len: Option<u64>, stream) → (Key, Pin) is the one public write
method — put, put_git_object, and the internal put_known
deleted outright (the matches! restriction died with them;
convention 12 by construction). put_in_batch takes
(batch, algorithm, len, stream); both arms route through it, not
through a deleted surface anywhere. The rustdoc is the store-api.md
§Write path contract verbatim in substance (both arms, the three
numbered unknown-length rules, pin-before-publish, kv-only
rejection, fleet forms, the stat semantic).
The put.rs:360 re-hardcode fixed: put_unknown now passes its
threaded algorithm parameter to fleet_put_pglo_unknown (the
threading existed; the defect was invisible because put was the
only caller and passed the constant). Pinned by a new pg-lo fleet
test (fleet_pglo_unknown_arm_honors_the_threaded_algorithm,
env-gated but driven live at the gate): put_as(GitObjectSha256, None, ...) over the declared threshold lands the family's plain-hash
oid — under the old hardcode the key would carry the blob tag and
preamble derivation.
The unknown-length arm's retention pinned per ADR-020 §Decision
5/§Decision 3:
unknown_length_identity_family_put_lands_the_known_length_key (new,
src/store/tests.rs) — put_as(GitObjectSha1, None, ...) lands the
known-length key on both the buffer (kv) and staged-bytes (large)
routes; the preamble member's restart-over-preamble + staged bytes
was already pinned by unknown_length_put_round_trips_on_both_tiers
and the git-interop both-tiers test, unchanged in substance.
The seam-guard test transformed as the task specified: the blob
algorithms are now legal at the seam, so
put_git_object_rejects_blob_algorithms →
put_as_blob_algorithm_lands_under_the_canonical_derivation —
put_as under either blob member lands the git-computed
known-answer vector (the old put's behavior is the seam's blob
arm).
The derivation constructors: Digest::derive,
Key::derive, and Digest::from_git_oid_hex are gone; explicit
construction is Algorithm::derive(algorithm, content, len), and
family-explicit construction from oid bytes is Key::from_bytes over
the physical encoding ([registry byte] + oid bytes). The interop
test file gained one helper (oid_key) implementing exactly that, so
every external-oid round-trip leg keeps its shape. The deleted
from_git_oid_hex hex battery in src/hashing.rs was replaced by a
physical-encoding battery (tag validation, truncation, wrong-tag
rejects).
Caller sweep shape: ~250 sites across src/, tests/, and the
conformance columns switched mechanically (.put(...) →
.put_as(Algorithm::GitBlobSha256, ...), put_git_object calls →
put_as(alg, Some(len), ...), Key::derive/Digest::derive →
Algorithm::GitBlobSha256.derive(...)), with per-module Algorithm
imports added where the sweep introduced the name. The one
non-mechanical caller: src/ops/put.rs's store arm now calls
put_as(Algorithm::GitBlobSha256, offered_len, reader) — the
interim canonical routing until the put-handler task (ADR-020 §Decision
4) parameterizes the tee hasher and store arm per offer algorithm;
the no-side-door audit's verb set was refreshed (put → put_as).
fuzz key_encoding target: the from_git_oid_hex arm died with
the constructor (HexVerdict/hex_verdict/assert_hex_invariants/
assert_hex_battery removed); Digest::derive sites became
Algorithm::GitBlobSha256.derive; Key::derive sites became
Key::from(Algorithm::GitBlobSha256.derive(...)); the structured
path re-points its string handling at Key::from_bytes (no string
parser remains); the target's doc comment's surface list updated.
Seed disposition: the generator's output does not depend on the
deleted constructor (the hex-form seeds are raw byte inputs either
way — the 40/64-hex oid batteries' bytes are unchanged); the
regeneration was a no-op diff on the committed corpus (verified, zero
working-tree entries), and the corpus-replay gate is green. A
comment-only update in fuzz/gen_fuzz_seeds.py records the
disposition ("hex-ish string forms ... no string parser accepts these
anymore; they persist as raw key-parser inputs for corpus
stability").
Doc sweep in code: lib.rs root docs, src/store/mod.rs and
src/store/fleet.rs module docs, fleet/family test doc comments and
names (fleet_kv_put_as_lands_through_the_joint_tx,
fleet_pglo_put_as_lands_through_the_consolidation_tx),
put_git_object_put_get_round_trip →
put_as_family_member_round_trips_under_the_oid_key in
tests/git_interop.rs — no code doc names the deleted surface as
current. README/store-api/hashing-and-keys restatement stays
record-corrections' scope; store-api.md already describes put_as
(the rustdoc was written against it and does not contradict).
Verification (all green): cargo test (default: 156 lib / 54
conformance mem+sqlite columns / 7 git-interop / 2 readme);
cargo test --all-features server-less (clean skips: 343 / 125 / 7 /
3) and against the live pglo-poc harness (343 / 125 / 7 / 3 — the pg
columns ran for real); both clippy variants with -D warnings;
cargo fmt --check; cargo doc --no-deps; the fuzz corpus-replay
gate. One transient parallelism flake under the first full harness
run (cancelled_acquire_cannot_strand_the_advisory_lock, a
wall-clock cadence test untouched by this task) passed on the full
re-run and in isolation — the recorded run-discipline posture, not a
regression.
Summary
The facade's write surface is one derivation-explicit seam,
put_as(algorithm, len: Option<u64>, stream) → (Key, Pin), with
put_in_batch taking the algorithm parameter: put,
put_git_object, Digest::derive, Key::derive, and
Digest::from_git_oid_hex are deleted outright (grep-clean across
src/, tests/, and the fuzz sidecar's compiled surface), the matches!
family restriction ceases to exist, the unknown-length arm is retained
with its semantics pinned for both derivation families, the
put.rs:360 re-hardcode is fixed and pinned by a live pg-lo fleet test,
and the fuzz key_encoding target compiles against the new surface
with a no-op seed-regeneration disposition. Every registered algorithm
writes and reads byte-identically to the pre-round behaviors (the
conformance family rows and the CLI-validated git-interop tests are
unchanged in substance), and the full AGENTS gate block is green.