From 9902a3f16cafce8789ac7b43f1a5f438dc96219c Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 21 Oct 2026 21:57:30 +0200 Subject: [PATCH] Name every group member's trunk in the composed verdict, the announced one when the seat names none (issue 1106) --- cmd/mesh-controller/checks.go | 16 ++++++++++++++-- cmd/mesh-controller/checks_test.go | 25 +++++++++++++++++++++++++ cmd/mesh-controller/delivery.go | 2 +- internal/link/build.go | 4 ++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/cmd/mesh-controller/checks.go b/cmd/mesh-controller/checks.go index 007b4adb..048f9a0d 100644 --- a/cmd/mesh-controller/checks.go +++ b/cmd/mesh-controller/checks.go @@ -594,11 +594,13 @@ func checkedOf(result link.BuildResult) link.Checked { // A delivery group's composed check (novox/hq ADR 0239): every head it judged, this one first. if g := result.Checked; g.Group != "" { c.Group = g.Group + // Every member is named with its trunk commit (novox/hq issue 1106): the seat's word, else the one the forge + // announced with the head, so the forge's merge tool never meets a member whose verdict names none. c.Members = append(c.Members, link.CheckedMember{Owner: g.Owner, Repo: g.Repo, Number: g.Number, - Commit: result.Ref, Trunk: c.Trunk}) + Commit: result.Ref, Trunk: firstTrunk(c.Trunk, g.Trunk)}) for _, m := range g.Members { c.Members = append(c.Members, link.CheckedMember{Owner: m.Owner, Repo: m.Repo, Number: m.Number, - Commit: m.Ref, Trunk: memberTrunk(result.Check, m)}) + Commit: m.Ref, Trunk: firstTrunk(memberTrunk(result.Check, m), m.Trunk)}) } } } @@ -627,6 +629,16 @@ func checkedOf(result link.BuildResult) link.Checked { return c } +// firstTrunk is the first trunk commit named, as twelve characters; empty when none is. +func firstTrunk(named ...string) string { + for _, n := range named { + if n != "" { + return twelve(n) + } + } + return "" +} + // memberTrunk is the trunk commit the build seat merged one of a group's heads into (novox/hq ADR 0323 §6), empty // when it said none. func memberTrunk(outcome *link.CheckOutcome, m link.GroupMember) string { diff --git a/cmd/mesh-controller/checks_test.go b/cmd/mesh-controller/checks_test.go index 27bc6f8f..d7e821eb 100644 --- a/cmd/mesh-controller/checks_test.go +++ b/cmd/mesh-controller/checks_test.go @@ -250,6 +250,31 @@ func TestAVerdictNamesTheTrunkCommitItJudged(t *testing.T) { } } +// **A group's composed verdict names every member's trunk commit** (novox/hq issue 1106): a member the build seat named no +// trunk for (an SDK member whose base the seat did not merge, or a seat from before the rule) is named by the trunk the forge +// announced with its head, and the primary the same, so the merge tool never reads "names no trunk commit" on a member +// whose trunk did not move. +func TestAGroupVerdictNamesEveryMembersTrunkTheSDKMemberToo(t *testing.T) { + group := &link.CheckRequest{Owner: "novox", Repo: "mesh-catalog", Number: 7, Base: "main", Group: "feat/x", + Trunk: "dddddddddddd0000", + Members: []link.GroupMember{ + {Owner: "novox", Repo: "mesh-sdk", Number: 8, Ref: "cccccccccccc0000", Base: "main", Trunk: "eeeeeeeeeeee0000"}, + {Owner: "novox", Repo: "mesh-host", Number: 9, Ref: "bbbbbbbbbbbb0000", Base: "main", Trunk: "ffffffffffff0000"}}} + c := checkedOf(link.BuildResult{ID: "g", Ref: "aaaaaaaaaaaa0000", Checked: group, Check: &link.CheckOutcome{ + Verdict: "pass", Head: "aaaaaaaaaaaa", Gate: &link.CheckLayer{Verdict: "pass"}, + Members: []link.CheckedMember{{Owner: "novox", Repo: "mesh-host", Commit: "bbbbbbbbbbbb", Trunk: "222222222222"}}}}) + want := []string{"dddddddddddd", "eeeeeeeeeeee", "222222222222"} + if len(c.Members) != 3 { + t.Fatalf("the composed check names its members %+v", c.Members) + } + for i, m := range c.Members { + if m.Trunk != want[i] { + t.Fatalf("member %d (%s/%s) is named with trunk %q, want %q (the seat's word first, else the announced one): %+v", + i, m.Owner, m.Repo, m.Trunk, want[i], c.Members) + } + } +} + // **The merge made for a check is never registered** (novox/hq ADR 0323 §2): a build of it — a commit on no branch // of the forge — is refused, naming the trunk. The rule of ADR 0238 §1 stands unchanged; this holds it for the // merge commit a check makes. diff --git a/cmd/mesh-controller/delivery.go b/cmd/mesh-controller/delivery.go index e8d35fd8..3d548ffa 100644 --- a/cmd/mesh-controller/delivery.go +++ b/cmd/mesh-controller/delivery.go @@ -1204,7 +1204,7 @@ func askGroupCheck(ctx context.Context, open *stores, group string, members []or return "", fmt.Errorf("%s cannot be cloned for the group's check: %w", m.ID, err) } request.Check.Members = append(request.Check.Members, link.GroupMember{Owner: mp.Owner, Repo: mp.Repo, - Number: mp.Number, Repository: url, Ref: mp.Commit, Paths: mp.Paths, Base: mp.Base}) + Number: mp.Number, Repository: url, Ref: mp.Commit, Paths: mp.Paths, Base: mp.Base, Trunk: mp.BaseSHA}) } seat := buildSeatHeld(ctx) ask, err := askOverOn(seat) diff --git a/internal/link/build.go b/internal/link/build.go index 9405c532..f06b86fd 100644 --- a/internal/link/build.go +++ b/internal/link/build.go @@ -292,6 +292,10 @@ type GroupMember struct { // Base is the branch it merges into: the build seat merges the head into that trunk's head before the // group is composed (novox/hq ADR 0323 §6). A build seat from before it composes the head alone. Base string `json:"base,omitempty"` + // Trunk is the base branch's head the forge announced with the head (novox/hq issue 1106): the trunk commit the + // composed verdict names for this member when the build seat named none, so a member whose trunk did not move is + // never merged-refused for a verdict that names no trunk. + Trunk string `json:"trunk,omitempty"` } // Who judges a merge check's gate.