A trunk move asks at once the check of a group member whose Approve is not asked yet (issue 1047)

This commit is contained in:
2026-10-18 01:10:12 +02:00
parent 983e4a6444
commit 814c1a991c
3 changed files with 39 additions and 2 deletions
@@ -285,3 +285,35 @@ func TestARefusalLoopInAClosedGroupIsSaidPastTenMinutes(t *testing.T) {
t.Fatalf("the loop was not said, naming the member, its group and the tool's answer: %+v", lines)
}
}
// Issue 1047 (the live case of 2026-10-17 23:34, ADR 0395 §1, design 47): a group member that is ready when a third party's
// merge moves its trunk, with no Approve asked yet (the composed check has not passed), has its own check asked at once,
// and the Approve is asked once the group's composed check has passed on it.
func TestATrunkMoveAsksTheCheckOfAGroupMemberWhoseApproveIsNotAskedYet(t *testing.T) {
w := newWorld(t)
w.op.auto = false
w.h.PullUpdated(pr(gCtl, 1, gHc, "feat/g", "internal/a.go"))
w.h.PullUpdated(pr(gCat, 2, gHk, "feat/g", "modules/app/module.json"))
w.h.Checked(judged(gCtl, 1, gHc, "pass", gTrunk0))
w.h.Checked(judged(gCat, 2, gHk, "pass", gTrunk0))
w.settleAll()
if w.op.count() != 0 {
t.Fatalf("the composed check has not passed, yet the operator was asked: %d", w.op.count())
}
asked := len(w.ctl.checks)
w.h.PullMerged(merged(gCat, 5, "5555555555555555", gT1))
w.settleAll()
got := w.ctl.checks[asked:]
if len(got) != 1 || got[0] != ":"+IDOf(gCat, gHk) {
t.Fatalf("the trunk's move asked %v; wanted the moved member's own check once", got)
}
if last := w.ctl.baseSHAs[len(w.ctl.baseSHAs)-1]; last != gT1[:12] && last != gT1 {
t.Fatalf("its check was not asked against the trunk's head: %v", w.ctl.baseSHAs)
}
// Not asked again at the next look while it is on its way.
w.later(time.Second)
w.settleAll()
if len(w.ctl.checks[asked:]) != 1 {
t.Fatalf("asked twice: %v", w.ctl.checks[asked:])
}
}
@@ -616,7 +616,9 @@ func (h *Holder) PullMerged(p PullEvent) {
// trunkMoved takes a merge into a trunk as that trunk's move (novox/hq ADR 0323 §7): every other open delivery into
// it keeps the trunk's new head, and a ready one whose verdict names the commit before goes back to proposed. A
// group's composed check goes out of date when the merge is not one of its members and a member still open merges
// into that trunk (§6); the group then reads checking. Nothing is asked: a recheck or the merge tool judges again.
// into that trunk (§6); the group then reads checking. Nothing is asked here: at the same look askFresh asks the check of
// a group's member, or of a head with an Approve asked or given (issue 1047); a lone head's is judged again by a recheck
// or the merge tool (the rest of ADR 0395 §1 is issue 888).
func (h *Holder) trunkMoved(merged *Delivery, base, commit string, now time.Time) {
if base == "" || commit == "" {
return
@@ -245,7 +245,10 @@ func (h *Holder) askFresh(d *Delivery, now time.Time) func() {
d.Number == 0 || d.TrunkNow == "" {
return nil
}
if !d.FreshWanted && h.covering(d) == nil {
// A group's member too (issue 1047, design 47: a move of the trunk asks every check again): the group's one Approve is
// asked after its composed check, so a member ready before that has no Approve to carry; its check is asked at once
// all the same, and the composed check follows on the new verdict. The rest of ADR 0395 §1 stays with issue 888.
if !d.FreshWanted && h.covering(d) == nil && h.groups[d.Group] == nil {
return nil
}
if tried, was := h.askTried[d.ID]; was && now.Sub(tried) < time.Minute {