BEGIN IMMEDIATE is a write lock on a local file and does not reach across hosts. The store is the
same protocol, graded by the same suite as SQLite; what changes is a URL.
Prerequisites: a Postgres 14 or later server, a database, a role for CTRLRun, and
pip install "ctrlrun[postgres]".
1
Install and connect
psycopg.connect unchanged, so ?sslmode=require, ?connect_timeout=5, a
service= name and the PG* environment variables all work. Put the password in
~/.pgpass or PGPASSWORD, not in the URL. On the command line the schema travels as
CTRLRun’s own query parameter:2
Create the schema and grant the role
The store does not create the schema, so a typo cannot become a second, empty,
authoritative-looking store. Create it once, and give the role what it needs:
CREATE on the schema is what migrations need; a read-only reporting role needs USAGE
and SELECT on the tables, and ctrlrun receipts --verify-chain runs fine on it. The
database’s encoding must be UTF-8; a lossy encoding is refused at open, because a receipt
that cannot store the argument it records is not a receipt.3
Let the first writer migrate
The schema is versioned. A writer opens the store, reads
schema_version, and applies any
migration forward, once, under a lock, so concurrent starts do not race. There is no flag
that opens a database un-migrated. A binary older than the schema refuses immediately with
SchemaMismatch, before reading any other table, so upgrade every reader before any writer.A read command (receipts, effects, inspect, stats, approve, deny, resolve)
migrates nothing: it opens only a database already at head, and names the missing migration
otherwise.4
Understand the one new ambiguity
A connection lost during
COMMIT is the case worth naming: Postgres very often did commit,
and the client cannot know. The store treats it as unknown and re-reads the row to find
out which, and only if the re-read itself fails does it refuse to proceed. FAILED is
reserved for an abort the server stated. Nothing here changes what AMBIGUOUS means for
the remote your executor called; that path is unchanged.Verify against it
ctrlrun verify --store-url postgresql://… runs the guarantee catalogue against a scratch
schema it creates and drops, never against yours. The store conformance suite,
python -m ctrlrun.conformance.store, grades your database with the same cases SQLite passes.
Throughput
Every reservation queues on one row per effect key and receipts append behind one chain head, so the store serializes writes the way a ledger does. That is the design: a hundred agents refunding a hundred different payments proceed in parallel; a hundred agents refunding the same payment are one winner and ninety-nine refusals.If it didn’t work
schema "ctrlrun" does not exist: create it; the store will not.SchemaMismatch: the database is at version N and this binary understands M: upgrade the binary, or downgrade nothing; there is no backward migration.encoding ... is not UTF8: create the database withENCODING 'UTF8'.permission denied for schema: the role lacksCREATEon the first open.
Next
- Effect keys: what the unique index protects.
- The operator’s Postgres page: grants, failover and what the store does not do for you, in full.
- Get started · Why.