Keep a module's condition open when the controller's conditions are read (issue 1217, review round 1 finding 1)

This commit is contained in:
2026-10-19 12:04:58 +02:00
parent 5b235a6e80
commit f737418bb3
4 changed files with 47 additions and 19 deletions
+17 -14
View File
@@ -59,19 +59,22 @@ const (
// Condition is what this holder needs of one.
type Condition struct {
Key string
Kind string
Scope string
ID string
Machine string
Severity string
Summary string
Headline string
Explanation string
Resolved string
Needs string
Actions []Action
Source string
Key string
Kind string
Scope string
ID string
Machine string
Severity string
Summary string
Headline string
Explanation string
Resolved string
Needs string
Actions []Action
Source string
// Publisher is the module that said the condition on the operator channel's one subject; empty for the
// controller's own, which a reading of the controller's conditions may end.
Publisher string
Resolver string
Raised time.Time
LastObserved time.Time
@@ -360,7 +363,7 @@ func (h *Holder) takeCondition(publisher string, body []byte) {
}
c := Condition{Key: sc.Key, Scope: sc.Subject.Scope, ID: sc.Subject.ID, Machine: sc.Subject.Machine,
Severity: sc.Severity, Summary: sc.Summary, Headline: sc.Headline, Explanation: sc.Explanation,
Resolved: sc.Resolved, Needs: sc.Needs, Source: publisher}
Resolved: sc.Resolved, Needs: sc.Needs, Source: publisher, Publisher: publisher}
if i := strings.LastIndex(sc.Key, "."); i >= 0 {
c.Kind = sc.Key[i+1:]
}
@@ -6,6 +6,7 @@ import (
"fmt"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/asks"
)
@@ -132,3 +133,24 @@ func TestABodyThatIsNotAConditionIsDroppedAndTheLogNamesThePublisher(t *testing.
}
}
}
func TestAModulesConditionStaysOpenWhenTheControllersConditionsAreRead(t *testing.T) { // 1217/AC7
h, tg, dt, c, _ := newHolder(t)
body := conditionBody(t, sdkCondition("look at it."))
if err := h.TakeAsk("condition", "mesh-delivery-teller", body); err != nil {
t.Fatal(err)
}
settle(h, c)
c.pass(time.Second)
res := h.Sync(nil, c.now()) // the controller lists nothing: the teller's condition is not its to end
if len(res.Gone) != 0 || len(h.Open()) != 1 {
t.Fatalf("the reading ended a module's condition: gone %v, open %+v", res.Gone, h.Open())
}
if err := h.TakeAsk("condition", "mesh-delivery-teller", body); err != nil { // raised again: the same message
t.Fatal(err)
}
settle(h, c)
if len(dt.sends)+len(tg.sends) != 1 {
t.Fatalf("said %d times, want once", len(dt.sends)+len(tg.sends))
}
}
+6 -4
View File
@@ -184,8 +184,10 @@ type Record struct {
Answered string `json:"answered,omitempty"`
// Ask is the open ask about this record, `<asker>.<id>`: its options are this record's answers
// (novox/hq ADR 0259).
Ask string `json:"ask,omitempty"`
Origin string `json:"origin"`
Ask string `json:"ask,omitempty"`
Origin string `json:"origin"`
// Publisher is the module that said it on the operator channel's one subject; empty for the controller's.
Publisher string `json:"publisher,omitempty"`
More string `json:"more"`
Raised time.Time `json:"raised"`
SilencedTill time.Time `json:"silenced_till,omitempty"`
@@ -228,8 +230,8 @@ func recordOf(c Condition) Record {
return Record{Key: c.Key, Kind: c.Kind, Subject: c.SubjectWords(), Scope: c.Scope, SubjectID: c.ID, Machine: c.Machine,
Severity: c.Severity, Summary: c.Summary,
Headline: c.Headline, Explanation: c.Explanation, Resolved: c.Resolved, Needs: c.Needs, Actions: c.Actions,
Origin: "condition",
More: "conditions show " + c.Key, Raised: c.Raised, SilencedTill: c.SilencedTill, SilencedWhy: c.SilencedWhy}
Origin: "condition", Publisher: c.Publisher,
More: "conditions show " + c.Key, Raised: c.Raised, SilencedTill: c.SilencedTill, SilencedWhy: c.SilencedWhy}
}
// takeWords keeps the plain words current, as the summary is: a condition's words follow what is seen.
+2 -1
View File
@@ -33,13 +33,14 @@ func (h *Holder) Sync(open []Condition, asked time.Time) SyncResult {
for _, c := range open {
in[c.Key] = true
}
// A module's condition (Publisher set) is not the controller's to end: only what it said is ended here.
// What is open here and no longer there ended while this holder was not told: forgotten without
// a message — said only where an edit notifies nobody. What this holder heard of after the
// reading was asked for is newer than it, and stands.
h.mu.Lock()
var gone []*Record
for _, r := range h.open {
if r.Origin == "condition" && r.Cleared.IsZero() && !in[r.Key] && !r.Seen.After(asked) {
if r.Origin == "condition" && r.Publisher == "" && r.Cleared.IsZero() && !in[r.Key] && !r.Seen.After(asked) {
gone = append(gone, r)
}
}