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
This commit is contained in:
2026-11-01 09:54:07 +01:00
parent aa4a4253ad
commit b0dc7e08de
3 changed files with 13 additions and 7 deletions
@@ -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 `<node>.<module>.<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 `<node>.<module>.<key>`, a have's
// `<node>.<module>` or `<node>.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}
}
@@ -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)
}
}
@@ -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)...)