# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-reject-a-delegated-aaif-credential-whose-scope-chain-is-structurally-valid-but-whose-revocation-epoch-is-stale.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-reject-a-delegated-aaif-credential-whose-scope-chain-is-structurally-valid-but-whose-revocation-epoch-is-stale.warrant.json
# Markdown SHA-256:   1cf5c31722cd06221c6c153053e6914590ec440a85e923fce9d32172b40a5f54
# Code AST SHA-256:   919a7488544e82031789cbae819d97b2782b83227a4d5bc9fac193399e0ba13e
# Verified Classes:   StaleRevocationEpochError, ChainEpochInversionError
# Defensive Guards:   4 fail-closed invariants (zero external deps)

class StaleRevocationEpochError(RuntimeError):
    """Raised when a delegated credential is structurally valid but its
    revocation epoch cannot be corroborated against local state."""


class ChainEpochInversionError(StaleRevocationEpochError):
    """Raised when a child credential claims an epoch older than its parent."""


DELTA_MAX = 30  # seconds of tolerated epoch-cache staleness


def validate_delegated_credential(chain, local_cached_epoch, now, issued_at):
    """
    chain: list of hops, root-first. Each hop has .rev_epoch and .scope.
    Structural validation (signatures, scope subset, depth) is assumed done.
    """
    # 1. Chain epoch monotonicity: child may not predate parent.
    for i in range(1, len(chain)):
        if chain[i].rev_epoch < chain[i - 1].rev_epoch:
            raise ChainEpochInversionError(
                f"hop {i} rev_epoch={chain[i].rev_epoch} < "
                f"parent rev_epoch={chain[i - 1].rev_epoch}: "
                f"possible credential splicing or replay"
            )

    leaf = chain[-1]

    # 2. Leaf must not predate a revocation we already know about.
    if leaf.rev_epoch < local_cached_epoch:
        raise StaleRevocationEpochError(
            f"leaf rev_epoch={leaf.rev_epoch} < local_cached_epoch="
            f"{local_cached_epoch}: credential predates known revocation"
        )

    # 3. Leaf must not claim an epoch we cannot corroborate.
    if leaf.rev_epoch > local_cached_epoch + DELTA_MAX:
        raise StaleRevocationEpochError(
            f"leaf rev_epoch={leaf.rev_epoch} exceeds corroborable bound "
            f"{local_cached_epoch + DELTA_MAX}: unverifiable epoch"
        )

    # 4. Root epoch must not be so old that staleness outlives the credential.
    root = chain[0]
    remaining_ttl = (issued_at + leaf.ttl) - now
    if (local_cached_epoch - root.rev_epoch) > remaining_ttl:
        raise StaleRevocationEpochError(
            f"root rev_epoch={root.rev_epoch} lags local_cached_epoch="
            f"{local_cached_epoch} by more than remaining TTL "
            f"{remaining_ttl}s: acceptance would outlive propagation"
        )

    return True
