# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-of-an-mcp-http-native-transport-session-lose-resumability-when-the-sse-event-id-sequence-is-re-based-across-a-reconnect.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-of-an-mcp-http-native-transport-session-lose-resumability-when-the-sse-event-id-sequence-is-re-based-across-a-reconnect.warrant.json
# Markdown SHA-256:   15eb131b609d0001f2d0acfb1b6d70cb6e187252ed3ff84b4dc0221ee10136f2
# Code AST SHA-256:   04a3d9b2128ed2d8f2e169d4cd2a8cc03c2b806437402db0b31821f43cf74be7
# Verified Classes:   ResumeSequenceError
# Defensive Guards:   4 fail-closed invariants (zero external deps)

class ResumeSequenceError(RuntimeError):
    """Raised when an SSE resume violates the session-scoped event-ID invariant."""
    pass


def validate_resume(last_processed_id: int, first_received_id: int, buffer_floor: int) -> str:
    """
    Classify a resume outcome for an MCP HTTP-native SSE stream.

    Returns one of: 'resumed', 'gap', 'rebase'.
    Raises ResumeSequenceError on a future ID (protocol violation) or on an
    unresolvable state that the caller must not silently accept.
    """
    if first_received_id == last_processed_id + 1:
        return "resumed"

    if first_received_id > last_processed_id + 1:
        # Events were dropped between disconnect and resume.
        if last_processed_id < buffer_floor:
            raise ResumeSequenceError(
                f"resume id {last_processed_id} predates buffer floor {buffer_floor}; "
                "gap is unrecoverable from server buffer"
            )
        return "gap"

    if first_received_id <= last_processed_id:
        # Server re-based its ID sequence across the reconnect.
        return "rebase"

    raise ResumeSequenceError(
        f"unreachable: last={last_processed_id} first={first_received_id}"
    )


def on_reconnect(last_processed_id: int, first_received_id: int, buffer_floor: int) -> None:
    outcome = validate_resume(last_processed_id, first_received_id, buffer_floor)
    if outcome == "rebase":
        # Do not continue as if the stream were healthy.
        raise ResumeSequenceError(
            f"server re-based event IDs (last={last_processed_id}, "
            f"first={first_received_id}); stream must be treated as new"
        )
    if outcome == "gap":
        # Caller must re-synchronize application state before consuming further.
        raise ResumeSequenceError(
            f"unrecoverable gap after id {last_processed_id}; "
            "re-initialize session state before resuming consumption"
        )
