Name the hold a waiting walk waits on in plans (issue 529)
mesh/delivery-group group fix/529-a-waiting-walk-is-answered-or-named checking: 0 of 2 member(s) ready
mesh/merge-gate pass: builds mesh-controller → 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
mesh/delivery superseded: a newer head of the same pull request

A walk whose delivery was held for a person said only that it waits for
mesh-delivery's word and that plans go starts it by hand, pointing the
reader at a hand start instead of the person's decision. Ask mesh-delivery
what holds each waiting walk and say the held delivery, its reason and
mesh-delivery.release, or the delivery not at its turn.
This commit is contained in:
2026-10-12 11:27:05 +02:00
parent da1473e9db
commit ed037784e2
4 changed files with 177 additions and 4 deletions
+39 -4
View File
@@ -11,6 +11,8 @@ import (
"strings"
"time"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/inventory"
"github.com/novox/mesh-controller/internal/link"
)
@@ -1258,6 +1260,13 @@ func planLineWith(p inventory.Plan, now time.Time, pause pauseView, bound time.D
// planLineOn is planLineWith naming the plan by what it moves and where (planHeadline), given what runs each
// module; nil names no machine.
func planLineOn(p inventory.Plan, now time.Time, pause pauseView, bound time.Duration, running func(string) []string) string {
return planLineSaid(p, now, pause, bound, running, nil)
}
// planLineSaid is planLineOn saying, of a walk waiting for its delivery's word, what the delivery's owner says
// holds it (novox/hq issue 529); nil waits says it as before.
func planLineSaid(p inventory.Plan, now time.Time, pause pauseView, bound time.Duration, running func(string) []string,
waits walkWaits) string {
name := planHeadline(p, running)
where := fmt.Sprintf("tier %d of %d", min(p.Tier+1, len(p.Tiers)), len(p.Tiers))
switch p.State {
@@ -1274,7 +1283,7 @@ func planLineOn(p inventory.Plan, now time.Time, pause pauseView, bound time.Dur
since := now.Sub(inTierSince(p)).Round(time.Second)
if p.Waiting() {
// Waiting for its delivery's word is no lateness of the walk's (novox/hq ADR 0239).
return fmt.Sprintf("%s · %s, %s, for %s", name, where, waitingNote(p), since)
return fmt.Sprintf("%s · %s, %s, for %s", name, where, waitingSaid(p, waits[p.ID]), since)
}
if deferred(p) {
// Nor is waiting for the walk before it to end (one walk at a time, novox/hq ADR 0276).
@@ -1479,11 +1488,11 @@ func plansCommand(ctx context.Context, args []string) error {
return err
}
bounds := readTierBounds(ctx, inv, now)
fmt.Printf("%s — %s\n", p.ID, planLineOn(p, now, buildSeatPause(ctx, inv, []inventory.Plan{p}),
fmt.Printf("%s — %s\n", p.ID, planLineSaid(p, now, buildSeatPause(ctx, inv, []inventory.Plan{p}),
bounds.of(p.Repository), func(module string) []string {
on, _ := inv.Running(ctx, module)
return on
}))
}, waitsOf(ctx, inv, []inventory.Plan{p})))
if r := p.Release; r != nil {
// A release plan's walk (ADR 0236): machines done, the one judged, those to come.
fmt.Printf(" machines in order: %s; done: %s; skipped: %s\n", strings.Join(r.Order, ", "),
@@ -1630,15 +1639,41 @@ func plansCommand(ctx context.Context, args []string) error {
on, _ := inv.Running(ctx, module)
return on
}
waits := waitsOf(ctx, inv, plans)
for _, p := range plans {
if p.Batch() {
continue
}
fmt.Printf("%-28s %s\n", p.ID, planLineOn(p, now, pause, bounds.of(p.Repository), running))
fmt.Printf("%-28s %s\n", p.ID, planLineSaid(p, now, pause, bounds.of(p.Repository), running, waits))
}
return nil
}
// waitsOf asks the delivery's owner what holds the walks among these that wait for its word (novox/hq issue 529),
// once, and only when one waits; nil when none waits, no holder is on record, or the bus or the owner does not
// answer within a few seconds — the lines then read as before.
func waitsOf(ctx context.Context, inv *inventory.Inventory, plans []inventory.Plan) walkWaits {
waiting := false
for _, p := range plans {
waiting = waiting || p.Open() && p.Waiting()
}
if !waiting {
return nil
}
entries, err := inv.Catalogued(ctx)
if err != nil {
return nil
}
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
var waits walkWaits
_ = onTheBus(func(conn *nats.Conn) error {
waits = walksWaiting(ctx, conn, deliverySeatHeld(entries))
return nil
})
return waits
}
// planWhatIf is the plan a merge would produce, computed the way the merge handler computes one
// and saved nowhere: the modules the repository's changed files touch (or the modules named), what
// packages their source, everything reachable from them, in tiers. For reading before merging.