# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-accepts-a-revoked-federated-agent-credential-during-an-offline-verification-window.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-accepts-a-revoked-federated-agent-credential-during-an-offline-verification-window.warrant.json
# Markdown SHA-256:   adb4c63247aae072dadf391cf74efe63ebfd610ba63022bec7049bdff9b383a7
# Code AST SHA-256:   3a8574206d06188ab377b565401cc3677576c8436acf398578e2eb5899955ba2
# Verified Classes:   RevocationShadowError, VerifierPolicy
# Defensive Guards:   3 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass

class RevocationShadowError(RuntimeError):
    """Raised when a credential is accepted inside the revocation shadow window."""

@dataclass(frozen=True)
class VerifierPolicy:
    delta_skew_s: int = 120
    delta_snap_s: int = 900

def verify_offline(
    cred_exp: float,
    revoked_at: float | None,
    snapshot_fetch_time: float,
    local_now: float,
    policy: VerifierPolicy,
) -> bool:
    # Snapshot freshness: NO skew adder. Monotonic local clock only.
    if local_now - snapshot_fetch_time > policy.delta_snap_s:
        raise RevocationShadowError(
            f"snapshot stale by {local_now - snapshot_fetch_time:.0f}s > {policy.delta_snap_s}s; fail closed"
        )
    # Credential boundary: single skew budget, not additive with snapshot.
    if cred_exp < local_now - policy.delta_skew_s:
        return False
    # Revocation: if revoked at or before snapshot fetch, snapshot must reflect it.
    if revoked_at is not None and revoked_at <= snapshot_fetch_time:
        raise RevocationShadowError(
            f"credential revoked at {revoked_at:.0f} but snapshot fetched {snapshot_fetch_time:.0f}; "
            f"shadow window = skew({policy.delta_skew_s}) + snap({policy.delta_snap_s})"
        )
    # Revocation after snapshot: accepted only until snapshot refresh; caller must re-fetch.
    if revoked_at is not None and revoked_at > snapshot_fetch_time:
        if local_now - snapshot_fetch_time > policy.delta_snap_s - policy.delta_skew_s:
            raise RevocationShadowError("inside revocation shadow; refresh snapshot before accepting")
    return True
