# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-a-stale-federation-trust-anchor-when-revocation-propagation-lags-attestation-issuance.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-a-stale-federation-trust-anchor-when-revocation-propagation-lags-attestation-issuance.warrant.json
# Markdown SHA-256:   5788f71e128a54eb01d7b3781a14b0aec857502332a7693f60c33edd9e815bd4
# Code AST SHA-256:   616683978ab330bf6f70c1eff970ccb80a101ece445ed0a4787d923a611c9b89
# Verified Classes:   StaleAnchorError, Attestation, FederationGateway
# Defensive Guards:   5 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass


class StaleAnchorError(RuntimeError):
    """Raised when a trust anchor or attestation cannot be proven fresh."""
    pass


@dataclass
class Attestation:
    issuer: str
    capability: str
    expires_at: float
    signature_valid: bool


class FederationGateway:
    def __init__(self, max_propagation_delay: float, anchor_rotation_grace: float):
        self.max_propagation_delay = max_propagation_delay
        self.anchor_rotation_grace = anchor_rotation_grace
        self.last_anchor_sync: dict[str, float] = {}
        self.trusted_anchors: dict[str, set[str]] = {}

    def record_anchor_sync(self, peer: str, anchor_id: str, now: float | None = None) -> None:
        now = time.time() if now is None else now
        self.last_anchor_sync[peer] = now
        self.trusted_anchors.setdefault(peer, set()).add(anchor_id)

    def admit(self, att: Attestation, now: float | None = None) -> bool:
        now = time.time() if now is None else now

        if not att.signature_valid:
            raise StaleAnchorError(f"signature invalid for {att.issuer}/{att.capability}")

        if att.expires_at <= now:
            raise StaleAnchorError(f"attestation expired for {att.issuer}/{att.capability}")

        sync = self.last_anchor_sync.get(att.issuer)
        if sync is None:
            raise StaleAnchorError(
                f"no anchor sync watermark for peer {att.issuer}; "
                f"cannot bound revocation lag"
            )

        freshness_ceiling = sync + self.max_propagation_delay
        if now > freshness_ceiling:
            raise StaleAnchorError(
                f"attestation for {att.issuer}/{att.capability} is unverifiable: "
                f"last anchor sync {now - sync:.1f}s ago exceeds propagation budget "
                f"{self.max_propagation_delay:.1f}s; revocation state unknown"
            )

        anchor_age = now - sync
        if anchor_age > self.anchor_rotation_grace:
            raise StaleAnchorError(
                f"anchor binding for {att.issuer} exceeds rotation grace "
                f"({anchor_age:.1f}s > {self.anchor_rotation_grace:.1f}s); "
                f"re-resolve against current anchor set"
            )

        return True
