- en-dash (U+2013) -> ASCII hyphen across src/tests/docs/tasks
- multiply sign (U+00D7) -> x in the same prose
- logical or (U+2228) -> slash-list "(pins / markers / sources)" in
src/gc.rs and its prose mirrors (gc-protocol.md, review-wave-2.md)
- task file's self-referencing mentions rewritten to codepoint/ASCII
phrasing; fuzz/target and fuzz/corpus never touched
- rule recorded as AGENTS.md convention 11
Verification:
- cargo test (149+54+4), cargo test --all-features (328+125+4) green
- cargo clippy --all-targets / --all-features -D warnings clean
- cargo fmt --check clean; fuzz/shared corpus-replay gate green
- git grep -nP '[\x{2013}\x{00D7}\x{2228}]' over tracked files: empty
9.7 KiB
id, name, status, depends_on, scope, risk, impact, level, tags
| id | name | status | depends_on | scope | risk | impact | level | tags | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| store-construction | Store facade type — constructor modes and size-threshold dispatch | completed |
|
moderate | medium | project | implementation |
|
Description
Introduce the Store facade type (store-api.md §What this is) — the
public surface consumers program against — with its constructor modes
and the size-threshold dispatch. This task builds the skeleton and
routing: the ops methods land in store-read-paths, gc-protocol,
and store-put-paths against this type.
- The
Storetype (public,src/facade.rsor a new module — implementer's call, one module per file): holds the kv-tier engine and the large-tier engine (or none, per mode) plus the GC state home thegc-protocoltask fills. Named design point — engine representation: a concreteStorewith a crate-internal enum dispatching over the closed five-engine set, vs a genericStore<Kv: Backend, Large: Backend>. The conformance factory's binding note (tests/conformance/factory.rs) anticipates the concrete type ("most plausibly the associated type collapsing to the concrete facade type across all factories"); engines are statically known and in-crate (nodynanywhere, wave-1 design point 3). Decide, record, and keep the public surface engine-blind (ADR-007/008/010: which engines a node runs changes nothing here). - Constructor modes (ADR-011 §4; ADR-012 §6.3):
- dual-tier (the default): kv + large, size-threshold dispatch.
Wave-2 constructible shape:
kv = mem+large = local— the dispatch-coverage test shape ADR-012 §6.3 names (both tiers load-bearing). - kv-only: no large tier; over-threshold puts reject at put time
(ADR-012 §6.2 — the rejection behavior itself lands in
store-put-paths; this task carries the mode and its validation). - mem-only: the mem engine alone, no dispatch threshold in effect; never production.
- Mode machinery is built so the wave-3-5 engine constructors slot in without reshaping (sqlite/postgres/pg-lo constructors are later waves; only feature-gated constructors exist when their feature is on).
- dual-tier (the default): kv + large, size-threshold dispatch.
Wave-2 constructible shape:
- Size-threshold dispatch (ADR-003): routing as a pure function of content length — same content ⇒ same length ⇒ same tier, no content ever migrates. Default threshold 128 KiB (constructor-tunable; the conservative edge of the measured ~128-256 KiB crossover zone, POC #3 A4). The dispatch function is pure and unit-tested (boundary values: exactly-at-threshold routing).
- The dispatch seam the ops tasks program through: tier selection for a given length, and the fall-through order (kv first, then large) the read paths and delete consume (ADR-012 §6.1's general rule).
- Conformance factory binding: bind
StoreFactory::Storeto the real facade type (the skeleton's anticipated collapse to the concrete type); if binding reveals a shape correction, make it and record it in factory.rs + tests/conformance/README.md per the skeleton's note.
Acceptance Criteria
- Public
Storetype with the three constructor modes; engine representation decided and recorded (enum-dispatch vs generic) with the public surface engine-blind - Size-threshold dispatch: pure function of length, default 128 KiB, constructor-tunable; boundary-value unit tests (at/below/above threshold route deterministically)
kv = mem+large = localdual-tier construction works (the ADR-012 §6.3 dispatch-coverage shape); kv-only and mem-only modes construct and validateStoreFactory::Storebound to the real facade; any shape correction recorded in factory.rs + READMEcargo testgreen (default build: local ungated);cargo test --features memandcargo test --all-featuresgreen; clippy/fmt gates green in all three feature shapes
References
- docs/architecture/store-api.md (§What this is; §Public surface)
- docs/architecture/backends-and-dispatch.md (§Size-threshold dispatch; §Constructor modes)
- docs/architecture/decisions/003-backend-contract-and-dispatch.md (§Dispatch policy)
- docs/architecture/decisions/011-vocabulary-tiers-engines-and-fleet-state.md §4
- docs/architecture/decisions/012-pre-decomposition-consistency-rulings.md §6.2/§6.3
- tests/conformance/factory.rs (the binding note)
Notes
- Engine representation decided: concrete
Storeholding two crate-internal engine enums (KvEngine: mem [feature-gated];LargeEngine: local [ungated]), not the genericStore<Kv, Large>— the generic would leakpub(crate)engine types through the public surface or forcedyn(wave-1 design point 3); the facade stays engine-blind (ADR-007/008/010) and the conformance factory's binding note anticipated exactly this collapse. Waves 3-5 slot in by adding an enum variant + a named (feature-gated) constructor — no reshaping. Recorded insrc/store.rsmodule docs. - Constructor modes land as named constructors, engines named by
the constructor (public surface can't take
pub(crate)engine handles):mem_only()/kv_only_mem(threshold)/dual_tier_mem_local(root, threshold)(the ADR-012 §6.3 dispatch-coverage shape; fallible because the local root's creation is I/O). Modes validate structurally (unrepresentable states unrepresentable — dual-tier constructs the large engine itself) and every instance carries its mode (Store::mode() → StoreMode, public). The fleet declaration (fleet: true, ADR-012 §3) defers to the engine waves: it is a constructor error for every non-fleet-valid configuration, and none is constructible yet — the ruling is recorded in store.rs docs rather than carried as a dead-machinery flag. - Threshold shape:
None-vs-value is the mode's ruling (mem-only = "no dispatch threshold in effect", ADR-011 §4), so the field is always a value and mem-only'sthreshold()returnsNoneby mode branch — no unrepresentableOptionstate toexpectaway (AGENTS convention 2). The pure function isdispatch::route(len, threshold)with the pinned boundary reading at-or-below → kv;DEFAULT_DISPATCH_THRESHOLD(128 KiB) is exported at the crate root and constructors take the threshold explicitly (tunability proven by tests at a non-default threshold). - Dispatch seam handed to the ops tasks: write-side
Store::tier_for(len)(dual-tier pure routing; kv-only routes kv for every length — the over-threshold rejection is the put seam's behavior keyed offthreshold(), ADR-012 §6.2; mem-only kv), and read-side fall-through order askv()-then-large()accessors documented as ADR-012 §6.1's general rule. The engine enum'sBackenddispatch methods and the cursor/staging enum wrappers are deliberately not built here — they are store-read-paths and store-put-paths named design points (the public stream shape, the declared-len mismatch case) and adding methods to the enums is slotting in. - GC state home:
pub(crate) struct GcHomeinsrc/gc.rs, held as aStorefield with a crate-internalgc()accessor — the gc-protocol task fills it (pin registry, liveness sources, protect callback, in-process window); fleet state explicitly never lands there (ADR-010 §2 / ADR-012 §4 host rule). - Factory binding made and recorded: the skeleton's
Storeassociated type is gone —StoreFactory::openvends the concretealkblobs::Storedirectly (tests/conformance/factory.rs+ README §The factory contract record the ruling); the wave-1 mock probe retired, replaced by mem-gated binding demo factories (tests/conformance/binding.rs) covering mem-only and the dispatch-coverage dual-tier shape with isolation + idempotent teardown. - Default-build note: the kv engine set is empty until wave 3
(sqlite), so
Storeexists in default builds but is unconstructible — constructors and construction tests are#[cfg(feature = "mem")]; the#![allow(dead_code)]scope notes ondispatch.rs/store.rsfollow the house pattern and shrink as the ops tasks land.
Summary
Implemented the Store facade skeleton in src/store.rs: the public
engine-blind facade type holding the two crate-internal tier enums
(KvEngine {mem}, LargeEngine {local}) per the recorded
engine-representation ruling (concrete + enum dispatch, generic
rejected), the three constructor modes as named constructors —
mem_only(), kv_only_mem(threshold), dual_tier_mem_local(root, threshold) (the ADR-012 §6.3 shape) — with StoreMode introspection,
the write-side routing seam (tier_for) composing the pure
dispatch::route (default 128 KiB, constructor-tunable, at-threshold →
kv), the read-side fall-through order (kv() then large(), ADR-012
§6.1), and the GcHome GC-state field the gc-protocol task fills
(added to src/gc.rs). dispatch.rs grew the pure routing function
plus DEFAULT_DISPATCH_THRESHOLD with boundary-value unit tests
(exactly-at-threshold, degenerate tunings, purity). The conformance
factory binding collapsed the Store associated type into the concrete
facade (recorded in factory.rs + the suite README); the wave-1 probe
became mem-gated binding demo factories. Tests: mem-only/kv-only/
dual-tier construction + validation, threshold tunability at a
non-default value, dispatch boundary values, factory open/teardown
lifecycle in both constructible shapes.
Verification: cargo test (default), cargo test --features mem,
cargo test --all-features all green; cargo clippy --all-targets -- -D warnings green in all three feature shapes; cargo fmt --check
green; cargo doc --no-deps clean of new warnings beyond the
pre-existing private-intra-doc-links class.