Five passes are twenty-five seconds, shorter than a controller restart, a store reconnecting or a file half written; the operator asked for both (hq ADR 0230).
5.2 KiB
postgres
The mesh store (novox/hq ADR 0079): one PostgreSQL server that provides the postgres-database
provision to every module on any machine that requires one, and holds the mesh-store seat.
Each consumer is given a database it alone owns, under the login the mesh derived for it and the password the mesh minted (ADR 0048). The provider never invents either: it makes exactly what the mesh handed both ends, so they agree by construction.
Requiring a database
A consumer requires postgres-database and contributes what it wants:
"requires": ["postgres-database"],
"contributes": {"postgres-database": {"name": "letta", "extensions": ["vector"]}},
"binds": {"postgres-database": "${dir:state}/database.json"},
"secrets": {"postgres-database": "${dir:state}/database.secret"}
| field | meaning |
|---|---|
name |
what the consumer calls its database. The database and its owning login are both named by the login the mesh derives (${bound:postgres-database:as}), so that is the name to connect to. |
extensions |
optional; a list of extension names to install in the consumer's database, e.g. ["vector"] for pgvector. |
Extensions are the provider's to install. Most extensions are not trusted (pgvector is
superuser=t, trusted=f), so the login that owns the database cannot create them. The provider does,
as the superuser, connected to that database, with CREATE EXTENSION IF NOT EXISTS — on every
provisioning pass, so a consumer that adds a name to its contribution, or a database that predates
this, gets it on the next pass, and a second pass changes nothing. Only a name the server lists in
pg_available_extensions is installed; any other is refused, by name, in the provider's log: the
consumer's database and login are still made, but none of its extensions is installed until the
contribution is corrected. An extension is never
dropped — not when it leaves the list, not when the consumer goes.
The minute-by-minute check (issue 120) connects as the consumer and looks for each extension it asked for, so one removed by hand is installed again.
What is never done
- No database is dropped by the mesh on its own. A consumer the mesh no longer asks for is
retired (novox/hq ADR 0230), once the same result holds for five passes and ten minutes and, if
it is more than three consumers or more than half of those held, once a person approved: its login is set
NOLOGIN, its sessions are ended, its role's comment marks it retired with when and why, and its database stays exactly as it was under its own name — still backed up. A consumer asked for again is enabled at once with the same database. Onlycleanup delete, a person's act through the controller, drops it. postgres_retire_databaserenames, it does not drop: the database becomes<name>_deleted_<yyyymmdd>and its owner is locked. Removing the data is a person's act, by hand.- A caller's statement never runs as the superuser.
postgres_queryand the seat'squeryverb run asmesh_store_reader—pg_read_all_dataand nothing else, every session read-only by the server's own setting, a 60 s statement timeout — with the statement sent as given (issue 193). Without the reader's password (own-secrets.reader) the call is refused.
Tools
| tool | answers |
|---|---|
postgres_list_databases |
every non-template database with its size |
postgres_query {database, sql} |
one read-only statement, as the reader; rows keyed by column, NULL as null |
postgres_retire_database {database, confirm} |
renames a database aside and locks its owner; confirm repeats the name |
mesh-store.databases, mesh-store.query |
the store seat's verbs: the same listing and read-only query |
provisioner_retirement |
what is held, what waits for a person, what was rejected, every retired consumer and set-aside database with when, why and size |
provisioner_retire_approve, provisioner_retire_reject {consumers, why, by} |
a person's answer to a set waiting; through the controller's `retire approve |
provisioner_delete {consumer, confirm, why, by} |
drops one retired consumer's database and role, or one set-aside database; through the controller's cleanup delete |
Where the code lives
One Go bundle, cmd/postgres-provider, launched by the node's runtime and speaking MCP over stdio
through the Go SDK (ADR 0193). Beside the tools it runs the provisioner: every five seconds it reads the
contributions file the mesh writes (MESH_RECEIVES), applies each consumer whose login, password or
contribution changed, and retires what is no longer listed (retirement.go); a file it cannot read
retires nobody. It is the TypeScript SDK's runProvisioner loop, carried in the module until the Go SDK
has one, byte for byte the same as keycloak's.
go test ./... runs against a fake server. MESH_POSTGRES_LIVE=postgres://postgres:…@host:port/postgres
also runs live_test.go against a real, throwaway one (see the file for a pgvector/pgvector
container): the extension installed and a second pass a no-op, the reader unable to write, a
retired login locked out with its data kept and listed retired, enabled again as it was, and a deletion
dropping only its own database and role.