mesh-delivery: a closed group whose members all ended ends, never delivering
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request

A superseded member counts as reached when a later delivered walk on its
trunk carried every module it moves (ADR 0270's covered test); otherwise
the group reads stopped, as groupTurns already treats it. Five groups
read "delivering, 1 of 2 delivered" for days with nothing in progress
(novox/hq issue 442).
This commit is contained in:
2026-10-11 02:39:45 +02:00
parent b9de001833
commit 036544ee0f
2 changed files with 183 additions and 2 deletions
@@ -1327,6 +1327,25 @@ func (h *Holder) planGroup(g *Group, members []*Delivery, now time.Time) []func(
h.keepGroup(g)
})
}
// A superseded member of a closed group: whether a later delivered walk on its trunk carried every module it
// moves, ADR 0270's covered test, kept on it as CoveredBy. Its own state stays superseded — a final state never
// moves — and its group counts it as reached (novox/hq issue 442).
if g.Closed {
for _, d := range members {
if d.State != Superseded || d.CoveredBy != "" {
continue
}
var ask []string
if d.CoveredBy, ask = h.coverOf(d); d.CoveredBy != "" {
h.keep(d)
}
for _, in := range ask {
if a := h.askContains(d, in, now); a != nil {
asks = append(asks, a)
}
}
}
}
// Its state, said when it changes.
if state, why := GroupState(g, members); state != g.Said {
g.Said = state
@@ -1398,6 +1417,8 @@ func (h *Holder) groupTurns(g *Group, members []*Delivery) []func() {
}
switch {
case d.State == Failed || d.State == Stopped || d.State == Superseded:
// A superseded member is an end here as in GroupState, which reads the group stopped unless a later
// delivered walk carried it (novox/hq issue 442).
for _, later := range ordered[i+1:] {
if a := h.stopInGroup(byID[later.ID], fmt.Sprintf("its group %s stopped: %s %s", g.ID, d.ID, d.State)); a != nil {
asks = append(asks, a)
@@ -1468,10 +1489,27 @@ func (h *Holder) stopInGroup(d *Delivery, why string) func() {
}
// GroupState is a group's state, derived from its members and its composed check — never set apart.
//
// A superseded member is an end, as groupTurns has always read it: it stops the members after it. It counts as
// reached when a later delivered walk on its trunk carried every module it moves (CoveredBy, ADR 0270's covered
// test); a closed group whose members all ended is then delivered, and otherwise stopped, naming the member. So
// GroupState and groupTurns agree, and a group with nothing in progress never reads delivering (novox/hq issue
// 442: five such groups read "delivering, 1 of 2 delivered" for days).
func GroupState(g *Group, members []*Delivery) (string, string) {
count := map[State]int{}
reached, ended := 0, 0
var uncovered []string
for _, d := range members {
count[d.State]++
if d.State.Final() {
ended++
}
switch {
case d.State.Reached(), d.State == Superseded && d.CoveredBy != "":
reached++
case d.State == Superseded:
uncovered = append(uncovered, d.ID)
}
}
n := len(members)
switch {
@@ -1479,10 +1517,13 @@ func GroupState(g *Group, members []*Delivery) (string, string) {
return "failed", "a member failed"
case count[Stopped] > 0 && g.Closed:
return "stopped", "a member was stopped"
case n > 0 && count[Delivered]+count[Retired]+count[Covered] == n:
case n > 0 && reached == n:
return "delivered", "every member is delivered"
case g.Closed && n > 0 && ended == n:
return "stopped", fmt.Sprintf("a member was superseded and no later delivered merge is known to carry it: %s",
strings.Join(uncovered, ", "))
case g.Closed:
return "delivering", fmt.Sprintf("%d of %d delivered", count[Delivered]+count[Covered], n)
return "delivering", fmt.Sprintf("%d of %d delivered", reached, n)
case len(g.Cycle) > 0:
return "rejected", cycleSaid(g)
case count[Rejected] > 0:
@@ -0,0 +1,140 @@
package main
import (
"strings"
"testing"
"time"
)
// novox/hq issue 442: five closed groups read "delivering, 1 of 2 delivered" for days, each beside a member
// whose walk a later walk of the same trunk superseded. Nothing in them was still in progress. A superseded
// member counts as reached when a later delivered walk on the same trunk carried every module it moves (ADR
// 0270's covered test); a closed group whose members all ended reads delivered when every one reached the
// machines, and stopped otherwise — never delivering.
//
// The shapes are the live groups' as mesh-delivery showed them on 2026-10-11: feat/delivery-checks-verb (the
// superseded member last in the order) and fix/assign-says-why-a-module-is-not-there (the superseded member
// first).
func TestAGroupWhoseMembersEndedEnds(t *testing.T) {
const cat, ctl, lab = "novox/mesh-catalog", "novox/mesh-controller", "novox/mesh-lab"
at := time.Date(2026, 10, 7, 19, 4, 10, 0, time.UTC)
// superseded is a member merged and walked, its walk superseded by a later walk of the same trunk.
superseded := func(repo, sha, merged, group string, modules ...string) *Delivery {
return &Delivery{ID: IDOf(repo, sha), Repository: repo, Commit: sha, Number: 109, Base: "main",
Branch: group, Group: group, State: Superseded, Since: at.Add(time.Minute), Created: at.Add(-time.Hour),
MergedAs: merged, MergedAt: at, Plan: planMoving(repo, modules...), Check: &Verdict{Gate: "pass"},
Walk: &WalkSeen{ID: "plan-" + merged[:6], Commit: merged, State: walkSuperseded,
Note: "superseded at tier 0 by plan-later (" + repo + " at 68aeee2c); planned there again"},
Transitions: []Transition{{At: at.Add(time.Minute), From: Delivering, To: Superseded, Event: EvSuperseded,
Why: "a newer delivery to the same trunk took over its walk"}}}
}
// delivered is the other member, delivered by its own walk.
delivered := func(repo, sha, merged, group string) *Delivery {
return &Delivery{ID: IDOf(repo, sha), Repository: repo, Commit: sha, Number: 117, Base: "main", Branch: group,
Group: group, State: Delivered, Since: at.Add(-10 * time.Minute), Created: at.Add(-2 * time.Hour),
MergedAs: merged, MergedAt: at.Add(-time.Hour), Plan: planMoving(repo, "mesh-controller"),
Check: &Verdict{Gate: "pass"}, Walk: &WalkSeen{ID: "plan-" + merged[:6], Commit: merged, State: walkDone}}
}
// later is the later merge of the same repository whose walk took the member's over and finished, sending
// each module given to every machine running it.
later := func(repo, merged string, modules ...string) *Delivery {
d := &Delivery{ID: IDOf(repo, "c0c0c0c0c0c0c0c0"), Repository: repo, Commit: "c0c0c0c0c0c0c0c0", Number: 110,
Base: "main", State: Delivered, Since: at.Add(30 * time.Minute), Created: at.Add(-time.Hour),
MergedAs: merged, MergedAt: at.Add(time.Minute), Plan: planMoving(repo, modules...), Check: &Verdict{Gate: "pass"},
Walk: &WalkSeen{ID: "plan-later", Commit: merged, State: walkDone}}
for _, m := range modules {
d.Steps = append(d.Steps, Step{Machine: "novox", Module: m, State: StepPassed},
Step{Machine: "the rest running it", Module: m, State: StepSent})
}
return d
}
group := func(id string, order ...string) *Group {
return &Group{ID: id, Members: append([]string(nil), order...), Closed: true, Order: order, Said: "delivering",
OrderOf: map[string]string{}}
}
const checks, assign = "feat/delivery-checks-verb", "fix/assign-says-why-a-module-is-not-there"
catID, ctlID := IDOf(cat, "13894744a701fe59"), IDOf(ctl, "1fdff00794e95470")
asCtlID, asLabID := IDOf(ctl, "8adb7f1a051941e9"), IDOf(lab, "33e63ee5b47a7552")
for _, c := range []struct {
name string
kept []*Delivery
group *Group
contains bool // what the forge says of the later merge containing the superseded member's
want string
whySays string
}{
{"superseded last, a later delivered walk carried every module it moves",
[]*Delivery{superseded(cat, "13894744a701fe59", "9d4bbbd2bd271049", checks, "gitea", "mesh-delivery"),
delivered(ctl, "1fdff00794e95470", "85d664438f87f885", checks),
later(cat, "68aeee2c00000000", "gitea", "mesh-delivery")},
group(checks, ctlID, catID), true, "delivered", "every member"},
{"superseded last, the later walk carried one of its two modules",
[]*Delivery{superseded(cat, "13894744a701fe59", "9d4bbbd2bd271049", checks, "gitea", "mesh-delivery"),
delivered(ctl, "1fdff00794e95470", "85d664438f87f885", checks),
later(cat, "68aeee2c00000000", "mesh-delivery")},
group(checks, ctlID, catID), true, "stopped", "superseded"},
{"superseded last, the forge says the later merge does not contain it",
[]*Delivery{superseded(cat, "13894744a701fe59", "9d4bbbd2bd271049", checks, "gitea", "mesh-delivery"),
delivered(ctl, "1fdff00794e95470", "85d664438f87f885", checks),
later(cat, "68aeee2c00000000", "gitea", "mesh-delivery")},
group(checks, ctlID, catID), false, "stopped", "superseded"},
{"superseded first, a later delivered walk carried every module it moves",
[]*Delivery{superseded(ctl, "8adb7f1a051941e9", "a5f53ef9ebdf3650", assign, "build-agent", "mesh-controller", "route-proxy"),
delivered(lab, "33e63ee5b47a7552", "77777777aaaaaaaa", assign),
later(ctl, "3f68a49500000000", "build-agent", "mesh-controller", "route-proxy")},
group(assign, asCtlID, asLabID), true, "delivered", "every member"},
{"superseded first, nothing later delivered",
[]*Delivery{superseded(ctl, "8adb7f1a051941e9", "a5f53ef9ebdf3650", assign, "build-agent", "mesh-controller", "route-proxy"),
delivered(lab, "33e63ee5b47a7552", "77777777aaaaaaaa", assign)},
group(assign, asCtlID, asLabID), true, "stopped", "superseded"},
{"still in progress: a member delivering beside a superseded one",
func() []*Delivery {
d := delivered(ctl, "1fdff00794e95470", "85d664438f87f885", checks)
d.State, d.Walk.State = Delivering, walkRolling
return []*Delivery{superseded(cat, "13894744a701fe59", "9d4bbbd2bd271049", checks, "gitea", "mesh-delivery"), d}
}(),
group(checks, ctlID, catID), true, "delivering", "0 of 2"},
} {
t.Run(c.name, func(t *testing.T) {
store := newMemStore()
for _, d := range c.kept {
if err := store.PutDelivery(d); err != nil {
t.Fatal(err)
}
}
if err := store.PutGroup(c.group); err != nil {
t.Fatal(err)
}
forge := newFakeForge()
forge.contains[c.kept[0].MergedAs+" in "+"68aeee2c00000000"] = c.contains
forge.contains[c.kept[0].MergedAs+" in "+"3f68a49500000000"] = c.contains
w := newWorldOver(t, store, newFakeController(), forge, at.Add(4*24*time.Hour))
if err := w.h.Reconcile(); err != nil {
t.Fatal(err)
}
w.settleAll()
g := w.h.groups[c.group.ID]
state, why := GroupState(g, w.h.membersOf(g))
if state != c.want || !strings.Contains(why, c.whySays) {
t.Fatalf("the group reads %s (%s), want %s saying %q", state, why, c.want, c.whySays)
}
// Each member keeps its own end: the group's label changes, no delivery is moved by hand.
for _, d := range c.kept {
if got := w.state(d.ID); got != d.State {
t.Fatalf("%s moved from %s to %s", d.ID, d.State, got)
}
}
// What the group said reached every member's head, once it ended.
if c.want != "delivering" {
w.forge.mu.Lock()
s := w.forge.statuses[c.kept[0].Repository+"@"+c.kept[0].Commit+" mesh/delivery-group"]
w.forge.mu.Unlock()
if !strings.Contains(s, c.want) {
t.Fatalf("mesh/delivery-group on the superseded member's head says %q", s)
}
}
})
}
}