# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-silent-resurrection-of-gdpr-erased-memory-records-under-scoped-retention-policies.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-silent-resurrection-of-gdpr-erased-memory-records-under-scoped-retention-policies.warrant.json
# Markdown SHA-256:   f82da7f58d004d8e3a509141f747e4114f85f4881abcbc4d498e9f77dbbb8102
# Code AST SHA-256:   1a6ec9e644687696963cda9ff087a512e4392dd5ab35690c84258014f5ba4fdb
# Verified Classes:   ErasureLineageViolation, ErasureLedger
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class ErasureLineageViolation(RuntimeError):
    """Raised when a read or derivation path encounters content whose
    erasure lineage is active but not honored by the caller."""
    pass


class ErasureLedger:
    def __init__(self):
        self._tombstones: set[tuple[str, str]] = set()

    def forget(self, scope: str, record_id: str) -> None:
        self._tombstones.add((scope, record_id))

    def is_erased(self, scope: str, record_id: str) -> bool:
        return (scope, record_id) in self._tombstones


def derive_profile(ledger: ErasureLedger, source_scope: str,
                   source_id: str, content: str) -> dict:
    # A derivation must refuse to run if its source carries an active
    # erasure tombstone; silently skipping would let the erased content
    # propagate through a different code path.
    if ledger.is_erased(source_scope, source_id):
        raise ErasureLineageViolation(
            f"refusing to derive from erased source "
            f"{source_scope}/{source_id}"
        )
    return {"derived_from": (source_scope, source_id), "text": content}


def read_project_record(ledger: ErasureLedger, record: dict) -> str:
    # Project-scoped reads must consult the lineage of every ancestor
    # scope embedded in the record, not just the project tombstone table.
    for (scope, rid) in record.get("lineage", []):
        if ledger.is_erased(scope, rid):
            raise ErasureLineageViolation(
                f"read of project record blocked: ancestor "
                f"{scope}/{rid} is erased"
            )
    return record["text"]
