From 1c29ef768450614545b4bf9c1419aa54371020e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 20 Sep 2026 21:57:24 +0000 Subject: [PATCH] fix(pm): the queue guard's remedy names the Merge button's bypass-rules option, and a pin reads the ruleset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The size limb (and the governed limb beside it) prescribed 人工直合 — "the maintainer's own click" — without naming which click. Ruleset `main` mandates the merge queue and lists this guard as a required context, so the only Merge that is not an enqueue is the Merge button's bypass-rules option, offered only while the ruleset configures a bypass actor. With none configured the remedy named a terminal nobody could reach: PR #19024 was enqueued three times and refused three times. The four remedy sentences (the header's and the three rendered ones) now name that option and keep 人工直合 as the NAME of the act. A new self-test battery judges the remedy text against a RECORDED reading of `GET /repos/objectstack-ai/objectstack/rulesets/12119582`: red when `bypass_actors` is present and empty, red when the remedy drifts back to a bare click, and a pass that PRINTS its reading when the field is unreadable — which is what every seat and Actions token gets, `administration` being outside the 17 permissions a workflow may grant. The reading is recorded rather than fetched because this self-test is the required guard job's first step and is declared offline; a live read would answer "unreadable" on every CI run, asserting nothing while adding a network dependency to a merge precondition. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude --- scripts/pm/check-governed-queue-guard.mjs | 74 ++++++++++++++++++++--- 1 file changed, 67 insertions(+), 7 deletions(-) diff --git a/scripts/pm/check-governed-queue-guard.mjs b/scripts/pm/check-governed-queue-guard.mjs index c5edabd8119..5b75964229a 100644 --- a/scripts/pm/check-governed-queue-guard.mjs +++ b/scripts/pm/check-governed-queue-guard.mjs @@ -407,7 +407,13 @@ * landed predicate says a human MERGE — 「人工审核」 landed as the same terminal * a governed diff has (ACCEPT on the card, `needs-user-decision` on the PR, a * final 维护者速读, review requested from `GOVERNED_APPROVERS`) and the - * maintainer's own click (人工直合). An authorized APPROVED review lifts a + * maintainer's own click (人工直合). ⭐ #19344 — THAT CLICK IS ONE BUTTON + * OPTION and this text now names it: `main` mandates the queue and requires + * this check, so the only Merge that is not an enqueue is the Merge button's + * BYPASS-RULES option, offered only while the ruleset configures a bypass + * actor. While none was, the remedy named a terminal nobody could reach and + * PR #19024 was enqueued and refused three times. That it IS offered is a + * ruleset fact the pin reads. An authorized APPROVED review lifts a * Tier H path because the 2026-08-27 ruling said so of PATHS; nothing has * said it of the NUMBER, and widening a governance gate past its own ruling is * how gates acquire policy nobody agreed to. Widening it is a one-line @@ -547,11 +553,12 @@ const SELF_TEST_BATTERIES = Object.freeze({ '⭐ #18020 → #19133 Tier S: a review of record, not an approval': 43, '⭐ #18701: the record lives on the PR or its card, and BOTH are read': 14, '⛔ #19036: the SIZE line at the queue — imported, per queued PR, fail-closed': 30, + '⭐ #19344: the remedy names a path the ruleset actually offers': 5, }); // 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 = 22; +const SELF_TEST_BATTERY_FLOOR = 23; // 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 @@ -1376,8 +1383,9 @@ export function renderGuardVerdict(verdict) { ` ✅ What a seat DOES do once an account in GOVERNED_APPROVERS (${GOVERNED_APPROVERS.join(', ')}) has APPROVED it,`, ' on ANY commit: the CLAIMING SEAT lands it — ruling C (#17971, maintainer 2026-09-13, verbatim', ' 「C. approve 后不管后续改动都由席位落地:」), 「席位落地 = 过落地前检、清标、ready、', - ' auto-merge,踢出/变基同法。」 Unapproved, the maintainer\'s own direct merge (人工直合) is', - ' the only landing this pull request has.', + ' auto-merge,踢出/变基同法。」 Unapproved, the maintainer\'s own direct merge (人工直合) is the', + ' only landing this pull request has, and it IS the Merge button\'s bypass-rules option —', + ' offered only while ruleset `main` configures a bypass actor (#19344).', ); } if (tierS.length > 0) { @@ -1469,7 +1477,8 @@ export function renderGuardVerdict(verdict) { ' 要卡最新的提交。」); the authorized set is still the 2026-08-27 one (「os-zhuang hotlong 批准', ' 算数」). ⛔ An agent seat never submits that approval, under any account — the post-merge audit', ' reads the approver too. ⛔ Unapproved, the maintainer\'s own direct merge (人工直合) is the only', - ' landing this pull request has.', + ' landing this pull request has, and it IS the Merge button\'s bypass-rules option — offered only', + ' while ruleset `main` configures a bypass actor (#19344); the audit log records it.', ); if (verdict.entries.some((e) => e.record !== undefined)) { lines.push( @@ -1925,6 +1934,32 @@ export async function runSizeGuard({ event, rows, namedPull = null, fetchPull }) return sizeGuardVerdict({ event, pulls, readings, apiCalls }); } +// ── #19344: the remedy's terminal is a RULESET fact, so it is READ ───────── +// +// GitHub offers it only to an account ruleset `main` lists as a bypass actor, +// and `bypass_actors` is withheld below `administration`, so a read answers +// three ways and only ONE is a refusal. ⛔ Never assert a path from a field that +// was not read (a permission difference must not red CI), ⛔ never pass mute. +export const BYPASS_OPTION_PHRASE = "Merge button's bypass-rules option"; + +/** `offered` | `not-offered` | `unreadable`, with the words a log should carry. */ +export function bypassActorReading(ruleset) { + const read = ruleset && typeof ruleset === 'object' ? ruleset : {}; + if (!Object.prototype.hasOwnProperty.call(read, 'bypass_actors')) { + return { reading: 'unreadable', detail: `bypass_actors: unreadable with this token (the key is absent); current_user_can_bypass: ${JSON.stringify(read.current_user_can_bypass ?? null)}` }; + } + const actors = read.bypass_actors; + if (Array.isArray(actors) && actors.length > 0) return { reading: 'offered', detail: `bypass_actors: ${actors.length} configured` }; + return { reading: 'not-offered', detail: `bypass_actors: ${JSON.stringify(actors)} — nobody is offered the bypass-rules option` }; +} + +/** Two ways to red: the remedy stops naming a path (#19344's defect), or the ruleset is READ to offer none. */ +export function remedyPathVerdict({ remedy, ruleset }) { + const names = remedy.includes(BYPASS_OPTION_PHRASE); + const { reading, detail } = bypassActorReading(ruleset); + return { ok: names && reading !== 'not-offered', names, reading, detail }; +} + /** * The words a reader acts on for this leg. Returns '' on the `pull_request` * leg. Every entry prints WHAT WAS READ — the two numbers, their sum and the @@ -2003,7 +2038,11 @@ export function renderSizeVerdict(verdict) { ' alone does NOT dequeue it) and park it there — parked outside the queue is the SAFE state.', ' 2. Then a HUMAN MERGE — the same terminal a governed diff has: ACCEPT on the card,', ' `needs-user-decision` on the PR, a final 维护者速读, review requested from GOVERNED_APPROVERS', - ` (${GOVERNED_APPROVERS.join(', ')}); the maintainer's own click lands it (人工直合).`, + ` (${GOVERNED_APPROVERS.join(', ')}); the maintainer's own click lands it (人工直合) — and that`, + ' click is the Merge button\'s bypass-rules option, offered only while ruleset `main` configures a', + ' bypass actor — ⛔ NOT a second Merge button: `main` mandates the queue and requires this check, so', + ' with none configured every re-enqueue comes back here (#19344). The audit log records the bypass', + ' and `check-governed-merges` lists such a landing on size.', ' ⛔ An authorized APPROVED review does NOT lift this limb the way it lifts a Tier H path, and no', ' review of record does either: the landed predicate says a human MERGE, and widening it is the', ' maintainer\'s one-line decision in the sibling, not this file\'s.', @@ -3851,6 +3890,25 @@ export async function selfTest() { assert('the-workflow-is-readable-for-the-size-scope-pin', false, String(error?.message ?? error).split('\n')[0]); } + // ── ⭐ #19344: the remedy names a path the RULESET actually offers ──────── + // + // RECORDED, not live: this self-test is the required guard job's FIRST step, + // declared offline, and a live read answers `unreadable` from every token CI + // can hold — a network dependency asserting nothing. ⛔ And a pinned phrase + // split across two wrapped array entries can never match: keep it on ONE. + battery('⭐ #19344: the remedy names a path the ruleset actually offers'); + // GET /repos/objectstack-ai/objectstack/rulesets/12119582, read 2026-09-20 by + // an ordinary seat token: HTTP 200, and NO `bypass_actors` key at all. + const RULESET_19344 = Object.freeze({ id: 12119582, name: 'main', enforcement: 'active', current_user_can_bypass: 'never' }); + const judgeRemedy = (remedy, over = {}) => remedyPathVerdict({ remedy, ruleset: { ...RULESET_19344, ...over } }); + const sizeRemedy = renderSizeVerdict(overOne); + const recorded = judgeRemedy(sizeRemedy); + console.log(` ℹ #19344 ruleset reading — ${recorded.detail}`); + assert('⭐ all-four-remedies-NAME-the-bypass-rules-option-and-keep-人工直合-as-the-name-of-the-act', recorded.names && judgeRemedy(refusalText).names && judgeRemedy(warnText).names && sizeRemedy.includes('人工直合') && /BYPASS-RULES option/.test(sizeOwnSource), sizeRemedy); + assert('⛔ a-field-this-token-cannot-see-is-UNREADABLE-and-PASSES-asserting-no-path-it-did-not-read', recorded.reading === 'unreadable' && recorded.ok === true && /unreadable with this token/.test(recorded.detail), recorded.detail); + assert('⛔ bypass_actors-PRESENT-and-EMPTY-REDS-the-remedy-names-a-path-the-ruleset-does-not-offer', judgeRemedy(sizeRemedy, { bypass_actors: [] }).ok === false && judgeRemedy(sizeRemedy, { bypass_actors: null }).reading === 'not-offered'); + assert('⭐ one-configured-bypass-actor-makes-the-named-path-REACHABLE-and-the-pin-clears', judgeRemedy(sizeRemedy, { bypass_actors: [{ actor_type: 'RepositoryRole', bypass_mode: 'pull_request' }] }).ok === true); + assert('⛔ and-a-remedy-drifting-back-to-a-bare-maintainer-click-REDS-even-where-the-path-IS-offered', judgeRemedy("the maintainer's own click lands it (人工直合).", { bypass_actors: [{ actor_id: 5 }] }).ok === false); // ── the WIRING pin: the workflow still spells this context name ────────── // @@ -4443,7 +4501,9 @@ export async function selfTest() { 'on the governed code; an authorized approval lifting nothing from the size; a certified regeneration lifting ' + 'nothing from it either; an unreadable size or a pull object without the pair FAIL-CLOSED on exit 9; the ' + 'pull_request leg silent and read-free; and the three-leg exit precedence (governed, size, carrier) pinned on ' + - 'every combination.', + 'every combination — and the #19344 remedy pin: every limb names the Merge button\'s bypass-rules option, ' + + 'judged against the recorded ruleset reading, red on a present-and-empty `bypass_actors` and on a remedy ' + + 'drifting back to a bare click, and passing with the reading PRINTED when the field is unreadable.', ); selfTestReachedVerdict = true;