Files
alkstore/docs/architecture/decisions/022-contract-suite-layout.md
T

7.6 KiB

ADR-022: Contract-suite layout — a shared internal suite crate, properties parameterized over a store factory

Status

Accepted (2026-10-07, Phase 1 wave-1 — discharges ADR-017 §4.2's "layout decided at implementation" deferral)

Context

ADR-017 §4.2 matured the verification backlog (core-contract.md §Verification backlog) into the contract suite — the pairing instrument whose version-stamped rows make "this engine implements contract vN" an auditable claim. What that ADR deliberately deferred was the suite's layout: shared crate, workspace test target, or per-engine module — "a test-side convenience decided at implementation (ADR-001 §4's posture for test artifacts)."

The implementation plan decoupled the decision from wave 5 (where the suite fills with cross-engine equivalence rows) and pulled the scaffold into wave 1, with the reasoning recorded in implementation.md: the harness shape — a Store-factory-parameterized property crate — is easier to grow row by row as engines land than to retrofit onto two finished engines, and the engine crates' dev-dependency edge existing early means wave 3/4 tasks adopt the harness rather than debate it.

The candidate layouts, at implementation:

  • (a) A shared internal suite crate — alkstore-contract-suite, publish = false, depending on core only; each engine crate takes it as a dev-dependency and drives the rows against its own factory.
  • (b) A workspace test target — the rows live as integration tests inside one workspace member (core, or a dedicated tests-only member), with engines contributing fixtures.
  • (c) Per-engine modules — each engine crate carries its own copy of the property set in its own test tree.

Shape facts that narrow the choice:

  • The rows are parameterized over a store factory, not a store: each property needs a fresh store over an isolated backing store (fresh file / fresh schema) so rows never observe one another's state — something a shared fixture alone does not give.
  • The rows are the same text for both engines — that is the point of the equivalence pinning (ADR-012 §2: the engines' arithmetic is "tested into equivalence" by the suite). Two copies of a property is the classic drift surface: one copy gets strengthened and the other silently lags.
  • The suite is a dev-side instrument; nothing in it belongs on any published artifact's dependency graph (dev-dependency edges only).
  • ADR-017 §4.2 also pinned per-row version stamps (the contract change that added the row — the same no-silent-change discipline §2), and the version-stamp convention needed a normative owner decided now, since every later row cites it.

Decision

Option (a): the suite is a shared internal crate — alkstore-contract-suite, unpublished (publish = false), a workspace member, depending on alkstore only (no driver deps, ever — the mirroring of ADR-001 §2's core rule keeps the suite compilable against any factory). Each engine crate takes it as a dev-dependency and drives the rows in its own test target against a factory it defines: a fresh store over an isolated backing store per run, plus teardown.

  • One normative owner per property — ADR-012 §2's one-owner rule applied to test artifacts: each property's text, assertion strength, and version stamp live in exactly one place. Per-engine copies (option (c)) double the owners and drift; the suite crate is the single owner, engines are executors.
  • A crate, not a test target (over option (b)): the suite's rows must be callable from each engine's test binary — a dev-dependency is the Cargo mechanism for exactly that shape. It also gives the suite a compilation gate of its own and a publish = false marker recording its internal posture in the manifest, where ADR-005's published-by-default default is answered in the artifact itself.
  • The factory abstraction is the suite's API: a minimal StoreFactory trait (open/fresh, teardown) over the boxed-future posture the contract traits use. The exact factory shape is deliberately not frozen — it may evolve as engines adopt the harness (wave 3/4 feedback); this ADR records the layout decision. Suite-side changes (rows, assertion strength, factory shape) are contract-non-events (ADR-017 §2 class 4 — the suite tests the contract, it is not contract surface); a suite row added to pin a contract addition carries that addition's ADR as its version stamp, and the class-2/3 change itself rides ADR-017's discipline on the core crate's side.
  • The version-stamp convention is the suite crate's, documented there (version stamp module): a row's doc comment carries one Contract stamp: item per contract change that pinned the behavior tested, citing the ADR §. The exemplar row (entry-point name validation — empty → InvalidName, reserved prefix → ReservedName, auto-commit and tx paths alike) is stamped ADR-008 §4 (+ ADR-021 §3, ADR-015 §2 for the rows its assertions encompass) and proves the harness end-to-end against a trivial in-crate mock store.

Consequences

Positive

  • The equivalence discipline has a mechanical home: one row text, run on both engines by their factories — the no-ghosts, ordering, and arithmetic-equality claims (wave 5's rows later) are one property each, not one per engine.
  • The engine/dev-dependency edge existing from wave 1 makes adoption the default path for waves 3/4 (their backlog-column tasks run the rows, not re-derive them) — the reason the scaffold was pulled forward from wave 5.
  • publish = false plus core-only deps keeps the artifact off every published graph and compilable anywhere (A mem-shaped engine, if wave 6 wants one (ADR-001 §4's deferral), can implement the same StoreFactory and reuse the suite verbatim).

Negative

  • A dev-dependency edge is graph-visible but Cargo-weak: the suite cannot enforce that an engine actually ran it (dev-deps can be ignored by a careless release). The enforcement is process-side — the wave-3/4 review gates and ADR-017 §4.2's release-time suite requirement, the same honesty posture the rest of the pairing carriers ride.
  • The factory shape's latitude is a small standing surface: engines will each own a factory impl, and a shapes-drift (e.g. someone adds a pool-reuse knob) needs review discipline to keep the two implementations comparable. The ADR's stated rule — factory changes are suite-crate changes first — bounds it.

References

  • ADR-017 §4.2 — the deferral this ADR discharges; the version-stamp duty the convention implements; class 4's suite-is-not-contract boundary.
  • ADR-001 §4 — the test-artifact posture the layout defers by; the mem-engine note's possible suite reuse.
  • ADR-012 §2 — the one-normative- owner rule the layout mirrors; the "tested into equivalence" framing.
  • core-contract.md §Verification backlog — the rows the suite matures from.
  • docs/plans/implementation.md "Decided points" — the wave-1 pull- forward rationale and the option-(a) record this ADR formalizes.