|
407 | 407 | * landed predicate says a human MERGE — 「人工审核」 landed as the same terminal |
408 | 408 | * a governed diff has (ACCEPT on the card, `needs-user-decision` on the PR, a |
409 | 409 | * final 维护者速读, review requested from `GOVERNED_APPROVERS`) and the |
410 | | - * maintainer's own click (人工直合). An authorized APPROVED review lifts a |
| 410 | + * maintainer's own click (人工直合). ⭐ #19344 — THAT CLICK IS ONE BUTTON |
| 411 | + * OPTION and this text now names it: `main` mandates the queue and requires |
| 412 | + * this check, so the only Merge that is not an enqueue is the Merge button's |
| 413 | + * BYPASS-RULES option, offered only while the ruleset configures a bypass |
| 414 | + * actor. While none was, the remedy named a terminal nobody could reach and |
| 415 | + * PR #19024 was enqueued and refused three times. That it IS offered is a |
| 416 | + * ruleset fact the pin reads. An authorized APPROVED review lifts a |
411 | 417 | * Tier H path because the 2026-08-27 ruling said so of PATHS; nothing has |
412 | 418 | * said it of the NUMBER, and widening a governance gate past its own ruling is |
413 | 419 | * how gates acquire policy nobody agreed to. Widening it is a one-line |
@@ -547,11 +553,12 @@ const SELF_TEST_BATTERIES = Object.freeze({ |
547 | 553 | '⭐ #18020 → #19133 Tier S: a review of record, not an approval': 43, |
548 | 554 | '⭐ #18701: the record lives on the PR or its card, and BOTH are read': 14, |
549 | 555 | '⛔ #19036: the SIZE line at the queue — imported, per queued PR, fail-closed': 30, |
| 556 | + '⭐ #19344: the remedy names a path the ruleset actually offers': 5, |
550 | 557 | }); |
551 | 558 |
|
552 | 559 | // DELETING an entry silences that battery's floor exactly as effectively as |
553 | 560 | // zeroing it, so the roster's own size is pinned too. |
554 | | -const SELF_TEST_BATTERY_FLOOR = 22; |
| 561 | +const SELF_TEST_BATTERY_FLOOR = 23; |
555 | 562 |
|
556 | 563 | // The key an assertion is filed under when no battery is open. It is not a |
557 | 564 | // declared battery, so it reds by the same set difference rather than silently |
@@ -1376,8 +1383,9 @@ export function renderGuardVerdict(verdict) { |
1376 | 1383 | ` ✅ What a seat DOES do once an account in GOVERNED_APPROVERS (${GOVERNED_APPROVERS.join(', ')}) has APPROVED it,`, |
1377 | 1384 | ' on ANY commit: the CLAIMING SEAT lands it — ruling C (#17971, maintainer 2026-09-13, verbatim', |
1378 | 1385 | ' 「C. approve 后不管后续改动都由席位落地:」), 「席位落地 = 过落地前检、清标、ready、', |
1379 | | - ' auto-merge,踢出/变基同法。」 Unapproved, the maintainer\'s own direct merge (人工直合) is', |
1380 | | - ' the only landing this pull request has.', |
| 1386 | + ' auto-merge,踢出/变基同法。」 Unapproved, the maintainer\'s own direct merge (人工直合) is the', |
| 1387 | + ' only landing this pull request has, and it IS the Merge button\'s bypass-rules option —', |
| 1388 | + ' offered only while ruleset `main` configures a bypass actor (#19344).', |
1381 | 1389 | ); |
1382 | 1390 | } |
1383 | 1391 | if (tierS.length > 0) { |
@@ -1469,7 +1477,8 @@ export function renderGuardVerdict(verdict) { |
1469 | 1477 | ' 要卡最新的提交。」); the authorized set is still the 2026-08-27 one (「os-zhuang hotlong 批准', |
1470 | 1478 | ' 算数」). ⛔ An agent seat never submits that approval, under any account — the post-merge audit', |
1471 | 1479 | ' reads the approver too. ⛔ Unapproved, the maintainer\'s own direct merge (人工直合) is the only', |
1472 | | - ' landing this pull request has.', |
| 1480 | + ' landing this pull request has, and it IS the Merge button\'s bypass-rules option — offered only', |
| 1481 | + ' while ruleset `main` configures a bypass actor (#19344); the audit log records it.', |
1473 | 1482 | ); |
1474 | 1483 | if (verdict.entries.some((e) => e.record !== undefined)) { |
1475 | 1484 | lines.push( |
@@ -1925,6 +1934,32 @@ export async function runSizeGuard({ event, rows, namedPull = null, fetchPull }) |
1925 | 1934 | return sizeGuardVerdict({ event, pulls, readings, apiCalls }); |
1926 | 1935 | } |
1927 | 1936 |
|
| 1937 | +// ── #19344: the remedy's terminal is a RULESET fact, so it is READ ───────── |
| 1938 | +// |
| 1939 | +// GitHub offers it only to an account ruleset `main` lists as a bypass actor, |
| 1940 | +// and `bypass_actors` is withheld below `administration`, so a read answers |
| 1941 | +// three ways and only ONE is a refusal. ⛔ Never assert a path from a field that |
| 1942 | +// was not read (a permission difference must not red CI), ⛔ never pass mute. |
| 1943 | +export const BYPASS_OPTION_PHRASE = "Merge button's bypass-rules option"; |
| 1944 | + |
| 1945 | +/** `offered` | `not-offered` | `unreadable`, with the words a log should carry. */ |
| 1946 | +export function bypassActorReading(ruleset) { |
| 1947 | + const read = ruleset && typeof ruleset === 'object' ? ruleset : {}; |
| 1948 | + if (!Object.prototype.hasOwnProperty.call(read, 'bypass_actors')) { |
| 1949 | + 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)}` }; |
| 1950 | + } |
| 1951 | + const actors = read.bypass_actors; |
| 1952 | + if (Array.isArray(actors) && actors.length > 0) return { reading: 'offered', detail: `bypass_actors: ${actors.length} configured` }; |
| 1953 | + return { reading: 'not-offered', detail: `bypass_actors: ${JSON.stringify(actors)} — nobody is offered the bypass-rules option` }; |
| 1954 | +} |
| 1955 | + |
| 1956 | +/** Two ways to red: the remedy stops naming a path (#19344's defect), or the ruleset is READ to offer none. */ |
| 1957 | +export function remedyPathVerdict({ remedy, ruleset }) { |
| 1958 | + const names = remedy.includes(BYPASS_OPTION_PHRASE); |
| 1959 | + const { reading, detail } = bypassActorReading(ruleset); |
| 1960 | + return { ok: names && reading !== 'not-offered', names, reading, detail }; |
| 1961 | +} |
| 1962 | + |
1928 | 1963 | /** |
1929 | 1964 | * The words a reader acts on for this leg. Returns '' on the `pull_request` |
1930 | 1965 | * leg. Every entry prints WHAT WAS READ — the two numbers, their sum and the |
@@ -2003,7 +2038,11 @@ export function renderSizeVerdict(verdict) { |
2003 | 2038 | ' alone does NOT dequeue it) and park it there — parked outside the queue is the SAFE state.', |
2004 | 2039 | ' 2. Then a HUMAN MERGE — the same terminal a governed diff has: ACCEPT on the card,', |
2005 | 2040 | ' `needs-user-decision` on the PR, a final 维护者速读, review requested from GOVERNED_APPROVERS', |
2006 | | - ` (${GOVERNED_APPROVERS.join(', ')}); the maintainer's own click lands it (人工直合).`, |
| 2041 | + ` (${GOVERNED_APPROVERS.join(', ')}); the maintainer's own click lands it (人工直合) — and that`, |
| 2042 | + ' click is the Merge button\'s bypass-rules option, offered only while ruleset `main` configures a', |
| 2043 | + ' bypass actor — ⛔ NOT a second Merge button: `main` mandates the queue and requires this check, so', |
| 2044 | + ' with none configured every re-enqueue comes back here (#19344). The audit log records the bypass', |
| 2045 | + ' and `check-governed-merges` lists such a landing on size.', |
2007 | 2046 | ' ⛔ An authorized APPROVED review does NOT lift this limb the way it lifts a Tier H path, and no', |
2008 | 2047 | ' review of record does either: the landed predicate says a human MERGE, and widening it is the', |
2009 | 2048 | ' maintainer\'s one-line decision in the sibling, not this file\'s.', |
@@ -3851,6 +3890,25 @@ export async function selfTest() { |
3851 | 3890 | assert('the-workflow-is-readable-for-the-size-scope-pin', false, String(error?.message ?? error).split('\n')[0]); |
3852 | 3891 | } |
3853 | 3892 |
|
| 3893 | + // ── ⭐ #19344: the remedy names a path the RULESET actually offers ──────── |
| 3894 | + // |
| 3895 | + // RECORDED, not live: this self-test is the required guard job's FIRST step, |
| 3896 | + // declared offline, and a live read answers `unreadable` from every token CI |
| 3897 | + // can hold — a network dependency asserting nothing. ⛔ And a pinned phrase |
| 3898 | + // split across two wrapped array entries can never match: keep it on ONE. |
| 3899 | + battery('⭐ #19344: the remedy names a path the ruleset actually offers'); |
| 3900 | + // GET /repos/objectstack-ai/objectstack/rulesets/12119582, read 2026-09-20 by |
| 3901 | + // an ordinary seat token: HTTP 200, and NO `bypass_actors` key at all. |
| 3902 | + const RULESET_19344 = Object.freeze({ id: 12119582, name: 'main', enforcement: 'active', current_user_can_bypass: 'never' }); |
| 3903 | + const judgeRemedy = (remedy, over = {}) => remedyPathVerdict({ remedy, ruleset: { ...RULESET_19344, ...over } }); |
| 3904 | + const sizeRemedy = renderSizeVerdict(overOne); |
| 3905 | + const recorded = judgeRemedy(sizeRemedy); |
| 3906 | + console.log(` ℹ #19344 ruleset reading — ${recorded.detail}`); |
| 3907 | + 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); |
| 3908 | + 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); |
| 3909 | + 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'); |
| 3910 | + 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); |
| 3911 | + 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); |
3854 | 3912 |
|
3855 | 3913 | // ── the WIRING pin: the workflow still spells this context name ────────── |
3856 | 3914 | // |
@@ -4443,7 +4501,9 @@ export async function selfTest() { |
4443 | 4501 | 'on the governed code; an authorized approval lifting nothing from the size; a certified regeneration lifting ' + |
4444 | 4502 | 'nothing from it either; an unreadable size or a pull object without the pair FAIL-CLOSED on exit 9; the ' + |
4445 | 4503 | 'pull_request leg silent and read-free; and the three-leg exit precedence (governed, size, carrier) pinned on ' + |
4446 | | - 'every combination.', |
| 4504 | + 'every combination — and the #19344 remedy pin: every limb names the Merge button\'s bypass-rules option, ' + |
| 4505 | + 'judged against the recorded ruleset reading, red on a present-and-empty `bypass_actors` and on a remedy ' + |
| 4506 | + 'drifting back to a bare click, and passing with the reading PRINTED when the field is unreadable.', |
4447 | 4507 | ); |
4448 | 4508 |
|
4449 | 4509 | selfTestReachedVerdict = true; |
|
0 commit comments