# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-nondeterministic-tool-call-divergence-during-deterministic-replay-of-agentic-sessions.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-nondeterministic-tool-call-divergence-during-deterministic-replay-of-agentic-sessions.warrant.json
# Markdown SHA-256:   1ae51b1892ecd2e54aa1180e54a51d5e4c0201e7b3c36f47113eaac3c7d557da
# Code AST SHA-256:   21696a3018367968cd2de006f4859161a032aa7af72a77f718d3b4e3e4663b03
# Verified Classes:   ReplayDivergenceError, AuditIncompleteError, RecordedCall
# Defensive Guards:   4 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass
from typing import Optional


class ReplayDivergenceError(RuntimeError):
    """Raised when a replayed tool call's effect does not match its recorded effect."""
    pass


class AuditIncompleteError(RuntimeError):
    """Raised when a recorded call lacks the post-state fingerprint needed to assert faithfulness."""
    pass


@dataclass
class RecordedCall:
    turn_index: int
    tool: str
    output: str
    post_state_fingerprint: Optional[str]   # None means the recorder never captured it
    served_from_idempotency_cache: bool = False


def verify_call(recorded: RecordedCall, replayed_output: str, replayed_state: bytes) -> None:
    if recorded.served_from_idempotency_cache:
        # Cache hit during replay: no re-execution occurred. Cannot assert faithfulness.
        raise AuditIncompleteError(
            f"turn {recorded.turn_index} ({recorded.tool}): served from idempotency cache "
            f"during replay; effect not re-executed, replay is not admissible as evidence"
        )

    if recorded.post_state_fingerprint is None:
        raise AuditIncompleteError(
            f"turn {recorded.turn_index} ({recorded.tool}): no recorded post-state fingerprint; "
            f"output-only match is not sufficient for effect-equivalence"
        )

    actual_fp = hashlib.sha256(replayed_state).hexdigest()

    if recorded.output != replayed_output:
        raise ReplayDivergenceError(
            f"turn {recorded.turn_index} ({recorded.tool}): output mismatch "
            f"recorded={recorded.output!r} replayed={replayed_output!r}"
        )

    if recorded.post_state_fingerprint != actual_fp:
        raise ReplayDivergenceError(
            f"turn {recorded.turn_index} ({recorded.tool}): effect divergence despite identical "
            f"output; recorded_state={recorded.post_state_fingerprint} replayed_state={actual_fp}"
        )


def replay_session(calls, executor) -> list:
    results = []
    for call in calls:
        replayed_output, replayed_state = executor(call)
        verify_call(call, replayed_output, replayed_state)
        results.append((call.turn_index, "faithful"))
    return results
