From b0dc7e08de112c33bc541e04e72b82e44bb94550 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 1 Nov 2026 03:33:48 +0100 Subject: [PATCH] mesh-provisioner recomposes a node when one of its haves changes, and pins mesh-sdk at 8311ecb ADR 0580 clause 2 as revised: the holder hears haves by watch as it hears resolved values, so a have's change recomposes its node from the copy it already holds. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL --- .../mesh-provisioner/cmd/mesh-provisioner/holder.go | 6 +++--- .../cmd/mesh-provisioner/holder_test.go | 11 ++++++++--- modules/mesh-provisioner/cmd/mesh-provisioner/main.go | 3 ++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go index 14769fc4..2a7ee6bd 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go @@ -1086,9 +1086,9 @@ func (w watchingStore) Keys(state string) ([]string, error) { return w.ws.Keys(state) } -// NodesOfResolved is the node a resolved value's key `..` names: its change recomposes that node -// (ADR 0580 clause 2). -func NodesOfResolved(key string) []string { +// NodesOfNodeKey is the node a key that starts with it names: a resolved value's `..`, a have's +// `.` or `.node`. A change of either recomposes that node (ADR 0580 clause 2). +func NodesOfNodeKey(key string) []string { if n, _, ok := strings.Cut(key, "."); ok && n != "" { return []string{n} } diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go index 17e770a1..96c53213 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go @@ -408,7 +408,9 @@ func TestAModuleHeldWithoutAWantIsComposedAtTheBuildItRuns(t *testing.T) { }, func(string, string) ([]byte, bool, error) { return nil, false, nil }) m.h.Store = Watching(bus, ws) wk := &work{pending: map[string]bool{}, wake: make(chan struct{}, 1)} - ws.Follow(ResolvedRead, func(c stdio.StateChange) error { wk.add(NodesOfResolved(c.Key)...); return nil }) + for _, r := range []string{ResolvedRead, HaveRead} { + ws.Follow(r, func(c stdio.StateChange) error { wk.add(NodesOfNodeKey(c.Key)...); return nil }) + } for i := range 2 { c := m.compose(t, "novox") if keptAt["nats"] != fp || len(keptAt) != 1 { @@ -423,9 +425,12 @@ func TestAModuleHeldWithoutAWantIsComposedAtTheBuildItRuns(t *testing.T) { t.Errorf("%s was read %d times from the bus and watched %d times", r, bus.read[r], watches[r]) } } + // The current haves queued their nodes when watched; a later change of a resolved value or a have queues its node. + wk.pending = map[string]bool{} _ = handles[ResolvedRead](stdio.StateChange{Key: "g14.dunst.font-size", Op: stdio.OpPut, Value: []byte("13")}) - if !wk.pending["g14"] || len(wk.pending) != 1 { - t.Errorf("a resolved value's change queued %v, want g14", wk.pending) + _ = handles[HaveRead](stdio.StateChange{Key: "ace.restic", Op: stdio.OpDelete}) + if !wk.pending["g14"] || !wk.pending["ace"] || len(wk.pending) != 2 { + t.Errorf("a resolved value's and a have's change queued %v, want g14 and ace", wk.pending) } } diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/main.go b/modules/mesh-provisioner/cmd/mesh-provisioner/main.go index f7467950..dcc74524 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/main.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/main.go @@ -113,7 +113,8 @@ func main() { // Each watch's handler runs after the copy took its change, so a composition it wakes reads the copy that change made. go ws.Follow(PlacementRead, func(c stdio.StateChange) error { w.add(NodesOfPlacement(c.Key)...); return nil }) go ws.Follow(WantsRead, func(c stdio.StateChange) error { w.add(NodesOfWant(c.Key)...); return nil }) - go ws.Follow(ResolvedRead, func(c stdio.StateChange) error { w.add(NodesOfResolved(c.Key)...); return nil }) + go ws.Follow(ResolvedRead, func(c stdio.StateChange) error { w.add(NodesOfNodeKey(c.Key)...); return nil }) + go ws.Follow(HaveRead, func(c stdio.StateChange) error { w.add(NodesOfNodeKey(c.Key)...); return nil }) go ws.Follow(ModulesRead, func(c stdio.StateChange) error { if module, err := delivery.ParseModulesKey(c.Key); err == nil { w.add(h.NodesOfModule(module)...)