async def forget_subtree(store: Any, scope: Any, *, operation: Any = None):
"""Bound graph-only native PG erasure before entering its mutation fence."""
from symfonic.capabilities.memory import graph_receipt
from symfonic.capabilities.memory.errors import MemoryContractError
from symfonic.capabilities.memory.graph_direct_claims import (
external_transaction,
retire_subtree,
)
from symfonic.capabilities.memory.graph_rows import _adapter_scope
from symfonic.capabilities.memory.records import ForgetInProgress, LifecycleReceipt
from symfonic.memory.backends.postgres_erase_routing import LargeEraseRequired
legacy = _adapter_scope(scope)
deleter = native_subtree_deleter(store._graph) if scope.identity_version else None
options = {"native_forget": True} if deleter is not None and store._vectors is None else {}
outer = external_transaction(store, scope)
proof = _proof(deleter)
if proof is not None: # CORE-171: refuse before any delete without the anchored lineage
if outer and not _DEFERRED.get():
raise MemoryContractError(UNANCHORABLE)
await _verified(proof, deleter.pool)
hold = (
graph_receipt.mutation_hold(
store, scope, operation, graph_receipt.MemoryOperationVerb.FORGET
)
if graph_receipt.domain_of(store) is not None
else store._mutations.hold(legacy, **options)
)
try:
async with hold:
claims = store._direct_claims.copy()
if deleter is not None:
doomed = await deleter.delete_native_subtree_ids(legacy)
else:
nodes = await store._graph.query_subtree(legacy, {}, limit=None)
doomed = tuple(sorted(str(node.id) for node in nodes))
await store._graph.delete_subtree(legacy)
if store._recall is not None:
try:
await store._recall.forget_subtree(legacy)
except MemoryContractError as error:
await _route_large_vector(store, deleter, error)
raise
except LargeEraseRequired as signal:
if deleter is None:
raise
claims = store._direct_claims.copy()
step = await _erase_large(store, deleter, legacy, signal, outer)
retire_subtree(store, scope, outer, claims)
if not step.complete:
return ForgetInProgress(scope.path, step.job_id, step.discarded)
doomed = step.discarded
else:
retire_subtree(store, scope, outer, claims)
if proof is not None and not outer: # acknowledged only once anchored in-lineage
await proof.acknowledge(deleter.pool)
return LifecycleReceipt(scope_path=scope.path, discarded=tuple(doomed))