Files
alkblobs/tests/conformance/README.md
T
glm-5.3-flash 327b952e4d feat(tests): the conformance suite's home — conventions, invariant backlog, harness skeleton (wave 1)
- 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
2026-10-11 18:44:04 +00:00

152 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.