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
This commit is contained in:
@@ -4,6 +4,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-host/internal/declaration"
|
||||
)
|
||||
|
||||
// Each test names the decision it defends (novox/hq ADR 0017).
|
||||
@@ -221,3 +223,99 @@ func TestTheAddressNodesWillDialIsReportedAndNotRewritten(t *testing.T) {
|
||||
"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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user