# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-incomplete-forgetting-in-a-scoped-memory-tool-api-when-tombstones-provenance-tags-and-derived-artifacts-disagree.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-incomplete-forgetting-in-a-scoped-memory-tool-api-when-tombstones-provenance-tags-and-derived-artifacts-disagree.warrant.json
# Markdown SHA-256:   478d6b1c1033548c1c08a1484f2a42c48c02816db33e813d38b4dc82fffe5132
# Code AST SHA-256:   cce528dc55e8ae724bec3a740ecaf5f4c276e3dddbc0eff99f1196f1f515c9cc
# Verified Classes:   IncompleteErasureError
# Defensive Guards:   3 fail-closed invariants (zero external deps)

class IncompleteErasureError(RuntimeError):
    """Raised when forget() succeeded on the primary key but live
    derivations still reach the tombstoned key via provenance."""
    pass


def attest_erasure(store, scope: str, key: str) -> None:
    tombstone = store.get_tombstone(scope, key)
    if tombstone is None:
        raise IncompleteErasureError(
            f"no tombstone for {scope}/{key}; forget() was never confirmed"
        )

    reverse = store.reverse_provenance_index()
    if reverse is None:
        raise IncompleteErasureError(
            f"cannot attest erasure of {scope}/{key}: no reverse provenance "
            f"index; derivations in derived/cache/index namespaces are "
            f"unverifiable. Record ErasureObligation, do not attest."
        )

    live = [
        art for art in reverse.lookup(scope, key)
        if not store.is_tombstoned(art.scope, art.key)
    ]
    if live:
        raise IncompleteErasureError(
            f"forget({scope}/{key}) left {len(live)} live derivations: "
            f"{[(a.scope, a.key) for a in live]}; cascade or redact before "
            f"attesting erasure"
        )

    # Only now is the reachability invariant satisfied.
    store.mark_obligation_closed(scope, key)
