# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-unpropagated-erasure-requests-in-memory-forget-tool-compliance-auditing.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-unpropagated-erasure-requests-in-memory-forget-tool-compliance-auditing.warrant.json
# Markdown SHA-256:   72ae327fa6fae5c608eebc81870aa5e2c8593a3c336c46d9f10428ba0a18772b
# Code AST SHA-256:   0e97210e00b4a941210c54a417b9b4bb16211707bb97e828a39881f0bf8588de
# Verified Classes:   ErasureAuditError, ErasureStatus, ForgetClaim, Store
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from enum import Enum


class ErasureAuditError(RuntimeError):
    """Raised when a forget-tool claim cannot be verified against all surfaces."""
    pass


class ErasureStatus(Enum):
    COMPLIANT = "compliant"
    PARTIAL = "partial"
    UNCONFIRMED = "unconfirmed"


@dataclass
class ForgetClaim:
    subject_id: str
    surfaces_reached: list[str]
    tombstone_written: bool


@dataclass
class Store:
    primary: dict = field(default_factory=dict)
    derived: dict = field(default_factory=dict)   # derived_id -> (source_id, text)
    embeddings: dict = field(default_factory=dict)  # content_hash -> vector
    tombstones: set = field(default_factory=set)


def audit_erasure(store: Store, claim: ForgetClaim, probe_tokens: list[str]) -> ErasureStatus:
    """Verify a forget claim against every persistence surface, not just the tool's own."""

    # 1. Primary surface must be clear.
    if claim.subject_id in store.primary:
        raise ErasureAuditError(
            f"primary still holds subject {claim.subject_id}; tool claim is false"
        )

    # 2. Derived surfaces reachable by provenance must be gone.
    leaked = [
        did for did, (src, _text) in store.derived.items()
        if src == claim.subject_id
    ]
    if leaked:
        # Propagation failure: tool never touched derived records.
        return ErasureStatus.PARTIAL

    # 3. Resurrection check: deleted content must not be re-ingestible.
    for token in probe_tokens:
        h = hash(token)
        if h in store.embeddings and token not in store.tombstones:
            raise ErasureAuditError(
                f"content token {token!r} remains re-ingestible; "
                f"no tombstone guards resurrection"
            )

    # 4. A compliant claim must name surfaces and write a tombstone.
    if not claim.tombstone_written:
        return ErasureStatus.UNCONFIRMED
    if not claim.surfaces_reached:
        raise ErasureAuditError(
            "forget tool returned success without enumerating any surface"
        )

    return ErasureStatus.COMPLIANT


def forget(store: Store, subject_id: str) -> ForgetClaim:
    """Minimal forget tool: deletes primary only, writes tombstone."""
    if subject_id not in store.primary:
        # Idempotent re-delete is fine, but do not invent a claim.
        return ForgetClaim(subject_id, surfaces_reached=[], tombstone_written=False)
    store.primary.pop(subject_id)
    store.tombstones.add(subject_id)
    return ForgetClaim(subject_id, surfaces_reached=["primary"], tombstone_written=True)
