From d16fa1e052ad19177113b506f8ac7488e1c42c73 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Tue, 20 Oct 2026 00:58:01 +0000 Subject: [PATCH] Hear the gate's transitions beside today's delivery module's in mesh-issues, and name the gate among the owners (issue 1350) --- .../cmd/mesh-issues/delivery_test.go | 31 ++++++++++++++++++- modules/mesh-issues/cmd/mesh-issues/main.go | 16 +++++++--- .../cmd/mesh-issues/manifest_test.go | 7 ++++- modules/mesh-issues/files/settings.json | 2 +- modules/mesh-issues/module.json | 5 +-- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/modules/mesh-issues/cmd/mesh-issues/delivery_test.go b/modules/mesh-issues/cmd/mesh-issues/delivery_test.go index 5ad2ec44..46b9d4a0 100644 --- a/modules/mesh-issues/cmd/mesh-issues/delivery_test.go +++ b/modules/mesh-issues/cmd/mesh-issues/delivery_test.go @@ -45,10 +45,16 @@ func (n *notified) notify(key, severity, summary, headline, explanation, resolve } func transition(t *testing.T, h *harness, number int, to, why, mergedAs string, at time.Time) { + t.Helper() + transitionOn(t, h, "mesh-delivery.transition", number, to, why, mergedAs, at) +} + +// transitionOn is a delivery's transition said under an event's name: today's module's, or the gate's. +func transitionOn(t *testing.T, h *harness, event string, number int, to, why, mergedAs string, at time.Time) { t.Helper() raw, _ := json.Marshal(map[string]any{"repository": "novox/mesh-catalog", "number": number, "to": to, "why": why, "merged_as": mergedAs, "at": at}) - if err := h.OnTransition(stdio.Envelope{Key: "mesh-delivery.transition", Body: raw}); err != nil { + if err := h.OnTransition(stdio.Envelope{Key: event, Body: raw}); err != nil { t.Fatal(err) } } @@ -333,3 +339,26 @@ func TestAResolvedIssueEndsItsStuckWarningAndIsNeverSaidStuck(t *testing.T) { t.Fatalf("a resolved issue said stuck: %q", note.said) } } + +// Issue 1350 AC1: the handler takes a transition under either name, today's module's or the gate's, and keeps the same fact. +func TestATransitionIsTakenUnderEitherNameOfTheDeliverysOwner(t *testing.T) { + for _, event := range transitionEvents { + t.Run(event, func(t *testing.T) { + h := newHarness(t) + n := h.open(t, "the counter skips a number") + if _, err := h.Move(MoveAsk{Number: n, Status: StatusLocated, LocatedIn: []string{"mesh-catalog modules/records"}, + FixedBy: []string{"mesh-catalog#9"}}); err != nil { + t.Fatal(err) + } + at := time.Date(2026, 10, 20, 9, 0, 0, 0, time.UTC) + transitionOn(t, h, event, 9, "delivered", "the walk is done", "abc123abc123", at) + v, err := h.View(n) + if err != nil { + t.Fatal(err) + } + if v.Delivery != "delivered to every machine it should reach" { + t.Fatalf("the issue's delivery line is %q, heard as %s; wanted delivered", v.Delivery, event) + } + }) + } +} diff --git a/modules/mesh-issues/cmd/mesh-issues/main.go b/modules/mesh-issues/cmd/mesh-issues/main.go index 03c7d83c..932f5485 100644 --- a/modules/mesh-issues/cmd/mesh-issues/main.go +++ b/modules/mesh-issues/cmd/mesh-issues/main.go @@ -145,15 +145,23 @@ func start(t *Tracker) { } } +// transitionEvents are the events a delivery's transitions are said under: today's module's, and the gate's that takes its place +// (novox/hq ADR 0450 §8, issue 1350). The old name stays until today's module is forgotten; the handler takes either. +var transitionEvents = []string{"mesh-delivery.transition", "mesh-delivery-gate.transition"} + // hearOpened subscribes the triage to the tracker's own `opened` and `moved` events and to mesh-delivery's -// `transition` (ADR 0278 §1, ADR 0279 §3), each asked again until the runtime takes it: the runtime answers only +// `transition`, and the gate's (ADR 0278 §1, ADR 0279 §3), each asked again until the runtime takes it: the runtime answers only // once Serve runs. func hearOpened(t *Tracker) { - for _, s := range []struct { + type hearing struct { pattern string handle func(stdio.Envelope) error - }{{"mesh-issues." + EventOpened, t.OnOpened}, {"mesh-issues." + EventMoved, t.OnMoved}, - {"mesh-delivery.transition", t.OnTransition}} { + } + hearings := []hearing{{"mesh-issues." + EventOpened, t.OnOpened}, {"mesh-issues." + EventMoved, t.OnMoved}} + for _, event := range transitionEvents { + hearings = append(hearings, hearing{event, t.OnTransition}) + } + for _, s := range hearings { for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) { time.Sleep(wait) err := stdio.Subscribe(s.pattern, s.handle) diff --git a/modules/mesh-issues/cmd/mesh-issues/manifest_test.go b/modules/mesh-issues/cmd/mesh-issues/manifest_test.go index 90d72212..f7ff9da7 100644 --- a/modules/mesh-issues/cmd/mesh-issues/manifest_test.go +++ b/modules/mesh-issues/cmd/mesh-issues/manifest_test.go @@ -102,9 +102,14 @@ func TestItDeclaresAndHoldsTheIssueTrackerSeatOnceForTheMesh(t *testing.T) { d[3].Name != ReservationsState || d[3].History > 1 || d[3].TTLSeconds != 0 { t.Fatalf("state %+v", d) } + // The delivery's transitions are heard from today's module and from the gate that takes its place (novox/hq ADR 0450 §8, + // issue 1350); the old name stays until today's module is forgotten. The subscriptions are the same list. + if !reflect.DeepEqual(transitionEvents, []string{"mesh-delivery.transition", "mesh-delivery-gate.transition"}) { + t.Fatalf("the triage subscribes to %v", transitionEvents) + } // The duplicate check hears the tracker's own opens and asks the agent module on its node (ADR 0278 §1, §5); // the import and a reservation read the record through the records module (issue 424). - if !reflect.DeepEqual(m.Consumes, []string{"mesh-issues." + EventOpened, "mesh-issues." + EventMoved, "mesh-delivery.transition"}) || + if !reflect.DeepEqual(m.Consumes, []string{"mesh-issues." + EventOpened, "mesh-issues." + EventMoved, "mesh-delivery.transition", "mesh-delivery-gate.transition"}) || !reflect.DeepEqual(m.Invokes, []string{"claude-code.claude_code_judge", "seat:mesh-delivery.checks", "seat:operator-channel.notify", "gitea.gitea_pull_reviews", "records.records_status", "records.records_list", "records.records_read", "mesh-knowledge.knowledge_highest"}) { t.Fatalf("consumes %v, invokes %v", m.Consumes, m.Invokes) diff --git a/modules/mesh-issues/files/settings.json b/modules/mesh-issues/files/settings.json index 54466137..dd487d3d 100644 --- a/modules/mesh-issues/files/settings.json +++ b/modules/mesh-issues/files/settings.json @@ -58,7 +58,7 @@ "mesh-catalog modules/memory-pressure", "mesh-catalog modules/mesh-catalog", "mesh-catalog modules/mesh-cli", - "mesh-catalog modules/mesh-delivery", + "mesh-catalog modules/mesh-delivery-gate", "mesh-catalog modules/mesh-issues", "mesh-catalog modules/mesh-vault", "mesh-catalog modules/mesh-watcher", diff --git a/modules/mesh-issues/module.json b/modules/mesh-issues/module.json index 36bd42b4..8d17cea4 100644 --- a/modules/mesh-issues/module.json +++ b/modules/mesh-issues/module.json @@ -57,7 +57,8 @@ "consumes": [ "mesh-issues.opened", "mesh-issues.moved", - "mesh-delivery.transition" + "mesh-delivery.transition", + "mesh-delivery-gate.transition" ], "invokes": [ "claude-code.claude_code_judge", @@ -151,7 +152,7 @@ "mesh-catalog modules/memory-pressure", "mesh-catalog modules/mesh-catalog", "mesh-catalog modules/mesh-cli", - "mesh-catalog modules/mesh-delivery", + "mesh-catalog modules/mesh-delivery-gate", "mesh-catalog modules/mesh-issues", "mesh-catalog modules/mesh-vault", "mesh-catalog modules/mesh-watcher",