# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-refuse-to-mint-a-delegated-aaif-capability-token-whose-scope-is-a-strict-superset-of-the-delegator-s-own-scope.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-refuse-to-mint-a-delegated-aaif-capability-token-whose-scope-is-a-strict-superset-of-the-delegator-s-own-scope.warrant.json
# Markdown SHA-256:   208b72cc603c7c1ecaadcc7704b27508b028f0752c2c71d4581dfd2bc9159d49
# Code AST SHA-256:   1d21f644138701f57a69c8e259efcf66471d1686dcdbea6ede8da8f2e5a6ebb7
# Verified Classes:   ScopeEscalationError, CapabilityToken
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import FrozenSet


class ScopeEscalationError(RuntimeError):
    """Raised when a requested delegated scope is not a subset of the delegator's scope."""


@dataclass(frozen=True)
class CapabilityToken:
    subject: str
    scope: FrozenSet[str]
    act_chain: tuple


def _normalize(scope: FrozenSet[str]) -> FrozenSet[str]:
    # Collapse trailing wildcards to their base element; reject bare '*'.
    out = set()
    for s in scope:
        if s == "*":
            raise ScopeEscalationError("wildcard scope '*' is not delegable")
        out.add(s[:-2] if s.endswith(":*") else s)
    return frozenset(out)


def mint_delegated_token(
    delegator: CapabilityToken,
    subject: str,
    requested_scope: FrozenSet[str],
) -> CapabilityToken:
    if not requested_scope:
        raise ScopeEscalationError(
            f"empty scope requested for {subject}; empty is interpreted as unrestricted downstream"
        )
    if not delegator.act_chain:
        raise ScopeEscalationError("delegator has no act chain; cannot establish custody")

    req = _normalize(requested_scope)
    have = _normalize(delegator.scope)

    extra = req - have
    if extra:
        raise ScopeEscalationError(
            f"scope escalation: {subject} requested {sorted(extra)} "
            f"not held by delegator {delegator.subject}"
        )

    return CapabilityToken(
        subject=subject,
        scope=req,
        act_chain=delegator.act_chain + (delegator.subject,),
    )
