# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-refuse-a-delegated-aaif-credential-exchange-the-audience-binding-temporal-boundary.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-refuse-a-delegated-aaif-credential-exchange-the-audience-binding-temporal-boundary.warrant.json
# Markdown SHA-256:   98311fea336b5b202aa1138d2942cc3b4a0604cd48c28d822501165199ae30a4
# Code AST SHA-256:   766f78de8bbb0b1477678ded480a8452e4cbcd285f7ed111cfb80e62cb2dac42
# Verified Classes:   DelegationAuthorityLapsed, Credential, KeyState
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from datetime import datetime, timedelta


class DelegationAuthorityLapsed(RuntimeError):
    """Raised when a delegate must refuse to present a credential whose
    delegator authority has lapsed or whose signing key was retired before
    the credential was minted. This is a pre-exchange refusal, not a
    post-exchange error."""

    def __init__(self, clause: str, evidence: dict):
        self.clause = clause
        self.evidence = evidence
        super().__init__(f"delegation refused: {clause} ({evidence})")


@dataclass
class Credential:
    iat: datetime
    exp: datetime
    delegation_exp: datetime | None
    signing_key_id: str


@dataclass
class KeyState:
    retired_after: datetime | None
    jwks_cached_at: datetime


MAX_JWKS_STALENESS = timedelta(minutes=5)


def _jwks_is_verifiable(now: datetime, key: KeyState) -> bool:
    return (now - key.jwks_cached_at) <= MAX_JWKS_STALENESS


def must_refuse_exchange(now: datetime, cred: Credential, key: KeyState) -> None:
    """Evaluate the AAIF delegate-side refusal predicate.

    Refuses iff (delegation_exp present and now >= delegation_exp) OR
    (key.retired_after <= cred.iat), AND the JWKS state is fresh enough
    to make the determination meaningful. Raises DelegationAuthorityLapsed
    on refusal; returns None when the exchange may proceed.
    """
    if not _jwks_is_verifiable(now, key):
        # Cannot prove authority lapsed; do not fabricate a refusal.
        return None

    if cred.delegation_exp is not None and now >= cred.delegation_exp:
        raise DelegationAuthorityLapsed(
            "delegation_exp_lapsed",
            {
                "delegation_exp": cred.delegation_exp.isoformat(),
                "now": now.isoformat(),
                "key_id": cred.signing_key_id,
            },
        )

    if key.retired_after is not None and key.retired_after <= cred.iat:
        raise DelegationAuthorityLapsed(
            "delegator_key_retired",
            {
                "retired_after": key.retired_after.isoformat(),
                "credential_iat": cred.iat.isoformat(),
                "key_id": cred.signing_key_id,
            },
        )

    return None
