diff --git a/internal/link/have.go b/internal/link/have.go index 5d190a18..f1a5a696 100644 --- a/internal/link/have.go +++ b/internal/link/have.go @@ -919,26 +919,32 @@ func nothingJudged(f ModuleFact, looked bool) string { return nodeengineclient.HealthUnknown } -// runningToSay is the event running for a module whose have was just written running and healthy at a fingerprint whose -// manifest is known, when it has not been said for that fingerprint and manifest (design 47, *What each step says*): the -// module, the fingerprint and the digest of the manifest of that build. A module not yet healthy, or one whose manifest -// the node does not know, says nothing, and is said at the write that knows. Under s.mu. +// runningToSay is the event running for a module whose have was just written running with a health of ok or failed at a +// fingerprint whose manifest is known, when it has not been said for that fingerprint, manifest and health (design 47 G +// §520): `healthy` says the build's health, and it is said again each time that changes and, the said set being empty, for +// every module the have holds at the node-engine's start. A module whose health is waits or unknown, or whose manifest the +// node does not know, says nothing, and is said at the write that knows. Under s.mu. func (s *haveState) runningToSay(module string, h nodeengine.HaveByNodeModuleValue, manifest func(module, fp string) string) (nodeengine.Running, bool) { - if h.Phase != nodeengineclient.PhaseRunning || h.Health != nodeengineclient.HealthOK || h.Fp == "" || manifest == nil { + if h.Phase != nodeengineclient.PhaseRunning || h.Fp == "" || manifest == nil { + return nodeengine.Running{}, false + } + if h.Health != nodeengineclient.HealthOK && h.Health != nodeengineclient.HealthFailed { return nodeengine.Running{}, false } digest := manifest(module, h.Fp) if digest == "" { return nodeengine.Running{}, false } - if s.runningSaid[module] == h.Fp+"\x00"+digest { + healthy := h.Health == nodeengineclient.HealthOK + said := fmt.Sprintf("%s\x00%s\x00%t", h.Fp, digest, healthy) + if s.runningSaid[module] == said { return nodeengine.Running{}, false } if s.runningSaid == nil { s.runningSaid = map[string]string{} } - s.runningSaid[module] = h.Fp + "\x00" + digest - return nodeengine.Running{V: sdkkeys.Version, Module: module, Fingerprint: h.Fp, Manifest: digest}, true + s.runningSaid[module] = said + return nodeengine.Running{V: sdkkeys.Version, Module: module, Fingerprint: h.Fp, Manifest: digest, Healthy: &healthy}, true } // waitToSay is the event wait-held for a have written with a wait the last one did not say, or wait-ended for one written with diff --git a/internal/link/have_test.go b/internal/link/have_test.go index 084557bf..132fa730 100644 --- a/internal/link/have_test.go +++ b/internal/link/have_test.go @@ -722,7 +722,7 @@ func TestAnAppliedModulesHaveNamesTheFingerprintAndTheDigestAndFollowsItsHealth( q.SayHealth(context.Background(), *healthOf(map[string]string{"web": StateHealthy})) q.SayHealth(context.Background(), *healthOf(map[string]string{"web": StateHealthy})) if got := l.saidOn("mesh.seat.node-engine.event.running.n1.web "); len(got) != 1 || - !strings.Contains(got[0], `"fingerprint":"`+fp+`"`) || !strings.Contains(got[0], `"manifest":"sha256:`+strings.Repeat("c", 64)+`"`) { + !strings.Contains(got[0], `"healthy":true`) || !strings.Contains(got[0], `"fingerprint":"`+fp+`"`) || !strings.Contains(got[0], `"manifest":"sha256:`+strings.Repeat("c", 64)+`"`) { t.Fatalf("running said %v, want one for the healthy build", got) } // A have with a wait says wait-held once on wait-held.., and wait-ended when the wait is gone (design 47 G §541, A-113). @@ -750,6 +750,10 @@ func TestAnAppliedModulesHaveNamesTheFingerprintAndTheDigestAndFollowsItsHealth( if got := l.have(t, "n1.web"); got.Health != nodeengineclient.HealthFailed || got.Fp != fp || got.Composed != dig { t.Fatalf("a failing check after the start window: %+v", got) } + // §520: running is said again when healthy changes, now false, and the stream's newest says it. + if got := l.saidOn("mesh.seat.node-engine.event.running.n1.web "); len(got) != 2 || !strings.Contains(got[1], `"healthy":false`) { + t.Fatalf("running said %v after the check failed, want a second with healthy false", got) + } // The bus's user list written alone rides on the module's have, and an apply after it keeps it (ADR 0547 §8). w := nodeengine.UserListWritten{Source: "n1.web." + fp, Revision: 4, Wanted: dig, Digest: dig, Users: 3,