Skip to content

symfonic.memory.backends.postgres_positional_rows

postgres_positional_rows

PostgreSQL rows for the CORE-171 positional migration, read and re-keyed.

One session is one READ COMMITTED transaction on the graph's own pool. It first takes the tenant key every lease acquisition and commit fence takes first, so no lease of the tenant is granted or committed against until it ends; then, in the order every native forget uses, the symfonic-memory:<tenant> advisory lock, memory_nodes in ROW EXCLUSIVE mode, and the CORE-199 erase-job fence. Nothing takes the lease key after those, so the order cannot invert. A sweep holds memory_nodes EXCLUSIVE, so a migration and a forget of the same store serialize, and a job the sweep committed is visible here before any row is read; a live lease of the tenant refuses. Writing sessions lock every candidate row FOR UPDATE, so the re-plan apply compares against is the state its re-key changes; a rollback would lock its moved nodes and the tenant's pending claims on them the same way before refusing any that is pending.

Every owner copy moves in that transaction: memory_nodes.scope_path and its properties.scope_path, memory_edges.scope_path and memory_pending_claims.scope_path. A copy absent before stays absent; a JSON copy beside a NULL owner is restored as JSON null. Any refusal or database error rolls the whole transaction back.

memory_vectors is never re-keyed: PostgreSQL v1 memory is graph-only (CORE-194), so no v1 forget reaches that table and a migrated vector copy would hold content nothing erases. A tenant with positional vector rows refuses to plan (VECTOR_GATE).

No PostgreSQL store can prove which native owners a forget erased. CORE-199's memory_erase_generations records only forgets that ran after it was installed, from a release that advances it; an erase without the table advances nothing (postgres_memory_generation.advance), installing it records no history, erase receipts keep only a keyed digest of a large erase's prefix, and an inline forget keeps nothing. So any plan that would move a row to a native owner refuses before any row is re-keyed (ERASE_UNPROVABLE), whether or not the table is installed, and no declaration or caller flag overrides it (CORE-171 owner decision, 2026-10-14). Apply executes only the plan it re-reads, so a hand-built plan cannot carry a move past that refusal; rollback refuses a native owner on every backend (ROLLBACK_UNPROVABLE). A plan that only quarantines and quarantine cleanup stay open. Rollback of a quarantine refuses too (RESTORE_UNPROVABLE): a positional forget accepted after it records nothing here, so a restored row could come back after its origin was forgotten. The pending-row check a rollback would take is kept, and tested, for when a restore becomes provable.

PostgresPositionalRows

PostgresPositionalRows(graph: Any)

Sessions over an exact PostgreSQL graph backend or nonowner runtime.

Source code in src/symfonic/memory/backends/postgres_positional_rows.py
def __init__(self, graph: Any) -> None:
    self._graph = graph

refuse_caller_transaction

refuse_caller_transaction() -> None

No refusal: apply moves rows only to quarantine keys and rollback always refuses, so a forget joined to a caller's pool transaction restores nothing; one between re-plan and re-key leaves rows missing (refused).

Source code in src/symfonic/memory/backends/postgres_positional_rows.py
def refuse_caller_transaction(self) -> None:
    """No refusal: apply moves rows only to quarantine keys and rollback always
    refuses, so a forget joined to a caller's pool transaction restores
    nothing; one between re-plan and re-key leaves rows missing (refused)."""