Files
mesh-catalog/modules/postgres
jochen d8b4d20886 Port postgres to Go and install the extensions a consumer asks for
letta crash-loops on 'type "vector" does not exist': pgvector is not a
trusted extension, so only the provider's superuser can create it, and
the provisioner never did. A contribution may now name extensions; the
provider creates each (IF NOT EXISTS, available ones only) in the
consumer's database on every pass. Go per the standing rule for a
TypeScript module that changes. letta asks for vector.
2026-10-05 23:29:55 +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.