The finalize job created the release/hotfix -> main merge-back PR but
never merged it, so every release and hotfix needed a manual merge click
on main. The opposite direction (main -> next) is already admin-merged by
auto-backmerge.yml; this direction was the asymmetric manual step where
the recurring divergence got hand-reconciled.
Adds a finalize step (after "Verify publish", so main only absorbs a
confirmed-published release) that finds the open merge-back PR, polls
until GitHub settles its mergeability, and admin-merges it with a merge
commit ONLY when MERGEABLE. A CONFLICTING PR is left open for manual
resolution rather than force-merged. Non-fatal (continue-on-error): the
tag + npm publish already happened, so a merge-back that can't complete
(org PR policy, token) must not fail the release.
With the main-is-ancestor-of-next invariant restored (#2504), this merge
is clean every release -- verified: a simulated 1.8.1 hotfix merges to
main producing [1.8.1]->[1.8.0]->[1.7.0]->[1.6.1] and version 1.8.1 with
no conflict, and main's hardened auto-backmerge.yml survives the merge.
Guarded by three assertions in release-backmerge-invariants.test.cjs
(step present + admin-merge, gates on MERGEABLE, continue-on-error);
verified they fail when --admin or the MERGEABLE guard or the
continue-on-error is removed.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The main->next auto-backmerge fails after nearly every release, leaving
main not an ancestor of next, so the following release->main merge-back
conflicts. Root cause is a copy-shuffling loop: auto-backmerge.yml must be
identical on main and next, but `-s ours` (main->next) and the release-tree
merge-back (release->main) each overwrite one copy wholesale, so a fix
applied to one copy is repeatedly overwritten by the copy that lacks it.
The build:lib step proves it: added to main (329233fc8), overwritten by
the 1.7.0 merge-back, re-added to next (#2281), never on old-main -> the
1.7.0 backmerge ran on main's broken copy and failed at "Sync next's
version" (npm version -> gen-capability-registry needs the gitignored
capability-ledger from build:lib -> absent -> step fails -> "Open PR"
skipped -> no PR -> main never becomes an ancestor of next).
Two-part durable fix:
1. Blast-radius containment: mark the version-sync steps continue-on-error.
The job's load-bearing purpose is opening + admin-merging the back-merge
PR (the ancestry that keeps release->main clean). A version-sync failure
(missing build:lib after a copy regression, or any npm-version lifecycle
hiccup) can no longer abort that PR. A sync failure now costs only a
stale next version, trivially re-synced -- never a broken back-merge.
2. Required-steps gate: tests/release-backmerge-invariants.test.cjs parses
the workflow YAML and asserts build:lib runs before the version-sync,
both steps are continue-on-error, the ancestry steps exist, and the
finalize timeout is >= 30 (sibling #2281 regression). It runs on every
branch, so a PR shipping a fix-less copy fails at PR time instead of at
release time -- which is exactly what #1855/#1928/#1990 did undetected.
Verified the test fails on both regression modes (continue-on-error removed;
build:lib step removed) and passes on the fixed workflow.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>