Judge a pull request into a release branch by that branch's controller and repositories
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head 4a75cc51dda6 merged into trunk 535c7641ac41 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 4a75cc51dda6 merged into trunk 535c7641ac41 — pass: its merge-check.sh passed

A release branch could never carry a change that needs another repository's
release-branch change: its pull requests were judged by the running controller
and against the other repositories' main (hq issue 704, ADR 0358).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 12:48:44 +00:00
co-authored by Claude Opus 5.5
parent 535c7641ac
commit 4a75cc51dd
8 changed files with 924 additions and 35 deletions
+14 -3
View File
@@ -24,6 +24,7 @@ import (
"net/url"
"os"
"os/signal"
"path/filepath"
"sort"
"strings"
"syscall"
@@ -313,6 +314,9 @@ func answer(ctx context.Context, publisher builder.Publisher, on, workspace stri
registry = r.Address
}
spec := checkSpecOf(request)
// A release branch's judge is kept beside the workspace, never in it: the check's containers mount the
// workspace, and a pull request's script must not plant the judge of the next check (novox/hq ADR 0358 §2).
spec.JudgeCache = judgeCacheOf(workspace)
// What the merge would move, built by the check for the walk to take (novox/hq ADR 0350 §1): into the
// scratch namespace. **Never with the package registry's credential**: this is code nobody has approved
// yet, and a recipe could read the credential it is handed (issue 462). A module that resolves packages
@@ -326,7 +330,8 @@ func answer(ctx context.Context, publisher builder.Publisher, on, workspace stri
if err == nil {
result.Check = &link.CheckOutcome{Verdict: v.Verdict, Summary: v.Summary, Report: v.Report,
Took: v.Took.Round(time.Second).String(), Gate: layerOf(v.Gate), RepoCheck: layerOf(v.Repo),
Head: v.Head, Trunk: v.Trunk, Beside: v.Beside, Built: checkBuiltOf(v.Built)}
Head: v.Head, Trunk: v.Trunk, Beside: v.Beside, Built: checkBuiltOf(v.Built),
Release: v.Release, JudgedBy: v.JudgedBy, BesideAt: v.BesideAt}
// What each of a group's heads was judged as (novox/hq ADR 0323 §6).
for _, m := range v.Members {
result.Check.Members = append(result.Check.Members, link.CheckedMember{Owner: m.Owner, Repo: m.Repo,
@@ -456,9 +461,9 @@ func checkSpecOf(request link.BuildRequest) builder.CheckSpec {
Owner: c.Owner, Repo: c.Repo, Number: c.Number, Paths: c.Paths, Beside: map[string]builder.Beside{},
Modules: c.Modules, New: c.New, Manifests: c.Manifests, Judge: c.Judge, Base: c.Base,
Toolchain: builder.ToolchainOf(request.Held), Toolchains: builder.ToolchainsOf(request.Held),
Tests: c.Tests, StoreOnly: c.StoreOnly}
Tests: c.Tests, StoreOnly: c.StoreOnly, Release: c.Release}
for dir, b := range c.Beside {
spec.Beside[dir] = builder.Beside{Repository: b.Repository, Ref: b.Ref}
spec.Beside[dir] = builder.Beside{Repository: b.Repository, Ref: b.Ref, Else: b.Else}
}
for _, b := range c.Builds {
spec.Builds = append(spec.Builds, builder.CheckBuildSpec{Module: b.Module, Repository: b.Repository, Path: b.Path,
@@ -471,6 +476,12 @@ func checkSpecOf(request link.BuildRequest) builder.CheckSpec {
return spec
}
// judgeCacheOf is where a release branch's judge is kept (novox/hq ADR 0358 §2): beside the workspace, outside what
// a check's containers mount.
func judgeCacheOf(workspace string) string {
return filepath.Clean(workspace) + "-judges"
}
// triedOf is a try's outcome as its log line says it (novox/hq issue 686): the repository's own check's verdict,
// or the check's where that did not run — a head that does not merge — and the lines naming what failed.
func triedOf(v builder.CheckVerdict) link.Tried {
+26
View File
@@ -0,0 +1,26 @@
package main
import (
"path/filepath"
"strings"
"testing"
"github.com/novox/mesh-controller/internal/link"
)
// **A pull request into a release branch reaches the check as one** (novox/hq ADR 0358): the build seat hands the
// check the release branch and, beside it, the trunk each repository is read at when it has no branch of that name;
// and it keeps the release branch's judge outside the workspace a check's containers mount.
func TestTheBuildSeatHandsTheCheckItsReleaseBranch(t *testing.T) {
spec := checkSpecOf(link.BuildRequest{ID: "b", Repository: "r", Ref: "abc", Check: &link.CheckRequest{
Owner: "novox", Repo: "mesh-catalog", Base: "feat/57", Release: "feat/57",
Beside: map[string]link.CheckedOut{"mesh-host": {Repository: "h", Ref: "feat/57", Else: "main"}}}})
if spec.Release != "feat/57" || spec.Beside["mesh-host"].Ref != "feat/57" || spec.Beside["mesh-host"].Else != "main" {
t.Fatalf("the check was handed release %q and mesh-host %+v", spec.Release, spec.Beside["mesh-host"])
}
workspace := filepath.Join(t.TempDir(), "mesh-builder")
cache := judgeCacheOf(workspace)
if rel, err := filepath.Rel(workspace, cache); err != nil || !strings.HasPrefix(rel, "..") {
t.Fatalf("the judge cache %s lies inside the workspace %s, which a check's containers mount", cache, workspace)
}
}