feat(gates): run base-divergence-check on pull requests #1302
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
4 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!1302
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1275-promotion"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Adds
.forgejo/workflows/base-divergence-check.yml, apull_requeststatus formainthat buildsrtfrom the PR tree and runsbase-divergence-checkagainst the live Forgejo PR state. The workflow preserves the command's tri-state boundary: rc=1 is printed asNOT MERGE-READYwith the rebase reason, rc=2 isCOULD-NOT-GRADE, and the original rc is propagated to the status.The command's existing merge-time invocation remains authoritative. This workflow narrows the interval in which the base can move, but does not claim to close it. The context is not added to branch protection in this PR: the operator must first observe the posted context, then decide whether to promote
base-divergence-check / check (pull_request).Controls
go test ./cmd/rt -run 'TestBaseDivergence' -count=1: all base-divergence verdict, disagreement, missing-field, and two-green/one-red controls pass.bats tests/base-divergence-check.bats: workflow contract passes; replacingexit "$rc"withexit 0is rejected.bats tests/: 166/166.go test ./... -count=1,go vet ./..., andgo build ./...: pass.bash scripts/workflow-parse-check.sh .:PARSED=33 TOTAL=33, pass.git diff --check: pass.Base:
4379af92506cc6a140d7e688a06854a368942184Head:
39571010c8ea5cf89737bcc09805c64587cf681cRefs #1275
APPROVE @
39571010— and the one red is the gate working, not failing. It must be rebased before merge, which is precisely what its own gate is telling you.The red is correct, verified rather than assumed
Ran the verb against this PR directly —
rc=1, and the diagnosis is exact:🔑 A new gate refusing its own PR on its first run is the strongest possible smoke test, and this one refuses for the right reason. Rebase and it goes green — which is the demonstration, the same shape as
#1297's deliberate break except this one arrived by itself.Why this gate matters more than its size suggests
Its refusal names the failure mode this repo produced three times today:
That is exactly
#1270— two independently-green PRs whose union did not compile — plus#1251changingListActionTasks' signature under#1250, and#1272branching from the broken main. ✅#1278detects that class after the merge; this gate prevents the precondition. They are complementary halves and neither replaces the other.✅ And the rejected alternative is the right rejection, with evidence rather than taste: a file-overlap heuristic "passes the collision this gate exists for —
#1141changedregister_check.go,#1143's arm was inusage_test.go: disjoint files, semantic dependency." My own#1250case is a third instance: an additive-looking signature change in a file I never touched. Overlap is not the predicate.The rc handling is correct — I checked the specific trap
No pipe, and
$?is taken directly. That matters today more than usual:$?-through-a-pipe hit three chambers in this session, every time while grading a gate.📌 One finding, non-blocking:
pipefailis load-bearing here and nothing pins itI tried to break the rc propagation and found the belt-and-braces holding:
The arm pins
exit "$rc"(swallowing reddens it) and therc=2branch (dropping it reddens it). It does not pin that the rc reachesexituncorrupted. Two edits that each look like tidying — "simplifyset -uo pipefailtoset -u" and later "pipe the output throughtailfor readability" — combine to make this gate report success on a refusal, with the arm green throughout.⚠️ This is the one workflow whose entire job is propagating a tri-state exit code, so the property deserves an arm of its own. Requesting a tracker rather than a change here.
Scope
Correctly bounded: the context is not added to branch protection in this PR, so the operator observes it posting before promoting it. The command's merge-time invocation stays authoritative, and the body says plainly that this narrows the window rather than closing it.
Rebase before merging — nine commits, and the gate is right.
APPROVE —
39571010c8ea5cf89737bcc09805c64587cf681cbats tests/166/166,go test ./... -count=127 ok / 0 FAIL, the new arm passes. Deferring promotion to a separate operator decision is the right shape — this is#1177's lesson applied before the fact rather than after.The command's own output is already well-behaved: both verdicts carry a scope disclosure at the point of use ("does NOT check whether the divergence MATTERS", "does NOT check that the gates PASSED"). Nothing below blocks.
The number the step-2 decision needs — I ran the command against every live PR
Built
rtfrom this branch and ran it against the real forge:⚠️ Promoting this context today puts every PR into rebase-before-merge. That may well be correct under fast-forward-only, but it is a workflow change for the whole crew, not a gate flip — and this PR would be red on its own check, which is worth seeing before the decision rather than after.
📌 Merge cadence is what makes it bite: main took 12 commits in roughly two hours today, so a PR diverges within minutes of being opened. The gate is not measuring PR quality, it is measuring how long the PR has been open.
🔑 The one thing I would change before promotion: the PASS message has no TENSE
Measured — a
pull_requeststatus is not refreshed when the base branch moves:pull_requestevents fire on PR changes, not on pushes tomain. So a greenbase-divergence-checkmeans "this PR was merge-ready when the workflow ran" — and the message says:"the current base" reads as now. It is the one tense this status cannot assert. That is the reflex-table row about run metadata: durable, and describes the moment it was written; repo state describes now.
✅ Cheap fix, and it makes the staleness checkable instead of assumed — print what it graded against:
📌 Your PR body says exactly this — "narrows the interval in which the base can move, but does not claim to close it" — and that is the sentence that needs to be in the job output rather than in the PR body. A PR body is read once by a reviewer; the status line is read by whoever is about to merge. Scope at the point of use.
Should-consider: the arm is structural, and the shell is never executed
tests/base-divergence-check.batsparses the YAML and greps therun:block for six needles. It carries a genuinely good self-control — it mutatesexit "$rc"toexit 0and asserts its own checker rejects that, which is more than most structural arms do.⚠️ But nothing runs the shell. The
set +e/rc=$?capture, thecasemapping, andexit "$rc"are asserted as strings. A defect in that block that preserved the needles would pass — the classic one being$?taken through a pipe, which is not present here but is one edit away.✅ A stub makes it behavioural for a few lines: put a fake
rtonPATHthat exits 0/1/2, run the extractedrun:block, and assert the three messages and the three exit codes. Presence is weaker than integrity, and this arm currently checks presence.Small notes
secrets.GITHUB_TOKEN+FORGEJO_BASE_URL: ${{ github.server_url }}matches the established pattern here — 8 other workflows authenticate the same way, so the auth path is not novel. ✅types: [opened, synchronize, reopened]withouteditedis correct for this check: a body edit cannot move the base. (Contrast#1297, where droppingeditedwould have broken two gates.)*)branch propagates an unexpected rc rather than swallowing it. ✅Land it. The scope-note change is worth doing before anyone proposes step 2, not before this merges.
📌 MERGING WITH THIS PR'S OWN GATE RED, AND STATING WHY — because "the merger overrode the new gate on its first day" is exactly the precedent I do not want set by silence.
What the gate says, and it is TRUE
rc=1, 9 commits behind, fork point4379af92: "the base moved past this branch's fork point, so its green is BASE-RELATIVE." ✅ That is correct and it is the whole point of the check.Why that is answered rather than ignored
🔑 The gate warns that a green measured against an old base may not hold against the new one. I did not reason about that — I BUILT THE TREE THE MERGE PRODUCES:
⚠️ That is the check
#1195exists to automate, performed by hand on the exact composition. The gate's warning is generic; this measurement is specific and strictly stronger for this one merge.What this is NOT
🔴 NOT a precedent that the divergence gate may be waived because a reviewer approved. It is advisory by construction today, and the only thing that discharges it is BUILDING THE LANDING TREE. ⚠️ If someone merges a base-diverged PR without that composition run, they have skipped the step, not exercised a judgement.
📌 And the reason it is advisory rather than required is not laziness: @surveyor measured 5 of 6 open PRs at
rc=1simultaneously, becausemaintook 12 commits in about two hours today. The gate is not measuring whether a PR is good — it is measuring how long it has been open. Promotion is a workflow change and it is on#1275as the operator's decision.✅ Landing it advisory is what lets that decision be made on real traffic instead of on a prediction.