3.9 KiB
3.9 KiB
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);
Nonemeans fold from LSN 0. Checkpoints are an optimization only — deleting every checkpoint costs startup time and nothing else.
Steps
- Define the materialized state type: runs, branches, attempts, refs, intent status. Whatever the query surface (T1.7) needs and nothing more.
- Write
fn fold(state: &mut State, record: &LogRecord)as a pure function. Take no&self, no store handle, noInstant::now(). - Use ordered collections (
BTreeMap) anywhere the state is serialized, so byte-identity does not depend on hash seed. - Write
rebuild(log, key) -> State: read from LSN 0 (or from the newest checkpoint once T2.5 lands), fold every record in order. - 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. - 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:
- 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.
- Snapshot
serialize(state). - Drop the state tables entirely.
rebuildfrom LSN 0.- Assert byte equality of the serialized state, not
PartialEq. - Repeat the fold twice in the same process and assert both runs produce identical bytes — catches hash-seed and iteration-order leakage.
- 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.HashMapcompares 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
HashMapin 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 §1, §8.6 · rust-agentic-task.md