# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-fail-agent-card-discovery-when-the-orchestrator-negotiates-capabilities-before-resolving-the-well-known-uri.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-fail-agent-card-discovery-when-the-orchestrator-negotiates-capabilities-before-resolving-the-well-known-uri.warrant.json
# Markdown SHA-256:   f8c3a33930db1faf499cc7bb8a69e5bf222cb46690c8af7b753e3281b323fef0
# Code AST SHA-256:   e4a50429b1a4749d6975b3160449a5a83980c4d024ea10b2b7cf493eabdaa836
# Verified Classes:   AgentCardResolutionError, ResolvedTarget
# Defensive Guards:   5 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass
from typing import Optional


class AgentCardResolutionError(RuntimeError):
    """Raised when an Agent Card cannot be bound to a concrete dispatch target."""


@dataclass(frozen=True)
class ResolvedTarget:
    dispatch_url: str
    tenant_id: Optional[str]


def negotiate_capabilities(
    gateway_card: dict,
    resolve_target,
    fetch_card,
    required: dict,
) -> dict:
    """
    Enforce resolve -> fetch -> match. The gateway card is never used
    for capability matching; it is only a routing hint.
    """
    if not gateway_card.get("url"):
        raise AgentCardResolutionError(
            "gateway card has no dispatch url; cannot resolve target"
        )

    target: ResolvedTarget = resolve_target(gateway_card["url"])
    if not target or not target.dispatch_url:
        raise AgentCardResolutionError(
            f"resolution produced no dispatch url for {gateway_card['url']!r}"
        )

    resolved_card = fetch_card(target.dispatch_url)
    if resolved_card is None:
        raise AgentCardResolutionError(
            f"no Agent Card at resolved url {target.dispatch_url!r}; "
            "refusing to fall back to gateway union card"
        )

    if hashlib.sha256(resolved_card.get("url", "").encode()).hexdigest() != \
       hashlib.sha256(target.dispatch_url.encode()).hexdigest():
        raise AgentCardResolutionError(
            f"resolved card url {resolved_card.get('url')!r} does not match "
            f"dispatch target {target.dispatch_url!r}"
        )

    caps = resolved_card.get("capabilities", {})
    for key, want in required.items():
        if want and not caps.get(key, False):
            raise AgentCardResolutionError(
                f"resolved agent {target.dispatch_url!r} lacks required "
                f"capability {key!r} (card claims {caps.get(key)!r})"
            )

    return {"target": target, "card": resolved_card, "capabilities": caps}
