Merge pull request 'mesh-delivery: a walk answers every merge it contains, never an ended delivery (hq issue 362, ADR 0276)' (#171) from fix/362-a-walk-answers-every-merge-it-contains into main

This commit was merged in pull request #171.
This commit is contained in:
2026-10-10 11:14:07 +00:00
11 changed files with 1370 additions and 103 deletions
+2
View File
@@ -7,7 +7,9 @@ modules/jetbrains-toolbox/cmd/toolbox-tools/toolbox-tools
modules/messenger/cmd/messenger/messenger
modules/mesh-watcher/cmd/mesh-watcher/mesh-watcher
modules/claude-code/cmd/claude-code/claude-code
modules/mesh-delivery/cmd/mesh-delivery/mesh-delivery
# ...and the same built from the module's root (go build ./cmd/<name>), which names it after the module.
modules/messenger/messenger
modules/claude-code/claude-code
modules/mesh-watcher/mesh-watcher
modules/mesh-delivery/mesh-delivery
@@ -0,0 +1,414 @@
package main
import (
"fmt"
"sort"
"strings"
"time"
)
// Which merges a walk answers (novox/hq issue 362, ADR 0276).
//
// On 2026-10-10 two merges of the catalogue 18 seconds apart were walked by one walk at the later merge's
// commit, which superseded the first. This owner found the walk a delivery follows by the walk's id or by the
// commit it was merged as, final or not: the surviving walk matched the dunst delivery, which the supersession
// had already ended, so nothing gave it its word, and the claude-code delivery, merged as another commit,
// matched no walk and was held for a person after ten minutes, for a reason that named neither.
//
// A walk answers every merge it carries: the merges it names (`delivery.merges`, said by a controller that
// batches merges), the merge of its own commit, a merge whose walk it took over, and a merge of its repository
// and branch the forge says its commit contains. A delivery follows the newest walk that answers it, and only a
// delivery not yet ended is ever tied to a walk by what it answers: an ended delivery is moved only by the
// record of its own walk, which no longer moves it.
// seenWalk is a walk's last record and when it was heard.
type seenWalk struct {
walk Walk
at time.Time
}
// keptWalkFor is how long a walk no longer open is kept for a merge heard after it ended; a batch is kept as
// long, past its window's latest close.
const keptWalkFor = 10 * time.Minute
// answer is a delivery a walk answers and the commit of its repository the walk carries for it.
type answer struct {
d *Delivery
carried string
}
// walkAnswers is whether a walk answers a delivery's merge, and the commit it carries for it.
func walkAnswers(w Walk, d *Delivery) (string, bool) {
if w.Release != nil || w.IsBatch() || d.MergedAs == "" {
return "", false
}
if c, ok := w.carriedFor(d.Repository, d.MergedAs); ok {
return c, true
}
if d.Walk != nil && d.Walk.TakenOverBy == w.ID && namesNoMerges(w) && strings.EqualFold(w.Repository, d.Repository) &&
sameBranch(w.Branch, d.Base) {
// The walk that took its walk over, from a controller that names no merges: it answers every merge the one
// it took over answered, by its commit of the same repository and branch. A walk that names its merges
// answers only those (carriedFor, above).
return w.Commit, true
}
return "", false
}
// containedAnswer is, for a controller that does not name the merges a walk answers, whether an open walk
// answers a ready merged delivery by containment: of its repository and branch, made for a merge after it, the
// forge says its commit contains this merge, and it moves every module this merge's plan moves. A later walk that
// moves only what its own merge moved does not deliver this one, however much of the trunk it contains.
func (h *Holder) containedAnswer(w Walk, d *Delivery) (string, bool) {
if !h.mayContain(w, d) || !d.ContainedIn[w.Commit] {
return "", false
}
return w.Commit, true
}
// mayContain is whether an open walk is one to ask the forge about for a ready merged delivery (containedAnswer).
func (h *Holder) mayContain(w Walk, d *Delivery) bool {
if w.Release != nil || w.IsBatch() || !w.Open() || !namesNoMerges(w) || d.State != Ready || d.MergedAs == "" || d.Walk != nil ||
d.Plan == nil || len(d.Plan.Moved) == 0 || !strings.EqualFold(w.Repository, d.Repository) ||
!sameBranch(w.Branch, d.Base) || sameCommit(w.Commit, d.MergedAs) {
return false
}
for _, m := range d.Plan.Moved {
if _, moves := w.Modules[m]; !moves {
return false
}
}
// Made for a merge after this one: the walk's own merge, where this owner holds it, or else the walk itself.
after := w.Created
for _, o := range h.deliveries {
if strings.EqualFold(o.Repository, w.Repository) && sameCommit(o.MergedAs, w.Commit) && !o.MergedAt.IsZero() {
after = o.MergedAt
}
}
return after.After(d.MergedAt)
}
// namesNoMerges is whether a walk is from a controller that does not name the merges it answers.
func namesNoMerges(w Walk) bool { return w.Delivery == nil || len(w.Delivery.Merges) == 0 }
func sameBranch(a, b string) bool { return a == "" || b == "" || strings.EqualFold(a, b) }
// follows is whether a delivery takes a walk that answers it as the one it follows: it has none, it is that walk,
// that walk took its own over, or that walk is newer than the one it follows. A walk record kept before its
// creation was read is followed past only once it is superseded or failed.
func follows(d *Delivery, w Walk) bool {
switch {
case d.Walk == nil || d.Walk.ID == w.ID || d.Walk.TakenOverBy == w.ID:
return true
case d.Walk.Created.IsZero():
return d.Walk.State == walkSuperseded || d.Walk.State == walkFailed
}
return w.Created.After(d.Walk.Created)
}
// deliveriesWalking are the deliveries a walk record moves: every delivery that already follows it — ended or
// not, its own record — and every delivery not ended that it answers and that follows it now. In id order.
func (h *Holder) deliveriesWalking(w Walk) []answer {
var out []answer
for _, d := range h.deliveries {
if d.Walk != nil && d.Walk.ID == w.ID {
carried := d.AnsweredBy
if c, ok := walkAnswers(w, d); ok {
carried = c
}
out = append(out, answer{d: d, carried: carried})
continue
}
if d.State.Final() || !follows(d, w) {
continue
}
if c, ok := walkAnswers(w, d); ok {
out = append(out, answer{d: d, carried: c})
}
}
sort.Slice(out, func(i, j int) bool { return out[i].d.ID < out[j].d.ID })
return out
}
// accounted is whether any delivery, ended or not, follows the walk or is answered by it: a walk known for
// what it answers is never made a delivery of its own.
func (h *Holder) accounted(w Walk) bool {
for _, d := range h.deliveries {
if d.Walk != nil && d.Walk.ID == w.ID {
return true
}
if _, ok := walkAnswers(w, d); ok {
return true
}
}
return false
}
// held is whether a live delivery follows the walk, or a live delivery it answers follows a newer one.
func (h *Holder) heldWalk(w Walk) bool {
for _, d := range h.deliveries {
if d.State.Final() {
continue
}
if d.Walk != nil && d.Walk.ID == w.ID {
return true
}
if _, ok := walkAnswers(w, d); ok {
return true
}
}
return false
}
// pickWalk is the newest walk heard that answers a merged delivery with no walk yet; nil for none.
func (h *Holder) pickWalk(d *Delivery) (Walk, string, bool) {
var best Walk
var carried string
found := false
for _, s := range h.walks {
if !follows(d, s.walk) {
continue
}
c, ok := walkAnswers(s.walk, d)
if !ok {
c, ok = h.containedAnswer(s.walk, d)
}
if !ok {
continue
}
if !found || s.walk.Created.After(best.Created) || s.walk.Created.Equal(best.Created) && s.walk.ID > best.ID {
best, carried, found = s.walk, c, true
}
}
return best, carried, found
}
// linkAnswering ties a merged delivery with no walk to the newest walk heard that answers it; under the lock.
func (h *Holder) linkAnswering(d *Delivery) bool {
if d.State.Final() || d.MergedAs == "" || d.Walk != nil {
return false
}
w, carried, ok := h.pickWalk(d)
if !ok {
return false
}
h.linkWalk(d, w, carried)
delete(h.unmatched, w.ID)
return true
}
// containsAsks are the forge's asks whether an open walk of a merged delivery's repository and branch, at
// another commit, contains its merge: for a controller that does not name the merges a walk answers (issue 362),
// asked once per pair, the answer kept on the delivery.
func (h *Holder) containsAsks(d *Delivery, now time.Time) []func() {
var asks []func()
for _, s := range h.walks {
w := s.walk
if !h.mayContain(w, d) {
continue
}
if _, known := d.ContainedIn[w.Commit]; known {
continue
}
if a := h.askContains(d, w.Commit, now); a != nil {
asks = append(asks, a)
}
}
return asks
}
// batchOf is the batch a merged delivery's merge is in, when the controller said one; false for none.
func (h *Holder) batchOf(d *Delivery) (Walk, bool) {
if d.MergedAs == "" {
return Walk{}, false
}
for _, s := range h.walks {
if !s.walk.IsBatch() || s.walk.Delivery == nil {
continue
}
for _, m := range s.walk.Delivery.Merges {
if strings.EqualFold(m.Repository, d.Repository) && sameCommit(m.Commit, d.MergedAs) {
return s.walk, true
}
}
}
return Walk{}, false
}
// batchSaid is what a batch holding a delivery's merge waits for, in words.
func batchSaid(b Walk, now time.Time) string {
said := "its batch " + b.ID + ": " + b.State
bt := b.Delivery.Batch
switch {
case b.State == walkQueued && bt != nil && bt.Behind != "":
said += " behind " + bt.Behind
case b.State == walkAssembling && bt != nil && bt.ClosesAt != nil:
left := bt.ClosesAt.Sub(now).Round(time.Second)
if left < 0 {
left = 0
}
said += fmt.Sprintf(", %s left", left)
if bt.AtMost != nil {
said += " (at the latest " + bt.AtMost.Local().Format("15:04:05") + ")"
}
}
return said + "; plan not yet calculated"
}
// unwalkedWhy is why a merged delivery no walk answers is held: the open walks of its repository and that none
// answers its merge, or that the controller holds none — what happened, not what might have.
func (h *Holder) unwalkedWhy(d *Delivery) string {
var open []string
for _, s := range h.walks {
w := s.walk
if w.Release == nil && !w.IsBatch() && w.Open() && strings.EqualFold(w.Repository, d.Repository) {
open = append(open, fmt.Sprintf("%s at %s", w.ID, shortOf(w.Commit, 8)))
}
}
sort.Strings(open)
head := fmt.Sprintf("merged as %s, and no walk answers its merge within %s: ", shortOf(d.MergedAs, 8), unwalked)
if len(open) == 0 {
return head + "the controller holds no open walk of " + d.Repository + " — its merge was not heard, or " +
"nothing the mesh holds follows " + orNone(d.Base) + ": a person decides"
}
return head + "the open walk(s) of " + d.Repository + " — " + strings.Join(open, ", ") + " — name no merge of " +
"it and the forge did not say they contain it: a person decides"
}
func orNone(s string) string {
if s == "" {
return "its branch"
}
return s
}
// orphanLines are the waiting walks no live delivery holds, past the grace a merge is given to be heard, as
// stalled lines in the state Unheld: nothing would ever give such a walk its word (issue 362). Under the lock.
func (h *Holder) orphanLines(now time.Time) []StalledLine {
var out []StalledLine
for id, first := range h.orphans {
s, known := h.walks[id]
if !known || now.Sub(first) < matchGrace {
continue
}
w := s.walk
out = append(out, StalledLine{ID: IDOf(w.Repository, w.Commit), State: Unheld, Walk: w.ID,
For: now.Sub(first).Round(time.Second).String(), Bound: matchGrace.String(),
H2: "none: no delivery holds the walk, so nothing gives it its word — the operator's",
Says: fmt.Sprintf("the walk %s of %s at %s waits for this owner's word, and no delivery that has not ended "+
"follows it or is answered by it: `mesh-delivery.show` for the deliveries of %s, and `plans go %s --why …` "+
"starts it by hand", w.ID, w.Repository, shortOf(w.Commit, 8), w.Repository, w.ID)})
}
return out
}
// Unheld is a waiting walk no live delivery holds: not a state of the table — only what a stalled line says.
const Unheld State = "unheld"
// seeOrphans keeps when each waiting walk no live delivery holds was first seen so, and forgets the rest; a
// walk still unmatched waits for its merge or its adoption instead. Under the lock.
func (h *Holder) seeOrphans(now time.Time) {
for id, s := range h.walks {
w := s.walk
_, unmatched := h.unmatched[id]
if unmatched || w.Release != nil || w.IsBatch() || !w.Open() || !w.Seen().Waits || h.heldWalk(w) {
delete(h.orphans, id)
continue
}
if _, was := h.orphans[id]; !was {
h.orphans[id] = now
h.Logf("[mesh-delivery] the walk %s waits for this owner's word and no live delivery holds it: said once "+
"it is %s so", id, matchGrace)
}
}
for id := range h.orphans {
if _, known := h.walks[id]; !known {
delete(h.orphans, id)
}
}
}
// forgetWalks drops a walk record no longer open after keptWalkFor, and a batch heard of that long past its
// window's latest close. Under the lock.
func (h *Holder) forgetWalks(now time.Time) {
for id, s := range h.walks {
w := s.walk
switch {
case w.IsBatch():
end := s.at
if w.Delivery != nil && w.Delivery.Batch != nil && w.Delivery.Batch.AtMost != nil && w.Delivery.Batch.AtMost.After(end) {
end = *w.Delivery.Batch.AtMost
}
if now.Sub(end) > 3*time.Hour {
delete(h.walks, id)
}
case !w.Open() && now.Sub(s.at) > keptWalkFor:
delete(h.walks, id)
}
}
}
// --- the word, once per walk ------------------------------------------------------------------------------
// turnWant is a walk whose turn came for some of the deliveries it carries, and why.
type turnWant struct {
ids []string
whys []string
}
// words are the asks of the word of every walk whose turn came for every live delivery it carries: one ask per
// walk, however many deliveries it carries, and none while one of them is not at its turn (ADR 0276 decision 5).
// Under the lock.
func (h *Holder) words() []func() {
walks := make([]string, 0, len(h.turning))
for walk := range h.turning {
walks = append(walks, walk)
}
sort.Strings(walks)
var asks []func()
for _, walk := range walks {
want := h.turning[walk]
var waiting []string
for _, d := range h.deliveries {
if d.State.Final() || d.Walk == nil || d.Walk.ID != walk || containsString(want.ids, d.ID) {
continue
}
waiting = append(waiting, fmt.Sprintf("%s (%s)", d.ID, d.State))
}
if len(waiting) > 0 {
sort.Strings(waiting)
h.Logf("[mesh-delivery] the walk %s is not let go yet: %s carried by it not at its turn", walk,
strings.Join(waiting, ", "))
continue
}
ids, why := append([]string(nil), want.ids...), strings.Join(want.whys, "; ")
if h.asking[walk] {
continue // its word is being asked
}
h.asking[walk] = true
walk := walk
asks = append(asks, func() {
err := h.Controller.Deliver(walk, why)
h.mu.Lock()
defer h.mu.Unlock()
delete(h.asking, walk)
if err != nil {
h.Logf("[mesh-delivery] the walk %s could not be let go yet: %v", walk, err)
return
}
for _, id := range ids {
d := h.deliveries[id]
if d == nil {
continue
}
d.LetGo, d.LetGoWhy = h.Now(), why
if d.Walk != nil && d.Walk.ID == walk {
d.Walk.Waits, d.Walk.GoBy = false, byOwner
}
h.settleAndKeep(d)
}
})
}
h.turning = nil
return asks
}
@@ -108,7 +108,11 @@ func statusOf(d *Delivery) (string, string) {
case Retired:
return "success", "history: merged before this owner delivered anything, never walked"
case Covered:
return "success", clip("covered: a later merge that contains it was delivered: "+d.CoveredBy, 140)
by := d.CoveredBy
if by == "" {
by = d.Carrier
}
return "success", clip("covered: a later merge that contains it was delivered: "+by, 140)
}
return "pending", string(d.State)
}
+181 -75
View File
@@ -45,6 +45,13 @@ type Holder struct {
refusals []string
// unchecked are the open pull requests' heads seen without a merge check (unchecked.go).
unchecked map[string]unchecked
// walks are the walks' and batches' last records (answers.go); orphans the waiting walks no live delivery
// holds, by when that was first seen; turning the walks whose turn came this tick, for some deliveries.
walks map[string]seenWalk
orphans map[string]time.Time
turning map[string]*turnWant
// asking are the walks whose word is being asked right now, outside the lock: not asked twice meanwhile.
asking map[string]bool
}
// unmatched is a walk no delivery is known for yet: its merge not heard, or merged before this owner held.
@@ -86,6 +93,9 @@ func (h *Holder) init() {
h.askTried = map[string]time.Time{}
h.walkAsked = map[string]time.Time{}
h.unchecked = map[string]unchecked{}
h.walks = map[string]seenWalk{}
h.orphans = map[string]time.Time{}
h.asking = map[string]bool{}
h.started = h.Now()
}
@@ -401,13 +411,8 @@ func (h *Holder) PullMerged(p PullEvent) {
} else {
d.MergedAt = now
}
// A walk already heard of for this commit.
for id, u := range h.unmatched {
if strings.EqualFold(u.walk.Repository, d.Repository) && sameCommit(u.walk.Commit, d.MergedAs) {
h.linkWalk(d, u.walk)
delete(h.unmatched, id)
}
}
// A walk already heard of that answers this merge: its own, or one that names it.
h.linkAnswering(d)
// A merge long past heard only now, from the forge's word — replayed from the bus's history when this owner
// first held the seat — is history: it reached the machines before this owner was there to deliver it, and
// holding it for a person asks a person about the past (novox/hq issue 313).
@@ -417,7 +422,7 @@ func (h *Holder) PullMerged(p PullEvent) {
}
ts = append(ts, settle(d, Facts{Now: now})...)
if d.State == Held && d.HeldWhy == "" {
d.HeldWhy = "merged without a passing check: only a person decides that it goes on"
d.HeldWhy, d.Unwalked = "merged without a passing check: only a person decides that it goes on", false
}
h.moved(d, ts)
h.closeGroupOf(d)
@@ -562,8 +567,17 @@ func (h *Holder) walkMoved(w Walk) {
h.creditBacklog(w)
return
}
d := h.deliveryWalking(w)
if d == nil {
if old, seen := h.walks[w.ID]; !seen || w.Revision >= old.walk.Revision {
h.walks[w.ID] = seenWalk{walk: w, at: h.Now()}
}
if w.IsBatch() {
return // a batch is shown, never linked, adopted or let go (ADR 0276)
}
links := h.deliveriesWalking(w)
if len(links) == 0 {
if h.accounted(w) {
return // it answers only deliveries that ended or follow a newer walk
}
if _, seen := h.unmatched[w.ID]; !seen {
h.unmatched[w.ID] = unmatched{walk: w, first: h.Now()}
} else {
@@ -575,43 +589,72 @@ func (h *Holder) walkMoved(w Walk) {
}
return
}
if d.Walk != nil && d.Walk.ID == w.ID && w.Revision < d.Walk.Revision {
return // an older record arriving late
delete(h.unmatched, w.ID)
moved := map[string]bool{}
for _, a := range links {
d := a.d
if d.Walk != nil && d.Walk.ID == w.ID && w.Revision < d.Walk.Revision {
continue // an older record arriving late
}
h.linkWalk(d, w, a.carried)
h.settleAndKeep(d)
moved[d.ID] = true
}
h.linkWalk(d, w)
h.settleAndKeep(d)
// What its sends carried of other deliveries' builds.
for _, m := range w.Modules {
if m != nil && m.Gate != nil {
h.creditCarried(m.Gate, w.ID, d.ID)
h.creditCarried(m.Gate, w.ID, moved)
}
}
}
// deliveryWalking is the delivery a walk walks: the one that landed as its commit, or that it walked before.
func (h *Holder) deliveryWalking(w Walk) *Delivery {
for _, d := range h.deliveries {
if d.Walk != nil && d.Walk.ID == w.ID {
return d
}
// linkWalk records a walk on its delivery: what it is at, the commit it carries for it, and the machine steps
// its record says — each moved only as the step table allows. A delivery that follows a new walk takes its
// steps as that walk's, a new journey; and one held because no walk answered it is lifted.
func (h *Holder) linkWalk(d *Delivery, w Walk, carried string) {
switching := d.Walk != nil && d.Walk.ID != w.ID
if switching {
h.Logf("[mesh-delivery] %s follows the walk %s now (it followed %s, %s)", d.ID, w.ID, d.Walk.ID, d.Walk.State)
d.LetGo, d.LetGoWhy = time.Time{}, "" // a new walk waits for a word of its own
}
for _, d := range h.deliveries {
if strings.EqualFold(d.Repository, w.Repository) && sameCommit(d.MergedAs, w.Commit) {
return d
}
}
return nil
}
// linkWalk records a walk on its delivery: what it is at, and the machine steps its record says — each
// moved only as the step table allows.
func (h *Holder) linkWalk(d *Delivery, w Walk) {
d.Walk = w.Seen()
if carried != "" {
d.AnsweredBy = carried
}
d.Carrier = ""
if d.carriedLater() {
d.Carrier = fmt.Sprintf("merged as %s, carried by the walk %s", shortOf(d.AnsweredBy, 12), w.ID)
for _, o := range h.deliveries {
if o.ID != d.ID && strings.EqualFold(o.Repository, d.Repository) && sameCommit(o.MergedAs, d.AnsweredBy) {
d.Carrier = fmt.Sprintf("%s (merged as %s into %s, walk %s)", o.ID, shortOf(d.AnsweredBy, 8), orNone(d.Base), w.ID)
}
}
}
if d.State == Held && d.Unwalked && w.Open() {
d.HeldWhy = ""
d.Lifted = fmt.Sprintf("the walk %s, which answers its merge, opened", w.ID)
}
d.Unwalked = false // a walk answers it
for _, s := range StepsOf(w) {
if switching {
h.restep(d, s)
continue
}
h.step(d, s)
}
}
// restep puts a step of a walk a delivery newly follows, whatever the walk before said of that machine.
func (h *Holder) restep(d *Delivery, s Step) {
for i, old := range d.Steps {
if old.Machine == s.Machine && old.Module == s.Module {
d.Steps[i] = s
return
}
}
d.Steps = append(d.Steps, s)
}
// step moves one machine's step, or refuses a move the step table does not hold; it says whether it moved.
func (h *Holder) step(d *Delivery, s Step) bool {
for i, old := range d.Steps {
@@ -647,7 +690,7 @@ func (h *Holder) creditBacklog(w Walk) {
}
}
if w.Release.Gate != nil {
h.creditCarried(w.Release.Gate, w.ID, "")
h.creditCarried(w.Release.Gate, w.ID, nil)
}
if w.State == walkDone {
h.creditReleased(w)
@@ -704,7 +747,7 @@ func (h *Holder) askWalk(id string, now time.Time) func() {
}
// creditCarried gives the delivery that published each carried build its machine step.
func (h *Holder) creditCarried(g *WalkGate, walk, except string) {
func (h *Holder) creditCarried(g *WalkGate, walk string, except map[string]bool) {
state := StepJudging
switch {
case g.Rollback == "rolled-back":
@@ -716,7 +759,7 @@ func (h *Holder) creditCarried(g *WalkGate, walk, except string) {
}
for _, c := range g.Carried {
for _, d := range h.deliveries {
if d.ID == except || d.MergedAs == "" || !sameCommit(d.MergedAs, c.To) {
if except[d.ID] || d.MergedAs == "" || !sameCommit(d.MergedAs, c.To) {
continue
}
if h.step(d, Step{Machine: c.Node, Module: c.Module, Build: c.Build, State: state, Since: h.Now(),
@@ -763,11 +806,19 @@ func (h *Holder) plan() []func() {
now := h.Now()
var asks []func()
h.forgetWalks(now)
// Walks no merge was heard for: made deliveries of their own once their merge had time to be heard.
for id, u := range h.unmatched {
if d := h.deliveryWalking(u.walk); d != nil {
h.linkWalk(d, u.walk)
h.settleAndKeep(d)
if links := h.deliveriesWalking(u.walk); len(links) > 0 {
for _, a := range links {
h.linkWalk(a.d, u.walk, a.carried)
h.settleAndKeep(a.d)
}
delete(h.unmatched, id)
continue
}
if h.accounted(u.walk) {
delete(h.unmatched, id)
continue
}
@@ -813,6 +864,39 @@ func (h *Holder) plan() []func() {
}
}
// Merged with no walk yet: tied to the newest walk heard that answers it — by name, by its own commit, or by
// the forge's word that the walk's commit contains it, asked here (novox/hq issue 362).
for _, d := range h.deliveries {
if h.linkAnswering(d) {
h.settleAndKeep(d)
continue
}
asks = append(asks, h.containsAsks(d, now)...)
}
// Following a walk that was taken over: the walk that took it over, read as soon as it is heard of, asked for
// by its id when it is not.
for _, d := range h.deliveries {
if d.State.Final() || d.Walk == nil || d.Walk.TakenOverBy == "" {
continue
}
if s, known := h.walks[d.Walk.TakenOverBy]; known {
if c, ok := walkAnswers(s.walk, d); ok {
h.linkWalk(d, s.walk, c)
} else {
// The walk that took its own over does not answer it: superseded, as before ADR 0276.
h.Logf("[mesh-delivery] %s: the walk %s, which took %s over, does not answer its merge", d.ID,
d.Walk.TakenOverBy, d.Walk.ID)
d.Walk.TakenOverBy = ""
}
h.settleAndKeep(d)
continue
}
if a := h.askWalk(d.Walk.TakenOverBy, now); a != nil {
asks = append(asks, a)
}
}
// Held, or merged with no walk opened for it: ended by what is already true of it, never asked of a person
// (novox/hq issue 319). A plan that moves nothing is delivered; one whose every module a later merge into the
// same trunk delivered is covered. Read again each tick, so a delivery read back after a restart, or held
@@ -853,13 +937,13 @@ func (h *Holder) plan() []func() {
// 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) {
d.HeldWhy = "merged, and the controller opened no walk for it within " + unwalked.String() + " — nothing it " +
"holds follows that branch, or the merge was not heard: a person decides"
d.HeldWhy, d.Unwalked = h.unwalkedWhy(d), true
h.settleAndKeep(d)
}
}
// Groups: membership, order, composed check, and their turns.
h.turning = map[string]*turnWant{}
grouped := map[string]bool{}
for _, g := range h.groups {
members := h.membersOf(g)
@@ -874,10 +958,13 @@ func (h *Holder) plan() []func() {
if grouped[d.ID] || d.Group != "" && h.groups[d.Group] != nil {
continue
}
if a := h.turn(d, "its turn: a delivery in no group"); a != nil {
asks = append(asks, a)
}
h.turn(d, "its turn: a delivery in no group")
}
// The word, once per walk, when every live delivery it carries is at its turn.
asks = append(asks, h.words()...)
// Waiting walks no live delivery holds: listed by `stalled` (issue 362).
h.seeOrphans(now)
// Final deliveries past their keeping, once nothing is owed for them.
for id, d := range h.deliveries {
@@ -893,6 +980,9 @@ func (h *Holder) plan() []func() {
// unwalkedPast is whether a merged delivery has gone without a walk past the bound one opens within, read
// again after it: what holds it for a person, unless something already ends it.
func (h *Holder) unwalkedPast(d *Delivery, now time.Time) bool {
if _, batched := h.batchOf(d); batched {
return false // its batch is assembling or queued: a walk is not owed yet (ADR 0276)
}
return d.State == Ready && d.MergedAs != "" && d.Walk == nil && !d.MovesNothing() && d.HeldWhy == "" &&
now.Sub(d.MergedAt) > unwalked && h.reconciled.After(d.MergedAt.Add(unwalked))
}
@@ -1075,11 +1165,18 @@ func (h *Holder) adopt(w Walk) {
d := &Delivery{ID: IDOf(w.Repository, w.Commit), Repository: strings.ToLower(w.Repository), Commit: w.Commit,
Base: w.Branch, MergedAs: w.Commit, Created: now}
if old := h.deliveries[d.ID]; old != nil {
h.linkWalk(old, w)
if old.State.Final() {
// Never revived: an ended delivery is not the walk's; the walk, held by nothing, is listed by
// `stalled` (issue 362).
h.Logf("[mesh-delivery] the walk %s is at %s, whose delivery has ended (%s): not adopted by it", w.ID,
shortOf(w.Commit, 12), old.State)
return
}
h.linkWalk(old, w, w.Commit)
h.settleAndKeep(old)
return
}
h.linkWalk(d, w)
h.linkWalk(d, w, w.Commit)
var t Transition
var err error
if d.Walk.Waits {
@@ -1098,7 +1195,9 @@ func (h *Holder) adopt(w Walk) {
h.settleAndKeep(d)
}
// turn is the ask that lets a published delivery's waiting walk start; nil when it is not its turn.
// turn says a published delivery's waiting walk may start as far as that delivery goes; nil when it is not its
// turn. The word itself is asked once per walk by words, when every live delivery the walk carries is at its turn
// (ADR 0276 decision 5). What it answers is only whether this delivery's turn came.
func (h *Holder) turn(d *Delivery, why string) func() {
if d.Walk == nil || !d.Walk.Waits || !d.LetGo.IsZero() {
return nil
@@ -1106,25 +1205,22 @@ func (h *Holder) turn(d *Delivery, why string) func() {
if d.State != Published && d.State != Delivering {
return nil
}
id, walk := d.ID, d.Walk.ID
return func() {
err := h.Controller.Deliver(walk, why)
h.mu.Lock()
defer h.mu.Unlock()
d := h.deliveries[id]
if d == nil {
return
}
if err != nil {
h.Logf("[mesh-delivery] %s's walk %s could not be let go yet: %v", id, walk, err)
return
}
d.LetGo, d.LetGoWhy = h.Now(), why
if d.Walk != nil && d.Walk.ID == walk {
d.Walk.Waits, d.Walk.GoBy = false, byOwner
}
h.settleAndKeep(d)
if d.Walk.State != walkBuilding && d.Walk.State != walkRolling {
return nil // a walk superseded, done or failed is no walk to start
}
if h.turning == nil {
h.turning = map[string]*turnWant{}
}
want := h.turning[d.Walk.ID]
if want == nil {
want = &turnWant{}
h.turning[d.Walk.ID] = want
}
if !containsString(want.ids, d.ID) {
want.ids = append(want.ids, d.ID)
want.whys = append(want.whys, d.ID+": "+why)
}
return func() {}
}
// membersOf are a group's current members, by id order.
@@ -1263,13 +1359,20 @@ func (h *Holder) groupTurns(g *Group, members []*Delivery) []func() {
// Composed for these very heads: a verdict on another set of heads is no verdict on these.
composed := g.Check != nil && (g.Check.Verdict == "pass" || g.Check.Verdict == "warning") &&
sameMembers(g.Check.Commits, commitsOf(members))
// A member's turn registered, the members after it that the same walk carries take theirs too: one walk is let
// go once, for all of them (ADR 0276 decision 5). A member on another walk, or not at its turn, waits.
var asks []func()
sharing := ""
for i, m := range ordered {
d := byID[m.ID]
switch {
case d.State.Reached():
if d.State.Reached() {
continue
}
if sharing != "" && (d.Walk == nil || d.Walk.ID != sharing) {
return asks
}
switch {
case d.State == Failed || d.State == Stopped || d.State == Superseded:
var asks []func()
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)
@@ -1286,32 +1389,35 @@ func (h *Holder) groupTurns(g *Group, members []*Delivery) []func() {
h.settleAndKeep(d)
if d.State == Published {
if a := h.turn(d, fmt.Sprintf("its turn in group %s (%d of %d)", g.ID, i+1, len(ordered))); a != nil {
return []func(){a}
asks, sharing = append(asks, a), d.Walk.ID
continue
}
}
}
return nil
return asks
case d.State == Published:
// Asked and not answered is not "did not pass": it waits, published, for the answer (issue 316).
if !composed && d.HeldWhy == "" && g.Check != nil && g.Check.Verdict == "" &&
sameMembers(g.Check.Commits, commitsOf(members)) {
return nil
return asks
}
if !composed && d.HeldWhy == "" {
d.HeldWhy = fmt.Sprintf("its group %s's composed check did not pass for the heads that merged; a person "+
"decides that it goes on", g.ID)
d.Unwalked = false
h.settleAndKeep(d)
return nil
return asks
}
if a := h.turn(d, fmt.Sprintf("its turn in group %s (%d of %d)", g.ID, i+1, len(ordered))); a != nil {
return []func(){a}
asks, sharing = append(asks, a), d.Walk.ID
continue
}
return nil
return asks
default:
return nil // not on the trunk yet, held, or delivering: the members after it wait
return asks // not on the trunk yet, held, or delivering: the members after it wait
}
}
return nil
return asks
}
// stopInGroup stops a member after one that failed, and its walk if it has one open.
@@ -162,7 +162,8 @@ func TestAFailedGateIsRolledBackAndTheDeliveryFails(t *testing.T) {
}
}
// A newer merge's walk took over this one's: superseded, from published or delivering.
// A walk superseded that names no walk taking it over (a record from before ADR 0276): superseded, from
// published or delivering. One that names its successor is followed (TestADeliveryFollowsTheWalkThatTookItsOver).
func TestANewerWalkSupersedesAnOlderDelivery(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/app", head)
@@ -172,7 +173,7 @@ func TestANewerWalkSupersedesAnOlderDelivery(t *testing.T) {
walk := aWalk("plan-1", "novox/app", merge, true)
w.h.WalkMoved(w.ctl.put(walk))
walk.State = walkSuperseded
walk.Note = "superseded at tier 0 by plan-2"
walk.Note = "superseded"
w.ctl.down = true // its word could not be given meanwhile
w.settleAll()
w.h.WalkMoved(w.ctl.put(walk))
@@ -0,0 +1,434 @@
package main
import (
"strings"
"testing"
"time"
)
// novox/hq issue 362, ADR 0276: two merges of one repository 18 seconds apart, walked by one walk at the later
// merge's commit, which superseded the first. Every delivery the walk answers follows it and ends with it; no
// walk is tied to a delivery that ended; and what cannot move is said at once.
// A controller that names the merges a walk answers (ADR 0276): the forge is not asked, and the earlier merge is
// tied at once.
func TestAWalkThatNamesItsMergesIsFollowedByEachOfThem(t *testing.T) {
w := twoMerges(t)
walk := catWalk("plan-9", mergeDu, atDu.Add(5*time.Second), "claude-code", "dunst")
walk.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: mergeCC, Carried: mergeDu}, {Repository: cat, Commit: mergeDu}}
w.h.WalkMoved(w.ctl.put(walk))
for _, id := range []string{IDOf(cat, headCC), IDOf(cat, headDu)} {
if d := w.delivery(id); d.State != Published || d.Walk == nil || d.Walk.ID != "plan-9" {
t.Fatalf("%s is %s on %v", id, d.State, d.Walk)
}
}
w.settleAll()
if len(w.forge.containsAsks) != 0 {
t.Fatalf("the forge was asked %v for a walk that names its merges", w.forge.containsAsks)
}
if len(w.ctl.delivers) != 1 {
t.Fatalf("the word was asked %v", w.ctl.delivers)
}
}
// A walk that failed marks no merge delivered: the merge it carried its own commit for fails, and the earlier
// merge it carried a later commit for waits for a walk of its own.
func TestAFailedWalkMarksNoMergeItCarriedDelivered(t *testing.T) {
w := twoMerges(t)
walk := catWalk("plan-9", mergeDu, atDu.Add(5*time.Second), "claude-code", "dunst")
walk.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: mergeCC, Carried: mergeDu}, {Repository: cat, Commit: mergeDu}}
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
walk = w.ctl.walk("plan-9")
walk.State, walk.Note = walkFailed, "dunst failed its gate on anchor"
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
if s := w.state(IDOf(cat, headDu)); s != Failed {
t.Fatalf("dunst is %s", s)
}
cc := w.delivery(IDOf(cat, headCC))
if cc.State != Published || cc.State.Reached() {
t.Fatalf("claude-code is %s", cc.State)
}
// Its own walk, when the controller opens one, is followed.
own := catWalk("plan-10", mergeCC, atDu.Add(time.Hour), "claude-code")
w.h.WalkMoved(w.ctl.put(own))
w.settleAll()
w.h.WalkMoved(w.ctl.put(done(w.ctl.walk("plan-10"), w.now)))
w.settleAll()
if s := w.state(IDOf(cat, headCC)); s != Delivered {
t.Fatalf("claude-code on its own walk is %s", s)
}
}
// Never revived: a walk at the commit of a delivery that ended is not tied to it, not adopted, and not let go —
// it is listed by `stalled` as a walk no live delivery holds.
func TestAWalkIsNeverTiedToADeliveryThatEnded(t *testing.T) {
w := twoMerges(t)
first := w.ctl.put(catWalk("plan-1", mergeDu, atDu.Add(5*time.Second), "dunst"))
w.h.WalkMoved(first)
w.h.WalkMoved(w.ctl.put(supersededBy(first, "")))
// A record from before ADR 0276: superseded, naming no walk — the delivery ends superseded.
sup := w.ctl.walk("plan-1")
sup.Note = "superseded"
w.h.WalkMoved(w.ctl.put(sup))
idDu := IDOf(cat, headDu)
if s := w.state(idDu); s != Superseded {
t.Fatalf("dunst is %s", s)
}
orphan := catWalk("plan-2", mergeDu, atDu.Add(6*time.Second), "dunst")
w.h.WalkMoved(w.ctl.put(orphan))
w.settleAll()
w.later(3 * time.Minute)
w.settleAll()
if d := w.delivery(idDu); d.State != Superseded || d.Walk.ID != "plan-1" {
t.Fatalf("the ended delivery is %s on %s", d.State, d.Walk.ID)
}
for _, asked := range w.ctl.delivers {
if asked == "plan-2" {
t.Fatal("a walk no live delivery holds was let go")
}
}
var found bool
for _, l := range w.h.Stalled() {
if l.State == Unheld && l.Walk == "plan-2" {
found = true
}
}
if !found {
t.Fatalf("stalled does not list the walk no delivery holds: %+v", w.h.Stalled())
}
}
// A merge no walk answers is held after ten minutes with the true reason, and listed by `stalled` at once; a walk
// that answers it later lifts it.
func TestAMergeNoWalkAnswersIsHeldTrulyAndSaidAtOnce(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atCC)
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
w.h.PullUpdated(cc)
w.h.Checked(verdictMoving(cat, 101, headCC, "claude-code"))
w.h.PullMerged(mergedAt(cc, mergeCC, atCC))
// An open walk of the repository that moves something else: it does not answer this merge.
w.h.WalkMoved(w.ctl.put(catWalk("plan-5", "eeeeeeeeeeee5555", atCC.Add(time.Minute), "dunst")))
w.later(11 * time.Minute)
if err := w.h.Reconcile(); err != nil {
t.Fatal(err)
}
w.settleAll()
id := IDOf(cat, headCC)
d := w.delivery(id)
if d.State != Held || !strings.Contains(d.HeldWhy, "plan-5") || !strings.Contains(d.HeldWhy, "name no merge") {
t.Fatalf("it is %s: %q", d.State, d.HeldWhy)
}
lines := w.h.Stalled()
if len(lines) != 1 || lines[0].ID != id || lines[0].Bound != "0s" {
t.Fatalf("stalled lists %+v", lines)
}
// The controller opens a walk that names it.
walk := catWalk("plan-6", mergeCC, w.now, "claude-code")
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
if s := w.state(id); s != Delivering || len(w.ctl.delivers) != 1 || w.ctl.delivers[0] != "plan-6" {
t.Fatalf("with its walk it is %s, let go %v", s, w.ctl.delivers)
}
}
// One word per walk, and none while a delivery it carries is not at its turn.
func TestAWalkIsLetGoOnlyWhenEveryDeliveryItCarriesIsAtItsTurn(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atDu.Add(5*time.Second))
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
du := pr(cat, 102, headDu, "feat/dunst", "modules/dunst/module.json")
w.h.PullUpdated(cc)
w.h.PullUpdated(du)
w.h.Checked(verdictMoving(cat, 101, headCC, "claude-code"))
// dunst merges unchecked: held for a person.
w.h.PullMerged(mergedAt(cc, mergeCC, atCC))
w.h.PullMerged(mergedAt(du, mergeDu, atDu))
walk := catWalk("plan-9", mergeDu, atDu.Add(5*time.Second), "claude-code", "dunst")
walk.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: mergeCC, Carried: mergeDu}, {Repository: cat, Commit: mergeDu}}
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
if s := w.state(IDOf(cat, headDu)); s != Held {
t.Fatalf("dunst is %s", s)
}
if len(w.ctl.delivers) != 0 {
t.Fatalf("the walk was let go %v while it carries a held delivery", w.ctl.delivers)
}
if _, err := w.h.Release(IDOf(cat, headDu), "checked by hand", "jochen"); err != nil {
t.Fatal(err)
}
w.settleAll()
if len(w.ctl.delivers) != 1 {
t.Fatalf("released, the word was asked %v", w.ctl.delivers)
}
}
// A merge in a batch still assembling is said so, is not held for a person, and nothing is let go.
func TestAMergeInAnAssemblingBatchIsShownAndNotHeld(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atCC)
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
w.h.PullUpdated(cc)
w.h.Checked(verdictMoving(cat, 101, headCC, "claude-code"))
w.h.PullMerged(mergedAt(cc, mergeCC, atCC))
closes, atMost := atCC.Add(90*time.Second), atCC.Add(10*time.Minute)
batch := Walk{ID: "plan-b1", Repository: cat, Branch: "main", Commit: mergeCC, Created: atCC, State: walkAssembling,
Delivery: &WalkDelivery{Awaits: "mesh-delivery", Merges: []WalkMerge{{Repository: cat, Commit: mergeCC}},
Batch: &WalkBatch{ClosesAt: &closes, AtMost: &atMost}}}
w.h.WalkMoved(w.ctl.put(batch))
w.later(48 * time.Second)
w.settleAll()
id := IDOf(cat, headCC)
var line Line
for _, l := range w.h.Deliveries("", "", "", true) {
if l.ID == id {
line = l
}
}
if !strings.Contains(line.Waits, "plan-b1: assembling, 42s left") || !strings.Contains(line.Waits, "plan not yet calculated") {
t.Fatalf("it waits for %q", line.Waits)
}
// Queued behind a walk for longer than a walk opens within: still not held.
batch = w.ctl.walk("plan-b1")
batch.State, batch.Delivery.Batch.Behind = walkQueued, "plan-7"
w.h.WalkMoved(w.ctl.put(batch))
w.later(20 * time.Minute)
if err := w.h.Reconcile(); err != nil {
t.Fatal(err)
}
w.settleAll()
if d := w.delivery(id); d.State != Ready || d.Walk != nil {
t.Fatalf("a merge in a queued batch is %s on %v", d.State, d.Walk)
}
if len(w.ctl.delivers) != 0 {
t.Fatalf("a batch was let go: %v", w.ctl.delivers)
}
// Cut: the batch is a walk now, under its id.
batch.State, batch.Delivery.Batch = walkBuilding, nil
batch.Tiers, batch.Modules = [][]string{{"claude-code"}}, map[string]*WalkModule{"claude-code": {}}
w.h.WalkMoved(w.ctl.put(batch))
w.settleAll()
if d := w.delivery(id); d.State != Delivering || d.Walk.ID != "plan-b1" {
t.Fatalf("cut, it is %s on %v", d.State, d.Walk)
}
}
// An older record of the walk a delivery no longer follows, arriving late, does not take it back.
func TestALateRecordOfAnOlderWalkDoesNotTakeADeliveryBack(t *testing.T) {
w := twoMerges(t)
w.forge.contains[mergeCC+" in "+mergeDu] = true
first := w.ctl.put(catWalk("plan-1", mergeDu, atDu.Add(5*time.Second), "dunst"))
w.h.WalkMoved(first)
w.h.WalkMoved(w.ctl.put(catWalk("plan-2", mergeDu, atDu.Add(6*time.Second), "claude-code", "dunst")))
w.h.WalkMoved(first) // the older record, again
if d := w.delivery(IDOf(cat, headDu)); d.Walk.ID != "plan-2" {
t.Fatalf("dunst went back to %s", d.Walk.ID)
}
}
// A walk no merge was heard for, at the commit of a delivery that ended, is not adopted by that delivery: it is
// listed by `stalled` as a walk no live delivery holds.
func TestAnUnheardWalkIsNotAdoptedByADeliveryThatEnded(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atCC)
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
w.h.PullUpdated(cc)
w.h.PullClosed(cc)
id := IDOf(cat, headCC)
if s := w.state(id); s != Stopped {
t.Fatalf("closed unmerged it is %s", s)
}
w.h.WalkMoved(w.ctl.put(catWalk("plan-3", headCC, atCC.Add(time.Minute), "claude-code")))
w.later(3 * time.Minute)
w.settleAll()
w.later(3 * time.Minute)
w.settleAll()
if d := w.delivery(id); d.State != Stopped || d.Walk != nil {
t.Fatalf("the stopped delivery is %s on %v", d.State, d.Walk)
}
var found bool
for _, l := range w.h.Stalled() {
found = found || l.State == Unheld && l.Walk == "plan-3"
}
if !found || len(w.ctl.delivers) != 0 {
t.Fatalf("stalled lists %+v; let go %v", w.h.Stalled(), w.ctl.delivers)
}
}
// Two members of one delivery group carried by one walk: let go once, for both (review of #171: the group's turn
// stopped at its first member, and the walk waited for the second for ever).
func TestAWalkCarryingTwoMembersOfAGroupIsLetGoOnce(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atDu.Add(5*time.Second))
a := pr("novox/a", 1, "aaaaaaaaaaaa0001", "feat/y", "x.go")
b := pr("novox/b", 2, "bbbbbbbbbbbb0002", "feat/y", "y.go")
w.h.PullUpdated(a)
w.h.PullUpdated(b)
w.h.Checked(verdictMoving("novox/a", 1, "aaaaaaaaaaaa0001", "app-a"))
w.h.Checked(verdictMoving("novox/b", 2, "bbbbbbbbbbbb0002", "app-b"))
w.settleAll()
g := w.h.groups["feat/y"]
if g == nil || g.Check == nil {
t.Fatalf("the group is %+v", g)
}
w.h.Checked(CheckedEvent{Group: "feat/y", ID: g.Check.Asked, Verdict: "pass", Summary: "composed"})
w.h.PullMerged(mergedAt(a, "aaaaaaaaaaaa1111", atCC))
w.h.PullMerged(mergedAt(b, "bbbbbbbbbbbb2222", atDu))
batch := Walk{ID: "plan-batch", Repository: "novox/a", Branch: "main", Commit: "aaaaaaaaaaaa1111",
Created: atDu.Add(time.Minute), State: walkBuilding, Tiers: [][]string{{"app-a"}, {"app-b"}},
Modules: map[string]*WalkModule{"app-a": {}, "app-b": {}},
Delivery: &WalkDelivery{Awaits: "mesh-delivery", Merges: []WalkMerge{
{Repository: "novox/a", Commit: "aaaaaaaaaaaa1111"}, {Repository: "novox/b", Commit: "bbbbbbbbbbbb2222"}}}}
w.h.WalkMoved(w.ctl.put(batch))
w.settleAll()
if got := w.ctl.delivers; len(got) != 1 || got[0] != "plan-batch" {
t.Fatalf("the word was asked %v, want once for plan-batch", got)
}
for _, id := range []string{IDOf("novox/a", "aaaaaaaaaaaa0001"), IDOf("novox/b", "bbbbbbbbbbbb0002")} {
if s := w.state(id); s != Delivering {
t.Fatalf("%s is %s", id, s)
}
}
}
// A walk that names its merges answers only those: a delivery whose walk it took over and which it does not name
// is not tied to it, and ends superseded once that is known.
func TestAWalkThatNamesItsMergesAnswersOnlyThose(t *testing.T) {
w := twoMerges(t)
first := w.ctl.put(catWalk("plan-1", mergeDu, atDu.Add(5*time.Second), "dunst"))
w.h.WalkMoved(first)
other := catWalk("plan-2", "ffffffffffff9999", atDu.Add(6*time.Second), "dunst")
other.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: "ffffffffffff9999"}}
w.h.WalkMoved(w.ctl.put(other))
w.h.WalkMoved(w.ctl.put(supersededBy(first, "plan-2")))
w.settleAll()
d := w.delivery(IDOf(cat, headDu))
if d.State != Superseded || d.Walk.ID != "plan-1" {
t.Fatalf("dunst is %s on %s", d.State, d.Walk.ID)
}
for _, asked := range w.ctl.delivers {
if asked == "plan-2" {
t.Fatal("a walk that does not name dunst was let go for it")
}
}
}
// The forge's containment ties a merge to a walk only when the walk moves everything the merge moves and was made
// for a later merge.
func TestContainmentAloneDoesNotTieAMerge(t *testing.T) {
for _, c := range []struct {
name string
modules []string
created time.Time
names bool
}{
{"the later walk moves only its own module", []string{"dunst"}, atDu.Add(5 * time.Second), false},
{"the walk was made for an earlier merge", []string{"claude-code", "dunst"}, atCC.Add(-time.Minute), false},
{"the walk names its merges, and not this one", []string{"claude-code", "dunst"}, atDu.Add(5 * time.Second), true},
} {
t.Run(c.name, func(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atDu.Add(5*time.Second))
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
w.h.PullUpdated(cc)
w.h.Checked(verdictMoving(cat, 101, headCC, "claude-code"))
w.h.PullMerged(mergedAt(cc, mergeCC, atCC))
w.forge.contains[mergeCC+" in "+mergeDu] = true
walk := catWalk("plan-2", mergeDu, c.created, c.modules...)
if c.names {
walk.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: mergeDu}}
}
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
if d := w.delivery(IDOf(cat, headCC)); d.Walk != nil || len(w.forge.containsAsks) != 0 {
t.Fatalf("it follows %v; the forge was asked %v", d.Walk, w.forge.containsAsks)
}
})
}
}
// After the walk that carried a later commit for it failed, a merge stays published — once — and is said within
// the ten minutes a walk opens within, naming what happened.
func TestAMergeWhoseCarryingWalkFailedWaitsOnceAndIsSaid(t *testing.T) {
w := twoMerges(t)
walk := catWalk("plan-9", mergeDu, atDu.Add(5*time.Second), "claude-code", "dunst")
walk.Delivery.Merges = []WalkMerge{{Repository: cat, Commit: mergeCC, Carried: mergeDu}, {Repository: cat, Commit: mergeDu}}
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
walk = w.ctl.walk("plan-9")
walk.State = walkFailed
w.h.WalkMoved(w.ctl.put(walk))
w.settleAll()
id := IDOf(cat, headCC)
d := w.delivery(id)
rewalks := 0
for _, tr := range d.Transitions {
if tr.Event == EvRewalk {
rewalks++
}
}
if d.State != Published || rewalks != 1 {
t.Fatalf("it is %s after %d rewalk(s)", d.State, rewalks)
}
w.later(11 * time.Minute)
var line *StalledLine
for _, l := range w.h.Stalled() {
if l.ID == id {
l := l
line = &l
}
}
if line == nil || !strings.Contains(line.Says, "no walk of its own commit") {
t.Fatalf("stalled says %+v", line)
}
}
// A delivery that ended is never tied to a walk after: not one that answers its merge, not the walk that took its
// own over.
func TestADeliveryThatEndedFollowsNoWalk(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atCC)
w.ctl.planned = planMoving(cat)
p := pr(cat, 103, "eeeeeeeeeeee0003", "docs/x", "README.md")
w.h.PullUpdated(p)
w.h.PullMerged(mergedAt(p, "eeeeeeeeeeee3333", atCC))
w.settleAll()
nothing := IDOf(cat, "eeeeeeeeeeee0003")
if s := w.state(nothing); s != Delivered {
t.Fatalf("a merge moving nothing is %s", s)
}
w.h.WalkMoved(w.ctl.put(catWalk("plan-4", "eeeeeeeeeeee3333", atCC.Add(time.Minute), "app")))
w.settleAll()
if d := w.delivery(nothing); d.Walk != nil {
t.Fatalf("a delivered merge was tied to %s", d.Walk.ID)
}
w2 := twoMerges(t)
first := w2.ctl.put(catWalk("plan-1", mergeDu, atDu.Add(5*time.Second), "dunst"))
w2.h.WalkMoved(first)
idDu := IDOf(cat, headDu)
if _, err := w2.h.Stop(idDu, "not now", "jochen"); err != nil {
t.Fatal(err)
}
w2.ctl.put(catWalk("plan-2", mergeDu, atDu.Add(6*time.Second), "claude-code", "dunst"))
w2.h.WalkMoved(w2.ctl.put(supersededBy(w2.ctl.walk("plan-1"), "plan-2")))
w2.settleAll()
if d := w2.delivery(idDu); d.State != Stopped || d.Walk.ID != "plan-1" {
t.Fatalf("the stopped delivery is %s on %s", d.State, d.Walk.ID)
}
}
// Today's controller, merges A then B minutes apart: B's walk took A's over before it started. A follows it and
// ends covered by B, rather than superseded (ADR 0276 decision 4).
func TestAnEarlierMergeFollowsTheWalkThatTookItsOver(t *testing.T) {
w := twoMerges(t)
first := w.ctl.put(catWalk("plan-1", mergeCC, atCC.Add(5*time.Second), "claude-code"))
w.h.WalkMoved(first)
w.h.WalkMoved(w.ctl.put(catWalk("plan-2", mergeDu, atDu.Add(6*time.Second), "claude-code", "dunst")))
w.h.WalkMoved(w.ctl.put(supersededBy(w.ctl.walk("plan-1"), "plan-2")))
w.settleAll()
idCC := IDOf(cat, headCC)
if d := w.delivery(idCC); d.Walk.ID != "plan-2" || len(w.ctl.delivers) != 1 {
t.Fatalf("claude-code follows %s; let go %v", d.Walk.ID, w.ctl.delivers)
}
w.h.WalkMoved(w.ctl.put(done(w.ctl.walk("plan-2"), w.now)))
w.settleAll()
if s := w.state(idCC); s != Covered {
t.Fatalf("claude-code is %s", s)
}
}
@@ -68,7 +68,14 @@ type Delivery struct {
// ContainedIn is the forge's answer, by a later merge commit, to whether that commit contains this one's
// merge (gitea_contains): asked once per pair, and kept (ADR 0270).
ContainedIn map[string]bool `json:"contained_in,omitempty"`
// Walk is the controller's walk of its trunk commit, as last read; Steps the machines it reached.
// AnsweredBy is the commit of its repository the walk it follows carries for it: its own merge, or a later merge
// of its branch that contains it (novox/hq ADR 0276). Unwalked says it is held because no walk answered it.
AnsweredBy string `json:"answered_by,omitempty"`
// Carrier says, for a merge a later one carries, which: its delivery and walk, in words.
Carrier string `json:"carrier,omitempty"`
Unwalked bool `json:"unwalked,omitempty"`
// Walk is the walk that delivers it, as last read: its own trunk commit's, or one that carries a later merge
// of its branch; Steps the machines it reached.
Walk *WalkSeen `json:"walk,omitempty"`
Steps []Step `json:"steps,omitempty"`
// LetGo is when this owner asked the controller to start its walk, and why.
@@ -166,6 +173,16 @@ type MachinePlan struct {
Waits []string `json:"waits,omitempty"`
}
// waitsForItsOwnWalk is whether it is published again after the walk that carried a later commit for it failed.
func (d *Delivery) waitsForItsOwnWalk() bool {
return d.State == Published && d.Walk != nil && d.Walk.State == walkFailed && d.carriedLater()
}
// carriedLater is whether the walk it follows carries a later merge of its branch for it, not its own.
func (d *Delivery) carriedLater() bool {
return d.AnsweredBy != "" && d.MergedAs != "" && !sameCommit(d.AnsweredBy, d.MergedAs)
}
// MovesNothing is whether the delivery's plan moves no module the mesh holds: nothing for a walk to do. A
// module it adds is not moved: the controller builds and registers it and sends it nowhere, and opens no walk
// for it (novox/hq issues 300 and 319). A delivery with no plan known is not said to move nothing.
@@ -337,7 +354,7 @@ type WalkRelease struct {
By string `json:"by,omitempty"`
}
// WalkDelivery is what a walk waits for.
// WalkDelivery is what a walk waits for, and which merges it answers (novox/hq ADR 0276).
type WalkDelivery struct {
Awaits string `json:"awaits"`
Go *time.Time `json:"go,omitempty"`
@@ -345,6 +362,81 @@ type WalkDelivery struct {
Why string `json:"why,omitempty"`
Stopped string `json:"stopped,omitempty"`
StoppedWhy string `json:"stopped_why,omitempty"`
// Merges are every merge the walk answers: each merge commit, its repository, and the commit of that
// repository the walk carries — the merge itself, or a later merge of its branch that contains it. Absent
// from a controller that does not say it: the walk then answers the merge of its own commit, and a merge
// the forge says its commit contains.
Merges []WalkMerge `json:"merges,omitempty"`
// TakenOverBy is, on a walk folded before it started, the walk that took it over and answers every merge
// it answered.
TakenOverBy string `json:"taken_over_by,omitempty"`
// Batch is set while the record is a batch, not yet a walk: its state `assembling` or `queued`. A batch, once
// cut, is the walk under the same id: its next record is `building`. A merge this owner reads in a batch is
// not held as unwalked while the batch is heard of.
Batch *WalkBatch `json:"batch,omitempty"`
}
// WalkMerge is one merge a walk answers.
type WalkMerge struct {
Repository string `json:"repository"`
Commit string `json:"commit"`
// Carried is the commit of the repository the walk carries for it: Commit itself, or a later merge of its
// branch that contains it. Empty means Commit.
Carried string `json:"carried,omitempty"`
}
// WalkBatch is a batch's window: when it closes unless another merge arrives, when it closes at the latest,
// and, once closed, the walk it waits behind.
type WalkBatch struct {
ClosesAt *time.Time `json:"closes_at,omitempty"`
AtMost *time.Time `json:"at_most,omitempty"`
Behind string `json:"behind,omitempty"`
}
// The states a batch's record is in before it is a walk (novox/hq ADR 0276).
const (
walkAssembling = "assembling"
walkQueued = "queued"
)
// IsBatch is whether the record is a batch still to be cut into a walk.
func (w Walk) IsBatch() bool { return w.State == walkAssembling || w.State == walkQueued }
// supersededNote reads the walk that took a superseded walk over from its note, for a controller that does not
// say TakenOverBy: "superseded at tier N by plan-… (…)".
var supersededNote = regexp.MustCompile(`^superseded at tier \d+ by (plan-[0-9A-Za-z_-]+)`)
// takenOverBy is the walk that took this one over, said or read from its note; empty for none.
func (w Walk) takenOverBy() string {
if w.State != walkSuperseded {
return ""
}
if w.Delivery != nil && w.Delivery.TakenOverBy != "" {
return w.Delivery.TakenOverBy
}
if m := supersededNote.FindStringSubmatch(w.Note); m != nil {
return m[1]
}
return ""
}
// carriedFor is the commit the walk carries for a merge of a repository, and whether it answers that merge by
// what it says: the merge named among its merges, or its own commit.
func (w Walk) carriedFor(repository, merge string) (string, bool) {
if w.Delivery != nil {
for _, m := range w.Delivery.Merges {
if strings.EqualFold(m.Repository, repository) && sameCommit(m.Commit, merge) {
if m.Carried != "" {
return m.Carried, true
}
return m.Commit, true
}
}
}
if strings.EqualFold(w.Repository, repository) && sameCommit(w.Commit, merge) {
return w.Commit, true
}
return "", false
}
// WalkSeen is what a delivery keeps of its walk.
@@ -363,6 +455,10 @@ type WalkSeen struct {
Updated time.Time `json:"updated"`
// Branch is the branch the walk's commit was merged into; empty in a record kept before it was read.
Branch string `json:"branch,omitempty"`
// Created is when the controller made the walk: a delivery follows the newest walk that answers it.
Created time.Time `json:"created"`
// TakenOverBy is the walk that took a superseded one over (novox/hq ADR 0276): its deliveries follow it.
TakenOverBy string `json:"taken_over_by,omitempty"`
}
// Seen is a walk as a delivery keeps it.
@@ -376,6 +472,8 @@ func (w Walk) Seen() *WalkSeen {
s.StoppedBy = d.Stopped
}
s.Branch = w.Branch
s.Created = w.Created
s.TakenOverBy = w.takenOverBy()
return s
}
@@ -0,0 +1,127 @@
package main
import (
"strings"
"testing"
"time"
)
// Replay R362 (novox/hq issue 362, mesh-lab's register): two merges of the catalogue 18 seconds apart, walked by
// one walk at the later merge's commit, which superseded the first — as the controller of 2026-10-10 said it, with
// no `merges` and the successor named only in the superseded walk's note. Written with only what mesh-delivery
// had before its fix (and fakes_test.go, covered_test.go's helpers), so it can be laid over the commit before.
const (
cat = "novox/mesh-catalog"
headCC = "cccccccccccc1111" // claude-code's head
headDu = "dddddddddddd2222" // dunst's head
mergeCC = "553b71910000aaaa" // merged first, 00:20:52
mergeDu = "48bda4750000bbbb" // merged 18 s later, 00:21:09: contains mergeCC
)
var (
atCC = time.Date(2026, 10, 9, 22, 20, 52, 0, time.UTC)
atDu = atCC.Add(17 * time.Second)
)
// twoMerges is the night of issue 362 up to the walks: both pull requests checked, both merged.
func twoMerges(t *testing.T) *world {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atDu.Add(5*time.Second))
cc := pr(cat, 101, headCC, "feat/claude-code", "modules/claude-code/module.json")
du := pr(cat, 102, headDu, "feat/dunst", "modules/dunst/module.json")
w.h.PullUpdated(cc)
w.h.PullUpdated(du)
w.h.Checked(verdictMoving(cat, 101, headCC, "claude-code"))
w.h.Checked(verdictMoving(cat, 102, headDu, "dunst"))
w.h.PullMerged(mergedAt(cc, mergeCC, atCC))
w.h.PullMerged(mergedAt(du, mergeDu, atDu))
return w
}
// catWalk is a walk of the catalogue at a commit, waiting for its word, moving the modules given.
func catWalk(id, commit string, created time.Time, modules ...string) Walk {
w := Walk{ID: id, Repository: cat, Branch: "main", Commit: commit, Created: created, State: walkBuilding,
Tiers: [][]string{modules}, Modules: map[string]*WalkModule{}, Delivery: &WalkDelivery{Awaits: "mesh-delivery"}}
for _, m := range modules {
w.Modules[m] = &WalkModule{}
}
return w
}
// done is a walk's record once every module passed on its first machine and was sent to the rest.
func done(w Walk, at time.Time) Walk {
w.State = walkDone
for name := range w.Modules {
judged := at
w.Modules[name] = &WalkModule{State: "built", First: []string{"anchor"}, FirstAt: &judged, SentAt: &judged,
Gate: &WalkGate{Machines: []string{"anchor"}, Verdict: "passed", JudgedAt: &judged}}
}
return w
}
func supersededBy(old Walk, by string) Walk {
old.State = walkSuperseded
old.Note = "superseded at tier 0 by " + by + " (novox/mesh-catalog at 48bda475); dunst planned there again"
return old
}
// The replay of issue 362, as today's controller says it: no `merges`, the successor named only in its note. In
// both orders the controller's two records can arrive in, the walk at the dunst commit delivers both merges
// without a person: claude-code covered, dunst delivered, the word asked once.
func TestReplay362(t *testing.T) {
for _, order := range []string{"newer walk first", "supersession first"} {
t.Run(order, func(t *testing.T) {
w := twoMerges(t)
w.forge.contains[mergeCC+" in "+mergeDu] = true
idCC, idDu := IDOf(cat, headCC), IDOf(cat, headDu)
first := w.ctl.put(catWalk("plan-1", mergeDu, atDu.Add(5*time.Second), "dunst"))
w.h.WalkMoved(first)
if s := w.state(idDu); s != Published {
t.Fatalf("dunst with its walk opened is %s", s)
}
second := catWalk("plan-2", mergeDu, atDu.Add(6*time.Second), "claude-code", "dunst")
if order == "newer walk first" {
w.h.WalkMoved(w.ctl.put(second))
w.h.WalkMoved(w.ctl.put(supersededBy(first, "plan-2")))
} else {
w.ctl.put(second) // kept by the controller, said after
w.h.WalkMoved(w.ctl.put(supersededBy(first, "plan-2")))
}
w.settleAll()
for _, id := range []string{idCC, idDu} {
if d := w.delivery(id); d.State.Final() || d.State == Held {
t.Fatalf("%s is %s (%s)", id, d.State, d.HeldWhy)
}
}
if got := w.ctl.delivers; len(got) != 1 || got[0] != "plan-2" {
t.Fatalf("the word was asked %v, want once for plan-2", got)
}
for _, id := range []string{idCC, idDu} {
if d := w.delivery(id); d.State != Delivering || d.Walk.ID != "plan-2" {
t.Fatalf("%s is %s on %v", id, d.State, d.Walk)
}
}
// Ten minutes and a reconcile later nothing is held for a person.
w.later(11 * time.Minute)
if err := w.h.Reconcile(); err != nil {
t.Fatal(err)
}
w.settleAll()
w.h.WalkMoved(w.ctl.put(done(w.ctl.walk("plan-2"), w.now)))
w.settleAll()
cc, du := w.delivery(idCC), w.delivery(idDu)
if du.State != Delivered {
t.Fatalf("dunst is %s", du.State)
}
if cc.State != Covered || !strings.Contains(cc.Transitions[len(cc.Transitions)-1].Why, idDu) {
t.Fatalf("claude-code is %s: %+v", cc.State, cc.Transitions[len(cc.Transitions)-1])
}
if len(w.h.Stalled()) != 0 {
t.Fatalf("stalled lists %+v", w.h.Stalled())
}
if len(w.ctl.delivers) != 1 {
t.Fatalf("the word was asked %v", w.ctl.delivers)
}
})
}
}
@@ -83,7 +83,8 @@ const (
EvHistory Event = "history" // made from the forge's word for a merge long past: history
EvRetire Event = "retire" // a person's word that a held delivery is history, never to be walked
EvCovered Event = "covered" // a later merge into the same trunk delivered every module it moves
EvLifted Event = "lifted" // what held it for a person is no longer so: its group's composed check passed
EvLifted Event = "lifted" // what held it for a person is no longer so: its group's composed check passed, or a walk now answers its merge
EvRewalk Event = "rewalk" // the walk that carried a later commit for it failed: it waits for a walk of its own commit
)
// Facts are what a guard reads beyond the delivery itself: the moment, and who asks, for an act.
@@ -222,8 +223,11 @@ var Table = []Row{
}},
{From: []State{Published}, Event: EvSuperseded, To: Superseded,
Guard: "a newer delivery to the same trunk took over its walk",
Guard: "its walk was superseded and names no walk that took it over: a walk that took it over answers its merge, and it follows that walk (novox/hq ADR 0276)",
Holds: func(d *Delivery, f Facts) error {
if d.Walk != nil && d.Walk.State == walkSuperseded && d.Walk.TakenOverBy != "" {
return fmt.Errorf("its walk was taken over by %s, which answers its merge: it follows that walk", d.Walk.TakenOverBy)
}
return unless(d.Walk != nil && d.Walk.State == walkSuperseded, "its walk is not superseded")
}},
{From: []State{Published}, Event: EvDone, To: Delivered,
@@ -236,8 +240,14 @@ var Table = []Row{
return unless(d.HeldWhy != "" && d.MergedAs != "", "nothing holds it, or it is not on the trunk")
}},
{From: []State{Published}, Event: EvGo, To: Delivering,
Guard: "its walk started: let go by this owner in its group's order, by a person, or on the controller's own path",
Holds: func(d *Delivery, f Facts) error { return unless(d.Walk != nil && d.Walk.Started(), "its walk waits") }},
Guard: "its walk started: let go by this owner in its group's order, by a person, or on the controller's own path " +
"— not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit",
Holds: func(d *Delivery, f Facts) error {
if d.Walk != nil && d.Walk.State == walkFailed && d.carriedLater() {
return errors.New("its walk, which carried a later commit for it, failed: it waits for a walk of its own commit")
}
return unless(d.Walk != nil && d.Walk.Started(), "its walk waits")
}},
{From: []State{Held}, Event: EvGo, To: Delivering,
Guard: "its walk started on a word that was not this owner's: the controller's own path, which waits for " +
"nobody, or a person's `plans go`",
@@ -246,8 +256,8 @@ var Table = []Row{
"its walk was not started by a person or the controller's own path")
}},
{From: []State{Held}, Event: EvLifted, To: Published,
Guard: "held only for its group's composed check, which has since passed or warned for the heads that merged: " +
"it is published again and waits for its turn",
Guard: "held only for its group's composed check, which has since passed or warned for the heads that merged, or " +
"held because no walk answered its merge, and one now does: it is published again and waits for its turn",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Lifted != "" && d.HeldWhy == "" && d.MergedAs != "" && walkCanGoOn(d), "nothing lifted its hold")
},
@@ -259,14 +269,26 @@ var Table = []Row{
}},
{From: []State{Delivering}, Event: EvSuperseded, To: Superseded,
Guard: "a newer delivery to the same trunk took over its walk",
Guard: "its walk was superseded and names no walk that took it over: a walk that took it over answers its merge, and it follows that walk (novox/hq ADR 0276)",
Holds: func(d *Delivery, f Facts) error {
if d.Walk != nil && d.Walk.State == walkSuperseded && d.Walk.TakenOverBy != "" {
return fmt.Errorf("its walk was taken over by %s, which answers its merge: it follows that walk", d.Walk.TakenOverBy)
}
return unless(d.Walk != nil && d.Walk.State == walkSuperseded, "its walk is not superseded")
}},
{From: []State{Delivering}, Event: EvDone, To: Delivered,
Guard: "its walk is done: every machine of its deploy plan passed, or was left as its policy says",
Guard: "its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Walk != nil && d.Walk.State == walkDone, "its walk is not done")
return unless(d.Walk != nil && d.Walk.State == walkDone && !d.carriedLater(), "its walk is not done, or carried a later commit for it")
}},
{From: []State{Delivering}, Event: EvCovered, To: Covered,
Guard: "its walk is done, carrying a later merge of its branch that contains its merge: answered by its successor in " +
"one walk (novox/hq ADR 0276)",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Walk != nil && d.Walk.State == walkDone && d.carriedLater(), "its walk is not done, or carried its own commit")
},
Says: func(d *Delivery) string {
return "covered: a later merge that contains it was delivered: " + d.Carrier
}},
{From: []State{Delivering}, Event: EvStop, To: Stopped,
Guard: "its walk was stopped through this owner",
@@ -274,9 +296,21 @@ var Table = []Row{
return unless(d.Walk != nil && d.Walk.State == walkFailed && d.Walk.StoppedBy != "", "its walk was not stopped")
}},
{From: []State{Delivering}, Event: EvFailed, To: Failed,
Guard: "its walk failed: a gate on a first machine (what it carried put back), a build, a machine",
Guard: "its walk failed, carrying its own commit: a gate on a first machine (what it carried put back), a build, a machine",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Walk != nil && d.Walk.State == walkFailed && d.Walk.StoppedBy == "", "its walk did not fail")
return unless(d.Walk != nil && d.Walk.State == walkFailed && d.Walk.StoppedBy == "" && !d.carriedLater(),
"its walk did not fail, or carried a later commit for it")
}},
{From: []State{Delivering}, Event: EvRewalk, To: Published,
Guard: "its walk failed carrying a later merge of its branch for it: a failed walk marks no merge delivered, and this " +
"merge waits, published, for a walk of its own commit (novox/hq ADR 0276 decision 3)",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Walk != nil && d.Walk.State == walkFailed && d.Walk.StoppedBy == "" && d.carriedLater(),
"its walk did not fail, or carried its own commit")
},
Says: func(d *Delivery) string {
return fmt.Sprintf("its walk %s, which carried %s for it, failed: it waits for a walk of its own commit",
d.Walk.ID, shortOf(d.AnsweredBy, 12))
}},
{From: notFinal, Event: EvStop, To: Stopped, Act: true,
@@ -450,10 +484,31 @@ func boundSince(d *Delivery) time.Time {
return d.Since
}
// heldUnwalked is the bound of a delivery held because no walk answers its merge (novox/hq issue 362): none. It
// waited the ten minutes a walk opens within already, and nothing but a person moves it now.
var heldUnwalked = Bound{For: 0, Says: "merged, and no walk answers its merge: the operator's"}
// rewalkBound is the bound of a merge whose walk carried a later commit for it and failed: a walk of its own
// commit opens within the ten minutes a walk opens within (ADR 0276 decision 3), or nothing but a person moves it.
var rewalkBound = Bound{For: unwalked, Says: "the walk that carried a later commit for it failed, and no walk of " +
"its own commit opened: the operator's"}
// boundOf is a delivery's bound: its state's, or none for a delivery held because no walk answers it.
func boundOf(d *Delivery) (Bound, bool) {
if d.State == Held && d.Unwalked && d.Walk == nil {
return heldUnwalked, true
}
if d.waitsForItsOwnWalk() {
return rewalkBound, true
}
b, bounded := Bounds[d.State]
return b, bounded
}
// pastBound is whether a delivery has been where it is longer than its state's bound.
func pastBound(d *Delivery, now time.Time) bool {
b, bounded := Bounds[d.State]
return bounded && !d.State.Final() && now.Sub(boundSince(d)) > b.For
b, bounded := boundOf(d)
return bounded && !d.State.Final() && (b.For == 0 || now.Sub(boundSince(d)) > b.For)
}
// StepState is where one machine stands in a delivering walk.
@@ -65,6 +65,14 @@ func aDeliveryFor(r Row) (*Delivery, Facts) {
}
case EvCovered:
d.MergedAs, d.CoveredBy = "aaaa1111", "delivered with novox/app@bbbb (merged as bbbb2222, walk plan-2): app"
if r.From[0] == Delivering {
// Answered by its successor in one walk (ADR 0276): the walk done, carrying a later merge.
d.CoveredBy, d.AnsweredBy, d.Carrier = "", "bbbb2222", "novox/app@bbbb (merged as bbbb2222, walk plan-2)"
d.Walk = &WalkSeen{ID: "plan-2", State: walkDone}
}
case EvRewalk:
d.MergedAs, d.AnsweredBy = "aaaa1111", "bbbb2222"
d.Walk = &WalkSeen{ID: "plan-2", State: walkFailed}
case EvLifted:
d.MergedAs, d.Lifted = "aaaa1111", "its group's composed check passed"
d.Walk = &WalkSeen{ID: "plan-1", State: walkBuilding, Waits: true, WaitedFor: "mesh-delivery"}
@@ -112,7 +120,7 @@ func TestEveryRowOfTheTableIsTakenWhenItsGuardHoldsAndRefusedWhenNot(t *testing.
func TestEveryPairTheTableDoesNotHoldIsRefusedByName(t *testing.T) {
events := []Event{EvAnnounced, EvAppeared, EvAdopted, EvChecked, EvAccepted, EvRefused, EvRecheck, EvReask, EvNewHead,
EvClosed, EvMerged, EvMergedUnchecked, EvGo, EvHold, EvRelease, EvDone, EvFailed, EvSuperseded, EvStop,
EvHistory, EvRetire, EvCovered, EvLifted}
EvHistory, EvRetire, EvCovered, EvLifted, EvRewalk}
for _, from := range append([]State{None}, AllStates...) {
for _, ev := range events {
for _, act := range []bool{false, true} {
@@ -150,8 +158,10 @@ func TestTheTableKeepsItsRules(t *testing.T) {
}
// The trunk rule: only a ready delivery is published, by its merge — or a held one published again when
// what held it is gone (issue 316); nothing off the trunk delivers.
// And a merge whose walk carried a later commit for it and failed waits there for a walk of its own (ADR 0276).
if r.To == Published && !(r.Event == EvMerged && len(r.From) == 1 && r.From[0] == Ready) &&
!(r.Event == EvLifted && len(r.From) == 1 && r.From[0] == Held) {
!(r.Event == EvLifted && len(r.From) == 1 && r.From[0] == Held) &&
!(r.Event == EvRewalk && len(r.From) == 1 && r.From[0] == Delivering) {
t.Errorf("%v —%s→ published: only a ready delivery's merge publishes", r.From, r.Event)
}
// Nothing a person did not decide ends a delivery as stopped or failed by observation from held.
@@ -33,10 +33,23 @@ func (h *Holder) waitsFor(d *Delivery) string {
case Proposed:
return "its check"
case Ready:
if b, batched := h.batchOf(d); batched {
return batchSaid(b, h.Now())
}
if d.MergedAs != "" {
return "a walk that answers its merge"
}
return "its merge"
case Rejected:
return "a new head, or a recheck"
case Published:
if d.waitsForItsOwnWalk() {
return "a walk of its own commit: its walk " + d.Walk.ID + ", which carried " + shortOf(d.AnsweredBy, 8) +
" for it, failed"
}
if d.Walk != nil && d.Walk.TakenOverBy != "" {
return "the walk " + d.Walk.TakenOverBy + ", which took its walk " + d.Walk.ID + " over"
}
if d.Group != "" {
return "its turn in group " + d.Group
}
@@ -154,12 +167,14 @@ func (h *Holder) Groups(all bool) []GroupLine {
type StalledLine struct {
ID string `json:"id"`
// Number is the pull request's, for a head the forge never announced: the condition names it (novox/hq issue 347).
Number int `json:"number,omitempty"`
State State `json:"state"`
For string `json:"for"`
Bound string `json:"bound"`
H2 string `json:"h2"`
Says string `json:"says"`
Number int `json:"number,omitempty"`
State State `json:"state"`
// Walk is the walk a line of the state Unheld is about (novox/hq issue 362).
Walk string `json:"walk,omitempty"`
For string `json:"for"`
Bound string `json:"bound"`
H2 string `json:"h2"`
Says string `json:"says"`
}
// Stalled is the `stalled` verb: what the controller's self-check raises as `delivery.<id>.stalled`.
@@ -172,7 +187,7 @@ func (h *Holder) Stalled() []StalledLine {
if !pastBound(d, now) {
continue
}
b := Bounds[d.State]
b, _ := boundOf(d)
h2 := "none: the state is the operator's"
switch {
case d.State == Proposed && d.CheckAsk == nil:
@@ -192,6 +207,7 @@ func (h *Holder) Stalled() []StalledLine {
Bound: b.For.String(), H2: h2, Says: b.Says})
}
out = append(out, h.uncheckedLines(now)...)
out = append(out, h.orphanLines(now)...)
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
return out
}
@@ -355,7 +371,7 @@ func (h *Holder) Close(id, why string) (string, error) {
h.mu.Unlock()
return "", fmt.Errorf("no delivery %q", id)
}
b, bounded := Bounds[d.State]
b, bounded := boundOf(d)
if !bounded || len(b.H2) == 0 {
h.mu.Unlock()
return "", fmt.Errorf("%s is %s: H2 has no transition there — the state is the operator's", id, d.State)