# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-trusts-a-manifest-s-declared-capability-surface-over-its-runtime-observed-tool-bindings.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-trusts-a-manifest-s-declared-capability-surface-over-its-runtime-observed-tool-bindings.warrant.json
# Markdown SHA-256:   cbd7b9a364368e8109b56d621099dc59db93d0c6c0dd63b16969ff4cce79cd46
# Code AST SHA-256:   f9a2399e13292e24611e64182276c5ea9ec666e728af6aaea5bcfc6f896cb30c
# Verified Classes:   CapabilityBindingMismatch, Manifest, Binding
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from __future__ import annotations

from dataclasses import dataclass


class CapabilityBindingMismatch(RuntimeError):
    """Raised when a resolved tool handler's attested capabilities are not a
    subset of the manifest-declared surface that the caller was authorized for."""


@dataclass(frozen=True)
class Manifest:
    server_id: str
    tool_name: str
    declared_capabilities: frozenset[str]
    digest: str


@dataclass(frozen=True)
class Binding:
    server_id: str
    tool_name: str
    attested_capabilities: frozenset[str]
    digest: str


def authorize_dispatch(
    manifest: Manifest,
    binding: Binding,
    caller_grant: frozenset[str],
) -> None:
    # Identity must match on the full tuple, never on tool_name alone.
    if (manifest.server_id, manifest.tool_name, manifest.digest) != (
        binding.server_id,
        binding.tool_name,
        binding.digest,
    ):
        raise CapabilityBindingMismatch(
            f"identity divergence: manifest={(manifest.server_id, manifest.tool_name, manifest.digest)} "
            f"binding={(binding.server_id, binding.tool_name, binding.digest)}"
        )

    # The binding must not exceed what the manifest advertised.
    if not binding.attested_capabilities <= manifest.declared_capabilities:
        extra = binding.attested_capabilities - manifest.declared_capabilities
        raise CapabilityBindingMismatch(
            f"handler {binding.server_id}/{binding.tool_name} attests undeclared "
            f"capabilities {sorted(extra)}"
        )

    # The call must not exceed what the caller was granted.
    if not binding.attested_capabilities <= caller_grant:
        raise CapabilityBindingMismatch(
            f"caller grant insufficient for {binding.server_id}/{binding.tool_name}: "
            f"needs {sorted(binding.attested_capabilities - caller_grant)}"
        )
