Merge pull request 'A node answers its seed with an old-form build, a failed or held module, and those held behind it, as they run today (issue 1126)' (#170) from fix/1126-seed-oldbuild into main
This commit was merged in pull request #170.
This commit is contained in:
@@ -3,7 +3,7 @@ module github.com/novox/mesh-host
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028212605-77b48a4f1aff
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028225633-76f7c4d2238f
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028212605-77b48a4f1aff h1:EHuM+ed+/M9woMZsMou6/2xm6hIdb1YCFINprrP54Ys=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028225633-76f7c4d2238f h1:jCd8WN+Olo4nlaB1cz1HYpEV4xc6Mzw7viXdwU+0fic=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028212605-77b48a4f1aff/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028225633-76f7c4d2238f/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 h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
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=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -1405,9 +1405,13 @@ func TestAHaveSaysWhetherItsModuleRunsInsideTheNodesOwn(t *testing.T) {
|
|||||||
heldToday bool
|
heldToday bool
|
||||||
}{
|
}{
|
||||||
{"failed, naming nothing it runs", delivery.Have{Phase: delivery.PhaseFailed, Health: delivery.HealthFailed,
|
{"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, false},
|
Detail: "the node cannot compose it, and keeps running what it ran: no build"}, false, true},
|
||||||
{"failed at a build fingerprint", delivery.Have{FP: "build-0123abcd", Composed: "sha256:" + strings.Repeat("c", 64), Phase: delivery.PhaseFailed,
|
{"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, false},
|
Health: delivery.HealthFailed, Detail: "the node cannot compose it, and keeps running what it ran: no build"}, true, true},
|
||||||
|
// The live have of novox's de-spiegel (2026-10-28): a build of the old form, named by `build`, and no `fp`.
|
||||||
|
{"running a build of the old form", delivery.Have{Build: "build-1790368817085312877", Phase: delivery.PhaseRunning, Health: delivery.HealthOK}, true, true},
|
||||||
|
{"running a build of the new form", delivery.Have{FP: delivery.SourcePrefix2 + strings.Repeat("a", 64), Composed: "sha256:" + strings.Repeat("c", 64),
|
||||||
|
Phase: delivery.PhaseRunning, Health: delivery.HealthOK}, false, false},
|
||||||
{"running with no fingerprint (mesh-wireguard)", delivery.Have{Phase: delivery.PhaseRunning, Health: delivery.HealthOK}, 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,
|
{"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},
|
Health: delivery.HealthWaits, Detail: "held: a required config value is unset"}, true, true},
|
||||||
@@ -1428,7 +1432,7 @@ func TestAHaveSaysWhetherItsModuleRunsInsideTheNodesOwn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
s := &Shadow{c: c, ran: map[string]string{}}
|
s := &Shadow{c: c, ran: map[string]string{}}
|
||||||
s.takeRan(agent.Update{Key: key, Value: value})
|
s.takeRan(agent.Update{Key: key, Value: value})
|
||||||
if got := slices.Contains(s.heldToday(), "web"); got != row.heldToday {
|
if _, got := s.heldToday()["web"]; got != row.heldToday {
|
||||||
t.Errorf("web held today: %v, want %v", got, row.heldToday)
|
t.Errorf("web held today: %v, want %v", got, row.heldToday)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -40,9 +40,11 @@ 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
|
// 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).
|
// from the declaration kept before the switch (issue 1126, probe #532 on shanks).
|
||||||
ran map[string]string
|
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
|
// heldNow is, by module, how its have says the node keeps it today as it runs: "held", "failed", or "runs a build of
|
||||||
// it as today (frombus.Scope.HeldToday, issue 1126).
|
// the old form" (a `build` and no `fp`, which no registered build of the trunk names); "" when it is composed as any
|
||||||
heldNow map[string]bool
|
// other. A candidate that cannot compose it, or holds it only behind one of these, keeps it as today
|
||||||
|
// (frombus.Scope.HeldToday, issue 1126).
|
||||||
|
heldNow map[string]string
|
||||||
// answered is, by proposal, the candidate revisions and the input changes (moved) each answer was composed at: a proposal
|
// 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
|
// 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).
|
// every 13-17 s, woken by its own modules' haves whose health words changed at every look).
|
||||||
@@ -629,23 +631,20 @@ func (s *Shadow) answerProposal(ctx context.Context, proposal string) (delivery.
|
|||||||
|
|
||||||
// heldToday is what the node holds today: each module whose have's phase is held, and what its own composer holds, the
|
// 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.
|
// node's own composition ("node") among them when it composes none of its own.
|
||||||
func (s *Shadow) heldToday() []string {
|
func (s *Shadow) heldToday() map[string]string {
|
||||||
set := map[string]bool{}
|
out := map[string]string{}
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
for m, held := range s.heldNow {
|
for m, how := range s.heldNow {
|
||||||
if held {
|
if how != "" {
|
||||||
set[m] = true
|
out[m] = how
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
for _, m := range s.c.heldNow() {
|
for _, m := range s.c.heldNow() {
|
||||||
set[m] = true
|
if _, said := out[m]; !said {
|
||||||
|
out[m] = "held"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out := make([]string, 0, len(set))
|
|
||||||
for m := range set {
|
|
||||||
out = append(out, m)
|
|
||||||
}
|
|
||||||
sort.Strings(out)
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,10 +660,22 @@ func (s *Shadow) takeRan(u agent.Update) bool {
|
|||||||
before, had := s.ran[module]
|
before, had := s.ran[module]
|
||||||
h, err := delivery.DecodeHave(u.Value)
|
h, err := delivery.DecodeHave(u.Value)
|
||||||
if s.heldNow == nil {
|
if s.heldNow == nil {
|
||||||
s.heldNow = map[string]bool{}
|
s.heldNow = map[string]string{}
|
||||||
}
|
}
|
||||||
wasHeld := s.heldNow[module]
|
wasHeld := s.heldNow[module]
|
||||||
s.heldNow[module] = !u.Deleted && err == nil && h.Phase == delivery.PhaseHeld
|
s.heldNow[module] = ""
|
||||||
|
if !u.Deleted && err == nil {
|
||||||
|
switch {
|
||||||
|
case h.Phase == delivery.PhaseHeld:
|
||||||
|
s.heldNow[module] = "held"
|
||||||
|
case h.Phase == delivery.PhaseFailed:
|
||||||
|
s.heldNow[module] = "failed"
|
||||||
|
case h.Phase != delivery.PhaseRemoved && h.FP == "" && h.Build != "":
|
||||||
|
// A build of the old form: its have names a `build` and no `fp`, which no registered build of the trunk
|
||||||
|
// names, so the controller keeps it as it runs (issue 1126: novox's de-spiegel at build-…); a seed keeps it so.
|
||||||
|
s.heldNow[module] = "runs a build of the old form"
|
||||||
|
}
|
||||||
|
}
|
||||||
holdMoved := wasHeld != s.heldNow[module] // a hold's start or end changes the answer too
|
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 {
|
if u.Deleted || err != nil || h.Composed == "" || h.Phase == delivery.PhaseRemoved {
|
||||||
delete(s.ran, module)
|
delete(s.ran, module)
|
||||||
|
|||||||
Reference in New Issue
Block a user