From 25d2fe1308ee6ba93915b0ef78654145a1e9333c Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 14 Sep 2026 17:32:43 +0200 Subject: [PATCH] Only a thing built and never run is unassignable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The check read "declares no resources" as "runs nowhere", and those are not the same. The private network declares no resources either — the control plane computes them when it composes a machine's declaration — and it is assigned to every machine that has to reach another one. Refusing it stopped a four-machine bed at its first assignment. The signal is narrower: it builds an artifact and places nothing. Made a function of its own, because a judgement with a wrong answer this expensive should be testable without a database — nothing guarded it, which is how it shipped. --- internal/inventory/buildinput_test.go | 44 +++++++++++++++++++++++++++ internal/inventory/catalogue.go | 17 ++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 internal/inventory/buildinput_test.go diff --git a/internal/inventory/buildinput_test.go b/internal/inventory/buildinput_test.go new file mode 100644 index 0000000..fd67283 --- /dev/null +++ b/internal/inventory/buildinput_test.go @@ -0,0 +1,44 @@ +package inventory + +import ( + "testing" + + "github.com/novox/mesh-control/internal/catalogue" +) + +// The image every module is compiled on top of is built and never run. +func TestAThingBuiltAndNeverRunIsABuildInput(t *testing.T) { + m := catalogue.Manifest{ + Module: "mesh-tools", + Build: &catalogue.Build{Artifacts: []catalogue.Artifact{{Name: "runtime", Kind: "image"}}}, + } + if !IsBuildInput(m) { + t.Fatal("a module that builds an artifact and places nothing is assignable, so `assign` " + + "succeeds and the machine is sent a declaration containing nothing of it") + } +} + +// The private network declares no resources either, and is assigned to every machine that has to +// reach another one. +// +// **This is the one that matters.** Reading "no resources" as "runs nowhere" refused it, and a +// four-machine bed stopped dead at the first assignment — the control plane computes its resources +// when it composes a declaration, so there is nothing in its manifest to see. +func TestTheMeshsOwnNetworkIsNotABuildInput(t *testing.T) { + if IsBuildInput(catalogue.Manifest{Module: "networking"}) { + t.Fatal("the private network was refused as a thing that runs nowhere; nothing could " + + "then reach anything else") + } +} + +// And an ordinary module that both builds and runs is assignable, which is most of them. +func TestAModuleThatBuildsAndRunsIsAssignable(t *testing.T) { + m := catalogue.Manifest{ + Module: "postgres", + Build: &catalogue.Build{Artifacts: []catalogue.Artifact{{Name: "runtime", Kind: "image"}}}, + Resources: []map[string]any{{"id": "server", "type": "container"}}, + } + if IsBuildInput(m) { + t.Fatal("a module that builds something and also runs it was refused") + } +} diff --git a/internal/inventory/catalogue.go b/internal/inventory/catalogue.go index 8a3401b..6d23461 100644 --- a/internal/inventory/catalogue.go +++ b/internal/inventory/catalogue.go @@ -906,7 +906,7 @@ func (i *Inventory) runsSomewhere(ctx context.Context, module string) error { // fault and refusing the assignment here would report it as the wrong one. return nil } - if m.Computed != "" || len(m.Resources) > 0 { + if !IsBuildInput(m) { return nil } return fmt.Errorf( @@ -914,3 +914,18 @@ func (i *Inventory) runsSomewhere(ctx context.Context, module string) error { "builds and other modules are built on top of, not something a machine runs — "+ "`module list` shows what it produces", module) } + +// IsBuildInput reports whether a module exists to be built and never to be run. +// +// **The signal is that it builds something and places nothing** — not merely that it declares no +// resources. Those are different, and confusing them refused a module the mesh itself ships: the +// private network declares no resources either, because the control plane computes them when it +// composes a machine's declaration, and it is assigned to every machine that has to reach another +// one. Refusing it stopped a four-machine bed dead. +// +// Its own function because it is a judgement rather than a lookup, and a judgement with a wrong +// answer this expensive should be testable without a database. +func IsBuildInput(m catalogue.Manifest) bool { + builds := m.Build != nil && len(m.Build.Artifacts) > 0 + return builds && m.Computed == "" && len(m.Resources) == 0 +} -- 2.54.0