# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-treat-an-mcp-session-resumption-token-as-invalid-despite-a-syntactically-valid-http-request.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-treat-an-mcp-session-resumption-token-as-invalid-despite-a-syntactically-valid-http-request.warrant.json
# Markdown SHA-256:   7fc603d4e8c725d5a22559ac52413fe6a36dbccd8e456d9ad6abcd6e6e6af764
# Code AST SHA-256:   c37a8bc7acd99a0f932dcd7899fff383ff39bf7f63175f10a167b6260901dc39
# Verified Classes:   ResumptionTokenError, CursorProvenanceError, SessionUnknownError, ReplayGapError, SessionRecord
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Optional


class ResumptionTokenError(RuntimeError):
    """Raised when a resumption token is presented but cannot be honored."""
    pass


class CursorProvenanceError(ResumptionTokenError):
    """Cursor was issued by a different session than the one presented."""
    pass


class SessionUnknownError(ResumptionTokenError):
    """Session ID is syntactically valid but not present in the registry."""
    pass


class ReplayGapError(ResumptionTokenError):
    """Session is live but the replay buffer no longer contains the cursor."""
    pass


@dataclass
class SessionRecord:
    session_id: str
    buffer_head: int   # earliest replayable event id
    buffer_tail: int   # most recent event id


def validate_resumption(
    presented_session: str,
    presented_cursor: int,
    registry: dict[str, SessionRecord],
    cursor_owner: dict[int, str],
) -> SessionRecord:
    """
    Gate a resumption request. Order matters: liveness, then provenance,
    then buffer reachability. Collapsing these produces wrong client recovery.
    """
    record = registry.get(presented_session)
    if record is None:
        raise SessionUnknownError(
            f"session {presented_session!r} not in registry; client must re-initialize"
        )

    owner = cursor_owner.get(presented_cursor)
    if owner is None:
        raise CursorProvenanceError(
            f"cursor {presented_cursor} was never issued by any session"
        )
    if owner != presented_session:
        raise CursorProvenanceError(
            f"cursor {presented_cursor} belongs to session {owner!r}, "
            f"presented against {presented_session!r}"
        )

    if presented_cursor < record.buffer_head:
        raise ReplayGapError(
            f"cursor {presented_cursor} precedes buffer head {record.buffer_head} "
            f"for live session {presented_session!r}; signal gap, do not restart"
        )

    return record
