Repository navigation
feat(automation): refuse a merge on the fork with a required check (#812) - #829
Conversation
/commits/<head>/check-runs answers { total_count, check_runs }, so
`gh api --jq '.[]'` printed a number and then the whole array. planCheck saw
neither with a name, and every event POSTed another enqueue-guard run for the
same head instead of PATCHing the one already there: the update branch was
unreachable, and the step-1 proof in #812 would have shown a second run rather
than a flip. The read is `.check_runs[]` now, the test fake answers the envelope
and slices it the way --jq does, so the wrong filter is visible in the suite, and
a test drives two judgments of one head into one POST and one PATCH.
Two more from the same review. --rollback proves the ruleset is gone by reading
the list again, and its test asserted the fake's own bookkeeping instead: a
fixture that keeps the ruleset listed after the DELETE now fails the rollback.
AGENTS.md says the ruleset is on the fork's main once --apply has run, not
before, because this pull request deliberately does not apply it.
Fixes #812
|
Verdict: PASS at 9e60f74 The INC at The envelope, and one run per head. Putting the old
Rollback. Removing the re-list after The rest of the gate. Numbers in the body match this tip: scripts 315 tests → 309 pass / 4 fail / 2 skip (the four Nothing here blocks the merge. Three things to keep in mind, none of them a code change I would ask for now:
Also: Ran: At 9e60f74: 27/27 in the first; 315 tests with 309 pass / 4 fail / 2 skip in the second (the four are the |
|
enqueue-guard: ALLOW — enqueue #829: no Run at head Written-by: Dev |
Written-by: Dev
Fixes #812
What changed
mainonBirfy/FlyBotgets a gate GitHub enforces, instead of a script somebody has to remember to run. The ruleset that requires the check is in the pull request but not applied by it — see the sequencing at the end..github/workflows/enqueue-guard.yml— onpull_request_target,pull_request_review,issue_comment(created/edited/deleted) and a manual run, it judges the live head with code checked out frommain(ref: main; no pull request code is fetched or run — the comment body is only parsed) and writes the result as a check run namedenqueue-guard.pull_request_target, notpull_request: apull_requestjob from a fork is read-only and could not write the check for the head it judges, and a required context that is never produced leaves that pull request on "Expected" forever (Merge gateis not a required check on the merge queue, so a red gate does not block a merge #639)..github/scripts/gate-check.mjs— turns the decision into that check run. It callsdecidePullinenqueue-guard.mjs(which callsmerge-gate.judgeandenqueue-guard.guard), the same function behind the command the guard prints, so the check and the command cannot disagree — a second copy of the rules is the issue's kill condition. The read takes the endpoint’scheck_runsarray out of the{ total_count, check_runs }envelope, andexternal_idis the head: a label or a comment PATCHes that one run, a new commit is a new head and a new run, and a stale green never answers for a new tip. A refusal is a red check, not a red job;--dry-runjudges and prints and writes nothing..github/scripts/enqueue-guard.mjs—decidePullextracted out ofmain()so both readers share one function; the printed ALLOW/REFUSE output and the command are unchanged (its 26 existing tests pass untouched)..github/agent/merge-gate-ruleset.jsonand.github/scripts/gate-ruleset.mjs— the ruleset body (branchrefs/heads/main,enforcement: active,bypass_actors: [], requiredtests-ok+enqueue-guard,strict_required_status_checks_policy: falseso one merge onmaindoes not force every open pull request to rebase, no approvals required because T2/T3 need none) plus--check(read-only) /--apply/--rollback.--applyre-reads what GitHub saved and fails on a bypass actor,current_user_can_bypassother thannever, a missing context, or strict checks.AGENTS.md→ Merge gates (the fork paragraph says the ruleset is in place once--applyhas run, because this pull request does not apply it),docs/autonomous-dev.md(new subsection), thetesting-layersrow and one changelog fragment.--jq '.[]'on an endpoint that answers{ total_count, check_runs }, so the update branch was unreachable and every event added a run; it is.check_runs[]now and the test fake answers the envelope the way the API does.--rollbacknow has a fixture that keeps the ruleset listed after the DELETE and requires the rollback to fail on it.Out of scope, as #812 says: the upstream ruleset (Release owns it), a merge queue on the fork, backfilling labels on already-merged pull requests.
Verification at the tip
Tip SHA:
9e60f7484beb08dfa0cb1bcbd071bbe117efb7a9Ran:
node --test .github/scripts/gate-check.test.mjs .github/scripts/gate-ruleset.test.mjs→ 27/27 pass (16 + 11).node --test .github/scripts/enqueue-guard.test.mjs .github/scripts/merge-gate.test.mjs .github/scripts/review-gate.test.mjs→ 97/97 pass; the guard's suite is unchanged and green on the refactor.node --test ".github/scripts/*.test.mjs"→ 315 tests: 309 pass, 4 fail, 2 skipped. All 4 are inds-bot-patches.test.mjs, which shells out togit diff --no-index/git apply; this box has nogit, and the same 4 fail on the base commit. The 2 skips saygit and bash are needed. CI has both and runs them.node .github/scripts/testing-layers.mjs --check→ 328 test files in 14 workflows, all classified.node .github/scripts/changelog.mjs check→ 10 fragments well formed.PATH=<shim> GH_TOKEN=… node .github/scripts/gate-ruleset.mjs --check --repo Birfy/FlyBot→NOT GATED — no ruleset named "main merge gate" on refs/heads/main: main is unprotected(exit 1): the read path works against the live API and reproduces the issue's premise.node .github/scripts/gate-check.mjs --dry-runagainst PR ci: Compatibility nightly + local Mac CI (follows #31) #35 on the live API, writing nothing →enqueue-guard: success,enqueue #35: no \hold` and the gate passes, newest conclusionPASS by Birfyatf027324, labelsagent-pr, pass. That run also caught a defect the unit tests could not see: the twoghcall shapes (variadic reads,[args]+ JSON body for writes) were wired the wrong way round, and the real CLI rejected it while the fake accepted both. Fixed inrealGh`; the live dry run is now part of verifying this file.Not run (and why):
actionlint1.7.7 — this box has nocurlandgithub.comis unreachable, so the binary cannot be fetched. The workflow is parsed locally with a YAML parser to confirm the four triggers,permissions.checks: write, theconcurrencygroup and the jobif; CI'spackagejob runsactionlintfor real.npm run verifyin full — nothing undercore/,plugins/orpatches/changes, and.github/scriptsis in neither its lint nor its suite list; the script suites CI runs are the two commands above.--apply/--rollbackagainst the live repository — they change repository protection, which this Bot does not do (AGENTS.md → Hard rules).pull_request_targetandissue_commenttake their workflow file from the default branch, so its first run is after this merges. That is the sequencing below, not an omission.Evidence that the tests pin the change
Every mutation was reverted immediately after:
success→ 6 of the 16 gate-check tests red;planCheckignoringexternal_id→ 1 red (a check for another head is never reused);checks: writechanged tochecks: read→ 1 red;missing label: agent-prrule dropped → 4 red across both suites (2 inenqueue-guard.test.mjs, 2 ingate-check.test.mjs). One rule, two readers — the property The fork'smainhas no ruleset and no branch protection, so a PASS is only advisory and an unreviewed pull request can be squash-merged #812 asks for;--jq '.[]'→ 3 red (the red→green flip on one run, the new envelope case, and the comment-after-refusal case), because the endpoint answers an envelope and that filter yields a number and the whole array;--rollback's list re-read deleted → 1 red (its fixture keeps the ruleset listed after the DELETE and requires the rollback to say so).Merge gates and the switch
Tier T3 (a workflow plus two new scripts under
.github/), so it needs a PASS at this tip; CI runstests-okon the head as usual.Applying the ruleset is deliberately not part of this merge, and the order is not optional:
agent-pr) must showenqueue-guard: failurewith the guard's exact REFUSE sentence, and a complete one must showsuccess; on one head it must also flip red → green without a new commit when the label or the PASS arrives..github/agent/merge-gate-ruleset.json(node .github/scripts/gate-ruleset.mjs --apply --repo Birfy/FlyBot), and--checkmust then pass on the read-back.Applied first, every open pull request reads "Expected — waiting for status" and none can merge.