# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-forget-before-it-can-write-the-salience-ttl-erasure-ordering-trap.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-forget-before-it-can-write-the-salience-ttl-erasure-ordering-trap.warrant.json
# Markdown SHA-256:   5a0b4bf46dc9fbf3f0eb4b2d4c2905995156e1149dd8975510ff3974d116a604
# Code AST SHA-256:   2c61a9b778e883892a49973168f2f4af3b7f9e8780977e732779640014610467
# Verified Classes:   ErasureOrderingViolation, MemoryStore
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class ErasureOrderingViolation(RuntimeError):
    """Raised when a write would observe derived state from an erased subject."""
    pass


class MemoryStore:
    def __init__(self):
        self.records = {}
        self.salience = {}          # id -> score (derived)
        self.dedup = {}             # content_hash -> id (derived)
        self.embeddings = {}        # id -> vector (derived)
        self.tombstoned_subjects = set()
        self._lock = None           # conceptual; real impl uses a real lock

    def forget(self, subject_id: str) -> None:
        # Eagerly purge ALL derived state for the subject, then tombstone.
        ids = [rid for rid, rec in self.records.items()
               if rec["subject"] == subject_id]
        for rid in ids:
            rec = self.records.pop(rid)
            self.salience.pop(rid, None)
            self.embeddings.pop(rid, None)
            self.dedup.pop(rec["content_hash"], None)
        self.tombstoned_subjects.add(subject_id)

    def write(self, subject_id: str, content: str, content_hash: str,
              requested_salience: float) -> str:
        # Guard: refuse to write for a subject erased in this epoch, and
        # refuse to consume derived state that could carry erased provenance.
        if subject_id in self.tombstoned_subjects:
            raise ErasureOrderingViolation(
                f"write for erased subject {subject_id!r} rejected; "
                f"subject must be re-admitted explicitly before writing"
            )
        existing = self.dedup.get(content_hash)
        if existing is not None and existing not in self.records:
            # Stale dedup entry pointing at a purged record: this is exactly
            # the resurrection vector. Fail closed instead of merging.
            raise ErasureOrderingViolation(
                f"dedup entry {content_hash!r} references missing record "
                f"{existing!r}; derived state is inconsistent with primary "
                f"store and may encode erased data"
            )
        rid = f"r{len(self.records)}"
        self.records[rid] = {
            "subject": subject_id,
            "content": content,
            "content_hash": content_hash,
        }
        self.salience[rid] = requested_salience
        self.dedup[content_hash] = rid
        return rid
