# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-structural-signals-indicate-a-brokered-agent-credential-has-silently-outlived-its-delegation-chain.md
# Remnant Warrant:    https://corpusfetch.com/c/what-structural-signals-indicate-a-brokered-agent-credential-has-silently-outlived-its-delegation-chain.warrant.json
# Markdown SHA-256:   b559f9b2e01fa3c6302eb2ce1b5842ad49d26a6d5582d09ae1f6dec29d1e17a4
# Code AST SHA-256:   c47dd6d9eb13459436186df399f84eb7448da859c23deb9309d2d03877491a2b
# Verified Classes:   StaleDelegationError, CapabilityToken
# Defensive Guards:   2 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass


class StaleDelegationError(RuntimeError):
    """Raised when a capability token's delegation chain is no longer live."""


@dataclass(frozen=True)
class CapabilityToken:
    subject: str
    scope: frozenset
    chain_root: str
    lease_epoch: int
    minted_at: float
    ttl: float


def validate_token(
    token: CapabilityToken,
    broker_epoch: int,
    revocation_slo: float,
    now: float | None = None,
) -> None:
    now = time.monotonic() if now is None else now

    if now - token.minted_at > token.ttl:
        raise StaleDelegationError(
            f"token for {token.subject} exceeded TTL "
            f"({now - token.minted_at:.1f}s > {token.ttl:.1f}s)"
        )

    # The structural check the self-contained model omits: a token minted
    # under an older lease epoch may reference a chain root that has since
    # been revoked. If the token's TTL outlives the revocation SLO, the
    # epoch check is the only thing standing between us and silent
    # authorization under dead authority.
    if token.lease_epoch < broker_epoch:
        raise StaleDelegationError(
            f"token for {token.subject} carries lease_epoch "
            f"{token.lease_epoch} < broker_epoch {broker_epoch}; "
            f"chain root {token.chain_root} may be revoked"
        )

    if token.ttl > revocation_slo and token.lease_epoch == broker_epoch:
        # Sound only because the epoch matches; if the epoch later advances
        # before this token expires, the next validation will catch it.
        return
