# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-reconcile-the-a2a-v1-0-task-lifecycle-with-webhook-signing-when-a-taskstatusupdateevent-and-a-taskartifactupdateevent-race-acro.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-reconcile-the-a2a-v1-0-task-lifecycle-with-webhook-signing-when-a-taskstatusupdateevent-and-a-taskartifactupdateevent-race-acro.warrant.json
# Markdown SHA-256:   9d685101d6ae69b7fa81ea1f5e77c121ce96484e51f45e1c29b7d2d226d2652e
# Code AST SHA-256:   17fbe19122651620ae5736ec42d0b0efd5798ba9aa495a797c588cf0035b9f41
# Verified Classes:   LateDeliveryOutsideHorizon, SignatureVerificationError, PushDelivery, TaskRecord, PushReconciler
# Defensive Guards:   2 fail-closed invariants (zero external deps)

import hmac
import hashlib
import time
from dataclasses import dataclass, field
from typing import Literal

TaskState = Literal["submitted", "working", "input-required", "completed", "failed", "canceled"]
TERMINAL_STATES = frozenset({"completed", "failed", "canceled"})


class LateDeliveryOutsideHorizon(RuntimeError):
    """A signature-valid event arrived after the reconciliation horizon for a terminal task."""
    pass


class SignatureVerificationError(RuntimeError):
    """Webhook HMAC did not match; must be distinguished from lifecycle rejection."""
    pass


@dataclass
class PushDelivery:
    task_id: str
    event_type: Literal["status", "artifact"]
    state: TaskState | None
    payload: bytes
    signature: str
    timestamp: int


@dataclass
class TaskRecord:
    state: TaskState = "submitted"
    terminal_at: float | None = None
    late_buffer: list[PushDelivery] = field(default_factory=list)


class PushReconciler:
    def __init__(self, secret: bytes, horizon_seconds: float):
        self._secret = secret
        self._horizon = horizon_seconds
        self._tasks: dict[str, TaskRecord] = {}

    def _verify(self, d: PushDelivery) -> None:
        expected = hmac.new(
            self._secret,
            f"{d.timestamp}.".encode() + d.payload,
            hashlib.sha256,
        ).hexdigest()
        if not hmac.compare_digest(expected, d.signature):
            raise SignatureVerificationError(f"bad signature for task {d.task_id}")

    def ingest(self, d: PushDelivery, now: float | None = None) -> str:
        now = time.monotonic() if now is None else now
        self._verify(d)  # gate 1: integrity, always first

        rec = self._tasks.setdefault(d.task_id, TaskRecord())

        if rec.state in TERMINAL_STATES:
            # gate 2: lifecycle reconciliation within horizon
            if rec.terminal_at is not None and now - rec.terminal_at > self._horizon:
                raise LateDeliveryOutsideHorizon(
                    f"task {d.task_id} terminal since {rec.terminal_at:.3f}, "
                    f"event {d.event_type} arrived at {now:.3f} beyond horizon {self._horizon}s"
                )
            rec.late_buffer.append(d)
            return "buffered"

        if d.event_type == "status" and d.state is not None:
            rec.state = d.state
            if d.state in TERMINAL_STATES:
                rec.terminal_at = now
        return "applied"

    def reconcile_expired(self, now: float | None = None) -> dict[str, list[PushDelivery]]:
        now = time.monotonic() if now is None else now
        out: dict[str, list[PushDelivery]] = {}
        for tid, rec in self._tasks.items():
            if rec.terminal_at is None or now - rec.terminal_at <= self._horizon:
                continue
            if rec.state == "completed":
                out[tid] = list(rec.late_buffer)  # commit artifacts to closed task
            rec.late_buffer.clear()
        return out
