Files
alkblobs/tasks/store-read-paths.md
T
glm-5.3-flash 6bce301c55 docs(tasks): decompose wave 2 — mem + local engines, facade, in-process GC, first suite columns
Nine tasks against the wave-1 contract surface as reviewed (no
amendments from the gate):

- engine-mem / engine-local (parallel, dependency-free): the two
  engines; local owns the stage-hygiene exact-count tests and the
  Drop-cancellation story the wave-1 review recorded
- store-construction: the Store type, constructor modes, size-threshold
  dispatch; binds the conformance factory's Store associated type
- store-read-paths ∥ gc-protocol: read ops with the ADR-012 §6.1
  fall-through rule; the ADR-005 in-process protocol (pins, liveness,
  delete windows, abort-as-data, GcRefuse with the full protection set)
- store-put-paths: the two put paths behind one signature, ADR-003's
  three numbered rules, pin-before-publish, Batch, kv-only rejection
- suite-column-mem → suite-column-local: the reference column, then the
  dual-tier dispatch-coverage shape mirroring it
- review-wave-2: the gate (facade/dispatch/GC vs ADR-003/005/012)

Verification: dependency graph validated (all ids resolve, no cycles,
topological order clean); docs-only change.

Refs: docs/plans/implementation.md §Wave 2
2026-10-11 21:39:28 +00:00

88 lines
4.1 KiB
Markdown

---
id: store-read-paths
name: Store read paths — get/stat/read_range/has/list with dispatch fall-through
status: pending
depends_on: [store-construction]
scope: moderate
risk: medium
impact: component
level: implementation
tags: [wave-2, facade, read-path]
---
## Description
Implement the facade's read-side ops on the `Store` type (store-api.md
§Read path, §Lifecycle), behind the dispatch seam from
`store-construction`. Direct `delete` is *not* here — it lands in
`gc-protocol` with its arbitration.
- **The fall-through general rule** (ADR-012 §6.1; POC #1 finding 6):
every key-addressed op consults the kv tier first, then the large
tier. `get`/`stat`/`has`/`read_range`/`list` all follow it.
- **`get(key) → Option<(len, stream)>`**: small tier returns a bounded
read; large tier streams. The per-engine cursor shapes stay hidden
behind the crate-internal `ReadCursor` the store core drives
(ADR-010 §1) — **named design point**: the public stream type that
wraps the cursor (the facade's `(len, stream)` shape is uniform
across tiers; the cursor is never exposed). A miss on both tiers is
`None` — `get` never carries `Error::Missing` (ADR-012 §6.5 ruling 5;
the error module's behavioral-enforcement note lands here).
- **`stat(key) → Option<EntryMeta>`**: the cheap probe riding the
trait's `size` method — length plus algorithm, never a content load
(ADR-008's size discipline). Fall-through per the general rule.
- **`read_range(key, range) → (slice, slice_digest)`**: slice plus the
out-of-band SHA-256 slice digest (ADR-006, fixed by convention —
composed through the cursor's `read_range` seam). `Err(Missing)` on a
both-tier miss. Range reads fall through tiers exactly as `get` does.
- **`has(key) → bool`**: the pure existence probe (ADR-008's `has`
discipline) — a size-probe under the fall-through rule; absent on
both tiers is `false`; virgin-store reads are no-ops.
- **`list() → stream of keys`**: whole-pool enumeration, complete by
contract. **Named design point**: the return shape — store-api.md
says "stream of keys"; decide Vec vs stream (the sweep consumes the
same enumeration crate-internally) and record it.
- **Verification posture on get** (named design point): ADR-006's
whole-blob verification vs the slice-digest-only reading — where the
store core's hash-check actually fires on the read path (kv
materialized reads are verifiable cheaply; large streaming reads ride
per-range slice digests). Pin the reading against ADR-006's text and
record it for the review gate.
- **`KeyInvalid` at the boundary**: typed `Key` conversion happens at
the store boundary only (ADR-002/003) — malformed key bytes on any
read op reject with `Error::KeyInvalid`, never a guess; engines never
see typed keys.
## Acceptance Criteria
- [ ] `get`/`stat`/`read_range`/`has`/`list` implemented with the
fall-through general rule (kv first, then large), unit-tested
including the both-tier-miss shapes (`None` vs `Err(Missing)` per
op)
- [ ] The public stream type decided and recorded; engine cursors never
leak into the public surface
- [ ] `stat` never fetches content (rides `size`); `EntryMeta` carries
len + algorithm
- [ ] `read_range` returns slice + SHA-256 slice digest per the ADR-006
convention; verification-on-get posture decided and recorded
- [ ] Malformed key bytes reject with `KeyInvalid` at the facade
boundary on every key-addressed op
- [ ] `cargo test` green; `cargo test --features mem` and `cargo test
--all-features` green; clippy/fmt gates green
## References
- docs/architecture/store-api.md (§Read path, §Lifecycle, §Error model)
- docs/architecture/backends-and-dispatch.md (§Size-threshold dispatch — get fall-through)
- docs/architecture/decisions/012-pre-decomposition-consistency-rulings.md §6.1/§6.5
- docs/architecture/decisions/006-verification-posture-and-transfer-encoding.md
- docs/architecture/decisions/008-trait-size-probe-fleet-gc-and-large-engines.md (size discipline)
- src/backend.rs (the ReadCursor seam this drives)
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion