# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-refuse-to-acknowledge-an-a2a-task-update-before-the-agent-card-s-capabilities-pushnotifications-flag-has-been-re-ve.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-refuse-to-acknowledge-an-a2a-task-update-before-the-agent-card-s-capabilities-pushnotifications-flag-has-been-re-ve.warrant.json
# Markdown SHA-256:   4166a5908b496edee0ff26c80b23c1f23344bd1e8b05019c2cc8e14e991cf8ce
# Code AST SHA-256:   aa040d7375f7b623f6ea359cbde0e9791f60b46eaf3d3f067ac909574c2dfaca
# Verified Classes:   StaleAgentCardCapabilityError, AgentCardSnapshot, TaskRecord, A2AClientGuard
# Defensive Guards:   2 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass, field
from typing import Optional


class StaleAgentCardCapabilityError(RuntimeError):
    """Raised when a task transition depends on a capability the live Agent Card no longer grants."""


@dataclass
class AgentCardSnapshot:
    version: str
    push_notifications: bool
    streaming: bool
    fetched_at: float


@dataclass
class TaskRecord:
    task_id: str
    context_id: str
    state: str
    card_version_at_creation: str
    webhook_registered: bool = False
    awaiting_push: bool = False


class A2AClientGuard:
    def __init__(self, discovery_fn, max_card_age_s: float = 60.0):
        # discovery_fn() -> AgentCardSnapshot, performs live GET /.well-known/agent.json
        self._discovery_fn = discovery_fn
        self._max_card_age_s = max_card_age_s
        self._cached: Optional[AgentCardSnapshot] = None

    def _live_card(self, force: bool = False) -> AgentCardSnapshot:
        now = time.monotonic()
        if (
            force
            or self._cached is None
            or (now - self._cached.fetched_at) > self._max_card_age_s
        ):
            self._cached = self._discovery_fn()
        return self._cached

    def advance_to_working(self, task: TaskRecord, use_push: bool) -> TaskRecord:
        """Guard the submitted -> working transition when push is the intended wakeup path."""
        if use_push:
            live = self._live_card(force=True)
            if not live.push_notifications:
                raise StaleAgentCardCapabilityError(
                    f"task {task.task_id}: live Agent Card v{live.version} "
                    f"revoked pushNotifications; cached v{task.card_version_at_creation} "
                    f"still advertised it. Fall back to polling."
                )
            if live.version != task.card_version_at_creation:
                # Capability surface changed under us; re-register the webhook against the new card.
                task.webhook_registered = False
            task.awaiting_push = True
        task.state = "working"
        return task

    def register_webhook(self, task: TaskRecord, register_fn) -> TaskRecord:
        """Registration is provisional until first delivery is observed."""
        live = self._live_card(force=True)
        if not live.push_notifications:
            raise StaleAgentCardCapabilityError(
                f"task {task.task_id}: refusing webhook registration, "
                f"live card v{live.version} has pushNotifications=false"
            )
        register_fn(task.task_id)
        task.webhook_registered = True
        return task
