Skip to content

fix(pm): the queue guard's remedy names the Merge button's bypass-rules option, and a pin reads the ruleset - #19448

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-19344-size-limb-remedy-names-bypass-path
Sep 20, 2026
Merged

os-steve merged 1 commit into
mainfrom
claude/issue-19344-size-limb-remedy-names-bypass-path

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes #19344

Clause-②: no

Item 6 of the maintainer's ratified directive — verbatim 「1 2 3 4 5 6 8」 (record 5750077963, relayed onto this card as comment 5750078192) — narrowed by the director's pointer 5749924838: route (a) was taken in person, PR #19024 merged by bypass (merged_by os-zhuang, squash 2277d1f). What remained is exactly what this PR does — the guard's remedy sentences name a path that EXISTS on ruleset main, and a pin fails when they name one it does not offer.

The defect

Ruleset main (id 12119582, enforcement active, target the default branch) carries a merge_queue rule and lists Governed Surface Queue Guard among its seven required contexts. So the only Merge button an ordinary account is offered is "Merge when ready" = enqueue, and the size limb then refuses the queued group. The remedy prescribed 人工直合 — "the maintainer's own click" — without naming WHICH click, and the only click 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.

Before / after — the four sentences

Each keeps 人工直合 as the NAME of the act (the 2026-09-18 ruling stands untouched); what changes is the description of how the act is reached.

1. The header sentence (:410, the size limb's own contract)

Before:

 * maintainer's own click (人工直合). An authorized APPROVED review lifts a

After: the same opener, then — 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.

2. The governed limb's early warning (renderGuardVerdict, Tier H block)

Before:

Unapproved, the maintainer's own direct merge (人工直合) is
the only landing this pull request has.

After:

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).

3. The governed limb's refusal (renderGuardVerdict, remedy item 2) — same replacement, plus "the audit log records it".

4. The SIZE limb's refusal (renderSizeVerdict, remedy item 2) — rendered on the #19024 shape:

        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
           (os-zhuang, hotlong); 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.

The ruleset reading this seat measured

GET /repos/objectstack-ai/objectstack/rulesets/12119582, with this seat's token, on the day this PR was written: HTTP 200, and the response carries no bypass_actors key at all — not null, absent. The keys it does return are id name target source_type source enforcement conditions rules node_id created_at updated_at current_user_can_bypass _links, and current_user_can_bypass reads "never".

That is a different fact and is recorded beside it: it says THIS token is not itself a bypass actor, which is true of every agent seat and says nothing about whether the ruleset configures one for the maintainer. The director's earlier pointer read the field as null; this seat's read gets no key. Both are "cannot conclude", and the pin treats them differently only in what it prints.

check-required-contexts.mjs's measured header is the authority on why: the ruleset endpoints answer 200 for metadata=read, but the bypass roster is an administration field, and administration is not one of the 17 permissions a workflow may grant its GITHUB_TOKEN. So no token this repository's CI can hold will ever read bypass_actors.

The pin, and its three verdicts

One new battery in --self-test, five cases, driving two new pure exports (bypassActorReading, remedyPathVerdict) over a frozen copy of the measured response:

bypass_actors as read reading pin
key absent (this seat's and every Actions token's answer) unreadable PASSES, printing bypass_actors: unreadable with this token (the key is absent); current_user_can_bypass: "never"
present and [] or null not-offered REDS — the remedy names a path the ruleset does not offer
present with at least one actor offered PASSES

Two ways to red, not one: the ruleset is READ to offer none, or the remedy stops naming a path at all — which is the defect this card filed, and a pin that only checked the first would sit green through it. The self-test prints the reading on every run, so "unreadable" is never a silent pass. ⛔ It never asserts a path from a field it did not read, and ⛔ it never reds CI on a permission difference.

Recorded, not live — the four axes

The card allowed either a live read or a recorded fixture. Recorded, and the live read deliberately not added.

  • 实际业务需求 — measured, not supposed: bypass_actors is unreadable to this seat's token AND to any Actions token (above). A live read wired into this self-test would therefore answer unreadable on every CI run in existence — it would assert nothing, on every run, while adding a network call. The real need is that the remedy sentence names a reachable path; the only party who can verify reachability is the maintainer, and their verification already happened (the revert(spec): take back the declaration-text snapshot, restore the 27 signature hashes #19024 bypass merge). A recorded reading is what that evidence looks like in this file.
  • 项目长远合理性 — this self-test is the first step of the required Governed Surface Queue Guard job, run under bash -e as the precondition for trusting the guard, and its own usage line declares it offline, no network, no git. Making a merge precondition depend on api.github.com reachability and on a token's permission tier is the permanently-red-gate shape this repo has already retired. The precedent is in-repo and exact: check-required-contexts.mjs keeps a frozen RULESET_SNAPSHOT in its self-test and leaves the live diff to a report-only mode that never runs in CI, for the same structural reason.
  • 防 AI 写代码犯错 — a live read is the lenient-consumer shape here: it passes for every token that cannot see the field, so the assertion would be phantom and the next author would read green as "the path is reachable". The recorded form makes the claim declared and falsifiable — change the remedy, and the pin demands a reading that offers the path; the reading is printed rather than swallowed.
  • 创业阶段不扩散需求 — one battery, two pure exports, one frozen object, no new entry point, no new script, no new workflow, no widened token scope. Net +60 lines, exactly the budget.

The fixture's cost is drift, and it is bounded on purpose: it carries its provenance in the comment above it (endpoint, id, read date, token class), its reading is printed on every self-test run rather than asserted silently, and the field it records is one no CI token can re-read anyway — so a live read would not have detected drift either.

Evidence

  • node scripts/pm/check-governed-queue-guard.mjs --self-test :: exit 0 — 301 cases (296 before; +5 is exactly the new battery), and the run prints ℹ #19344 ruleset reading — bypass_actors: unreadable with this token (the key is absent); current_user_can_bypass: "never".
  • Ablation (scripts/ablation-replace.mjs, wrap mode, on the committed state): anchor click is the Merge button's bypass-rules option replaced in renderSizeVerdict — on-disk proof anchor 1 -> 0, blob 5b75964229ac -> e6ee4e00f482 — self-test went red, 3 of 301, naming all-four-remedies-NAME-the-bypass-rules-option…, a-field-this-token-cannot-see-is-UNREADABLE-and-PASSES… and one-configured-bypass-actor-makes-the-named-path-REACHABLE…. Restore proven byte-identical: blob == HEAD (5b75964229ac) and git diff HEAD empty. The direction is the expected one (red), and it reds via BOTH limbs of the contract, which is what "two ways to red" means.
  • Derived gate union (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, 31 families): 30 run with exit captured before any pipe, all exit 0; pnpm check:pm-dispatch-gates was still running when this PR was opened and is reported in the card comment. --ran reconciliation: 31 derived, 30 accounted with real exit codes, 0 NOT-MEASURED among them.
  • node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-governed-queue-guard.mjs :: exit 0 — NOT governed; ordinary queue landing applies. --pr 19024 :: exit 3 — the size-decided landing is listed, which is the recognition the remedy text now points at.
  • PM mechanism assumptions: (1) confirmed — the three sentences read as described on origin/main; (2) PR fix(pm): repair two carriers still spelling the superseded references-tier boundary #19379's region :268–:294 is untouched by this diff; (3) confirmed — the self-test is wired at .github/workflows/governed-surface-guard.yml, extended in place, no second entry point; (4) confirmed above.

Acceptance notes

  • The ruleset response carries current_user_can_bypass, which an ordinary token CAN read and which directly answers "is the bypass option offered to the account asking". Nothing in this repo probes it live; it is recorded in this fixture only. Observation, not filed — it is a capability nobody has pulled on, and the four-axis call above is not to add a live probe to a required precondition.
  • check-required-contexts.mjs's RULESET_SNAPSHOT records the 2026-08-18 reading with six required contexts; the live ruleset now carries seven (Governed Surface Queue Guard joined since). That file's assertions are deliberately written on the SHAPE, not on membership, and its header says so, so nothing is wrong — but the snapshot is a frozen historical reading and reads at a glance like a current one. Observation, not filed.
  • ⛔ Not touched, per the card's own "Not this card": the 5000-line threshold (it lives in check-governed-merges.mjs, imported here), the required-context set, the 「THIRD leg」 header at :268–:294 (PR fix(pm): repair two carriers still spelling the superseded references-tier boundary #19379's region), and any workflow.

Generated by Claude Code

…es option, and a pin reads the ruleset

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 <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1c29ef768450614545b4bf9c1419aa54371020e3

① Derived judgments

  • Published accept set / public surface: none moves — one PM-loop gate script (scripts/pm/check-governed-queue-guard.mjs, +67 / −7, net +60 against the ≤ +60 budget, spent exactly): four remedy sentences (:410 header, :1379 Tier H warning, :1471 refusal item 2, :2006 size refusal item 2) and one self-test battery over two pure exports; no threshold, no required-set, no workflow, no second entry point. check-governed-merges --pr 19448: NOT governed — ordinary queue landing. Judged correct.
  • The fix, read line by line: every remedy now names the path that exists on ruleset main — the Merge button's bypass-rules option, offered only while a bypass actor is configured — and keeps 人工直合 as the NAME of that act, which is what item 6 of the maintainer's 「1 2 3 4 5 6 8」 (5750077963, narrowed by 5749924838) ratified. bypassActorReading answers three ways (offered / not-offered / unreadable) and remedyPathVerdict reds on exactly two: a remedy that stops naming a path, or a ruleset READ to offer none; a field the token cannot see PASSES with its reading printed — ⛔ never a path asserted from an unread field, ⛔ never a CI red on a permission difference. The recorded fixture (the measured GET /rulesets/12119582: no bypass_actors key, current_user_can_bypass: "never") is the primary source by a four-axis argument the seat accepts: the field sits under administration, which no Actions token can hold, so a live read would answer unreadable on every CI run while adding a network dependency to the required job's first step — the permanently-red-gate shape this repo retired; the in-repo precedent (check-required-contexts.mjs's frozen snapshot) is exact. Judged correct.
  • Evidence, seat-run on this head in a detached worktree: --self-test exit 0 — 301 cases (296 on origin/main + the 5-case battery; SELF_TEST_BATTERY_FLOOR 22 → 23), the reading line printed 「bypass_actors: unreadable with this token」; merge-tree vs origin/main 57ceb9d: 0 conflicts; CI on 1c29ef7: 35 check runs, 1 still running, 0 red. The dev's ablation (the size-remedy phrase replaced from the committed state → 3 of 301 red naming the three pins; blob restored and proven) and the 31-family derived union 31 / 31 exit 0 with --ran reconciled, check:pm-dispatch-gates exit 0 captured.

② Semver level

None — nothing published; skip-changeset on the PR is the correct declaration. Judged correct.

③ Boundary flags

open_questions empty. All four mechanism assumptions confirmed by measurement (the four sentences unchanged at dispatch; PR #19379's header region :268–:294 untouched by this diff — region-parallel under 甲, no merge owed as #19379 has not landed; the self-test is the required job's first step, extended not duplicated; NOT governed on the PR). Deviations read and accepted (the detached slow gate waited on in-turn by PID; net lines exactly at the budget, paid in comment prose). Two noted, not filed observations with a named carrier (current_user_can_bypass as a strictly better live probe for the report-only --verify-required-set mode; the 2026-08-18 six-context snapshot beside today's seven) — observations, not defects, correctly not filed.

Implemented-by: claude/issue-19344-size-limb-remedy-names-bypass-path
Reviewed-by: session_017ETYWqMQD4qMtZzAGovWNi

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants