Files
alkblobs/tests/conformance/README.md
T
glm-5.3-flash 532adfa707 feat(store): the Store facade — constructor modes, size-threshold dispatch, the factory binding
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.
2026-10-11 22:18:32 +00:00

9.1 KiB
Raw Blame History

The conformance suite — home and conventions

The in-crate conformance harness for alkblobs' engines. Decided point (docs/plans/implementation.md, 2026-10-11): engines are features of one crate, so the suite lives in-crate, factory- parameterized over engine constructors — the alkstore ADR-017/022 contract-suite pattern adapted (there a sibling suite crate over Box<dyn Store>; here one test target over factories whose open vends the future Store facade, whose concrete type wave 2 binds).

This document is the convention's normative home on the test side (the in-code module docs in main.rs/factory.rs/stamp.rs restate their slices). Waves 2–6 grow columns against it: one column per engine, each column the same rows run against that engine's factory.

Row anatomy

A row is one named, independently-runnable property — an async function taking a StoreFactory (not a store), asserting by panicking on violation (a failing row is a test failure naming the property and expectation; the family-wide no-panic rule is relaxed here by the suite's test-side purpose).

Row naming

  • snake_case, and the name states the property, not the engine (e.g. round_trip_known_length, dedup_is_one_pool_entry). The engine dimension is the column (which factory the row runs against), never the row name — that is what makes rows engine-uniform.
  • Dimensions a property spans within one run — the content matrix, both put paths, both tiers where the invariant ranges over them — loop inside the row's body. A genuinely different property (e.g. sweep safety's fleet form vs its single-node form) is a separate row, not a flag inside the owner.
  • Planned row families are registered in the invariant backlog (table below); a row that does not trace to a backlog entry is not a conformance row.

Contract stamps (the ADR-017 §4.2 convention adapted)

Every row is version-stamped with a doc-comment list item beginning Contract stamp:, citing the spec § and/or ADR § that pins the behavior tested:

/// <one-line statement of the property>
///
/// Contract stamp: store-api.md §Invariants 2; ADR-003 (whole-value
/// put is CAS first-writer-wins); ADR-005 (re-put refcounts).
pub async fn dedup_is_one_pool_entry<F: StoreFactory>(factory: &F) { ... }

Rules:

  • The stamp cites the ADR § / spec § that pins the property — never a bare "wave 2" or a date. Several stamps are allowed when one row tests several changes' behavior together.
  • A row added to pin a new contract addition is stamped with that addition's ADR; a row whose property predates the suite is stamped with the ADR/spec section that pinned it.
  • A later change that amends a row's property appends to its stamp list; rows accumulate stamps, the ADRs' own texts record the history.
  • Contract stamp: is greppable — grep -rn "Contract stamp:" tests/conformance/ is the stamp inventory.
  • Rows are also free of engine-specific code by construction: a row may not branch on factory.name().

One normative owner per property

Each property has exactly one normative owner: the suite row. Engine-specific unit tests inside src/ may duplicate coverage for debugging convenience, but the suite row is the normative assertion — an engine's conformance claim rests on its column being green, not on its in-crate tests. When the suite itself needs per-engine divergence (the fleet-form sweep safety against postgres, INV-4), that is its own row with its own owner stamp, still one per property.

The factory contract

factory.rs pins the [StoreFactory] shape. The contract:

  • open() vends a fresh, isolated store instance. No state is shared between rows or between columns: a fresh backing store per call — a fresh temp directory (local), a fresh pool (mem), a fresh schema or file (sqlite/postgres/pg-lo) — so a property's rows never observe another property's entries. Isolation is the implementer's owned guarantee.
  • teardown() is idempotent and safe to run against an abandoned store: a failing assertion panics the row (early exit, no teardown on that path), so teardown must also make sense over a dropped / half-open instance. The factory owns what teardown means — deleting the temp dir, dropping the schema, closing the pool — and owns the isolated-ness guarantee above.
  • Factories are Send + Sync: shareable across row runs.
  • The wave-2 binding — made and recorded. The Store facade type landed with wave 2's store-construction task as a concrete type (alkblobs::Store, engine-blind; src/store.rs holds the engine- representation design point). The binding made the anticipated shape correction: the skeleton's Store associated type collapsed into the concrete facade — open() vends alkblobs::Store directly (factory.rs records the ruling; the wave-1 mock probe retired with it, replaced by the mem-gated binding demo factories in main.rs). A new engine column binds by implementing the trait; there is no longer a vendible type to choose.

The SKIP posture

SQL-attached factories (postgres, pg-lo) ride a server (dockerized postgres) whose connection settings come from the environment. When no server is reachable, the column skips cleanly: a skip is not a failure, and the harness reports it as skipped rather than green or red. Two sides to keep honest:

  • Engine waves' acceptance criteria require green against the server (the alkstore harness convention) — a skip never substitutes for acceptance.
  • Server-less engines (mem, sqlite, local, and any future dependency-free engine) never skip: their columns must always be green in every environment.

The invariant backlog (rows-to-grow)

The seven invariants of store-api.md §Invariants, registered as the table every wave's rows grow against. Traceability: each row family names its store-api.md invariant and the ADRs pinning it. A suite row asserting something outside this table (or outside a wave's specced additions) needs a backlog entry and its stamp first.

# Invariant (store-api.md §Invariants) Row family Pinning text First column
INV-1 Whole-blob put/get round-trips byte-identical under the canonical derivation — both tiers, both put paths, every shipped engine one round-trip family; content matrix and put paths loop in-body; per-engine columns store-api.md §Invariants 1; ADR-002 (derivation); ADR-003 (put paths, dispatch); ADR-007/008/009/011 (engines) wave 2 — mem (kv tier), then local (large tier)
INV-2 Dedup: identical content twice (same or different path) is one pool entry one dedup row, same-path and different-path legs store-api.md §Invariants 2; ADR-003 (CAS first-writer-wins); ADR-005 (re-put refcounts) wave 2 — mem
INV-3 Stage hygiene: failure mid-put leaves zero stage files / no entry; success leaves exactly one committed entry one hygiene family over both put paths and both failure modes (discard, failed commit) store-api.md §Invariants 3; ADR-010 §1 (staged-put contract); ADR-003 wave 2 — mem (degenerate stage) and local
INV-4 Sweep safety: exact counts with correct liveness; aborting sources delete nothing. Engine-gated fleet form (DB-backed pins + sweep lease vs postgres) one single-node family; the fleet form is its own engine-gated row family (runs under --all-features) store-api.md §Invariants 4; ADR-005 (windows, sweeps); ADR-008 (fleet form); ADR-012 §1/§4/§6.7; ADR-013 §2 wave 2 — mem (in-process protocol); fleet form waits for wave 6
INV-5 list() correctness is observable only through GC list-correctness asserted through sweep outcomes (exact counts), never by comparing lists directly store-api.md §Invariants 5; ADR-003 (complete + well-formed list()); ADR-005 wave 2 — mem
INV-6 stat/size returns entry length without fetching content one stat-discipline family per engine column store-api.md §Invariants 6; ADR-008 (size probe); ADR-009 (pg-lo companion-row probe) wave 2 — mem
INV-7 Engine-conformance uniformity: every engine's bodies pass the same contract suite mem passes the meta-gate — a column's green run is the row; no separate test store-api.md §Invariants 7; ADR-010 §1; ADR-011 §2 (mem's reference role) wave 2 — mem defines it

Column plan

mem is the contract-reference engine (ADR-011 §2): wave 2 lands its column first (the first column binds the Store facade type), and every other engine's rows mirror mem's column — an addition to mem's column propagates as a requirement to all other columns (INV-7). The pg columns (postgres, pg-lo) additionally run the SKIP posture above; the fleet-form rows of INV-4 are engine-gated to the postgres column and first run in wave 6.

Current state

The harness skeleton is compile-able with zero rows: the trait shape, the stamp convention, and the backlog registry are in place; the wave-2 factory binding is made — open() vends the concrete alkblobs::Store facade (see §The factory contract), demonstrated by the mem-gated binding demo factories. The rows arrive with wave 2's mem column.