# 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. Only `cleanup delete`, a person's act through the controller, drops it. - **`postgres_retire_database` renames, it does not drop**: the database becomes `_deleted_` 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 | | `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|reject` | | `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.