# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/where-must-an-instance-of-an-a2a-v1-0-agent-card-be-treated-as-authoritative-and-where-must-it-be-treated-as-a-hint-a-boundary-failure-in-cross-vendor.md
# Remnant Warrant:    https://corpusfetch.com/c/where-must-an-instance-of-an-a2a-v1-0-agent-card-be-treated-as-authoritative-and-where-must-it-be-treated-as-a-hint-a-boundary-failure-in-cross-vendor.warrant.json
# Markdown SHA-256:   563d20a42e133f9d98a604006171a8fd87bf02837680af2b1d68b5500bc4b895
# Code AST SHA-256:   83beb36e7395eb801eea43839fc04ea59bcfdfd648c8867362996f87eb115c87
# Verified Classes:   StaleAgentCardError, AgentCardClaim, ProbeResult
# Defensive Guards:   4 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass
from typing import Optional


class StaleAgentCardError(RuntimeError):
    """Raised when an Agent Card's claims diverge from the live JSON-RPC endpoint.

    This is a *routing* signal, not a fatal error. Callers must re-fetch the
    card with cache bypass and re-route, not escalate to a human.
    """


@dataclass(frozen=True)
class AgentCardClaim:
    url: str
    skill_id: str
    region_hint: Optional[str]
    fetched_at: float  # monotonic seconds
    cache_ttl_s: float = 86400.0


@dataclass(frozen=True)
class ProbeResult:
    endpoint_alive: bool
    task_lifecycle_ok: bool
    error_code: Optional[int]


def probe_endpoint(card: AgentCardClaim, rpc_call) -> ProbeResult:
    """Issue a side-effect-free tasks/send probe. Returns liveness, not skill validity."""
    resp = rpc_call(card.url, method="tasks/send", params={
        "message": {"role": "user", "parts": [{"type": "text", "text": "__probe__"}]},
        "metadata": {"a2a_probe": True},
    })
    if resp.get("error"):
        code = resp["error"].get("code")
        # -32001 TaskNotFound and -32004 SkillUnavailable both prove the endpoint
        # speaks the task lifecycle; -32601/-32602 prove it does not.
        lifecycle_ok = code in (-32001, -32004)
        return ProbeResult(endpoint_alive=True, task_lifecycle_ok=lifecycle_ok, error_code=code)
    task_id = resp.get("result", {}).get("id")
    if task_id:
        rpc_call(card.url, method="tasks/cancel", params={"id": task_id})
    return ProbeResult(endpoint_alive=True, task_lifecycle_ok=True, error_code=None)


def delegate(card: AgentCardClaim, real_task: dict, rpc_call) -> dict:
    """Delegate a real task, treating the card as a hint until proven otherwise."""
    if time.monotonic() - card.fetched_at > 300.0:
        raise StaleAgentCardError(
            f"card for {card.url} is {time.monotonic() - card.fetched_at:.0f}s old; "
            f"re-fetch with Cache-Control: no-cache before delegating skill {card.skill_id!r}"
        )

    probe = probe_endpoint(card, rpc_call)
    if not probe.task_lifecycle_ok:
        raise StaleAgentCardError(
            f"endpoint {card.url} answered probe with error code {probe.error_code}; "
            f"card claims skill {card.skill_id!r} but endpoint does not implement task lifecycle"
        )

    resp = rpc_call(card.url, method="tasks/send", params=real_task)
    if resp.get("error"):
        code = resp["error"].get("code")
        if code == -32004:
            data = resp["error"].get("data", {})
            raise StaleAgentCardError(
                f"skill {card.skill_id!r} unavailable at {card.url} "
                f"(region={data.get('region')}, available={data.get('availableSkills')}); "
                f"card is stale for this region — invalidate cache and re-route"
            )
        raise RuntimeError(f"delegation failed with JSON-RPC error {code}: {resp['error']}")
    return resp["result"]
