(tasks) add tasks for harness

This commit is contained in:
Story Crater Bot
2026-08-17 23:05:20 -07:00
parent e2d678b118
commit 5a30d0ffc6
74 changed files with 8204 additions and 1 deletions
+86
View File
@@ -0,0 +1,86 @@
# T0.4 — Upcaster framework
| Field | Value |
|---|---|
| Phase | P0 — Foundations |
| Size | M — 1 to 3 days |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | T2.4 |
## Goal
Read-time migration chain so old log records fold correctly after the event enum
changes. Never rewrite stored records.
## Facts (inlined — no spec read needed)
- Migrations are **upcasters**: `fn upcast(vN) -> vN+1`, applied on read.
- Rewriting an append-only log is a contradiction. History stays as written; the
reader adapts.
- Registry keyed by version. Reading a v1 record under a v3 binary runs
`v1→v2→v3` in sequence.
- Deprecated variants stay decodable forever — an upcaster maps them forward, it
does not delete them.
- The guarantee is the fixture test: a stored fixture of every historical version
still folds to the expected state.
## Steps
1. Keep the per-version wire types as real types — `WorkEventV1`, `WorkEventV2`,
… — with the current one aliased to `WorkEvent`.
2. Define the upcaster shape: `fn(vN) -> vN+1`, either as function pointers or a
`trait Upcast { type From; type To; fn upcast(from: Self::From) -> Self::To; }`.
3. Build the registry keyed by `SchemaVersion`. Registration is explicit; a
missing link is an error at registry construction, not at read time.
4. `read_record(bytes)`: decode `schema`, decode the matching `vN` body, then
apply each upcaster in order up to `CURRENT_SCHEMA`.
5. Return typed errors — `UnknownSchemaVersion`, `MissingUpcaster { from, to }`.
A corrupt or future-version record must not panic the fold.
6. Build the synthetic v1→v2 migration as the test vehicle: add a field or split
a variant in `WorkEventV2`, write the upcaster, commit fixtures for both.
## Acceptance
- Synthetic v1 → v2 migration with a fixture per version.
- Folding a v1 log through the upcaster yields the **same state** as a
natively-v2 log. Assert on the folded state, not on intermediate events.
## Verify
**Harness:** committed v1 and v2 fixtures, plus T0.8's fold.
**Integration test**`tests/it_upcast_equivalence.rs`:
1. Fold the committed **v1** log through the upcaster chain → `state_a`.
2. Fold the committed **native v2** log directly → `state_b`.
3. Assert `serialize(state_a) == serialize(state_b)` byte for byte.
4. Instrument the registry with a counter; assert **at least one** upcaster
actually ran on the v1 path.
5. Feed a record stamped with an unknown future version; assert
`UnknownSchemaVersion` is returned and **no panic** escapes.
**Command:** `cargo test -p log upcast`
**False pass:**
- `CURRENT_SCHEMA` still equal to v1, so the chain is empty and both folds
trivially agree. Step 4 is the guard.
- Comparing folded states with a derived `PartialEq` over `HashMap`, which
ignores ordering. Compare serialized bytes.
- The v2 change being cosmetic (a renamed local, an added comment) so no upcaster
logic is exercised. The v2 fixture must contain a record whose shape actually
differs.
## Traps
- Lossy upcasters that default a new field to a value the fold treats as
meaningful. If v1 cannot supply the field, the fold handles `Option` — it does
not receive a fabricated value.
- A registry populated by iteration order rather than an explicit chain. The gap
only shows at v3.
---
Background (not required to do this task):
[rust-agentic-sys.md](../../../rust-agentic-sys.md) §8.7 ·
[rust-agentic-task.md](../../../rust-agentic-task.md)