# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/where-must-an-instance-distinguish-stream-complete-from-artifact-complete-in-a2a-v1-0-partial-result-exchange.md
# Remnant Warrant:    https://corpusfetch.com/c/where-must-an-instance-distinguish-stream-complete-from-artifact-complete-in-a2a-v1-0-partial-result-exchange.warrant.json
# Markdown SHA-256:   f4cddb77d6ae7fe20c2c86acb76eabb0aee0fb5e89a33cfbacd805414be80b13
# Code AST SHA-256:   31a2053f1673eb7fa9b2d369b155d834044818154e32c3e92b05052948f07e28
# Verified Classes:   ArtifactIncompleteError, ArtifactBuffer, ArtifactAssembler
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Dict, List, Optional


class ArtifactIncompleteError(RuntimeError):
    """Raised when an artifact is flushed without a lastChunk marker."""
    pass


@dataclass
class ArtifactBuffer:
    artifact_id: str
    chunks: List[str] = field(default_factory=list)
    last_chunk_seen: bool = False


class ArtifactAssembler:
    def __init__(self) -> None:
        self._buffers: Dict[str, ArtifactBuffer] = {}

    def on_chunk(self, artifact_id: str, text: str, last_chunk: bool) -> None:
        buf = self._buffers.setdefault(artifact_id, ArtifactBuffer(artifact_id))
        buf.chunks.append(text)
        if last_chunk:
            buf.last_chunk_seen = True

    def finalize_on_stream_close(self, task_final: bool) -> List[str]:
        """Return only artifacts safe to emit downstream; reject the rest."""
        ready: List[str] = []
        for buf in self._buffers.values():
            if buf.last_chunk_seen:
                ready.append("".join(buf.chunks))
            elif task_final:
                # Server asserted task completion without lastChunk; accept with warning.
                ready.append("".join(buf.chunks))
            else:
                raise ArtifactIncompleteError(
                    f"artifact {buf.artifact_id} closed without lastChunk "
                    f"and task not final; buffer is resumable, not deliverable"
                )
        return ready
