# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-refuse-to-renew-an-a2a-session-after-a-proof-rotation.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-refuse-to-renew-an-a2a-session-after-a-proof-rotation.warrant.json
# Markdown SHA-256:   3bef4ca149756d54fd03f6ac6d0bd2606d04496d8d4df8b7e81d869052d2a5d7
# Code AST SHA-256:   18a42583fffad0c9a856366069d4f210347ab12fa3fcceed1633a63baa6ef74b
# Verified Classes:   SessionBindingError, SessionBinding
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Optional


class SessionBindingError(RuntimeError):
    """Raised when a session's proof-generation binding cannot be safely advanced."""
    pass


@dataclass
class SessionBinding:
    session_id: str
    bound_generation: str
    generation_scoped_decisions: list = field(default_factory=list)
    ttl_extended: bool = False


def handle_inbound_proof(
    binding: SessionBinding,
    proof_generation: str,
    proof_valid: bool,
    rebind_exchange: Optional[dict],
    original_generation_expired: bool,
) -> SessionBinding:
    if not proof_valid:
        raise SessionBindingError(
            f"session {binding.session_id}: inbound proof for generation "
            f"{proof_generation} failed validation"
        )

    # Case 1: same generation as the session is bound to.
    if proof_generation == binding.bound_generation:
        binding.ttl_extended = True
        return binding

    # Case 2: new generation with an explicit, countersigned re-bind.
    if rebind_exchange is not None:
        if (
            rebind_exchange.get("from_generation") != binding.bound_generation
            or rebind_exchange.get("to_generation") != proof_generation
            or not rebind_exchange.get("countersigned")
        ):
            raise SessionBindingError(
                f"session {binding.session_id}: malformed or unsigned rebind "
                f"exchange {rebind_exchange!r}"
            )
        binding.bound_generation = proof_generation
        binding.generation_scoped_decisions.clear()
        binding.ttl_extended = True
        return binding

    # Case 3: the boundary. New generation, valid proof, no re-bind.
    # Never advance the binding or extend TTL here.
    if original_generation_expired:
        raise SessionBindingError(
            f"session {binding.session_id}: peer presented generation "
            f"{proof_generation} without session_rebind while bound generation "
            f"{binding.bound_generation} has expired; session must be re-established"
        )
    raise SessionBindingError(
        f"session {binding.session_id}: peer presented generation "
        f"{proof_generation} without session_rebind; bound generation remains "
        f"{binding.bound_generation}. Refusing to renew; require explicit re-bind."
    )
