Files
mesh-host/internal/bootstrap/rewrite_test.go
T
jschoubben af953dbb0d bootstrap: the temporary control plane gets a temporary name
The substrate raises a control plane and a module will later declare one. If both
are called `mesh-control` then for one moment two owners hold one container, and
the host — which tracks what it owns — has no way to stop owning something without
destroying it. That looked like a missing mechanism.

It is a naming problem. The substrate's container becomes `temp-mesh-control` and
the module's keeps the plain name: two containers, two owners, nothing to hand
over. Dropping the temporary one from the bundle at the end is then destruction by
omission, which is what the host already does to anything that leaves a
declaration — and the right end for something named "temp" (novox/hq ADR 0067).

The rename is textual and matches the QUOTED name, so the `mesh-control` inside
the image reference is not caught by it. Read back afterwards: the produced bundle
must call it the temporary name, and no other container may have been renamed.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-10 23:59:32 +02:00

322 lines
12 KiB
Go

package bootstrap
import (
"os"
"strings"
"testing"
"github.com/novox/mesh-host/internal/declaration"
)
// Each test names the decision it defends (novox/hq ADR 0017).
const (
held = "sha256:1111111111111111111111111111111111111111111111111111111111111111"
otherHeld = "sha256:2222222222222222222222222222222222222222222222222222222222222222"
)
// theRealBundle is this repository's own substrate example, used rather than a fixture.
//
// A fixture would agree with whatever this code does. The example is what an installer is actually
// pointed at, it names the control plane twice, and it is the file that changes when the substrate
// changes — so a rewrite that stops working on it is a rewrite that has stopped working.
func theRealBundle(t *testing.T) []byte {
t.Helper()
raw, err := os.ReadFile("../../examples/substrate-first-node.lock")
if err != nil {
t.Fatalf("reading the substrate example: %v", err)
}
return raw
}
func TestTheControlPlaneIsNamedByTheImageThisMachineHolds(t *testing.T) {
out, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
if !out.Changed {
t.Error("the rewrite reported nothing changed, and the template named a registry image")
}
control, err := controlPlaneIn(out.Declaration)
if err != nil {
t.Fatal(err)
}
if control.Image != held {
t.Errorf("the control plane is %q, want %q", control.Image, held)
}
}
// **Every place the bundle names that image, not only the container.**
//
// The substrate names the control plane's image twice: the container that runs `serve`, and the
// action that runs `migrate` to create the contexts' schemas. Rewriting only the container leaves
// the migration pointing at an image no registry serves, and the apply dies in the middle — after
// the store is up and before the broker. This is the test that would have caught that.
func TestEveryPlaceTheBundleNamesTheControlPlaneIsRewritten(t *testing.T) {
template := theRealBundle(t)
out, err := Rewrite(template, held)
if err != nil {
t.Fatal(err)
}
if out.Places < 2 {
t.Fatalf("the control plane's image was found in %d place(s); the substrate names it in "+
"the container AND in the migration action", out.Places)
}
if remaining := strings.Count(string(out.Bundle), out.Was); remaining != 0 {
t.Errorf("the produced bundle still names %q in %d place(s)", out.Was, remaining)
}
if got := strings.Count(string(out.Bundle), held); got != out.Places {
t.Errorf("the produced bundle names the held image %d time(s), and %d were replaced",
got, out.Places)
}
}
// Third-party images are somebody else's, at somebody else's registry, and the installer has no
// business touching them (novox/hq ADR 0006).
func TestPostgresAndTheBrokerAreLeftExactlyAsTheyWere(t *testing.T) {
template := theRealBundle(t)
out, err := Rewrite(template, held)
if err != nil {
t.Fatal(err)
}
produced := containerImages(out.Declaration)
for _, id := range []string{"store", "broker"} {
image, named := produced[id]
if !named {
t.Fatalf("the substrate example no longer declares a %q container", id)
}
// Compared against the template's own text rather than against an expectation written
// here: what is being defended is "unchanged", and the template is the only thing that
// knows what it said.
if !strings.Contains(string(template), `"image": "`+image+`"`) {
t.Errorf("%s is now %q, which the template does not say", id, image)
}
if strings.HasPrefix(image, "sha256:") {
t.Errorf("%s was rewritten to an image this machine holds, and nothing holds it", id)
}
}
// And the claim in the report is the same claim, so a person reading it is reading evidence.
if len(out.Kept) != 2 {
t.Errorf("the rewrite reports %d untouched image(s): %v", len(out.Kept), out.Kept)
}
}
// A bundle with no control plane raises a store and a broker and no mesh. Refused, because
// applying it would succeed and leave a machine that looks bootstrapped.
func TestABundleThatNamesNoControlPlaneIsRefused(t *testing.T) {
template := []byte(`{"declaration":1,"resources":[
{"id":"store","type":"container","name":"mesh-store","image":"postgres@sha256:` +
strings.Repeat("7", 64) + `"}
]}`)
_, err := Rewrite(template, held)
if err == nil {
t.Fatal("a bundle with no control plane was rewritten and would have been applied")
}
// The refusal has to be actionable: it says what the bundle DID declare, so somebody can see
// they pointed it at the wrong file or misspelled the id.
if !strings.Contains(err.Error(), ControlPlaneID) || !strings.Contains(err.Error(), "store") {
t.Errorf("the refusal names neither what was wanted nor what was there: %v", err)
}
}
func TestAControlPlaneThatIsNotAContainerIsRefused(t *testing.T) {
template := []byte(`{"declaration":1,"resources":[
{"id":"control-plane","type":"package","package":"mesh-control"}
]}`)
if _, err := Rewrite(template, held); err == nil {
t.Fatal("a control plane declared as a package was accepted, and a package has no image")
}
}
// Idempotence. This program is run over and over while somebody gets a machine working, and the
// second run must be able to say the bundle already names this image rather than reporting a
// rewrite it did not perform.
func TestRewritingABundleThatAlreadyNamesTheImageChangesNothing(t *testing.T) {
first, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
second, err := Rewrite(first.Bundle, held)
if err != nil {
t.Fatal(err)
}
if second.Changed {
t.Error("re-running the rewrite reported a change, and the image was already the one held")
}
if string(second.Bundle) != string(first.Bundle) {
t.Error("re-running the rewrite produced different bytes")
}
if second.Was != held {
t.Errorf("the second run reports it replaced %q; it replaced nothing", second.Was)
}
}
// A DIFFERENT image, though, must move — the ordinary case of a new control plane being installed
// over an old one. "Already correct" must not be the same code path as "already ran".
func TestANewImageReplacesAnOlderHeldOne(t *testing.T) {
first, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
second, err := Rewrite(first.Bundle, otherHeld)
if err != nil {
t.Fatal(err)
}
if !second.Changed || second.Was != held || second.Now != otherHeld {
t.Errorf("a new image did not replace the old one: changed=%v was=%q now=%q",
second.Changed, second.Was, second.Now)
}
}
// The produced bundle is meant to be READ. Re-serialising a parsed declaration would drop every
// comment in the template, and the substrate example is mostly comments — each one recording why a
// resource is the way it is, several of them paid for in the lab.
func TestTheProducedBundleKeepsTheTemplatesComments(t *testing.T) {
template := theRealBundle(t)
out, err := Rewrite(template, held)
if err != nil {
t.Fatal(err)
}
const remembered = "NAMED VOLUME"
if !strings.Contains(string(out.Bundle), remembered) {
t.Errorf("the produced bundle lost the template's comments; %q is gone", remembered)
}
}
// The installer must refuse its own bad input in its own words, rather than writing a bundle and
// letting the host refuse a declaration somebody did not write.
func TestSomethingThatIsNotAnImageIdIsRefused(t *testing.T) {
for _, bad := range []string{
"",
"mesh-control:latest",
"sha256:abc",
"sha256:" + strings.Repeat("1", 63),
"sha256:" + strings.Repeat("g", 64),
"mesh-control@sha256:" + strings.Repeat("1", 64),
} {
if _, err := Rewrite(theRealBundle(t), bad); err == nil {
t.Errorf("image id %q was accepted", bad)
}
}
}
// The address every enrolment token will carry is reported and never invented. It differs per
// machine and it is silently fatal when wrong: a node enrols against a dead address and nothing
// says so until it fails to come back.
func TestTheAddressNodesWillDialIsReportedAndNotRewritten(t *testing.T) {
template := theRealBundle(t)
out, err := Rewrite(template, held)
if err != nil {
t.Fatal(err)
}
if out.BrokerAddress == "" {
t.Fatal("the substrate example no longer says what address enrolling nodes will dial")
}
if !strings.Contains(string(out.Bundle), out.BrokerAddress) {
t.Errorf("the produced bundle no longer carries %q — it was rewritten, and nothing here "+
"knows this machine's address", out.BrokerAddress)
}
}
// ---------------------------------------------------------------------------------------------
// The rename, which is what makes genesis a pivot rather than a handover (novox/hq ADR 0067).
// ---------------------------------------------------------------------------------------------
// **This is the test that dissolves the blocker.** The substrate raises a control plane and a
// module later declares one; if both are called `mesh-control` then for one moment two owners hold
// one container, and the host — which tracks what it owns — has no way to stop owning something
// without destroying it. Nothing here invents such a mechanism. The substrate's container is
// called `temp-mesh-control` instead, and there are simply two containers.
func TestTheSubstratesControlPlaneMovesOutOfTheModulesWay(t *testing.T) {
out, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
if !out.Renamed {
t.Error("the rewrite reported nothing renamed, and the template named it mesh-control")
}
if out.TempName != "temp-mesh-control" {
t.Errorf("the substrate's control plane is called %q", out.TempName)
}
control, err := controlPlaneIn(out.Declaration)
if err != nil {
t.Fatal(err)
}
if control.Name != out.TempName {
t.Errorf("the produced bundle calls it %q, want %q", control.Name, out.TempName)
}
// And the plain name is free, which is the whole point: it belongs to the module now.
for _, name := range containerNames(out.Declaration) {
if name == ControlPlaneModule {
t.Errorf("the produced bundle still declares a container called %q, which the module "+
"will also declare", ControlPlaneModule)
}
}
}
// The image reference contains the string `mesh-control` too, and it is not a container name. A
// substitution that caught it would produce `…/temp-mesh-control@sha256:…`, which no registry
// serves — and it would be found inside a pull rather than here.
func TestTheImageReferenceIsNotMistakenForTheContainerName(t *testing.T) {
out, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(out.Bundle), TempPrefix+"mesh-control@") ||
strings.Contains(string(out.Bundle), "/"+TempPrefix+"mesh-control") {
t.Error("the rename reached inside an image reference")
}
}
// Everything else keeps the name the substrate gave it. The store and the broker are containers
// too, and a rename that moved them would leave a machine whose substrate the host cannot find.
func TestRenamingTheControlPlaneLeavesEveryOtherContainerAlone(t *testing.T) {
before, err := declaration.ParseFileTrusted(theRealBundle(t))
if err != nil {
t.Fatal(err)
}
out, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
was, now := containerNames(before), containerNames(out.Declaration)
for id, name := range was {
if id == ControlPlaneID {
continue
}
if now[id] != name {
t.Errorf("%s was renamed from %q to %q", id, name, now[id])
}
}
}
// A re-run against a bundle this installer produced renames nothing and says so. The installer is
// run over and over while somebody gets a machine working, and a step that could not tell "already
// done" from "just done" makes the second run indistinguishable from the first.
func TestRewritingABundleThisAlreadyProducedRenamesNothing(t *testing.T) {
first, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
second, err := Rewrite(first.Bundle, held)
if err != nil {
t.Fatal(err)
}
if second.Renamed {
t.Error("a bundle already naming temp-mesh-control was renamed again")
}
if second.TempName != first.TempName {
t.Errorf("the second pass calls it %q and the first called it %q",
second.TempName, first.TempName)
}
if string(second.Bundle) != string(first.Bundle) {
t.Error("rewriting a produced bundle changed it")
}
}