Files
poimen/tasks/T0.8-fold-and-re-derive.md
T

3.9 KiB
Raw Blame History

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 P1P8 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 testtests/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 §1, §8.6 · rust-agentic-task.md