From 3496b58f660fb6157d76e20914134ee1f9b4e8cd Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 10 Oct 2026 14:47:11 +0200 Subject: [PATCH] A walk let go behind another reads as a wait: its note on the line, never late, and no tier of it watched (hq ADR 0276 review of #200) --- cmd/mesh-controller/batches_test.go | 14 ++++++++++++++ cmd/mesh-controller/release_plan.go | 27 +++++++++++++++------------ cmd/mesh-controller/watchdogs.go | 5 +++++ 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/cmd/mesh-controller/batches_test.go b/cmd/mesh-controller/batches_test.go index c7cada52..846804ca 100644 --- a/cmd/mesh-controller/batches_test.go +++ b/cmd/mesh-controller/batches_test.go @@ -624,6 +624,20 @@ func TestAWalkLetGoBesideAStartedOneStartsOnceItEnded(t *testing.T) { if len(*asked) != before || !strings.Contains(got.Note, "starts once "+started[0].ID) { t.Fatalf("a walk let go beside a started one asked (%d → %d) or does not say it waits: %q", before, len(*asked), got.Note) } + // Read as a wait, an hour on: its note on the line, never LATE, and no tier of it late for S3. + later := time.Now().Add(time.Hour) + if line := planLine(got, later); !strings.Contains(line, "starts once "+started[0].ID) || strings.Contains(line, "LATE") { + t.Fatalf("a deferred walk reads %q", line) + } + facts, _, err := gatherPlans(ctx, open.inventory, later, nil) + if err != nil { + t.Fatal(err) + } + for _, f := range facts { + if f.id == got.ID { + t.Fatalf("a deferred walk is watched as a tier running late: %+v", f) + } + } done := started[0] done.State = inventory.PlanDone if err := open.inventory.SavePlan(ctx, &done); err != nil { diff --git a/cmd/mesh-controller/release_plan.go b/cmd/mesh-controller/release_plan.go index 6717cf11..45aaa0fd 100644 --- a/cmd/mesh-controller/release_plan.go +++ b/cmd/mesh-controller/release_plan.go @@ -448,7 +448,7 @@ func planBuilt(ctx context.Context, open *stores, module, commit, failed string, // 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) { +func startedBeside(ctx context.Context, inv *inventory.Inventory, id string, created time.Time) (string, error) { plans, err := inv.OpenPlans(ctx) if err != nil { return "", err @@ -458,13 +458,21 @@ func startedBeside(ctx context.Context, inv *inventory.Inventory, id string) (st 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)) { + if q.Tier > 0 || askedAny(q) || q.Created.Before(created) { return q.ID, nil } } return "", nil } +// deferred says a walk has its word and has not started: let go while another walk was started, it waits for +// that one to end (startedBeside). Read as a wait, not as a tier running late: `plans` and `status` say its +// note, and S3 leaves it out. +func deferred(p inventory.Plan) bool { + return p.Open() && p.Release == nil && p.Tier == 0 && !askedAny(p) && p.Delivery != nil && + p.Delivery.Awaits != "" && p.Delivery.Go != nil +} + // askedAny says a plan asked any module. func askedAny(p inventory.Plan) bool { for _, s := range p.Modules { @@ -475,15 +483,6 @@ func askedAny(p inventory.Plan) bool { 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. @@ -619,7 +618,7 @@ func advanceOnce(ctx context.Context, open *stores, p *inventory.Plan, // **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 { + if behind, err := startedBeside(ctx, inv, p.ID, p.Created); err != nil { return false, err } else if behind != "" { note := fmt.Sprintf("let go; starts once %s ended — one walk at a time", behind) @@ -1257,6 +1256,10 @@ func planLineOn(p inventory.Plan, now time.Time, pause pauseView, bound time.Dur // 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) } + if deferred(p) { + // Nor is waiting for the walk before it to end (one walk at a time, novox/hq ADR 0276). + return fmt.Sprintf("%s · %s, %s, for %s", name, where, p.Note, since) + } if waiting, paused := pausedWaiting(p, pause, now); paused { return fmt.Sprintf("%s · %s, %s", name, where, waiting) } diff --git a/cmd/mesh-controller/watchdogs.go b/cmd/mesh-controller/watchdogs.go index 3822f62b..86d9f710 100644 --- a/cmd/mesh-controller/watchdogs.go +++ b/cmd/mesh-controller/watchdogs.go @@ -490,6 +490,11 @@ func gatherPlans(ctx context.Context, inv *inventory.Inventory, now time.Time, b awaits: p.Delivery.Awaits, since: p.Created, modules: planModules(p), merges: p.Delivery.Merges}) continue } + // A walk let go and waiting for the walk before it to end (ADR 0276) is no tier late either: the walk + // before it is the one S3 watches. + if deferred(p) { + continue + } _, paused := pausedWaiting(p, pause, now) bound := bounds.of(p.Repository) out = append(out, planFacts{id: p.ID, repository: p.Repository, commit: p.Commit, tier: p.Tier,