symfonic.memory.backends.postgres_staged_content¶
postgres_staged_content ¶
Per-record content seal for one staged PostgreSQL v1 turn (CORE-339a).
READY binds the run and the output digest; the claim fence keeps each staged row pending and claimed. Neither binds what a row says: a session that may UPDATE a claimed pending row (the owner, or the CORE-376 writer by policy) can rewrite its label or properties while keeping the claim keys, and a session that may DELETE can remove it. This module closes both:
seal_recordsruns inside the READY transaction. For every claim of the sealed FLUSH manifest, in manifest order, it reads the exact row (tenant, id) and records a server-computed SHA-256 over every published column. The result travels inside the AEAD-sealed READY payload, which the writer may append but never rewrite.spendruns inside the receipt transaction that publishes the FLUSH. It inserts the FLUSH receipt PENDING there, not in a prior committed transaction, then locks every sealed row FOR UPDATE and requires each to be present with an equal digest, all before any vector is written. A missing or changed row raises, the transaction rolls back, and no receipt, proof, release or vector survives: the turn stays READY rather than UNKNOWN.
The digest is computed by PostgreSQL on both sides, over jsonb text, so it
does not depend on client decoding; both sides run inside a transaction with
extra_float_digits pinned to 1, the server default, so the float8
importance text does not depend on the session either. created_at,
updated_at and access_count are bookkeeping, not published content,
and are excluded.
ContentSeal ¶
Bases: NamedTuple
The verified per-record digests and the READY row bytes they came from.
StagedContentRefused ¶
Bases: MemoryContractError
A sealed staged row is absent or no longer equals its READY digest.
require_seal ¶
Exact shape and version, and exactly the manifest's rows in its order.
Source code in src/symfonic/memory/backends/postgres_staged_content.py
seal_records
async
¶
Inside READY: every claimed row present, pending, and digested.
Source code in src/symfonic/memory/backends/postgres_staged_content.py
spend
async
¶
Inside the receipt transaction: reserve the FLUSH, then prove every row.
A concurrent activation holding the same id blocks this INSERT until it
ends; if it committed, nothing is inserted and this attempt is UNKNOWN to
its caller, whose fresh snapshot then reads the winner's ACTIVE. The
reservation and the row locks wait at most SPEND_LOCK_TIMEOUT, so an
activator parked inside its publication, or a session holding a staged
row, cannot hang this one: it rolls back with the turn READY. The caller's
own lock_timeout is restored for the rest of the transaction.