Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/scripts/enqueue-guard.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1117,3 +1117,46 @@ test('#602 (2026-11-01): an inline stop written before the PASS at this tip is n
const r = run({ comments: [comment(report(), '2026-11-01T17:35:30Z', { id: 9103 })], reviewComments: [early] })
assert.equal(r.ok, true, r.reasons.join('; '))
})

// ---- P-479, 2026-11-03: the lift that answers a hold was itself thrown away. `parseReport` treats a line
// beginning `Verdict:` below the first line as this body's verdict (#643/#640 keep a quoted block from
// overwriting a conclusion only when the first line already is one), so a lift comment that quotes the PASS
// it answers parses as a conclusion, and the stop filter at `enqueue-rules.mjs:199` dropped the whole
// comment. The guard then told the author to answer the hold on the record — the one thing its own reader
// refused to see. The filter keeps a conclusion-shaped body when that body also asks to lift the hold; the
// second case is the counterweight: quoting a PASS without a lift phrase still clears nothing.

test('P-479 (2026-11-03): a lift that quotes the PASS it answers still clears the stop', () => {
const lift = note({
id: 6596858166,
login: 'hugo',
body: [
'The PASS for this head has landed. You may merge this tip now.',
'',
'`Verdict: PASS at ' + HEAD + '` names exactly this head.',
'',
'Written-by: FlyBot Lead',
].join('\n'),
at: '2026-11-03T06:15:25Z',
})
const r = run({
comments: [comment(report(), '2026-11-03T05:00:00Z', { id: 6596712952 }), note({ id: 6596354014, at: '2026-11-03T05:38:25Z' }), lift],
})
assert.equal(r.ok, true, r.reasons.join('; '))
assert.equal(r.stopRequest, null, 'the lift is the answer to the stop, so no stop stands')
})

test('P-479 (2026-11-03): a conclusion-shaped comment without a lift phrase does not clear the stop', () => {
const quoted = note({
id: 6596858167,
login: 'hugo',
body: ['The PASS for this head has landed.', '', '`Verdict: PASS at ' + HEAD + '` names exactly this head.'].join('\n'),
at: '2026-11-03T06:15:25Z',
})
const r = run({
comments: [comment(report(), '2026-11-03T05:00:00Z', { id: 6596712952 }), note({ id: 6596354014, at: '2026-11-03T05:38:25Z' }), quoted],
})
assert.equal(r.ok, false, 'a quoted conclusion is not an answer to the hold')
assert.match(r.reasons.join('; '), /asks not to merge/)
assert.match(r.reasons.join('; '), /6596354014/, 'the refusal still names the stop that stands')
})
14 changes: 12 additions & 2 deletions .github/scripts/enqueue-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ const withoutQuotes = (text) => String(text ?? '')
.replace(/"[^"\n]*"/g, ' ')
.replace(/“[^”\n]*”/g, ' ')

// A body that parses as a conclusion but asks only to lift a stop — no ask to stop of its own. This is the
// lift that quotes the PASS it answers (#2246 on 2026-11-03); "record only — do not merge" in a PASS is the
// marker `recordOnlyAt` reads, so it is not one of these.
const responseOnly = (body) => {
const text = withoutQuotes(body)
return LIFT_REQUEST.test(text) && !STOP_REQUEST.test(text) && !HOLD_LINE.test(text)
}

// The stop request still standing, or null. `since` is the timestamp of the conclusion the decision used, in
// ms; null reads the whole list (no anchor, or one that cannot be read — a guard that cannot find the PASS it
// judged must not narrow what it refuses).
Expand All @@ -195,8 +203,10 @@ export function stopRequestAt(reports, { author, since } = {}) {
// whichever box it was typed in, and the callers already pass both (`enqueue-guard.mjs:69-71`,
// `merge-gate.mjs:219-221`). A body carrying a conclusion is the conclusion, not a request:
// "record only — do not merge" in a PASS is the marker `recordOnlyAt` already reads, and reading it
// here too would make every parked PASS its own stop.
.filter(({ r }) => !parseReport(r?.body).verdict)
// here too would make every parked PASS its own stop. The one exception is the answer to a stop: a
// lift that quotes the PASS it answers parses as a conclusion too (#2246 on 2026-11-03), and dropping
// it left the guard telling the author to do the one thing its own reader refused to see.
.filter(({ r }) => !parseReport(r?.body).verdict || responseOnly(r?.body))
.filter(({ t }) => after == null || t > after)
.sort((a, b) => a.t - b.t || a.i - b.i)
let stop = null
Expand Down
Loading