symfonic.memory.backends.postgres_erase_receipts¶
postgres_erase_receipts ¶
Completed erase receipts and the claim keyset index (CORE-199 S2/S3).
A finished job leaves one row here and nothing else: its id and a digest of its tenant and prefix. The prefix itself, which names every level of the erased subtree, is not retained; the CHECK refuses anything but a lowercase SHA-256 hex digest in that column. The receipt lets an operator who resumes by job id learn that the job finished, and lets an audit holding the key prove which subtree it erased.
The digest is HMAC-SHA256 under the operator's receipt key, never a bare
hash: tenant ids and scope levels are short and guessable, so anyone who
can read receipts could otherwise confirm a guessed subtree offline. The key
is supplied explicitly to the eraser and never derived from any id. A
legacy receipt does not record which key made it: after rotation it verifies
only under the key in force at completion. Opt-in permanent retirement uses
postgres_retirement_receipt and the CORE-428 migration instead: a versioned
digest binds job, root, stable graph identity and external anchor identity to
the signing key ID pinned at initiation. Legacy rows are never relabelled.
The chunk driver walks the subtree's claims by (scope_path, token) after
a cursor persisted on the job row. The owner range index alone cannot seek a
token inside one scope_path, and a node rewritten forty thousand times
keeps all its generations in one scope_path; restarting from the prefix
instead revisits every dead index entry earlier chunks left behind while any
old snapshot keeps them from being pruned, which is quadratic.
Both objects come from scripts/migrations/core199_erase_chunks.sql, never from application startup. They sit beside memory_nodes, like the job table, and are resolved in that schema.
eraser_tables
async
¶
Schema-qualified receipts and claims beside memory_nodes, keyset index sound.
Source code in src/symfonic/memory/backends/postgres_erase_receipts.py
no_access_drift
async
¶
Runtime roles hold nothing on receipts: only the eraser writes them.
Source code in src/symfonic/memory/backends/postgres_erase_receipts.py
prefix_digest ¶
The only trace a completed job keeps of the subtree it erased.
Source code in src/symfonic/memory/backends/postgres_erase_receipts.py
require_receipt_key ¶
The operator's receipt key: at least 32 random bytes, or refuse.