Files

110 lines
5.1 KiB
Markdown
Raw Permalink Normal View History

2026-08-17 23:05:20 -07:00
# 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 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)