# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-deletes-a-memory-record-whose-provenance-is-still-referenced-by-a-retained-derivative.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-deletes-a-memory-record-whose-provenance-is-still-referenced-by-a-retained-derivative.warrant.json
# Markdown SHA-256:   88a67602cb147c980a6d7559fb80aaeb481db5e9caa18ecdd8bb06253edf902d
# Code AST SHA-256:   1ceab823b9b9e75a479776d9b32498eb950bf5c86540f8e6ed17384e677ac26b
# Verified Classes:   ProvenanceResolutionError, UnsafeForgetError, MemoryRecord, MemoryStore
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Optional


class ProvenanceResolutionError(RuntimeError):
    """Raised when a recall result cites a source that no longer resolves."""
    pass


class UnsafeForgetError(RuntimeError):
    """Raised when forget is attempted on a record with live dependents."""
    pass


@dataclass
class MemoryRecord:
    id: str
    content_hash: str
    provenance: list[str] = field(default_factory=list)  # source_ids
    confidence: float = 1.0


class MemoryStore:
    def __init__(self) -> None:
        self._records: dict[str, MemoryRecord] = {}
        self._dependents: dict[str, set[str]] = {}  # source_id -> {dependent_id}

    def write(self, rec: MemoryRecord) -> None:
        self._records[rec.id] = rec
        for src in rec.provenance:
            self._dependents.setdefault(src, set()).add(rec.id)

    def forget(self, record_id: str, cascade: bool = False) -> None:
        deps = self._dependents.get(record_id, set())
        if deps and not cascade:
            raise UnsafeForgetError(
                f"refusing to forget {record_id!r}: live dependents {sorted(deps)}; "
                f"pass cascade=True to invalidate them"
            )
        self._records.pop(record_id, None)
        if cascade:
            for dep in deps:
                rec = self._records.get(dep)
                if rec is not None:
                    rec.provenance = [p for p in rec.provenance if p != record_id]
                    rec.confidence = min(rec.confidence, 0.5)
        self._dependents.pop(record_id, None)

    def recall(self, record_id: str, require_resolution: bool = True) -> MemoryRecord:
        rec = self._records.get(record_id)
        if rec is None:
            raise ProvenanceResolutionError(f"record {record_id!r} not found")
        missing = [p for p in rec.provenance if p not in self._records]
        if missing and require_resolution:
            raise ProvenanceResolutionError(
                f"record {record_id!r} cites unresolvable sources {missing}; "
                f"confidence {rec.confidence} is not trustworthy"
            )
        return rec
