Say checked, healer-acted and act-recorded on their seats' subjects, where their consumers hear them, and pin mesh-sdk fbd00c8 (issues 1952, 1953)

stdio.Emit put them on the module's subject, which no consumer of a seat's event hears. The manifests no longer list a seat's events as the module's own, which the SDK now refuses. mesh-hand-acts takes its act layout and names from mesh-sdk's handacts, and counts a repeated cause by its Repair rule.
This commit is contained in:
2026-10-29 22:12:08 +01:00
parent d632461eb9
commit b5fdcb88c8
26 changed files with 102 additions and 130 deletions
@@ -40,8 +40,8 @@ func (runtimeStore) Keys(state string) ([]string, error) { return stdio.State(st
// NewChecker is the self-check over the runtime.
func NewChecker() *Checker {
return &Checker{Store: runtimeStore{}, Registry: Registry(), Now: time.Now, Logf: logf, Host: os.Getenv("MESH_NODE"),
Ask: func(key string, body any) (json.RawMessage, error) { return stdio.Ask(key, body) },
Emit: func(event string, body any) error { return stdio.Emit(event, body) }}
Ask: func(key string, body any) (json.RawMessage, error) { return stdio.Ask(key, body) },
Publish: stdio.SeatPublish}
}
func main() {
@@ -47,7 +47,8 @@ func TestTheManifestSaysWhatTheCodeDoes(t *testing.T) {
if len(m.Claims) != 1 || m.Claims[0].Name != Seat || m.Claims[0].Scope != "mesh" || !slices.Equal(m.Claims[0].Serves, Verbs) {
t.Errorf("claims %+v, want %s at mesh scope serving %v", m.Claims, Seat, Verbs)
}
if !slices.Equal(m.Emits, []string{CheckedEvent}) {
// checked is the seat's event, said on the seat's subject: a module lists no event of a seat it claims.
if len(m.Emits) != 0 {
t.Errorf("emits %v", m.Emits)
}
if !slices.Equal(m.Invokes, []string{StreamsTool, ClosedTool, SignalsVerb}) {
@@ -32,6 +32,9 @@ const (
VerdictsState = "verdicts"
LastKey = "last"
CheckedEvent = health.EventChecked
// CheckedSubject is where the seat says it: a seat's events are on mesh.seat.<seat>.event.<name>, which its
// consumers hear (catalogue.SeatSays).
CheckedSubject = "mesh.seat." + Seat + ".event." + CheckedEvent
)
// The clocks: the self-check's run is the one timer among the four holders (ADR 0564 §1).
@@ -83,9 +86,10 @@ type (
// Checker is the self-check: the registry and what its probes reach.
type Checker struct {
Store Store
Ask func(key string, body any) (json.RawMessage, error)
Emit func(event string, body any) error
Store Store
Ask func(key string, body any) (json.RawMessage, error)
// Publish says a seat event on its subject, de-duplicated by id (stdio.SeatPublish).
Publish func(subject string, body any, id string) error
Now func() time.Time
Logf func(format string, args ...any)
Host string
@@ -328,10 +332,10 @@ func (c *Checker) keep(run Run) {
if err := c.Store.Put(VerdictsState, run.Run, Overview(run)); err != nil {
c.Logf("[%s] the run %s is not kept among the week's verdicts: %v", Module, run.Run, err)
}
if c.Emit == nil {
if c.Publish == nil {
return
}
if err := c.Emit(CheckedEvent, run); err != nil {
if err := c.Publish(CheckedSubject, run, run.Run); err != nil {
c.mu.Lock()
c.unsaid++
c.mu.Unlock()
@@ -81,9 +81,9 @@ func checker(store *memStore, registry ...Probe) (*Checker, *[]string) {
now := time.Date(2026, 10, 29, 21, 0, 0, 0, time.UTC)
c := &Checker{Store: store, Registry: registry, Host: "novox", Logf: func(string, ...any) {},
Now: func() time.Time { now = now.Add(time.Second); return now },
Emit: func(event string, body any) error {
Publish: func(subject string, body any, id string) error {
raw, _ := json.Marshal(body)
said = append(said, event+" "+string(raw))
said = append(said, subject+" "+id+" "+string(raw))
return nil
}}
return c, &said
@@ -147,11 +147,14 @@ func TestARunWritesFindingsAsTheyChange(t *testing.T) {
if keys, _ := store.Keys(VerdictsState); len(keys) != len(steps) {
t.Errorf("verdicts kept %d runs, want %d", len(keys), len(steps))
}
if len(*said) != len(steps) || !strings.HasPrefix((*said)[0], CheckedEvent+" ") {
// Said on the seat's subject, where its consumers hear it, de-duplicated by the run's id.
if len(*said) != len(steps) || !strings.HasPrefix((*said)[0], "mesh.seat.mesh-self-check.event.checked doctor-") {
t.Fatalf("said %v", *said)
}
_, rest, _ := strings.Cut((*said)[0], " ")
_, rest, _ = strings.Cut(rest, " ")
var body map[string]any
_ = json.Unmarshal([]byte(strings.TrimPrefix((*said)[0], CheckedEvent+" ")), &body)
_ = json.Unmarshal([]byte(rest), &body)
for _, field := range []string{"run", "at", "started", "took", "interval-seconds", "counts", "probes", "controller", "why"} {
if _, ok := body[field]; !ok {
t.Errorf("checked's body lacks %s, which doctor-heartbeat carries: %v", field, body)
@@ -146,6 +146,13 @@ func (p *player) handle(m wire) {
err = kv.Delete(p.ctx, a.Key)
}
p.answer(m.ID, map[string]any{}, err)
case "mesh/seat.publish":
var e struct {
Subject string `json:"subject"`
Body json.RawMessage `json:"body"`
}
_ = json.Unmarshal(m.Params, &e)
p.answer(m.ID, map[string]any{}, p.nc.Publish(e.Subject, e.Body))
case "mesh/publish":
var e struct {
Key string `json:"key"`
@@ -230,7 +237,7 @@ func TestTheSelfCheckWholeRunsOnTheBus(t *testing.T) {
t.Fatal(err)
}
}
checked, err := nc.SubscribeSync("mesh.mod." + Module + ".event." + CheckedEvent)
checked, err := nc.SubscribeSync("mesh.seat." + Seat + ".event." + CheckedEvent)
if err != nil {
t.Fatal(err)
}
+1 -1
View File
@@ -3,7 +3,7 @@ module mesh-self-check
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029204453-69fcb6c1c1ba
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029211044-fbd00c89436c
github.com/nats-io/nats.go v1.54.0
)
+2 -2
View File
@@ -1,5 +1,5 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029204453-69fcb6c1c1ba h1:uXwkcQWgbu/UthizGHFaKPiA4NBVJgqL9q2jJnnGR1o=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029204453-69fcb6c1c1ba/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029211044-fbd00c89436c h1:Yd6AslGN7pCniQ9Q7ch05xAWHP57grXRoH3AsDQrSeY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029211044-fbd00c89436c/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=
-3
View File
@@ -12,9 +12,6 @@
]
}
],
"emits": [
"checked"
],
"invokes": [
"nats.nats_streams",
"nats.nats_closed_connections",