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}})