An own-path batch is cut first and folds no waiting walk; a walk let go beside a started one starts once it ended; a late catalogue merge is not answered by a walk that waited for nobody (hq ADR 0276 review, tracker issue 377)
mesh/merge-gate pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without …
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

This commit is contained in:
jochen
2026-10-10 14:33:10 +02:00
parent b9ceeace41
commit 04fcce2fcc
6 changed files with 430 additions and 127 deletions
+124 -10
View File
@@ -446,6 +446,44 @@ func planBuilt(ctx context.Context, open *stores, module, commit, failed string,
advanceHeld(ctx, open)
}
// startedBeside is the id of a started walk open beside this one — a merge's walk past its wait, not the
// backlog's — or empty: one walk at a time (novox/hq ADR 0276).
func startedBeside(ctx context.Context, inv *inventory.Inventory, id string) (string, error) {
plans, err := inv.OpenPlans(ctx)
if err != nil {
return "", err
}
for _, q := range plans {
if q.ID == id || q.Release != nil || q.Waiting() {
continue
}
// Started: a tier asked, or on its way (let go, or waiting for nobody) before this one.
if q.Tier > 0 || askedAny(q) || q.Created.Before(createdOf(ctx, inv, id)) {
return q.ID, nil
}
}
return "", nil
}
// askedAny says a plan asked any module.
func askedAny(p inventory.Plan) bool {
for _, s := range p.Modules {
if s != nil && s.State != "" {
return true
}
}
return false
}
// createdOf is when a plan was made; zero when it cannot be read.
func createdOf(ctx context.Context, inv *inventory.Inventory, id string) time.Time {
p, err := inv.PlanByID(ctx, id)
if err != nil {
return time.Time{}
}
return p.Created
}
// advancePlans moves every open plan as far as the facts allow: a tier whose modules are all built
// and whose gates are applied gives way to the next; the last tier done is the plan done. Called
// after every outcome and on a timer, so a plan waiting on a machine's report moves when it comes.
@@ -578,6 +616,18 @@ func advanceOnce(ctx context.Context, open *stores, p *inventory.Plan,
}
}
if unasked == len(tier) {
// **One walk at a time** (novox/hq ADR 0276): a walk about to ask its first tier while another started
// walk is open waits for that one to end, let go or not, and says so.
if p.Tier == 0 {
if behind, err := startedBeside(ctx, inv, p.ID); err != nil {
return false, err
} else if behind != "" {
note := fmt.Sprintf("let go; starts once %s ended — one walk at a time", behind)
changed := p.Note != note
p.Note = note
return changed, nil
}
}
if err := askTier(ctx, inv, p); err != nil {
return false, err
}
@@ -1181,27 +1231,34 @@ func inTierSince(p inventory.Plan) time.Time {
// planLineWith is planLine knowing whether the build seat is paused (novox/hq ADR 0219): a plan
// waiting on builds nobody will take until a person resumes the seat says so, and is not late. bound is
// the plan's tier bound, the one its stalled condition is raised at (tierBounds): LATE is that condition
// said on the line (novox/hq issue 296).
// said on the line (novox/hq issue 296). The machines running what it moves are not named: planLineOn.
func planLineWith(p inventory.Plan, now time.Time, pause pauseView, bound time.Duration) string {
return planLineOn(p, now, pause, bound, nil)
}
// 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 {
name := planHeadline(p, running)
where := fmt.Sprintf("tier %d of %d", min(p.Tier+1, len(p.Tiers)), len(p.Tiers))
switch p.State {
case inventory.PlanAssembling, inventory.PlanQueued:
// A batch not yet a walk (novox/hq ADR 0276): what it holds and how long is left.
return batchWords(p, now)
case inventory.PlanDone:
return fmt.Sprintf("%s done, %d tier(s)", p.Named(), len(p.Tiers))
return fmt.Sprintf("%s · done, %d tier(s)", name, len(p.Tiers))
case inventory.PlanFailed:
return fmt.Sprintf("%s FAILED at %s: %s", p.Named(), where, p.Note)
return fmt.Sprintf("%s · FAILED at %s: %s", name, where, p.Note)
case inventory.PlanSuperseded:
return fmt.Sprintf("%s %s", p.Named(), p.Note)
return fmt.Sprintf("%s · %s", name, p.Note)
}
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", p.Named(), where, waitingNote(p), since)
return fmt.Sprintf("%s · %s, %s, for %s", name, where, waitingNote(p), since)
}
if waiting, paused := pausedWaiting(p, pause, now); paused {
return fmt.Sprintf("%s %s, %s", p.Named(), where, waiting)
return fmt.Sprintf("%s · %s, %s", name, where, waiting)
}
late := ""
if since > bound {
@@ -1211,7 +1268,53 @@ func planLineWith(p inventory.Plan, now time.Time, pause pauseView, bound time.D
if p.State == inventory.PlanRolling {
what = p.Note
}
return fmt.Sprintf("%s %s, %s for %s%s", p.Named(), where, what, since, late)
return fmt.Sprintf("%s · %s, %s for %s%s", name, where, what, since, late)
}
// planHeadline names a plan as a person knows it (asked by the operator, 2026-10-10: a plan called by its
// repository alone said nothing of what it delivers): each repository as its pull request and title, what the
// plan moves, and the machines running that — "mesh-catalog #175 a tap shows its outcome · messenger,
// telegram → novox". A record naming no pull request is named by its commit, as before; one naming no moves
// says none; running nil names no machine.
func planHeadline(p inventory.Plan, running func(string) []string) string {
var repos []string
moves := map[string]bool{}
for _, c := range p.Carried() {
seg := repoName(c.Repository) + " " + short(c.Commit)
if p.Delivery != nil {
for _, m := range p.Delivery.Merges {
if !strings.EqualFold(m.Repository, c.Repository) {
continue
}
for _, n := range m.Moves {
moves[n] = true
}
if m.Commit == c.Commit && m.Number > 0 {
seg = repoName(c.Repository) + " " + pullWords(m)
}
}
}
repos = append(repos, seg)
}
out := strings.Join(repos, " + ")
if len(moves) == 0 {
return out
}
names := sortedKeysOf(boolsToStrings(moves))
out += " · " + strings.Join(names, ", ")
if running == nil {
return out
}
nodes := map[string]bool{}
for _, n := range names {
for _, node := range running(n) {
nodes[node] = true
}
}
if len(nodes) > 0 {
out += " → " + strings.Join(sortedKeysOf(boolsToStrings(nodes)), ", ")
}
return out
}
// planFailedBuild marks the module a failed build was for when the result names no module: by the
@@ -1353,8 +1456,11 @@ func plansCommand(ctx context.Context, args []string) error {
return err
}
bounds := readTierBounds(ctx, inv, now)
fmt.Printf("%s — %s\n", p.ID, planLineWith(p, now, buildSeatPause(ctx, inv, []inventory.Plan{p}),
bounds.of(p.Repository)))
fmt.Printf("%s — %s\n", p.ID, planLineOn(p, now, buildSeatPause(ctx, inv, []inventory.Plan{p}),
bounds.of(p.Repository), func(module string) []string {
on, _ := inv.Running(ctx, module)
return on
}))
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, ", "),
@@ -1490,14 +1596,22 @@ func plansCommand(ctx context.Context, args []string) error {
}
for _, b := range batches {
fmt.Printf("%-28s %s\n", b.ID, batchWords(b, now))
// One line per repository: its pull request, what it answers, what it moves.
for _, line := range batchLines(b) {
fmt.Printf("%-28s %s\n", "", line)
}
}
pause := buildSeatPause(ctx, inv, plans)
bounds := readTierBounds(ctx, inv, now)
running := func(module string) []string {
on, _ := inv.Running(ctx, module)
return on
}
for _, p := range plans {
if p.Batch() {
continue
}
fmt.Printf("%-28s %s\n", p.ID, planLineWith(p, now, pause, bounds.of(p.Repository)))
fmt.Printf("%-28s %s\n", p.ID, planLineOn(p, now, pause, bounds.of(p.Repository), running))
}
return nil
}