feat(tests): the conformance suite's mem column — the reference row set green (wave 2)

- rows.rs: the reference row set (INV-1…6 + two constructor-mode rows),
  factory-generic and engine-uniform, every row ADR-stamped and traced
  to the backlog: round-trips over the content matrix on both put
  paths, dedup (same/cross-path), stage hygiene (both paths, both
  injectable failure modes, kv-degenerate residue via sweep outcomes),
  sweep safety (full-protection-set happy shape, all three
  GcAbortCause abort shapes incl. the single-flight SweeperLock lease,
  the sweep-vs-pinned-put race), list correctness through exact sweep
  outcomes only, stat discipline, mem-only posture, and kv-only
  put-time rejection (threshold discovered by free probes; immediate/
  mid-stream rejections and at-threshold-lands legs)
- mem_column.rs: the column's factories — mem-only (reference host)
  and kv-only(mem) at a declared threshold — fresh isolated opens,
  no-op idempotent teardown; structural factory-contract tests cover
  isolation and teardown against a panicking row
- README: the reference row set recorded (INV-7's basis — what other
  columns mirror), the row-lifecycle convention, column plan and
  current state updated
- task suite-column-mem: status completed, notes and summary filled

Verification: cargo test --features mem (97 lib + 18 conformance +
4 git-interop), cargo test (default), cargo test --all-features,
clippy --all-features --all-targets -D warnings, fmt --check, and
cargo doc --no-deps — all green
This commit is contained in:
glm-5.3-flash committed 2026-10-11 23:19:59 +00:00
1 parent 539dabcbf7
commit f86e20a217
5 files changed
+1601 -13

No files matched your search

+70 -3
View File
@@ -1,7 +1,7 @@
---
id: suite-column-mem
name: Conformance suite — factory binding and the mem column (the reference column)
status: pending
status: completed
depends_on: [store-read-paths, store-put-paths, gc-protocol]
scope: moderate
risk: medium
@@ -76,8 +76,75 @@ every other engine's rows mirror (store-api.md invariant 7; ADR-011 §2).
## Notes
> To be filled by implementation agent
> Filled by the implementation agent (2026-10-11).
- **The factory binding was already made and recorded** by the
store-construction task: `open()` vends the concrete
`alkblobs::Store` facade (the skeleton's `Store` associated type
collapsed), recorded in `factory.rs` + README §The factory contract
before this task ran. Verified; no further shape correction was
needed. The wave-1 binding demonstrator (`binding.rs`) remains as
the proof-of-implementability tests.
- **The reference row set** lives in `tests/conformance/rows.rs`
(engine-uniform, factory-generic, every row ADR-stamped and traced
to the backlog): INV-1 (content matrix × both put paths, in-body),
INV-2 (same-path + cross-path legs), INV-3 (both paths × both
injectable failure modes — mid-stream `Io` failure and
declared-len `Verification` mismatch; the kv stage form is
degenerate, so residue is the pool itself, observed through exact
sweep outcomes), INV-4 (happy shape over the full protection set;
all three `GcAbortCause` abort shapes — `NoLivenessSources`,
`ProtectFailed`, `SweeperLock`, the last completing the
single-node family per ADR-013 §2/ADR-012 §5; the sweep-vs-pinned-
put race over both put paths), INV-5 (exact sweep outcomes, never
list comparison — honored by every row), INV-6, and the two
constructor-mode rows.
- **INV-6's slice through the facade**: stat's shape (len +
algorithm), purity, and `None`-on-miss; the engine-level
non-content-load half rides the tier `size` probes' own bodies
(ADR-008 §1) — noted in the row doc; the mismatch-based proof
needs a plant seam the factory contract deliberately lacks.
- **The kv-only row is threshold-agnostic by discovery**: a
known-length rejection reads nothing, so `Unreadable` probes
distinguish rejection from acceptance and binary search finds the
factory's rejection point (~64 free probes); the row then asserts
immediate/mid-stream rejections and both at-threshold-lands legs.
Configuration contract recorded in the README: threshold < u64::MAX
and materializable (≤ 8 MiB, `MAX_BOUNDARY_MATERIALIZATION`).
Any column's kv-only factory mirrors the row unchanged.
- **Mem factories** (`mem_column.rs`): `MemOnlyFactory` (the
reference host for the INV rows) and `KvOnlyMemFactory` at a
column-declared threshold (1024) — fresh isolated store per open by
construction, teardown a no-op (mem holds no external state),
trivially idempotent and panicking-row-safe. Structural wiring
tests (not backlog rows) demonstrate factory isolation and
teardown-against-panic: `factory_open_vends_fresh_isolated_stores`,
`factory_teardown_is_safe_against_a_panicking_row`.
- **Row lifecycle convention recorded**: the row body owns
open/assert/teardown; a failing assertion panics before teardown
(README §The factory contract grew the bullet). The column runs
under feature `mem`; rows.rs compiles ungated so the reference rows
stay type-checked in default builds. No SKIP machinery added.
## Summary
> To be filled on completion
> Filled on completion (2026-10-11).
Landed the **mem column** — the conformance suite's contract-reference
column (ADR-011 §2). `tests/conformance/rows.rs` carries the reference
row set: INV-1…6 families (12 rows total, incl. all three sweep-abort
shapes and the sweep-vs-put race) plus the two constructor-mode rows
(mem-only posture; kv-only put-time rejection), each ADR-stamped and
traceable to the backlog. `tests/conformance/mem_column.rs` binds the
factories (`mem-only`, `kv-only(mem)`) and runs every row; the factory
contract's isolation and idempotent-teardown guarantees are
demonstrated structurally, including the panicking-row path. README
records the reference row set (INV-7's basis — what other columns
mirror), the row-lifecycle convention, and the new current state. The
wave-2 factory binding (concrete `Store` facade) was already made and
recorded upstream of this task; verified, nothing further to correct.
Verification: `cargo test --features mem` green (97 lib + 18
conformance + 4 git-interop), `cargo test` (default) and
`cargo test --all-features` green, `cargo clippy --all-features
--all-targets -- -D warnings` green, `cargo fmt --check` green.
+58 -6
View File
@@ -105,6 +105,15 @@ row with its own owner stamp, still one per property.
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 row body owns its lifecycle.** A row calls `open()` first —
a fresh, isolated store per call — drives its assertions, and calls
`teardown()` before returning; a failing assertion panics the row
*before* teardown runs (the early exit above), which is why
`teardown()` must make sense over the abandoned instance — dropped
or still alive at unwind. The mem column's structural tests
(`factory_teardown_is_safe_against_a_panicking_row`) exercise that
path; isolation itself (fresh state per open, nothing shared) is
exercised alongside (`factory_open_vends_fresh_isolated_stores`).
## The SKIP posture
@@ -139,6 +148,43 @@ additions) needs a backlog entry *and* its stamp first.
| 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 |
## The reference row set (INV-7's basis)
The mem column's rows (rows.rs) are the **reference row set** — the
required set every other engine's rows mirror (ADR-011 §2, mem's
contract-reference role; store-api.md §Invariants 7). A column mirrors
this inventory row-for-row against its own factory; an addition to the
set propagates to all columns (INV-7). The INV-row families are the
engine mirror targets — the round-trip family covers the tier the
column's engine serves (mem: the kv tier; local: the large tier). The
two constructor-mode rows mirror wherever that configuration is
constructible: a column with a kv-only factory runs the kv-only row
against it — the row discovers the mode's threshold by probing, since
known-length rejections read nothing, and requires the threshold
materializable for its at-threshold legs (≤ 8 MiB,
`MAX_BOUNDARY_MATERIALIZATION`).
| Row (`rows.rs`) | Traces to |
|---|---|
| `round_trip_puts_and_gets_are_byte_identical` | INV-1 (content matrix, both put paths, in-body) |
| `dedup_identical_content_is_one_pool_entry` | INV-2 (same-path and cross-path legs) |
| `stage_hygiene_zero_residue_on_failure_exactly_one_entry_on_success` | INV-3 (both paths, both failure modes) |
| `sweep_counts_are_exact_across_the_full_protection_set` | INV-4 (happy shape: full protection set) |
| `sweep_without_liveness_sources_aborts_and_deletes_nothing` | INV-4 (abort shape: `NoLivenessSources`) |
| `sweep_with_a_failed_protect_callback_aborts_and_deletes_nothing` | INV-4 (abort shape: `ProtectFailed`) |
| `sweep_is_single_flight_the_contended_second_aborts_as_data` | INV-4 (abort shape: `SweeperLock` lease) |
| `sweep_racing_a_pinned_put_never_deletes_the_protected_entry` | INV-4 (the sweep-vs-put race) |
| `list_correctness_is_observable_through_sweep_outcomes` | INV-5 (exact counts, never list comparison) |
| `stat_reports_len_and_algorithm_without_fetching_content` | INV-6 (the stat-discipline family) |
| `mem_only_mode_accepts_every_length_on_both_put_paths` | constructor mode (ADR-011 §4) |
| `kv_only_mode_rejects_over_threshold_puts_at_put_time` | constructor mode (ADR-012 §6.2) |
Stamps live on the rows themselves — `grep -rn "Contract stamp:"
tests/conformance/` is the inventory. The INV-4 rows beyond the
families' first shapes (the `SweeperLock` abort) complete the three
`GcAbortCause` variants within the single-node exact-count family
(ADR-013 §2's lease, its in-process ADR-012 §5 form).
## Column plan
**mem is the contract-reference engine** (ADR-011 §2): wave 2 lands
@@ -151,9 +197,15 @@ 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.
The harness is green on the **mem column** (wave 2): the factory
binding is the concrete `alkblobs::Store` facade (see §The factory
contract — the binding demonstrator remains in `binding.rs`), the
reference row set is landed (§The reference row set — `rows.rs`,
every row ADR-stamped and traced to the backlog), the column's
factories (`mem-only`, the reference; `kv-only(mem)` at a
column-declared threshold) vend fresh isolated stores with idempotent
teardown (`mem_column.rs`, the structural factory-contract tests
included), and mem never skips. The column runs under feature `mem`
(`cargo test --features mem`); the row set compiles ungated so the
reference rows stay type-checked in default builds. The local column
grows next.
+12 -4
View File
@@ -13,11 +13,16 @@
//! `factory.rs` and the README. The wave-1 mock probe retired with it;
//! the binding demonstrator ([`binding`], mem-gated — mem is the
//! skip-free reference shape) proves the contract implementable
//! against the real type. Rows arrive with wave 2's columns (mem
//! first, the contract-reference engine).
//! against the real type.
//!
//! Items beyond the tests below are the wave-2 drivers' surface, hence
//! the scoped `#![allow(dead_code)]` — it shrinks as rows land.
//! The rows landed with wave 2's mem column: [`rows`] holds the
//! reference row set (INV-1…6 plus the two constructor-mode rows,
//! ADR-stamped, traced to [`backlog`]) and [`mem_column`] is the
//! contract-reference column ([`README`](./README.md) §The reference
//! row set is the recorded inventory other columns mirror).
//!
//! Items beyond the tests below are the later columns' surface, hence
//! the scoped `#![allow(dead_code)]` — it shrinks as columns land.
#![allow(dead_code)]
@@ -25,6 +30,9 @@ mod backlog;
#[cfg(feature = "mem")]
mod binding;
mod factory;
#[cfg(feature = "mem")]
mod mem_column;
mod rows;
mod stamp;
#[test]
+240
View File
@@ -0,0 +1,240 @@
//! The **mem column** — the contract-reference column (ADR-011 §2):
//! the mem engine's column of the reference row set ([`crate::rows`]),
//! the first column bound to the real facade, and INV-7's basis —
//! every other engine's rows mirror this set.
//!
//! The factories (README §The factory contract): `open()` vends the
//! concrete [`alkblobs::Store`] facade over a fresh, isolated backing
//! store per call — the mem engine is `BTreeMap`-shaped and shares
//! nothing across instances, so isolation is by construction. Teardown
//! is a no-op (the mem engine holds no external state), which makes it
//! trivially idempotent and safe against abandoned instances — the
//! panicking-row path included, which the structural tests below
//! exercise. Mem never skips (README §SKIP posture — the SKIP
//! machinery stays unneeded until the SQL waves).
//!
//! The INV rows run against the reference `mem-only` factory (the kv
//! tier, no dispatch threshold in effect); the two constructor-mode
//! rows run against the mode factories they name — `mem-only`, and
//! `kv-only(mem)` at the column-declared threshold, which the kv-only
//! row discovers by probing.
#![allow(dead_code)]
use std::io::Cursor;
use std::sync::Arc;
use crate::factory::StoreFactory;
use crate::rows;
use alkblobs::{Error, Key, Store};
/// The reference factory: the mem-only mode (ADR-011 §4) — mem as the
/// sole engine, no dispatch threshold in effect. The column never
/// skips.
pub struct MemOnlyFactory;
impl StoreFactory for MemOnlyFactory {
fn name(&self) -> &'static str {
"mem-only"
}
async fn open(&self) -> Result<Store, Error> {
Ok(Store::mem_only())
}
async fn teardown(&self) -> Result<(), Error> {
Ok(())
}
}
/// The kv-only(mem) factory (ADR-011 §4; ADR-012 §6.2): no large
/// tier; the threshold is the put-time rejection point the kv-only
/// row discovers by probing.
pub struct KvOnlyMemFactory {
/// The rejection point this column's configuration declares. The
/// kv-only row discovers it behaviorally and requires it
/// materializable for its boundary legs.
pub threshold: u64,
}
impl StoreFactory for KvOnlyMemFactory {
fn name(&self) -> &'static str {
"kv-only(mem)"
}
async fn open(&self) -> Result<Store, Error> {
Ok(Store::kv_only_mem(self.threshold))
}
async fn teardown(&self) -> Result<(), Error> {
Ok(())
}
}
/// The column's declared kv-only threshold — small, so every leg of
/// the rejection row stays trivially materializable.
const KV_ONLY_THRESHOLD: u64 = 1024;
const MEM: MemOnlyFactory = MemOnlyFactory;
const KV_ONLY: KvOnlyMemFactory = KvOnlyMemFactory {
threshold: KV_ONLY_THRESHOLD,
};
// ------------------------------------------------------------------
// The reference row set, run against the mem column's factories.
// ------------------------------------------------------------------
#[tokio::test]
async fn inv1_round_trip_puts_and_gets_are_byte_identical() {
rows::round_trip_puts_and_gets_are_byte_identical(&MEM).await;
}
#[tokio::test]
async fn inv2_dedup_identical_content_is_one_pool_entry() {
rows::dedup_identical_content_is_one_pool_entry(&MEM).await;
}
#[tokio::test]
async fn inv3_stage_hygiene_zero_residue_on_failure_one_entry_on_success() {
rows::stage_hygiene_zero_residue_on_failure_exactly_one_entry_on_success(&MEM).await;
}
#[tokio::test]
async fn inv4_sweep_counts_are_exact_across_the_full_protection_set() {
rows::sweep_counts_are_exact_across_the_full_protection_set(&MEM).await;
}
#[tokio::test]
async fn inv4_sweep_without_liveness_sources_aborts_and_deletes_nothing() {
rows::sweep_without_liveness_sources_aborts_and_deletes_nothing(&MEM).await;
}
#[tokio::test]
async fn inv4_sweep_with_a_failed_protect_callback_aborts_and_deletes_nothing() {
rows::sweep_with_a_failed_protect_callback_aborts_and_deletes_nothing(&MEM).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn inv4_sweep_is_single_flight_the_contended_second_aborts_as_data() {
rows::sweep_is_single_flight_the_contended_second_aborts_as_data(&MEM).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn inv4_sweep_racing_a_pinned_put_never_deletes_the_protected_entry() {
rows::sweep_racing_a_pinned_put_never_deletes_the_protected_entry(&MEM).await;
}
#[tokio::test]
async fn inv5_list_correctness_is_observable_through_sweep_outcomes() {
rows::list_correctness_is_observable_through_sweep_outcomes(&MEM).await;
}
#[tokio::test]
async fn inv6_stat_reports_len_and_algorithm_without_fetching_content() {
rows::stat_reports_len_and_algorithm_without_fetching_content(&MEM).await;
}
#[tokio::test]
async fn constructor_mem_only_accepts_every_length_on_both_put_paths() {
rows::mem_only_mode_accepts_every_length_on_both_put_paths(&MEM).await;
}
#[tokio::test]
async fn constructor_kv_only_rejects_over_threshold_puts_at_put_time() {
rows::kv_only_mode_rejects_over_threshold_puts_at_put_time(&KV_ONLY).await;
}
// ------------------------------------------------------------------
// The factory contract's structural shapes (binding.rs' demonstrations
// carried to the real column factories): fresh isolated opens and
// idempotent teardown safe against a panicking row. These are not
// conformance rows — no backlog entry — they are the factory
// contract's own wiring-level assertions.
// ------------------------------------------------------------------
/// Every `open()` vends a fresh, isolated store: two stores live
/// concurrently without observable shared state, and a third open sees
/// neither island's entries.
#[tokio::test]
async fn factory_open_vends_fresh_isolated_stores() {
let a = MEM.open().await.expect("open vends a fresh store");
let b = MEM.open().await.expect("open vends another fresh store");
let content_a = b"island a's content".to_vec();
let content_b = b"island b's content".to_vec();
let (key_a, _) = a
.put(
Some(content_a.len() as u64),
Cursor::new(content_a.as_slice()),
)
.await
.expect("island a's put publishes");
let (key_b, _) = b
.put(None, Cursor::new(content_b.as_slice()))
.await
.expect("island b's put publishes");
assert!(a.has(&key_a.as_bytes()).await.unwrap());
assert!(
!a.has(&key_b.as_bytes()).await.unwrap(),
"island a sees nothing of island b"
);
assert!(b.has(&key_b.as_bytes()).await.unwrap());
assert!(
!b.has(&key_a.as_bytes()).await.unwrap(),
"island b sees nothing of island a"
);
let c = MEM.open().await.expect("a third open vends fresh");
assert!(
!c.has(&key_a.as_bytes()).await.unwrap(),
"a third open shares nothing with the first"
);
assert!(
!c.has(&key_b.as_bytes()).await.unwrap(),
"a third open shares nothing with the second"
);
}
/// A failing assertion panics the row before teardown runs — and
/// teardown must make sense over the abandoned instance anyway: it is
/// idempotent against the panicked row's path, and the next open vends
/// a fresh store with no residue (README §The factory contract).
#[tokio::test]
async fn factory_teardown_is_safe_against_a_panicking_row() {
let content = b"put before the assertion fails".to_vec();
let abandoned = Key::derive(&content, content.len() as u64);
let factory = Arc::new(MemOnlyFactory);
let task = {
let factory = Arc::clone(&factory);
tokio::spawn(async move {
let store = factory.open().await.expect("open vends the store");
let (_, pin) = store
.put(Some(content.len() as u64), Cursor::new(content.as_slice()))
.await
.expect("the put publishes");
drop(pin);
panic!("a failing assertion panics the row (the factory contract's early exit)");
})
};
assert!(
task.await.is_err(),
"the row's panic surfaced as its failure"
);
factory
.teardown()
.await
.expect("teardown makes sense over the abandoned instance");
factory.teardown().await.expect("teardown is idempotent");
let store = factory
.open()
.await
.expect("the next open still vends a store");
assert!(
!store.has(&abandoned.as_bytes()).await.unwrap(),
"no residue from the panicked row — isolation is factory-owned"
);
}
File diff suppressed because it is too large. Load diff