# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-reject-an-a2a-v1-0-agent-card-whose-capabilities-pushnotifications-flag-is-true-but-whose-webhook-binding-canno.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-reject-an-a2a-v1-0-agent-card-whose-capabilities-pushnotifications-flag-is-true-but-whose-webhook-binding-canno.warrant.json
# Markdown SHA-256:   22a2b835c12139212d442509371fdba7c90c7a9bd510c67ffc1c0d85670a62f7
# Code AST SHA-256:   64d295a2e83c7b1c05b3376c7ac46196d83500639e692de2e06e9aa57ab4ca74
# Verified Classes:   PushBindingUnverifiedError, AgentCard, PushNotificationConfig, PushConfigAck
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Optional


class PushBindingUnverifiedError(RuntimeError):
    """Raised when an A2A agent card advertises pushNotifications but the
    webhook binding cannot be authenticated/acknowledged at negotiation time."""


@dataclass
class AgentCard:
    name: str
    push_notifications: bool
    version: str = "1.0"


@dataclass
class PushNotificationConfig:
    url: str
    token: str
    auth_scheme: str  # scheme the CLIENT will accept on inbound POSTs


@dataclass
class PushConfigAck:
    echoed_scheme: Optional[str]
    echoed_url: Optional[str]


def negotiate_push_binding(
    card: AgentCard,
    config: PushNotificationConfig,
    ack: Optional[PushConfigAck],
    has_polling_fallback: bool,
) -> str:
    """
    Decide whether to bind push or degrade to polling.

    Returns "push" or "polling". Raises PushBindingUnverifiedError only when
    the caller has no safe degradation path and still attempts push.
    """
    if not card.push_notifications:
        return "polling"

    if ack is None:
        if not has_polling_fallback:
            raise PushBindingUnverifiedError(
                f"agent {card.name!r} advertises push but produced no "
                f"PushNotificationConfig ack; no polling fallback configured"
            )
        return "polling"

    scheme_mismatch = ack.echoed_scheme != config.auth_scheme
    url_rewritten = ack.echoed_url is not None and ack.echoed_url != config.url

    if scheme_mismatch or url_rewritten:
        if not has_polling_fallback:
            raise PushBindingUnverifiedError(
                f"agent {card.name!r} rewrote push binding "
                f"(scheme {config.auth_scheme!r} -> {ack.echoed_scheme!r}, "
                f"url_rewritten={url_rewritten}); no polling fallback configured"
            )
        return "polling"

    return "push"
