diff --git a/AGENTS.md b/AGENTS.md index 683836374..cf3a47647 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -169,7 +169,9 @@ What this buys is presence and a name from a closed list, not truth: with one sh - **The label awaiting-approval is retired.** A PASS at the tip goes straight to the queue through the enqueue guard (#687); no pull request waits for an approval click and the label is not set again. - **A pull request enters the queue with its issue and its `agent-pr` label.** On a branch this repository owns, the enqueue guard also refuses one whose body names no issue (`Fixes #` / `Closes #`) and one without the `agent-pr` label (#718), so "one issue, one pull request" is checked rather than remembered. It refuses a **live, non-draft** request whose body carries no `Owner:` line or no `Reviewer:` line in its first 20 lines, or a `Reviewer:` that is neither a `reviewers` login nor a `writers` Bot name, or that names nobody who can record a PASS — a Bot name counts only through the one identity every Bot writes as, `Birfy`, so it names nobody while that account is outside `reviewers` (#947); a `Reviewer:` that names the same Bot as the `Owner:` is refused too, compared name by name after commas, a leading `@` and case are normalised, because one Bot owning and reviewing the same pull request is the shape #940 had with no lines at all (#951); both lines also need a value the policy knows — a `Reviewer:` outside both vocabularies and an `Owner:` outside `writers` are refused, and a line with no value is the missing line, so an empty `Owner:` cannot clear the comparison (#951); the refusal names the missing line, or the word and the list it missed (#943, #947, #951; #940 merged in about seven minutes with the largest diff of the day and neither line). A **merged** pull request is never re-judged: this check also runs on every comment, and history is not a request. Two exemptions: a mechanical release PR, which carries neither by design, and a **fork** (`pull.head.repo.full_name` is not this repository), whose head we cannot push to and where the contributor keeps their own workflow. -**On the code fork `Birfy/FlyBot`, where this team's pull requests actually merge, the gate is a ruleset, not a queue** (#812). Once the ruleset from `--apply` is on that repository, its `main` requires two checks, `tests-ok` and `enqueue-guard`, with `bypass_actors: []`, so `PUT /pulls//merge` is refused — 405/422 — while the head carries a `hold`, no PASS naming that exact tip, a PASS that lists no `Ran:`, no `agent-pr`, or a body naming no issue. The required check is written by [`enqueue-guard.yml`](.github/workflows/enqueue-guard.yml), from code taken from `main` and never from the pull request, on every event that can change the answer for a fixed head: a push, a label added or removed, a comment, a review body, or a manual run. It calls [`enqueue-guard.mjs`](.github/scripts/enqueue-guard.mjs)'s own decision — the same function that prints the command — so the check and the command cannot disagree; a new commit is a new head and a new check run, and a green check never carries over. The ruleset is [`merge-gate-ruleset.json`](.github/agent/merge-gate-ruleset.json): `node .github/scripts/gate-ruleset.mjs --check` reads the live state and writes nothing, while `--apply` and `--rollback` change repository protection and are the owner's call, not a Bot's. The order is not optional: the check must already report on the open pull requests **before** the ruleset goes on, or every one of them reads "Expected — waiting for status" and cannot merge. +**`enqueue-guard` writes a check on the head, and the queue's ruleset does not require it** (#812, re-read 2026-10-24). Team pull requests merge upstream in `ThinkFlowLab/FlyBot` through the `main merge queue`, whose ruleset requires three contexts — `Node 24 · DSH 0.2.0-rc.2`, `tests-ok` and `merge-gate`; the paragraph below (#960) is how `merge-gate` reads the body the queue does not read. `enqueue-guard` still runs on that head, and on the queue entry: [`enqueue-guard.yml`](.github/workflows/enqueue-guard.yml) takes its code from `main` and never from the pull request, on every event that can change the answer for a fixed head — a push, a label added or removed, a comment, a review body, or a manual run — and calls [`enqueue-guard.mjs`](.github/scripts/enqueue-guard.mjs)'s own decision, the same function that prints the command, so the check and the command cannot disagree. It refuses a head that carries a `hold`, that has no PASS naming that exact tip, a PASS that lists no `Ran:`, a pull request with no `agent-pr`, or a body that names no issue; a new commit is a new head and a new check run, and a green check never carries over. + +**Historical: the fork's own ruleset (#812, used until 2026-10-23).** While the team's pull requests merged on the code fork `Birfy/FlyBot`, that repository's `main` required two contexts — `tests-ok` and `enqueue-guard` — with `bypass_actors: []`, so `PUT /pulls//merge` was refused (405/422); [`merge-gate-ruleset.json`](.github/agent/merge-gate-ruleset.json) is that protection, and `node .github/scripts/gate-ruleset.mjs --check` still reads a repository's live state and writes nothing. The fork is an archive now — nothing merges into it — so those two contexts are not this repository's gate, and `--apply` / `--rollback` are not part of any routine — no Bot runs them — and the ordering instruction that came with them (the check had to report on every open pull request before the ruleset went on) described that fork alone. **The queue does not read the `enqueue-guard` context here, so the required `merge-gate` reads the body too** (#960). On `merge_group` the queue entry is judged by `merge-gate.mjs`, which now also calls the guard's own `guard()` for the pull request behind the entry: the `Fixes #` line, the `Owner:`/`Reviewer:` lines and the `agent-pr` label are refused there with the guard's own reasons, and `fork` is read from `pull.head.repo.full_name` so an external pull request keeps the exemption #711/#713 gives it. It is **best effort by design**: when the queue branch names no pull request, or the API no longer returns it, the run logs one line and lets the entry through — a wedged queue is worse than a missed judgement. `enqueue-guard.yml` also runs on `merge_group` and writes its check on the entry commit the queue merges, so the context exists where a queue rule would look for it; the `main merge queue` ruleset is **not** changed by this — it still requires three contexts (`Node 24 · DSH 0.2.0-rc.2`, `tests-ok`, `merge-gate`), and adding `enqueue-guard` stays the separate decision #960 describes.