# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-identity-revocation-fail-to-propagate-through-a-delegated-credential-chain-that-was-minted-before-the-revocation-epoch.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-identity-revocation-fail-to-propagate-through-a-delegated-credential-chain-that-was-minted-before-the-revocation-epoch.warrant.json
# Markdown SHA-256:   8add16b9148272bd8307035382775beb85eba7e2bce834f619a97e8732ef0cd7
# Code AST SHA-256:   050b9758119ea87817e58fb215078d9909242d8edf6ca760e4d09f94246b15c3
# Verified Classes:   DelegatedCredentialRevokedError, ChildCredential, RevocationRecord
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from __future__ import annotations

from dataclasses import dataclass
from typing import Mapping


class DelegatedCredentialRevokedError(RuntimeError):
    """Raised when a child credential's parent authority was revoked after mint."""


@dataclass(frozen=True)
class ChildCredential:
    subject: str
    issuer: str
    parent_epoch: int
    not_before: float
    not_after: float


@dataclass(frozen=True)
class RevocationRecord:
    subject: str
    revocation_epoch: int
    revoked_at: float


def verify_delegated_credential(
    cred: ChildCredential,
    now: float,
    revocation_table: Mapping[str, RevocationRecord],
) -> None:
    """Validate a delegated scoped credential including chain-epoch fencing.

    Per-object checks (expiry, subject revocation) are necessary but NOT
    sufficient. The relational check below is what prevents a validly-minted
    child from outliving its revoked parent.
    """
    if not (cred.not_before <= now < cred.not_after):
        raise DelegatedCredentialRevokedError(
            f"credential for {cred.subject} outside validity window "
            f"[{cred.not_before}, {cred.not_after}) at {now}"
        )

    # Subject revocation: the holder itself is untrusted.
    if cred.subject in revocation_table:
        rec = revocation_table[cred.subject]
        raise DelegatedCredentialRevokedError(
            f"subject {cred.subject} revoked at {rec.revoked_at}"
        )

    # Chain revocation: the ISSUER's authority was withdrawn after this
    # credential was minted. This is the non-obvious boundary. A child minted
    # at parent_epoch E is only trustworthy while the parent's revocation
    # epoch is still <= E. Once the parent is revoked at epoch > E, every
    # child minted under E is retroactively untrusted.
    parent_rev = revocation_table.get(cred.issuer)
    if parent_rev is not None and cred.parent_epoch < parent_rev.revocation_epoch:
        raise DelegatedCredentialRevokedError(
            f"credential for {cred.subject} was minted under {cred.issuer} "
            f"at parent_epoch {cred.parent_epoch}, but issuer was revoked at "
            f"epoch {parent_rev.revocation_epoch} ({parent_rev.revoked_at}); "
            f"delegated authority cannot outlive its issuer"
        )
