# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-of-a-federated-capability-broker-silently-grant-on-a-stale-delegation-chain-after-a-policy-negotiation-rollback.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-of-a-federated-capability-broker-silently-grant-on-a-stale-delegation-chain-after-a-policy-negotiation-rollback.warrant.json
# Markdown SHA-256:   765c1d2bd721d252434e156040b197a43056de5695914f09d62bae10f4367c74
# Code AST SHA-256:   25082a9ce7bb45a7fe212d91d390f2e6c8a335d7f83f4b090d0843a2a63b8370
# Verified Classes:   StaleDelegationError, RollbackTombstone, DelegationChain, FederationBroker
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from datetime import datetime, timedelta


class StaleDelegationError(RuntimeError):
    """Raised when a delegation chain references a policy version that was
    rolled back after the chain was minted under the optimistic window."""
    pass


@dataclass(frozen=True)
class RollbackTombstone:
    version: int
    effective_from: datetime
    effective_to: datetime


@dataclass(frozen=True)
class DelegationChain:
    issuer: str
    subject: str
    capability: str
    policy_version: int
    epoch: int
    minted_at: datetime


class FederationBroker:
    def __init__(self, current_epoch: int, committed_versions: set[int]):
        self.current_epoch = current_epoch
        self.committed_versions = committed_versions
        self.tombstones: list[RollbackTombstone] = []

    def record_rollback(self, version: int, t0: datetime, t1: datetime) -> None:
        self.tombstones.append(RollbackTombstone(version, t0, t1))

    def validate_chain(self, chain: DelegationChain) -> None:
        if chain.epoch != self.current_epoch:
            raise StaleDelegationError(
                f"epoch mismatch: chain={chain.epoch} current={self.current_epoch}"
            )
        if chain.policy_version not in self.committed_versions:
            # Not committed. Either it is a future version (legit in-flight)
            # or it was rolled back. Disambiguate via tombstone + mint time.
            for tomb in self.tombstones:
                if tomb.version == chain.policy_version:
                    if tomb.effective_from <= chain.minted_at <= tomb.effective_to:
                        raise StaleDelegationError(
                            f"chain minted under rolled-back policy "
                            f"version={chain.policy_version} at {chain.minted_at.isoformat()}; "
                            f"rollback window "
                            f"[{tomb.effective_from.isoformat()}, "
                            f"{tomb.effective_to.isoformat()}]"
                        )
            # Future version, no tombstone: allow (will commit or be swept).
            return
        # Committed version: valid regardless of mint time.
        return
