# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-replay-safe-session-resumption-from-backpressure-induced-truncation-in-mcp-http-native-transport-streaming.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-replay-safe-session-resumption-from-backpressure-induced-truncation-in-mcp-http-native-transport-streaming.warrant.json
# Markdown SHA-256:   271eea7f0610330f3a16ff9d49fbf06036691a910bfd650f9dc3f9295b02ee8b
# Code AST SHA-256:   1a6b005caea7a987eb8889095fbd1472a902602b2c7d802d253336c777bce335
# Verified Classes:   StreamResumeGapError, ResumeCheck
# Defensive Guards:   2 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass


class StreamResumeGapError(RuntimeError):
    """Raised when a resumed MCP HTTP stream skips event IDs.

    A gap means the server evicted events under backpressure (or lost
    them from its replay buffer) and cannot honor Last-Event-ID. The
    stream is lossy; the caller must abort and re-issue the request.
    """


@dataclass(frozen=True)
class ResumeCheck:
    last_seen_id: int
    first_received_id: int
    payload_digest: str


def validate_resume(check: ResumeCheck) -> None:
    if check.first_received_id < 0 or check.last_seen_id < 0:
        raise StreamResumeGapError(
            f"invalid event id: last_seen={check.last_seen_id} "
            f"first={check.first_received_id}"
        )
    if check.first_received_id != check.last_seen_id + 1:
        raise StreamResumeGapError(
            f"stream resume gap: expected id {check.last_seen_id + 1}, "
            f"got {check.first_received_id}; "
            f"{check.first_received_id - check.last_seen_id - 1} event(s) lost "
            f"between last_seen and resume (payload digest "
            f"{check.payload_digest[:12]}); abort and re-request"
        )


def digest_events(ids: list[int]) -> str:
    return hashlib.sha256(",".join(map(str, ids)).encode()).hexdigest()


# Example: server evicted 108-115 under backpressure, client resumes at 107.
try:
    validate_resume(
        ResumeCheck(
            last_seen_id=107,
            first_received_id=116,
            payload_digest=digest_events(list(range(100, 141))),
        )
    )
except StreamResumeGapError as exc:
    # Correct response: do not reconstruct; re-issue the originating request.
    print(f"aborting lossy resume: {exc}")
