From 19d06163de06c0b7593990238ea0d791eb95dc5f Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 22 Oct 2026 21:38:53 +0200 Subject: [PATCH] Announce the checker's states, owed readable, so the forge module may read it (R2-1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit R4e round 2 finding R2-1, ADR 0521: the forge module writes mesh/check from the checker's owed state, read by a watch, because no event order between the forge bridge and the forge module is kept by the delivery plan. A reader is granted from the owner's announcement (ADR 0495 §2-§3), and the checker announced nothing. It now says every state it keeps, with owed, previews and verdicts readable: the forge module and the bridge read owed, the indexer previews, the gate verdicts. --- .../mesh-delivery-checker/integration_test.go | 30 ++++++++++ .../cmd/mesh-delivery-checker/kept_test.go | 55 +++++++++++++++++++ .../cmd/mesh-delivery-checker/main.go | 16 ++++++ 3 files changed, 101 insertions(+) create mode 100644 modules/mesh-delivery-checker/cmd/mesh-delivery-checker/kept_test.go diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go index a44507a..e2f4c15 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go @@ -19,6 +19,7 @@ import ( "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" + "git.novox.be/novox/mesh-sdk/go/announce" "git.novox.be/novox/mesh-sdk/go/delivery" "mesh-delivery-checker/internal/check" @@ -651,3 +652,32 @@ func TestATryIsJudgedAndItsEndWrittenWithNoStatusOwed(t *testing.T) { } } } + +// TestTheCheckerSaysOwedReadableOnTheBus: started, the checker says on its own states subject what it keeps, owed among +// the readable ones, which is what grants the forge module its read of owed (ADR 0495 §2-§3, ADR 0521; R4e finding R2-1). +func TestTheCheckerSaysOwedReadableOnTheBus(t *testing.T) { // R2-1 + b := startBed(t) + if _, err := b.js.CreateStream(b.ctx, jetstream.StreamConfig{Name: "EVENTS", Subjects: []string{"mesh.mod.*.event.>"}}); err != nil { + t.Fatal(err) + } + b.start(t, nil) + events, err := b.js.Stream(b.ctx, "EVENTS") + if err != nil { + t.Fatal(err) + } + var said announce.States + for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) { + m, err := events.GetLastMsgForSubject(b.ctx, announce.Subject("mesh-delivery-checker")) + if err != nil { + continue + } + if err := json.Unmarshal(m.Data, &said); err != nil { + t.Fatal(err) + } + break + } + if said.Module != "mesh-delivery-checker" || said.Node != "n1" || !said.Keeps("owed") || !said.Readable("owed") || + !said.Readable("previews") || !said.Readable("verdicts") || said.Readable("kept") || said.Readable("tries") || len(said.States) != 5 { + t.Fatalf("the checker said %+v, want its five states with owed, previews and verdicts readable", said) + } +} diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/kept_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/kept_test.go new file mode 100644 index 0000000..e19c92c --- /dev/null +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/kept_test.go @@ -0,0 +1,55 @@ +package main + +import ( + "encoding/json" + "os" + "sort" + "testing" +) + +// The checker says on the bus which states it keeps and which others may read (novox/hq ADR 0495 clauses 2 and 3): a +// reader is granted from that announcement. owed is read by the forge module, which writes mesh/check from it (ADR 0521, +// R4e round 2 finding R2-1), and by the forge bridge; previews by the indexer; verdicts by the gate. The list names every +// state of the manifest, so no state is refused at the call for want of being announced (clause 5). +func TestTheCheckerAnnouncesEveryStateItKeepsAndOwedReadable(t *testing.T) { // R2-1, ADR 0495 §2-§3, ADR 0521 + raw, err := os.ReadFile("../../module.json") + if err != nil { + t.Fatal(err) + } + var m struct { + State []struct{ Name string } `json:"state"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatal(err) + } + var manifest, announced, readable []string + for _, s := range m.State { + manifest = append(manifest, s.Name) + } + for _, s := range keptStates { + announced = append(announced, s.Name) + if s.Readable { + readable = append(readable, s.Name) + } + } + sort.Strings(manifest) + sort.Strings(announced) + if !equal(manifest, announced) { + t.Errorf("announced %v, the manifest keeps %v", announced, manifest) + } + if want := []string{"owed", "previews", "verdicts"}; !equal(readable, want) { + t.Errorf("readable %v, want %v", readable, want) + } +} + +func equal(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go index 3320df7..7026c9f 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go @@ -25,6 +25,7 @@ import ( "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" + "git.novox.be/novox/mesh-sdk/go/announce" "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/delivery/buildrun" "git.novox.be/novox/mesh-sdk/go/process" @@ -113,6 +114,16 @@ func durationEnv(getenv func(string) string, name string, def time.Duration) (ti } // run is the program: it returns when ctx ends or the bus cannot be used. +// keptStates is every state the checker keeps and which others may read (ADR 0495 §2-§3): owed by the forge module, +// which writes mesh/check from it (ADR 0521), and by the forge bridge; previews by the indexer; verdicts by the gate. +var keptStates = []announce.State{ + {Name: "kept"}, + {Name: "owed", Readable: true}, + {Name: "previews", Readable: true}, + {Name: "tries"}, + {Name: "verdicts", Readable: true}, +} + func run(ctx context.Context, getenv func(string) string, logOut io.Writer) error { logger := log.New(logOut, "mesh-delivery-checker: ", log.LstdFlags) path := strings.TrimSpace(getenv("MESH_BROKER_FILE")) @@ -167,6 +178,11 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro return err } defer caller.Close() + // What the checker keeps, said on the bus (ADR 0495 §2-§3): a reader is granted from it. Not fatal when it cannot be said + // now: process.Conn says it again at every reconnect. + if err := caller.Keeps(keptStates...); err != nil { + logger.Printf("what the checker keeps could not be said on the bus, so readers of owed, previews and verdicts are not granted until it is: %v", err) + } stepBound, err := durationEnv(getenv, "MESH_CHECKER_STEP_BOUND", defaultStepBound) if err != nil {