# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-a-stale-agent-card-during-a2a-v1-0-cross-vendor-task-delegation.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-a-stale-agent-card-during-a2a-v1-0-cross-vendor-task-delegation.warrant.json
# Markdown SHA-256:   8aac063a7dd7ea25d9db1de9cbd10bad0d3090e4e859e33e642287363dff6d4b
# Code AST SHA-256:   2c8923d12e6da0c5d5fc25920b25976a3eadf09549e8608a69b837d21e5a77a3
# Verified Classes:   StaleAgentCardError
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class StaleAgentCardError(RuntimeError):
    """Raised when a cached A2A Agent Card no longer matches remote runtime behavior."""
    pass


def delegate_with_card_revalidation(client, remote_url, cached_card, task_envelope):
    """
    Attempt delegation against a cached card. On a capability-class rejection,
    refetch the card once and refuse to retry blindly if it is unchanged.
    """
    try:
        return client.send_task(remote_url, cached_card, task_envelope)
    except client.CapabilityRejected as first_err:
        # Transport failures are handled elsewhere; this path is protocol/capability only.
        fresh_card = client.fetch_agent_card(remote_url)

        if fresh_card.canonical_bytes() == cached_card.canonical_bytes():
            raise StaleAgentCardError(
                f"Card at {remote_url} is unchanged after refetch, yet the remote "
                f"rejected skillId={task_envelope.get('skillId')!r}. "
                f"This is remote-side card drift, not a client cache problem. "
                f"Original error: {first_err}"
            ) from first_err

        changed_skills = (
            {s["id"] for s in cached_card.skills}
            ^ {s["id"] for s in fresh_card.skills}
        )
        if task_envelope.get("skillId") not in {s["id"] for s in fresh_card.skills}:
            raise StaleAgentCardError(
                f"Skill {task_envelope.get('skillId')!r} no longer advertised by "
                f"{remote_url}; changed skill IDs: {sorted(changed_skills)}. "
                f"Refusing to replay against a stale contract."
            ) from first_err

        # Card genuinely changed and the skill still exists: one bounded retry.
        return client.send_task(remote_url, fresh_card, task_envelope)
