Files
mesh-host/internal/bootstrap/builder.go
T
jschoubben 9109a8c178 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
2026-09-16 21:04:06 +02:00

87 lines
3.5 KiB
Go

package bootstrap
import (
"context"
"fmt"
"strings"
)
// BuilderModule is the module that lets a mesh produce anything at all.
const BuilderModule = "builder"
// BuilderRepository is what its image is called in this mesh's own registry.
const BuilderRepository = "mesh-builder"
// Builder is what installing it produced.
type Builder struct {
Published Published
Installed Installed
// Account is true when a broker account was issued for it here.
Account bool
}
// InstallBuilder gives a fresh mesh the thing that makes everything else.
//
// **Without this a mesh can run and cannot produce** (novox/hq ADR 0073). Genesis ends with a
// control plane, a store, a queue and a registry — and almost every module in the catalogue is
// waiting to be built, because a manifest names artifacts and nothing has made them. The builder is
// one of the few things that cannot be built by the thing it is, so it is carried; and it is
// already here, because it is what built the control plane.
//
// So this is the same two acts the control plane went through, in the same order and for the same
// reason: publish the image so the mesh names it by a digest its own registry assigned rather than
// by a local identity nothing else can fetch, then install it as an ordinary module pinned to that.
//
// And then the part only it needs: a broker account. A builder takes work from a queue and
// announces what it made, and it holds its own credential for that like any module — asking for a
// generic one produced an account that could do neither, which is what made this worth its own step
// rather than a line in another.
func InstallBuilder(ctx context.Context, o Options, d Deps, control controlPlane, imageID string,
say func(string)) (Builder, error) {
var out Builder
published, err := publishAs(ctx, o, d, imageID, BuilderRepository, say)
out.Published = published
if err != nil {
return out, err
}
manifest, err := readManifest(o.Catalogue, BuilderModule)
if err != nil {
return out, fmt.Errorf("%w\n"+
"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)
}
pinned, places, err := pinImage(manifest, published.Reference, BuilderModule)
if err != nil {
return out, err
}
say(fmt.Sprintf(" pinned to %s, named in %d place(s)", published.Reference, places))
installed, err := registerAndAssign(ctx, o, control, BuilderModule, pinned, say)
out.Installed = installed
if err != nil {
return out, err
}
// Before the push, so the account is in the declaration the machine receives rather than in
// the one after it. A builder that arrives without its credential starts, finds nothing it may
// read, and waits — which looks exactly like a builder with no work.
account := o.Node + "-" + BuilderModule
if _, err := control.tell(ctx, "builder", "issue", account, "--node", o.Node); err != nil {
// Said and carried on. An account that already exists is the ordinary case on a re-run,
// and the push below is what makes either state true on the machine.
if !strings.Contains(err.Error(), "already") {
return out, fmt.Errorf("the builder has no broker account, so it can take no work: %w", err)
}
say(" broker account " + account + " — already issued")
} else {
out.Account = true
say(" broker account " + account + ", scoped to what it consumes and emits")
}
out.Installed.Pushed, err = pushNode(ctx, o, control, say)
return out, err
}