# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-a-spiffe-trust-domain-rotation-from-a-federation-revocation-at-the-a2a-endpoint-boundary.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-a-spiffe-trust-domain-rotation-from-a-federation-revocation-at-the-a2a-endpoint-boundary.warrant.json
# Markdown SHA-256:   77ba35353f17f3da7247b332570312d1f26025af5a04280750dc51e01e79f17b
# Code AST SHA-256:   67674771c88494eae524b094d7c4abbd8f11b0279aae68ff8de5aac1890d12dc
# Verified Classes:   FederationTrustError, RelationshipState, BundleCache, FederationRelationship, BundleFetcher
# Defensive Guards:   6 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional


class FederationTrustError(RuntimeError):
    """Raised when a federation trust decision cannot be made safely."""
    pass


class RelationshipState(Enum):
    TRUSTED = "trusted"
    DEGRADED = "degraded"
    TERMINATED = "terminated"


@dataclass
class BundleCache:
    sequence: int
    valid_until: float
    keys: dict = field(default_factory=dict)

    def validates(self, svid_key_id: str) -> bool:
        return svid_key_id in self.keys


@dataclass
class FederationRelationship:
    trust_domain: str
    state: RelationshipState
    bundle: BundleCache


class BundleFetcher:
    """Abstract bundle endpoint. Returns (status, sequence, keys) or raises."""

    def fetch(self, trust_domain: str):
        raise NotImplementedError


def resolve_signature_failure(
    rel: FederationRelationship,
    svid_key_id: str,
    fetcher: BundleFetcher,
    now: Optional[float] = None,
) -> RelationshipState:
    """
    Called when an inbound SVID from rel.trust_domain fails signature
    verification against rel.bundle. Returns the new relationship state.
    Never transitions to TERMINATED on transport error.
    """
    now = time.time() if now is None else now

    if rel.state is RelationshipState.TERMINATED:
        raise FederationTrustError(
            f"relationship {rel.trust_domain} already terminated; "
            f"refusing to process SVID key {svid_key_id}"
        )

    try:
        status, new_seq, new_keys = fetcher.fetch(rel.trust_domain)
    except (TimeoutError, ConnectionError) as exc:
        # Indeterminate: hold DEGRADED, keep serving within valid_until.
        if now > rel.bundle.valid_until:
            rel.state = RelationshipState.DEGRADED
            raise FederationTrustError(
                f"bundle for {rel.trust_domain} expired and endpoint "
                f"unreachable: {exc}"
            ) from exc
        rel.state = RelationshipState.DEGRADED
        return rel.state

    if status in (404, 410):
        # Administrative revocation: relationship is gone.
        rel.state = RelationshipState.TERMINATED
        return rel.state

    if status >= 500:
        rel.state = RelationshipState.DEGRADED
        return rel.state

    if status == 200 and new_seq > rel.bundle.sequence:
        # Rotation: adopt newer bundle, re-verify.
        rel.bundle = BundleCache(
            sequence=new_seq,
            valid_until=now + 300.0,
            keys=new_keys,
        )
        if not rel.bundle.validates(svid_key_id):
            # Peer presents a key its own newer bundle does not vouch for.
            raise FederationTrustError(
                f"post-rotation bundle for {rel.trust_domain} "
                f"does not validate SVID key {svid_key_id}; "
                f"possible forgery or peer misconfiguration"
            )
        rel.state = RelationshipState.TRUSTED
        return rel.state

    if status == 200 and new_seq == rel.bundle.sequence:
        # Same bundle, still fails: request-level rejection, not revocation.
        raise FederationTrustError(
            f"SVID key {svid_key_id} not in current bundle for "
            f"{rel.trust_domain} (seq {new_seq}); reject request only"
        )

    raise FederationTrustError(
        f"unexpected bundle response for {rel.trust_domain}: "
        f"status={status} seq={new_seq}"
    )
