From a5ba47e23c35acd4072644717d048e0dd1d1a985 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 17 Oct 2026 00:45:00 +0200 Subject: [PATCH] Name the open walk a change is related to, own path related to all (issue 973) --- cmd/mesh-controller/related.go | 32 +++++++++++++++++++- cmd/mesh-controller/related_test.go | 47 +++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/cmd/mesh-controller/related.go b/cmd/mesh-controller/related.go index fdaefed0..1d9858aa 100644 --- a/cmd/mesh-controller/related.go +++ b/cmd/mesh-controller/related.go @@ -11,7 +11,9 @@ import ( // related when they share a repository, a module, a delivery group's head, or an edge of the module graph // between their modules. A module is every one the walk builds or sends: the modules its merges moved and the // modules that depend on them, not only the ones the merges touched. Unrelated changes may walk beside each -// other (ADR 0304 decision 2, ADR 0282 decision 3); nothing walks so yet, and nothing but tests reads this. +// other (ADR 0304 decision 2, ADR 0282 decision 3); a closed batch is cut beside an open walk it is +// not related to, and a walk about to ask its first tier waits only for a started walk it is related to (issue 973, +// ADR 0407 decision 4). // footprint is what one change reaches: its repositories (lower case), its modules, and its delivery group // heads (the head branch names of its merges). unknown says a merge of it was kept without the modules it @@ -109,3 +111,31 @@ func firstShared(a, b map[string]bool) string { sort.Strings(both) return both[0] } + +// openWalk is a walk open beside a change: its id, what it reaches, and whether it is on the controller's own path. +type openWalk struct { + id string + fp footprint + own bool +} + +// relatedOpen is the first open walk a change is related to, with the reason; empty when it is related to none +// and may walk beside them all (issue 973, ADR 0407 decision 4, ADR 0306 decision 3). +// +// **The controller's own path is related to everything** (decided for issue 973): a walk or batch that replaces +// the controller, the node-engine, the tool runner, the bus or mesh-delivery never runs beside another, and +// nothing runs beside it, whatever the footprints say. +func relatedOpen(cand footprint, candOwn bool, open []openWalk, edges []inventory.Edge) (behind, why string) { + for _, w := range open { + if candOwn { + return w.id, "it is on the controller's own path" + } + if w.own { + return w.id, w.id + " is on the controller's own path" + } + if ok, why := related(cand, w.fp, edges); ok { + return w.id, why + } + } + return "", "" +} diff --git a/cmd/mesh-controller/related_test.go b/cmd/mesh-controller/related_test.go index 764df1d3..41593367 100644 --- a/cmd/mesh-controller/related_test.go +++ b/cmd/mesh-controller/related_test.go @@ -108,3 +108,50 @@ func TestABatchWhoseMovesAreNotKnownIsRelatedToEverything(t *testing.T) { t.Fatalf("a known, unrelated batch read as related: %q", why) } } + +// TestRelatedOpenNamesTheFirstOpenWalkAChangeIsRelatedTo: a change that shares nothing with any open walk is +// held behind none (issue 973, ADR 0407 decision 4); one that shares a repository, a module, a graph edge or a +// group head is held behind the first walk it shares it with, with the reason; one whose moves are not known +// is held behind the first; a walk or change on the controller's own path is related to every other. +func TestRelatedOpenNamesTheFirstOpenWalkAChangeIsRelatedTo(t *testing.T) { + edges := []inventory.Edge{{From: "nzbget", To: "base-image", Kind: inventory.EdgeStandsOn}} + walk := func(id, repo string, own bool, modules ...string) openWalk { + return openWalk{id: id, fp: footprintOfWalk(walkOf(repo, modules...), nil), own: own} + } + one, two := walk("plan-1", "novox/one", false, "notes"), walk("plan-2", "novox/two", false, "nzbget") + cand := func(repo, head string, moves ...string) footprint { + return footprintOfBatch([]inventory.BatchedMerge{heardMerge(repo, head, moves...)}, edges) + } + cases := []struct { + name string + fp footprint + own bool + open []openWalk + behind string + why string + }{ + {"nothing open", cand("novox/x", "fix/a", "app"), false, nil, "", ""}, + {"unrelated to every open walk", cand("novox/x", "fix/a", "app"), false, []openWalk{one, two}, "", ""}, + {"a repository", cand("novox/ONE", "fix/a", "app"), false, []openWalk{two, one}, "plan-1", "both change novox/one"}, + {"a module through its dependents", cand("novox/x", "fix/a", "base-image"), false, []openWalk{one, two}, + "plan-2", "both move nzbget"}, + {"a graph edge", cand("novox/x", "fix/a", "nzbget"), + false, []openWalk{one, walk("plan-3", "novox/y", false, "base-image")}, "plan-3", + "nzbget depends on base-image (stands-on)"}, + {"moves not known", cand("novox/x", "fix/a"), false, []openWalk{one, two}, "plan-1", + "the modules a merge of novox/x moves are not known until its batch is cut"}, + {"the candidate on the own path", cand("novox/x", "fix/a", "app"), true, []openWalk{one}, "plan-1", + "it is on the controller's own path"}, + {"an open walk on the own path", cand("novox/x", "fix/a", "app"), false, + []openWalk{one, walk("plan-4", "novox/mesh-controller", true, "mesh-controller")}, "plan-4", + "plan-4 is on the controller's own path"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + behind, why := relatedOpen(c.fp, c.own, c.open, edges) + if behind != c.behind || why != c.why { + t.Errorf("relatedOpen = %q, %q; want %q, %q", behind, why, c.behind, c.why) + } + }) + } +}