# 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.1–T0.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 1–2 — they exist only for this gate and stay in the tree as the anti-drift device. **Integration test** — `tests/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](../../../rust-agentic-sys.md) §3, §7, §8 · [rust-agentic-task.md](../../../rust-agentic-task.md)