The checker decides a node the sandbox did not judge by the contract's field (composer.Machine.NotJudged, verdict not-judged) and names it with the composer's words, never by a phrase in its reason (ADR 0593 §9; the prepare's seams finding on "not judged")

This commit is contained in:
2026-11-03 16:44:47 +01:00
parent b342c67b19
commit 6b9d211f72
2 changed files with 32 additions and 1 deletions
@@ -147,6 +147,12 @@ func JudgeEngines(group, heads, composerName string, nodes []string, answers map
v.Warnings = append(v.Warnings, node+": "+delivery.ComposeNoAnswer)
continue
}
if a.Verdict == delivery.ComposeCandidateNotJudged {
// Composed in the sandbox and not judged (ADR 0593 §9): named as no answer, with the composer's words; decided by the
// verdict, never by the words (ADR 0601 §4's kind).
v.Warnings = append(v.Warnings, node+": "+delivery.ComposeNoAnswer+" ("+a.Refused+")")
continue
}
v.Machines = append(v.Machines, node)
if a.Verdict != delivery.ComposeCandidateRefused {
continue
@@ -221,8 +227,11 @@ func (e EngineCompositions) inSandbox(ctx context.Context, c *Case, head, node s
return a, fmt.Errorf("the composer at the node-engine's head %s did not answer for %s: %w", head, node, err)
}
out := delivery.ComposeCandidateAnswer{V: a.V, Node: node, Candidate: a.Candidate, Verdict: delivery.ComposeCandidateComposed}
notJudged := ""
for _, cv := range verdict.Cases {
if m, ok := cv.Machines[node]; ok && !m.Composes {
if m, ok := cv.Machines[node]; ok && m.NotJudged != "" {
notJudged = m.NotJudged // the composer did not judge the node: neither composed nor refused (ADR 0593 §9)
} else if ok && !m.Composes {
out.Verdict = delivery.ComposeCandidateRefused
out.Refused = strings.Join(append(append([]string{m.Reason}, m.Problems...), m.Refusals...), "; ")
}
@@ -234,5 +243,8 @@ func (e EngineCompositions) inSandbox(ctx context.Context, c *Case, head, node s
}
}
sort.Slice(out.Modules, func(i, j int) bool { return out.Modules[i].Module < out.Modules[j].Module })
if notJudged != "" && out.Verdict == delivery.ComposeCandidateComposed {
out.Verdict, out.Refused = delivery.ComposeCandidateNotJudged, notJudged
}
return out, nil
}
@@ -168,6 +168,25 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) {
if v.State != delivery.CompositionFail || v.SDK != head2 || !strings.Contains(v.Words, "the new composer refuses it") {
t.Errorf("verdict = %+v", v)
}
// ADR 0593 §9 (ADR 0601 §4's kind): a node the composer did not judge is named as no answer with its words, by the
// contract's field, and the check passes; words in Reason alone are no such mark.
sb.err = nil
for _, row := range []struct {
m composer.Machine
pass bool
}{
{composer.Machine{NotJudged: "its node-engine sends no running state for web"}, true},
{composer.Machine{Reason: "not judged: its node-engine sends no running state for web"}, false},
} {
sb.v = func(node string) composer.Verdict {
return composer.Verdict{V: composer.Version, Cases: []composer.CaseVerdict{{Name: "change", Machines: map[string]composer.Machine{node: row.m}}}}
}
v, err = ec.Compose(context.Background(), host)
named := len(v.Warnings) == 1 && strings.Contains(v.Warnings[0], "anchor: "+delivery.ComposeNoAnswer+" (its node-engine sends no running state for web)")
if err != nil || (v.State == delivery.CompositionPass) != row.pass || named != row.pass {
t.Errorf("%+v: verdict %+v, %v", row.m, v, err)
}
}
// Review finding 3 (ADR 0593 §9 lets only a node that does not answer pass): a composer at the head that cannot
// run fails the composition, naming the head, never passing as a silent node.
sb.err = errors.New("the build of mesh-compose failed")