The hourly release of ADR 0229's brake still ended in the mesh acting alone on a mistake. A consumer now stays active until the same unasked set holds for five passes, waits for a person past three or half of those held, is disabled and marked rather than withdrawn, comes back as it was when asked again, and is deleted only through the provider's delete tool. The backend keeps the mark, so a restart forgets nothing and finds what was withdrawn before.
80 lines
3.4 KiB
Go
80 lines
3.4 KiB
Go
package main
|
|
|
|
// postgres's provisioner — the adapter that makes postgres a provider of the mesh
|
|
// `postgres-database` interface (novox/hq ADR 0039/0040/0048). A consumer connects to a database it
|
|
// alone owns, as `as` with the password the mesh minted.
|
|
//
|
|
// **The role name and password are the mesh's, not the provisioner's (ADR 0048).** postgres creates
|
|
// a role and a same-named database under exactly that login — a name the consumer cannot learn is a
|
|
// database it cannot reach.
|
|
//
|
|
// **Extensions are the provider's to install.** A contribution may name extensions
|
|
// (`"extensions": ["vector"]`); most are not trusted, so only the superuser this module holds can
|
|
// create them, in the consumer's database, on every pass, and never drops one.
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// provisioner is the adapter over the client; announce emits a lifecycle event.
|
|
type provisioner struct {
|
|
pg *Client
|
|
announce func(event string, body map[string]string)
|
|
}
|
|
|
|
func (a provisioner) Create(ctx context.Context, p Provision) error {
|
|
// Read before anything runs: a malformed list is refused without touching the server.
|
|
extensions, err := Extensions(p.Values)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Database and owning role share the consumer's login, so the consumer owns exactly its own.
|
|
database := p.As
|
|
if err := a.pg.CreateDatabaseAndRole(ctx, database, p.As, p.Password); err != nil {
|
|
return err
|
|
}
|
|
if err := a.pg.EnsureExtensions(ctx, database, extensions); err != nil {
|
|
return err
|
|
}
|
|
// The active mark: a retired consumer asked for again loses its mark to delete here, its LOGIN
|
|
// already set again by the role statement above (novox/hq ADR 0230).
|
|
if err := a.pg.MarkActive(ctx, p.As, p.Consumer); err != nil {
|
|
return err
|
|
}
|
|
a.announce("database.provisioned", map[string]string{"consumer": p.Consumer, "database": database, "user": p.As})
|
|
return nil
|
|
}
|
|
|
|
// Retire locks the login, never drops (novox/hq issue 241, ADR 0230): the database is kept under its
|
|
// own name, the role marked retired with when and why (retire_pg.go). On 2026-10-04 a misread
|
|
// contributions file withdrew every consumer at once, and dropping made that a loss of seven databases.
|
|
// Deleting is `cleanup delete`, a person's act; taking a database out of service by hand is
|
|
// postgres_retire_database, which renames rather than drops.
|
|
func (a provisioner) Retire(ctx context.Context, as string, _ map[string]any, why string, at time.Time) error {
|
|
if err := a.pg.RetireRole(ctx, as, why, at); err != nil {
|
|
return err
|
|
}
|
|
a.announce("database.deprovisioned", map[string]string{"database": as, "kept": "true", "retired": "true"})
|
|
return nil
|
|
}
|
|
|
|
// Inventory is what this server holds that the mesh made (retire_pg.go).
|
|
func (a provisioner) Inventory(ctx context.Context) (Inventory, error) { return a.pg.Inventory(ctx) }
|
|
|
|
// Delete drops a retired consumer's database and role, or a database set aside — a person's act.
|
|
func (a provisioner) Delete(ctx context.Context, r Retired) (int64, error) {
|
|
return a.pg.DeleteRetired(ctx, r)
|
|
}
|
|
|
|
// Holds is asked every minute: whether the consumer can still log in as the mesh gave it, and finds
|
|
// the extensions it asked for, so a login or extension lost behind the provisioner's back is made
|
|
// again (novox/hq issue 120).
|
|
func (a provisioner) Holds(ctx context.Context, p Provision) (bool, error) {
|
|
extensions, err := Extensions(p.Values)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return a.pg.Holds(ctx, p.As, p.As, p.Password, extensions)
|
|
}
|