Files
poimen/tasks/T0.9-p0-composition-gate.md
T

5.1 KiB
Raw Blame History

T0.9 — P0 composition gate

Field Value
Phase P0 — Foundations
Size M — 1 to 3 days
Status Not started
Flags gate
Spec inlined below
Blocks P1 — nothing in P1 starts until this is green

Goal

Prove T0.1T0.8 compose. Each was verified alone; this task verifies they work together and interchangeably — that the ports are real seams, not shapes.

Phase gate criterion: types compile, log round-trips, schema fixture test green, drop-and-re-fold byte-identical.

Facts (inlined — no spec read needed)

  • A port is only real if a second implementation can be dropped in without editing the suite. P0 ships one implementation of each port; the seam is therefore unproven until a second one exists. T7.1/T7.2 supply the production second implementation much later — too late to learn the port is short.
  • The four properties that must hold jointly, not individually:
    1. every key is tenant-scoped (T0.1) — including keys written by T0.6's commit;
    2. every record carries SchemaVersion and decodes through the upcaster chain (T0.3, T0.4);
    3. all four table writes are atomic (T0.6);
    4. dropping derived state and re-folding from LSN 0 is byte-identical (T0.8).
  • The failure this gate exists to catch: each task passing its own test while the composition violates a property none of them owns. A commit path that writes a correctly-scoped log key and an unscoped outbox key passes T0.1's tests and T0.6's tests, and leaks across tenants.

Steps

  1. Build a second EventLog implementation: a deliberately naive in-memory one, correct but sharing no code with the redb backend.
  2. Build a second BlobStore the same way.
  3. Run T0.5's and T0.7's conformance suites against both implementations, unmodified. Any edit needed is a finding about the port, not the test.
  4. Write the cross-invariant matrix (below) as a single test binary that exercises every P0 task in one path.
  5. Wire T0.8's assert_refold_identical into that path so composition and re-derivability are asserted together.
  6. Make this binary a required CI job and the P1 entry condition.

Acceptance

  • Both EventLog implementations and both BlobStore implementations pass the conformance suites unmodified.
  • The cross-invariant matrix is green.
  • P0's stated gate holds: types compile, log round-trips, schema fixture test green, re-fold byte-identical.

Verify

Harness: the two extra port implementations from steps 12 — they exist only for this gate and stay in the tree as the anti-drift device.

Integration testtests/it_p0_composition.rs:

  1. Port interchange: parameterize the whole test over [redb, in_memory] × [redb_blobs, in_memory_blobs]. All four combinations must pass. A combination that fails means a caller depends on an implementation detail rather than the trait.
  2. Scope sweep: after a full workload across 2 tenants, scan every raw table — log, state, position, outbox, blobs — and assert every key carries the expected tenant. Not through the read path; through a raw scan.
  3. Atomicity under composition: arm T0.6's fault hook while a commit carries records, state deltas, a position advance and an outbox entry simultaneously. Assert all-or-nothing across all four.
  4. Schema + fold: write records at v1, upgrade to v2, then drop state and re-fold. Assert byte-identical state. This is T0.4 and T0.8 composed, and it is the pair most likely to break each other.
  5. LSN under contention across branches: 16 concurrent writers across 3 branches of 2 runs; assert per-branch 1..=n gap-free and no cross-branch interference.
  6. Blob/log linkage: put a blob, reference it from a committed record, drop state, re-fold, resolve the ref. Assert the bytes.
  7. Re-run every P0 task's own test suite in the same job, so this gate catches a regression rather than only a composition bug.

Command: cargo test -p foundations --test it_p0_composition

False pass:

  • Running the matrix against one implementation "because the second is only a test double". The second implementation is the test — it is the only thing that proves the port is a seam before T7.1 finds out the expensive way.
  • Step 2 through the read path, which filters by tenant and passes against a completely unscoped table.
  • Step 4 with a v2 change that touches no recorded variant, so no upcaster runs and the fold is trivially identical.
  • Treating this gate as optional because the eight underlying suites are green. Every property here is one no single task owns.

Traps

  • Deleting the in-memory implementations once T7.1 lands. They are the fast CI path and the drift detector; the Postgres backend is neither.
  • Letting the conformance suite grow implementation-specific branches (if backend == redb). At that point it no longer tests the port.

Background (not required to do this task): rust-agentic-sys.md §3, §7, §8 · rust-agentic-task.md