ci(release): fail PRs that regress the beta cycle in .changeset/pre.json (#3486)

## Problem

PR #3472 resolved a `.changeset/pre.json` rebase conflict against a copy
predating the v0.76.0 stable:

| | at `v0.77.0-beta.1` | after #3472 |
|---|---|---|
| `initialVersions["@runfusion/fusion"]` | `0.76.0` | `0.75.1` |
| consumed ledger | 67 entries | 158 (the pre-0.76.0 cycle's) |

Nothing failed at PR time. Days later `pnpm release` saw the cycle
anchored below the shipped `v0.76.0`, fired its stale-cycle re-anchor
(`pre exit` → rewrite all 36 `package.json` → `pre enter`), and proposed
**`0.77.0-beta.0`** — below the already-published `0.77.0-beta.1`. The
re-anchor guard exists to stop a beta numbering under a stable; fed a
stale anchor it caused exactly that.

`pre.json` is generated by changesets, hand-edited by nobody, and
conflicts in nearly every long-lived branch — so a wrong resolution is
invisible until release day. This moves the failure to the PR that
causes it.

## The check

`scripts/check-pre-json-anchor.mjs`, three invariants:

- **`anchor-below-stable`** — `initialVersions` must not sit below the
newest `v*` stable tag. This is the exact predicate
`evaluateBetaCycleAnchor` keys on in `release.mjs`, so green here means
the release will *not* re-anchor.
- **`ledger-regression`** — the consumed ledger must stay a **superset**
of the last `chore(release):` commit's. Deliberately not a count test:
#3472's ledger *grew* 67 → 158 while dropping all 67 real entries, so a
size comparison would have passed it.
- **`dangling-ledger-entry`** — every consumed entry keeps its
`.changeset/*.md`, which pre-mode needs to aggregate notes into the
eventual stable release.

Skips cleanly outside pre-mode (the stable track deletes `pre.json`).

## Wiring

Added to the **Lint** job and `pretest`. Job names are unchanged, so no
branch-protection update is needed. The Lint checkout takes
`fetch-depth: 200` + `fetch-tags` rather than a full 486MB clone —
releases land every few days, so that always reaches a baseline; out of
range the ledger rule reports `SKIPPED` rather than passing vacuously,
and the two local rules still run.

## Verification

- Reproducing #3472's exact `pre.json` in the tree → **exit 1** on all
three rules, with the 67 dropped entries named.
- Clean `main` → exit 0.
- 11 unit tests (`scripts/__tests__/check-pre-json-anchor.test.mjs`),
including an explicit assertion that the ledger *grew* in the regression
case.
- `eslint` clean; workflow YAML parses; job names still `Lint,
Typecheck, Build, Gate`.

No changeset: CI config only, no `@runfusion/fusion` behavior change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Added automated validation for beta release-cycle metadata.
* Checks anchor versions, consumed changeset records, and corresponding
changeset files.
* Provides clear success or error messages and skips checks when release
history is unavailable or not applicable.

* **Chores**
  * Pull request checks now run the beta-cycle validation automatically.
* Added comprehensive coverage for valid, invalid, and skipped
validation scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-08-18 22:10:59 -07:00
committed by GitHub
parent 4d54cfea77
commit 027faaa09f
4 changed files with 376 additions and 1 deletions

View File

@@ -37,6 +37,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
with:
# The pre.json anchor check needs the newest `chore(release):` commit
# that touched .changeset/pre.json (its ledger baseline) and the `v*`
# stable tags. Bounded depth, not fetch-depth: 0 — releases land every
# few days, so 200 commits always reaches one, and a full 486MB clone
# on every PR is not worth it. The check falls back to `git ls-remote
# --tags` for the tags and reports the ledger rule SKIPPED (never
# silently passed) if the baseline is out of range.
fetch-depth: 200
fetch-tags: true
- name: Setup Node.js and pnpm
uses: ./.github/actions/setup-node-pnpm
@@ -47,6 +57,16 @@ jobs:
- name: Changeset format
run: pnpm check:changesets
# PR #3472 resolved a .changeset/pre.json rebase conflict against a copy
# predating the v0.76.0 stable. Nothing failed at PR time; days later
# `pnpm release` saw the cycle anchored below the shipped stable, fired its
# stale-cycle re-anchor, and proposed 0.77.0-beta.0 — below the published
# 0.77.0-beta.1. pre.json is generated, hand-edited by nobody, and conflicts
# in nearly every long-lived branch, so a wrong resolution is invisible
# until release day. ~50ms.
- name: Beta cycle anchor (pre.json)
run: pnpm check:pre-json
- name: Dashboard route modularity
run: pnpm check:routes-modular