mesh-build-node: name each bundle's launchers by mesh-sdk's one rule, and move the recipe for the SDK a member's lock names (R4e, lead decision L10)

writeLaunchers takes its entrypoint-to-launcher names from buildstep.Launchers, the rule the
controller reads a bundle by. RecipeSteps goes to 2: a member is now built against the SDK
its own lock names, which can change what a build produces (ADR 0405 §7).
This commit is contained in:
2026-10-22 18:39:06 +02:00
parent b9cdca78b0
commit 5a79776625
3 changed files with 20 additions and 12 deletions
@@ -21,10 +21,6 @@ import (
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// launcherSuffix is what an entrypoint's launcher is called beside it: index.js is started as index.serve.mjs
// (novox/hq ADR 0193).
const launcherSuffix = ".serve.mjs"
// Out is where a bundle's compiled files go in the module's directory (builder toolchain.go:118).
func Out(artifact string) string { return ".mesh-build/" + artifact }
@@ -216,15 +212,12 @@ func (m *Module) bundle(ctx context.Context, root string, publish bool, dir stri
}
// writeLaunchers writes, beside every entrypoint, an executable that imports the entrypoint and serves what it
// registered over MCP on stdio (builder.go:1926-1956; novox/hq ADR 0193). Its answer is each entrypoint's launcher,
// relative to the bundle's root.
// registered over MCP on stdio (builder.go:1926-1956; novox/hq ADR 0193). Which entrypoint gets which launcher is
// mesh-sdk's one rule, buildstep.Launchers, which the controller reads a bundle by (R4e). Its answer is each
// entrypoint's launcher, relative to the bundle's root.
func writeLaunchers(root string, a artifact) (map[string]string, error) {
out := map[string]string{}
for _, entry := range a.Entrypoints {
if !strings.HasSuffix(entry, ".js") {
continue
}
launcher := strings.TrimSuffix(entry, ".js") + launcherSuffix
for entry, launcher := range buildstep.Launchers(a.Language, a.Entrypoints) {
body := "#!/usr/bin/env node\n" +
"// Written by the mesh's builder (novox/hq ADR 0193): serve what " + entry + " registers,\n" +
"// over MCP on stdio, as the module the node's runtime names in MESH_SERVED_MODULE.\n" +
@@ -6,12 +6,14 @@ import (
"compress/gzip"
"context"
"io"
"maps"
"os"
"path/filepath"
"slices"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/buildstep"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
@@ -252,3 +254,16 @@ func TestTheBuildRunsWithThePackagesTwoLevelsAboveTheModule(t *testing.T) {
}
}
}
// R4e: the launchers a build writes are mesh-sdk's one rule (buildstep.Launchers), the names the controller reads a bundle
// by; an entrypoint that is not JavaScript gets none.
func TestTheLaunchersAreTheSDKsRule(t *testing.T) {
a := artifact{Language: "typescript", Entrypoints: []string{"tools/index.js", "web/server.js", "assets/x.css"}}
got, err := writeLaunchers(t.TempDir(), a)
if err != nil {
t.Fatal(err)
}
if want := buildstep.Launchers(a.Language, a.Entrypoints); !maps.Equal(got, want) || len(got) != 2 {
t.Errorf("launchers %v, mesh-sdk's rule %v", got, want)
}
}
@@ -14,7 +14,7 @@ import (
// RecipeSteps is the recipe's own version: changed whenever the steps of fetch, test or build change in a way that
// can change what they produce (ADR 0405 §7). It is one of the two values the recipe version hashes.
const RecipeSteps = "1"
const RecipeSteps = "2" // 2: a member is built against the SDK its own lock names (R4e, the lead's decision L10)
// Recipe is every value that makes up the recipe version.
type Recipe struct {