# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-silent-resurrection-of-forgotten-memory-entries-under-scoped-retention.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-silent-resurrection-of-forgotten-memory-entries-under-scoped-retention.warrant.json
# Markdown SHA-256:   d04be13ac56d9d90d32595f2616d143efc74e983a3b551641468f5d6cc127dc1
# Code AST SHA-256:   2c24324ad57acf31865068a472ec5be0c8633addd76cb9eef1a2d204da126d61
# Verified Classes:   IncompleteDeletionProofError, ResurrectionDetectedError, DeletionProof, MemoryStore
# Defensive Guards:   2 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass, field
from typing import Iterable


class IncompleteDeletionProofError(RuntimeError):
    """Raised when a deletion proof does not cover the derived-artifact closure."""


class ResurrectionDetectedError(RuntimeError):
    """Raised when a forgotten entry's canary reappears at a superscope."""


@dataclass
class DeletionProof:
    entry_id: str
    scope: str
    coverage_set: frozenset[str]
    tombstone_hash: str


@dataclass
class MemoryStore:
    # scope -> {entry_id -> content}
    primary: dict[str, dict[str, str]] = field(default_factory=dict)
    # artifact_name -> {entry_id -> content}
    derived: dict[str, dict[str, str]] = field(default_factory=dict)
    tombstones: set[str] = field(default_factory=set)

    def derived_closure(self, entry_id: str) -> frozenset[str]:
        return frozenset(
            name for name, arts in self.derived.items() if entry_id in arts
        )

    def forget(self, entry_id: str, scope: str) -> DeletionProof:
        closure = self.derived_closure(entry_id)
        if not closure:
            # No derived artifacts: proof over primary store is complete.
            self.primary.get(scope, {}).pop(entry_id, None)
            self.tombstones.add(entry_id)
            return DeletionProof(
                entry_id=entry_id,
                scope=scope,
                coverage_set=frozenset({f"primary:{scope}"}),
                tombstone_hash=hashlib.sha256(entry_id.encode()).hexdigest(),
            )
        # Derived artifacts exist. A proof over primary alone is incomplete.
        raise IncompleteDeletionProofError(
            f"forget({entry_id!r}, scope={scope!r}) has derived closure "
            f"{sorted(closure)}; proof must cover these or be reported as "
            f"'retracted, not deleted'."
        )

    def resurrection_probe(self, entry_id: str, superscopes: Iterable[str]) -> None:
        canary = hashlib.sha256(entry_id.encode()).hexdigest()
        for scope in superscopes:
            for name, arts in self.derived.items():
                if entry_id in arts:
                    raise ResurrectionDetectedError(
                        f"entry {entry_id!r} resurrected via artifact {name!r} "
                        f"at superscope {scope!r} (canary {canary[:12]}...)"
                    )


def safe_forget(store: MemoryStore, entry_id: str, scope: str) -> DeletionProof:
    proof = store.forget(entry_id, scope)
    store.resurrection_probe(entry_id, superscopes=[scope, f"{scope}:global"])
    return proof
