# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-treat-vector-store-deletion-as-a-tombstone-rather-than-a-physical-erasure.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-treat-vector-store-deletion-as-a-tombstone-rather-than-a-physical-erasure.warrant.json
# Markdown SHA-256:   c3e0101ea09374afb6ec6b59183905cef4028a44772a2c77f13b0ef06848119b
# Code AST SHA-256:   62b92f291ea94dc8f4263e0e03a7033cad51f17b3fba9d6de2e37535db19451b
# Verified Classes:   ErasureIncompleteError, ErasureClass, DerivedArtifact, ErasureJob
# Defensive Guards:   2 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass, field
from enum import Enum


class ErasureIncompleteError(RuntimeError):
    """Raised when an erasure cannot be attested as physically complete."""
    pass


class ErasureClass(Enum):
    PHYSICAL = "physical"          # bytes unrecoverable (shard drop / key destruction)
    REBUILD_REQUIRED = "rebuild"   # only removable by index rebuild
    LOGICAL_ONLY = "logical"       # tombstoned; residual influence persists


@dataclass
class DerivedArtifact:
    name: str
    erasure_class: ErasureClass
    resolved: bool = False


@dataclass
class ErasureJob:
    subject_id: str
    artifacts: list[DerivedArtifact] = field(default_factory=list)

    def attest(self) -> str:
        unresolved = [a for a in self.artifacts if not a.resolved]
        logical_only = [
            a for a in self.artifacts
            if a.erasure_class is ErasureClass.LOGICAL_ONLY and a.resolved
        ]
        if unresolved:
            raise ErasureIncompleteError(
                f"subject {self.subject_id}: unresolved derived artifacts: "
                f"{[a.name for a in unresolved]}"
            )
        if logical_only:
            raise ErasureIncompleteError(
                f"subject {self.subject_id}: only logical deletion achieved for "
                f"{[a.name for a in logical_only]}; physical erasure not attestable. "
                f"Trigger index rebuild or per-subject shard drop."
            )
        digest = hashlib.sha256(self.subject_id.encode()).hexdigest()[:16]
        return f"erasure-attested:{digest}"


# Example: vector index tombstoned but not rebuilt -> attestation must fail.
job = ErasureJob(
    subject_id="subj-4471",
    artifacts=[
        DerivedArtifact("episodic_event_log", ErasureClass.PHYSICAL, resolved=True),
        DerivedArtifact("ann_index_topology", ErasureClass.LOGICAL_ONLY, resolved=True),
        DerivedArtifact("weekly_summary_cache", ErasureClass.REBUILD_REQUIRED, resolved=False),
    ],
)
job.attest()  # raises ErasureIncompleteError
