Skip to content

symfonic.capabilities.memory.positional_store

positional_store

Which stores a CORE-171 positional migration admits, and what each must offer.

A migration is planned and applied through one adapter per persistent backend. Every adapter opens the backend's own write/forget serialization -- the in-process writer lock, the PostgreSQL lease-acquisition key and tenant advisory lock plus a memory_nodes lock ahead of the erase-job fence, or the Mongo tenant guard transaction -- and does all of its reading and re-keying inside it, so a reviewed plan, its re-check and its re-key see the same rows and commit or roll back together.

An adapter lists only candidate rows: owners that are neither native v1 keys nor quarantine keys. Both of those start with symfonic.memory. and never hold the v0 delimiter; a v0 path whose adopter-chosen kind spells the header still holds it, so it stays a candidate and is never taken for quarantined. An adapter refuses (MemoryContractError) rather than plan around state it cannot move safely: pending rows, live consolidation leases, an erasing subtree, owner copies that disagree, or more rows than one transaction may hold. A rollback's rows are no longer candidates, so the session also refuses a named moved row that a write has since staged (refuse_pending), inside the same serialization as the re-key. erased reads, in that serialization too, which native targets a recorded forget already erased, so a recorded forget either precedes the plan and quarantines the row or follows the re-key and erases it. refuse_unerasable lets a backend whose forget is bounded refuse an apply that would leave rows no public forget erases.

PositionalRows

Bases: Protocol

Open a serialized session over one tenant's rows.

apply_refusal names a privacy gate that keeps apply closed on this backend while planning and quarantine cleanup stay open (every admitted backend has None today). restore_refusal names why no rollback is provable on this backend, so rollback refuses before the session; None where the session decides. refuse_caller_transaction refuses, before apply or rollback opens a session, a caller transaction a forget could join between the backend's proof and its re-key. write=False promises no row changes, not a read-only transaction: the Mongo session still bumps its tenant guard counter to serialize.

RowSession

Bases: Protocol

Reads and writes inside one serialized backend transaction.

erased async

erased(targets: tuple[str, ...]) -> frozenset[str]

The native targets a forget of that key or an ancestor erased.

Refuses (MemoryContractError) when the backend cannot prove the answer from its own durable records; no caller input overrides that.

Source code in src/symfonic/capabilities/memory/positional_store.py
async def erased(self, targets: tuple[str, ...]) -> frozenset[str]:
    """The native ``targets`` a forget of that key or an ancestor erased.

    Refuses (``MemoryContractError``) when the backend cannot prove the
    answer from its own durable records; no caller input overrides that.
    """
    ...

refuse_pending async

refuse_pending(rows: tuple[tuple[str, str], ...]) -> None

Refuse (PENDING_REFUSAL) if any named (kind, id) row is pending.

Source code in src/symfonic/capabilities/memory/positional_store.py
async def refuse_pending(self, rows: tuple[tuple[str, str], ...]) -> None:
    """Refuse (``PENDING_REFUSAL``) if any named ``(kind, id)`` row is pending."""
    ...

refuse_unerasable async

refuse_unerasable(changes: tuple[tuple[str, str, Any, Any], ...], deleted: frozenset[str]) -> None

Refuse, before apply re-keys, if no public forget could erase the result.

deleted names the quarantined edges apply deletes after the re-key.

Source code in src/symfonic/capabilities/memory/positional_store.py
async def refuse_unerasable(
    self, changes: tuple[tuple[str, str, Any, Any], ...], deleted: frozenset[str],
) -> None:
    """Refuse, before apply re-keys, if no public forget could erase the result.

    ``deleted`` names the quarantined edges apply deletes after the re-key.
    """
    ...

refuse_unproven_restore async

refuse_unproven_restore(rows: tuple[tuple[str, str, Any, Any], ...]) -> None

Refuse unless the store proves each present (kind, id, origin, key) row was quarantined by its own apply and no forget could cover it since.

Source code in src/symfonic/capabilities/memory/positional_store.py
async def refuse_unproven_restore(self, rows: tuple[tuple[str, str, Any, Any], ...]) -> None:
    """Refuse unless the store proves each present ``(kind, id, origin, key)``
    row was quarantined by its own apply and no forget could cover it since."""
    ...

StoredRow dataclass

StoredRow(kind: str, row_id: str, stored: Any, source: str | None = None, target: str | None = None, record: str | None = None)

One stored row and its raw owner; content is never carried.

kind is node, edge, vector or claim. source and target are an edge's endpoint ids; record is a claim's record id.

adapter_for

adapter_for(store: Any) -> PositionalRows

The exact admitted adapter for store, or refuse before any read.

Source code in src/symfonic/capabilities/memory/positional_store.py
def adapter_for(store: Any) -> PositionalRows:
    """The exact admitted adapter for ``store``, or refuse before any read."""
    from symfonic.capabilities.memory.graph_store import GraphBackedHms

    if type(store) is not GraphBackedHms:
        raise MemoryContractError(NOT_ADMITTED)
    graph = store.graph
    module = type(graph).__module__
    if module == "symfonic.memory.backends.in_memory":
        from symfonic.memory.backends.in_memory import InMemoryGraphBackend
        from symfonic.memory.backends.in_memory_positional_rows import InMemoryPositionalRows

        if type(graph) is InMemoryGraphBackend and store.transaction_participants == (graph,):
            return InMemoryPositionalRows(graph)
    elif module in ("symfonic.memory.backends.postgres_graph",
                    "symfonic.memory.backends.postgres_graph_runtime"):
        from symfonic.capabilities.memory.graph_native_admission import exact_pg_graph
        from symfonic.memory.backends.postgres_positional_rows import PostgresPositionalRows

        vectors = store.transaction_participants[1:]
        if exact_pg_graph(graph) and all(_same_pg_pool(graph, vector) for vector in vectors):
            return PostgresPositionalRows(graph)
    elif module in ("symfonic.memory.backends.mongo_tenant_guard",
                    "symfonic.memory.backends.mongo_native_receipt"):
        from symfonic.memory.backends.mongo_positional_rows import MongoPositionalRows

        adapter = MongoPositionalRows.admit(store)
        if adapter is not None:
            return adapter
    raise MemoryContractError(NOT_ADMITTED)

is_quarantined_owner

is_quarantined_owner(stored: Any) -> bool

True for a quarantine key; a v0 path spelling its header is not one.

Source code in src/symfonic/capabilities/memory/positional_store.py
def is_quarantined_owner(stored: Any) -> bool:
    """True for a quarantine key; a v0 path spelling its header is not one."""
    return is_settled_owner(stored) and stored.startswith(QUARANTINE_HEADER)

is_settled_owner

is_settled_owner(stored: Any) -> bool

True for a native or quarantined owner, which no migration moves.

Source code in src/symfonic/capabilities/memory/positional_store.py
def is_settled_owner(stored: Any) -> bool:
    """True for a native or quarantined owner, which no migration moves."""
    return (isinstance(stored, str) and stored.startswith(_SETTLED_HEADER)
            and LEGACY_SCOPE_DELIMITER not in stored)