Skip to content

Commit 28c8bb9

Browse files
committed
chore(scripts): watch npm for the stable release that retires a prerelease pin (#5024)
`pnpm-workspace.yaml` pins the better-auth family to a 1.7.0 release candidate and promises, in a comment, to "revert to a stable `^1.7.x` line the moment one ships". #3002 (revert the family) and #3653 (the SCIM migration) are both gated on that event, and nothing watched for it — the promise had no producer, so redeeming it depended on someone remembering to check npm. Add `scripts/check-prerelease-pin-watch.mjs`, run nightly by `.github/workflows/prerelease-pin-watch.yml` at 06:00 UTC. The watch list is DERIVED from the pins (every override whose target is a prerelease version), so it cannot drift from the file it polices, a new prerelease pin is watched the day it lands, and the probe retires itself when the last one goes stable. The trigger is semver — a version with no prerelease segment at or above the pinned base — never the `latest` dist-tag: today better-auth's `latest` is 1.6.26 while the 1.7 line publishes rcs, so a tag-watching probe would sleep through the event it exists to catch. A stable release only in a LATER line is reported as its own case, so upstream stabilizing 1.8 and abandoning 1.7 cannot leave the probe silent. Three exit codes, because "npm was unreachable" and "no stable release yet" are different facts: 0 waiting (one quiet line), 1 a stable release exists (loud, names #3002/#3653 and their action lists), 2 a registry read failed (warning plus step summary, non-blocking — an unattended nightly that goes red on a transient 5xx is a nightly everyone learns to skim, and the event it watches for is permanent, so a missed night costs a day). `--strict` promotes 2 to 1. `--self-test` is offline and covers all three states, the empty watch list, and the CLI exit codes over fabricated registry responses; it runs on PRs that touch the probe, the workflow, or the pins. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
1 parent 0285f7f commit 28c8bb9

4 files changed

Lines changed: 1037 additions & 0 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
---
3+
4+
chore(scripts): watch npm for the stable release that retires a prerelease pin (#5024)
5+
6+
`pnpm-workspace.yaml` pins the better-auth family to `1.7.0-rc.2` (scim to
7+
`1.7.0-rc.1`) and promises, in a comment, to "revert to a stable `^1.7.x` line
8+
the moment one ships". #3002 and #3653 are both gated on that event and nothing
9+
watched for it — the promise had no producer.
10+
11+
`scripts/check-prerelease-pin-watch.mjs` is that producer, run nightly by
12+
`.github/workflows/prerelease-pin-watch.yml`. Its watch list is derived from the
13+
pins themselves (every override whose target is a prerelease), so it cannot drift
14+
from the file it polices and it retires itself when the last prerelease pin goes
15+
stable. The trigger is semver — a version with no prerelease segment at or above
16+
the pinned base — never the `latest` dist-tag, which today still sits on 1.6.26
17+
while the 1.7 line publishes release candidates. Tooling only; no runtime or
18+
authorable surface changes.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
name: Prerelease Pin Watch
2+
3+
# Has upstream shipped a STABLE release that retires one of our prerelease
4+
# `overrides` pins? (#5024)
5+
#
6+
# WHAT IT WATCHES, AND WHY IT IS A WORKFLOW AT ALL
7+
# -----------------------------------------------
8+
# `pnpm-workspace.yaml` pins the better-auth family to `1.7.0-rc.2` (scim one rc
9+
# behind at `1.7.0-rc.1`) and promises, in a comment:
10+
#
11+
# revert to a stable `^1.7.x` line the moment one ships.
12+
#
13+
# #3002 (revert the family) and #3653 (the SCIM migration) are both gated on that
14+
# event, and until this workflow existed NOTHING watched for it — redeeming the
15+
# promise depended on a person remembering to check npm. That is the repo's
16+
# `declared != enforced` shape applied to a comment, and this workflow is the
17+
# missing producer. It never edits a pin; it only makes the trigger arrive as a
18+
# signal within a day instead of as a memory.
19+
#
20+
# The watch list is DERIVED from the pins (every override whose target is a
21+
# prerelease version), so it cannot drift from the file it polices, and it empties
22+
# itself when the last prerelease pin goes stable. See the script header.
23+
#
24+
# THREE EXIT CODES, ON PURPOSE
25+
# ----------------------------
26+
# 0 no stable release yet → quiet, one line
27+
# 1 a stable release EXISTS → job RED, report names #3002 / #3653
28+
# 2 a registry read failed → `::warning::` + step summary, job GREEN
29+
#
30+
# Exit 2 is the considered trade-off, not an oversight. This is an unattended
31+
# nightly: a transient npm 5xx that turns it red teaches everyone to skim it, and
32+
# a nightly nobody reads is exactly the "nobody is watching" state #5024 is about.
33+
# Missing one night costs at most a day — the next run re-probes from scratch and
34+
# a published version is never unpublished back into silence. So red keeps meaning
35+
# exactly one thing here (a stable release is out), and an inconclusive probe is
36+
# loud-but-non-blocking instead of being silently reported as "no release yet",
37+
# which is the one thing it must never do. `--strict` promotes exit 2 to exit 1 if
38+
# the warnings ever start being missed.
39+
#
40+
# (Contrast `check:objectui-pin-fresh`, where an unreadable remote DOES fail: that
41+
# one is a required gate on the release PR, so silence there ships the defect.)
42+
#
43+
# WHY NOT lint.yml / validate-deps.yml
44+
# ------------------------------------
45+
# Not `lint.yml`: the probe needs the network, and no required PR gate in this
46+
# repo may depend on a third-party registry being up. Not `validate-deps.yml`
47+
# either, close as it is in subject matter — it is scheduled WEEKLY (Mon 03:00
48+
# UTC), and the acceptance criterion here is a signal within the first working
49+
# day of the release. A weekly slot can sit on the event for six days.
50+
#
51+
# On a PR that touches the probe, the pins, or this file, only the offline
52+
# `--self-test` runs ("a change to the guard runs the guard"). No `pnpm install`
53+
# anywhere: the script is dependency-free by design, which is what keeps this
54+
# whole workflow a ~15-second job.
55+
56+
on:
57+
schedule:
58+
# 06:00 UTC nightly — after Rerun Safety (04:00) and Spec Coverage (05:00),
59+
# before Showcase Smoke (07:00). Nightly, not weekly: see the note above.
60+
- cron: '0 6 * * *'
61+
workflow_dispatch:
62+
pull_request:
63+
paths:
64+
- 'scripts/check-prerelease-pin-watch.mjs'
65+
- '.github/workflows/prerelease-pin-watch.yml'
66+
# The pins ARE the watch list, so a PR that edits them re-runs the
67+
# self-test that proves the real file still parses into one.
68+
- 'pnpm-workspace.yaml'
69+
70+
permissions:
71+
contents: read
72+
73+
concurrency:
74+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
75+
cancel-in-progress: true
76+
77+
jobs:
78+
watch:
79+
name: Stable release watch for prerelease pins
80+
runs-on: ubuntu-latest
81+
timeout-minutes: 10
82+
steps:
83+
- name: Checkout repository
84+
uses: actions/checkout@v7
85+
86+
- name: Setup Node.js
87+
uses: actions/setup-node@v7
88+
with:
89+
node-version: '22'
90+
91+
# "A change to the guard runs the guard." Offline and hermetic — fabricated
92+
# registry responses cover all three states plus the empty watch list — so
93+
# it is safe on a PR where the probe itself would not be.
94+
- name: Self-test the probe
95+
run: node scripts/check-prerelease-pin-watch.mjs --self-test
96+
97+
- name: Probe is nightly-only on PRs
98+
if: github.event_name == 'pull_request'
99+
run: |
100+
echo "::notice::Self-test only on a PR — the probe itself reads registry.npmjs.org, and no PR gate here may depend on a third-party registry. It runs nightly at 06:00 UTC (#5024)."
101+
102+
- name: Probe npm for a stable release
103+
if: github.event_name != 'pull_request'
104+
run: |
105+
set -o pipefail
106+
status=0
107+
node scripts/check-prerelease-pin-watch.mjs --verbose 2>&1 \
108+
| tee "$RUNNER_TEMP/prerelease-pin-watch.log" || status=$?
109+
110+
{
111+
echo '### Prerelease pin watch (#5024)'
112+
echo
113+
echo '```'
114+
cat "$RUNNER_TEMP/prerelease-pin-watch.log"
115+
echo '```'
116+
} >> "$GITHUB_STEP_SUMMARY"
117+
118+
if [ "$status" -eq 0 ]; then
119+
exit 0
120+
elif [ "$status" -eq 2 ]; then
121+
# Loud but non-blocking — see the header for why this is not red.
122+
# The script has already printed its own ::warning:: with the reason.
123+
echo "::warning::Prerelease pin watch was INCONCLUSIVE (a registry read failed). This is NOT 'no stable release yet'. It re-probes tomorrow; if it stays inconclusive, run this workflow with --strict wired in and investigate (#5024)."
124+
exit 0
125+
fi
126+
127+
echo "::error::A STABLE release now exists for a pin this repo holds at a prerelease. The trigger condition of #3002 / #3653 has arrived — see the step summary for the per-package verdicts and the action list (#5024)."
128+
exit "$status"

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"check:console-sha": "node scripts/check-console-sha.mjs",
5151
"check:objectui-changeset": "node scripts/objectui-changeset-digest.mjs --self-test && node scripts/objectui-range.mjs --self-test",
5252
"check:objectui-pin-fresh": "node scripts/check-objectui-pin-fresh.mjs --self-test && node scripts/check-objectui-pin-fresh.mjs",
53+
"check:prerelease-pins": "node scripts/check-prerelease-pin-watch.mjs --self-test && node scripts/check-prerelease-pin-watch.mjs",
5354
"check:release-notes": "node scripts/check-release-notes.mjs",
5455
"check:release-body": "node scripts/release-github-releases.mjs --self-test",
5556
"check:node-version": "node scripts/check-node-version.mjs",

0 commit comments

Comments
 (0)