Delivery gate: a placement change is no pull request path, so the ask drops its placement wording and case

This commit is contained in:
2026-10-28 17:58:22 +01:00
parent b5aa226649
commit 4e3b140d43
3 changed files with 13 additions and 28 deletions
@@ -59,7 +59,7 @@ type checkerVerdict struct {
Words string `json:"words,omitempty"`
} `json:"failed,omitzero"`
// Composition is what the check of the machines' composition made of it (mesh-sdk delivery.Composition): its state
// not-run says the composition, and with it a placement change's answers from its nodes, was not judged.
// not-run says the composition was not judged.
Composition *struct {
State string `json:"state"`
Words string `json:"words,omitempty"`
@@ -341,7 +341,7 @@ func (h *Holder) checkAt(members []*Delivery) checkSaid {
if ok {
switch v.State {
case checkerPass:
return checkSaid{Key: checkSaidPass, Words: "passed its check" + notComposed(v, members)}
return checkSaid{Key: checkSaidPass, Words: "passed its check" + notComposed(v)}
case checkerRunning:
words := "its check has no verdict yet: it is running"
if since, held := h.runningSince[hash]; held && !since.IsZero() {
@@ -384,20 +384,13 @@ func (h *Holder) checkAt(members []*Delivery) checkSaid {
return checkSaid{Key: checkSaidNone, Words: "its check has no verdict yet"}
}
// notComposed is what a passing verdict adds when its composition was not judged (mesh-sdk delivery.CompositionNotRun): a
// placement change's pass then holds no node's answer to its placement (ADR 0559 §5), which the ask says. Empty otherwise.
func notComposed(v checkerVerdict, members []*Delivery) string {
// notComposed is what a passing verdict adds when its composition was not judged (mesh-sdk delivery.CompositionNotRun).
// Empty otherwise.
func notComposed(v checkerVerdict) string {
if v.Composition == nil || v.Composition.State != sdk.CompositionNotRun {
return ""
}
why := firstOf(oneLine(v.Composition.Words), "it was not run")
for _, m := range members {
for _, p := range m.Paths {
if strings.HasPrefix(p, "placement/") && strings.HasSuffix(p, ".json") {
return ", but its composition was not judged (" + why + "), so no node answered its placement"
}
}
}
return ", but its composition was not judged (" + why + ")"
}
@@ -465,7 +465,7 @@ const waitsCheckBeforeAsking = "waits its check before asking the operator's App
// has no verdict (the operator, 2026-10-27 23:54: "if you don't want me to approve it, don't send the approval request").
// A pass or a failure is asked (a failure in its words: the operator may still merge it), and so is a check that has not
// answered within askCheckBound of the heads becoming ready, which the ask says. For a group the check is the one of all its
// heads together; for a placement change it ends only once every node it places answered (ADR 0559 §5). Under the lock.
// heads together. Under the lock.
func (h *Holder) verdictWaits(d *Delivery) string {
members := h.askMembers(d)
c := h.checkAt(members)
@@ -75,12 +75,11 @@ func TestAnApprovedPullRequestIsMergedThroughTheMergeTool(t *testing.T) {
// past which it is asked anyway, saying so, and asked again in new words once the check passes. The verdict is the
// checker's verdicts bucket value as captured on the bus (mesh-delivery-checker, `<group>.<heads hash>`).
for _, row := range []struct {
name, state, failed, then, path string
gated bool
notComposed bool
after time.Duration
asks, open int
want, not []string
name, state, failed, then string
gated bool
after time.Duration
asks, open int
want, not []string
}{
{name: "gated, passed", gated: true, state: "pass", asks: 1,
want: []string{"app#7 Fix it passed its check. Approve merges it into main at " + readyHead[:12] + ";"},
@@ -100,9 +99,6 @@ func TestAnApprovedPullRequestIsMergedThroughTheMergeTool(t *testing.T) {
want: []string{waitsCheckBeforeAsking + "its check has no verdict yet: it is running (since 15:00 CEST); asked anyway at 16:00 CEST"}},
{name: "ungated, passed, its verdict gone past its bound", state: "pass", after: askCheckBound, then: "gone", asks: 1, open: -1,
want: []string{waitsCheckBeforeAsking + "its check has no verdict yet; asked anyway at 16:00 CEST"}},
// A placement change's pass with its composition not judged holds no node's answer, and the ask says so (ADR 0559 §5).
{name: "ungated, placement passed uncomposed", state: "pass", path: "placement/node-a.json", notComposed: true, asks: 1,
want: []string{"passed its check, but its composition was not judged (the assigner is not running), so no node answered its placement"}},
{name: "ungated, failed", state: "fail", failed: "TestThing: wanted 1, got 2", asks: 1,
want: []string{"its check failed: TestThing: wanted 1, got 2", "although its check failed"}, not: []string{"passed", "anyway"}},
} {
@@ -112,7 +108,7 @@ func TestAnApprovedPullRequestIsMergedThroughTheMergeTool(t *testing.T) {
ungated(v)
}
v.op.auto = false
p := pr("novox/app", 7, readyHead, "feat/x", firstOf(row.path, "src/a.go"))
p := pr("novox/app", 7, readyHead, "feat/x", "src/a.go")
p.Title = "Fix it"
v.h.PullUpdated(p)
head := headOf("novox/app", 7, readyHead)
@@ -121,11 +117,7 @@ func TestAnApprovedPullRequestIsMergedThroughTheMergeTool(t *testing.T) {
}
v.settleAll()
if !row.gated && row.state != "" {
var more map[string]any
if row.notComposed {
more = map[string]any{"composition": map[string]string{"state": "not-run", "words": "the assigner is not running"}}
}
checkerSaysWith(v, "g", row.state, row.failed, more, head)
checkerSaysWith(v, "g", row.state, row.failed, nil, head)
v.settleAll()
}
if row.after > 0 {