105 lines
4.3 KiB
Markdown
105 lines
4.3 KiB
Markdown
# T2.3 — Rewind as fork
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| Phase | P2 — Durability hard parts |
|
|
| Size | L — over 3 days |
|
|
| Status | Not started |
|
|
| Flags | — |
|
|
| Spec | inlined below |
|
|
| Blocks | — |
|
|
|
|
## Goal
|
|
|
|
Rewind allocates a new `BranchId` and starts its LSNs at zero. Nothing is
|
|
deleted.
|
|
|
|
## Facts (inlined — no spec read needed)
|
|
|
|
```
|
|
lsn 0 ─ 1 ─ 2 ─ 3 ─ 4 ─ 5 ─ 6(failed) branch 0, retained
|
|
└─ 0 ─ 1 ─ 2 ─ ... branch 1, forked at (0, 3)
|
|
```
|
|
|
|
- Same rule as attempt N+1 never mutating attempt N, for the same reason: **the
|
|
discarded branch is the evidence.** Truncating it destroys the failure that
|
|
motivated the rewind, which is what the learning loop exists to consume.
|
|
- Queries default to the live branch; grading may read all of them. **Only the
|
|
live branch is exported.**
|
|
- Rewinding past a `Committed` intent with a non-idempotent effect is a
|
|
**compensation** problem, not a replay problem. The log records what happened;
|
|
it cannot un-happen it. Flag rather than pretend.
|
|
- A rewind is also one of the three mutators the verifier snapshot barrier
|
|
defends against (T4.2) — it can fork a new `BranchId` while verifiers hold a
|
|
view of the old one.
|
|
|
|
## Steps
|
|
|
|
1. Allocate the next `BranchId` for the run. Record a fork event carrying
|
|
`(parent_branch, parent_lsn)` — the fork point is data, not inference.
|
|
2. Start the new branch's LSNs at 0. They are per branch already (T0.3), so this
|
|
falls out of the key shape rather than needing a reset.
|
|
3. Seed the new branch's state by folding the parent branch up to the fork LSN.
|
|
Do not copy the parent's records into the new branch.
|
|
4. Mark which branch is live. Default every query to it; expose an explicit
|
|
all-branches mode for grading (T1.7).
|
|
5. Restrict export to the live branch — check this where the outbox entry is
|
|
built (T0.6), not at the relay.
|
|
6. Before rewinding past a `Committed` intent, look up the effect class. If it is
|
|
not idempotent, emit a compensation-required flag on the fork event and
|
|
surface it. Do not block the rewind and do not silently proceed.
|
|
|
|
## Acceptance
|
|
|
|
- Rewind a 6-step run to step 3, run a different path.
|
|
- The original branch is **fully readable**.
|
|
- The live-branch query returns only the new path.
|
|
|
|
## Verify
|
|
|
|
**Harness:** a 6-step run driven by the stub model, plus the raw log reader so
|
|
branch 0 can be inspected directly rather than through the query surface.
|
|
|
|
**Integration test** — `tests/it_rewind_fork.rs`:
|
|
1. Run 6 steps to a failure at step 6. Snapshot branch 0's serialized records.
|
|
2. Rewind to step 3; run a different path on branch 1.
|
|
3. Assert branch 0's records are **byte-identical** to the snapshot — nothing
|
|
truncated, nothing rewritten.
|
|
4. Assert branch 1's LSNs start at 0 and its fork event carries
|
|
`(parent_branch: 0, parent_lsn: 3)`.
|
|
5. Assert the default query returns only branch 1's path; the all-branches query
|
|
returns both.
|
|
6. Assert branch 1 does **not** contain copies of branch 0's records — count
|
|
records per branch and compare against expected.
|
|
7. Export path: assert outbox entries exist only for branch 1.
|
|
8. Compensation flag: rewind past a `Committed` non-idempotent intent; assert the
|
|
fork event carries the compensation-required flag and the rewind still
|
|
proceeds.
|
|
9. `assert_refold_identical` on both branches.
|
|
|
|
**Command:** `cargo test -p durability rewind`
|
|
|
|
**False pass:**
|
|
- Step 3 checked through the query surface, which defaults to the live branch and
|
|
will happily report "branch 0 unchanged" without reading it. Read the raw log.
|
|
- Step 6 omitted: an implementation that copies parent records into the child
|
|
passes every other assertion here and makes every later "what happened" query
|
|
ambiguous.
|
|
- Asserting the fork point by re-deriving it from LSNs rather than reading the
|
|
recorded fork event.
|
|
|
|
## Traps
|
|
|
|
- Truncating the log at the fork point "since that history is dead". It is the
|
|
evidence.
|
|
- Copying parent records into the child branch. It duplicates history and makes
|
|
every later "what actually happened" query ambiguous.
|
|
- Exporting all branches. Downstream consumers then see two contradictory
|
|
timelines for one run.
|
|
|
|
---
|
|
|
|
Background (not required to do this task):
|
|
[rust-agentic-sys.md](../../../rust-agentic-sys.md) §8.5 ·
|
|
[rust-agentic-task.md](../../../rust-agentic-task.md)
|