The wave-2 facade skeleton and routing (tasks/store-construction.md):
the public surface consumers program against, with the ops methods
still to land against it in store-read-paths/gc-protocol/store-put-paths.
- src/store.rs — the public `Store` type (engine-blind, ADR-007/008/
010) over crate-internal tier enums (`KvEngine`{mem}, `LargeEngine`{
local}): the engine-representation design point decided as concrete
+ enum dispatch, generic rejected (pub(crate) engines, no dyn);
the three constructor modes as named constructors — `mem_only`,
`kv_only_mem`, `dual_tier_mem_local` (the ADR-012 §6.3
dispatch-coverage shape) — with `StoreMode` introspection; the
write-side routing seam (`tier_for`), the read-side fall-through
order (`kv()` then `large()`, ADR-012 §6.1), and the `GcHome`
GC-state field; the ADR-012 §3 fleet declaration ruled deferred to
the engine waves (nothing fleet-valid is constructible yet)
- src/dispatch.rs — the pure routing function (`route`, at-or-below
→ kv), `DEFAULT_DISPATCH_THRESHOLD` (128 KiB) exported at the
crate root, boundary-value unit tests (exactly-at-threshold,
degenerate tunings, purity)
- src/gc.rs — `GcHome`: the store-instance GC-state home the
gc-protocol task fills (in-process; fleet state stays on the
engine-state seam per ADR-010 §2/ADR-012 §4)
- tests/conformance — the wave-2 binding made as anticipated: the
factory's `Store` associated type collapsed into the concrete
`alkblobs::Store` (`open` vends it directly), recorded in
factory.rs + README; the wave-1 mock probe retired, replaced by
mem-gated binding demo factories (binding.rs) over both
constructible shapes
Verification: `cargo test` (default), `cargo test --features mem`,
`cargo test --all-features` green (41/53/53 unit + conformance
targets green in all three shapes); `cargo clippy --all-targets
-- -D warnings` green in all three feature shapes; `cargo fmt
--check` green; `cargo doc --no-deps` adds no warnings beyond the
pre-existing private-intra-doc-links class.
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.