Merge pull request 'mesh-delivery: cover a held merge whose module a backlog walk carried to every machine (hq issue 351)' (#163) from fix/a-release-carried-module-counts-as-sent into main

This commit was merged in pull request #163.
This commit is contained in:
2026-10-09 15:03:36 +00:00
3 changed files with 301 additions and 4 deletions
@@ -474,3 +474,197 @@ func TestAMemberHeldForItsComposedCheckIsLiftedWhenItPasses(t *testing.T) {
})
}
}
// **A module a release carried to every machine the held delivery sends it to covers it** (novox/hq issue
// 351). A build the controller held back for a person and released on their word reaches each machine by a
// backlog walk, which credits the delivery that published the build one step per machine — never the walk's
// own "every machine running it" or "the rest running it". The held delivery's plan says which machines
// receive the module: a passed step on each of them is the module sent everywhere. A machine still judging,
// one that failed, or a plan that names no machines leaves it held, as before.
func TestAModuleAReleaseCarriedToEveryMachineCovers(t *testing.T) {
const cat = "novox/mesh-catalog"
merged1, merged2 := t0, t0.Add(2*time.Hour)
laptop, desktop := "laptop", "desktop"
plan := func(machines ...string) *DeliveryPlan {
p := planMoving(cat, "openrazer")
for _, m := range machines {
p.Machines = append(p.Machines, MachinePlan{Machine: m, Receives: []string{"openrazer"}})
}
return p
}
held := func(p *DeliveryPlan) *Delivery {
why := "its group feat/j's composed check did not pass for the heads that merged; a person decides that it goes on"
return &Delivery{ID: IDOf(cat, head), Repository: cat, Commit: head, Number: 7, Base: "main", State: Held,
Since: merged1.Add(10 * time.Minute), Created: merged1.Add(-time.Hour), MergedAs: merge, MergedAt: merged1,
HeldWhy: why, Plan: p, Walk: &WalkSeen{ID: "plan-7", Commit: merge, State: walkSuperseded, Waits: true, WaitedFor: "mesh-delivery"},
Transitions: []Transition{{At: merged1, From: Published, To: Held, Event: EvHold, Why: why}}}
}
step := func(machine string, state StepState) Step {
return Step{Machine: machine, Module: "openrazer", Build: "build-1", State: state, Why: "carried by release-1"}
}
coverer := func(steps ...Step) *Delivery {
return &Delivery{ID: IDOf(cat, "c0c0c0c0c0c0c0c0"), Repository: cat, Commit: "c0c0c0c0c0c0c0c0", Number: 90,
Base: "main", State: Delivered, Since: merged2.Add(time.Hour), Created: merged2.Add(-time.Hour),
MergedAs: "f0f0f0f0f0f0f0f0", MergedAt: merged2, Plan: planMoving(cat, "power"), Check: &Verdict{Gate: "pass"},
Walk: &WalkSeen{ID: "plan-90", Commit: "f0f0f0f0f0f0f0f0", State: walkDone},
Steps: append([]Step{{Machine: "anchor", Module: "power", State: StepPassed},
{Machine: "the rest running it", Module: "power", State: StepSent}}, steps...)}
}
for _, c := range []struct {
name string
held *Delivery
by *Delivery
want State
}{
{"passed on every machine its plan sends it to", held(plan(laptop, desktop)),
coverer(step(laptop, StepPassed), step(desktop, StepPassed)), Covered},
{"still judging on one of them", held(plan(laptop, desktop)),
coverer(step(laptop, StepPassed), step(desktop, StepJudging)), Held},
{"failed on one of them", held(plan(laptop, desktop)),
coverer(step(laptop, StepPassed), step(desktop, StepFailed)), Held},
{"never sent to one of them", held(plan(laptop, desktop)), coverer(step(laptop, StepPassed)), Held},
{"its plan names no machines", held(plan()), coverer(step(laptop, StepPassed), step(desktop, StepPassed)), Held},
} {
t.Run(c.name, func(t *testing.T) {
store := newMemStore()
for _, d := range []*Delivery{c.held, c.by} {
if err := store.PutDelivery(d); err != nil {
t.Fatal(err)
}
}
forge := newFakeForge()
forge.contains[merge+" in f0f0f0f0f0f0f0f0"] = true
w := newWorldOver(t, store, newFakeController(), forge, merged2.Add(30*time.Hour))
if err := w.h.Reconcile(); err != nil {
t.Fatal(err)
}
w.settleAll()
d := w.delivery(c.held.ID)
if d.State != c.want {
t.Fatalf("it is %s (%s), want %s", d.State, d.HeldWhy, c.want)
}
if c.want == Covered {
if s := statusOn(w, cat, head); !strings.HasPrefix(s, "success covered: a later merge that contains it was delivered") {
t.Fatalf("its pull request's mesh/delivery says %q", s)
}
if last := d.Transitions[len(d.Transitions)-1]; !strings.Contains(last.Why, "openrazer") {
t.Fatalf("its transition does not name the module: %q", last.Why)
}
}
})
}
}
// **A finished release moves the steps it carried to passed** (novox/hq issue 351): a backlog walk's gate
// credits a step per machine while it judges, and its last record keeps no gate — only which machines it is
// done on. A step credited to it, on a machine it is done on, is passed; a step it failed on, one on a machine
// it is not done on, and a step another walk carried are left as they are.
func TestAFinishedReleaseMovesTheStepsItCarriedToPassed(t *testing.T) {
const cat = "novox/mesh-catalog"
carried := func(machine string, state StepState, by string) Step {
return Step{Machine: machine, Module: "openrazer", Build: "build-1", State: state, Why: "carried by " + by}
}
for _, c := range []struct {
name string
steps []Step
done []string
want map[string]StepState
}{
{"done on both", []Step{carried("laptop", StepJudging, "release-1"), carried("desktop", StepJudging, "release-1")},
[]string{"laptop", "desktop"}, map[string]StepState{"laptop": StepPassed, "desktop": StepPassed}},
{"done on one, the other still to come", []Step{carried("laptop", StepJudging, "release-1"), carried("desktop", StepJudging, "release-1")},
[]string{"laptop"}, map[string]StepState{"laptop": StepPassed, "desktop": StepJudging}},
{"a step it failed on stays failed", []Step{carried("laptop", StepFailed, "release-1")},
[]string{"laptop"}, map[string]StepState{"laptop": StepFailed}},
{"a step another walk carried is not its", []Step{carried("laptop", StepJudging, "release-2")},
[]string{"laptop"}, map[string]StepState{"laptop": StepJudging}},
} {
t.Run(c.name, func(t *testing.T) {
d := &Delivery{ID: IDOf(cat, "c0c0c0c0c0c0c0c0"), Repository: cat, Commit: "c0c0c0c0c0c0c0c0", Number: 90,
Base: "main", State: Delivered, Since: t0.Add(time.Hour), Created: t0, MergedAs: "f0f0f0f0f0f0f0f0", MergedAt: t0,
Plan: planMoving(cat, "power"), Check: &Verdict{Gate: "pass"},
Walk: &WalkSeen{ID: "plan-90", Commit: "f0f0f0f0f0f0f0f0", State: walkDone}, Steps: c.steps}
store := newMemStore()
if err := store.PutDelivery(d); err != nil {
t.Fatal(err)
}
w := newWorldOver(t, store, newFakeController(), newFakeForge(), t0.Add(2*time.Hour))
w.h.WalkMoved(Walk{ID: "release-1", Repository: "the builds waiting for a gate", State: walkDone,
Tiers: [][]string{{"openrazer"}}, Modules: map[string]*WalkModule{},
Release: &WalkRelease{Order: []string{"laptop", "desktop"}, Next: len(c.done), Done: c.done}})
got := map[string]StepState{}
for _, s := range w.delivery(d.ID).Steps {
got[s.Machine] = s.State
}
for machine, want := range c.want {
if got[machine] != want {
t.Fatalf("openrazer on %s is %s, want %s (steps %v)", machine, got[machine], want, got)
}
}
})
}
}
// The day's case whole (novox/hq issue 351): a member held for its group's check, its own walk taken over by
// later merges; the one later walk that carried its module failed its gate; a later merge's walk was delivered
// without the module; the controller then held the module's build back for a person, who released it, and the
// release reached both machines. The held delivery is covered by that later merge once the release is done —
// read by its id, since a walk that ended before the holder's restart may be older than the ended walks the
// controller lists back.
func TestAHeldDeliveryWhoseModuleAReleaseCarriedIsCoveredOnceTheReleaseIsDone(t *testing.T) {
const cat = "novox/mesh-catalog"
merged1, merged2 := t0, t0.Add(2*time.Hour)
plan := planMoving(cat, "openrazer")
plan.Machines = []MachinePlan{{Machine: "laptop", Receives: []string{"openrazer"}}, {Machine: "desktop", Receives: []string{"openrazer"}}}
why := "its group feat/j's composed check did not pass for the heads that merged; a person decides that it goes on"
held := &Delivery{ID: IDOf(cat, head), Repository: cat, Commit: head, Number: 7, Base: "main", State: Held,
Since: merged1.Add(10 * time.Minute), Created: merged1.Add(-time.Hour), MergedAs: merge, MergedAt: merged1,
HeldWhy: why, Plan: plan, Walk: &WalkSeen{ID: "plan-7", Commit: merge, State: walkSuperseded, Waits: true, WaitedFor: "mesh-delivery"},
Transitions: []Transition{{At: merged1, From: Published, To: Held, Event: EvHold, Why: why}}}
later := &Delivery{ID: IDOf(cat, "c0c0c0c0c0c0c0c0"), Repository: cat, Commit: "c0c0c0c0c0c0c0c0", Number: 90,
Base: "main", State: Delivered, Since: merged2.Add(time.Hour), Created: merged2.Add(-time.Hour),
MergedAs: "f0f0f0f0f0f0f0f0", MergedAt: merged2, Plan: planMoving(cat, "power"), Check: &Verdict{Gate: "pass"},
Walk: &WalkSeen{ID: "plan-90", Commit: "f0f0f0f0f0f0f0f0", State: walkDone},
Steps: []Step{{Machine: "anchor", Module: "power", State: StepPassed}, {Machine: "the rest running it", Module: "power", State: StepSent}}}
store := newMemStore()
for _, d := range []*Delivery{held, later} {
if err := store.PutDelivery(d); err != nil {
t.Fatal(err)
}
}
forge := newFakeForge()
forge.contains[merge+" in f0f0f0f0f0f0f0f0"] = true
w := newWorldOver(t, store, newFakeController(), forge, merged2.Add(3*time.Hour))
if err := w.h.Reconcile(); err != nil {
t.Fatal(err)
}
w.settleAll()
if s := w.state(held.ID); s != Held {
t.Fatalf("before any release it is %s", s)
}
// The release judges the build on each machine in turn: its gate names what it carried.
release := Walk{ID: "release-1", Repository: "the builds waiting for a gate", State: walkRolling,
Tiers: [][]string{{"openrazer"}}, Modules: map[string]*WalkModule{},
Release: &WalkRelease{Order: []string{"laptop", "desktop"}, Gate: &WalkGate{Machines: []string{"laptop"},
Carried: []WalkCarry{{Module: "openrazer", Node: "laptop", To: "f0f0f0f0f0f0f0f0", Build: "build-1"},
{Module: "openrazer", Node: "desktop", To: "f0f0f0f0f0f0f0f0", Build: "build-1"}}}}}
w.h.WalkMoved(release)
w.settleAll()
if s := w.state(held.ID); s != Held {
t.Fatalf("while the release judges it is %s", s)
}
// Done on both: its last record keeps no gate. It ended while this holder was down, and is older than the
// ended walks the controller lists back: the holder asks for it by its id, since a step it carried still judges.
release.State, release.Release.Gate, release.Release.Done, release.Release.Next = walkDone, nil, []string{"laptop", "desktop"}, 2
w.ctl.put(release)
w.later(time.Hour) // a walk a step still judges is asked for once an hour; at a start, at once
w.settleAll()
d := w.delivery(held.ID)
if d.State != Covered {
t.Fatalf("once the release is done it is %s (%s), want covered", d.State, d.HeldWhy)
}
if last := d.Transitions[len(d.Transitions)-1]; !strings.Contains(last.Why, IDOf(cat, "c0c0c0c0c0c0c0c0")) ||
!strings.Contains(last.Why, "openrazer") {
t.Fatalf("its transition does not name what covered it: %q", last.Why)
}
}
@@ -40,7 +40,9 @@ type Holder struct {
unreached string
// askTried is when a check this owner asks was last tried and could not be asked: tried again a minute on.
askTried map[string]time.Time
refusals []string
// walkAsked is when a walk that carried a step still judging was last asked for by its id: once an hour.
walkAsked map[string]time.Time
refusals []string
// unchecked are the open pull requests' heads seen without a merge check (unchecked.go).
unchecked map[string]unchecked
}
@@ -82,6 +84,7 @@ func (h *Holder) init() {
h.dirtyGroup = map[string]bool{}
h.unmatched = map[string]unmatched{}
h.askTried = map[string]time.Time{}
h.walkAsked = map[string]time.Time{}
h.unchecked = map[string]unchecked{}
h.started = h.Now()
}
@@ -646,6 +649,58 @@ func (h *Holder) creditBacklog(w Walk) {
if w.Release.Gate != nil {
h.creditCarried(w.Release.Gate, w.ID, "")
}
if w.State == walkDone {
h.creditReleased(w)
}
}
// creditReleased passes the steps a finished backlog walk carried: its last record keeps no gate, only the
// machines it is done on, and a release ends done only when its gate passed on each of them (novox/hq issue
// 351). A step it failed on, and a step another walk carried, are left as they are.
func (h *Holder) creditReleased(w Walk) {
why := carriedBy + w.ID
for _, d := range h.deliveries {
moved := false
for _, s := range d.Steps {
if s.Why != why || !containsString(w.Release.Done, s.Machine) || s.State != StepSent && s.State != StepJudging {
continue
}
s.State, s.Since = StepPassed, h.Now()
if h.step(d, s) {
moved = true
}
}
if moved {
h.keep(d)
}
}
}
// carriedBy begins the why of a step another walk's send carried, followed by that walk's id.
const carriedBy = "carried by "
// askWalk is the ask of one walk's record by its id, once an hour while a step it carried still judges;
// what it answers is taken as said.
func (h *Holder) askWalk(id string, now time.Time) func() {
if asked, was := h.walkAsked[id]; was && now.Sub(asked) < time.Hour {
return nil
}
h.walkAsked[id] = now
return func() {
_, walks, err := h.Controller.Walks(id)
h.mu.Lock()
defer h.mu.Unlock()
if err != nil {
h.Logf("[mesh-delivery] the walk %s, which carried a step still judging, cannot be read yet: %v", id, err)
h.walkAsked[id] = h.Now().Add(time.Minute - time.Hour) // asked again a minute on
return
}
for _, w := range walks {
if w.ID == id {
h.walkMoved(w)
}
}
}
}
// creditCarried gives the delivery that published each carried build its machine step.
@@ -665,7 +720,7 @@ func (h *Holder) creditCarried(g *WalkGate, walk, except string) {
continue
}
if h.step(d, Step{Machine: c.Node, Module: c.Module, Build: c.Build, State: state, Since: h.Now(),
Why: "carried by " + walk}) {
Why: carriedBy + walk}) {
h.keep(d)
}
}
@@ -781,6 +836,20 @@ func (h *Holder) plan() []func() {
}
}
// A step a walk carried and left judging: that walk's record is asked for by its id. A walk that ended
// while this holder was down may be older than the ended walks the controller lists back at its start,
// and its end is what passes the step (novox/hq issue 351).
for _, d := range h.deliveries {
for _, s := range d.Steps {
if s.State != StepSent && s.State != StepJudging || !strings.HasPrefix(s.Why, carriedBy) {
continue
}
if a := h.askWalk(strings.TrimPrefix(s.Why, carriedBy), now); a != nil {
asks = append(asks, a)
}
}
}
// Merged, and no walk opened for it after the walks were read again: never silent — held, saying so.
for _, d := range h.deliveries {
if h.unwalkedPast(d, now) {
@@ -884,7 +953,11 @@ func (h *Holder) coverOf(d *Delivery) (string, []string) {
// carries is whether c delivered a module after d: of the same repository, merged later into the same trunk,
// delivered on its own passing check — a merge held without one is covered only by a later merge that passed
// its own — by its own walk, of its merge commit on that trunk, which sent the module to every machine running
// it. Whether c's merge commit contains d's is the forge's to say (coverOf).
// it. Its machine steps say so in one of two ways: the walk's own record, which sends to every machine at once
// or to the rest after its first machines passed; or, for a build the controller held back and a person
// released, one step per machine credited by the backlog walk that carried it — passed on every machine d's
// plan sends the module to (novox/hq issue 351). Whether c's merge commit contains d's is the forge's to say
// (coverOf).
func carries(c, d *Delivery, module string) bool {
if c.ID == d.ID || c.State != Delivered || !c.Check.Passes() || !strings.EqualFold(c.Repository, d.Repository) ||
!strings.EqualFold(c.Base, d.Base) || c.MergedAs == "" || sameCommit(c.MergedAs, d.MergedAs) ||
@@ -893,6 +966,7 @@ func carries(c, d *Delivery, module string) bool {
return false
}
everywhere := false
passed := map[string]bool{}
for _, s := range c.Steps {
if s.Module != module {
continue
@@ -902,9 +976,23 @@ func carries(c, d *Delivery, module string) bool {
return false
case s.State == StepSent && (s.Machine == stepEveryMachine || s.Machine == stepTheRest):
everywhere = true
case s.State == StepPassed:
passed[s.Machine] = true
}
}
return everywhere
if everywhere {
return true
}
machines := d.Plan.receiving(module)
if len(machines) == 0 {
return false
}
for _, m := range machines {
if !passed[m] {
return false
}
}
return true
}
// askContains is the ask of the forge whether a later merge commit contains a delivery's merge: at most once a
@@ -144,6 +144,21 @@ type DeliveryPlan struct {
Summary string `json:"summary"`
}
// receiving are the machines the plan sends a module to, in the plan's order; none when the plan names no
// machines, as a plan kept before they were said may not.
func (p *DeliveryPlan) receiving(module string) []string {
if p == nil {
return nil
}
var out []string
for _, m := range p.Machines {
if containsString(m.Receives, module) {
out = append(out, m.Machine)
}
}
return out
}
// MachinePlan is one machine's part of a plan.
type MachinePlan struct {
Machine string `json:"machine"`