Skip to content

Commit c46b5f1

Browse files
chore(release): exit Changesets pre mode (next), so the next version pass cuts the 18.0.0 GA (#24555)
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>
1 parent de43524 commit c46b5f1

2 files changed

Lines changed: 12 additions & 12 deletions

File tree

‎.changeset/pre.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"mode": "pre",
2+
"mode": "exit",
33
"tag": "next"
44
}

‎content/docs/protocol/backward-compatibility.mdx‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ ObjectStack follows strict backward compatibility guarantees to ensure predictab
1717
<Callout type="warn">
1818
**The 17.x minors ran under a launch-window override: a breaking change shipped as a MINOR release, not a MAJOR one.** Every published `@objectstack/*` package versions in lockstep, so the version number alone is not an upgrade-safety signal; read [Launch Window](#launch-window-minor-releases-can-contain-breaking-changes) at the end of this page before you upgrade across 17.x.
1919

20-
**The v18 line is a major in prerelease.** Changesets is in pre mode with the tag `next`, and `scripts/check-changeset-no-major.mjs` stands aside in pre mode, so a pull request may declare a major bump. Every release cut from `main` is the next `18.0.0-next.N` on npm's `next` tag, while `latest` stays on 17.x. See [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line).
20+
**The v18 line has exited prerelease mode.** While Changesets was in pre mode with the tag `next`, every release cut from `main` was the next `18.0.0-next.N` on npm's `next` tag, and `scripts/check-changeset-no-major.mjs` stood aside in pre mode, so a pull request could declare a major bump. Changesets has exited pre mode, so the line's GA version is `18.0.0`, which publishes to npm's `latest` tag, and the guard again fails a pull request that declares a `major` bump. See [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line).
2121
</Callout>
2222

2323
ObjectStack follows [Semantic Versioning 2.0.0](https://semver.org/) (`MAJOR.MINOR.PATCH`). The table below describes what each component means; which version a breaking change ships in depends on the line, as the callout above says.
@@ -65,7 +65,7 @@ When a feature, schema property, or API is deprecated, ObjectStack follows a str
6565
- Code examples are updated to use the replacement
6666
- CLI tooling may provide automated migration commands
6767

68-
### Phase 3: Removal (MINOR release in 17.x, the next `18.0.0-next.N` on the v18 line)
68+
### Phase 3: Removal (MINOR release in 17.x, the next `18.0.0-next.N` during the v18 prerelease)
6969

7070
- Deprecated feature is removed from the schema
7171
- TypeScript types no longer include the property
@@ -79,11 +79,11 @@ Shipped examples: `17.2.0` retired `http_request_errors_total` and `sys_position
7979
```mermaid
8080
flowchart TD
8181
A["Deprecation notice — feature marked deprecated (warning emitted)"] --> B["Migration period — deprecated feature keeps working, no behavior change"]
82-
B --> C["Removal — retired at the boundary the ADR-0087 registry records (MINOR release in 17.x, the next 18.0.0-next.N on the v18 line)"]
82+
B --> C["Removal — retired at the boundary the ADR-0087 registry records (MINOR release in 17.x, the next 18.0.0-next.N during the v18 prerelease)"]
8383
```
8484

8585
<Callout type="warn">
86-
**No minimum survival window.** ObjectStack does not guarantee a deprecated feature survives for a fixed number of releases — [ADR-0049](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0049-no-unenforced-security-properties.md)'s dispositions for a flagged property are enforce, `experimental`, or remove, and none of them carries a dwell time. A deprecated feature is retired at the boundary the ADR-0087 registry records for it; in the 17.x minors that removal itself landed in a MINOR release, so a MINOR bump was where to expect a removal to arrive. On the v18 line it lands in the next `18.0.0-next.N`.
86+
**No minimum survival window.** ObjectStack does not guarantee a deprecated feature survives for a fixed number of releases — [ADR-0049](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0049-no-unenforced-security-properties.md)'s dispositions for a flagged property are enforce, `experimental`, or remove, and none of them carries a dwell time. A deprecated feature is retired at the boundary the ADR-0087 registry records for it; in the 17.x minors that removal itself landed in a MINOR release, so a MINOR bump was where to expect a removal to arrive. During the v18 prerelease it landed in the next `18.0.0-next.N`.
8787
</Callout>
8888

8989
---
@@ -92,7 +92,7 @@ flowchart TD
9292

9393
### What Constitutes a Breaking Change
9494

95-
Read the **Breaking?** column, not the version number: in the 17.x minors every row below — breaking or not — shipped in a MINOR or PATCH release, and on the v18 line every row ships in the next `18.0.0-next.N`, so on neither line does the bump size tell you whether you have work to do.
95+
Read the **Breaking?** column, not the version number: in the 17.x minors every row below — breaking or not — shipped in a MINOR or PATCH release, and during the v18 prerelease every row shipped in the next `18.0.0-next.N`, so in neither case does the bump size tell you whether you have work to do.
9696

9797
| Change Type | Breaking? | Version Impact (17.x minors) |
9898
|:---|:---|:---|
@@ -114,7 +114,7 @@ Read the **Breaking?** column, not the version number: in the 17.x minors every
114114
1. **RFC (Request for Comments)** — Breaking changes are proposed as GitHub issues with the `protocol:breaking` label.
115115
2. **Deprecation** — The old behavior is deprecated in a MINOR release (see timeline above).
116116
3. **Migration Guide** — A detailed migration guide is published before the removal lands, in the release notes for the version that carries it.
117-
4. **Release** — In the 17.x minors the breaking change shipped in the next **MINOR** version, carrying a changeset entry marked `**BREAKING**`, and `scripts/check-changeset-no-major.mjs` failed a pull request that declared a `major` bump, because under lockstep one `major` would promote all 73 published packages. On the v18 line it ships in the next `18.0.0-next.N`: the guard stands aside in pre mode, so a pull request may declare a major bump. See [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line).
117+
4. **Release** — In the 17.x minors the breaking change shipped in the next **MINOR** version, carrying a changeset entry marked `**BREAKING**`, and `scripts/check-changeset-no-major.mjs` failed a pull request that declared a `major` bump, because under lockstep one `major` would promote all 73 published packages. During the v18 prerelease it shipped in the next `18.0.0-next.N`, because the guard stood aside in pre mode and a pull request could declare a major bump. Changesets has exited pre mode, so the guard fails a `major` bump again. See [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line).
118118

119119
---
120120

@@ -173,7 +173,7 @@ The `@objectstack/spec` package provides additional stability guarantees:
173173
### Export Stability
174174

175175
- All public exports are listed in the package's `index.ts` barrel files
176-
- Removing an export is always a breaking change; the 17.x minors shipped it in a MINOR release, and on the v18 line it ships in the next `18.0.0-next.N`
176+
- Removing an export is always a breaking change; the 17.x minors shipped it in a MINOR release, and during the v18 prerelease it shipped in the next `18.0.0-next.N`
177177
- Internal modules (prefixed with `_` or in `internal/` directories) are not covered by SemVer guarantees
178178

179179
### Runtime Behavior
@@ -187,26 +187,26 @@ The `@objectstack/spec` package provides additional stability guarantees:
187187
## Launch Window: MINOR Releases Can Contain Breaking Changes
188188

189189
<Callout type="warn">
190-
**The 17.x minors ran under this exception — read it before you upgrade across them.** Every published `@objectstack/*` package versions in **lockstep**, and in the 17.x minors a breaking change shipped as a **MINOR** release rather than burning a MAJOR, so those minors contain documented breaking changes. The v18 line is a major in prerelease: every release cut from `main` is the next `18.0.0-next.N` on npm's `next` tag, while `latest` stays on 17.x. See [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line).
190+
**The 17.x minors ran under this exception — read it before you upgrade across them.** Every published `@objectstack/*` package versions in **lockstep**, and in the 17.x minors a breaking change shipped as a **MINOR** release rather than burning a MAJOR, so those minors contain documented breaking changes. During the v18 prerelease every release cut from `main` was the next `18.0.0-next.N` on npm's `next` tag; Changesets has exited pre mode, so the line's GA version is `18.0.0`, which publishes to npm's `latest` tag. See [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line).
191191
</Callout>
192192

193193
### Which surfaces this covers
194194

195195
**All of them.** It was not scoped to an experimental corner or a pre-release channel: all **73** packages published from this repository belong to a single Changesets `fixed` group, so they share one version number and one policy. No published surface was exempt.
196196

197-
The convention was enforced rather than informal — `scripts/check-changeset-no-major.mjs` failed a pull request that introduced a `major` bump, because under lockstep a single `major` on one package would promote the entire stack. Changesets is now in pre mode with the tag `next`, and the guard stands aside in pre mode, so a pull request on the v18 line may declare a major bump.
197+
The convention was enforced rather than informal — `scripts/check-changeset-no-major.mjs` failed a pull request that introduced a `major` bump, because under lockstep a single `major` on one package would promote the entire stack. During the v18 prerelease Changesets was in pre mode with the tag `next` and the guard stood aside, so a pull request on the v18 line could declare a major bump. Changesets has exited pre mode, and the guard fails a `major` bump again.
198198

199199
### What this means for an upgrade
200200

201201
- **Do not read a MINOR bump as safe to take unattended.** Read the [release notes](/docs/releases) for the version you are moving to: they lead with breaking changes and carry the migration steps.
202202
- **Pin exact versions** instead of caret ranges if you cannot review each MINOR before it lands.
203203
- **Diff your own metadata** across the upgrade with `os diff <before> <after> --breaking-only`.
204204

205-
MAJOR releases do still happen — 17.0.0 was cut precisely because its breaking density was too high to carry `^16.x` consumers across on a caret range, and the v18 line is a major in prerelease — but in the 17.x minors an individual breaking change did not, on its own, force one.
205+
MAJOR releases do still happen — 17.0.0 was cut precisely because its breaking density was too high to carry `^16.x` consumers across on a caret range, and the v18 line is the next one — but in the 17.x minors an individual breaking change did not, on its own, force one.
206206

207207
### When it stops applying
208208

209-
The tables and deprecation timeline above state this rule for the 17.x minors directly, beside what the v18 line does. Classic SemVer — where a breaking change once again requires a MAJOR — is the settled form the project returns to once the launch window closes: the window closes at GA, and strict SemVer resumes from that point. **For the 17.x minors, this section was the operative rule wherever any part of this page disagreed.** For the `18.0.0-next.N` prereleases, the operative rule is [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line).
209+
The tables and deprecation timeline above state this rule for the 17.x minors directly, beside what the v18 prerelease did. Classic SemVer — where a breaking change once again requires a MAJOR — is the settled form the project returns to once the launch window closes: the window closes at GA, and strict SemVer resumes from that point. **For the 17.x minors, this section was the operative rule wherever any part of this page disagreed.** For the `18.0.0-next.N` prereleases, the operative rule is [how a breaking stage lands on the v18 line](/docs/releases/v18#how-a-breaking-stage-lands-on-this-line).
210210

211211
---
212212

0 commit comments

Comments
 (0)