Phase 3.1: adopt the foundation store as the postgres module

InstallStore turns the mesh-store the foundation raised at genesis into the
postgres module, adopted in place: it verifies the module's server names the
same container and the same image the foundation is running (fail-fast on a
drift, rather than tearing down the mesh's store), then registers, builds the
provisioner, and carries the superuser in via secret accept — the mesh cannot
invent a credential that already made the databases (mirroring the control
plane's store-connection delivery, control.go). pinImage generalised to any
module for reuse.

Issue 051 (WBS 3.1). One server holds the controller's contexts and every
module's database.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
2026-09-16 21:04:06 +02:00
parent 121367319d
commit 9109a8c178
6 changed files with 223 additions and 16 deletions
+1 -1
View File
@@ -55,7 +55,7 @@
"POSTGRES_PASSWORD": "bootstrap", "POSTGRES_PASSWORD": "bootstrap",
"PGDATA": "/var/lib/postgresql/data/pgdata" "PGDATA": "/var/lib/postgresql/data/pgdata"
}, },
"ports": ["127.0.0.1:5432:5432"], "ports": ["5432:5432"],
"volumes": ["mesh-store-data:/var/lib/postgresql/data"] "volumes": ["mesh-store-data:/var/lib/postgresql/data"]
}, },
// Over TCP, not the socket. While the store initialises it runs a temporary server on the // Over TCP, not the socket. While the store initialises it runs a temporary server on the
+6 -4
View File
@@ -588,10 +588,12 @@ func Run(ctx context.Context, o Options, d Deps, say func(string)) (Result, erro
} }
// ---- 14. store ------------------------------------------------------------------------ // ---- 14. store ------------------------------------------------------------------------
// A database PROVIDER. The foundation's store is the control plane's own memory and offers // The foundation's own store, ADOPTED as the `postgres` module (novox/hq issue 051): one
// nothing to anything; the first thing that wants a database is the catalogue, next. // server holds the control plane's contexts and every module's database, rather than the
say("store — a database provider, which the foundation's own store is not") // foundation's store beside a second one a module raised. Adopted in place — the module names
if err := InstallFromCatalogue(ctx, o, permanentControl, "postgres", say); err != nil { // the container the foundation is already running, and the applier leaves it be (phase3.go).
say("store — the foundation's store, adopted as the postgres module: one server, not two")
if err := InstallStore(ctx, o, permanentControl, rewritten.Declaration, say); err != nil {
return result, failed(StepStore, err) return result, failed(StepStore, err)
} }
+1 -1
View File
@@ -53,7 +53,7 @@ func InstallBuilder(ctx context.Context, o Options, d Deps, control controlPlane
"This is the manifest that makes the builder an ordinary module. Without it the mesh "+ "This is the manifest that makes the builder an ordinary module. Without it the mesh "+
"has the image and no way to run it, so nothing can be built here", err) "has the image and no way to run it, so nothing can be built here", err)
} }
pinned, places, err := pinImage(manifest, published.Reference) pinned, places, err := pinImage(manifest, published.Reference, BuilderModule)
if err != nil { if err != nil {
return out, err return out, err
} }
+7 -7
View File
@@ -68,7 +68,7 @@ func InstallControlPlane(ctx context.Context, o Options, d Deps, control control
"have pivoted", err) "have pivoted", err)
} }
pinned, places, err := pinImage(manifest, image) pinned, places, err := pinImage(manifest, image, ControlPlaneModule)
if err != nil { if err != nil {
return out, err return out, err
} }
@@ -130,15 +130,15 @@ func InstallControlPlane(ctx context.Context, o Options, d Deps, control control
// carrying a real digest is one somebody pinned by hand, and quietly registering it would install a // carrying a real digest is one somebody pinned by hand, and quietly registering it would install a
// control plane that is not the image this machine just published — which is the one thing this // control plane that is not the image this machine just published — which is the one thing this
// step exists to guarantee. // step exists to guarantee.
func pinImage(manifest []byte, reference string) ([]byte, int, error) { func pinImage(manifest []byte, reference, module string) ([]byte, int, error) {
places := bytes.Count(manifest, []byte(placeholderDigest)) places := bytes.Count(manifest, []byte(placeholderDigest))
if places == 0 { if places == 0 {
return nil, 0, fmt.Errorf( return nil, 0, fmt.Errorf(
"the %s module's manifest carries no placeholder digest (%s), so there is nothing to "+ "the %s module's manifest carries no placeholder digest (%s), so there is nothing to "+
"pin to the image this machine just published.\n"+ "pin to the image this machine just published.\n"+
"A manifest already naming a digest was pinned by somebody else, to some other "+ "A manifest already naming a digest was pinned by somebody else, to some other "+
"build. Registering it would install a control plane that is not the one this "+ "build. Registering it would install a module that is not the one this "+
"installer carried and pushed", ControlPlaneModule, placeholderDigest) "installer carried and pushed", module, placeholderDigest)
} }
// The reference the registry gave back is `<registry>/<repository>@sha256:…`, and what the // The reference the registry gave back is `<registry>/<repository>@sha256:…`, and what the
// manifest holds is `<something>@sha256:0…0`. Replacing only the digest would leave the // manifest holds is `<something>@sha256:0…0`. Replacing only the digest would leave the
@@ -157,7 +157,7 @@ func pinImage(manifest []byte, reference string) ([]byte, int, error) {
if start < 0 { if start < 0 {
return nil, 0, fmt.Errorf( return nil, 0, fmt.Errorf(
"the %s module's manifest has a placeholder digest that is not inside a JSON "+ "the %s module's manifest has a placeholder digest that is not inside a JSON "+
"string, so the installer cannot tell what image it belongs to", ControlPlaneModule) "string, so the installer cannot tell what image it belongs to", module)
} }
out.Write(rest[:start+1]) out.Write(rest[:start+1])
out.WriteString(reference) out.WriteString(reference)
@@ -170,12 +170,12 @@ func pinImage(manifest []byte, reference string) ([]byte, int, error) {
var checked map[string]any var checked map[string]any
if err := json.Unmarshal(pinned, &checked); err != nil { if err := json.Unmarshal(pinned, &checked); err != nil {
return nil, 0, fmt.Errorf( return nil, 0, fmt.Errorf(
"pinning the %s module's image broke its manifest: %w", ControlPlaneModule, err) "pinning the %s module's image broke its manifest: %w", module, err)
} }
if bytes.Contains(pinned, []byte(placeholderDigest)) { if bytes.Contains(pinned, []byte(placeholderDigest)) {
return nil, 0, fmt.Errorf( return nil, 0, fmt.Errorf(
"the %s module's manifest still carries a placeholder digest after pinning", "the %s module's manifest still carries a placeholder digest after pinning",
ControlPlaneModule) module)
} }
return pinned, places, nil return pinned, places, nil
} }
+3 -3
View File
@@ -63,7 +63,7 @@ const pushedReference = "127.0.0.1:5000/mesh-controller@sha256:" +
// with no registry in front — which a runtime would go to the internet for, and this mesh's // with no registry in front — which a runtime would go to the internet for, and this mesh's
// control plane exists in no public registry by design. // control plane exists in no public registry by design.
func TestTheControlPlaneIsPinnedToWhatThisMeshsRegistryAssigned(t *testing.T) { func TestTheControlPlaneIsPinnedToWhatThisMeshsRegistryAssigned(t *testing.T) {
pinned, places, err := pinImage([]byte(theControlPlaneModule), pushedReference) pinned, places, err := pinImage([]byte(theControlPlaneModule), pushedReference, "mesh-controller")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -85,7 +85,7 @@ func TestTheControlPlaneIsPinnedToWhatThisMeshsRegistryAssigned(t *testing.T) {
func TestAManifestAlreadyPinnedByHandIsRefused(t *testing.T) { func TestAManifestAlreadyPinnedByHandIsRefused(t *testing.T) {
already := strings.Replace(theControlPlaneModule, placeholderDigest, already := strings.Replace(theControlPlaneModule, placeholderDigest,
"sha256:"+strings.Repeat("9", 64), 1) "sha256:"+strings.Repeat("9", 64), 1)
if _, _, err := pinImage([]byte(already), pushedReference); err == nil { if _, _, err := pinImage([]byte(already), pushedReference, "mesh-controller"); err == nil {
t.Fatal("a manifest already pinned to some other image was accepted") t.Fatal("a manifest already pinned to some other image was accepted")
} }
} }
@@ -100,7 +100,7 @@ func TestEveryPlaceTheManifestNamesTheImageIsPinned(t *testing.T) {
{"id": "migrate", "type": "container", "name": "mesh-controller-migrate", "run-once": true, {"id": "migrate", "type": "container", "name": "mesh-controller-migrate", "run-once": true,
"image": "mesh-controller@`+placeholderDigest+`", "args": ["migrate"]},`, 1) "image": "mesh-controller@`+placeholderDigest+`", "args": ["migrate"]},`, 1)
pinned, places, err := pinImage([]byte(twice), pushedReference) pinned, places, err := pinImage([]byte(twice), pushedReference, "mesh-controller")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+205
View File
@@ -0,0 +1,205 @@
package bootstrap
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/novox/mesh-host/internal/declaration"
)
// Phase three — nothing is special after installation (novox/hq issue 051).
//
// Genesis raises a store and a broker before any module system exists, because the control plane
// cannot ask provisioning for the store it keeps its own records in or the broker it is reached over
// (novox/hq ADR 0006). That leaves two servers behind: the foundation's, and a second one the
// `postgres`/`lavinmq` modules used to raise for other modules to use. This turns the foundation's
// own servers into those modules, so a mesh runs ONE postgres and ONE lavinmq — the control plane's
// contexts and every module's database in the same server (WBS 3.1/3.2).
//
// **Adopted in place, not replaced.** The control plane is stateless and is swapped for a fresh
// container (control.go); the store and broker hold the mesh's memory and its bus, so they are kept.
// The module declares a container with the same name, image and spec the foundation raised, and the
// applier — which keys on the container name and compares a spec digest (mesh-host internal/apply) —
// finds it already running and leaves it be. The image is pinned to the one the foundation is
// running, read from the bundle this installer produced, so the two specs are the same digest and
// nothing is recreated. A recreate happens only on a real upgrade, which is where a stated window
// belongs (WBS 3.3).
// StoreID and BrokerID are what the foundation bundle calls the two servers it raises; the modules
// that adopt them are found by these ids in the bundle this installer produced, the same way the
// control plane's own container is (ControlPlaneID).
const (
StoreID = "store"
BrokerID = "broker"
)
// InstallStore makes the foundation's store the `postgres` module, adopted in place.
//
// The order is InstallFromCatalogue's, with two additions the store needs and an ordinary provider
// does not: the server image is pinned to the one the foundation is already running (so the module's
// container is the same spec and is adopted, not a second one raised), and the superuser password —
// the foundation's, made at genesis — is carried in through `secret accept`, because the mesh cannot
// invent a credential that already created the databases (the same reasoning as the control plane's
// store connections, control.go deliverStores).
func InstallStore(ctx context.Context, o Options, control controlPlane,
foundation *declaration.Declaration, say func(string)) error {
const module = "postgres"
manifest, err := readManifest(o.Catalogue, module)
if err != nil {
return err
}
store, err := storeIn(foundation)
if err != nil {
return err
}
// The module adopts the running store rather than raising a second one, so its server container
// has to BE the foundation's — same name, same image. The applier keys on the name and compares
// a spec digest (internal/apply), so a mismatch here would not adopt the mesh's memory but
// replace it. Checked before anything is registered, so a drift between the two pinned upstream
// images (the catalogue's and the foundation bundle's) fails fast and by name, rather than
// surfacing as the mesh's store being torn down and recreated.
//
// Not rewritten to the foundation's: the server image travels through the builder, which reads
// the manifest from the repository and leaves a concrete image alone but would carry a rewrite
// nowhere. The two are kept equal at the source — one pinned postgres, named in both places.
if err := serverMatchesFoundation(manifest, store, module); err != nil {
return err
}
say(" adopting " + store.Name + " — the store the foundation raised, unchanged")
remote := "/" + module + "-module.json"
if err := control.carrying(ctx, module+"-module.json", manifest, remote); err != nil {
return err
}
if _, err := control.tell(ctx, "module", "add", remote); err != nil {
return err
}
say(" registered " + module)
if o.CatalogSource.Repository == "" {
return fmt.Errorf("%s has to be built and there is no --catalog-source to build it from: "+
"the catalogue CHECKOUT says what it is, the catalogue REPOSITORY is where a builder "+
"clones it", module)
}
say(" building " + module + " (the provisioner; the server is adopted, not built)")
if _, err := control.within(buildWait).tell(ctx, "build", o.CatalogSource.Repository,
"--path", "modules/"+module, "--ref", refOr(o.CatalogSource.Ref), "--wait", "1200s"); err != nil {
return err
}
if _, err := control.tell(ctx, "module", "issue", module, "--node", o.Node); err != nil {
say(" no account " + module + " — it declares nothing to say on the broker")
} else {
say(" account issued " + module)
}
if _, err := control.tell(ctx, "assign", o.Node, module); err != nil {
return err
}
// The superuser is the foundation's, made at genesis — carried in before the push, or the push
// would seal random bytes where a working password has to be and the provisioner would not open
// the store it is meant to manage.
if err := deliverSuperuser(ctx, o, control, module, store, say); err != nil {
return err
}
if _, err := pushNode(ctx, o, control, say); err != nil {
return err
}
say(" adopted mesh-store — the foundation's store is now the " + module + " module")
return nil
}
// storeIn finds the store container in the bundle this installer produced.
func storeIn(d *declaration.Declaration) (*declaration.Container, error) {
return foundationContainer(d, StoreID, "store")
}
// brokerIn finds the broker container in the bundle this installer produced.
func brokerIn(d *declaration.Declaration) (*declaration.Container, error) {
return foundationContainer(d, BrokerID, "broker")
}
func foundationContainer(d *declaration.Declaration, id, what string) (*declaration.Container, error) {
for _, r := range d.Resources {
if r.Identity() != id {
continue
}
container, ok := r.(*declaration.Container)
if !ok {
return nil, fmt.Errorf(
"this bundle's %q is a %s, not a container, so the %s module has nothing to adopt",
id, r.Kind(), what)
}
return container, nil
}
return nil, fmt.Errorf(
"this bundle names no %q, so there is no %s for a module to adopt. It declares: %s",
id, what, strings.Join(identities(d), ", "))
}
// serverMatchesFoundation checks that the module's adopting container is the one the foundation
// raised — same name, same image — so the applier reconciles it in place rather than replacing it.
func serverMatchesFoundation(manifest []byte, store *declaration.Container, module string) error {
var m struct {
Resources []struct {
Type string `json:"type"`
Name string `json:"name"`
Image string `json:"image"`
} `json:"resources"`
}
if err := json.Unmarshal(manifest, &m); err != nil {
return fmt.Errorf("the %s module's manifest is not readable: %w", module, err)
}
for _, r := range m.Resources {
if r.Type != "container" || r.Name != store.Name {
continue
}
if r.Image != store.Image {
return fmt.Errorf(
"the %s module's %q container is pinned to %q, and the foundation is running %q.\n"+
"The module adopts the foundation's store in place, so the two must name the same "+
"image — a different one would tear down the mesh's store and raise a new one on "+
"its data. Pin both to the same postgres image",
module, store.Name, r.Image, store.Image)
}
return nil
}
return fmt.Errorf(
"the %s module declares no container named %q, so it has nothing to adopt the foundation's "+
"store with. Its server container has to carry the name the foundation raised",
module, store.Name)
}
// deliverSuperuser carries the store's superuser password into the module.
//
// It is the foundation's, set on the bundle's store container at genesis; the mesh cannot invent a
// credential that already made the databases, so it goes in through `secret accept`, exactly as the
// control plane's store connections do (control.go deliverStores).
func deliverSuperuser(ctx context.Context, o Options, control controlPlane, module string,
store *declaration.Container, say func(string)) error {
const secret = "superuser"
value := strings.TrimSpace(store.Env["POSTGRES_PASSWORD"])
if value == "" {
return fmt.Errorf(
"the foundation's store names no POSTGRES_PASSWORD, so the %s module has no superuser "+
"to open it with — and the mesh cannot invent the one that already made the databases",
module)
}
at := "/accepting-" + secret
if err := control.carrying(ctx, "mesh-accepting-"+secret, []byte(value), at); err != nil {
return err
}
if _, err := control.tell(ctx, "secret", "accept", o.Node, module, secret, "--from", at); err != nil {
return err
}
say(" accepted " + secret + " — the store's superuser, as the foundation made it")
return nil
}