Node agent: say what it holds today in its answer, so a seed that holds the same is the same; pin mesh-sdk b03bf4c (issue 1126)
This commit is contained in:
@@ -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.20261028174713-38dbe1d3a388
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028211337-b03bf4c118d9
|
||||
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
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028162235-0de30064156a h1:BYJrXqPfeNSF0eypX3UnczLq/geXNkfJbiUK9JHlHNA=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028162235-0de30064156a/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028174713-38dbe1d3a388 h1:ycu/sNieTdLpD3bzhZOCXdzcoqXUOxPRmqWV019REpo=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028174713-38dbe1d3a388/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028211337-b03bf4c118d9 h1:Zy31cb1lVyFn1j1T7qT8cXJgnO3pyEr5SKCFERVM1CE=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028211337-b03bf4c118d9/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=
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -100,6 +101,9 @@ type Composer struct {
|
||||
sealedOwn map[string]ownSealed
|
||||
said string
|
||||
refusal string
|
||||
// held is what the composer's last pass held: each module refused alone, and the node's own ("node") when it could
|
||||
// compose none (heldNow).
|
||||
held []string
|
||||
// doing is what the composer waits on now (waiting), for a running answer to say (issue 1126, T2); its own lock, so
|
||||
// it is read while c.mu is held.
|
||||
doingMu sync.Mutex
|
||||
@@ -621,12 +625,23 @@ func (c *Composer) refused(why string, held map[string]string) {
|
||||
c.mu.Lock()
|
||||
same := c.refusal == said
|
||||
c.refusal = said
|
||||
c.held = heldModules(held)
|
||||
if why != "" {
|
||||
c.held = append(c.held, delivery.HaveNodeWord)
|
||||
}
|
||||
c.mu.Unlock()
|
||||
if !same && c.env.Refused != nil {
|
||||
c.env.Refused(why, held)
|
||||
}
|
||||
}
|
||||
|
||||
// heldNow is what the composer's last pass held, sorted.
|
||||
func (c *Composer) heldNow() []string {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return slices.Clone(c.held)
|
||||
}
|
||||
|
||||
// keepRunning is comp with each sealed value the node already runs at the same slot kept, where it opens to the same
|
||||
// value (delivery.KeepSeals, issue 1126, C1): of what runs (running, the agent's), else of what its module's have says
|
||||
// it runs (Held). A have written before slots names no slot: its boxes are reused within their own module alone, at a
|
||||
|
||||
@@ -1401,12 +1401,16 @@ func TestAHaveSaysWhetherItsModuleRunsInsideTheNodesOwn(t *testing.T) {
|
||||
name string
|
||||
have delivery.Have
|
||||
in bool
|
||||
// heldToday says the shadow counts the module held today (issue 1126: a seed holding it too holds it as today).
|
||||
heldToday bool
|
||||
}{
|
||||
{"failed, naming nothing it runs", delivery.Have{Phase: delivery.PhaseFailed, Health: delivery.HealthFailed,
|
||||
Detail: "the node cannot compose it, and keeps running what it ran: no build"}, false},
|
||||
Detail: "the node cannot compose it, and keeps running what it ran: no build"}, false, false},
|
||||
{"failed at a build fingerprint", delivery.Have{FP: "build-0123abcd", Composed: "sha256:" + strings.Repeat("c", 64), Phase: delivery.PhaseFailed,
|
||||
Health: delivery.HealthFailed, Detail: "the node cannot compose it, and keeps running what it ran: no build"}, true},
|
||||
{"running with no fingerprint (mesh-wireguard)", delivery.Have{Phase: delivery.PhaseRunning, Health: delivery.HealthOK}, true},
|
||||
Health: delivery.HealthFailed, Detail: "the node cannot compose it, and keeps running what it ran: no build"}, true, false},
|
||||
{"running with no fingerprint (mesh-wireguard)", delivery.Have{Phase: delivery.PhaseRunning, Health: delivery.HealthOK}, true, false},
|
||||
{"held today", delivery.Have{FP: "build-0123abcd", Composed: "sha256:" + strings.Repeat("c", 64), Phase: delivery.PhaseHeld,
|
||||
Health: delivery.HealthWaits, Detail: "held: a required config value is unset"}, true, true},
|
||||
} {
|
||||
t.Run(row.name, func(t *testing.T) {
|
||||
c := New(Env{Node: "n1"})
|
||||
@@ -1422,6 +1426,11 @@ func TestAHaveSaysWhetherItsModuleRunsInsideTheNodesOwn(t *testing.T) {
|
||||
if c.inOwn["web"] != row.in {
|
||||
t.Errorf("web counted inside the node's own composition: %v, want %v", c.inOwn["web"], row.in)
|
||||
}
|
||||
s := &Shadow{c: c, ran: map[string]string{}}
|
||||
s.takeRan(agent.Update{Key: key, Value: value})
|
||||
if got := slices.Contains(s.heldToday(), "web"); got != row.heldToday {
|
||||
t.Errorf("web held today: %v, want %v", got, row.heldToday)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ type Shadow struct {
|
||||
// ran is, by module, the digest the module's have says runs: what runs, where the agent's list holds only the copy
|
||||
// from the declaration kept before the switch (issue 1126, probe #532 on shanks).
|
||||
ran map[string]string
|
||||
// heldNow is, by module, whether its have says the node holds it today (phase held): a candidate holding it too holds
|
||||
// it as today (frombus.Scope.HeldToday, issue 1126).
|
||||
heldNow map[string]bool
|
||||
// answered is, by proposal, the candidate revisions and the input changes (moved) each answer was composed at: a proposal
|
||||
// is answered again only when either moved (issue 1126, 2026-10-28: shanks answered probe/1126-seed-7 at revision 84
|
||||
// every 13-17 s, woken by its own modules' haves whose health words changed at every look).
|
||||
@@ -614,7 +617,7 @@ func (s *Shadow) answerProposal(ctx context.Context, proposal string) (delivery.
|
||||
c.wants = held
|
||||
c.mu.Unlock()
|
||||
a := frombus.Answer(ctx, reader{c: c}, c.env.Mesh, frombus.Scope{Node: node, KeptAt: kept, Placements: given, InOwn: inOwn,
|
||||
SameValue: c.sameValue}, fps, running, proposal, rev)
|
||||
SameValue: c.sameValue, HeldToday: s.heldToday()}, fps, running, proposal, rev)
|
||||
s.mu.Lock()
|
||||
ran := make(map[string]string, len(s.ran))
|
||||
for m, d := range s.ran {
|
||||
@@ -624,6 +627,28 @@ func (s *Shadow) answerProposal(ctx context.Context, proposal string) (delivery.
|
||||
return asHavesSay(a, ran), true
|
||||
}
|
||||
|
||||
// heldToday is what the node holds today: each module whose have's phase is held, and what its own composer holds, the
|
||||
// node's own composition ("node") among them when it composes none of its own.
|
||||
func (s *Shadow) heldToday() []string {
|
||||
set := map[string]bool{}
|
||||
s.mu.Lock()
|
||||
for m, held := range s.heldNow {
|
||||
if held {
|
||||
set[m] = true
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
for _, m := range s.c.heldNow() {
|
||||
set[m] = true
|
||||
}
|
||||
out := make([]string, 0, len(set))
|
||||
for m := range set {
|
||||
out = append(out, m)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// takeRan keeps the digest one of this node's module haves says runs, and says whether it changed: a have whose health
|
||||
// or words alone moved runs the same.
|
||||
func (s *Shadow) takeRan(u agent.Update) bool {
|
||||
@@ -635,12 +660,18 @@ func (s *Shadow) takeRan(u agent.Update) bool {
|
||||
defer s.mu.Unlock()
|
||||
before, had := s.ran[module]
|
||||
h, err := delivery.DecodeHave(u.Value)
|
||||
if s.heldNow == nil {
|
||||
s.heldNow = map[string]bool{}
|
||||
}
|
||||
wasHeld := s.heldNow[module]
|
||||
s.heldNow[module] = !u.Deleted && err == nil && h.Phase == delivery.PhaseHeld
|
||||
holdMoved := wasHeld != s.heldNow[module] // a hold's start or end changes the answer too
|
||||
if u.Deleted || err != nil || h.Composed == "" || h.Phase == delivery.PhaseRemoved {
|
||||
delete(s.ran, module)
|
||||
return had
|
||||
return had || holdMoved
|
||||
}
|
||||
s.ran[module] = h.Composed
|
||||
return !had || before != h.Composed
|
||||
return !had || before != h.Composed || holdMoved
|
||||
}
|
||||
|
||||
// asHavesSay is a with each module that differs only from the copy the agent keeps of the declaration before the switch
|
||||
|
||||
Reference in New Issue
Block a user