3.6 KiB
3.6 KiB
T2.4 — Schema evolution end-to-end
| Field | Value |
|---|---|
| Phase | P2 — Durability hard parts |
| Size | M — 1 to 3 days |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | — |
Goal
Change the event enum for real and prove a log recorded in P1 still folds. This is T0.4's framework exercised against actual history rather than a synthetic fixture.
Facts (inlined — no spec read needed)
- Depends on T0.4 (upcaster registry) and T0.3 (
SchemaVersionon every record). - The rules being tested: variants are never removed or repurposed; deprecated variants stay decodable forever; migrations are upcasters applied on read, never a rewrite of stored records.
- The failure this prevents: two years of records, one
WorkEventvariant renamed, and the drop-and-re-fold property is silently gone. Nothing fails at the time of the rename — it fails at the first cold rebuild. - The v1 fixture must be a real P1 log, not a hand-written one. A synthetic fixture only tests the variants whoever wrote it remembered.
Steps
- Capture a full P1 run's log as a committed fixture, at v1, with its blobs. Store the expected folded state alongside it.
- Make a genuine v2 change: add a field to an existing variant, or split one variant into two. Do not rename or delete anything.
- Bump
CURRENT_SCHEMAto v2 and register the v1→v2 upcaster. - Run the fold over the v1 fixture under the v2 binary; assert the folded state equals the recorded expectation.
- Record a fresh v2 fixture from the same workflow and assert the two folded states agree — an upcast v1 log and a native v2 log must be indistinguishable downstream.
- Extend T0.3's directory-walk fixture test so both version folders are covered permanently, not just in this task's test.
Acceptance
- A v1 log fixture recorded in P1 still folds correctly after the event enum changes.
Verify
Harness: a real P1 run's log committed as a fixture, with its expected folded state committed beside it. Not hand-written.
Integration test — tests/it_schema_evolution.rs:
- Under the v2 binary, fold the committed v1 fixture →
state_a. - Assert
state_aequals the committed expected state, byte for byte. - Record a fresh v2 run of the same workflow → fold →
state_b. - Assert
state_aandstate_bare equivalent under the fields that should not have changed. An upcast v1 log and a native v2 log must be indistinguishable downstream. - Assert the v1 fixture file's bytes are unchanged by the test run — the fixture is read-only history.
- Variant-removal guard: a test that enumerates v1's variant set and asserts every one is still decodable under v2.
Command: cargo test -p log schema_evolution
False pass:
- Regenerating the fixture when the test goes red. That is the one move that destroys the guarantee — the fixture is the historical record, the upcaster is what changes. Make the fixture path read-only in the test and assert on that.
- A v2 change that touches no variant present in the P1 fixture, so no upcaster logic runs. Step 6 catches the removal case; choose a v2 change that provably affects a recorded variant.
Traps
- "Fixing" the fixture bytes when the test fails. The fixture is the historical record; the upcaster is what changes.
- A v2 change that removes a variant, which passes this test today because nothing in the P1 fixture used it, and breaks the moment an older log does.
Background (not required to do this task): rust-agentic-sys.md §8.7 · rust-agentic-task.md