Files
mesh-catalog/modules/postgres
jochen 4769dadf63 State each provision's identity bound (hq issue 263)
Consumers were held to an S3 access key's 20 characters whatever they
required. Each provider now says what its backend keeps: minio 20,
PostgreSQL and MongoDB and DNS 63, Gitea 40, a mailbox 64, SQL Server 128,
Keycloak 255, unbounded where the store has no limit, and none for the
resolver and route provisions, which keep no name of their consumers.
Needs the controller that reads the field (mesh-controller, ADR 0225).
2026-10-06 02:16:27 +02:00
..

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 NOLOGIN and 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_database renames, 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_query and the seat's query verb run as mesh_store_reader — pg_read_all_data and 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.