# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-revocation-from-attestation-expiry-under-aaif-short-lived-identity-rotation.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-revocation-from-attestation-expiry-under-aaif-short-lived-identity-rotation.warrant.json
# Markdown SHA-256:   4bd0f23f1162c0487d3a8808756328bb3d3430a9deae04a7a6ab5d1443ba42a0
# Code AST SHA-256:   dd7bba8b52b7a50e94d24f752d477bc2c1271f43da51bbd583e6cf682bd45ebe
# Verified Classes:   RevocationLaunderedViaRotation, StaleIssuerDetected, RevocationTerminal
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class RevocationLaunderedViaRotation(RuntimeError):
    """Raised when an EXPIRED-triggered rotation returns an attestation
    that cannot be proven free of a pending revocation. Halts the lineage."""
    pass


class StaleIssuerDetected(RuntimeError):
    """Raised when a rotation returns an identity_epoch not strictly greater
    than the last known epoch, indicating the issuer has not observed a
    state change the verifier has. Halts the lineage."""
    pass


class RevocationTerminal(RuntimeError):
    """Raised on an explicit REVOKED verification result. Terminal for lineage."""
    pass


def rotate_on_expiry(instance, last_epoch: int, request_attestation, authoritative_revocation_check):
    new = request_attestation(instance.subject)

    # A rotation that does not advance the epoch is evidence of a stale issuer,
    # which is precisely the condition under which revocation laundering occurs.
    if new.identity_epoch <= last_epoch:
        instance.halt_lineage()
        raise StaleIssuerDetected(
            f"rotation returned epoch {new.identity_epoch} <= last {last_epoch}"
        )

    # Never trust the issuer's issuance-path revocation check; verify against
    # the authoritative source with cache bypass before adopting.
    if not authoritative_revocation_check(new.subject, cache_bypass=True):
        instance.halt_lineage()
        raise RevocationLaunderedViaRotation(
            f"subject {new.subject!r} revoked but issuer re-issued at epoch {new.identity_epoch}"
        )

    instance.adopt(new)
    return new.identity_epoch
