- tests/conformance/README.md: the convention doc — row naming (property- stated names, engine dimension is the column), the Contract stamp: format (the alkstore ADR-017 §4.2 convention adapted), the one- normative-owner-per-property rule, the factory contract (fresh isolated instance per open, idempotent teardown, safe over an abandoned instance), the SKIP posture (SQL-attached columns skip cleanly server-less; acceptance requires green against the server), the invariant backlog (the seven store-api.md §Invariants registered as the rows-to-grow table with pinning citations), and the mem-first column plan (mem is the contract-reference engine, ADR-011 §2) - harness skeleton, compile-able with zero rows: StoreFactory (fresh + isolated + idempotent-teardown contract; the Store associated type is wave 2's binding point for the facade type), stamp.rs (the marker + convention anchor), backlog.rs (in-code registry with a traceability gate asserting INV-1..7 mirror §Invariants in order) - no engine-specific code; no dependencies added Verification: cargo test (33 passed across lib + 3 targets), cargo clippy --all-targets -- -D warnings, cargo fmt --check
152 lines
8.6 KiB
Markdown
152 lines
8.6 KiB
Markdown
# 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:
|
||
|
||
```text
|
||
/// <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 note: the trait's `Store` associated type is the
|
||
stub's placeholder for the `Store` facade type (wave 2). Wave 2's
|
||
first column binds it; if binding reveals a shape correction (e.g.
|
||
the associated type collapses to the concrete facade type), that is
|
||
wave 2's to make and record in this README and `factory.rs`.
|
||
|
||
## 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** (this task's
|
||
scoped deliverable): the trait shape, the stamp convention, and the
|
||
backlog registry are in place; the rows arrive with wave 2's mem
|
||
column. |