"""Data plane SPI (ADR-0004). The control plane (``registry`` / ``api`` / ``retention`` / ``audit``) interacts with bytes exclusively through this surface. v1 ships an in-process implementation backed by a :class:`StorageBackend`; a future Rust daemon will satisfy the same protocol over a Unix socket. The SPI uses CBOR-serialisable input and output types so the in-process implementation and a future RPC implementation share representations. """ from __future__ import annotations from collections.abc import AsyncIterator from dataclasses import dataclass from typing import Protocol, runtime_checkable from artifactstore.identity import ContentAddress, Digest from artifactstore.storage.spi import BackendStatus, DeletionResult, StorageReceipt __all__ = [ "DataPlane", "IngestHints", "IngestResult", "VerifyResult", ] @dataclass(frozen=True, slots=True) class IngestHints: """Optional hints attached to an ingest call. ``size_hint`` lets backends pre-allocate or pick an upload strategy. ``primary_algorithm`` overrides the default primary hash (BLAKE3). ``backend_id`` lets the data plane select among configured backends (ignored when only one backend is configured). """ size_hint: int | None = None primary_algorithm: str | None = None backend_id: str | None = None @dataclass(frozen=True, slots=True) class IngestResult: """Return value of :meth:`DataPlane.ingest_stream`.""" primary_digest: Digest sha256_digest: Digest size_bytes: int receipt: StorageReceipt @dataclass(frozen=True, slots=True) class VerifyResult: """Return value of :meth:`DataPlane.verify_object`. ``verified`` is ``True`` when the bytes currently held at ``content_address`` re-digest to the expected primary digest. ``mismatch`` is populated with a short explanation when verification fails. """ content_address: ContentAddress verified: bool actual_primary_digest: Digest actual_sha256_digest: Digest actual_size_bytes: int mismatch: str | None = None @runtime_checkable class DataPlane(Protocol): """Byte-handling surface used by the control plane.""" async def ingest_stream( self, stream: AsyncIterator[bytes], *, hints: IngestHints | None = None, ) -> IngestResult: ... async def serve_object( self, content_address: ContentAddress, *, byte_range: tuple[int, int] | None = None, backend_id: str | None = None, ) -> AsyncIterator[bytes]: ... async def verify_object( self, content_address: ContentAddress, *, backend_id: str | None = None, ) -> VerifyResult: ... async def delete_object( self, content_address: ContentAddress, *, backend_id: str | None = None, ) -> DeletionResult: ... async def backend_health(self) -> BackendStatus: ...