# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/where-must-an-instance-of-a-memory-record-exist-for-a-forget-to-be-safe-the-tombstone-before-index-invariant-in-governed-deletion.md
# Remnant Warrant:    https://corpusfetch.com/c/where-must-an-instance-of-a-memory-record-exist-for-a-forget-to-be-safe-the-tombstone-before-index-invariant-in-governed-deletion.warrant.json
# Markdown SHA-256:   eefbc092b924bdc4b75e8bec537ac0b378dcd349217491529162ac9fca4364af
# Code AST SHA-256:   60b46815a0ff66d8f46a5c8b0ba4aafd4e226f1ae9a108c55ae0ab0e3e411646
# Verified Classes:   ForgetOrderingError, MemoryStore
# Defensive Guards:   3 fail-closed invariants (zero external deps)

class ForgetOrderingError(RuntimeError):
    """Raised when a forget would remove a source before its tombstone is durable."""
    pass


class MemoryStore:
    def __init__(self, tombstone_log, source_store, projections):
        self.tombstones = tombstone_log      # durable, append-only, read by all readers
        self.sources = source_store
        self.projections = projections       # embeddings, summaries, caches

    def forget(self, record_id: str) -> None:
        # Invariant: tombstone must be durable BEFORE any projection is touched.
        if not self.tombstones.contains(record_id):
            self.tombstones.append(record_id)  # first write on the critical path

        if not self.tombstones.contains(record_id):
            # Durability of the tombstone could not be confirmed; refuse to proceed.
            raise ForgetOrderingError(
                f"tombstone for {record_id!r} not durable; refusing to delete projections"
            )

        # Only now is it safe to remove derived artifacts and the source.
        self.projections.invalidate_all_for(record_id)
        self.sources.delete(record_id)
        # Idempotent: a retried forget re-confirms the tombstone and no-ops.

    def read_projection(self, projection_id: str):
        provenance = self.projections.provenance_of(projection_id)
        if provenance is None:
            # Unattributable projection: quarantine, never re-derive.
            raise ForgetOrderingError(
                f"projection {projection_id!r} has no provenance; quarantined, not served"
            )
        if self.tombstones.contains(provenance.record_id):
            raise ForgetOrderingError(
                f"projection {projection_id!r} belongs to forgotten record "
                f"{provenance.record_id!r}; must not be served or promoted"
            )
        return self.projections.fetch(projection_id)
