From a493c90c0d78fdfaeb786838f41a93de6c8cc385 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 23 Oct 2026 16:50:03 +0200 Subject: [PATCH] Require node-tools in the three build modules, so the delivery plan puts the tool runner they pull asks through before them on every node (issue 1911) --- .../mesh-build-go/cmd/mesh-build-go/forge_test.go | 5 +++-- .../cmd/mesh-build-go/manifest_test.go | 9 +++++++++ modules/mesh-build-go/module.json | 3 ++- .../cmd/mesh-build-image/compose_test.go | 3 ++- .../cmd/mesh-build-image/forge_test.go | 2 +- .../cmd/mesh-build-image/manifest_test.go | 15 +++++++++++++-- modules/mesh-build-image/module.json | 3 ++- .../cmd/mesh-build-node/manifest_test.go | 9 +++++++++ modules/mesh-build-node/module.json | 3 ++- 9 files changed, 43 insertions(+), 9 deletions(-) diff --git a/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go b/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go index 3abc2400..89ff15c8 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/forge_test.go @@ -144,8 +144,9 @@ func TestForgeFilesRenderFromTheBoundValuesInTheModulesOwnHome(t *testing.T) { func TestManifestRequiresForgeAccountKeepsKeysAndTakesFilesFromTemplates(t *testing.T) { m := readForgeManifest(t) // artifact-store: the module pushes a done build's archive itself (ADR 0498 §2). - if len(m.Requires) != 3 || m.Requires[0] != "forge-account" || m.Requires[1] != "go-toolchain" || m.Requires[2] != "artifact-store" { - t.Errorf("requires = %v, want [forge-account go-toolchain artifact-store]", m.Requires) + // node-tools: the module pulls its asks through the tool runner's seat pull (1911). + if len(m.Requires) != 4 || m.Requires[0] != "forge-account" || m.Requires[1] != "go-toolchain" || m.Requires[2] != "artifact-store" || m.Requires[3] != "node-tools" { + t.Errorf("requires = %v, want [forge-account go-toolchain artifact-store node-tools]", m.Requires) } keeps := false for _, s := range m.State { diff --git a/modules/mesh-build-go/cmd/mesh-build-go/manifest_test.go b/modules/mesh-build-go/cmd/mesh-build-go/manifest_test.go index ace29333..7ddfe322 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/manifest_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/manifest_test.go @@ -176,3 +176,12 @@ func TestTheModuleRequiresTheArtifactStoreAndIsToldItsAddress(t *testing.T) { t.Errorf("files/registry is %q (%v), want the store seat's reach", raw, err) } } + +// 1911: the module pulls its asks through node-tools' seat pull (mesh-tools, R4g), so it requires node-tools on its node +// and the delivery plan puts node-tools first: a node's build module never pulls through a tool runner that cannot. +func TestTheModuleRequiresNodeToolsItPullsThrough(t *testing.T) { + m := readManifest(t) + if !slices.Contains(m.Requires, "node-tools") { + t.Errorf("requires %v lack node-tools", m.Requires) + } +} diff --git a/modules/mesh-build-go/module.json b/modules/mesh-build-go/module.json index db0dbe1c..4d2b080a 100644 --- a/modules/mesh-build-go/module.json +++ b/modules/mesh-build-go/module.json @@ -51,7 +51,8 @@ "requires": [ "forge-account", "go-toolchain", - "artifact-store" + "artifact-store", + "node-tools" ], "own-secrets": { "broker": "${dir:state}/broker" diff --git a/modules/mesh-build-image/cmd/mesh-build-image/compose_test.go b/modules/mesh-build-image/cmd/mesh-build-image/compose_test.go index 04f9a2ed..6ae5a655 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/compose_test.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/compose_test.go @@ -50,10 +50,11 @@ func TestTheModuleComposesWhereItsFilesAreHandedToTheProvisionersClient(t *testi "buildah": read("buildah"), "forge": {Module: "forge", Version: "1", Provides: []catalogue.Offer{{Name: "forge-account"}}}, "store": {Module: "store", Version: "1", Provides: []catalogue.Offer{{Name: "artifact-store"}}}, + "runner": {Module: "runner", Version: "1", Provides: []catalogue.Offer{{Name: "node-tools"}}}, // node-tools, which the module pulls its asks through (1911) } node := catalogue.Node{Name: "builder", Site: "house", Capabilities: map[string]bool{"package-manager": true, "user-namespaces": true}} - r, err := catalogue.Resolve(shelf, []string{"forge", "store", "buildah", "mesh-build-image"}, node, catalogue.World{}) + r, err := catalogue.Resolve(shelf, []string{"forge", "store", "runner", "buildah", "mesh-build-image"}, node, catalogue.World{}) if err != nil { t.Fatal(err) } diff --git a/modules/mesh-build-image/cmd/mesh-build-image/forge_test.go b/modules/mesh-build-image/cmd/mesh-build-image/forge_test.go index 99107658..71c952ef 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/forge_test.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/forge_test.go @@ -144,7 +144,7 @@ func TestForgeFilesRenderFromTheBoundValuesInTheModulesOwnHome(t *testing.T) { // template in the files/ archive (novox/hq ADR 0349 §2). func TestManifestRequiresForgeAccountKeepsKeysAndTakesFilesFromTemplates(t *testing.T) { m := readForgeManifest(t) - if want := []any{"forge-account", "rootless-image-build", "artifact-store"}; !reflect.DeepEqual(m.Requires, want) { + if want := []any{"forge-account", "rootless-image-build", "artifact-store", "node-tools"}; !reflect.DeepEqual(m.Requires, want) { t.Errorf("requires = %v, want %v", m.Requires, want) } keeps := false diff --git a/modules/mesh-build-image/cmd/mesh-build-image/manifest_test.go b/modules/mesh-build-image/cmd/mesh-build-image/manifest_test.go index 3a1bd523..b8be9de0 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/manifest_test.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/manifest_test.go @@ -163,6 +163,8 @@ func TestACompositionWithoutBuildahIsRefusedNamingItAndWithBuildahComposes(t *te shelf := map[string]catalogue.Manifest{ "forge": {Module: "forge", Version: "1", Provides: []catalogue.Offer{{Name: "forge-account"}}}, "store": {Module: "store", Version: "1", Provides: []catalogue.Offer{{Name: "artifact-store"}}}, + // node-tools, the tool runner the module pulls its asks through (1911), stood in for as the others are. + "runner": {Module: "runner", Version: "1", Provides: []catalogue.Offer{{Name: "node-tools"}}}, } for _, name := range []string{"mesh-build-image", "buildah", "docker"} { m, err := catalogue.ReadTreeManifest(catalogueRoot, filepath.Join(catalogueRoot, "modules", name)) @@ -173,7 +175,7 @@ func TestACompositionWithoutBuildahIsRefusedNamingItAndWithBuildahComposes(t *te } node := catalogue.Node{Name: "builder", Site: "house", Capabilities: map[string]bool{"package-manager": true, "user-namespaces": true}} - _, err := catalogue.Resolve(shelf, []string{"forge", "store", "docker", "mesh-build-image"}, node, catalogue.World{}) + _, err := catalogue.Resolve(shelf, []string{"forge", "store", "runner", "docker", "mesh-build-image"}, node, catalogue.World{}) if err == nil { t.Fatal("mesh-build-image composed on a machine without buildah (docker does not stand in for it)") } @@ -182,7 +184,16 @@ func TestACompositionWithoutBuildahIsRefusedNamingItAndWithBuildahComposes(t *te t.Errorf("the refusal does not say %q:\n%v", want, err) } } - if _, err := catalogue.Resolve(shelf, []string{"forge", "store", "buildah", "mesh-build-image"}, node, catalogue.World{}); err != nil { + if _, err := catalogue.Resolve(shelf, []string{"forge", "store", "runner", "buildah", "mesh-build-image"}, node, catalogue.World{}); err != nil { t.Errorf("with buildah assigned the composition is refused: %v", err) } } + +// 1911: the module pulls its asks through node-tools' seat pull (mesh-tools, R4g), so it requires node-tools on its node +// and the delivery plan puts node-tools first: a node's build module never pulls through a tool runner that cannot. +func TestTheModuleRequiresNodeToolsItPullsThrough(t *testing.T) { + m := readManifest(t) + if !slices.Contains(m.Requires, "node-tools") { + t.Errorf("requires %v lack node-tools", m.Requires) + } +} diff --git a/modules/mesh-build-image/module.json b/modules/mesh-build-image/module.json index 2678972c..cfe8489f 100644 --- a/modules/mesh-build-image/module.json +++ b/modules/mesh-build-image/module.json @@ -39,7 +39,8 @@ "requires": [ "forge-account", "rootless-image-build", - "artifact-store" + "artifact-store", + "node-tools" ], "own-secrets": { "broker": "${dir:state}/broker" diff --git a/modules/mesh-build-node/cmd/mesh-build-node/manifest_test.go b/modules/mesh-build-node/cmd/mesh-build-node/manifest_test.go index 9f66603e..8b9ff5ec 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/manifest_test.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/manifest_test.go @@ -239,3 +239,12 @@ func TestTheModuleContributesWriteAccessToThePackageRegistry(t *testing.T) { t.Errorf("npm-package-registry access = %v, want write", got) } } + +// 1911: the module pulls its asks through node-tools' seat pull (mesh-tools, R4g), so it requires node-tools on its node +// and the delivery plan puts node-tools first: a node's build module never pulls through a tool runner that cannot. +func TestTheModuleRequiresNodeToolsItPullsThrough(t *testing.T) { + m := readManifest(t) + if !slices.Contains(m.Requires, "node-tools") { + t.Errorf("requires %v lack node-tools", m.Requires) + } +} diff --git a/modules/mesh-build-node/module.json b/modules/mesh-build-node/module.json index c3b1cf84..25f858eb 100644 --- a/modules/mesh-build-node/module.json +++ b/modules/mesh-build-node/module.json @@ -50,7 +50,8 @@ "forge-account", "npm-package-registry", "node-toolchain", - "artifact-store" + "artifact-store", + "node-tools" ], "contributes": { "npm-package-registry": {