# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-reject-a-delegated-aaif-capability-token-whose-delegation-chain-is-structurally-valid-but-whose-sc.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-reject-a-delegated-aaif-capability-token-whose-delegation-chain-is-structurally-valid-but-whose-sc.warrant.json
# Markdown SHA-256:   d0562e037bb6196fc2bb36d6bd281e7d0b8b7136d2b2f85b50e8bfddae4a18d5
# Code AST SHA-256:   8f3251986b0d25d876815a651fca37d4b59702a14b80bb0a21419d1c0fe4455e
# Verified Classes:   AAIFMonotonicityViolation, ChainLink
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import FrozenSet, Sequence


class AAIFMonotonicityViolation(RuntimeError):
    """Raised when a delegation chain's scope attenuation is non-monotonic."""


@dataclass(frozen=True)
class ChainLink:
    subject: str
    scope: FrozenSet[str]
    delegated_by: str
    revoked: bool = False


def validate_delegation_chain(chain: Sequence[ChainLink]) -> FrozenSet[str]:
    if not chain:
        raise AAIFMonotonicityViolation("empty delegation chain")

    # Walk root -> leaf. Every link must be unrevoked and must not widen scope.
    for idx, link in enumerate(chain):
        if link.revoked:
            raise AAIFMonotonicityViolation(
                f"link {idx} ({link.subject}) is revoked; chain walk aborted"
            )
        if idx == 0:
            continue
        prev = chain[idx - 1]
        added = link.scope - prev.scope
        if added:
            # Non-monotonic: a delegator granted scope it never held.
            # Reject outright; do NOT intersect-and-continue.
            raise AAIFMonotonicityViolation(
                f"link {idx} ({link.subject}) adds scope {sorted(added)} "
                f"not held by delegator {prev.subject}; "
                f"delegation forgery or misconfiguration"
            )

    # Monotone chain: leaf scope is already a subset of every ancestor.
    return chain[-1].scope
