# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-advertises-a-taskstate-transition-it-cannot-durably-commit-the-a2a-v1-0-agent-card-task-lifecycle-artifact-negotiation-s.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-advertises-a-taskstate-transition-it-cannot-durably-commit-the-a2a-v1-0-agent-card-task-lifecycle-artifact-negotiation-s.warrant.json
# Markdown SHA-256:   ece3406f4a006a5310014200ad18fe49b28e74d4b2593104481445a410775da3
# Code AST SHA-256:   853e1445589e28e42a1f2cd09503df661a998e77c8e3703added9821cc072114
# Verified Classes:   TaskState, CapabilityContractError, AgentCard, VerifiedCapabilities, TaskRecord
# Defensive Guards:   5 fail-closed invariants (zero external deps)

from __future__ import annotations

import enum
from dataclasses import dataclass, field


class TaskState(enum.Enum):
    SUBMITTED = "submitted"
    WORKING = "working"
    INPUT_REQUIRED = "input-required"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELED = "canceled"


TERMINAL = {TaskState.COMPLETED, TaskState.FAILED, TaskState.CANCELED}


class CapabilityContractError(RuntimeError):
    """Raised when emitted state or artifact violates the Agent Card contract,
    or when a TaskNotFound is observed for a non-terminal task and cannot be
    reconciled against a verified-capability worker."""


@dataclass
class AgentCard:
    advertised_states: frozenset[TaskState]
    output_modes: frozenset[str]
    cache_control_seconds: int = 300


@dataclass
class VerifiedCapabilities:
    """What we actually confirmed by probing, not what the card claimed."""
    reachable_states: frozenset[TaskState]
    emitting_modes: frozenset[str]


@dataclass
class TaskRecord:
    correlation_id: str
    server_task_id: str | None
    last_observed: TaskState
    artifacts: list[tuple[str, str]] = field(default_factory=list)  # (correlation_id, mime)


def emit_artifact(card: AgentCard, verified: VerifiedCapabilities,
                  record: TaskRecord, mime: str, payload: bytes) -> None:
    """Emitter-side enforcement: the artifact we are about to emit must be
    inside both the advertised and the verified output modes."""
    if mime not in card.output_modes:
        raise CapabilityContractError(
            f"emitting {mime!r} outside advertised output_modes {sorted(card.output_modes)}"
        )
    if mime not in verified.emitting_modes:
        raise CapabilityContractError(
            f"emitting {mime!r} outside *verified* emitting modes {sorted(verified.emitting_modes)}"
        )
    record.artifacts.append((record.correlation_id, mime))


def transition(record: TaskRecord, new_state: TaskState,
               card: AgentCard, verified: VerifiedCapabilities) -> None:
    """Every transition must be inside the advertised AND verified reachable set."""
    if new_state not in card.advertised_states:
        raise CapabilityContractError(
            f"transition to {new_state.value} not advertised by card"
        )
    if new_state not in verified.reachable_states:
        raise CapabilityContractError(
            f"transition to {new_state.value} not verified reachable; "
            f"card/implementation divergence"
        )
    record.last_observed = new_state


def handle_task_not_found(record: TaskRecord,
                          candidates: dict[str, VerifiedCapabilities]) -> str:
    """TaskNotFound is INDETERMINATE for non-terminal tasks. Reconcile by
    correlation_id against workers whose verified set contains the last
    observed state. Never mark failed on TaskNotFound alone."""
    if record.last_observed in TERMINAL:
        return "terminal-ok"
    eligible = [
        wid for wid, vc in candidates.items()
        if record.last_observed in vc.reachable_states
    ]
    if not eligible:
        raise CapabilityContractError(
            f"TaskNotFound for non-terminal task {record.correlation_id} "
            f"in state {record.last_observed.value}; no worker verifies that "
            f"state as reachable. Cannot reconcile; do NOT mark failed."
        )
    # Re-dispatch using correlation_id as the durable key, not server_task_id.
    return eligible[0]
