A provider failed every consumer for a day and said so only in its journal (hq issue 179). The provisioner loop now emits provisioner.failing after five minutes without a success — create, check or secret — and repeats it every fifteen; provisioner.recovered on the next success, on withdrawal, and on the first success after a restart, so the controller can name it in status (hq ADR 0224).
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 ever dropped. A consumer the mesh no longer asks for is withdrawn: its login is
set
NOLOGINand its sessions are ended, and its database stays as it was under its own name (issue 241). A consumer that comes back is given the same database. 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 |
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 withdraws each one no longer listed; a file it cannot read withdraws nobody.
It is the TypeScript SDK's runProvisioner loop, carried in the module until the Go SDK has one.
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
withdrawn login locked out with its data kept.