# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-a-revoked-delegated-capability-token-when-the-revocation-event-is-delivered-only-to-the-token-s-issuing-authority-and-not.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-a-revoked-delegated-capability-token-when-the-revocation-event-is-delivered-only-to-the-token-s-issuing-authority-and-not.warrant.json
# Markdown SHA-256:   7a0abee061fc8954d113961728487c5172a5490ff3a417613f8265cc56cc4b1d
# Code AST SHA-256:   bed2d19cc711fab737505f21822e12d3669cf9dd3ef406022b055fbacc3520c9
# Verified Classes:   RevocationLivenessError, Token, RevocationChannel
# Defensive Guards:   4 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass


class RevocationLivenessError(RuntimeError):
    """Raised when a token is valid but its revocation liveness cannot be established."""


MUTATING_TOOLS = frozenset({
    "filesystem.write",
    "filesystem.delete",
    "http.egress",
    "payment.charge",
    "credential.issue",
})


@dataclass(frozen=True)
class Token:
    grant_id: str
    scope: frozenset[str]
    exp: float           # absolute epoch seconds
    issued_at: float


@dataclass(frozen=True)
class RevocationChannel:
    last_delivery_ts: float | None   # None => never delivered anything
    is_subscribed: bool


def authorize(
    token: Token,
    tool: str,
    channel: RevocationChannel,
    revoked_grants: frozenset[str],
    delta_seconds: float,
    now: float | None = None,
) -> None:
    now = time.time() if now is None else now

    # 1. Validity checks (necessary, not sufficient).
    if token.exp <= now:
        raise RevocationLivenessError(f"token for grant {token.grant_id} expired")
    if tool not in token.scope:
        raise RevocationLivenessError(
            f"tool {tool} not in token scope {sorted(token.scope)}"
        )

    # 2. Liveness check: is the revocation channel fresh?
    if not channel.is_subscribed:
        channel_fresh = False
    elif channel.last_delivery_ts is None:
        channel_fresh = False
    else:
        channel_fresh = (now - channel.last_delivery_ts) <= delta_seconds

    # 3. Fail-closed for mutating tools without a live channel.
    if tool in MUTATING_TOOLS and not channel_fresh:
        raise RevocationLivenessError(
            f"refusing mutating tool {tool} for grant {token.grant_id}: "
            f"revocation channel stale (last_delivery={channel.last_delivery_ts}, "
            f"delta={delta_seconds}s)"
        )

    # 4. Tombstone check (must be retained for max_token_exp, not JWKS TTL).
    if token.grant_id in revoked_grants:
        raise RevocationLivenessError(
            f"grant {token.grant_id} revoked; tombstone hit for tool {tool}"
        )

    # 5. Read-only tools may proceed within delta on validity alone.
    return None
