From 4632b6a50857ddc1b5b04850bc1661f4e4e99f4d Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 11 Oct 2026 03:20:15 +0200 Subject: [PATCH] Judge the SDK conformance fixtures against the SDK the controller pins, never a desktop's checkout (issue 449) A check clones mesh-sdk beside the controller at the commit go.mod pins; elsewhere the test reads a copy captured at that commit, held to go.mod. A missing clone fails instead of skipping. --- cmd/mesh-controller/checks.go | 49 +++++++++++++++++-- cmd/mesh-controller/merge_gate_test.go | 28 +++++++++-- internal/beside/beside.go | 5 +- internal/link/conformance_test.go | 44 +++++++++++++++-- testdata/beside/CAPTURED | 7 ++- .../conformance/events/module-event.json | 44 +++++++++++++++++ 6 files changed, 161 insertions(+), 16 deletions(-) create mode 100644 testdata/beside/mesh-sdk/conformance/events/module-event.json diff --git a/cmd/mesh-controller/checks.go b/cmd/mesh-controller/checks.go index 2fdfbddd..4cbb75b3 100644 --- a/cmd/mesh-controller/checks.go +++ b/cmd/mesh-controller/checks.go @@ -5,6 +5,8 @@ import ( "encoding/json" "fmt" "path" + "regexp" + "runtime/debug" "slices" "sort" "strings" @@ -246,9 +248,11 @@ func checkRequestFor(ctx context.Context, open *stores, p link.PullUpdated, scop if dir == "mesh-controller" { beside["mesh-controller-main"] = link.CheckedOut{Repository: url, Ref: refs["mesh-controller-main"]} if e.Source.Seat != "" { - if lab, err := clone(inventory.Source{Seat: e.Source.Seat, Repository: siblingOf(e.Source.Repository, - "mesh-lab")}); err == nil { - beside["mesh-lab"] = link.CheckedOut{Repository: lab, Ref: refs["mesh-lab"]} + for _, sibling := range []string{"mesh-lab", "mesh-sdk"} { + if url, err := clone(inventory.Source{Seat: e.Source.Seat, Repository: siblingOf(e.Source.Repository, + sibling)}); err == nil { + beside[sibling] = link.CheckedOut{Repository: url, Ref: refs[sibling]} + } } } } @@ -271,17 +275,52 @@ func checkRequestFor(ctx context.Context, open *stores, p link.PullUpdated, scop // catalogue, whose checkout beside is what tests read its files from, at its main, what the next merge // builds from; and beside the controller its main, for a judge the running controller predates, and the // lab's main, whose replays every check runs. **One rule, read by the check the controller asks for and by -// the facts snapshot** (Facts.Beside), so a check run by hand clones what the build seat clones. +// the facts snapshot** (Facts.Beside), so a check run by hand clones what the build seat clones. Beside the +// controller also the SDK, at the commit the controller's go.mod pins (sdkPinned): its conformance fixtures +// are judged against the SDK the controller is built with, not one a desktop holds (novox/hq issue 449). func besideRefs(dir, running string) map[string]string { switch dir { case "mesh-catalog": return map[string]string{dir: "main"} case "mesh-controller": - return map[string]string{dir: running, "mesh-controller-main": "main", "mesh-lab": "main"} + return map[string]string{dir: running, "mesh-controller-main": "main", "mesh-lab": "main", + "mesh-sdk": sdkPinned()} } return map[string]string{dir: running} } +// sdkModule is the Go module of the SDK the controller is built against. +const sdkModule = "git.novox.be/novox/mesh-sdk/go" + +// pseudoCommit is the commit a Go pseudo-version names: v0.1.11-0.20261009143344-f047d0a4a970 → f047d0a4a970. +var pseudoCommit = regexp.MustCompile(`-([0-9a-f]{12})$`) + +// sdkPinned is the ref of the SDK repository this controller was built from, as its go.mod pins it and its +// build records it: a pseudo-version's commit, or a release's tag (the SDK tags its Go module under go/). +// "main" only for a binary that records no SDK version — a local replace — which a running controller is +// not (novox/hq issue 449). +func sdkPinned() string { + info, ok := debug.ReadBuildInfo() + if !ok { + return "main" + } + for _, dep := range info.Deps { + if dep.Path != sdkModule { + continue + } + if dep.Replace != nil { + dep = dep.Replace + } + if m := pseudoCommit.FindStringSubmatch(dep.Version); m != nil { + return m[1] + } + if strings.HasPrefix(dep.Version, "v") { + return "go/" + dep.Version + } + } + return "main" +} + // siblingOf is another repository of the same owner: novox/mesh-controller → novox/mesh-lab. func siblingOf(repository, name string) string { if cut := strings.LastIndex(repository, "/"); cut >= 0 { diff --git a/cmd/mesh-controller/merge_gate_test.go b/cmd/mesh-controller/merge_gate_test.go index 49792733..00289ff2 100644 --- a/cmd/mesh-controller/merge_gate_test.go +++ b/cmd/mesh-controller/merge_gate_test.go @@ -390,9 +390,10 @@ func TestAWithheldPathIsStoodInForByAPath(t *testing.T) { // controller's ask and by the facts — and finds the repository it checks from its origin. func TestACheckByHandClonesWhatTheSeatClones(t *testing.T) { for dir, refs := range map[string]map[string]string{ - "mesh-catalog": {"mesh-catalog": "main"}, - "mesh-host": {"mesh-host": "c0ffee"}, - "mesh-controller": {"mesh-controller": "c0ffee", "mesh-controller-main": "main", "mesh-lab": "main"}, + "mesh-catalog": {"mesh-catalog": "main"}, + "mesh-host": {"mesh-host": "c0ffee"}, + "mesh-controller": {"mesh-controller": "c0ffee", "mesh-controller-main": "main", "mesh-lab": "main", + "mesh-sdk": sdkPinnedByGoMod(t)}, } { got := besideRefs(dir, "c0ffee") for d, ref := range refs { @@ -412,3 +413,24 @@ func TestACheckByHandClonesWhatTheSeatClones(t *testing.T) { } } } + +// sdkPinnedByGoMod is the SDK commit this tree's go.mod pins, read from the file rather than the build, so +// sdkPinned is held to what the repository says (novox/hq issue 449). +func sdkPinnedByGoMod(t *testing.T) string { + t.Helper() + raw, err := os.ReadFile(filepath.Join("..", "..", "go.mod")) + if err != nil { + t.Fatal(err) + } + for _, line := range strings.Split(string(raw), "\n") { + fields := strings.Fields(line) + if len(fields) >= 2 && fields[0] == sdkModule { + if m := pseudoCommit.FindStringSubmatch(fields[1]); m != nil { + return m[1] + } + return "go/" + fields[1] + } + } + t.Fatalf("go.mod requires no %s", sdkModule) + return "" +} diff --git a/internal/beside/beside.go b/internal/beside/beside.go index 3b281765..b64f67cc 100644 --- a/internal/beside/beside.go +++ b/internal/beside/beside.go @@ -1,5 +1,5 @@ // Package beside is where a test finds another repository of the mesh it reads: the catalogue's manifests, -// the node-engine's genesis template (novox/hq issue 432). +// the node-engine's genesis template (novox/hq issue 432), the SDK's conformance fixtures (issue 449). // // A test used to read the checkout beside this one, `../../../mesh-catalog`, so its verdict depended on // whatever sat on the machine running it: a stale or dirty checkout failed it on a desktop, and where none @@ -10,7 +10,8 @@ // test judges against those clones, so agreement with the other repository is checked where // `mesh/repo-check` runs; a repository missing there fails the test, never skips it. Which clones a // check gets is chosen from the inventory: mesh-catalog by the source of the `nats` module, mesh-host -// by the source of `mesh-host`. In a mesh where either module has no source repository nothing is +// by the source of `mesh-host`, and mesh-sdk as the controller's sibling at the commit the controller's +// go.mod pins. In a mesh where either module has no source repository nothing is // cloned, and these tests fail loudly with "not beside this check": a cause in the setup, not in the // change. And in a delivery group that holds a mesh-catalog pull request, these tests read the // catalogue's main, not the group's head. diff --git a/internal/link/conformance_test.go b/internal/link/conformance_test.go index 50f60dac..edc3f148 100644 --- a/internal/link/conformance_test.go +++ b/internal/link/conformance_test.go @@ -5,15 +5,21 @@ import ( "os" "path/filepath" "regexp" + "strings" "testing" "time" + + "github.com/novox/mesh-controller/internal/beside" ) // The Go implementation, held to the shared fixtures (novox/hq ADR 0074, design 19). // -// **Read from the sdk's conformance directory by sibling path**, the way the lab finds its -// siblings — deliberately not copied here. A fixture copied into each implementation is two -// fixtures, and two fixtures drift, which is the exact failure the suite exists to prevent. +// **Read from the SDK's own conformance directory, never copied by hand**: a fixture written into each +// implementation is two fixtures, and two fixtures drift, which is the failure the suite exists to prevent. +// Which SDK is read is named, never the one a desktop happens to hold beside this checkout (novox/hq issue +// 449): in a merge check the SDK the build seat clones beside it, at the commit this controller's go.mod +// pins, and a missing clone fails the test; elsewhere the copy captured in testdata/beside at that same +// commit (internal/beside, testdata/beside/CAPTURED), held to go.mod by TestTheCapturedSDKIsTheOneGoModPins. type fixture struct { Name string `json:"name"` Given struct { @@ -32,10 +38,11 @@ type fixture struct { func loadFixture(t *testing.T, name string) fixture { t.Helper() - path := filepath.Join("..", "..", "..", "mesh-sdk", "conformance", name) + path := filepath.Join(beside.Dir(t, "mesh-sdk"), "conformance", name) raw, err := os.ReadFile(path) if err != nil { - t.Skipf("the sdk's conformance fixtures are not beside this checkout: %v", err) + // Failed, never skipped: a skip here passed the suite with nothing judged. + t.Fatalf("the SDK's conformance fixture %s: %v", name, err) } var f fixture if err := json.Unmarshal(raw, &f); err != nil { @@ -44,6 +51,33 @@ func loadFixture(t *testing.T, name string) fixture { return f } +// The captured SDK is the one this controller is built against: when go.mod moves the SDK, the copy moves +// with it, or the tests away from a merge check judge an SDK the controller no longer uses (novox/hq issue +// 449). +func TestTheCapturedSDKIsTheOneGoModPins(t *testing.T) { + mod, err := os.ReadFile(filepath.Join("..", "..", "go.mod")) + if err != nil { + t.Fatal(err) + } + pinned := regexp.MustCompile(`(?m)^\s*git\.novox\.be/novox/mesh-sdk/go v\S+-([0-9a-f]{12})$`).FindSubmatch(mod) + if pinned == nil { + t.Fatal("go.mod pins no commit of git.novox.be/novox/mesh-sdk/go as a pseudo-version; say here how a " + + "release's tag is matched against testdata/beside/CAPTURED") + } + captured, err := os.ReadFile(filepath.Join(beside.Captured(), "CAPTURED")) + if err != nil { + t.Fatal(err) + } + at := regexp.MustCompile(`(?m)^mesh-sdk\s+([0-9a-f]{40})\s`).FindSubmatch(captured) + if at == nil { + t.Fatal("testdata/beside/CAPTURED names no commit for mesh-sdk") + } + if !strings.HasPrefix(string(at[1]), string(pinned[1])) { + t.Errorf("the SDK is captured at %s but go.mod pins %s: capture it again at the pinned commit, as "+ + "testdata/beside/CAPTURED says", at[1], pinned[1]) + } +} + // Every header the fixture requires is one this implementation actually sets. func TestTheGoEmitterSetsEveryRequiredHeader(t *testing.T) { f := loadFixture(t, "events/module-event.json") diff --git a/testdata/beside/CAPTURED b/testdata/beside/CAPTURED index 6b819622..17d63986 100644 --- a/testdata/beside/CAPTURED +++ b/testdata/beside/CAPTURED @@ -9,6 +9,7 @@ a second place to clean besides the catalogue itself. mesh-catalog b9de001833b1b61b297182b8e3bcdae1cbdeace9 modules/*/module.json, modules/nats/Dockerfile mesh-host bd5cc6980419c1bd4824be6d58dfebd2381a9de3 examples/foundation-first-node-nats.lock +mesh-sdk f047d0a4a9702f5d7f3d7eadd3e62496311acc00 conformance/events/module-event.json To move them, from this repository's root, with the two repositories checked out beside it: @@ -19,4 +20,8 @@ To move them, from this repository's root, with the two repositories checked out find testdata/beside -name module.json -exec mv {} {}.captured \; git -C ../mesh-host archive examples/foundation-first-node-nats.lock | tar -x -C testdata/beside/mesh-host -and write the commits here. +and write the commits here. The SDK is captured at the commit go.mod pins for git.novox.be/novox/mesh-sdk/go +(the last part of its pseudo-version; novox/hq issue 449), which internal/link's conformance test holds it to: + + rm -rf testdata/beside/mesh-sdk && mkdir -p testdata/beside/mesh-sdk + git -C ../mesh-sdk archive conformance/events | tar -x -C testdata/beside/mesh-sdk diff --git a/testdata/beside/mesh-sdk/conformance/events/module-event.json b/testdata/beside/mesh-sdk/conformance/events/module-event.json new file mode 100644 index 00000000..8de1169b --- /dev/null +++ b/testdata/beside/mesh-sdk/conformance/events/module-event.json @@ -0,0 +1,44 @@ +{ + "capability": "events", + "name": "a module emits an event", + "why": "The envelope is what two implementations can disagree about without either failing: a missing header, a header spelled differently, or a body nested where metadata belongs. None of those stop a mesh running; they stop it reacting.", + "given": { + "module": "shop", + "node": "one", + "key": "order.placed", + "body": { "id": "a1", "total": 12 }, + "headers": { + "x-event-id": "0123456789abcdef0123456789abcdef", + "x-source": "shop", + "x-node": "one", + "x-time": "2026-09-26T12:00:00Z", + "content-type": "application/json" + } + }, + "wire": { + "subject": "mesh.mod.shop.event.order.placed", + "requiredHeaders": ["x-event-id", "x-source", "x-node", "x-time", "content-type"], + "optionalHeaders": ["x-causation-id", "x-schema"], + "headerFormats": { + "x-time": "RFC3339", + "content-type": "application/json", + "x-event-id": "^[0-9a-f]{32}$" + }, + "payloadIs": "the body alone, not the envelope", + "keyRecoveredFrom": "the subject, after the .event. token" + }, + "refuses": [ + { + "what": "an envelope nested in the payload", + "why": "an implementation that publishes the whole envelope as the body passes all of its own tests and is unreadable to every other" + }, + { + "what": "a missing x-event-id", + "why": "delivery is at-least-once and only the emitter can say which of two messages is a redelivery" + }, + { + "what": "an x-source that differs from the subject's module", + "why": "the bus enforces the namespace, so a disagreement means the envelope is lying about its origin" + } + ] +}