From caafd9306e9339b918954d92931c71b3430b96c4 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 27 Oct 2026 08:51:41 +0100 Subject: [PATCH] Keep what the node runs inside its own composition there when it composes itself, so the seed's answer and the node's own composition keep the tunnel and the bus (issue 1126) Pins mesh-sdk 05b67831, whose compositions fold a module no new-form build names into the node's own. The shadow composed a placed module it runs inside its own composition (nats, photos) at the trunk's build, as if the candidate added it; shadowBuilds now leaves those to the fold, and both paths pass frombus.InOwn of what runs. --- go.mod | 2 +- go.sum | 2 ++ internal/selfcompose/selfcompose.go | 9 ++++- internal/selfcompose/selfcompose_test.go | 12 +++++++ internal/selfcompose/shadow.go | 45 +++++++++++++++--------- 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 0a71ab3..c5812c9 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/novox/mesh-host go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027014310-cadd7b1b2f8f + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027075026-05b678310366 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 golang.org/x/crypto v0.57.0 diff --git a/go.sum b/go.sum index 04ad7e0..ae78f0e 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027012704-062c29c3a22f h1:AKzV973U git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027012704-062c29c3a22f/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027014310-cadd7b1b2f8f h1:mimAGERguQmCGYVNdRTrFcZ1dhpU6GWo+Q3EDhHOrVk= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027014310-cadd7b1b2f8f/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027075026-05b678310366 h1:B7oQ82ar2McqZ9OrbJfyvG+50Ol6fBUoK3eYE2uTPvo= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261027075026-05b678310366/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/internal/selfcompose/selfcompose.go b/internal/selfcompose/selfcompose.go index 12ef2a7..3561df3 100644 --- a/internal/selfcompose/selfcompose.go +++ b/internal/selfcompose/selfcompose.go @@ -359,8 +359,15 @@ func (c *Composer) compose(ctx context.Context) []agent.Update { } } _, nodeWanted := c.wants[agent.NodeModule] + placement := c.placements[c.env.Node] r := reader{c: c} c.mu.Unlock() + // What it runs inside its own composition and no want names (mesh-wireguard, a module at a build of the old form) + // stays there, as the controller's composed writer folds it (issue 1126). + var inOwn []string + if c.env.Running != nil { + inOwn = frombus.InOwn(c.env.Running(), placement) + } sort.Strings(behind) for _, module := range behind { digest, err := c.builtManifest(ctx, module, fps[module]) @@ -373,7 +380,7 @@ func (c *Composer) compose(ctx context.Context) []agent.Update { kept[module] = digest } - compositions, _, err := frombus.Compose(ctx, r, c.env.Mesh, frombus.Scope{Node: c.env.Node, Kept: kept}, fps) + compositions, _, err := frombus.Compose(ctx, r, c.env.Mesh, frombus.Scope{Node: c.env.Node, Kept: kept, InOwn: inOwn}, fps) if err != nil { c.refused(fmt.Sprintf("%v", err)) c.sayOnce(fmt.Sprintf("the node cannot compose its modules, and keeps running what it runs: %v", err)) diff --git a/internal/selfcompose/selfcompose_test.go b/internal/selfcompose/selfcompose_test.go index ba3a246..321ab00 100644 --- a/internal/selfcompose/selfcompose_test.go +++ b/internal/selfcompose/selfcompose_test.go @@ -444,6 +444,18 @@ func TestTheNodeComposesItsWantsFromTheBus(t *testing.T) { _, ok := a["drops"] return !ok && len(a) == 2 }) + + // Issue 1126, the seed's review: a module n1 runs inside its own composition (nats, at a build of the old form) is + // composed there again, not at the trunk's build as one added; lemurs at the build it runs, keeper (added) at the + // trunk's. + inOwnRunning := []delivery.Composed{{Node: "n1", Resources: []json.RawMessage{json.RawMessage(`{"id":"nats.server","type":"service"}`)}}, + {Node: "n1", Module: "lemurs", FP: fp}} + other := "src2:" + strings.Repeat("9", 64) + gotFps, gotInOwn := shadowBuilds(inOwnRunning, delivery.Placement{Node: "n1", Modules: []delivery.PlacedModule{{Module: "lemurs"}, {Module: "nats"}, {Module: "keeper"}}}, + map[string]delivery.Module{"lemurs": {FP2: other}, "nats": {FP2: other}, "keeper": {FP2: other}}) + if fmt.Sprint(gotFps) != fmt.Sprint(map[string]string{"lemurs": fp, "keeper": other}) || fmt.Sprint(gotInOwn) != "[nats]" { + t.Errorf("shadow builds %v, in its own %v; want lemurs at what runs, keeper at the trunk's, nats in its own", gotFps, gotInOwn) + } } // patternBus is a bus whose every watch gets the updates of its bucket matching its pattern, as the bus's own watches diff --git a/internal/selfcompose/shadow.go b/internal/selfcompose/shadow.go index 2bbdb1d..23f72d8 100644 --- a/internal/selfcompose/shadow.go +++ b/internal/selfcompose/shadow.go @@ -305,14 +305,6 @@ func (s *Shadow) answerGroup(ctx context.Context, group string) (delivery.Candid return a, true } - // Each module at the build it runs; one the candidate adds at the trunk's build. - fps := map[string]string{} - kept := map[string]string{} - for _, r := range running { - if r.Module != "" && r.FP != "" { - fps[r.Module] = r.FP - } - } placement := given[node] if placement == nil { c.mu.Lock() @@ -320,13 +312,8 @@ func (s *Shadow) answerGroup(ctx context.Context, group string) (delivery.Candid c.mu.Unlock() placement = &p } - for _, m := range placement.Modules { - if _, runs := fps[m.Module]; !runs { - if mod, ok := modules[m.Module]; ok && mod.FP2 != "" { - fps[m.Module] = mod.FP2 - } - } - } + fps, inOwn := shadowBuilds(running, *placement, modules) + kept := map[string]string{} for module, fp := range fps { if mod, ok := modules[module]; ok && mod.FP2 == fp { continue @@ -347,10 +334,36 @@ func (s *Shadow) answerGroup(ctx context.Context, group string) (delivery.Candid c.mu.Lock() c.wants = held c.mu.Unlock() - a := frombus.Answer(ctx, reader{c: c}, c.env.Mesh, frombus.Scope{Node: node, Kept: kept, Placements: given}, fps, running, head, rev) + a := frombus.Answer(ctx, reader{c: c}, c.env.Mesh, frombus.Scope{Node: node, Kept: kept, Placements: given, InOwn: inOwn}, fps, running, head, rev) return a, true } +// shadowBuilds is the build each module placed is composed at in shadow: the one it runs, and for one the candidate +// adds, the trunk's; and inOwn, the modules it runs inside its own composition (mesh-wireguard, one at a build of the +// old form), which are composed there again rather than at the trunk's build as if the candidate added them (issue +// 1126: nats and photos have a trunk build that no want names, and the seed's answer named them added). +func shadowBuilds(running []delivery.Composed, placement delivery.Placement, modules map[string]delivery.Module) (map[string]string, []string) { + fps := map[string]string{} + for _, r := range running { + if r.Module != "" && r.FP != "" { + fps[r.Module] = r.FP + } + } + inOwn := frombus.InOwn(running, placement) + folded := map[string]bool{} + for _, m := range inOwn { + folded[m] = true + } + for _, m := range placement.Modules { + if _, runs := fps[m.Module]; !runs && !folded[m.Module] { + if mod, ok := modules[m.Module]; ok && mod.FP2 != "" { + fps[m.Module] = mod.FP2 + } + } + } + return fps, inOwn +} + func sameAnswer(a, b delivery.CandidateAnswer) bool { x, errA := delivery.EncodeHaveNode(delivery.HaveNode{Candidates: map[string]delivery.CandidateAnswer{"g": a}}) y, errB := delivery.EncodeHaveNode(delivery.HaveNode{Candidates: map[string]delivery.CandidateAnswer{"g": b}})