# T0.8 — Fold and re-derive | Field | Value | |---|---| | Phase | P0 — Foundations | | Size | M — 1 to 3 days | | Status | Not started | | Flags | — | | Spec | inlined below | | Blocks | P1 gate | ## Goal The projection from log to materialized state, as a pure function. This task establishes the property every later phase re-runs. ## Facts (inlined — no spec read needed) - **Nothing derived is authoritative.** Materialized state is a cache of the log. If it cannot be dropped and rebuilt byte-identically, it has hidden inputs and that is a bug. - The fold is pure: `fold(state, record) -> state`. No clock reads, no random, no filesystem, no map iteration order leaking into the output. - Reduction (T8.4) is designed around this property: it appends a `Reduced { original, summary }` event rather than editing the log or rewriting a blob, precisely so the fold still reaches the same state from LSN 0. - Restart folds forward from the newest checkpoint (T2.5); `None` means fold from LSN 0. Checkpoints are an optimization only — deleting every checkpoint costs startup time and nothing else. ## Steps 1. Define the materialized state type: runs, branches, attempts, refs, intent status. Whatever the query surface (T1.7) needs and nothing more. 2. Write `fn fold(state: &mut State, record: &LogRecord)` as a pure function. Take no `&self`, no store handle, no `Instant::now()`. 3. Use ordered collections (`BTreeMap`) anywhere the state is serialized, so byte-identity does not depend on hash seed. 4. Write `rebuild(log, key) -> State`: read from LSN 0 (or from the newest checkpoint once T2.5 lands), fold every record in order. 5. Write the property test as a **reusable helper**, not a one-off: drop the state tables, `rebuild`, assert the serialized bytes match. Every subsequent phase calls this helper on its own fixtures. 6. Wire that helper into P1–P8 fixture suites as they land. ## Acceptance - Drop the state tables, re-fold from LSN 0, assert byte-identical state. - The helper is exported and used by at least one later phase's fixture set. ## Verify **Harness:** export `assert_refold_identical(&log, key)` as a **public test helper from this crate**. Every later phase calls it on its own fixtures; that reuse is the deliverable, not the one-off test. **Integration test** — `tests/it_refold.rs`: 1. Build a log containing a retry, a rewind (once T2.3 lands) and a reduction (once T8.4 lands) — the three cases with non-trivial fold logic. 2. Snapshot `serialize(state)`. 3. Drop the state tables entirely. 4. `rebuild` from LSN 0. 5. Assert **byte equality** of the serialized state, not `PartialEq`. 6. Repeat the fold twice in the same process and assert both runs produce identical bytes — catches hash-seed and iteration-order leakage. 7. Purity check: run the fold with the system clock moved forward and with a different `TZ`; assert the bytes are unchanged. **Command:** `cargo test -p projection refold` **False pass:** - Comparing states with derived `PartialEq`. `HashMap` compares order-insensitively, so byte-identity can be broken while the test is green — and byte-identity is the actual property. - Testing on a log with one linear run and no branches, retries or reductions. Every implementation passes that. - Running the fold once and comparing to the live state that was built by the same code path incrementally — assert against a committed expected snapshot for at least one fixture. ## Traps - `HashMap` in serialized state — iteration order varies and byte-identity fails intermittently, which reads as flakiness rather than as the bug it is. - Any hidden input: a timestamp taken at fold time, a config value read from the environment, a default filled in from the current schema version. --- Background (not required to do this task): [rust-agentic-sys.md](../../../rust-agentic-sys.md) §1, §8.6 · [rust-agentic-task.md](../../../rust-agentic-task.md)