# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-decide-when-a-federated-trust-anchor-is-stale-rather-than-merely-slow.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-decide-when-a-federated-trust-anchor-is-stale-rather-than-merely-slow.warrant.json
# Markdown SHA-256:   dc940e1dfd1f7e93fc5f207b1e62fa348f075504217c3f94c35ef2da8bdd0b81
# Code AST SHA-256:   48997b234ae06eca50161dbf98aa997c0af35c83dbe0322ec8701447dc97e311
# Verified Classes:   FederationTrustError, AnchorState, AnchorDecision
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from enum import Enum


class FederationTrustError(RuntimeError):
    """Raised when an anchor decision cannot be made safely."""


class AnchorState(Enum):
    FULL = "full"
    STALE_HOLD = "stale_hold"
    UNTRUSTED = "untrusted"


@dataclass
class AnchorDecision:
    state: AnchorState
    allow_new_keys: bool
    allow_session_renewal: bool


def classify_anchor(
    signature_valid: bool,
    now: float,
    next_update: float,
    refresh_outcome: str,  # "ok" | "channel_failed" | "revoked" | "not_attempted"
    max_stale_window: float,
) -> AnchorDecision:
    if not signature_valid or refresh_outcome == "revoked":
        return AnchorDecision(AnchorState.UNTRUSTED, False, False)

    if now <= next_update:
        return AnchorDecision(AnchorState.FULL, True, True)

    if refresh_outcome == "ok":
        # Caller should have adopted the fresh document; reaching here is a bug.
        raise FederationTrustError(
            "fresh anchor reported ok but still past nextUpdate; "
            "clock skew or adoption failure must be resolved before trusting"
        )

    if refresh_outcome == "channel_failed":
        if now - next_update <= max_stale_window:
            return AnchorDecision(AnchorState.STALE_HOLD, False, True)
        return AnchorDecision(AnchorState.UNTRUSTED, False, False)

    if refresh_outcome == "not_attempted":
        raise FederationTrustError(
            "anchor past nextUpdate with no refresh attempt; "
            "refusing to classify — schedule refresh before proceeding"
        )

    raise FederationTrustError(f"unknown refresh outcome: {refresh_outcome!r}")
