Skip to content

symfonic.capabilities.memory.admin_erase

admin_erase

Memory-first admin erasure, atomic only on a confirmed shared PG pool.

forget async

forget(store: Any, conversation: Any, scope: MemoryScope, journal: Any = None) -> Any

Return the store receipt after both erases succeed, or its job in progress.

A store refusal must leave the transcript untouched. A shared PostgreSQL pool also rolls back memory if conversation erasure fails. Other domains retain sequential behavior and offer no cross-store rollback guarantee. A CORE-199 large erase commits on the eraser's own login, outside that rollback, so on a shared pool it refuses as inherited and changes nothing. Raw graph-only PostgreSQL v1 (CORE-194) admits the store's verb-gated forget alone, or jointly with an exact PostgresConversationLog. On a pool of its own the transcript is fenced by a durable erase intent before memory is touched, and erased, with the intent, only once memory is. The read-only native Mongo v1 store forgets only through journal (CORE-171), and never with a conversation log; journal admits no other store.

Source code in src/symfonic/capabilities/memory/admin_erase.py
async def forget(store: Any, conversation: Any, scope: MemoryScope, journal: Any = None) -> Any:
    """Return the store receipt after both erases succeed, or its job in progress.

    A store refusal must leave the transcript untouched. A shared PostgreSQL
    pool also rolls back memory if conversation erasure fails. Other domains
    retain sequential behavior and offer no cross-store rollback guarantee.
    A CORE-199 large erase commits on the eraser's own login, outside that
    rollback, so on a shared pool it refuses as inherited and changes nothing.
    Raw graph-only PostgreSQL v1 (CORE-194) admits the store's verb-gated
    forget alone, or jointly with an exact ``PostgresConversationLog``. On a
    pool of its own the transcript is fenced by a durable erase intent before
    memory is touched, and erased, with the intent, only once memory is.
    The read-only native Mongo v1 store forgets only through ``journal``
    (CORE-171), and never with a conversation log; ``journal`` admits no
    other store.
    """
    from symfonic.capabilities.memory import mongo_native_forget as native_mongo

    if native_mongo.native_mongo_store(store):
        if journal is None:
            raise MemoryContractError(NO_JOURNAL)
        if conversation is not None:
            raise MemoryContractError(native_mongo.CONVERSATION)
        return await native_mongo.forget_native_mongo(store, journal, scope)
    if journal is not None:
        raise MemoryContractError(JOURNAL_NOT_ADMITTED)
    from symfonic.capabilities.memory.commit import domain_of
    from symfonic.capabilities.memory.graph_native_admission import (
        admit_raw_pg_mutation,
        require_native_backends,
    )
    from symfonic.capabilities.memory.graph_receipt import MemoryOperationVerb
    from symfonic.capabilities.memory.graph_store import GraphBackedHms
    from symfonic.capabilities.memory.transactions import one_transaction
    from symfonic.memory.backends.pool import PostgresPoolManager

    admitted = isinstance(store, GraphBackedHms) and (
        conversation is None or _exact_log(conversation)) and (
        admit_raw_pg_mutation(scope, store, MemoryOperationVerb.FORGET))
    if isinstance(store, GraphBackedHms) and scope.identity_version and not admitted:
        participants = store.transaction_participants
        require_native_backends(scope, participants[0],
                                participants[1] if len(participants) > 1 else None)

    async def erase() -> Any:
        from symfonic.capabilities.memory.records import ForgetInProgress

        receipt = await store.forget(scope)
        # CORE-199: an unfinished erase job is not a finished forget; the
        # transcript goes with the resume that completes it.
        if conversation is not None and not isinstance(receipt, ForgetInProgress):
            await conversation.forget(scope)
        return receipt

    domain = domain_of(store) if conversation is not None else None
    if isinstance(domain, PostgresPoolManager) and domain_of(conversation) is domain:
        from symfonic.capabilities.memory.graph_forget import (
            _proof,
            deferred_acknowledgement,
            native_subtree_deleter,
        )
        from symfonic.capabilities.memory.records import ForgetInProgress

        deleter = native_subtree_deleter(getattr(store, "_graph", None))
        with deferred_acknowledgement():
            async with one_transaction(domain):
                receipt = await erase()
        proof = _proof(deleter)
        if proof is not None and not isinstance(receipt, ForgetInProgress):
            await proof.acknowledge(deleter.pool)  # CORE-171: after the joint commit
        return receipt
    if admitted and conversation is not None:
        await _fence(store._graph, conversation, scope)
    return await erase()