# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-delegated-capability-tokens-from-crossing-issuer-trust-boundaries-when-chain-depth-exceeds-the-verifier-s-policy-horizon.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-delegated-capability-tokens-from-crossing-issuer-trust-boundaries-when-chain-depth-exceeds-the-verifier-s-policy-horizon.warrant.json
# Markdown SHA-256:   296cca04f0acacb0d1f3351666fcf474983b63c52a9b6832880a65f0d04986d1
# Code AST SHA-256:   3153f55883a5447dc9d5f15ea815c2ecd6297504f90674cf4760798d1206abac
# Verified Classes:   ChainDepthExceededError, Hop, Decision
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Sequence


class ChainDepthExceededError(RuntimeError):
    """Raised when a presented delegation chain cannot be fully resolved
    within the verifier's trust horizon. Must never be downgraded to Allow."""


@dataclass(frozen=True)
class Hop:
    issuer: str
    subject: str
    attenuated_rights: frozenset[str]


@dataclass(frozen=True)
class Decision:
    allow: bool
    effective_principal: str | None
    reason: str


def evaluate_chain(
    chain: Sequence[Hop],
    trust_anchors: frozenset[str],
    max_chain_depth: int,
) -> Decision:
    if not chain:
        return Decision(False, None, "empty_chain")

    claimed_depth = len(chain)
    if claimed_depth > max_chain_depth:
        # Pre-walk rejection: never truncate to the horizon.
        raise ChainDepthExceededError(
            f"claimed_depth={claimed_depth} exceeds horizon={max_chain_depth}; "
            f"terminal_subject={chain[-1].subject!r} must not be silently dropped"
        )

    # Walk the chain, enforcing attenuation monotonicity at every hop.
    prev_rights = chain[0].attenuated_rights
    for i, hop in enumerate(chain):
        if i > 0 and not hop.attenuated_rights.issubset(prev_rights):
            return Decision(False, None, f"attenuation_widened_at_hop_{i}")
        prev_rights = hop.attenuated_rights

    # Anchor resolution is a distinct terminal state from horizon exhaustion.
    final_issuer = chain[-1].issuer
    if final_issuer not in trust_anchors:
        # We reached the end of the presented chain but did not land on an
        # anchor we hold. This is an irreducible mismatch, not a success.
        raise ChainDepthExceededError(
            f"chain terminated at issuer={final_issuer!r} which is not a held "
            f"trust anchor; cannot bind decision to terminal subject "
            f"{chain[-1].subject!r}"
        )

    return Decision(
        allow=True,
        effective_principal=chain[-1].subject,
        reason="anchor_resolved",
    )


def authorize(request_principal: str, decision: Decision) -> None:
    if not decision.allow:
        raise ChainDepthExceededError(
            f"authorization denied for request_principal={request_principal!r}: "
            f"{decision.reason}"
        )
    if decision.effective_principal != request_principal:
        # Invariant: decision identity must equal terminal subject identity.
        raise ChainDepthExceededError(
            f"principal_mismatch: decision bound to "
            f"{decision.effective_principal!r} but request claims "
            f"{request_principal!r}"
        )
