# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-treat-revocation-propagation-latency-when-federating-agent-identities-across-org-boundaries.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-treat-revocation-propagation-latency-when-federating-agent-identities-across-org-boundaries.warrant.json
# Markdown SHA-256:   d87c6a2b67073c98a73e31a6b1166f5a334c3150f87a20d7668813d061ea28eb
# Code AST SHA-256:   05fa12daeb755647a20c496a668d94206d106fe67b6093c353374622b7aebd6f
# Verified Classes:   RevocationBoundUnverifiable, Credential, RelyingParty
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from datetime import datetime, timedelta, timezone


class RevocationBoundUnverifiable(RuntimeError):
    """Raised when a claimed revocation bound cannot be enforced by the federation."""


@dataclass(frozen=True)
class Credential:
    subject: str
    issued_at: datetime
    expires_at: datetime

    @property
    def ttl(self) -> timedelta:
        return self.expires_at - self.issued_at


@dataclass(frozen=True)
class RelyingParty:
    org: str
    positive_cache_ttl: timedelta
    revocation_pull_interval: timedelta
    attests_policy: bool


def assert_revocation_bound(
    credential: Credential,
    relying_parties: list[RelyingParty],
    claimed_budget: timedelta,
) -> None:
    """Verify that revocation is enforceable within claimed_budget.

    The enforceable bound is min(credential TTL, per-RP cache/pull intervals),
    but per-RP intervals are only enforceable if the RP attests them.
    """
    if credential.ttl <= claimed_budget:
        # Expiry alone enforces the bound; feed latency only improves it.
        return

    if not relying_parties:
        raise RevocationBoundUnverifiable(
            "no relying parties enumerated; cannot bound revocation propagation"
        )

    for rp in relying_parties:
        if not rp.attests_policy:
            raise RevocationBoundUnverifiable(
                f"relying party {rp.org!r} does not attest cache/pull policy; "
                f"credential TTL {credential.ttl} exceeds claimed budget "
                f"{claimed_budget} and cannot be enforced"
            )
        if rp.positive_cache_ttl > claimed_budget:
            raise RevocationBoundUnverifiable(
                f"relying party {rp.org!r} caches positive assertions for "
                f"{rp.positive_cache_ttl}, exceeding claimed budget {claimed_budget}"
            )
        if rp.revocation_pull_interval > claimed_budget:
            raise RevocationBoundUnverifiable(
                f"relying party {rp.org!r} pulls revocation list every "
                f"{rp.revocation_pull_interval}, exceeding claimed budget {claimed_budget}"
            )


if __name__ == "__main__":
    now = datetime.now(timezone.utc)
    cred = Credential("agent:acme:ops-7", now, now + timedelta(hours=24))
    rps = [
        RelyingParty("beta", timedelta(minutes=5), timedelta(minutes=1), True),
        RelyingParty("gamma", timedelta(hours=24), timedelta(hours=1), True),
    ]
    try:
        assert_revocation_bound(cred, rps, claimed_budget=timedelta(minutes=10))
    except RevocationBoundUnverifiable as exc:
        # Expected: 24h TTL with a 10m claim is unenforceable.
        print(f"bound unverifiable: {exc}")
