# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-delegated-authorization-from-identity-impersonation-at-the-first-hop-of-an-agent-to-agent-secure-channel.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-delegated-authorization-from-identity-impersonation-at-the-first-hop-of-an-agent-to-agent-secure-channel.warrant.json
# Markdown SHA-256:   d54979ef8966e739b1d27d6f6fc770adb21a17e5f6e1441f105b3eeceb17572f
# Code AST SHA-256:   de7c823acbdbfab9c91122798bfcf3a0847c327c351d4b7a7945d7c43e18a0cb
# Verified Classes:   DelegationBindingError
# Defensive Guards:   6 fail-closed invariants (zero external deps)

class DelegationBindingError(RuntimeError):
    """Raised when the authorization subject is derived from channel identity
    rather than from a verified delegation chain rooted at the origin."""
    pass


def authorize_hop(
    session_peer_id: str,
    delegation_chain: list[dict],
    origin_grants: set[str],
    peer_grants: set[str],
) -> set[str]:
    """
    Enforce per-hop authorization against the delegation chain tail.

    Each chain link: {"issuer": str, "subject": str, "scope": set[str], "sig_ok": bool}
    Returns the effective scope the request may exercise.
    """
    if not delegation_chain:
        raise DelegationBindingError(
            "no delegation chain present; refusing to authorize against "
            f"session peer {session_peer_id!r} identity alone"
        )

    # 1. Every link must be signed and monotonically attenuating.
    prev_scope = None
    for i, link in enumerate(delegation_chain):
        if not link.get("sig_ok"):
            raise DelegationBindingError(f"chain link {i} signature invalid")
        if prev_scope is not None and not link["scope"] <= prev_scope:
            raise DelegationBindingError(
                f"chain link {i} expands scope beyond parent (attenuation violated)"
            )
        prev_scope = link["scope"]

    # 2. Issuer continuity: link[i].issuer must equal link[i-1].subject.
    for i in range(1, len(delegation_chain)):
        if delegation_chain[i]["issuer"] != delegation_chain[i - 1]["subject"]:
            raise DelegationBindingError(
                f"chain link {i} issuer {delegation_chain[i]['issuer']!r} does not "
                f"match prior subject {delegation_chain[i - 1]['subject']!r}"
            )

    # 3. The session peer MUST be the chain's immediate issuer.
    tail_issuer = delegation_chain[-1]["issuer"]
    if session_peer_id != tail_issuer:
        raise DelegationBindingError(
            f"session peer {session_peer_id!r} is not the delegation tail issuer "
            f"{tail_issuer!r}; relayed credential treated as impersonation"
        )

    # 4. Authorize against origin grants ∩ chain tail scope -- NOT peer grants.
    effective = delegation_chain[-1]["scope"] & origin_grants
    if not effective:
        raise DelegationBindingError(
            "delegation chain tail intersects origin grants to empty set"
        )
    return effective
