Install the build agent at genesis, the module that builds since ADR 0190 (hq issue 596)
mesh/merge-gate head d941d798e9df merged into trunk d34b4f32e17f — pass: the change touches no module of the mesh's graph
mesh/repo-check head d941d798e9df merged into trunk d34b4f32e17f — pass: its merge-check.sh passed
mesh/delivery delivered

Genesis's builder step read modules/builder from the catalogue, which retired it on 2026-10-03 for
the build agent (node-build-agent on every machine), so no genesis got past step 13. The step now
installs build-agent from the catalogue, puts the builder image it carried and published where the
manifest names the image it builds, and issues its bus account as any module's.
This commit is contained in:
jochen
2026-10-13 02:25:10 +02:00
parent 8431ac9656
commit d941d798e9
2 changed files with 146 additions and 21 deletions
+74 -3
View File
@@ -2,6 +2,7 @@ package bootstrap
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
@@ -44,15 +45,15 @@ func TestTheBuildersPackageRegistryPortIsASettingAndNotTheManifest(t *testing.T)
[]byte(theBuildersBinding), quietly); err != nil {
t.Fatal(err)
}
if got := c.settings["builder-settings.json"]; got != `{"serves":{"port":3100}}` {
if got := c.settings[BuilderModule+"-settings.json"]; got != `{"serves":{"port":3100}}` {
t.Errorf("the builder was told %q about the package registry's port", got)
}
// The manifest reaches the mesh as the catalogue wrote it. A port rewritten into it here is a
// port the next registration from the catalogue silently takes back.
if got := c.settings["builder-module.json"]; got != theBuildersBinding {
if got := c.settings[BuilderModule+"-module.json"]; got != theBuildersBinding {
t.Errorf("the installer changed the builder's manifest:\n%s", got)
}
set, push := c.index("settings set builder"), c.index("push anchor")
set, push := c.index("settings set "+BuilderModule), c.index("push anchor")
if set < 0 || push < 0 || set > push {
t.Fatalf("the port was not set before the push that raises the builder: %v", c.told)
}
@@ -167,3 +168,73 @@ func TestAForgeManifestTheCatalogueDoesNotHaveIsNamed(t *testing.T) {
t.Errorf("a missing forge manifest was not explained: %v", err)
}
}
// theCataloguesModule is the catalogue's own manifest of a module, never a copy: the clone beside a merge
// check (MESH_CHECK_BESIDE), where it must be; else a checkout beside this one; else the test skips aloud.
func theCataloguesModule(t *testing.T, module string) []byte {
t.Helper()
inside := filepath.Join("mesh-catalog", "modules", module, "module.json")
if root := os.Getenv("MESH_CHECK_BESIDE"); root != "" {
raw, err := os.ReadFile(filepath.Join(root, inside))
if err != nil {
t.Fatalf("the catalogue beside this check (MESH_CHECK_BESIDE=%s) has no %s: %v", root, inside, err)
}
return raw
}
raw, err := os.ReadFile(filepath.Join("..", "..", "..", inside))
if err != nil {
t.Skipf("NOT JUDGED: no catalogue beside this checkout and no MESH_CHECK_BESIDE; the merge check judges it")
}
return raw
}
// **Genesis installs the build agent the catalogue holds** (novox/hq issue 596, ADR 0190): the
// one-holder `builder` left the catalogue on 2026-10-03, and genesis stopped at step 13 asking for it.
// The catalogue's own manifest is read, and the image the installer carried is put where the manifest
// names the image it builds.
func TestGenesisInstallsTheCataloguesBuildAgent(t *testing.T) {
manifest := theCataloguesModule(t, BuilderModule)
const reference = "127.0.0.1:5000/mesh-builder@sha256:" + "4444444444444444444444444444444444444444444444444444444444444444"
pinned, places, err := pinTheCarriedBuilder(manifest, reference)
if err != nil {
t.Fatal(err)
}
var m struct {
Module string `json:"module"`
Claims []struct {
Name string `json:"name"`
} `json:"claims"`
Resources []map[string]any `json:"resources"`
}
if err := json.Unmarshal(pinned, &m); err != nil {
t.Fatal(err)
}
if m.Module != BuilderModule || len(m.Claims) == 0 || m.Claims[0].Name != "node-build-agent" {
t.Fatalf("genesis installs %q claiming %v; the build agent holds node-build-agent (ADR 0190)", m.Module, m.Claims)
}
running := 0
for _, r := range m.Resources {
if r["type"] != "container" {
continue
}
if _, still := r["artifact"]; still {
t.Errorf("the container %v still names an artifact nothing has built yet", r["name"])
}
if r["image"] == reference {
running++
}
}
if places != 1 || running != 1 {
t.Errorf("the carried builder runs in %d container(s) (%d place(s) said); the build agent runs one", running, places)
}
}
// A manifest that runs nothing from an image it builds has nowhere to put the carried builder, and is
// refused rather than registered running nothing.
func TestABuildAgentManifestWithNowhereForTheCarriedBuilderIsRefused(t *testing.T) {
_, _, err := pinTheCarriedBuilder([]byte(`{"module":"build-agent","resources":[{"id":"server","type":"container","name":"x","artifact":"server"}],"build":{"artifacts":[{"name":"server","kind":"bundle"}]}}`),
"127.0.0.1:5000/mesh-builder@sha256:"+strings.Repeat("4", 64))
if err == nil || !strings.Contains(err.Error(), "nowhere to put") {
t.Fatalf("a manifest with no image artifact was pinned: %v", err)
}
}