Judge a delivery group's composed check by the group's own controller, release branch included (issue 867, ADR 0239 §14)
mesh/merge-gate head 151faa10a13b merged into trunk a07dd78c3e90 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 151faa10a13b merged into trunk a07dd78c3e90 — pass: its merge-check.sh passed
mesh/delivery delivered

The composed check of mesh-controller#346 and mesh-host#114 into feat/57-config-and-provisioning was judged by the release branch's controller, which cannot build the node-engine member's validator, so the group could never pass. ADR 0239 §14 has the group's own controller judge when a member moves it; ADR 0358 §2 changes the judge only for a pull request into a release branch. A group member in mesh-controller now judges, with the mesh-host member's validator, built in the isolated judge environment and kept by content; the statuses say the group's controller judged.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-10-16 00:59:27 +00:00
co-authored by Claude
parent a07dd78c3e
commit 151faa10a1
3 changed files with 181 additions and 13 deletions
+50 -7
View File
@@ -771,11 +771,14 @@ func Check(ctx context.Context, run Runner, spec CheckSpec, workspace, registry
if spec.Judge == "self" {
judgedBy = tree
}
if siblingJudge.Dir != "" {
judgedBy = siblingJudge.Dir
}
judged.JudgedBy = headAt(ctx, run, judgedBy)
if siblingJudge.Validator == "" {
judged.Validator = releaseValidator(spec, judged.BesideAt, judged.Beside)
}
said := releaseWords(spec.Release, judged.JudgedBy, judged.Validator, judged.BesideAt, judged.Beside)
said := releaseWords(spec.Release, whoJudges(siblingJudge), judged.JudgedBy, judged.Validator, judged.BesideAt, judged.Beside)
fmt.Fprintf(&out, "--- %s\n", said)
say("check", "%s", said)
}
@@ -846,7 +849,7 @@ func Check(ctx context.Context, run Runner, spec CheckSpec, workspace, registry
if spec.Release != "" {
// The statuses name the release branch and the judge first, so the forge's short description keeps them.
mark := releaseMark(spec.Release, judged.JudgedBy)
mark := releaseMark(spec.Release, whoJudges(siblingJudge), judged.JudgedBy)
v.Gate.Summary, v.Repo.Summary = mark+v.Gate.Summary, mark+v.Repo.Summary
}
// What the check printed travels in the verdict to the forge and the controller: redacted as the log is.
@@ -1586,21 +1589,30 @@ func releaseValidator(spec CheckSpec, at, commits map[string]string) string {
}
// releaseWords is how a release-branch check says what judged it and what it read (novox/hq ADR 0358 §4).
func releaseWords(release, judgedBy, validator string, at, commits map[string]string) string {
func releaseWords(release, controller, judgedBy, validator string, at, commits map[string]string) string {
var read []string
for _, dir := range sortedKeys(at) {
read = append(read, fmt.Sprintf("%s %s at %s", dir, refWords(at[dir]), commits[dir]))
}
said := fmt.Sprintf("judged for the release branch %s by its controller at %s, with %s", release, judgedBy, validator)
said := fmt.Sprintf("judged for the release branch %s by %s at %s, with %s", release, controller, judgedBy, validator)
if len(read) > 0 {
said += "; read beside it: " + strings.Join(read, ", ")
}
return said
}
// whoJudges says which controller judged: the release branch's own ("its controller"), or — when a delivery group's
// member in mesh-controller judged (novox/hq ADR 0239 §14) — the group's.
func whoJudges(j siblingJudgement) string {
if j.Dir != "" {
return "the group's controller"
}
return "its controller"
}
// releaseMark opens each layer's summary of a release-branch check, so a status says it first.
func releaseMark(release, judgedBy string) string {
return fmt.Sprintf("[release %s, judged by its controller %s] ", release, judgedBy)
func releaseMark(release, controller, judgedBy string) string {
return fmt.Sprintf("[release %s, judged by %s %s] ", release, controller, judgedBy)
}
// refWords is a ref as a line says it: a commit by its first eight characters, a branch whole.
@@ -1949,6 +1961,22 @@ func lastLine(s string) string {
// the controller it was built from and Validator the node-engine validator in it. Both empty for any other judge.
type siblingJudgement struct {
From, Validator string
// Dir is the checkout of the controller that judges.
Dir string
}
// groupDir is the checkout of the group's member in a repository of that name — the checked one (tree) or one cloned
// beside it for the group — empty when no member is in it.
func groupDir(spec CheckSpec, root, tree, repo string) string {
if spec.Repo == repo {
return tree
}
for _, g := range spec.Group {
if g.Repo == repo && safeName.MatchString(g.Repo) {
return filepath.Join(root, "group-"+g.Repo)
}
}
return ""
}
// judgeFromTheGroup builds the judge of a member that has siblings (novox/hq ADR 0382 §3); done says it did (or failed
@@ -1974,6 +2002,21 @@ func judgeFromTheGroup(ctx context.Context, labelled, run Runner, spec CheckSpec
return fmt.Sprintf("sibling %s#%d merge %s", s.Repository, s.Number, judged.Beside[dir])
}
switch {
case len(spec.Group) > 0 && groupDir(spec, root, tree, "mesh-controller") != "":
// **A delivery group's composed check is judged by the group's own controller** (novox/hq ADR 0239 §14), whatever
// base it merges into — a release branch included (ADR 0358 §2 changes the judge of one pull request, not of a
// group): the member that moves mesh-controller, as merged, with the validator of the group's mesh-host member's
// merge when it has one, else the one that controller vendors.
source, validatorDir = groupDir(spec, root, tree, "mesh-controller"), groupDir(spec, root, tree, "mesh-host")
from = "the group's own controller, merge " + headAt(ctx, run, source)
validator = "the validator the group's controller vendors"
if validatorDir != "" {
validator = "the group's mesh-host merge " + headAt(ctx, run, validatorDir) + "'s validator"
}
inputs = append(inputs, "group member "+spec.Repo+" "+judged.Head+" onto "+judged.Trunk)
for _, m := range judged.Members {
inputs = append(inputs, "group member "+m.Repo+" "+m.Head+" onto "+m.Trunk)
}
case spec.Judge == "self" && host != nil:
source, validatorDir = tree, filepath.Join(root, "mesh-host")
from = "this change's own merge " + judged.Head
@@ -2004,7 +2047,7 @@ func judgeFromTheGroup(ctx context.Context, labelled, run Runner, spec CheckSpec
sum := sha256.Sum256([]byte(strings.Join(inputs, "\n")))
key := "siblings-" + hex.EncodeToString(sum[:16])
bin := filepath.Join(root, "bin", "judge-of-the-group")
said.From, said.Validator = from, validator
said.From, said.Validator, said.Dir = from, validator, source
if judgeKept(keptJudges(spec.JudgeCache), key, bin) {
say("check", "judged by %s, with %s, built for an earlier check of the same inputs", from, validator)
return bin, "", true, nil