Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .changeset/release-pr-ci-gates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
---

ci(release): unblock the two gates that fail every `chore: version packages` PR (#4894)

Both failures on #4422 were the gates themselves, not the release PR's content,
and both recur on every release PR.

**`Check Changeset` was structurally unsatisfiable for the release PR.** The
gate counts changesets a PR *adds* (`git diff --diff-filter=A` against the base)
— the right question 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 Changesets
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.
It is 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 happens 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.
15 changes: 14 additions & 1 deletion .github/workflows/pr-automation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,20 @@ jobs:
changeset-check:
name: Check Changeset
runs-on: ubuntu-latest
if: "!contains(github.event.pull_request.labels.*.name, 'skip-changeset')"
# Two exemptions, both meaning "this PR declares no release of its own":
# - the `skip-changeset` label — the author's explicit opt-out;
# - the Changesets release PR (`changeset-release/main`, pushed by
# changesets/action). That PR is the CONSUMING side: it applies pending
# changesets into versions and CHANGELOGs and adds none, so the gate
# below can only ever fail it. It did, on every `chore: version
# packages` PR (#4422 / #4894), leaving the release blocked on a check
# that was structurally unsatisfiable.
# Pin the author as well as the branch name, so a hand-pushed branch of
# that name cannot borrow the exemption as an escape hatch.
if: >-
!contains(github.event.pull_request.labels.*.name, 'skip-changeset')
&& !(github.head_ref == 'changeset-release/main'
&& github.event.pull_request.user.login == 'github-actions[bot]')
permissions:
contents: read
pull-requests: write
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/scaffold-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,47 @@ jobs:
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n");
'
npm install --no-fund --no-audit
# The fallback just swapped the repo's unpublished version for
# whatever `latest` points at — which during an RC window is the
# PREVIOUS major (17.0.0-rc.N unpublished → latest is 16.x). The
# template's manifest still stamps the repo's protocol major
# (`engines: { protocol: '^17' }`, written by
# scripts/sync-template-versions.mjs), so the ADR-0087 D1 handshake
# in `os start` correctly refuses to boot the artifact:
# ✗ package 'e2e-app' targets protocol ^17 (engines.protocol)
# but this runtime is protocol 16.0.0
# That is the gate working, on a skew this step introduced (#4894).
# Re-stamp the manifest to the protocol major actually installed so
# the rest of the job exercises the template against a coherent
# runtime. Same alignment the Docker step below already does by
# reading the resolved CLI version; done here BEFORE `npm run
# build`, so the artifact carries the corrected range too.
#
# The major is read off the installed @objectstack/spec package:
# PROTOCOL_VERSION is kept in lockstep with that package's own major
# (packages/spec/src/kernel/protocol-version.ts, asserted by
# protocol-version.test.ts), so the two cannot drift.
#
# Deliberately confined to the fallback branch: on the normal path
# the project installs the repo's own version and the majors agree
# by construction — a template that stamped the wrong major would
# still fail, which is what template-consistency.test.ts is for.
node -e '
const fs = require("fs");
const major = JSON.parse(
fs.readFileSync("node_modules/@objectstack/spec/package.json", "utf8"),
).version.split(".")[0];
const path = "objectstack.config.ts";
const src = fs.readFileSync(path, "utf8");
const stamp = /engines:\s*\{\s*protocol:\s*[\x27"][^\x27"]*[\x27"]\s*\}/;
if (!stamp.test(src)) {
console.log("::error::fallback cannot re-stamp engines.protocol — no stamp found in " + path);
process.exit(1);
}
const out = src.replace(stamp, "engines: { protocol: \x27^" + major + "\x27 }");
fs.writeFileSync(path, out);
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");
'
fi

- name: Validate and build the generated project
Expand Down
Loading