Durable native subtree erasure on a separate PostgreSQL principal.
A host composes this role explicitly with PostgresGraphBackend. Without
it an oversized forget refuses before mutation. Runtime, writer and publisher
remain SELECT-only on jobs; only this role advances them.
start locks lifecycle and graph, erases bounded content inline, or records
an invisible subtree job for oversized content once its migration is installed.
resume advances bounded chunks on demand; nothing runs in the background.
Every transaction pins the configured schema. Startup attests the eraser login,
its grants, the graph relation and separation from the runtime principal.
An unverified eraser cannot mutate; drift withdraws admission and a partial
erase never reports completion.
EraseStep
dataclass
EraseStep(job_id: str | None, complete: bool, discarded: tuple[str, ...] = ())
What one eraser transaction left: complete, or a job still erasing.
EraserAttestation
dataclass
EraserAttestation(runtime_login: str, eraser_login: str, binding: Any)
What attest proved, once, on the eraser's own login (CORE-194).
PostgresNativeEraser
PostgresNativeEraser(pool: Any, *, schema: str, receipt_key: bytes, erase_anchor: Any = None, receipt_key_id: str | None = None, receipt_verification_keys: Any = None, anchor_key: bytes | None = None)
Job creation, chunking and completion on the eraser's own login.
Source code in src/symfonic/memory/backends/postgres_native_eraser.py
| def __init__(
self,
pool: Any,
*,
schema: str,
receipt_key: bytes,
erase_anchor: Any = None,
receipt_key_id: str | None = None,
receipt_verification_keys: Any = None,
anchor_key: bytes | None = None,
) -> None:
from symfonic.memory.backends.postgres_retirement_receipt import configure
configure(
self,
pool,
schema,
receipt_key,
erase_anchor,
receipt_key_id,
receipt_verification_keys,
anchor_key,
)
self._attested: dict[tuple[str, Any], EraserAttestation] = {}
|
attest
async
attest(*, runtime_login: str, binding: Any) -> EraserAttestation
D6 on this login, binding's very graph, and not runtime_login.
Read-only. Refuses with EraserNotReady and withdraws every earlier
attestation, so a failed re-attestation cannot leave a stale proof.
Source code in src/symfonic/memory/backends/postgres_native_eraser.py
| async def attest(self, *, runtime_login: str, binding: Any) -> EraserAttestation:
"""D6 on this login, ``binding``'s very graph, and not ``runtime_login``.
Read-only. Refuses with ``EraserNotReady`` and withdraws every earlier
attestation, so a failed re-attestation cannot leave a stale proof.
"""
login = await self._probe(runtime_login, binding)
attestation = EraserAttestation(runtime_login, login, binding)
self._attested[(runtime_login, binding)] = attestation
return attestation
|
attestation_for
attestation_for(runtime_login: str, binding: Any) -> EraserAttestation | None
The standing attestation for that runtime identity, or None.
Source code in src/symfonic/memory/backends/postgres_native_eraser.py
| def attestation_for(self, runtime_login: str, binding: Any) -> EraserAttestation | None:
"""The standing attestation for that runtime identity, or None."""
return self._attested.get((runtime_login, binding))
|
guarding
guarding(runtime_pool: Any) -> PostgresNativeEraser
Guard the runtime's pool with this eraser's anchor proof (CORE-171).
Source code in src/symfonic/memory/backends/postgres_native_eraser.py
| def guarding(self, runtime_pool: Any) -> PostgresNativeEraser:
"""Guard the runtime's pool with this eraser's anchor proof (CORE-171)."""
from symfonic.memory.backends.postgres_erase_incarnation import install
install(runtime_pool, self._guard)
return self
|
require_ready
async
Startup: refuse unless this login holds exactly D6 in its schema.
Source code in src/symfonic/memory/backends/postgres_native_eraser.py
| async def require_ready(self) -> None:
"""Startup: refuse unless this login holds exactly D6 in its schema."""
await self._probe(None, None)
|
resume
async
resume(job_id: str) -> EraseStep
Erase one chunk of job_id in the configured schema; the operator's entry.
Source code in src/symfonic/memory/backends/postgres_native_eraser.py
| async def resume(self, job_id: str) -> EraseStep:
"""Erase one chunk of ``job_id`` in the configured schema; the operator's entry."""
if type(job_id) is not str or not job_id:
raise MemoryContractError("native erase job id must be a nonempty string")
await self._verified()
return await self._advance(job_id, None)
|
retire_tenant
async
retire_tenant(scope: Any, *, runtime_login: str, binding: Any) -> EraseStep
Explicit permanent tenant retirement; ordinary start stays reusable.
Source code in src/symfonic/memory/backends/postgres_native_eraser.py
| async def retire_tenant(self, scope: Any, *, runtime_login: str, binding: Any) -> EraseStep:
"""Explicit permanent tenant retirement; ordinary ``start`` stays reusable."""
from symfonic.memory.backends.postgres_erase_retirement import retire
return await retire(self, scope, runtime_login=runtime_login, binding=binding)
|
start
async
start(scope: Any, *, runtime_login: str, binding: Any, vectors: bool = False) -> EraseStep
Erase content and create the job, or resume the identical one.
Source code in src/symfonic/memory/backends/postgres_native_eraser.py
| async def start(
self, scope: Any, *, runtime_login: str, binding: Any, vectors: bool = False
) -> EraseStep:
"""Erase content and create the job, or resume the identical one."""
from symfonic.memory.backends.postgres_erase_create import active, create
from symfonic.memory.backends.postgres_erase_routing import (
OVERLAPPING,
overlap_args,
overlap_sql,
)
from symfonic.memory.backends.postgres_native_subtree import GRAPH_LOCK
await self._verified()
async with self._transaction(binding) as conn:
jobs = await self._jobs(conn, runtime_login)
resumed = await active(conn, jobs, scope)
if resumed is None:
await conn.execute(_LIFECYCLE_LOCK, f"symfonic-memory:{scope.tenant_id}")
await conn.execute(GRAPH_LOCK)
# A start of this same scope may have committed its job while
# this one waited; that job is resumed, not an overlap.
resumed = await active(conn, jobs, scope)
if resumed is None:
if await conn.fetch(overlap_sql(jobs), *overlap_args(scope)):
raise MemoryContractError(OVERLAPPING)
if vectors:
from symfonic.memory.backends.postgres_erase_vector_subtree import preflight
await preflight(conn, jobs, scope)
created, chunked = await create(conn, jobs, scope, vectors=vectors)
if not chunked:
return created
resumed = created.job_id
elif vectors:
from symfonic.memory.backends.postgres_erase_vector_subtree import require_job
await require_job(conn, jobs, resumed)
return await self._advance(resumed, binding)
|
runtime_identity
async
runtime_identity(pool: Any) -> tuple[str, Any]
The runtime's session_user and the memory_nodes it resolves.
Source code in src/symfonic/memory/backends/postgres_native_eraser.py
| async def runtime_identity(pool: Any) -> tuple[str, Any]:
"""The runtime's ``session_user`` and the ``memory_nodes`` it resolves."""
from symfonic.memory.backends.postgres_retirement_receipt import runtime_identity as resolve
return await resolve(pool)
|