How must an instance recover memory consistency after a corrupted write-ahead log?

**Date of Record:** 2026 October 27 **Scope:** Applies when an agentic session's durable state is backed by a write-ahead log (WAL), the WAL has been corrupted (partial writes, bit flips, or truncation), and the session must recover consistency without losing committed transactions or corrupting the state further. Does not apply to sessions without WAL-based durability or sessions where the WAL is verified intact.

Core Distinction

The separation between **WAL presence** (the log file exists and is readable) and **WAL integrity** (the log entries are internally consistent and match the committed state). Relying on presence creates the **Partial Recovery Corruption**: the session replays the WAL from the beginning, hits a corrupted entry, and either stops (losing later valid entries) or skips the corrupt entry (potentially replaying subsequent entries out of order, corrupting state).

What Was Observed

In WAL-based agent architectures, failing to handle corruption properly produces three primary failure modes:

  1. **Truncation Loss:** The WAL is truncated mid-entry, and the session replays valid entries until the truncation, missing any entries after. The state is stale.
  2. **Bit Flip Replay:** A bit flip corrupts an entry's payload. The session replays the corrupted entry, and the state diverges from what was actually committed.
  3. **Reorder Replay:** The corruption affects entry ordering (timestamps, sequence numbers). The session replays entries in wrong order, causing state inconsistencies.

Recorded Elements

  1. **`wal_entry_integrity_receipt`:** For each WAL entry, a cryptographic hash of the entry content and a link to the previous entry, forming a hash chain that makes corruption or truncation detectable.
  2. **`wal_checkpoint_receipt`:** Periodic cryptographic proofs that the WAL's state matches the committed durable state at specific epochs, allowing recovery to start from the last known-good checkpoint rather than the beginning.
  3. **`wal_corruption_boundary_signature`:** When corruption is detected, a cryptographic record of the exact boundary (first corrupted entry, last valid entry before corruption), so recovery knows where to stop.
  4. **`replay_determinism_receipt`:** For replay operations, the cryptographic proof that replaying the valid WAL prefix produces the expected state, confirming the replay is correct.

Condition of Validity

* **Applies when:** Durable state is backed by a WAL, corruption is possible, and correct state recovery is essential. * **Does not apply when:** WAL is verified intact, or state can be fully rebuilt from a source without WAL.

Decision Boundary

  1. **Is the WAL corrupted?** If no, standard replay applies. If yes, proceed.
  2. **Is there a `wal_checkpoint_receipt` before the corruption?** If yes, start replay from the checkpoint, not from the WAL beginning.
  3. **Where is the `wal_corruption_boundary_signature`?** The replay must stop at the last valid entry before the boundary. If the boundary is not detected, replay is unsafe.
  4. **Conversely, when can the session proceed past corruption?** Only when the corruption is confined to a single entry that can be reconstructed from a checkpoint or source, AND subsequent entries are verified to be independent of the corrupted entry.

Operational Effect

  1. **Truncation Detection:** Hash chains detect truncation.
  2. **Bit Flip Localization:** Corruption is confined to the corrupted entry, and replay stops before propagating it.
  3. **Checkpoint Recovery:** Last-good checkpoint enables partial recovery without full replay.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json

class WALVerdict(Enum):
    WAL_INTACT = auto()
    RECOVERABLE_FROM_CHECKPOINT = auto()
    CORRUPTION_BOUNDARY_DETECTED = auto()
    UNRECOVERABLE_CORRUPTION = auto()

@dataclass(frozen=True)
class WALEntry:
    entry_id: str
    entry_hash: str
    previous_hash: str
    sequence_number: int
    is_corrupted: bool

@dataclass(frozen=True)
class WALState:
    entries: tuple[WALEntry, ...]
    last_checkpoint_epoch: float | None
    checkpoint_state_hash: str | None

@dataclass(frozen=True)
class WALRecoveryPolicy:
    require_checkpoint: bool
    allow_partial_replay: bool

@dataclass(frozen=True)
class WALManifest:
    verdict: WALVerdict
    replay_until_sequence: int | None
    directive: str
    manifest_digest: str

class WALCorruptionGuard:
    @staticmethod
    def _hash(data: str) -> str:
        return hashlib.sha256(data.encode("utf-8")).hexdigest()

    @classmethod
    def evaluate_wal(
        cls, state: WALState, policy: WALRecoveryPolicy
    ) -> WALManifest:
        
        # Gate 1: Corruption detection
        corrupted_entries = [e for e in state.entries if e.is_corrupted]
        if not corrupted_entries:
            digest = cls._hash(json.dumps({
                "entries": str(len(state.entries)),
                "checkpoint": state.last_checkpoint_epoch
            }, sort_keys=True))
            return WALManifest(
                verdict=WALVerdict.WAL_INTACT,
                replay_until_sequence=max((e.sequence_number for e in state.entries), default=0),
                directive=f"PROCEED: WAL intact, {len(state.entries)} entries.",
                manifest_digest=digest
            )

        # Gate 2: Checkpoint availability
        if not state.last_checkpoint_epoch or not state.checkpoint_state_hash:
            if policy.require_checkpoint:
                return WALManifest(
                    verdict=WALVerdict.UNRECOVERABLE_CORRUPTION,
                    replay_until_sequence=None,
                    directive=f"HALT: Corruption detected, no checkpoint, checkpoint required.",
                    manifest_digest=cls._hash("NO_CHECKPOINT")
                )

        # Gate 3: Hash chain verification
        sorted_entries = sorted(state.entries, key=lambda e: e.sequence_number)
        last_valid_sequence = 0
        previous_hash = "GENESIS"

        for entry in sorted_entries:
            if entry.is_corrupted or entry.previous_hash != previous_hash:
                break
            if not entry.entry_hash:
                break
            previous_hash = entry.entry_hash
            last_valid_sequence = entry.sequence_number

        # Gate 4: Partial replay
        if last_valid_sequence > 0:
            digest = cls._hash(json.dumps({
                "replay_until": str(last_valid_sequence),
                "corrupted_at": str(sorted(corrupted_entries, key=lambda e: e.sequence_number)[0].sequence_number),
                "checkpoint": f"{state.last_checkpoint_epoch:.0f}" if state.last_checkpoint_epoch else "NONE"
            }, sort_keys=True))
            return WALManifest(
                verdict=WALVerdict.CORRUPTION_BOUNDARY_DETECTED,
                replay_until_sequence=last_valid_sequence,
                directive=f"PROCEED: Replay up to sequence {last_valid_sequence}, stop at corruption.",
                manifest_digest=digest
            )

        # Gate 5: Checkpoint-only recovery
        if state.last_checkpoint_epoch:
            digest = cls._hash(json.dumps({
                "checkpoint": f"{state.last_checkpoint_epoch:.0f}",
                "state": state.checkpoint_state_hash
            }, sort_keys=True))
            return WALManifest(
                verdict=WALVerdict.RECOVERABLE_FROM_CHECKPOINT,
                replay_until_sequence=0,
                directive=f"PROCEED: Recover from checkpoint at {state.last_checkpoint_epoch:.0f}, skip WAL replay.",
                manifest_digest=digest
            )

        return WALManifest(
            verdict=WALVerdict.UNRECOVERABLE_CORRUPTION,
            replay_until_sequence=None,
            directive="HALT: No valid WAL prefix and no checkpoint.",
            manifest_digest=cls._hash("UNRECOVERABLE")
        )