# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-treat-a2a-v1-0-agent-card-discovery-as-a-transaction-boundary-rather-than-a-lookup.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-treat-a2a-v1-0-agent-card-discovery-as-a-transaction-boundary-rather-than-a-lookup.warrant.json
# Markdown SHA-256:   929b035b70bf15dfd13b3bf1a375ecd0dc49d10a9056441772de0e2e59223401
# Code AST SHA-256:   b2d8c1c49225433a354f780491cb1dffe11222b22cd9977d5ec09fbbc9b9251d
# Verified Classes:   CardGenerationMismatch, TaskNotFoundAfterRotation
# Defensive Guards:   4 fail-closed invariants (zero external deps)

class CardGenerationMismatch(RuntimeError):
    """Raised when a cached Agent Card generation no longer matches the task's owning generation."""
    pass


class TaskNotFoundAfterRotation(RuntimeError):
    """Raised when a task ID is absent on the current card generation (did not survive rotation)."""
    pass


def bind_webhook_for_task(client, cached_card, task_id):
    # Re-resolve the card conditionally; do not trust the cache across a transition.
    fresh_card = client.get_agent_card(if_none_match=cached_card.etag)
    if fresh_card.etag != cached_card.etag:
        # Card rotated. The task may or may not have survived.
        task = client.get_task(task_id)
        if task is None:
            raise TaskNotFoundAfterRotation(
                f"task {task_id} absent on card generation {fresh_card.etag}; "
                f"cached generation was {cached_card.etag} — re-submit with new ID"
            )
        if task.card_generation != fresh_card.etag:
            raise CardGenerationMismatch(
                f"task {task_id} owned by generation {task.card_generation}, "
                f"current card generation is {fresh_card.etag}"
            )
        # Rebind from the task's own pushNotificationConfig, not the card.
        return client.set_push_notification(task_id, task.push_notification_config)

    # Card unchanged: still bind from the task object, never from the card's block.
    task = client.get_task(task_id)
    if task is None:
        raise TaskNotFoundAfterRotation(
            f"task {task_id} not found despite unchanged card {cached_card.etag}"
        )
    if not fresh_card.capabilities.push_notifications:
        raise CardGenerationMismatch(
            f"card {fresh_card.etag} no longer advertises pushNotifications"
        )
    return client.set_push_notification(task_id, task.push_notification_config)
