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 delivered
mesh/delivery-group group fix/529-a-waiting-walk-is-answered-or-named delivered: every member is delivered
The review showed the give-the-word rule skipped a failed or missing check and group order, and let go moves nothing judged; to-be 47 has no such path. Such a walk is now said, by stalled with walks, as held by nothing, why.
143 lines
6.5 KiB
Go
143 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// novox/hq issue 362's note of 2026-10-11 (the nzbgeek case) and issue 529: mesh-media-catalog #18 added a module
|
|
// whose delivery plan said "sent nowhere". This owner ended its delivery at once, "nothing for a walk to move", but
|
|
// the controller opened a walk for the merge that moved modules to a machine and waited for this owner's word.
|
|
// With its delivery ended, nothing gave the walk its word: it held 39 minutes and two walks queued behind it.
|
|
|
|
const (
|
|
media = "novox/mesh-media-catalog"
|
|
headNzb = "abababab00001818"
|
|
mergeNzb = "cdcdcdcd00001818"
|
|
nzbWalkID = "plan-1791748193836841649"
|
|
)
|
|
|
|
var atNzb = time.Date(2026, 10, 11, 19, 43, 0, 0, time.UTC)
|
|
|
|
// nzbgeekMerged is #18 checked on a plan that builds a new module and sends it nowhere, and merged: its delivery
|
|
// ends at once, nothing for a walk to move.
|
|
func nzbgeekMerged(t *testing.T) *world {
|
|
t.Helper()
|
|
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atNzb.Add(-time.Minute))
|
|
p := pr(media, 18, headNzb, "feat/nzbgeek", "modules/nzbgeek/module.json")
|
|
w.h.PullUpdated(p)
|
|
v := verdict(media, 18, headNzb, "pass")
|
|
v.Plan = &DeliveryPlan{Repository: media, Base: "main", New: []string{"modules/nzbgeek"},
|
|
Summary: "builds new: modules/nzbgeek, sent nowhere; no bus step"}
|
|
w.h.Checked(v)
|
|
w.later(time.Minute)
|
|
w.h.PullMerged(mergedAt(p, mergeNzb, atNzb))
|
|
w.settleAll()
|
|
if d := w.delivery(IDOf(media, headNzb)); d.State != Delivered {
|
|
t.Fatalf("#18 is %s, not delivered as nothing for a walk to move", d.State)
|
|
}
|
|
return w
|
|
}
|
|
|
|
// nzbWalk is the walk the controller cut for the merge's batch: it names the merge and moves nzbgeek and nzbget.
|
|
func nzbWalk(created time.Time, merges ...WalkMerge) Walk {
|
|
w := Walk{ID: nzbWalkID, Repository: media, Branch: "main", Commit: mergeNzb, Created: created, State: walkBuilding,
|
|
Tiers: [][]string{{"nzbgeek", "nzbget"}}, Modules: map[string]*WalkModule{"nzbgeek": {}, "nzbget": {}},
|
|
Delivery: &WalkDelivery{Awaits: "mesh-delivery", Merges: merges}}
|
|
return w
|
|
}
|
|
|
|
// **A walk answering only merges that ended as nothing for a walk to move is named as held by nothing** (novox/hq
|
|
// issues 362 and 529): never let go by this owner — nothing judged what it moves — and said, by `stalled` with
|
|
// `walks`, with why, so the controller's `plans` says so instead of pointing nowhere.
|
|
func TestAWalkOfNothingToMoveMergesIsNamedAsHeldByNothing(t *testing.T) {
|
|
w := nzbgeekMerged(t)
|
|
w.later(90 * time.Second)
|
|
w.h.WalkMoved(w.ctl.put(nzbWalk(w.now, WalkMerge{Repository: media, Commit: mergeNzb})))
|
|
w.settleAll()
|
|
if len(w.ctl.delivers) != 0 {
|
|
t.Fatalf("a walk no live delivery holds was let go: %v", w.ctl.delivers)
|
|
}
|
|
lines := w.h.WalksWaiting()
|
|
if len(lines) != 1 || lines[0].Walk != nzbWalkID || lines[0].State != Unheld ||
|
|
!strings.Contains(lines[0].Says, "ended as nothing-to-move") || !strings.Contains(lines[0].Says, IDOf(media, headNzb)) ||
|
|
!strings.Contains(lines[0].Says, "nzbget") {
|
|
t.Fatalf("the walk is said as %+v", lines)
|
|
}
|
|
}
|
|
|
|
// **The status names each held delivery, why, and the verb that releases it** (novox/hq issue 529): "held: 1"
|
|
// alone pointed a reader nowhere.
|
|
func TestTheStatusNamesAHeldDeliveryAndWhy(t *testing.T) {
|
|
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atNzb)
|
|
p := pr(media, 22, headNzb, "adr-0307-state-history", "modules/nzbgeek/module.json")
|
|
w.h.PullUpdated(p)
|
|
w.h.PullMerged(mergedAt(p, mergeNzb, atNzb)) // merged before any verdict: held for a person
|
|
w.settleAll()
|
|
id := IDOf(media, headNzb)
|
|
d := w.delivery(id)
|
|
if d.State != Held {
|
|
t.Fatalf("#22 is %s", d.State)
|
|
}
|
|
held, _ := w.h.Status("listening")["held"].([]string)
|
|
if len(held) != 1 || !strings.Contains(held[0], id) || !strings.Contains(held[0], d.HeldWhy) ||
|
|
!strings.Contains(held[0], "mesh-delivery.release") {
|
|
t.Fatalf("the status says held %q", held)
|
|
}
|
|
}
|
|
|
|
// **A walk waiting for this owner's word is told why** (novox/hq issue 529): asked with `walks`, `stalled` says of
|
|
// each walk waiting for its word what holds it — a held delivery, naming it, its reason and `mesh-delivery.release`,
|
|
// or the deliveries not at their turn — so the controller's `plans` points at the person's decision, not at a hand
|
|
// start.
|
|
func TestAWaitingWalkIsToldWhatHoldsIt(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"))
|
|
w.h.PullMerged(mergedAt(cc, mergeCC, atCC))
|
|
w.h.PullMerged(mergedAt(du, mergeDu, atDu)) // unchecked: held for a person
|
|
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()
|
|
idDu := IDOf(cat, headDu)
|
|
lines := w.h.WalksWaiting()
|
|
if len(lines) != 1 || lines[0].Walk != "plan-9" || lines[0].State != Held || lines[0].ID != idDu ||
|
|
!strings.Contains(lines[0].Says, w.delivery(idDu).HeldWhy) || !strings.Contains(lines[0].Says, "mesh-delivery.release") {
|
|
t.Fatalf("the waiting walk is said as %+v", lines)
|
|
}
|
|
// Not by default: `stalled` stays what the self-check raises conditions for.
|
|
for _, l := range w.h.Stalled() {
|
|
if l.Walk == "plan-9" {
|
|
t.Fatalf("stalled lists the waiting walk without being asked: %+v", l)
|
|
}
|
|
}
|
|
// Released, its word is asked: nothing holds it any more.
|
|
if _, err := w.h.Release(idDu, "checked by hand", "jochen"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
w.h.WalkMoved(w.ctl.walk("plan-9"))
|
|
w.settleAll()
|
|
if lines := w.h.WalksWaiting(); len(lines) != 0 {
|
|
t.Fatalf("a walk let go is still said waiting: %+v", lines)
|
|
}
|
|
}
|
|
|
|
// A walk waiting while a delivery it carries is not at its turn says so, naming the delivery — what was said only
|
|
// in this owner's journal (novox/hq issue 529, note 2).
|
|
func TestAWaitingWalkSaysWhichDeliveryIsNotAtItsTurn(t *testing.T) {
|
|
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), atNzb)
|
|
w.h.mu.Lock()
|
|
w.h.waitsWhy = map[string]string{"plan-7": "novox/x@1 (published) carried by it not at its turn"}
|
|
w.h.walks["plan-7"] = seenWalk{walk: aWalk("plan-7", "novox/x", "1111", true), at: atNzb}
|
|
w.h.mu.Unlock()
|
|
lines := w.h.WalksWaiting()
|
|
if len(lines) != 1 || lines[0].Walk != "plan-7" || !strings.Contains(lines[0].Says, "not at its turn") {
|
|
t.Fatalf("the waiting walk is said as %+v", lines)
|
|
}
|
|
}
|