Repository navigation
Commit c46b5f1
chore(release): exit Changesets pre mode (
Fixes #24528
Clause-②: no
Exits Changesets pre mode on `main`, on the maintainer's order 「发 GA
要先退出预发布模式。立卡派发」. After this lands, the next version pass computes the
GA: the 73-member lockstep (`fixed`) group goes from `18.0.0-next.5` to
`18.0.0` and publishes to npm's `latest` tag. **Draft, for the
maintainer to merge.** Its merge sets when the next Version Packages PR
versions 18.0.0, and the dry-run below shows that refresh is **still
blocked by two release-owned doc sentences**, so read "GA blockers"
before merging.
⛔ No `changeset version` output is committed. Nothing was published or
tagged, no release workflow was dispatched, and neither the Version
Packages PR nor `changeset-release/main` was touched.
Head `abec338235` = `5b14b7f3ad` (base at worktree creation) + this
change + `origin/main` `04669a467e`, merged in before opening.
## The change
1. **`.changeset/pre.json`**, written by `pnpm changeset pre exit`
(`@changesets/cli` 3.0.3) and committed exactly as the tool wrote it
(`4fe7eb6abe`):
| | content |
|---|---|
| before | `{ "mode": "pre", "tag": "next" }` |
| after | `{ "mode": "exit", "tag": "next" }` (2-space JSON plus a
trailing newline, as before) |
- `@changesets/pre` 3.0.0 `exitPre` spreads the state it read and sets
`mode: "exit"`. Its `migratePreState` only rewrites a file that still
carries v2's `initialVersions` or `changesets` keys. This file has
neither, so it is a no-op here.
- So under v3, `pre exit` neither writes nor expects `initialVersions`
or `changesets`. The `.changeset/pre/` layout does not change what it
writes. What the layout changes is what the GA pass reads: the CLI's
reader (`@changesets/read` 1.0.1) always lists `.changeset/pre/`, and in
`exit` mode those changesets are consumed again (measured below).
- The CLI's own exit message says: "Please also review the changesets in
the .changeset/pre folder as they will be used as changelogs for the
normal versions." See the acceptance notes for what that costs.
2. **`content/docs/protocol/backward-compatibility.mdx`, 11 sentences**
(`1b8d66ed1c`). This page is hand-written and not release-owned. Merging
the mode switch makes these present-tense sentences false: "Changesets
is in pre mode", "the guard stands aside", "on the v18 line it ships in
the next `18.0.0-next.N`".
- Each sentence now puts the `18.0.0-next.N` behaviour in the
prerelease, in the past tense. Two of them also state the exit facts:
the line's GA version is `18.0.0`, and the guard fails a `major` bump
again.
- The page's own post-GA rule ("the window closes at GA, and strict
SemVer resumes from that point") is left as written. No new policy prose
is added.
- Both lockstep-count anchors (`would promote all 73 published
packages.` and `all **73** packages published from this repository`) are
byte-unchanged, and `check:lockstep-package-count` is green.
- This file is outside the claim's declared surface. It is here because
the role contract makes a published statement that this change falsifies
a must-fix. The release-owned pages that it also falsifies are listed
under GA blockers, because `content/docs/releases/` is never edited in
this PR.
## Dry-run of the version pass (scratch clone, never pushed)
Setup: a clone of this branch at `abec338235` in the scratchpad, outside
the worktree.
- Full history (`--is-shallow-repository` false, root commit present)
and local `main` at `04669a467e`. `release-rehearsal-clone.mjs` reported
"✓ FIT".
- Push URL set to an invalid path. Install was `pnpm install
--frozen-lockfile --ignore-scripts`, with the migration registry
generated by hand, because the scratchpad path is too long for tsx's IPC
socket during `prepare`.
- The `version-pr` job's steps `Render the post-version tree`, `Validate
the post-version tree` and `Restore the pre-version tree` were
**extracted verbatim from `release.yml`** and run with `RUNNER_TEMP` and
`GITHUB_OUTPUT` pointed at scratch files. The workflow itself was not
run.
### What the pass consumes, and what happens to `pre.json`
| reading | value |
|---|---|
| `pending_before` (Render step, `exit` mode counts `.changeset/*.md`
plus `.changeset/pre/*.md`) | **1029** |
| consumed from `.changeset/pre/` (deleted by the pass) | **1011** |
| consumed from `.changeset/` | **18** |
| `.changeset/pre.json` after the pass | **deleted** |
| `.md` files under `.changeset/` after the pass | `README.md` only
(`.changeset/pre/` is left empty, so git tracks nothing there) |
| `@changesets/read` 1.0.1 on the post-version tree | 0 changesets |
| paths moved (Validate's shape assertion) | 1205, "all inside the
reviewed shape" |
| `annotate-shipped` (first step of `pnpm run version`) | 6 changesets
left unconsumed by the `18.0.0-next.5` version commit `820fd5aef0`
annotated "Shipped in 18.0.0-next.5" |
| publish dist-tag after the GA commit | `latest`. `getReleaseTag`
(`@changesets/cli` 3.0.3 `getPublishPlan.mjs:599-603`) returns `latest`
when there is no pre state, and the GA version commit has none. This is
a reading of the code; nothing was published. |
### Every package, before and after
**Lockstep group: all 73 members `18.0.0-next.5` → `18.0.0`.** The GA is
`18.0.0`, not `18.0.0-next.6` and not `19.0.0`. The group:
`@objectstack/hono`, `@objectstack/cli`, `create-objectstack`,
`@objectstack/spec` and the other 69 public packages listed in
`.changeset/config.json`. `check-changeset-fixed.mjs` reports "in sync
with 73 public workspace packages", and the dry-run read 73 of 73
members at `18.0.0`.
The 13 private workspace packages outside the group:
| package | before | after |
|---|---|---|
| `@objectstack/example-crm` | `4.0.100-next.5` | `4.0.100` |
| `@objectstack/example-todo` | `4.0.100-next.5` | `4.0.100` |
| `@objectstack/example-multi-package` | `0.0.7-next.5` | `0.0.7` |
| `@objectstack/example-showcase` | `0.3.22-next.5` | `0.3.22` |
| `@objectstack/example-embed-objectql` | `0.0.40-next.5` | `0.0.40` |
| `@objectstack/dogfood` | `0.0.48-next.5` | `0.0.48` |
| `@objectstack/downstream-contract` | `0.0.46-next.5` | `0.0.46` |
| `@objectstack/http-conformance` | `0.1.8-next.5` | `0.1.8` |
| `@objectstack/docs` | `4.2.3` | unchanged |
| `@objectstack/spec-monorepo` (root) | `4.0.1` | unchanged |
| `objectstack-blank` (template) | `0.1.0` | unchanged |
| `@objectstack/refd-timer-testkit` | `0.1.0` | unchanged |
| `@objectstack/vitest-filter-preflight` | `0.1.0` | unchanged |
### GA blockers: the refresh cannot produce the 18.0.0 Version Packages
PR yet
**Leg A, the tree as this PR leaves it.**
- **Render step exits 1.** `changeset version`, `sync-protocol-version`,
`sync-template-versions` and `sync-docs-image-tags` all succeed. Then
`sync-release-index-currency` refuses, by design: "the v18 entry's
status reads "prerelease: `18.0.0-next.N` on npm's `next` tag, no GA
date", but the newest released 18.x version is 18.0.0 … These are the
shapes this rewriter deliberately refuses, because each needs a SENTENCE
and not a token". Under `set -e` the job stops there, so neither
Validate nor the PR step runs.
- Completing the chain by hand (`check-skill-compatibility-version
--fix`) and running Validate anyway shows the second refusal. 14 of 15
content gates are green, and **`check:release-page-status` is red**:
- `content/docs/releases/v18/index.mdx`: "the release-status blockquote
never states that a 18.x version is released";
- `content/docs/releases/index.mdx`: the v18 entry is flagged for
`prerelease-word` and for `prerelease-version`.
**Leg B, the same tree with stand-in GA prose** (rehearsal clone only,
committed there and never pushed).
- Two edits: the index entry's status changed to `(current series:
18.0.0, released 2026-10-19)`, and a blockquote lead added that reads
`18.0.0 was released on 2026-10-19`.
- Result: Render exits 0, and Validate exits 0 with "Post-version tree
validated." (15 of 15 green, major-boundary surfaces left unvalidated:
0). The CLI's reader then sees 0 pending, and Restore exits 0 with
"Pre-version tree restored: 1029 pending changeset(s), working tree
clean."
- So these two sentences are the **only** blockers this dry-run found.
**What is needed, for the seat to card.** A docs-only PR, because
`content/docs/releases/` is release-owned and never edited in a code PR:
1. `content/docs/releases/index.mdx`: the v18 entry's status becomes
`(current series: 18.0.0, released DATE)`. Also rewrite the paragraph
"**The v18 line is a major in prerelease.** Changesets is in pre mode…"
(lines 58-62), which this PR makes false at merge.
2. `content/docs/releases/v18/index.mdx`: a release-status blockquote
stating that 18.0.0 is released. Also rewrite the pre-mode sentences at
lines 6-11, 27 and 62. The `description` front matter carries the same
claim.
3. Advisory only, not blocking: `check-release-section-coverage` notes
that no heading on the v18 page names the 18.0 series.
Timing: `sync-release-index-currency` refuses to re-date a field that
already names the newest GA. So DATE is never corrected by the pass, and
no gate checks it. Write the UTC day of the refresh whose Version
Packages PR is merged. The docs PR has to land after this one and before
that refresh. Until both have landed, every six-hourly refresh is red.
That red is loud and publishes nothing, and the standing Version
Packages PR keeps its last validated content (`18.0.0-next.6`).
The other route was considered and not taken: teaching
`sync-release-index-currency` a prerelease-to-first-GA transition. That
would still leave the v18 page's blockquote (blocker 2) needing a
sentence, so the docs PR is needed either way. It would also reverse
that rewriter's documented boundary ("An entry whose parenthetical is
prose. Same reason.").
## Mode-keyed gates, read on the exited tree (`abec338235`)
| gate | verdict |
|---|---|
| `scripts/check-changeset-no-major.mjs` | **Green, and it re-arms.**
PR-scoped `--base origin/main` exits 0: "This diff introduces no `major`
bump". `--list` exits 0: "18 pending changeset(s), 0 declaring a major.
.changeset/pre.json mode: exit". The 29 files in `.changeset/pre/` that
declare a major (47 major entries) are **not** refused: the gate judges
diffs, and no PR's diff carries them. Re-arm probe (rehearsal clone
only): a new `"@objectstack/spec": major` changeset on the exited tree
gives **exit 1** ("This PR introduces changeset(s) that declare a
`major` bump"). The control, the same tree with `mode: "pre"`, gives
**exit 0** (exempt). Consequence: from the merge on, an open PR that
adds a `major` changeset goes red at `Check Changeset` unless it carries
`allow-major`. |
| `release.yml` pending count (Render `:511`ff, Restore `:861`ff) |
**Green.** In `exit` mode it counts `.changeset/*.md` plus
`.changeset/pre/*.md` = 1029, equal to what `changeset version` deleted.
Restore reads the mode from the restored tree, because the pass deleted
`pre.json`. Replayed verbatim, it restored 1029. |
| Major-boundary classes (`release.yml:373`ff) | **Green, nothing
moves.** `PROTOCOL_VERSION` already reads `'18.0.0'` ("✓
PROTOCOL_VERSION already 18.0.0"). The blank template already stamps
`^18.0.0` and `'^18'` ("template stamps moved: 0"). Validate reports
"major-boundary surfaces left unvalidated: 0", so the
`protocol-version.ts` refusal branch is not reached. |
| hotcrm smoke (`release.yml:1941`ff) | **Unaffected by the mode.** It
is no longer keyed on `pre.json`. `BLOCKING: '0'` keeps it advisory in
every mode (the card's "advisory while in pre mode" is out of date). |
| `cut-rc.yml` allowlist (`:232-237`) | **Unchanged.** It requires
`mode: pre` with `tag: rc`, so it refused before (tag `next`) and still
refuses (mode `exit`). The GA lane is `release.yml`. |
| `scripts/release-pending-publish.mjs` | **Green.** `--self-test` exits
0 (112 cases), and `annotate-shipped` ran in the dry-run as described
above. |
| `scripts/check-docs-image-tag.mjs` | **Green.** On the exited tree it
expects the newest GA, `17.7.0`, because `exit` still means the
prerelease line. On the post-version tree it is green inside Validate,
with the docs at `18.0.0`. |
| `scripts/sync-docs-image-tags.mjs` | **Green.** In the dry-run it
moved 9 pins on 3 surfaces from `17.7.0` to `18.0.0`, and
`check:docs-image-tag-sync` is green on both trees. |
| `scripts/check-published-files.mjs` | **Green.** "73 publishable
package(s) of 84 workspace member(s)". |
| `scripts/check-adr-0087-registration.mjs` | **Green.** `--base
origin/main` exits 0 and `--self-test` exits 0. |
| `pr-automation.yml` `Check Changeset` | **Phase-agnostic by design.**
"`changeset pre exit` re-arms it", as measured in the probe above. |
| `scripts/objectui-changeset-digest.mjs` `inPreMode` | **Reads false
after the exit.** An objectui `major` would now be digested as `minor`,
which mirrors the no-major guard. `check:objectui-changeset` is green. |
## Changeset and label
This PR adds no changeset, and the mode switch is not a package change.
The `Check Changeset` job's route 0 does not apply, because no
`.changeset/*.md` row changes (`pre.json` is not a `.md`). Route 2
applies, because the PR releases nothing: neither `.changeset/pre.json`
nor `content/docs/**` is in any public package's `files[]` (measured
over every non-private `package.json`). So `skip-changeset` is applied
at creation.
## Gates (local, exit codes captured before any pipe)
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `abec338235` derives **54** commands for
this two-file diff.
- The `--ran` reconciliation reads "54 derived, 54 run, 0 NOT-MEASURED,
0 UNRUN", and all 54 exit 0.
- Two families first returned exit 3 (PREREQUISITE NOT MET), and both
are recorded at their re-run: `check:docs-transcript-drift` needs the
lint dist, and `check:skill-examples` needs the client and client-react
dists. I built `@objectstack/lint...` (4 packages, 121 s) and
`@objectstack/client-react...` (39 packages, 340 s) under
`os-verify-lock.sh`, then re-ran both, exit 0.
- Beyond the derivation, all exit 0: `check-changeset-no-major --list`,
`check-changeset-fixed`, `check:docs-image-tag-sync`,
`check:release-page-status`, `check-release-section-coverage --strict`,
`check:release-index-currency-sync`, `check:future-spec-major`,
`check:template-version-sync`, `check:skill-compatibility` and
`check:release-notes`. These are the exited, pre-version tree, which
stays green until the version pass runs.
- **NOT MEASURED:**
- package tests and type-check lanes, because no TypeScript or package
source is touched;
- the `changesets/action` read rehearsal, because it is a GitHub Action,
so the CLI's own reader was read instead;
- `check:dev-prereqs`, because the diff does not touch the
`packages/spec` build.
- CI check-runs: see the report comment on the card.
## Acceptance notes
- **The standing Version Packages PR (#24524) reads `18.0.0-next.6`.**
Do not merge it after this PR expecting the GA. Until the docs blockers
land, every refresh is red and it keeps next.6.
- If it is merged after this PR anyway, it publishes `18.0.0-next.6` to
`next`. The version commit's tree still carries `pre.json` with `mode:
"exit"`, so `getReleaseTag` returns `preState.tag`. The GA cut then
follows on the next green refresh. This is a reading of the code, not a
measurement.
- **The GA CHANGELOG is large.** Measured at `4fe7eb6abe` (1028
changesets):
- `packages/spec/CHANGELOG.md` gains a 6,006-line `## 18.0.0` section of
983,037 bytes, added on top of the six `18.0.0-next.N` sections;
- the version diff is +58,729 / −21,347 lines.
- The GitHub Release bodies are truncated at 125,000 characters by
`release-github-releases.mjs`.
- **`--list` undercounts in `exit` mode.** `check-changeset-no-major
--list` and the `check-adr-0087-registration` audit surfaces count root
stock only (18) and exclude `.changeset/pre/` as consumed, while the GA
pass consumes 1029. This is informational only, because both gates judge
diffs. Noted, not carded.
- The card's mechanism hypothesis that `pre exit` "expects
`initialVersions`/`changesets`" does not hold under v3 (see The change,
item 1).
- objectstack-ai/objectui's own pre mode is out of scope and untouched.
No gate was added.
## Card checklist
- **Wanted: exit pre mode with the CLI, never by hand.** Done: `pnpm
changeset pre exit`, committed as written.
- **Wanted: a dry-run with every package's versions, the consumed counts
and the fate of `pre.json`.** Done: the tables above (73 members to
`18.0.0`; 1011 consumed from `pre/` plus 18 from the root; `pre.json`
deleted).
- **Wanted: every mode-keyed gate read on the exited tree.** Done: the
gate table. The two gates that break the GA Version Packages PR are the
release-docs refusals, and their remedy is release-owned prose, not a
small gate edit. So they are named with their remedy for the seat
instead of being edited here.
- **Wanted: no publish, tag, Version Packages PR or release dispatch.**
Held.
- **Wanted: the PR stays draft.** Held.
- **Done when ①: draft open, with the plan and the gate verdicts.** This
PR.
- **Done when ②: the next Version Packages PR's head carries `18.0.0`
for `@objectstack/spec`.** Not reachable until the docs-only PR above
lands. Leg B shows that once it does, the refresh validates with
`@objectstack/spec` at `18.0.0`.
## 维护者速读(草稿)
**改了什么**:退出 Changesets 预发布模式。`.changeset/pre.json` 由 `pnpm changeset pre
exit` 写出,模式从 `pre` 改为 `exit`。另外把
`content/docs/protocol/backward-compatibility.mdx` 里 11 处"v18
正在预发布"的现在时句子改成过去时,因为合并后这些句子就不再成立。没有代码、工作流或 changeset 改动。
**为什么改**:执行维护者的指令「发 GA 要先退出预发布模式。立卡派发」。在临时克隆里演练了版本步骤。合并后,下一次版本 PR 会把 73
个锁步包从 `18.0.0-next.5` 定为 `18.0.0`(不是 next.6,也不是 19.0.0)。这一步会消费全部 1029 条
changeset,删除 `pre.json`,发布时打 npm 的 `latest` 标签。
**风险与代价(含回滚)**:
- **只合并本 PR,GA 版本 PR 还出不来。** 演练表明,版本 PR
的刷新会在两处按设计拒绝:`content/docs/releases/` 下 v18 的发布页和发布索引仍写着"预发布、没有 GA
日期",需要人写一句 GA 文案。这两处属于发布文档,本 PR 不能改,要另开一个 docs-only PR。加上替代文案后,整条流程演练全绿。
- docs PR 落地之前,每 6 小时一次的刷新都会变红。变红不发布任何东西,现有的版本 PR #24524 会停在
`18.0.0-next.6`,不要把它当成 GA 合并。
- 合并后"禁止 major"守卫重新生效:在途 PR 如果带 major changeset,会在 Check Changeset
变红,除非加 `allow-major`。
- 合并之后、GA 发布之前,`latest` 仍是 17.7.0。
- 18.0.0 的 CHANGELOG 会把所有预发布条目再汇总一遍,约 1 MB。
- 回滚:在 GA 版本 PR 合并之前,直接 revert 本 PR 即可(恢复 `mode: "pre"`)。GA 发布到 npm
之后无法回滚。
**席位意见**:
**你要做的**:
1. 决定 GA 时机。
2. 让席位出那份 docs-only PR,写好 GA 文案。发布索引里的日期要写成最终被合并的那次版本 PR 刷新的 UTC
日期,这个日期没有门禁校验。
3. 先合并本 PR,再合并 docs PR,然后触发版本 PR 刷新。确认它的 head 里 `@objectstack/spec` 是
`18.0.0` 之后,再合并版本 PR。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp)_
---------
Co-authored-by: Claude <noreply@anthropic.com>next), so the next version pass cuts the 18.0.0 GA (#24555)1 parent de43524 commit c46b5f1
2 files changed
Lines changed: 12 additions & 12 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
68 | | - | |
| 68 | + | |
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
82 | | - | |
| 82 | + | |
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
86 | | - | |
| 86 | + | |
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
| |||
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
95 | | - | |
| 95 | + | |
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
| |||
114 | 114 | | |
115 | 115 | | |
116 | 116 | | |
117 | | - | |
| 117 | + | |
118 | 118 | | |
119 | 119 | | |
120 | 120 | | |
| |||
173 | 173 | | |
174 | 174 | | |
175 | 175 | | |
176 | | - | |
| 176 | + | |
177 | 177 | | |
178 | 178 | | |
179 | 179 | | |
| |||
187 | 187 | | |
188 | 188 | | |
189 | 189 | | |
190 | | - | |
| 190 | + | |
191 | 191 | | |
192 | 192 | | |
193 | 193 | | |
194 | 194 | | |
195 | 195 | | |
196 | 196 | | |
197 | | - | |
| 197 | + | |
198 | 198 | | |
199 | 199 | | |
200 | 200 | | |
201 | 201 | | |
202 | 202 | | |
203 | 203 | | |
204 | 204 | | |
205 | | - | |
| 205 | + | |
206 | 206 | | |
207 | 207 | | |
208 | 208 | | |
209 | | - | |
| 209 | + | |
210 | 210 | | |
211 | 211 | | |
212 | 212 | | |
| |||
0 commit comments