From 7fb0234604f504f9c59f6c8b469a16d7e142a3b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 10 Sep 2026 16:18:39 +0000 Subject: [PATCH] feat(pm): refuse a merge group whose queued PR still carries `needs:contract-review` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SKILL.md 〈入队与落地〉 states the clause-② enqueue gate as binding — 「双肢命中任一 ⇒ 无席内条款②复核 PASS 在案 ⛔ 禁止入队」 — and `references/contract-review.md` 〈载体纪律〉 reads the label itself: 「开着的载体恒 = 真实待审」. Nothing mechanical read it at the queue: a grep over `.github/workflows` and this guard answered with one comment line in `lint.yml` about a label description's byte length. The gate was advisory in practice while the protocol treated it as binding, and eleven enqueues in one day (2026-09-09, both repos, event logs read per PR) is what that cost. The queue guard gains a second, independent leg on `merge_group`: every pull request the group is landing is enumerated per commit — the same decomposition the governed leg uses, so a bare PR cannot carry a gated sibling through — and its labels are read from the pull object. Refuse while the carrier is open (exit 6); refuse when the label set cannot be read or the group names no pull request (exit 7); pass otherwise. The `pull_request` leg is untouched and reads no label, so its output stays byte-identical. The label read uses `GET /pulls/{n}` rather than the issues-API labels route, so `pull-requests: read` — already declared for the review read — is the whole permission it needs and the workflow is unchanged. Two things the header states out loud rather than leaving to be discovered. The leg reads the LABEL, not the verdict: of the eleven measured enqueues it refuses five and passes six, because those six stripped the carrier seconds before enqueue with no PASS on record. And it has no cheap local pre-filter, so a GitHub outage now refuses a merge group whose diff touches nothing governed; the zero-cost-clear rendering is scoped on that leg rather than left making a promise this change took away. The carrier spelling is mirrored, not imported: `check-half-states.mjs` owns the constant, but importing it here is a module-eval cycle — its H43 awaits `import('./check-governed-queue-guard.mjs')` at module scope — which deadlocks both modules (node exit 13). The self-test pins the mirror to that file's source and pins the reason it is a mirror. Claude-Session: https://claude.ai/code/session_01YKEjmbYNvYWJvWGSWx26zK Co-authored-by: Claude --- scripts/pm/check-governed-queue-guard.mjs | 589 +++++++++++++++++++++- 1 file changed, 585 insertions(+), 4 deletions(-) diff --git a/scripts/pm/check-governed-queue-guard.mjs b/scripts/pm/check-governed-queue-guard.mjs index 240297fb1f..4fd3ccf4de 100644 --- a/scripts/pm/check-governed-queue-guard.mjs +++ b/scripts/pm/check-governed-queue-guard.mjs @@ -215,6 +215,45 @@ * governed is still never blocked by any of it: the path test runs first and * returns before provenance is consulted at all. * + * ## The SECOND leg: the contract-review carrier (#17040) + * + * Everything above is about GOVERNED SURFACES. This file also carries a second, + * independent queue predicate, and it is keyed on a LABEL rather than on paths. + * + * `.claude/skills/pm-dispatch/SKILL.md` 〈入队与落地〉 states the rule, and the + * quote IS the operative criterion so it is reproduced rather than paraphrased: + * + * > 双肢命中任一 ⇒ 无席内条款②复核 PASS 在案 ⛔ 禁止入队 + * + * with `references/contract-review.md` 〈载体纪律〉 supplying the reading of the + * label itself: 「开着的载体恒 = 真实待审」. So `needs:contract-review` on a pull + * request IS the gate, and until #17040 nothing mechanical read it at the queue: + * `git grep needs:contract-review -- .github/workflows scripts/pm/check-governed-queue-guard.mjs` + * answered with ONE comment line in `lint.yml` about a label description's byte + * length, and nothing at the queue at all. The gate was advisory in practice + * while the protocol treated it as binding, and eleven measured enqueues in a + * single day (2026-09-09, both repos, event logs read per PR) is what that + * costs. + * + * ⭐ THE HONEST BOUNDARY, and it is not a detail: this leg reads the LABEL, not + * the verdict. Of those eleven measured enqueues, FIVE carried the label into + * the queue and this leg refuses them; the other SIX had the carrier stripped + * seconds before the enqueue with no PASS on record for that head, and this leg + * passes them — 「被剥」 and 「从未挂过」 are the same bytes to a label reader. One + * of those six (objectui#8164) landed a real published-face defect. Whether a + * VERDICT exists is `check-clause2-carriers.mjs`'s question and this file does + * not pretend to answer it; the CLEAR rendering says so out loud, so nobody + * reads a green carrier leg as "the review happened". + * + * ⚖️ THE ACCEPTED COST: there is no cheap local pre-filter for this leg, because + * a carrier is remote state a seat hangs rather than a property of the diff. So + * unlike the governed leg, every merge group now costs one label read per queued + * pull request, and a GitHub outage refuses a merge group whose diff touches + * nothing governed. The zero-cost-clear rendering is scoped to say so rather + * than left making a promise this leg has taken away (#15406's lesson: a verdict + * may not deny its own evidence). Fail-open was the alternative, and this is the + * one file where it is ruled out by construction. + * * ## Exit codes — the refusal is impossible to read as clean * * 0 CLEAR — nothing governed in the diff (no API call was made), or every @@ -234,6 +273,11 @@ * since 2026-09-04 the head decides nothing, so it is recorded * as a missing reading and the review list alone judges. * 5 REFUSED — governed paths on a commit attributable to no pull request. + * 6 REFUSED — a queued pull request carries `needs:contract-review` + * (#17040). The carrier leg, not the governed one. + * 7 REFUSED — the carrier leg could not READ a queued pull request's labels, + * or the merge group names no pull request to read them from. + * Split from 6 for the same reason 4 is split from 3. * 1 CANNOT RUN — unusable event payload, unsupported event, unreadable git. * Still non-zero, still red: this file has no green that means * "did not look". @@ -303,11 +347,12 @@ const SELF_TEST_BATTERIES = Object.freeze({ 'the WIRING pin: the workflow still spells this context name': 8, '⭐ #14063: the environment the exemption needs, pinned to the YAML': 7, '⭐ #15406: a CLEAR reached through a lift is not a clear that saw nothing': 10, + '⛔ #17040: the contract-review carrier is the enqueue gate': 39, }); // DELETING an entry silences that battery's floor exactly as effectively as // zeroing it, so the roster's own size is pinned too. -const SELF_TEST_BATTERY_FLOOR = 18; +const SELF_TEST_BATTERY_FLOOR = 19; // The key an assertion is filed under when no battery is open. It is not a // declared battery, so it reds by the same set difference rather than silently @@ -323,6 +368,14 @@ export const EXIT_CANNOT_RUN = 1; export const EXIT_REFUSED_UNAPPROVED = 3; export const EXIT_REFUSED_UNREADABLE = 4; export const EXIT_REFUSED_UNATTRIBUTED = 5; +/** + * The contract-review carrier leg's two refusals (#17040). Split for the same + * reason 3 and 4 are split on the governed leg: "the carrier is open" and "we + * could not find out" are different facts and a queue log must be able to tell + * them apart. ⛔ Neither may ever be folded into the other to tidy the table. + */ +export const EXIT_REFUSED_CARRIER = 6; +export const EXIT_REFUSED_CARRIER_UNREADABLE = 7; /** * The check-run name branch protection would pin, and the wiring it belongs @@ -349,6 +402,34 @@ export const EVENT_PULL_REQUEST = 'pull_request'; */ export const GOVERNED_APPROVERS = Object.freeze(['os-zhuang', 'hotlong']); +/** + * The clause-② enqueue gate's label (#17040). + * + * ⚠️ THIS IS A MIRROR, NOT THE SOURCE, and the distinction is the whole comment. + * `scripts/pm/check-half-states.mjs` OWNS this constant — H31 declares it and + * H51/H53 pin that ownership ("the gate constant is the one H31 already owns, + * not a second spelling") — and `check-clause2-carriers.mjs` imports it from + * there rather than restating it, which is what this file would do too. + * + * ⛔ IT CANNOT. Importing `check-half-states.mjs` from here is a MODULE-EVAL + * CYCLE: that file's H43 resolves its governed register at module scope with a + * top-level `await loadGovernedRegister()`, and that awaits + * `import('./check-governed-queue-guard.mjs')` — this file — for + * `GOVERNED_APPROVERS`. Adding the reverse edge deadlocks BOTH modules; measured + * on 2026-09-10, node exits 13 with "Detected unsettled top-level await" and + * `check-half-states.mjs` stops loading standalone as well. A dynamic import + * inside the self-test deadlocks identically, because this file reaches its own + * self-test through a top-level await too. + * + * So the spelling is mirrored here and PINNED to H31's by reading that file's + * SOURCE in the self-test — the same "read it from disk, a constant asserting + * against itself proves nothing" idiom the workflow wiring pin below uses. Drift + * in either direction reddens. ⭐ The self-test also pins the REASON: when H31's + * module-scope await goes away, the mirror should become a real import, and the + * case that fails will say so. + */ +export const CONTRACT_REVIEW_LABEL = 'needs:contract-review'; + /** * The pull-request number a merge-queue head ref names, or null. * @@ -630,6 +711,20 @@ export function renderGuardVerdict(verdict) { ' never from a restated list. ⛔ ZERO review lookups were made: the path test runs first and returns,', ' so a GitHub API outage can never block a diff that touches nothing governed.', ); + // ⭐ #17040, and #15406's lesson applied rather than re-learned: a verdict + // may not deny its own evidence. The three lines above are byte-identical + // and stay that way — the `pull_request` leg's byte-identity constraint + // (2026-08-27) reaches them, and that leg does not run the carrier read at + // all. But on `merge_group` this file now DOES make an unconditional API + // call, so the last of those lines would otherwise be a claim this run has + // already falsified. It is scoped here instead of rewritten. + if (verdict.event === EVENT_MERGE_GROUP) { + lines.push( + ' ⚠️ Scoped to the GOVERNED-SURFACE leg. On merge_group this file ALSO reads the contract-review', + ' carrier on every queued pull request — one label read each, fail-closed — and that leg reports', + ' separately below. An outage there DOES refuse this merge group.', + ); + } return lines.join('\n'); } lines.push( @@ -841,6 +936,200 @@ export async function runGuard({ event, rows, fetchReviews, fetchPullHead, lifte return guardVerdict({ event, governed, unattributed, approvals, apiCalls, headNotes, lifted }); } +// ── the contract-review carrier: the SECOND leg, and the label IS the gate ── + +/** + * The leg's own name in a log. Deliberately NOT `CHECK_CONTEXT_NAME`: this is a + * second predicate inside the same check run, not a second check run, and a + * reader must be able to see which leg answered which way. + */ +export const CARRIER_LEG_NAME = 'Contract-Review Carrier (enqueue gate)'; + +/** + * Every pull request this merge group is landing — deduplicated, in group order. + * + * ⭐ It reads the SAME per-commit decomposition the governed leg reads, and that + * is load-bearing rather than convenient. `merge_group.head_ref` names only the + * LAST pull request in the group, so keying this leg to it would let PR A's open + * carrier ride into `main` behind PR B — under-enumeration, the one direction a + * queue reading must never be wrong in (#9902), and the same trap the governed + * leg's header documents. The queue ref is consulted ONLY when the decomposition + * attributes no pull request at all, where it cannot mis-attribute because there + * is nothing else to attribute to. + */ +export function carrierPullsInGroup(rows, namedPull = null) { + const pulls = []; + const seen = new Set(); + for (const row of Array.isArray(rows) ? rows : []) { + const pr = row?.pr; + if (typeof pr !== 'number' || !Number.isInteger(pr) || pr <= 0) continue; + if (seen.has(pr)) continue; + seen.add(pr); + pulls.push(pr); + } + if (pulls.length === 0 && Number.isInteger(namedPull) && namedPull > 0) pulls.push(namedPull); + return pulls; +} + +/** + * The carrier verdict, as data. Pure, exactly like `guardVerdict` — the renderer + * and the exit code both read it rather than re-deriving it. + * + * ⚠️ `merge_group` ONLY. The `pull_request` leg returns `not-applicable` and + * renders the empty string, so that leg's output stays byte-identical to what it + * was before this predicate existed (the 2026-08-27 card's own constraint). An + * early warning on the PR leg would be useful and is deliberately NOT taken + * here: the ruling this leg implements is about ENQUEUE, and widening a + * governance gate past its own ruling is how gates acquire policy nobody agreed + * to. Widening it is a one-line maintainer decision. + */ +export function carrierVerdict({ event, pulls = [], readings = new Map(), apiCalls = 0 }) { + const base = { event, entries: [], apiCalls, legName: CARRIER_LEG_NAME, label: CONTRACT_REVIEW_LABEL }; + if (event !== EVENT_MERGE_GROUP) { + return { ...base, conclusion: 'not-applicable', exitCode: EXIT_CLEAR, refusalKind: null }; + } + if (pulls.length === 0) { + return { ...base, conclusion: 'refused', exitCode: EXIT_REFUSED_CARRIER_UNREADABLE, refusalKind: 'no-pull' }; + } + const entries = pulls.map( + (pr) => + readings.get(pr) ?? { + pr, + state: 'unreadable', + labels: [], + reason: 'no label reading was recorded for this pull request', + }, + ); + const out = { ...base, entries }; + if (entries.some((e) => e.state === 'unreadable')) { + return { ...out, conclusion: 'refused', exitCode: EXIT_REFUSED_CARRIER_UNREADABLE, refusalKind: 'unreadable' }; + } + if (entries.some((e) => e.state === 'gated')) { + return { ...out, conclusion: 'refused', exitCode: EXIT_REFUSED_CARRIER, refusalKind: 'gated' }; + } + return { ...out, conclusion: 'clear', exitCode: EXIT_CLEAR, refusalKind: null }; +} + +/** + * The carrier orchestrator, with its one IO dependency injected. + * + * ⚠️ Unlike the governed leg there is no cheap local pre-filter to run first, + * and there cannot be one: the carrier is REMOTE state hung by a seat, not a + * property of the diff. So every merge group pays one label read per queued pull + * request. ⚖️ THE ACCEPTED COST, stated here rather than left to be discovered: + * a GitHub outage now refuses a merge group whose diff touches nothing governed, + * which the governed leg alone never did. The alternative is a carrier read that + * passes when it cannot see the label — and 「门禁被剥不是红灯是放行」 is precisely + * the fail-open shape this whole regime exists to end. The cost is bounded: one + * GET per queued PR, against the same endpoint the governed leg already calls. + */ +export async function runCarrierGuard({ event, rows, namedPull = null, fetchLabels }) { + if (event !== EVENT_MERGE_GROUP) return carrierVerdict({ event }); + const pulls = carrierPullsInGroup(rows, namedPull); + const readings = new Map(); + let apiCalls = 0; + for (const pr of pulls) { + try { + apiCalls += 1; + const names = (await fetchLabels(pr)).map((n) => String(n)); + readings.set(pr, { pr, state: names.includes(CONTRACT_REVIEW_LABEL) ? 'gated' : 'bare', labels: names }); + } catch (error) { + readings.set(pr, { pr, state: 'unreadable', labels: [], reason: String(error?.message ?? error).split('\n')[0] }); + } + } + return carrierVerdict({ event, pulls, readings, apiCalls }); +} + +/** + * The words a reader acts on for this leg. Returns '' on the `pull_request` leg, + * so nothing is appended to that leg's byte-identical output. + * + * The refusal QUOTES the governing rule rather than paraphrasing it — the quote + * IS the operative criterion here, so it is reproduced from SKILL.md's own bytes + * and left untranslated — and it names WHO can act, because a refusal a reader + * cannot act on is a refusal they route around. + */ +export function renderCarrierVerdict(verdict) { + if (verdict.conclusion === 'not-applicable') return ''; + const lines = []; + lines.push( + `${CARRIER_LEG_NAME} — ${verdict.event} — ${verdict.entries.length} queued pull request(s), ` + + `${verdict.apiCalls} label read(s).`, + ); + + // ⚠️ The boundary, printed on the CLEAR path too and not only on the refusal: + // this leg reads the LABEL, never the verdict. A carrier stripped seconds + // before any PASS existed is, to this predicate, identical to a carrier that + // was never hung — 「被剥」 and 「从未挂过」 are indistinguishable in the evidence. + // Six of the eleven measured #17040 enqueues are exactly that shape, so a + // reader who takes this CLEAR as "the review happened" has misread it. + const boundary = [ + ' ⚠️ This leg reads the LABEL, not the verdict. A carrier stripped before any PASS was on record', + ' is indistinguishable here from one that was never hung. Whether a verdict EXISTS is a question', + ' for `scripts/pm/check-clause2-carriers.mjs`, not for this one.', + ]; + + if (verdict.conclusion === 'clear') { + lines.push(` ✅ CLEAR — no queued pull request carries \`${CONTRACT_REVIEW_LABEL}\`.`, ...boundary); + return lines.join('\n'); + } + + for (const entry of verdict.entries) { + if (entry.state === 'gated') { + lines.push( + '', + ` #${entry.pr} — ⛔ CARRIES \`${CONTRACT_REVIEW_LABEL}\` — this pull request may not be in the queue.`, + ` labels read: ${entry.labels.join(', ') || '(none)'}`, + ); + } else if (entry.state === 'unreadable') { + lines.push('', ` #${entry.pr} — ⛔ the label set could NOT be read — ${entry.reason}`); + } else { + lines.push('', ` #${entry.pr} — ✅ does not carry the gate label.`); + } + } + + lines.push('', ' ⛔ REFUSED — this merge group must not land.'); + if (verdict.refusalKind === 'no-pull') { + lines.push( + ' This merge group names NO pull request — neither its commit subjects nor its queue head ref', + ' resolve to one — so there is no carrier to read at all. Fail closed: a queue entry nobody can', + ' point at a pull request for cannot be shown to have cleared the gate.', + ); + } else if (verdict.refusalKind === 'unreadable') { + lines.push( + ' The label set could not be READ for at least one queued pull request above. ⛔ This is a refusal', + ' and not a pass, deliberately: 「门禁被剥不是红灯是放行」 — a gate label that cannot be seen and', + ' one that is absent are the same bytes to a reader that shrugs. Re-run once the API is reachable.', + ); + } else { + lines.push( + ' At least one pull request above entered the merge queue while the contract-review carrier was', + ' still open on it. The governing rule, from `.claude/skills/pm-dispatch/SKILL.md` 〈入队与落地〉:', + '', + ' 「双肢命中任一 ⇒ 无席内条款②复核 PASS 在案 ⛔ 禁止入队」', + '', + ' and `references/contract-review.md` 〈载体纪律〉: 「开着的载体恒 = 真实待审」.', + ); + } + lines.push( + '', + ' What satisfies this check:', + ' 1. ⭐ Take the pull request out of the queue: convert it back to DRAFT (disarming auto-merge', + ' alone does NOT dequeue it), and leave it parked. 「挂标后复核完成前短暂停靠」 — parked', + ' outside the queue is the SAFE state, not a stalled one.', + ' 2. Then the dispatching seat completes the in-seat clause-② review and posts the verdict as a', + ' comment on the PR or the card. On PASS that same seat strips the carrier from BOTH carriers,', + ' cites the record, and re-enqueues: 「PASS ⇒ 同席剥标并引记录、ready、auto-merge」. On FAIL', + ' it is a patch round.', + '', + ' ⛔ Stripping the label to get past this check, with no verdict on record, is the defect this leg', + ' was built from — not a way through it. ⛔ Neither is "edit this check".', + ...boundary, + ); + return lines.join('\n'); +} + + // ── git (diff decomposition; zero API) ────────────────────────────────────── function git(root, args) { @@ -1066,6 +1355,38 @@ export function makePullHeadReader({ apiUrl, slug, token, fetchImpl = fetch }) { }; } +/** + * The label names on a pull request. + * + * ⭐ READ FROM THE PULL OBJECT, deliberately, and this choice is what makes the + * carrier leg cost ZERO new workflow permission. The labels endpoint proper + * (`GET /repos/{o}/{r}/issues/{n}/labels`) is an ISSUES-API route and needs + * `issues: read`, which this workflow does not grant and would have to be + * widened to grant; the pull object carries the same `labels[]` and is already + * covered by the `pull-requests: read` the review read needs. Measured on + * #17442: `labels` came back `["ci/cd","size/l","skip-changeset"]` from + * `GET /repos/{o}/{r}/pulls/{n}`. ⛔ Do not "simplify" this to the issues route + * without adding the scope in the same edit — the failure is a 403 on every + * queue build, which this leg then correctly turns into a REFUSAL. + * + * Same channel and same shape as the two readers above: throws on any non-2xx + * and on a body whose `labels` is not an array, and the caller turns the throw + * into a REFUSAL (exit 7), never a pass. A reader that quietly returned `[]` + * would be the fail-open bug this whole leg exists to close: an unreadable + * carrier would render as a carrier that is not there. + */ +export function makeLabelReader({ apiUrl, slug, token, fetchImpl = fetch }) { + return async function fetchLabels(pull) { + const res = await fetchImpl(`${apiUrl}/repos/${slug}/pulls/${pull}`, { headers: apiHeaders(token) }); + if (!res.ok) throw new Error(`GET /repos/${slug}/pulls/${pull} answered HTTP ${res.status}`); + const body = await res.json(); + if (!Array.isArray(body?.labels)) { + throw new Error(`GET /repos/${slug}/pulls/${pull} answered no labels array — the carrier cannot be read`); + } + return body.labels.map((entry) => String(entry?.name ?? '')); + }; +} + // ── CLI ───────────────────────────────────────────────────────────────────── async function main() { @@ -1122,9 +1443,19 @@ async function main() { }; const fetchReviews = makeReviewReader(reader); const fetchPullHead = makePullHeadReader(reader); + const fetchLabels = makeLabelReader(reader); const verdict = await runGuard({ event: context.event, rows, fetchReviews, fetchPullHead, lifted }); - const report = [`${context.label} — ${rows.length} commit(s) in range`, ...notes, renderGuardVerdict(verdict)].join('\n'); + // The second leg (#17040). It runs on `merge_group` only and renders '' on the + // other, so the `pull_request` output is byte-identical to what it was. + const carrier = await runCarrierGuard({ event: context.event, rows, namedPull: context.namedPull, fetchLabels }); + const carrierBlock = renderCarrierVerdict(carrier); + const report = [ + `${context.label} — ${rows.length} commit(s) in range`, + ...notes, + renderGuardVerdict(verdict), + ...(carrierBlock === '' ? [] : ['', carrierBlock]), + ].join('\n'); console.log(report); // The step summary is where a reader actually looks at a red queue build. @@ -1135,7 +1466,13 @@ async function main() { /* a summary that cannot be written changes no verdict */ } } - return verdict.exitCode; + // BOTH legs are always evaluated and BOTH blocks are always printed, so no + // reading is lost whichever refuses. The governed refusal wins the exit code + // when both fire, because its remedy is the stricter of the two (the + // maintainer's own merge, Prime Directive #14) and it subsumes the carrier's + // "take it out of the queue". ⛔ The carrier code is not swallowed silently — + // its block states the refusal in full either way. + return verdict.exitCode !== EXIT_CLEAR ? verdict.exitCode : carrier.exitCode; } if (isEntrypoint(import.meta.url) && !process.argv.includes('--self-test')) { @@ -1925,6 +2262,244 @@ export async function selfTest() { assert('a-non-2xx-pull-read-throws-with-its-status', /HTTP 502/.test(await readerThrow(fakeRes({}, false, 502)))); assert('a-body-with-no-parseable-head-sha-throws-never-pins-nothing-silently', /head\.sha/.test(await readerThrow(fakeRes({ head: {} })))); + // ── ⛔ #17040: the contract-review carrier is the enqueue gate ─────────── + // + // The eleven measured enqueues of 2026-09-09 are replayed as fixtures at the + // bottom of this battery, with their PREDICTED DIRECTION, because the whole + // claim of this leg is "it would have refused these" — and six of them it + // would NOT have, which is the boundary that must keep being measured rather + // than remembered. + battery('⛔ #17040: the contract-review carrier is the enqueue gate'); + // ⭐ The mirror pin. H31 in `check-half-states.mjs` OWNS this spelling; this + // file cannot import it (module-eval cycle — see the constant's own comment), + // so the two are held equal by reading that file's source. A constant + // asserting against itself would prove nothing. + let h31Source = ''; + try { + h31Source = readFileSync(join(repoRoot, 'scripts', 'pm', 'check-half-states.mjs'), 'utf8'); + } catch (error) { + h31Source = ''; + assert('H31s-file-is-readable-so-the-mirror-can-be-pinned-at-all', false, String(error?.message ?? error).split('\n')[0]); + } + const h31Spelling = /^export const CONTRACT_REVIEW_LABEL = '([^']+)';$/m.exec(h31Source)?.[1] ?? null; + assert( + '⭐ the-carrier-label-MIRRORS-H31s-OWNED-constant-read-from-that-files-source', + h31Spelling !== null && h31Spelling === CONTRACT_REVIEW_LABEL, + `H31 spells ${JSON.stringify(h31Spelling)}, this mirror spells ${JSON.stringify(CONTRACT_REVIEW_LABEL)}`, + ); + assert( + '⭐ the-REASON-the-label-is-mirrored-instead-of-imported-is-still-true', + /^export const GOVERNED_REGISTER = await loadGovernedRegister\(\);$/m.test(h31Source) && + /check-governed-queue-guard\.mjs/.test(h31Source), + 'H31 no longer awaits this file at module scope — the cycle is gone, so replace the mirror with a real import', + ); + assert( + 'the-two-carrier-codes-are-distinct-non-zero-and-collide-with-no-governed-code', + new Set([EXIT_CLEAR, EXIT_CANNOT_RUN, EXIT_REFUSED_UNAPPROVED, EXIT_REFUSED_UNREADABLE, EXIT_REFUSED_UNATTRIBUTED, EXIT_REFUSED_CARRIER, EXIT_REFUSED_CARRIER_UNREADABLE]).size === 7 && + EXIT_REFUSED_CARRIER !== 0 && + EXIT_REFUSED_CARRIER_UNREADABLE !== 0, + JSON.stringify([EXIT_REFUSED_CARRIER, EXIT_REFUSED_CARRIER_UNREADABLE]), + ); + + // Enumeration — the under-enumeration trap, which is this leg's #9902. + const carrierRow = (pr, sha = 'a'.repeat(40)) => ({ sha, subject: `x (#${pr})`, pr, paths: ['README.md'] }); + assert( + 'group-pulls-dedupe-and-keep-group-order', + JSON.stringify(carrierPullsInGroup([carrierRow(7), carrierRow(3), carrierRow(7)])) === JSON.stringify([7, 3]), + JSON.stringify(carrierPullsInGroup([carrierRow(7), carrierRow(3), carrierRow(7)])), + ); + assert( + '⭐ a-multi-PR-group-does-NOT-collapse-to-the-queue-refs-LAST-pr', + JSON.stringify(carrierPullsInGroup([carrierRow(11), carrierRow(22)], 22)) === JSON.stringify([11, 22]), + 'the head ref names only the last PR; keying the leg to it lets an earlier open carrier ride in behind it', + ); + assert( + 'the-queue-ref-is-the-fallback-ONLY-when-nothing-is-attributed', + JSON.stringify(carrierPullsInGroup([{ sha: 'b'.repeat(40), subject: 'no pr here', pr: null, paths: [] }], 99)) === JSON.stringify([99]), + ); + assert( + 'no-rows-and-no-named-pull-yields-no-pulls-never-a-phantom-zero', + carrierPullsInGroup([], null).length === 0 && carrierPullsInGroup(undefined, 0).length === 0, + ); + + // The verdict table. + const carrierRun = (event, pulls, states) => { + const readings = new Map(); + for (const [pr, state] of Object.entries(states)) { + const n = Number(pr); + readings.set( + n, + state === 'unreadable' + ? { pr: n, state: 'unreadable', labels: [], reason: 'HTTP 403 (fixture)' } + : { pr: n, state, labels: state === 'gated' ? ['priority:p2', CONTRACT_REVIEW_LABEL] : ['priority:p2'] }, + ); + } + return carrierVerdict({ event, pulls, readings, apiCalls: pulls.length }); + }; + const gatedOne = carrierRun(EVENT_MERGE_GROUP, [5], { 5: 'gated' }); + assert('an-OPEN-carrier-on-a-queued-PR-REFUSES', gatedOne.exitCode === EXIT_REFUSED_CARRIER && gatedOne.conclusion === 'refused', JSON.stringify(gatedOne.exitCode)); + const bareOne = carrierRun(EVENT_MERGE_GROUP, [5], { 5: 'bare' }); + assert('a-queued-PR-without-the-carrier-PASSES', bareOne.exitCode === EXIT_CLEAR && bareOne.conclusion === 'clear', JSON.stringify(bareOne.exitCode)); + const unreadableOne = carrierRun(EVENT_MERGE_GROUP, [5], { 5: 'unreadable' }); + assert( + 'an-unreadable-label-set-REFUSES-with-its-OWN-code-never-passes', + unreadableOne.exitCode === EXIT_REFUSED_CARRIER_UNREADABLE && unreadableOne.conclusion === 'refused', + JSON.stringify(unreadableOne.exitCode), + ); + const noPull = carrierVerdict({ event: EVENT_MERGE_GROUP, pulls: [], readings: new Map(), apiCalls: 0 }); + assert('a-merge-group-naming-NO-pull-request-REFUSES', noPull.exitCode === EXIT_REFUSED_CARRIER_UNREADABLE && noPull.refusalKind === 'no-pull'); + const carrierMixed = carrierRun(EVENT_MERGE_GROUP, [11, 22], { 11: 'gated', 22: 'bare' }); + assert( + '⭐ a-bare-PR-does-NOT-carry-a-gated-sibling-through-the-group', + carrierMixed.exitCode === EXIT_REFUSED_CARRIER, + 'PR B being clean is not a reading about PR A', + ); + // A reading that never arrived is not a pass either — the same fail-closed + // default `guardVerdict` gives an approval nobody recorded. + const missingReading = carrierVerdict({ event: EVENT_MERGE_GROUP, pulls: [5], readings: new Map(), apiCalls: 1 }); + assert('a-pull-with-NO-recorded-reading-REFUSES-rather-than-defaulting-to-bare', missingReading.exitCode === EXIT_REFUSED_CARRIER_UNREADABLE); + + // The pull_request leg: not applicable, silent, and it never reads a label. + const carrierPrLeg = carrierVerdict({ event: EVENT_PULL_REQUEST, pulls: [5], readings: new Map() }); + assert('the-pull_request-leg-is-NOT-APPLICABLE-and-exits-clear', carrierPrLeg.conclusion === 'not-applicable' && carrierPrLeg.exitCode === EXIT_CLEAR); + assert('the-pull_request-leg-renders-NOTHING-so-that-legs-output-stays-byte-identical', renderCarrierVerdict(carrierPrLeg) === ''); + let labelsTouched = 0; + const explodeLabels = () => { + labelsTouched += 1; + throw new Error('the carrier leg must not read a label on the pull_request leg'); + }; + const prLegRun = await runCarrierGuard({ event: EVENT_PULL_REQUEST, rows: [carrierRow(5)], fetchLabels: explodeLabels }); + assert( + 'the-pull_request-leg-makes-ZERO-label-reads-measured-with-a-spy-that-THROWS', + labelsTouched === 0 && prLegRun.apiCalls === 0 && prLegRun.conclusion === 'not-applicable', + `labelsTouched=${labelsTouched}`, + ); + + // End to end through `runCarrierGuard`, with the reader injected. + const e2eGated = await runCarrierGuard({ + event: EVENT_MERGE_GROUP, + rows: [carrierRow(11), carrierRow(22)], + fetchLabels: async (pull) => (pull === 11 ? ['size/s', CONTRACT_REVIEW_LABEL] : ['size/s']), + }); + assert( + 'end-to-end-one-label-read-per-queued-PR-and-the-open-carrier-refuses', + e2eGated.apiCalls === 2 && e2eGated.exitCode === EXIT_REFUSED_CARRIER, + JSON.stringify({ apiCalls: e2eGated.apiCalls, exit: e2eGated.exitCode }), + ); + const e2eThrows = await runCarrierGuard({ + event: EVENT_MERGE_GROUP, + rows: [carrierRow(11)], + fetchLabels: async () => { + throw new Error('GET /repos/o/r/pulls/11 answered HTTP 403'); + }, + }); + assert( + 'a-throwing-label-read-becomes-a-REFUSAL-and-never-escapes-as-a-rejection', + e2eThrows.exitCode === EXIT_REFUSED_CARRIER_UNREADABLE && /403/.test(renderCarrierVerdict(e2eThrows)), + renderCarrierVerdict(e2eThrows), + ); + + // The words a reader acts on. + const gatedText = renderCarrierVerdict(gatedOne); + assert( + 'the-refusal-QUOTES-the-rule-verbatim-untranslated', + gatedText.includes('双肢命中任一 ⇒ 无席内条款②复核 PASS 在案 ⛔ 禁止入队') && gatedText.includes('开着的载体恒 = 真实待审'), + gatedText, + ); + assert( + 'the-refusal-NAMES-WHO-CAN-ACT-and-forbids-stripping-the-label-to-get-past-it', + /同席剥标/.test(gatedText) && /Stripping the label to get past this check/.test(gatedText) && /DRAFT/.test(gatedText), + gatedText, + ); + assert( + 'the-unreadable-refusal-names-the-cause-and-reads-as-a-refusal-not-a-pass', + /could NOT be read/.test(renderCarrierVerdict(unreadableOne)) && /REFUSED/.test(renderCarrierVerdict(unreadableOne)), + renderCarrierVerdict(unreadableOne), + ); + assert( + '⭐ the-CLEAR-states-the-label-not-verdict-boundary-so-green-is-never-read-as-the-review-happened', + /reads the LABEL, not the verdict/.test(renderCarrierVerdict(bareOne)) && /check-clause2-carriers/.test(renderCarrierVerdict(bareOne)), + renderCarrierVerdict(bareOne), + ); + + // The label reader's own contract — same shape as the two readers beside it. + const labelArgs = { apiUrl: 'https://api.example', slug: 'o/r', token: null }; + const fakeLabelRes = (body, ok = true, status = 200) => async () => ({ ok, status, json: async () => body }); + assert( + 'the-label-reader-answers-the-names-on-the-PULL-object-the-scope-already-granted', + JSON.stringify(await makeLabelReader({ ...labelArgs, fetchImpl: fakeLabelRes({ labels: [{ name: 'a' }, { name: CONTRACT_REVIEW_LABEL }] }) })(7)) === + JSON.stringify(['a', CONTRACT_REVIEW_LABEL]), + ); + const labelReaderThrow = async (fetchImpl) => { + try { + await makeLabelReader({ ...labelArgs, fetchImpl })(7); + return null; + } catch (error) { + return String(error?.message ?? error); + } + }; + assert('a-non-2xx-label-read-throws-with-its-status', /HTTP 403/.test(await labelReaderThrow(fakeLabelRes({}, false, 403)))); + assert( + 'a-body-with-no-labels-array-throws-never-silently-reads-as-an-absent-carrier', + /no labels array/.test(await labelReaderThrow(fakeLabelRes({}))), + ); + + // ⭐ #15406's constraint, re-measured for this leg: the merge_group zero-cost + // clear is now SCOPED (this leg took its promise away), while the + // pull_request one keeps its pre-#17040 bytes exactly. + const clearMg = renderGuardVerdict(guardVerdict({ event: EVENT_MERGE_GROUP, governed: [], unattributed: [], apiCalls: 0 })); + const clearPr = renderGuardVerdict(guardVerdict({ event: EVENT_PULL_REQUEST, governed: [], unattributed: [], apiCalls: 0 })); + assert( + '⭐ the-merge_group-zero-cost-clear-SCOPES-its-outage-promise-because-the-carrier-leg-took-it-away', + clearMg.includes('Scoped to the GOVERNED-SURFACE leg') && clearMg.includes('An outage there DOES refuse this merge group.'), + clearMg, + ); + assert( + 'the-pull_request-zero-cost-clear-keeps-its-pre-17040-bytes-carrying-NO-carrier-note', + !clearPr.includes('Scoped to the GOVERNED-SURFACE leg') && clearPr.includes('so a GitHub API outage can never block a diff that touches nothing governed.'), + clearPr, + ); + + // ── the replay: the eleven measured enqueues of 2026-09-09 ─────────────── + // + // Carrier state at `added_to_merge_queue`, read from each PR's own event log + // (`GET /repos/{o}/{r}/issues/{n}/events`), not from the card's prose. The + // six `carrier: false` rows are the boundary this leg does NOT cover: the + // label was stripped before the enqueue with no verdict on record for that + // head, and one of them (objectui#8164) landed a real published-face defect. + const INCIDENTS_17040 = [ + { pr: 8723, repo: 'objectui', carrier: true, note: 'enqueued 01:58:38Z, carrier stripped 02:11:28Z — after' }, + { pr: 16998, repo: 'objectstack', carrier: true, note: 'enqueued 02:02:08Z, carrier stripped 02:10:51Z — after' }, + { pr: 16783, repo: 'objectstack', carrier: true, note: 'enqueued 06:11:15Z, carrier NEVER stripped, merged carrying it' }, + { pr: 17036, repo: 'objectstack', carrier: true, note: 'enqueued 04:46:19Z, carrier NEVER stripped, merged carrying it' }, + { pr: 8779, repo: 'objectui', carrier: true, note: 'enqueued 06:26:20Z, carrier NEVER stripped, merged carrying it' }, + { pr: 17085, repo: 'objectstack', carrier: false, note: 'stripped 07:38:11Z, enqueued 07:39:41Z — 90s, no verdict on record' }, + { pr: 8795, repo: 'objectui', carrier: false, note: 'stripped 09:41:40Z, enqueued 09:42:27Z — 47s, PASS posted 6s after the strip' }, + { pr: 8796, repo: 'objectui', carrier: false, note: 'stripped 10:28:43Z, enqueued 10:28:53Z — 10s, verdict of record was CHANGES REQUIRED' }, + { pr: 17067, repo: 'objectstack', carrier: false, note: 'stripped 07:51:52Z, enqueued 11:42:45Z — newest verdict was FAIL' }, + { pr: 8799, repo: 'objectui', carrier: false, note: 'stripped 11:23:13Z, enqueued 11:23:24Z — 11s, no verdict on the new head' }, + { pr: 8164, repo: 'objectui', carrier: false, note: 'stripped 2026-09-08, enqueued 11:59:19Z — SELF-REVIEW, real defect landed' }, + ]; + for (const incident of INCIDENTS_17040) { + const replay = await runCarrierGuard({ + event: EVENT_MERGE_GROUP, + rows: [carrierRow(incident.pr)], + fetchLabels: async () => (incident.carrier ? ['priority:p2', CONTRACT_REVIEW_LABEL] : ['priority:p2']), + }); + const expected = incident.carrier ? EXIT_REFUSED_CARRIER : EXIT_CLEAR; + assert( + `replay-${incident.repo}#${incident.pr}-${incident.carrier ? 'REFUSED' : 'passes (the boundary)'}`, + replay.exitCode === expected, + `${incident.note} — expected ${expected}, got ${replay.exitCode}`, + ); + } + assert( + '⭐ the-replay-population-splits-5-refused-6-passed-and-that-second-number-is-the-honest-limit', + INCIDENTS_17040.filter((i) => i.carrier).length === 5 && INCIDENTS_17040.filter((i) => !i.carrier).length === 6, + JSON.stringify(INCIDENTS_17040.map((i) => [i.pr, i.carrier])), + ); + + // ── the WIRING pin: the workflow still spells this context name ────────── // // Without this, renaming the job detaches the required context silently — @@ -2048,7 +2623,13 @@ export async function selfTest() { 'drift, on a hand-authored sibling, and on a recompute that throws), and the workflow wiring pin including the ' + 'dependency install the recompute needs, its register-agnostic filter-free form, and its continue-on-error ' + 'degradation), and the #15406 replay of PR #15284 — a clear reached THROUGH a lift no longer reports itself as a ' + - 'clear that matched nothing, while the zero-cost clear keeps its wording byte-for-byte on both legs.', + 'clear that matched nothing, while the zero-cost clear keeps its wording byte-for-byte on the pull_request ' + + 'leg and is SCOPED on the merge_group one, because #17040 added a second queue predicate there: the ' + + 'contract-review carrier, read from the pull object under the scope the review read already needs, ' + + 'fail-closed on an unreadable label set and on a group naming no pull request, enumerated per commit so a ' + + 'bare PR cannot carry a gated sibling through, silent and read-free on the pull_request leg, and replayed ' + + 'against the eleven measured enqueues of 2026-09-09 — five refused, six passed, that second number being ' + + 'the boundary a label reader cannot cross.', ); selfTestReachedVerdict = true;