From 8e12b3c9e400b3e6082832a43521c3a059c9574d Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 31 Aug 2026 17:33:34 +0200 Subject: [PATCH] Name the decisions these tests defend, and check the bundle at all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From auditing the decision records: of 28, only 12 were named by any test, so "which decisions are defended" could not be answered without reading everything. ADR 0017 says a test names the decision it defends — that rule was itself unenforced. Most of the gap was citation, not coverage. Drift detection was tested in several places without naming ADR 0011; the archive refusal without naming 0012; forged declarations without naming 0002. Named now, so the question is answerable by grep. The bundle was the real gap: nothing tested substrate-first-node.lock at all. It is what a machine becomes when there is no mesh to ask — the one declaration applied with nothing to verify it against — and it was edited by hand and read by nothing but a running host. Two tests now assert what it carries: exactly postgres, lavinmq and the control plane. That defends ADR 0028, which removed the object store from the substrate after it had been a member for months on the strength of "it cannot grant itself a bucket" — true, and the answer to only half the test. Nothing counted what the bundle held. Fault-injected, and the first attempt did not bite: the injection landed on a comment line, which stripComments discards. Injecting into the image field fails as it should. --- examples/bundle_test.go | 69 +++++++++++++++++++++++++++++++ internal/apply/apply_test.go | 4 ++ internal/apply/vocabulary_test.go | 5 +++ internal/link/messages_test.go | 5 +++ 4 files changed, 83 insertions(+) create mode 100644 examples/bundle_test.go diff --git a/examples/bundle_test.go b/examples/bundle_test.go new file mode 100644 index 0000000..47ce39d --- /dev/null +++ b/examples/bundle_test.go @@ -0,0 +1,69 @@ +// Package examples checks the bundles shipped in this directory. +// +// **Nothing checked them before.** `substrate-first-node.lock` is what a machine becomes when +// there is no mesh to ask — the one declaration applied with nothing to verify it against — and +// it was edited by hand and read by nobody but a running host. +package examples + +import ( + "os" + "sort" + "strings" + "testing" + + "github.com/novox/mesh-host/internal/declaration" +) + +func bundle(t *testing.T) *declaration.Declaration { + t.Helper() + raw, err := os.ReadFile("substrate-first-node.lock") + if err != nil { + t.Fatal(err) + } + d, err := declaration.ParseFileTrusted(raw) + if err != nil { + t.Fatalf("the bundle a first node applies does not parse: %v", err) + } + return d +} + +// Defends novox/hq ADR 0028: the substrate supplies the control plane and nothing else. +// +// The object store was substrate for months on the strength of "it cannot grant itself a bucket", +// which answers half the test. The control plane never needed one, and nothing noticed because +// nothing counted what the bundle holds. +func TestTheBundleCarriesTheSubstrateAndTheControlPlaneAndNothingElse(t *testing.T) { + var images []string + for _, r := range bundle(t).Resources { + c, ok := r.(*declaration.Container) + if !ok { + continue + } + // The registry the images come from is the lab's, and is not what this asserts. + name := c.Image + if i := strings.LastIndex(name, "/"); i >= 0 { + name = name[i+1:] + } + images = append(images, strings.SplitN(name, "@", 2)[0]) + } + sort.Strings(images) + + want := []string{"lavinmq", "mesh-control", "postgres"} + if strings.Join(images, ",") != strings.Join(want, ",") { + t.Fatalf("the bundle carries %v; expected exactly %v.\n\n"+ + "Adding one is a change to what every first node becomes, and to ADR 0006's "+ + "membership — take it deliberately, not by editing a lock file.", images, want) + } +} + +// The control plane is in the bundle, and the design overlooked it once by reasoning about +// substrate services rather than counting containers (novox/hq 03-DESIGN/01-to-be/07). +func TestTheControlPlaneIsCarriedToo(t *testing.T) { + for _, r := range bundle(t).Resources { + if c, ok := r.(*declaration.Container); ok && strings.Contains(c.Image, "mesh-control") { + return + } + } + t.Fatal("nothing in the bundle starts the control plane, so the machine would raise a " + + "substrate and stop") +} diff --git a/internal/apply/apply_test.go b/internal/apply/apply_test.go index 01e587e..034e041 100644 --- a/internal/apply/apply_test.go +++ b/internal/apply/apply_test.go @@ -57,6 +57,10 @@ func TestApplyingTwiceChangesNothingTheSecondTime(t *testing.T) { } } +// Defends novox/hq ADR 0011: a managed file is generated onto a node and never edited there. +// +// Not by overwriting silently — by noticing. An edit that vanishes without a word is how somebody +// spends an afternoon re-fixing a bug they already fixed. func TestADriftedMachineIsReturned(t *testing.T) { // The other half of idempotence, and the half that matters: converging is not "do nothing // if the state file says it was done". The machine is read, not the record. diff --git a/internal/apply/vocabulary_test.go b/internal/apply/vocabulary_test.go index 94e95ba..d654093 100644 --- a/internal/apply/vocabulary_test.go +++ b/internal/apply/vocabulary_test.go @@ -204,6 +204,11 @@ func TestAnUnpackedArchiveIsNotFetchedAgainForNothing(t *testing.T) { } } +// Defends novox/hq ADR 0012: the mesh creates no symlinks — a derived file is a copy. +// +// The archive is the one path where a symlink could arrive without anybody declaring it, which is +// why the refusal lives here. ADR 0012 was earned by production data loss through a symlink +// resolved inside a container volume path. func TestAnArchiveWithSomethingThatIsNotAFileIsRefused(t *testing.T) { // A theme needing a symlink would otherwise arrive silently incomplete, and a device node in // an archive is not something to unpack quietly onto a machine. diff --git a/internal/link/messages_test.go b/internal/link/messages_test.go index 970a2aa..7983221 100644 --- a/internal/link/messages_test.go +++ b/internal/link/messages_test.go @@ -44,6 +44,11 @@ func TestTheMeshsOwnDeclarationIsApplied(t *testing.T) { } } +// Defends novox/hq ADR 0002: everything reaching a node arrives over the broker — and therefore +// ADR 0004's consequence, that the broker is not trusted to say who is speaking. +// +// A transport nobody authenticates per-message would let whatever holds the connection attribute +// a declaration to any node it liked. func TestAForgedDeclarationIsNeverApplied(t *testing.T) { // The check that stands between "the mesh changes this machine" and "anybody does". The host // applies whatever the link delivers, so a forged declaration is the whole machine.