110 lines
5.1 KiB
Markdown
110 lines
5.1 KiB
Markdown
# 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)
|