ADR-0003: Semantic checkpoints with replay verification before resume¶
Status: Accepted Date: 2026-05-18 Tags: durability · replay · safety
Context¶
Phase 6 must prove local restartability before Phase 9 introduces Temporal durability. Writing after every helper call is wasteful; resuming from unverified state is unsafe.
Decision¶
Persist checkpoints only at semantic boundaries and verify the previous chain head before hydration on resume.
Tradeoffs¶
| Gain | Cost |
|---|---|
| Durable enough for workflow recovery without excessive writes | A crash between semantic checkpoints replays a little work |
| Tamper or partial writes fail closed before new work starts | Ledger code is slightly more involved than naïve snapshots |
Consequences¶
- Kill/resume tests pin checkpoint ordering.
- Failed verification transitions to
FailedUnrecoverable.
Amendment 2026-05-25 (S2-02)¶
Two additive clarifications surfaced by the S2-02 (replay verification) implementation:
-
CheckpointStoreProtocol gains a sixth methoditer_persisted_chain(workflow_id) -> Iterator[tuple[TransitionEvent, ChainHead]]. The replay verifier needs per-row persistednext_headto compute theChainMismatch.divergence_index. The five existing methods (append,read_all_for_workflow,tail_chain_head,lock,close) are byte-equal-unchanged. The verifier dispatches through the Protocol (no substrate-specific shortcut), so the in-memory adapter parity holds. The contract-snapshot meta-test classifies this delta as additive. -
The chain head is folded over the sanitized-reconstructed event, not the live event. S2-01 originally computed the chain head over the live (cleartext) event while persisting sanitized bytes — meaning the verifier could not reproduce the head from persisted bytes when sanitization triggered. The fix lands in both adapters:
append()reparsessanitize_for_persistence(canonical_bytes)into aTransitionEventand folds over that reconstructed event. For events with no secret-shaped content the reconstructed event is byte-equal to the live event, so existing chain heads andtests/golden/phase6-checkpoint/clean_completion_chain.jsonare unchanged. The chain now protects the bytes on disk — the substrate-replayable invariant the Phase-9 SQLite ↔ Postgres byte-equality test depends on.
Owner: Phase 6 S2-02 attempt log entry (Attempt 1, 2026-05-25).