A verdict, composed check or status of the same heads that changes what a refusal waited on starts the count of repeated refusals again (milestone 47/G, W11.7, issue 1785)

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ

(Joined from 80d401c4.)
Builds: design 47 G (main file G row: issues 1785, 1794) (GATE)

Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
2026-11-11 14:45:50 +01:00
parent 6fe1bba067
commit 435d1206e4
4 changed files with 33 additions and 3 deletions
@@ -81,7 +81,7 @@ func (h *Holder) CommitStatus(e ForgeStatusEvent) {
head = sha
}
if sameCommit(head, e.SHA) {
h.mergeAgainNow(d) // its approved head's check turned success on the forge: the merge is asked again at once
h.causeChanged(d) // its approved head's check turned success on the forge: the merge is asked again at once, the count anew
}
}
}
@@ -224,3 +224,23 @@ func TestARefusalThatRepeatsWithNoTrunkMoveIsNotAskedAgainWithoutEnd(t *testing.
t.Fatal("a trunk's move did not let the merge be asked again")
}
}
// 1785: a verdict, a composed check or a status of the same heads that changes what the refusal waited on starts the count of
// the same words again, so an approved merge whose cause cleared is asked again without a second Approve; a status that is no
// success changes nothing.
func TestANewCauseOfTheSameHeadsStartsTheCountOfARepeatedRefusalAgain(t *testing.T) {
w := answeredFirst(t)
d := w.h.deliveries[IDOf(gCat, gHk)]
d.Ready.Repeats, d.Ready.RepeatTrunk = maxRepeatedRefusals, d.TrunkNow
d.Ready.LastTry = w.now
owner, repo, _ := strings.Cut(gCat, "/")
w.h.CommitStatus(ForgeStatusEvent{Owner: owner, Repo: repo, SHA: gHk, Context: "mesh/check", State: "pending"})
if d.Ready.Repeats != maxRepeatedRefusals || d.Ready.LastTry.IsZero() {
t.Fatalf("a status that is no success changed the refusal: %d repeats", d.Ready.Repeats)
}
w.h.CommitStatus(ForgeStatusEvent{Owner: owner, Repo: repo, SHA: gHk, Context: "mesh/check", State: "success"})
if d.Ready.Repeats != 0 || !d.Ready.LastTry.IsZero() {
t.Fatalf("the check turned success on the same head: %d repeats, last try %v; the merge is asked again and the count starts anew",
d.Ready.Repeats, d.Ready.LastTry)
}
}
@@ -185,7 +185,7 @@ func (h *Holder) takeCheckStatus(repository, sha, state, desc string) {
return // past its verdict: the merge reads the forge's status itself
}
d.Check, d.AskedAgain, d.CheckAsk = v, nil, nil
h.mergeAgainNow(d)
h.causeChanged(d)
if why := d.outOfDate(); why != "" {
h.Logf("[mesh-delivery] %s: its mesh/check is %s; the checker checks it again", d.ID, why)
}
@@ -299,7 +299,7 @@ func (h *Holder) composeFromChecker(g *Group, members []*Delivery, now time.Time
h.keepGroup(g)
if c.Verdict != "" {
for _, d := range members {
h.mergeAgainNow(d)
h.causeChanged(d) // a composed check that differs from the last: what a refusal waited on changed (issue 1785)
}
h.Logf("[mesh-delivery] group %s: %s — %s", g.ID, c.Verdict, c.Summary)
}
@@ -323,6 +323,16 @@ func (h *Holder) mergeAgainNow(d *Delivery) {
}
}
// causeChanged takes a verdict, a composed check or a status of the same heads that decides whether the merge may go: what the
// refusal waited on changed, so the count of the same words starts again and the merge is asked at once (issue 1785); only a
// refusal that truly repeats, with nothing new behind it, stops the asking (issue 1106). Under the lock.
func (h *Holder) causeChanged(d *Delivery) {
if d != nil && d.Ready != nil && d.Ready.Merged == nil {
d.Ready.Repeats = 0
}
h.mergeAgainNow(d)
}
// maxRepeatedRefusals is how many refusals in a row, with the same words and the trunk not moved, end the asking again: a
// refusal that repeats with no trunk move is not a trunk's move, and the check asked after each only brings the same words.
const maxRepeatedRefusals = 3