From da31740f00dae32b114e7133f00e37b37bc7db24 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 24 Oct 2026 18:31:29 +0200 Subject: [PATCH] The view's motion events carry the time they are about and call a held or past-bound module stopped, as the messenger reads them (issue 1906 D10) --- .../cmd/mesh-delivery-view/integration_test.go | 2 +- .../mesh-delivery-view/internal/view/view.go | 17 ++++++++++++----- .../internal/view/wants_test.go | 6 +++--- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go b/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go index 144d91b..69d4247 100644 --- a/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go +++ b/modules/mesh-delivery-view/cmd/mesh-delivery-view/integration_test.go @@ -911,7 +911,7 @@ func TestProcessWholeSaysModulesInMotion(t *testing.T) { } have("g14", "web", fpOld, delivery.PhaseHeld, delivery.HealthWaits, "no space left") stk := waitFor("stuck.g14.web." + hex) - if err := json.Unmarshal(stk.Data, &body); err != nil || body.Outcome != "held" || body.Words != "no space left" { + if err := json.Unmarshal(stk.Data, &body); err != nil || body.Outcome != "stopped" || body.Words != "no space left" { t.Errorf("stuck = %+v, %v", body, err) } time.Sleep(time.Second) diff --git a/modules/mesh-delivery-view/internal/view/view.go b/modules/mesh-delivery-view/internal/view/view.go index fbe176c..689532f 100644 --- a/modules/mesh-delivery-view/internal/view/view.go +++ b/modules/mesh-delivery-view/internal/view/view.go @@ -1109,7 +1109,8 @@ const ( ) // MotionBody is the body of the three events. Outcome and Words are those of settled and stuck: settled is "running"; stuck is -// "held" or "failed" (the have's own words in Words) or "past-bound" (the watchman's words of what it waited for). +// "failed" (a health check failed) or "stopped" (the module is held, or the watchman says the want stopped moving past its bound), +// with the have's own words or the watchman's in Words. type MotionBody struct { Node string `json:"node"` Module string `json:"module"` @@ -1118,8 +1119,10 @@ type MotionBody struct { Composed string `json:"composed"` Since time.Time `json:"since"` BoundSeconds int `json:"bound_seconds"` - Outcome string `json:"outcome,omitempty"` - Words string `json:"words,omitempty"` + // At is when the have or the want last changed, the time the event is about. + At time.Time `json:"at"` + Outcome string `json:"outcome,omitempty"` + Words string `json:"words,omitempty"` } // MotionEvent is an event to say: its type (moving, settled or stuck), its id (the bus's dedup id and the event id header, one for @@ -1218,8 +1221,12 @@ func (m *Model) motionOf(hk string) []MotionEvent { h, has := m.haves[hk] has = has && h.err == "" body := MotionBody{Node: node, Module: module, ToFP: w.value.FP, Composed: w.value.Composed, Since: w.at, BoundSeconds: MotionBoundSeconds} + body.At = w.at if has { body.FromFP = h.have.FP + if h.at.After(body.At) { + body.At = h.at + } } id := func(kind string) string { return kind + "." + node + "." + module + "." + strings.TrimPrefix(w.value.Composed, "sha256:") @@ -1247,12 +1254,12 @@ func (m *Model) motionOf(hk string) []MotionEvent { stop := body switch { case has && h.have.Phase == delivery.PhaseHeld: - stop.Outcome, stop.Words = "held", h.have.Detail + stop.Outcome, stop.Words = "stopped", h.have.Detail case has && (h.have.Health == delivery.HealthFailed || h.have.Phase == delivery.PhaseFailed) && h.have.FP == w.value.FP: stop.Outcome, stop.Words = "failed", h.have.Detail default: if s, ok := m.stuckWant(w.value, node, module); ok { - stop.Outcome, stop.Words = "past-bound", s + stop.Outcome, stop.Words = "stopped", s } else { return out } diff --git a/modules/mesh-delivery-view/internal/view/wants_test.go b/modules/mesh-delivery-view/internal/view/wants_test.go index a6f3180..42832ef 100644 --- a/modules/mesh-delivery-view/internal/view/wants_test.go +++ b/modules/mesh-delivery-view/internal/view/wants_test.go @@ -261,7 +261,7 @@ func TestAModuleInMotionIsSaidMovingThenSettledOnceEach(t *testing.T) { } mv := (*said)[0] if mv.Body.Node != "ace" || mv.Body.Module != "web" || mv.Body.FromFP != fpOld || mv.Body.ToFP != fpNew || mv.Body.Composed != "sha256:"+strings.Repeat("c", 64) || - mv.Body.Since.IsZero() || mv.Body.BoundSeconds != 1800 || mv.ID != "moving.ace.web."+strings.Repeat("c", 64) { + mv.Body.Since.IsZero() || mv.Body.At.IsZero() || mv.Body.BoundSeconds != 1800 || mv.ID != "moving.ace.web."+strings.Repeat("c", 64) { t.Errorf("moving = %+v", mv) } fill(m, haveFP(t, "ace", "web", fpOld, delivery.HealthOK), wantE(t, "ace", "web", delivery.Want{FP: fpNew})) @@ -284,7 +284,7 @@ func TestAModuleHeldOrFailedIsSaidStuckWithItsWords(t *testing.T) { fill(m, wantE(t, "ace", "web", delivery.Want{FP: fpNew})) b, _ := delivery.EncodeHave(delivery.Have{FP: fpOld, Phase: delivery.PhaseHeld, Health: delivery.HealthWaits, Detail: "no space left"}) fill(m, Entry{Bucket: delivery.HaveBucket, Key: "ace.web", Value: b, At: t0}) - if types(*said) != "moving,stuck" || (*said)[1].Body.Outcome != "held" || (*said)[1].Body.Words != "no space left" { + if types(*said) != "moving,stuck" || (*said)[1].Body.Outcome != "stopped" || (*said)[1].Body.Words != "no space left" { t.Fatalf("said %v: %+v", types(*said), *said) } fill(m, Entry{Bucket: delivery.HaveBucket, Key: "ace.web", Value: b, At: t0}) @@ -310,7 +310,7 @@ func TestAWantTheWatchmanSaysStoppedMovingIsSaidStuckPastItsBound(t *testing.T) t.Fatal(err) } fill(m, Entry{Bucket: delivery.StuckBucket, Key: sk, Value: v, At: t0}) - if types(*said) != "moving,stuck" || (*said)[1].Body.Outcome != "past-bound" { + if types(*said) != "moving,stuck" || (*said)[1].Body.Outcome != "stopped" { t.Errorf("said %q: %+v", types(*said), *said) } }