The packages port given at genesis is a module's setting, like every other

Every foundation port given at genesis became a per-node setting of the module
that binds it, except the package registry's: that one was fixed by rewriting
the builder's manifest when the installer registered it. Registering the builder
again from the catalogue undid it, and the forge's own module, when it took the
bootstrap forge over, came up on the catalogue's port — which on a machine where
a predecessor holds 3000 points the builder at the predecessor's forge.

So the rewrite is gone, and the port is recorded twice as a setting, both from
the one input:

- the forge's module is registered at genesis — not assigned, nothing of it runs
  — so the controller has something to hold `{"ports": {"3000": <given>}}`
  against. Assigning the forge later raises it on the port this machine was
  given, and its container, its filter rule, its opening, what it serves and
  what consumers are told all read it from there.
- the builder is given `{"serves": {"port": <given>}}`, which merges into the
  binding it carries in place of one nothing can resolve yet.

A genesis on the catalogue's port records nothing and registers nothing, so it
does exactly what it did before.

novox/hq 04-ISSUES/085, ADR 0100
This commit is contained in:
2026-09-22 21:40:02 +02:00
parent 0db1fbdb3b
commit c4ce57997e
6 changed files with 179 additions and 70 deletions
+102 -36
View File
@@ -1,16 +1,21 @@
package bootstrap
import (
"encoding/json"
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// Defends novox/hq ADR 0100: a port given for the package registry at genesis reaches the one
// thing that dials it by a fixed number, the builder's package binding.
// Defends novox/hq 04-ISSUES/085: the port given for the package registry at genesis is a setting
// of a module and not text in a manifest, so registering that manifest again does not put the
// catalogue's number back.
// The builder's package binding exactly as the catalogue's manifest carries it.
const builderManifest = `{
// theBuildersBinding is the resource the builder carries in place of a binding nothing can resolve
// yet, exactly as the catalogue's manifest has it — settable, with the port as its only default.
const theBuildersBinding = `{
"module": "builder",
"resources": [
{
@@ -18,52 +23,113 @@ const builderManifest = `{
"type": "file",
"path": "/var/lib/mesh/builder/package-registry.json",
"mode": "0600",
"merge": "json",
"protected": ["provision", "from", "as"],
"content": "{\"provision\": \"package-registry\", \"from\": \"gitea\", \"at\": \"127.0.0.1\", \"as\": \"mesh-builder\", \"serves\": {\"scheme\": \"http\", \"port\": 3000, \"npm-path\": \"/api/packages/novox/npm/\"}}\n"
}
]
}`
func bindingPort(t *testing.T, manifest []byte) float64 {
func recording(t *testing.T) (*controlRecorder, controlPlane) {
t.Helper()
var m struct {
Resources []struct {
Content string `json:"content"`
} `json:"resources"`
}
if err := json.Unmarshal(manifest, &m); err != nil {
t.Fatal(err)
}
var binding struct {
Serves struct {
Port float64 `json:"port"`
} `json:"serves"`
}
if err := json.Unmarshal([]byte(m.Resources[0].Content), &binding); err != nil {
t.Fatal(err)
}
return binding.Serves.Port
t.Setenv("TMPDIR", t.TempDir())
c := &controlRecorder{settings: map[string]string{}}
return c, controlPlane{container: "temp-mesh-controller", run: c.run, timeout: time.Second}
}
func TestTheBuilderFollowsThePackageRegistrysGivenPort(t *testing.T) {
got, err := followPackagesPort([]byte(builderManifest), 3100)
if err != nil {
func TestTheBuildersPackageRegistryPortIsASettingAndNotTheManifest(t *testing.T) {
c, control := recording(t)
o := Options{Node: "anchor", Ports: FoundationPorts{Packages: 3100}, Wait: time.Second}
if _, err := installModule(context.Background(), o, control, BuilderModule,
[]byte(theBuildersBinding), quietly); err != nil {
t.Fatal(err)
}
if p := bindingPort(t, got); p != 3100 {
t.Errorf("the builder's binding dials %v, not the port given", p)
if got := c.settings["builder-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 {
t.Errorf("the installer changed the builder's manifest:\n%s", got)
}
set, push := c.index("settings set builder"), 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)
}
}
func TestTheBuilderOnTheDefaultPortIsUnchanged(t *testing.T) {
got, err := followPackagesPort([]byte(builderManifest), 3000)
if err != nil || string(got) != builderManifest {
t.Errorf("the default port changed the manifest: %v", err)
func TestTheBuilderOnTheDefaultPackagesPortIsToldNothing(t *testing.T) {
c, control := recording(t)
o := Options{Node: "anchor", Wait: time.Second}
if _, err := installModule(context.Background(), o, control, BuilderModule,
[]byte(theBuildersBinding), quietly); err != nil {
t.Fatal(err)
}
if c.index("settings") >= 0 {
t.Errorf("a genesis on the catalogue's packages port set a setting: %v", c.told)
}
}
func TestABuilderManifestThatNoLongerNamesThePortIsRefused(t *testing.T) {
moved := strings.Replace(builderManifest, `\"port\": 3000`, `\"port\": 3001`, 1)
if _, err := followPackagesPort([]byte(moved), 3100); err == nil || !strings.Contains(err.Error(), "--packages-port") {
t.Errorf("a manifest the port cannot reach was accepted: %v", err)
// aCatalogueWith writes a checkout holding one module's manifest, which is all the installer reads
// a catalogue for.
func aCatalogueWith(t *testing.T, module, manifest string) string {
t.Helper()
dir := t.TempDir()
at := filepath.Join(dir, catalogueDir, module)
if err := os.MkdirAll(at, 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(at, "module.json"), []byte(manifest), 0o600); err != nil {
t.Fatal(err)
}
return dir
}
const theForgesManifest = `{"module": "gitea", "version": "1"}`
func TestThePackagesPortIsTheForgeModulesSettingOnThisNode(t *testing.T) {
c, control := recording(t)
o := Options{Node: "anchor", Catalogue: aCatalogueWith(t, ForgeModule, theForgesManifest),
Ports: FoundationPorts{Packages: 3100}, Wait: time.Second}
if err := recordTheForgesPort(context.Background(), o, control, quietly); err != nil {
t.Fatal(err)
}
if got := c.settings["gitea-settings.json"]; got != `{"ports":{"3000":3100}}` {
t.Errorf("the forge module was told %q about its port", got)
}
// Registered so there is something to hold the setting against, and never assigned: the forge
// module cannot run until the base it stands on has been built.
add, set := c.index("module add"), c.index("settings set gitea")
if add < 0 || set < 0 || add > set {
t.Fatalf("the forge's setting was recorded against a module the mesh does not know: %v", c.told)
}
if c.index("assign") >= 0 {
t.Errorf("genesis assigned the forge module: %v", c.told)
}
if !strings.Contains(c.told[set], "--node anchor") {
t.Errorf("the forge's port was set for the whole mesh, not this machine: %s", c.told[set])
}
}
func TestAGenesisOnTheCataloguesPackagesPortRegistersNoForge(t *testing.T) {
c, control := recording(t)
o := Options{Node: "anchor", Catalogue: aCatalogueWith(t, ForgeModule, theForgesManifest),
Wait: time.Second}
if err := recordTheForgesPort(context.Background(), o, control, quietly); err != nil {
t.Fatal(err)
}
if len(c.told) != 0 {
t.Errorf("a genesis on the defaults told the mesh something new: %v", c.told)
}
}
func TestAForgeManifestTheCatalogueDoesNotHaveIsNamed(t *testing.T) {
_, control := recording(t)
o := Options{Node: "anchor", Catalogue: t.TempDir(),
Ports: FoundationPorts{Packages: 3100}, Wait: time.Second}
err := recordTheForgesPort(context.Background(), o, control, quietly)
if err == nil || !strings.Contains(err.Error(), ForgeModule) ||
!strings.Contains(err.Error(), "package registry") {
t.Errorf("a missing forge manifest was not explained: %v", err)
}
}