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.
This commit is contained in:
2026-10-27 08:51:41 +01:00
parent 0d22776b20
commit caafd9306e
5 changed files with 52 additions and 18 deletions
+1 -1
View File
@@ -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
+2
View File
@@ -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=
+8 -1
View File
@@ -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))
+12
View File
@@ -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
+29 -16
View File
@@ -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}})