docs: replace Merge Gate v1 with a satisfiable merge policy - #1408
Conversation
v1 defined nine default-deny admission gates in Notion. None of its eleven implementation tasks shipped: no MERGE_POLICY.md was committed, no merge queue was enabled, branch protection never required the six checks. What ran was an agent reading the Notion page and returning pull requests to draft. Measured result: 61 open PRs, 59 draft, median 44 commits behind main, while 44 other PRs merged in four days with red checks. Work that could not leave draft was re-cut as fresh PRs -- #869 and #1376 are the same change 17 days apart; #1317, #1320 and #1342 were three PRs for one cleanup. Three v1 rules caused it: * "any new commit resets all gates" combined with a zero-commits-behind requirement. Against a main moving ~5.5 commits/day the loop never closes -- rebasing to satisfy freshness discards the approval and preview just earned. * Gates with no satisfying action: a preview READY on the exact SHA (previews get canceled), an approval from a reviewer that skips any PR without one of 26 labels, and a truth gate that failed invalid_payload on every PR lacking a dispatch contract. * Mutual quarantine of overlapping PRs, so every duplicate pair blocked itself permanently. v2 keeps the parts that work -- binding to one issue, required checks, risk classes, the review clock -- and fixes the rest. Reviews are invalidated only by changes to the files reviewed, not by any push. Previews gate only apps/web changes. Overlaps are labeled and assigned with a 72h deadline rather than blocked. Freshness is a clean merge, not zero-behind. Unknown risk defaults to Class B, not C. Adds the rule that would have caught this: any required check failing on more than 50% of PRs over 7 days is automatically demoted to advisory and an issue is opened against its owner. agent-completion/truth-gate was red on ~100% of PRs for weeks, including merged ones. Does not change any workflow or branch protection. Steps 3-6 in "Adopting this" are follow-ups; committing this and retiring the v1 Notion page are what stop the livelock, because the livelock was a document. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited) Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
Agent Completion Truth Gate: BLOCKEDReasons: Machine-readable verdict{
"details": {
"collection_errors": [
"incomplete_linked_issue_contract",
"missing_intent_snapshot",
"missing_agent_run_id",
"missing_agent_login"
],
"invalid_fields": [
"policy.agent_login",
"policy.run_id"
]
},
"reasons": [
"invalid_payload"
],
"verdict": "blocked"
} |
This PR's own truth-gate failure proves the gate cannot be repaired, only removedI expected this PR to be the first to resolve The verdict did move. On #1377 it was: "collection_errors": ["incomplete_linked_issue_contract", "missing_linked_issue",
"missing_closing_issue_reference", ...]Here it is: "collection_errors": ["incomplete_linked_issue_contract", "missing_intent_snapshot",
"missing_agent_run_id", "missing_agent_login"]
But it cannot help any well-formed pull request, because of a contradiction between two required checks:
So satisfying The two gates are in direct opposition, and no change to the applicability rule resolves it: the arming condition is mandatory under a different check. That retires the repair option. Narrowing applicability was the right first move and it removed a real false-positive class, but the apparatus has to come out. Scope measured against current
Filed as a follow-up to this PR rather than folded into it: removing ~7,400 lines deserves its own diff and its own revert. Two notes on this PR's other checks, both consistent with the policy it proposes:
Generated by Claude Code |
Correcting my earlier comment: the gate is repairable, and #1409 repairs itMy comment above concluded "the apparatus has to come out" and scoped a ~7,400-line removal. That conclusion was wrong, and I'd rather say so before anyone acts on it. The argument was that two required checks are in direct opposition — The relevant line was: return login !== 'dependabot[bot]' &&
(issueDispatch || (pullProvenance && Boolean(selectedIssue)));
This was already filed as #1401 on Aug 5, with the same diagnosis and a proposed patch. I missed it when I wrote the comment above. Two things worth carrying forward:
Nothing in this changes what #1408 itself does. The policy file stands on its own; only the follow-up it named — retiring the apparatus — is withdrawn in favour of #1409. Whether to keep the truth gate at all is still yours to decide, but it should be decided on whether the gate earns its complexity, not on the belief that it cannot be made to pass. Generated by Claude Code |
|
@coderabbitai full review Marked ready for review, so triggering the review it never got — the auto-review skipped at open time for want of a qualifying label, and Ahead of that, two findings from checking gate 2 against the check runs this repository actually produces. Both are the failure mode this document is named for — a gate naming something that cannot be satisfied — so they seem worth catching before v2 ships rather than after. 1. Five of gate 2's six required checks do not exist under those namesAdoption step 3 says "Enable branch protection: the six checks from gate 2." Branch protection matches check-run / status context names verbatim. Enumerating every check on #1381 (30) and on this PR (46), here is what those six resolve to:
Only Worth spelling the list out verbatim in gate 2, since the whole point of v2 is that a gate names something real. 2.
|
|
|
Gate 2 listed `CI`, `Coverage`, `CodeQL`, `Security`, `Secret Scan`, `Dependency Review` -- inherited verbatim from v1. Those are workflow names. Branch protection matches check-run names (a job's `name:`, or its job id), so of that list only `CodeQL` can actually be selected. Anyone following adoption step 3 would find the strings absent from GitHub's picker, which is plausibly why MG-2 was never done. That is the v1 failure mode reproduced inside its replacement: a gate naming things that cannot be satisfied. Replaced with the contexts actually observed on live pull requests. Also splits out conditionally-required checks. `Generate and Upload Coverage` does not run on documentation-only changes -- it ran on #1377 and is absent from #1408. Requiring it unconditionally would leave docs pull requests permanently pending, which blocks harder than failing, since a check that never reports never resolves. Same for `E2E Pipeline Tests` and the JavaScript security scan. Adds the rule that would have caught this: before adding a check to branch protection, confirm the exact string appears in the picker and that it reports on a documentation-only pull request. Raised by the CodeRabbit review on #1408. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YcHjCZ6pGn6A5BeeoZ6eZi
|
Checked Confirmed correct
Wrong:
|
| head | Security Scan - javascript |
|---|---|
92a4e26 (docs-only) |
success — ran to completion |
27b2ecf (docs-only) |
in_progress — running now |
It has never failed to report here. There is no deadlock risk, so the reason given for demoting it does not apply.
The consequence: gate 2 now requires no security scan at all
This is the part that matters. The surface table lists six security contexts:
Security Scan - python,Security Scan - javascript,bandit,python-safety,npm-audit,trivy
The unconditional list contains none of them. The conditional list names only Security Scan - javascript. So Security Scan - python, bandit, python-safety, npm-audit and trivy appear in the table and then in neither list — a reader cannot tell whether they are required, and the literal reading is that they are not.
All five run on this docs-only PR (bandit already success on 27b2ecf; the rest in progress; all six were success on 92a4e26). They can be required unconditionally.
Net effect: v1 listed Security among the required six. The rewrite that set out to make gate 2 precise has, as written, dropped every security scan out of the required set — a weakening in the one direction you would not want, introduced by the fix rather than present before it. Same defect class as the original finding: a list that does not say what it means.
Suggested correction — move five into the unconditional list and drop the false rationale from the sixth:
Required for every pull request: `validate`, `guards`, `lint-python`,
`lint-frontend`, `build`, `test`, `CodeQL`, `gitleaks (working tree)`,
-`dependency-review`, `PR Governance`, `Canonical issue and evidence`.
+`dependency-review`, `PR Governance`, `Canonical issue and evidence`,
+`Security Scan - python`, `Security Scan - javascript`, `bandit`,
+`python-safety`, `npm-audit`, `trivy`.
+
+All six security contexts were verified to report on a documentation-only
+pull request (#1408), so none of them can strand a PR pending.
**Conditionally required — never require these unconditionally:**
- `Generate and Upload Coverage` — …
- `E2E Pipeline Tests` — passing or repository-skipped.
-- `Security Scan - javascript` — only when the diff touches JS/TS.If you would rather keep Security Scan - javascript conditional as a matter of policy, that is defensible — but the stated reason should change, since it does report on docs-only changes.
I have not pushed this; claude/event-relay-blockers-1k020k is not my branch.
This supersedes my earlier "mergeable today" note. Gates 1–8 still evaluate clean, but I would not merge until gate 2 names the security checks it means to require — that is the difference between adopting a policy and adopting a gap.
Generated by Claude Code
Canonical issue
Closes #1407
Outcome
Replaces Merge Gate v1 — which put 61 pull requests in draft and kept them there — with a policy whose every gate names an action an author can actually take.
The discovery behind this: v1 was never built. None of its eleven implementation tasks shipped. No
MERGE_POLICY.mdin the repo (MG-1), no merge queue (MG-3 — zeromerge_grouptriggers), and branch protection never required the six checks (MG-2 — #1368 merged with red ones). The only artifact was the Notion page, enforced by an agent reading it and returning pull requests to draft: "Six newly ready PRs were returned to draft."So there was no system to rewrite. The livelock was a document, which is why committing a better one is the fix.
Scope
MERGE_POLICY.md— new file,policy_version: v2.agent-completion/truth-gateapparatus (~7,400 lines) — separate PR, as agreed.Risk
git revert. Nothing depends on the file.Verification
git diff --statisMERGE_POLICY.md | 1 file changed.git ls-files | grep -i merge.policy→ empty (MG-1 never shipped)grep -rl "merge_group" .github/workflows/→ empty (MG-3 never shipped)agent-completion/truth-gatefailure,Vercelfailure, andCodeRabbit: Review skipped(MG-2 never shipped)Production evidence
Not applicable — documentation only, no runtime or build surface. No preview needed under the policy this file itself proposes (gate 4 scopes previews to
apps/web/**).What changed from v1
mainis what a merge queue is forapps/web/**— a canceled preview on a backend change is not evidenceKept from v1 because it was right: binding to one issue, required checks, risk classes, and the review clock.
The rule that would have caught this
agent-completion/truth-gatewas red on ~100% of pull requests for weeks, including merged ones..github/workflows/agent-completion-enforcement.ymldocuments this exact failure mode in its own comments — "destroys its signal value and hides real build/test failures behind constant noise" — while its sibling did precisely that.Agent handoff
Generated by Claude Code