Read the wave-2 implementation against the pinned ADR text line by
line (ADR-003/005/006/010/011/012/013 §2, store-api.md): the three
numbered unknown-length rules each test-backed, the delete-window
ordering invariants (arbitrate-at-delete-time, pin-before-publish on
both put paths and both tiers, raced conversion ordering,
delete-then-recover), abort-as-data with all three causes, dispatch
purity + fall-through + constructor modes + kv-only's two put-time
rejection points, stage-hygiene exact counts on both engines (incl.
the Drop-cancellation story), both suite columns green/ADR-stamped/
traceable with the factory binding recorded, the dispatch-coverage
shape exercised, all eight named design points recorded and
consistent, and the staged-put key-at-commit amendment audited
accepted. No wave-3 amendments arise.
Inline fixes applied by the gate:
- facade.rs: stale Batch::add dead-code allow + stale task-pointer
comment removed (put_in_batch's call sites landed)
- dispatch.rs: module-level allow(dead_code) removed (route/Tier now
driven; clippy clean without it), scope note updated
- backend.rs: the allow kept but its scope note corrected to what is
actually dead (trait name, kv put until wave 3)
- mem.rs: stale constructor doc line updated to the landed state
- tasks/gc-protocol.md: stray placeholder lines removed
Verification: cargo test (42 lib + 2 conformance + 3 git-interop),
cargo test --features mem and --all-features (97 lib + 37 conformance
+ 4 git-interop), clippy --all-targets and --all-features --all-targets
(-D warnings), fmt --check — all green post-fix. Wave 3 decomposition
unblocked.
- `put(len: Option<u64>, stream: impl AsyncRead + Unpin + Send) -> (Key, Pin)`:
known-length one-pass flow; unknown-length buffer-to-threshold flow with
the mid-stream fresh-stage flush (ADR-003 option (d), three numbered rules:
flush-and-continue, derivation over the eventual stored artifact as a
whole via buffer form and StagingCursor read-back form, zero residue on
stream error)
- staged-put seam amended (operator sign-off): `put_staged()` keyless
handout, `StagingCursor::commit(self, key)` publishes under the
store-core-resolved key — the canonical key completes at stream end and
the wave-1 handout shape could not express the commit-rename flow
- pin-before-publish rides the arbitration lock on both paths and both
tiers; dedup re-puts refcount the existing entry
- Batch seam: `Store::batch()` + `put_in_batch()`, members publish pin-
before-publish, group pins drop together on `Batch::commit` (new public
verb) or drop
- kv-only rejection (ADR-012 §6.2): known-length over-threshold rejects
immediately (stream never read), unknown-length at mid-stream overflow;
zero residue; error carrier `Io(String)` (named design point)
- declared-len mismatch rejects `Verification` zero-residue (named design
point); stream shape generic-no-dyn (named design point)
- incremental BlobHasher (pinned byte-equal to Algorithm::derive);
AnyStaging cursor dispatch; git-interop put-path leg against
git hash-object; eleven put-path behavior tests
Verification: cargo test (default/--features mem/--all-features) green;
clippy --all-targets -D warnings green in all three feature shapes;
cargo fmt --check green; cargo doc --no-deps at the 24-warning baseline.
The gc-protocol task (ADR-005; abort-as-data per ADR-012 §1; non-SQL
inline window staging per ADR-012 §5) wired on the Store facade:
- src/gc.rs — GcHome + RegistryCore: one arbitration lock over per-key
pin refcounts, in-flight delete markers, and the registered source
lists; async pin minting (acquire) that blocks on in-flight deletes
(ADR-005's re-put case (a) delete-then-recover without engine I/O
under the lock); the ordered-step convert primitive (pin release +
registration in one section); batch one-step release; the
single-node sweep lease (the in-process analog of ADR-013 §2).
- src/facade.rs — Pin/Batch carry the registry back-reference and own
their drop release (batch drop decrements every member in one
ordered step); new public vocabulary LivenessSource, ProtectCallback,
Registration, re-exported from lib.rs.
- src/store.rs — sweep() (single-flight → zero-source safe default →
protect consult → mark over both tiers' list() union → arbitrate at
delete time → ~100/batch commit → exact-count report or
abort-as-data), direct delete() (existence probe → full protection
set per ADR-012 §6.4 → GcRefuse/Missing/delete through the
fall-through), and the registration/unregistration verbs.
- 16 new tests + the facade pin/batch shape test reworked on the wired
machinery: exact counts across both tiers, abort shapes, dedup
refcounts, batch drops, live-shared sources, the conversion ordered
step sequentially and raced, sweep-vs-pinned-put (64 rounds, every
schedule, byte-identical delete-then-recover), publish-after-mark,
no-torn reads under a window, single-flight contention, and
malformed-list sweeps as operational errors.
- tasks/gc-protocol.md — status completed; named design points recorded
in Notes.
Verification: cargo test (default) 46 passed; --features mem and
--all-features 89 passed each; cargo clippy --all-targets -- -D
warnings (default and --all-features) clean; cargo fmt --check clean;
cargo doc --no-deps at the pre-existing baseline (no new warnings).
- Public `(len, stream)` shape: `Store::get` returns
`Option<(u64, Blob)>`; `Blob` (src/facade.rs) is a bounded, re-entrant
windowed reader over the new crate-internal `AnyCursor` tier dispatch
(mem materializing / local pread body families) — engine cursors
never leak onto the surface; no chunk-iterator machinery.
- Verification-on-get pinned against ADR-006: kv-tier materialized
reads whole-blob hash-check at get time (mismatch = Err(Verification),
never a further fall-through); the large tier's streaming open is
metadata + handle only (put-path verification + slice digests are its
story); `read_range` carries slice digests only, on both tiers.
- `stat` rides the trait's `size` probe (never a content load) into
`EntryMeta { len, algorithm }`; `has` is the pure existence probe;
both fall through kv -> large like every key-addressed op (ADR-012
§6.1) — consult order, not routing (never consults `tier_for`).
- Miss shapes enforced (the error module's behavioral note): both-tier
misses are Ok(None) on get/stat, false on has, Err(Missing) on
read_range; `src/error.rs` wave-2 deferral notes updated to enforced
state.
- `list` decided Vec<Key>: materialized, sorted total-order union of
both tiers (the sweep consumes the engines' enumeration directly);
malformed pool bytes are fatal `Err(KeyInvalid)`, never a silent
skip.
- `KeyInvalid` at the facade boundary: ops take opaque key bytes and
convert via `Key::from_bytes` once (`boundary_key`); engines never
see typed keys.
- Twelve read-path tests: virgin-store shapes per mode (dual-tier,
mem-only, kv-only), the `(len, stream)` windows, fall-through,
kv-first (decoy content in the large tier), both verification
postures, list union/sort/pool-fatality, and malformed-key
rejection on every op.
Task: tasks/store-read-paths.md (design points recorded in Notes for
the wave-2 review gate).
Verification: cargo test (default), cargo test --features mem, cargo
test --all-features green; cargo clippy --all-targets -- -D warnings
green in all three feature shapes; cargo fmt --check green; cargo doc
--no-deps clean beyond the pre-existing private-intra-doc-links class.
The wave-1 contract surface: everything engine-blind that waves 2-6
implement against. No engine bodies, no facade behavior logic.
- src/backend.rs: the pub(crate) Backend trait — seven methods +
put_staged, opaque byte keys/values, size metadata-only, list
complete-and-well-formed, virgin-store reads as no-ops (ADR-003,
ADR-008); the ReadCursor/StagingCursor I/O seams with the read_range
slice-digest convention (ADR-006) at one seam; unit tests against
both body-family doubles, a test-double engine, and the stage-hygiene
invariant's two forms (discard; commit failure self-discards).
- src/gc.rs: the engine-state companion seam as EngineWindowState /
EngineFleetState — ADR-012 §5's applicability table expressed
structurally (no dead machinery); JointTx ownership rule pinned in
the signature (store holds the tx; engine contributes mechanics).
- src/facade.rs: EntryMeta, Pin, Batch, SweepReport, GcAbortCause
(x3, #[non_exhaustive]), PinState with lifecycle contracts
(ADR-005, ADR-012 §1); re-exported from lib.rs.
- Design points pinned for the wave-1 review gate in
tasks/core-trait-and-types.md: pub(crate) trait visibility,
associated-type put-form shapes, AFIT + associated types (no dyn).
Verification: cargo test (26 lib + 3 git-interop), cargo test
--all-features, clippy --all-targets -D warnings (default and
--all-features), cargo fmt --check, cargo doc --no-deps — all green.