# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-revokes-a-federated-agent-credential-mid-token-rotation-the-split-brain-window-between-issuer-and-verifier.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-revokes-a-federated-agent-credential-mid-token-rotation-the-split-brain-window-between-issuer-and-verifier.warrant.json
# Markdown SHA-256:   2ffe51d9455611425848e6e5ca59a19dac2967aeec516b92c17a3c12a96b6e34
# Code AST SHA-256:   ef04ba4b0d494cd1670035f88fa19099acb3a96bf988fa27a228f16438dbc02f
# Verified Classes:   StaleCredentialAccepted, RevocationPropagationGap, Credential, Verifier
# Defensive Guards:   4 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass, field


class StaleCredentialAccepted(RuntimeError):
    """Raised when a verifier would honor a credential superseded by a higher epoch."""
    pass


class RevocationPropagationGap(RuntimeError):
    """Raised when a revocation record has not yet reached the verifier."""
    pass


@dataclass
class Credential:
    identity: str
    epoch: int
    issued_at: float
    ttl: float

    def expired(self, now: float) -> bool:
        return now >= self.issued_at + self.ttl


@dataclass
class Verifier:
    # highest epoch observed per identity; the monotonic high-water mark
    high_water: dict = field(default_factory=dict)
    # credentials seen but whose revocation record has not arrived
    pending_revocation: dict = field(default_factory=dict)

    def observe(self, cred: Credential) -> None:
        prev = self.high_water.get(cred.identity, -1)
        if cred.epoch > prev:
            # successor implies all lower epochs are invalid; record them
            for lower in range(prev + 1, cred.epoch):
                self.pending_revocation[(cred.identity, lower)] = True
            self.high_water[cred.identity] = cred.epoch

    def validate(self, cred: Credential, now: float) -> bool:
        if cred.expired(now):
            raise StaleCredentialAccepted(
                f"credential {cred.identity}@{cred.epoch} expired at "
                f"{cred.issued_at + cred.ttl:.0f}, validated at {now:.0f}"
            )
        hw = self.high_water.get(cred.identity, -1)
        if cred.epoch < hw:
            # a successor exists; this credential is disowned regardless of TTL
            raise StaleCredentialAccepted(
                f"credential {cred.identity}@{cred.epoch} superseded by "
                f"epoch {hw}; revocation propagation gap is not an excuse"
            )
        if (cred.identity, cred.epoch) in self.pending_revocation:
            raise RevocationPropagationGap(
                f"revocation for {cred.identity}@{cred.epoch} not yet observed"
            )
        return True


def rotate_and_verify(verifier: Verifier, old: Credential, new: Credential,
                      now: float) -> None:
    # rotation mints the successor before the predecessor expires
    verifier.observe(new)
    # a replayed predecessor arrives during the propagation gap
    if old.epoch < verifier.high_water.get(old.identity, -1):
        raise StaleCredentialAccepted(
            f"replayed predecessor {old.identity}@{old.epoch} rejected: "
            f"epoch {verifier.high_water[old.identity]} is live"
        )
    verifier.validate(new, now)
