# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-mcp-http-native-transport-from-silently-resuming-a-session-across-a-channel-rebind.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-mcp-http-native-transport-from-silently-resuming-a-session-across-a-channel-rebind.warrant.json
# Markdown SHA-256:   ded6764e478dcbf15d751115ef7a7f7f866a66fbbd7842403be7dfd87fd50a90
# Code AST SHA-256:   60f04c20c33d68b184320c095ceca9b8a3842fca5fbd00c70f64f42bf1f4ffd9
# Verified Classes:   ChannelRebindError, StaleGenerationError
# Defensive Guards:   3 fail-closed invariants (zero external deps)

class ChannelRebindError(RuntimeError):
    """Raised when a resumption cursor is presented on an unbound channel."""
    pass


class StaleGenerationError(RuntimeError):
    """Raised when a request carries a session generation older than current."""
    pass


def resume_stream(session, channel_token, cursor, idempotency_key):
    if channel_token != session.bound_channel_token:
        # A cursor from a prior channel is not a continuation; it is a rebind.
        # Refuse to replay or re-execute without an explicit idempotency gate.
        raise ChannelRebindError(
            f"cursor {cursor!r} issued on channel "
            f"{session.cursor_channel_token!r} presented on "
            f"{channel_token!r}; rebind requires idempotency key"
        )
    if cursor.generation < session.generation:
        raise StaleGenerationError(
            f"cursor generation {cursor.generation} < session "
            f"generation {session.generation}"
        )
    if cursor.generation == session.generation and not idempotency_key:
        raise ChannelRebindError(
            "same-generation resumption without idempotency key; "
            "duplicate effect possible"
        )
    session.generation += 1
    session.bound_channel_token = channel_token
    return session.replay_from(cursor, idempotency_key=idempotency_key)
