mesh-issues stops listening to the removed module's transition, which every controller merge check now refuses (issue 1406) #382

Merged
mesh-tools merged 1 commits from fix/1406-drop-the-old-transition-name into main 2026-10-20 13:28:30 +00:00
9 changed files with 19 additions and 21 deletions
@@ -13,10 +13,9 @@ import (
// issue 1351; how it is checked). Today's module `modules/mesh-delivery` was removed by a pull request of its own after the
// handover, so the claimants are exactly one. And no manifest but the gate's and mesh-issues' names the old module in what it
// reads or hears, but through the seat's name (`mesh-delivery` as a seat or a repository, `seat:mesh-delivery.<verb>`) and the
// names below: the three buckets the gate carries over (ADR 0450 §6) and the event the trackers hear (issue 1350).
// names below: the three buckets the gate carries over (ADR 0450 §6). The event `mesh-delivery.transition` is no longer heard by anyone (issue 1406).
var oldModuleNames = map[string][]string{
"mesh-delivery-gate": {"mesh-delivery.deliveries", "mesh-delivery.groups", "mesh-delivery.pulls"},
"mesh-issues": {"mesh-delivery.transition"},
}
// seatClaimants are the modules that claim the seat: the gate alone.
@@ -13,7 +13,7 @@ import (
// the forge's holder, asked through its tools). Owed, never lost: kept with the delivery before anything is
// tried, and tried again until done.
// transitionEvent is what `mesh-delivery.transition` says: no secret, no address. opened is when its pull
// transitionEvent is what `mesh-delivery-gate.transition` says: no secret, no address. opened is when its pull
// request was first heard (zero when not known); with its class and its merge's moment it is what the issue
// tracker measures a fix's time by (novox/hq ADR 0313 §2, §4).
func transitionEvent(d *Delivery, t Transition, opened time.Time) map[string]any {
+2 -2
View File
@@ -83,7 +83,7 @@ still read as that issue's verdict.
**Ready.** An issue is ready when it is not resolved or wontfix and every issue it depends on is wontfix, or
resolved with every pull request its `fixed-by` names (`<repository>#<number>`) delivered. The module consumes
`mesh-delivery.transition` and marks a pull request delivered on every issue naming it; at its own `moved` to
`mesh-delivery-gate.transition` and marks a pull request delivered on every issue naming it; at its own `moved` to
resolved it asks `seat:mesh-delivery.checks` once for what it has not heard. An issue made ready gains a note and
the event `ready`. `get` answers `waits-for`, `needed-by` and `ready`; `list` filters on `severity`, `needs` and
`ready` — an agent looking for work asks `severity=minor`, `needs=none`, `ready=true`. `set` changes severity,
@@ -100,7 +100,7 @@ key-value state has no schema to migrate.
## Whether the fix is delivered (hq ADR 0281)
For each pull request an issue's `fixed-by` names, the module keeps what `mesh-delivery.transition` last said —
For each pull request an issue's `fixed-by` names, the module keeps what `mesh-delivery-gate.transition` last said —
state, why, merged and when — or asks `seat:mesh-delivery.checks` once (at its own `moved`) for one it has not
heard of. Nothing sets it by hand. `get` and every `list` row carry `delivery`, one line derived from it, worst
first: "fix not delivered: …", "held: …", "fix merged, not delivered: …", "fix not merged: …", "nothing heard from
@@ -46,10 +46,10 @@ 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(t, h, "mesh-delivery-gate.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.
// transitionOn is a delivery's transition said under an event's name.
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,
@@ -340,8 +340,8 @@ func TestAResolvedIssueEndsItsStuckWarningAndIsNeverSaidStuck(t *testing.T) {
}
}
// 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) {
// Issue 1350 AC1, issue 1406: the handler takes a transition under every name it hears (the gate's alone) and keeps the fact.
func TestATransitionIsTakenUnderEveryNameTheTrackerHears(t *testing.T) {
for _, event := range transitionEvents {
t.Run(event, func(t *testing.T) {
h := newHarness(t)
@@ -50,7 +50,7 @@ func (c *fixClock) delivery(t *testing.T, number int, to, class string, opened,
body["opened"] = *opened
}
raw, _ := json.Marshal(body)
if err := c.OnTransition(stdio.Envelope{Key: "mesh-delivery.transition", Body: raw}); err != nil {
if err := c.OnTransition(stdio.Envelope{Key: "mesh-delivery-gate.transition", Body: raw}); err != nil {
t.Fatal(err)
}
}
+5 -5
View File
@@ -145,12 +145,12 @@ 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"}
// transitionEvents are the events a delivery's transitions are said under: the gate's (novox/hq ADR 0450 §8, issues 1350 and
// 1406). Today's module, which said mesh-delivery.transition, is forgotten.
var transitionEvents = []string{"mesh-delivery-gate.transition"}
// hearOpened subscribes the triage to the tracker's own `opened` and `moved` events and to mesh-delivery's
// `transition`, and the gate's (ADR 0278 §1, ADR 0279 §3), each asked again until the runtime takes it: the runtime answers only
// hearOpened subscribes the triage to the tracker's own `opened` and `moved` events and to the gate's
// `transition` (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) {
type hearing struct {
@@ -103,14 +103,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"}) {
// The delivery's transitions are heard from the gate alone (novox/hq ADR 0450 §8, issues 1350 and 1406): today's module is
// forgotten, so nothing says mesh-delivery.transition. The subscriptions are the same list.
if !reflect.DeepEqual(transitionEvents, []string{"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", "mesh-delivery-gate.transition"}) ||
if !reflect.DeepEqual(m.Consumes, []string{"mesh-issues." + EventOpened, "mesh-issues." + EventMoved, "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)
@@ -472,7 +472,7 @@ func TestAnIssueIsReadyOnlyWhenWhatItDependsOnIsDelivered(t *testing.T) {
if asked != 1 || ready(h, w) {
t.Fatalf("ready before its fix is delivered (asked %d)", asked)
}
tr := stdio.Envelope{Key: "mesh-delivery.transition", Body: json.RawMessage(`{"repository":"novox/mesh-catalog","number":9,"to":"delivered"}`)}
tr := stdio.Envelope{Key: "mesh-delivery-gate.transition", Body: json.RawMessage(`{"repository":"novox/mesh-catalog","number":9,"to":"delivered"}`)}
for i := 0; i < 2; i++ {
if err := h.OnTransition(tr); err != nil {
t.Fatal(err)
-1
View File
@@ -57,7 +57,6 @@
"consumes": [
"mesh-issues.opened",
"mesh-issues.moved",
"mesh-delivery.transition",
"mesh-delivery-gate.transition"
],
"invokes": [