# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-a-revoked-delegation-from-a-delayed-one-in-federated-multi-agent-orchestration.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-a-revoked-delegation-from-a-delayed-one-in-federated-multi-agent-orchestration.warrant.json
# Markdown SHA-256:   668214bfdfdefcf7a20b3f69e3d7b15d44c32345e6a842d09e2e4132dfff4d9f
# Code AST SHA-256:   156d00b6633bab6a05e2423df386fc4d7a2f83c1f3890fd743cb2739268ecbf4
# Verified Classes:   DelegationUndecidableError, RevocationBypassError, DelegationToken
# Defensive Guards:   4 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass


class DelegationUndecidableError(RuntimeError):
    """Raised when a rejection cannot be classified as revoked vs delayed."""


class RevocationBypassError(RuntimeError):
    """Raised when re-derivation from root would defeat an active revocation."""


@dataclass(frozen=True)
class DelegationToken:
    token_id: str
    issuer_epoch: int   # epoch at which this token was issued
    expires_at: float   # monotonic deadline, not wall clock


def classify_rejection(
    token: DelegationToken,
    issuer_current_epoch: int | None,
    rejection_reason: str,
) -> str:
    """Return 'revoked' or 'delayed'. Raise if the distinction is undecidable."""
    if rejection_reason not in ("invalid_token", "insufficient_scope", "not_yet_valid"):
        raise DelegationUndecidableError(
            f"unrecognized rejection reason {rejection_reason!r}; cannot classify"
        )
    if issuer_current_epoch is None:
        # No epoch exposed: fail closed. Caller must escalate to principal.
        raise DelegationUndecidableError(
            f"issuer exposes no revocation epoch for token {token.token_id}; "
            "cannot distinguish revoked from delayed"
        )
    if issuer_current_epoch > token.issuer_epoch:
        return "revoked"
    return "delayed"


def maybe_rederive(
    token: DelegationToken,
    issuer_current_epoch: int | None,
    rejection_reason: str,
    deadline: float,
) -> DelegationToken:
    """Re-derive a fresh token from root ONLY if no revocation has occurred."""
    if time.monotonic() > deadline:
        raise DelegationUndecidableError(
            f"deadline exceeded while classifying token {token.token_id}"
        )
    state = classify_rejection(token, issuer_current_epoch, rejection_reason)
    if state == "revoked":
        # Re-deriving from the same root would silently reinstate a dead grant.
        raise RevocationBypassError(
            f"refusing to re-derive token {token.token_id}: issuer epoch "
            f"{issuer_current_epoch} > token epoch {token.issuer_epoch}"
        )
    # state == "delayed": safe to retry the SAME token, or re-derive if epoch unchanged
    return token
