Skip to content

Commit 0f4656a

Browse files
os-zhuangclaude
andauthored
ci(release): unblock the two gates that fail every release PR (#4894) (#4896)
Both red checks on #4422 (`chore: version packages (rc)`) are defects in the gates themselves, and both recur on every Changesets release PR. Check Changeset was structurally unsatisfiable for the release PR. The gate counts changesets a PR *adds* (`git diff --diff-filter=A` vs the base) — right for an ordinary PR, and the fix #3373 landed after a global `find | wc -l` proved unable to ever go red in RC mode. But the release PR is the *consuming* side: it applies pending changesets into versions and CHANGELOGs and adds none, by construction. Nobody labels a bot-authored PR `skip-changeset`, so the release sat blocked on a check that could only be red. `changeset-release/main` is now exempt at the job level, pinned to the bot author as well as the branch name so a hand-pushed branch of that name cannot borrow the exemption. Scaffold E2E skewed the protocol major against itself during an RC window. The install step already falls back to `latest` when the repo's version is not yet published (`@objectstack/cli@^17.0.0-rc.2` -> ETARGET -> retry as `latest`). That fallback rewrote the generated project's dependencies but not its manifest, and the template stamps the repo's protocol major (`engines: { protocol: '^17' }`, written at version time by sync-template-versions.mjs) while `latest` still pointed at 16.x. The ADR-0087 D1 handshake then correctly refused to boot the artifact — the gate working, on a skew the step had introduced: package 'e2e-app' targets protocol ^17 (engines.protocol) but this runtime is protocol 16.0.0 The fallback now re-stamps `engines.protocol` to the major actually installed, read off node_modules/@objectstack/spec (PROTOCOL_VERSION is kept in lockstep with that package's own major, asserted by protocol-version.test.ts), and logs a `::notice` so the run's true protocol is visible rather than silently rewritten. Confined to the fallback branch: on the normal path the project installs the repo's own version, the majors agree by construction, and a template stamping the wrong major must still fail — which is what template-consistency.test.ts is for. Re-stamping runs before `npm run build`, so the artifact and the Docker image (already pinned to the resolved CLI version by the same reasoning) stay in step. CI configuration only; releases nothing. Claude-Session: https://claude.ai/code/session_01BbNVKv6KgPzuQ5p76nMgnf Co-authored-by: Claude <noreply@anthropic.com>
1 parent e2086fe commit 0f4656a

3 files changed

Lines changed: 100 additions & 1 deletion

File tree

.changeset/release-pr-ci-gates.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
---
3+
4+
ci(release): unblock the two gates that fail every `chore: version packages` PR (#4894)
5+
6+
Both failures on #4422 were the gates themselves, not the release PR's content,
7+
and both recur on every release PR.
8+
9+
**`Check Changeset` was structurally unsatisfiable for the release PR.** The
10+
gate counts changesets a PR *adds* (`git diff --diff-filter=A` against the base)
11+
— the right question for an ordinary PR, and the fix #3373 landed after a global
12+
`find | wc -l` proved unable to ever go red in RC mode. But the Changesets
13+
release PR is the *consuming* side: it applies pending changesets into versions
14+
and CHANGELOGs and adds none, by construction. Nobody labels a bot-authored PR
15+
`skip-changeset`, so the release sat blocked on a check that could only be red.
16+
`changeset-release/main` is now exempt at the job level, pinned to the bot author
17+
as well as the branch name so a hand-pushed branch of that name cannot borrow the
18+
exemption.
19+
20+
**`Scaffold E2E` skewed the protocol major against itself during an RC window.**
21+
The install step already falls back to `latest` when the repo's version is not
22+
yet published (`@objectstack/cli@^17.0.0-rc.2` → ETARGET → retry as `latest`).
23+
That fallback rewrote the generated project's dependencies but not its manifest,
24+
and the template stamps the repo's protocol major (`engines: { protocol: '^17' }`,
25+
written at version time by `sync-template-versions.mjs`) while `latest` still
26+
pointed at 16.x. The ADR-0087 D1 handshake then correctly refused to boot the
27+
artifact — the gate working, on a skew the step had introduced:
28+
29+
```
30+
✗ package 'e2e-app' targets protocol ^17 (engines.protocol) but this runtime is
31+
protocol 16.0.0
32+
```
33+
34+
The fallback now re-stamps `engines.protocol` to the major actually installed,
35+
read off `node_modules/@objectstack/spec` (`PROTOCOL_VERSION` is kept in lockstep
36+
with that package's own major, asserted by `protocol-version.test.ts`), and logs
37+
a `::notice` so the run's true protocol is visible rather than silently rewritten.
38+
It is confined to the fallback branch: on the normal path the project installs
39+
the repo's own version, the majors agree by construction, and a template stamping
40+
the wrong major must still fail — which is what `template-consistency.test.ts` is
41+
for. Re-stamping happens before `npm run build`, so the artifact and the Docker
42+
image (already pinned to the resolved CLI version by the same reasoning) stay in
43+
step.
44+
45+
CI configuration only; releases nothing.

.github/workflows/pr-automation.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,20 @@ jobs:
4949
changeset-check:
5050
name: Check Changeset
5151
runs-on: ubuntu-latest
52-
if: "!contains(github.event.pull_request.labels.*.name, 'skip-changeset')"
52+
# Two exemptions, both meaning "this PR declares no release of its own":
53+
# - the `skip-changeset` label — the author's explicit opt-out;
54+
# - the Changesets release PR (`changeset-release/main`, pushed by
55+
# changesets/action). That PR is the CONSUMING side: it applies pending
56+
# changesets into versions and CHANGELOGs and adds none, so the gate
57+
# below can only ever fail it. It did, on every `chore: version
58+
# packages` PR (#4422 / #4894), leaving the release blocked on a check
59+
# that was structurally unsatisfiable.
60+
# Pin the author as well as the branch name, so a hand-pushed branch of
61+
# that name cannot borrow the exemption as an escape hatch.
62+
if: >-
63+
!contains(github.event.pull_request.labels.*.name, 'skip-changeset')
64+
&& !(github.head_ref == 'changeset-release/main'
65+
&& github.event.pull_request.user.login == 'github-actions[bot]')
5366
permissions:
5467
contents: read
5568
pull-requests: write

.github/workflows/scaffold-e2e.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,47 @@ jobs:
9090
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n");
9191
'
9292
npm install --no-fund --no-audit
93+
# The fallback just swapped the repo's unpublished version for
94+
# whatever `latest` points at — which during an RC window is the
95+
# PREVIOUS major (17.0.0-rc.N unpublished → latest is 16.x). The
96+
# template's manifest still stamps the repo's protocol major
97+
# (`engines: { protocol: '^17' }`, written by
98+
# scripts/sync-template-versions.mjs), so the ADR-0087 D1 handshake
99+
# in `os start` correctly refuses to boot the artifact:
100+
# ✗ package 'e2e-app' targets protocol ^17 (engines.protocol)
101+
# but this runtime is protocol 16.0.0
102+
# That is the gate working, on a skew this step introduced (#4894).
103+
# Re-stamp the manifest to the protocol major actually installed so
104+
# the rest of the job exercises the template against a coherent
105+
# runtime. Same alignment the Docker step below already does by
106+
# reading the resolved CLI version; done here BEFORE `npm run
107+
# build`, so the artifact carries the corrected range too.
108+
#
109+
# The major is read off the installed @objectstack/spec package:
110+
# PROTOCOL_VERSION is kept in lockstep with that package's own major
111+
# (packages/spec/src/kernel/protocol-version.ts, asserted by
112+
# protocol-version.test.ts), so the two cannot drift.
113+
#
114+
# Deliberately confined to the fallback branch: on the normal path
115+
# the project installs the repo's own version and the majors agree
116+
# by construction — a template that stamped the wrong major would
117+
# still fail, which is what template-consistency.test.ts is for.
118+
node -e '
119+
const fs = require("fs");
120+
const major = JSON.parse(
121+
fs.readFileSync("node_modules/@objectstack/spec/package.json", "utf8"),
122+
).version.split(".")[0];
123+
const path = "objectstack.config.ts";
124+
const src = fs.readFileSync(path, "utf8");
125+
const stamp = /engines:\s*\{\s*protocol:\s*[\x27"][^\x27"]*[\x27"]\s*\}/;
126+
if (!stamp.test(src)) {
127+
console.log("::error::fallback cannot re-stamp engines.protocol — no stamp found in " + path);
128+
process.exit(1);
129+
}
130+
const out = src.replace(stamp, "engines: { protocol: \x27^" + major + "\x27 }");
131+
fs.writeFileSync(path, out);
132+
console.log("::notice::fallback re-stamped engines.protocol to ^" + major + " (installed @objectstack/spec major) — this run exercises the template against protocol " + major + ", not the repo\x27s");
133+
'
93134
fi
94135
95136
- name: Validate and build the generated project

0 commit comments

Comments
 (0)