# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/where-must-an-instance-verify-scope-attenuation-before-delegating-an-agent-capability-token-and-why-the-obvious-answer-is-wrong.md
# Remnant Warrant:    https://corpusfetch.com/c/where-must-an-instance-verify-scope-attenuation-before-delegating-an-agent-capability-token-and-why-the-obvious-answer-is-wrong.warrant.json
# Markdown SHA-256:   1e85291531ba70c904185f0ebf7ab0f6378e7a7a2bb3e9aed7b80d6d7bbb1ca6
# Code AST SHA-256:   061ff3ad057d25325c73175a6a6596be21df897caec7ce6a6e5c6f823a293e6b
# Verified Classes:   ScopeAttenuationError
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class ScopeAttenuationError(RuntimeError):
    """Raised when a derived capability token cannot be proven to be a
    semantic subset of its parent under the resource's authorization model."""
    pass


def attenuate(parent_resolved: frozenset[str],
              child_requested: frozenset[str],
              resolvable: bool) -> frozenset[str]:
    """
    parent_resolved: the parent token's scope set AFTER resolution against
    the resource's authorization model, evaluated in the issuer's context.
    child_requested: the raw scope set the delegate is asking for.
    resolvable: whether every scope in child_requested can be resolved to a
    concrete set. If False, no attenuation is permitted.
    """
    if not resolvable:
        raise ScopeAttenuationError(
            "child scope contains unresolvable wildcard or context-dependent "
            "term; cannot prove semantic subset, refusing to delegate"
        )

    excess = child_requested - parent_resolved
    if excess:
        raise ScopeAttenuationError(
            f"child scope exceeds resolved parent scope: {sorted(excess)}"
        )

    # Structural and semantic subset now coincide because both sides are
    # concrete. Safe to mint the child token.
    return child_requested


def delegate(parent_token: dict, child_scopes: list[str],
             resource_introspect) -> dict:
    parent_resolved = frozenset(
        resource_introspect(parent_token["scope"], parent_token["tenant"])
    )
    requested = frozenset(child_scopes)

    # Every requested term must itself be resolvable before we compare.
    resolvable = True
    for term in requested:
        try:
            resource_introspect(term, parent_token["tenant"])
        except LookupError:
            resolvable = False
            break

    safe = attenuate(parent_resolved, requested, resolvable)
    return {"scope": sorted(safe), "parent": parent_token["id"]}
