# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-scoped-forgetting-from-silently-degrading-into-logical-deletion-under-concurrent-re-ingestion.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-scoped-forgetting-from-silently-degrading-into-logical-deletion-under-concurrent-re-ingestion.warrant.json
# Markdown SHA-256:   c72084d0f29b224f02017ea4ddc8444c3bcbc1390a5c353f7de676b30fb01513
# Code AST SHA-256:   6be7b89a8a68461ca11f649cfcdae954a5e2518da818e749a521530fcbf63a29
# Verified Classes:   ForgottenFieldReintroductionError, ForgettingLedger
# Defensive Guards:   1 fail-closed invariants (zero external deps)

class ForgottenFieldReintroductionError(RuntimeError):
    """Raised when a write would reintroduce a field under a live forgetting obligation."""
    pass


class ForgettingLedger:
    def __init__(self):
        # (resolved_scope, field) -> obligation_id
        self._entries = {}

    def record(self, resolved_scope: str, field: str, obligation_id: str) -> None:
        self._entries[(resolved_scope, field)] = obligation_id

    def lookup(self, resolved_scope: str, field: str):
        return self._entries.get((resolved_scope, field))


def resolve_scope(raw_scope: str, aliases: dict) -> str:
    # Resolve session -> subject, tenant alias -> canonical tenant, etc.
    return aliases.get(raw_scope, raw_scope)


def write_record(store: dict, ledger: ForgettingLedger, raw_scope: str,
                 aliases: dict, key: str, fields: dict) -> None:
    resolved = resolve_scope(raw_scope, aliases)
    for field in fields:
        obligation = ledger.lookup(resolved, field)
        if obligation is not None:
            # Fail closed: the write path is the enforcement point, not the read path.
            raise ForgottenFieldReintroductionError(
                f"write of key={key!r} under scope={resolved!r} would reintroduce "
                f"field={field!r} blocked by obligation={obligation!r}; "
                f"transform or reject at the caller, do not bypass the ledger"
            )
    store[(resolved, key)] = dict(fields)


def forget(store: dict, ledger: ForgettingLedger, raw_scope: str,
           aliases: dict, field: str, obligation_id: str) -> None:
    resolved = resolve_scope(raw_scope, aliases)
    # Durable ledger entry first; write-path enforcement is what discharges the obligation.
    ledger.record(resolved, field, obligation_id)
    for (scope, key), fields in list(store.items()):
        if scope == resolved and field in fields:
            del fields[field]
            if not fields:
                del store[(scope, key)]


# Demonstration of the boundary: forget succeeds, re-ingestion is rejected.
ledger = ForgettingLedger()
store = {}
aliases = {"session_alice_7": "subject_alice"}

write_record(store, ledger, "session_alice_7", aliases, "doc_1", {"ssn": "123-45-6789", "note": "x"})
forget(store, ledger, "subject_alice", aliases, "ssn", "obl_001")

# The re-ingestion path resolves the same subject and is now blocked.
try:
    write_record(store, ledger, "session_alice_7", aliases, "doc_991", {"ssn": "123-45-6789"})
except ForgottenFieldReintroductionError as exc:
    print("blocked:", exc)
