# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-reject-a-delegated-scoped-credential-whose-delegation-chain-is-intact-but-whose-scope-narrowing-was-performed-by-a.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-reject-a-delegated-scoped-credential-whose-delegation-chain-is-intact-but-whose-scope-narrowing-was-performed-by-a.warrant.json
# Markdown SHA-256:   27aa99591fa7e5709351d558c9702d42981711d032b46dfb28a479737cc5fa2e
# Code AST SHA-256:   d673ea62518060a4299b5d1bf0259f1ee80553af51dbc0fba9fed77fceefb998
# Verified Classes:   UnauthorizedAttenuationError
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class UnauthorizedAttenuationError(RuntimeError):
    """Raised when an intermediate hop narrowed a scope in a way it had no authority to perform."""
    pass


def validate_delegation_hop(parent_scope, child_scope, hop_index, is_root_owner):
    """
    Validate a single delegation hop in an AAIF chain.

    Structural signature and audience checks are assumed already passed.
    This function enforces attenuation authority: an intermediate hop may
    only remove capabilities, never rewrite predicates or patterns into a
    scope whose satisfaction set is not provably contained in the parent's.
    """
    if is_root_owner:
        # Hop 0 may mint any scope within its own grant.
        return True

    attenuation = diff_scopes(parent_scope, child_scope)

    if attenuation.is_pure_removal():
        return True

    if attenuation.rewrites_predicate() or attenuation.rewrites_pattern():
        if not subset_semantic(child_scope, parent_scope):
            raise UnauthorizedAttenuationError(
                f"hop {hop_index}: attenuation rewrote scope into a non-subset. "
                f"parent={parent_scope!r} child={child_scope!r}. "
                f"The intermediate hop lacked authority to mint this scope; "
                f"re-delegate using pure capability removal."
            )
        return True

    # Undecidable or unknown attenuation form: reject by default.
    raise UnauthorizedAttenuationError(
        f"hop {hop_index}: attenuation form not provably narrowing; "
        f"rejecting per default-deny. parent={parent_scope!r} child={child_scope!r}"
    )
