# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/where-must-an-instance-of-a-replayable-agent-trajectory-diverge-the-non-idempotent-side-effect-invariant-in-counterfactual-forensic-replay.md
# Remnant Warrant:    https://corpusfetch.com/c/where-must-an-instance-of-a-replayable-agent-trajectory-diverge-the-non-idempotent-side-effect-invariant-in-counterfactual-forensic-replay.warrant.json
# Markdown SHA-256:   563d439031b004241b96d5b129d9a53a54d073b631848237e12b04e932caa10c
# Code AST SHA-256:   98b76fe811d5f857c3c6bbd22b35da531ea27ba7c5b621ddfbd27a233a8ee70b
# Verified Classes:   ReplayContaminationError, AuditabilityClass, SideEffectStep
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum


class ReplayContaminationError(RuntimeError):
    """Raised when a counterfactual replay branch cannot be certified as a
    true counterfactual of the recorded trajectory."""


class AuditabilityClass(Enum):
    COUNTERFACTUAL_ADMISSIBLE = "counterfactual-admissible"
    OBSERVABLE_ONLY = "observable-only"


@dataclass(frozen=True)
class SideEffectStep:
    step_index: int
    idempotency_token: str | None
    token_reverifiable: bool
    response_payload: dict
    # Fields of response_payload that downstream agents read. If any of these
    # is live-state (lock id, timestamp, seqno), the step is not replay-safe
    # even when its token verifies.
    downstream_live_state_fields: frozenset[str]


def classify_trajectory(
    steps: list[SideEffectStep], divergence_step_index: int
) -> AuditabilityClass:
    """Decide whether a counterfactual branch diverging at divergence_step_index
    is admissible, per the first-unverifiable-side-effect boundary."""
    for step in steps:
        if step.step_index > divergence_step_index:
            break
        if step.idempotency_token is None:
            return AuditabilityClass.OBSERVABLE_ONLY
        if not step.token_reverifiable:
            # Divergence is at or after an unverifiable effect: inadmissible.
            return AuditabilityClass.OBSERVABLE_ONLY
        if step.downstream_live_state_fields:
            # Token verifies, but downstream logic keys on live-state fields
            # of the response. This is the L-4471 / L-9932 failure class.
            return AuditabilityClass.OBSERVABLE_ONLY
    return AuditabilityClass.COUNTERFACTUAL_ADMISSIBLE


def run_counterfactual_branch(
    steps: list[SideEffectStep],
    divergence_step_index: int,
    live_call_issued: bool,
) -> AuditabilityClass:
    """Execute a counterfactual branch, refusing contaminated runs."""
    if live_call_issued:
        raise ReplayContaminationError(
            "live gateway call issued during replay; branch is contaminated "
            "and must not be reported as a counterfactual"
        )

    cls = classify_trajectory(steps, divergence_step_index)
    if cls is AuditabilityClass.OBSERVABLE_ONLY:
        raise ReplayContaminationError(
            f"trajectory is observable-only: first unverifiable side effect "
            f"precedes or coincides with divergence at step "
            f"{divergence_step_index}; escalate to log-correlation forensics"
        )
    return cls


if __name__ == "__main__":
    recorded = [
        SideEffectStep(
            step_index=0,
            idempotency_token="L-4471",
            token_reverifiable=False,  # TTL expired before audit
            response_payload={"lock": "L-4471", "ttl": 30},
            downstream_live_state_fields=frozenset({"lock"}),
        ),
        SideEffectStep(
            step_index=1,
            idempotency_token="drop-9f2",
            token_reverifiable=True,
            response_payload={"dropped": True},
            downstream_live_state_fields=frozenset(),
        ),
    ]
    try:
        run_counterfactual_branch(recorded, divergence_step_index=1, live_call_issued=False)
    except ReplayContaminationError as exc:
        print(f"audit refused: {exc}")
