Skip to content

Commit ec8f37c

Browse files
fix(scripts): in Changesets pre mode the doc image and install pins follow the newest GA, not the prerelease (#22134)
Fixes #22131 Clause-②: no Refs #22085 (the parent; this PR carries its Q3 half). Ruling record: comment 6049734955 on #22085, quoted as the dispatch carried it: > **Q3 → B. During the `next` line, the documented image and install versions follow the newest GA, not the prerelease.** In pre mode `sync-docs-image-tags` and `check-docs-image-tag` pin the newest GA (read from the CHANGELOG, the same way the GA is already judged); outside pre mode nothing changes. ⛔ Not taken: A (self-hosting and upgrade documents reading `18.0.0-next.N` while npm `latest` and the published image stay 17.7.0, so a production user copying the documents is led into the breaking prerelease line). ## What changed Two files, both under `scripts/`, nothing published: - `scripts/check-docs-image-tag.mjs` gains `expectedVersion(root)`, the ONE function that answers which version the doc surfaces must pin. - No `.changeset/pre.json`: `packages/cli/package.json`'s version (`VERSION_SOURCE`, kept with its name), as before. - `.changeset/pre.json` in mode `pre` or `exit`: the newest GA in `packages/spec/CHANGELOG.md`, overall (not of the CLI's major). - It reads through the existing readers only: `SPEC_CHANGELOG`, `gaVersions` and `newestGaOfMajor` from `check-release-section-coverage.mjs` (unchanged), and `readPre` from `check-changeset-no-major.mjs` (unchanged). No second GA reader and no third `pre.json` reader. - Every pre-mode state it cannot read throws instead of falling back: a `pre.json` that is present but does not parse, a mode Changesets never writes, no spec CHANGELOG, or a CHANGELOG with no GA heading. - `main()` judges against it. The STALE detail, the scope line and the red footer name the source used. A pre-mode red also prints why the expectation is not `packages/cli`'s prerelease. Outside pre mode the gate's output is byte-identical to `main`'s. - `scripts/sync-docs-image-tags.mjs` gains `syncRepo({ root })`, which `main()` runs: the target comes from the gate's `expectedVersion()`, followed by the existing `syncSurfaces()`. The rewriter and the gate therefore read one value. A pre-mode run logs why the docs did not follow `packages/cli`. Not touched: `content/docs/**`, `release.yml`, `cut-rc.yml`, `lint.yml`, the root `version` script, `.changeset/**`, #21988. ## Premises measured on `origin/main` `8fc50b7647` 1. The root `version` script (`package.json:20`) is `changeset version && sync-protocol-version && sync-template-versions && sync-docs-image-tags && sync-release-index-currency`. Holds. 2. `VERSION_SOURCE`, `SURFACES` and `PROSE_CLAIMS` are as the card states, and the rewriter imports them from the gate. Holds. 3. `check-release-section-coverage.mjs` exports `SPEC_CHANGELOG`, `gaVersions` (ascending, GA-only, end-anchored) and `newestGaOfMajor`. Holds. - On a pre-mode tree the answer has to be the newest GA overall. After the first `next` cut the CHANGELOG's top heading is `## 18.0.0-next.0` and there is no GA of 18, so `newestGaOfMajor(versions, 18)` is `null`. - `@objectstack/spec` and `@objectstack/cli` are in one `fixed` group in `.changeset/config.json`, so the spec CHANGELOG's newest GA is also the CLI's. 4. Pre mode is `.changeset/pre.json`. **Refined:** Changesets writes it in two modes, and both are the prerelease line. - `changeset pre exit` only rewrites the mode to `"exit"`, so the tree stays on `18.0.0-next.N`. - The next `changeset version` then computes the GA and deletes `pre.json`. Measured in `@changesets/apply-release-plan` 8.1.1, which removes the file when the mode is `exit`; replayed below. - That `exit` commit reaches `main` before the Version Packages PR does. Keying on `"pre"` alone would expect `18.0.0-next.N` in that window while the docs read the GA, which reds this required gate on every PR in between, the exiting PR included. - So `exit` counts as pre mode here. The self-tests pin this case. 5. Other consumers: no change in meaning except the rc lane below. - `release.yml:569` and `cut-rc.yml:605` import `SURFACES` only. Re-run after the change, the import prints the same 3 paths and exits 0; the two new imports are side-effect-free, and `check:entry-guard` is green. - `lint.yml:2649` and `lint.yml:2672` run the two gates. Outside pre mode the output is identical. - **Correction to the dispatch's reading of `cut-rc.yml`:** it does not refuse pre mode. It refuses unless `pre.json` is mode `pre` **with tag `rc`** (`cut-rc.yml:232-236`). So it never runs on the `next` line, but it does run in pre mode on an rc line. - Under this change, an rc cut's version pass leaves the doc surfaces at the newest GA instead of stamping `X.Y.Z-rc.N`. That follows the ruling's text ("in pre mode") and its reason, a production reader led into a prerelease, which applies to an rc as well. - Its allowlist permits the doc surfaces to change and does not require it (`git add -A -- … "${DOCS_SURFACES[@]}"`), so a cut that leaves them unchanged passes. The self-test pins the rc case: CLI `17.8.0-rc.0`, expected `17.7.0`. ## The pin, replayed Each run used a throwaway `git worktree add --detach` under the session scratch directory, then `pnpm install --frozen-lockfile --offline --ignore-scripts` and `pnpm run version`. All trees were removed afterwards. PR #22084's two files were copied from its head `a630de657d`: `.changeset/pre.json` (`{"mode": "pre", "tag": "next"}`) and `.changeset/22080-v18-line-opens.md`. **Tree 1: this branch (`a4b1b5106d`) plus PR #22084's two `.changeset` files.** `pnpm run version` exit 0. `packages/cli` went from `17.7.0` to `18.0.0-next.0`, and the spec CHANGELOG's top heading is now `## 18.0.0-next.0`, above `## 17.7.0`. ```text ✓ sync-docs-image-tags: all 9 concrete pin(s) across 3 surface(s) already at the newest GA in packages/spec/CHANGELOG.md (pre mode) 17.7.0 — nothing rewritten. Pre mode: .changeset/pre.json is in mode "pre" (tag "next"), so the documented versions follow the newest GA, 17.7.0, and not packages/cli/package.json's 18.0.0-next.0. ... $ git diff -- docker/README.md content/docs/deployment/self-hosting.mdx content/docs/upgrading.mdx | wc -l 0 pins on the 3 surfaces: 7 x ghcr.io/objectstack-ai/objectstack:17.7.0, 1 x OS_CLI_VERSION=17.7.0, 1 x @objectstack/cli@17.7.0 $ pnpm check:docs-image-tag # exit 0 check-docs-image-tag: OK (3/3 enumerated surface(s) read, 9 concrete pin(s) compared against the newest GA in packages/spec/CHANGELOG.md (pre mode) 17.7.0, ...) ``` **Tree 1, continued: the exit window and the GA pass.** These are the states premise 4 is about. ```text $ pnpm exec changeset pre exit # .changeset/pre.json → {"mode": "exit", "tag": "next"}; packages/cli still 18.0.0-next.0 $ pnpm check:docs-image-tag # exit 0, compared against the newest GA in packages/spec/CHANGELOG.md (pre mode) 17.7.0 $ pnpm run version # exit 0; .changeset/pre.json deleted; packages/cli 18.0.0 ✓ sync-docs-image-tags: 9 pin(s) across 3 surface(s) → 18.0.0 (lockstep with packages/cli/package.json). $ pnpm check:docs-image-tag # exit 0, compared against packages/cli/package.json 18.0.0 ``` **Tree 2: this branch on plain `main`, without `pre.json`.** `pnpm run version` exit 0. `packages/cli` went from `17.7.0` to `17.8.0` (the 58 pending changesets), and the surfaces move to the CLI's version, as they do today. ```text ✓ sync-docs-image-tags: 9 pin(s) across 3 surface(s) → 17.8.0 (lockstep with packages/cli/package.json). pins on the 3 surfaces: 7 x ghcr.io/objectstack-ai/objectstack:17.8.0, 1 x OS_CLI_VERSION=17.8.0, 1 x @objectstack/cli@17.8.0 $ pnpm check:docs-image-tag # exit 0, compared against packages/cli/package.json 17.8.0 ``` **Control tree: `main` at `8fc50b7647`, without this change, plus PR #22084's two files.** This is the state the ruling rejects (⛔ A): ```text ✓ sync-docs-image-tags: 9 pin(s) across 3 surface(s) → 18.0.0-next.0 (lockstep with packages/cli/package.json). pins on the 3 surfaces: 7 x ghcr.io/objectstack-ai/objectstack:18.0.0-next.0, 1 x OS_CLI_VERSION=18.0.0-next.0, 1 x @objectstack/cli@18.0.0-next.0 ``` ## Self-tests and the ablation New batteries, each case with its positive control: - In the gate, `Pre mode: the expectation is the newest GA (#22131)` has 18 cases. The roster floor goes from 11 to 12, and the run has 112 assertions. - In the rewriter, `Control I: in pre mode the target is the newest GA, from the gate's one function` has 10 cases and runs `syncRepo()`. The roster floor goes from 8 to 9, and the run has 45 assertions. The three Done-when cases, and what each is controlled against: | Case | Expected result | Positive control | |---|---|---| | Pre mode, CLI `18.0.0-next.0`, newest GA `17.7.0` | expects `17.7.0` | The same tree's CLI reads `18.0.0-next.0`, and the CHANGELOG has no GA of 18 | | The same tree without `pre.json` | expects the CLI's `18.0.0-next.0` | Case 1 answers differently, and `pre.json` is the only difference between the two trees | | Pre mode, docs already at the GA | gate green, rewriter writes nothing (byte-identical, mtime unchanged) | The same docs against the CLI's version are 3 STALE in the gate and 3 rewrites in the rewriter | Further cases cover mode `exit` and an rc pre mode, which both expect the GA. Pins moved onto the prerelease in pre mode are STALE, and the finding names the pre-mode source. Each of the four unreadable states is refused, and the rewriter refuses before any write. **Ablation, at `a4b1b5106d`.** The change was committed first. `node scripts/ablation-replace.mjs` rewrote the pre-mode test in `expectedVersion()` (`if (!existsSync(join(root, PRE_STATE))) {` became `if (true) {`), which is the old logic: `pre.json` ignored. - The mutation landed: the anchor count went 1 → 0, the marker count during the mutation was 1, and the blob went `e27045098c80` → `97307ba4e54a`. - `node scripts/check-docs-image-tag.mjs --self-test` exited 1 with **13 failures**, every one a new pre-mode case. - `node scripts/sync-docs-image-tags.mjs --self-test` exited 1 with **6 failures**, every one a new pre-mode case. - The controls that do not depend on the branch stayed green. - The restore was proven by blob hash: after the restore the blob is `e27045098c80`, equal to `HEAD:scripts/check-docs-image-tag.mjs`, and `git diff HEAD` is empty. ## Gates, at `a4b1b5106d` `node scripts/pm/dispatch-gates.mjs --commands` was derived from this worktree over the actual diff (2 paths, +548/-39) and gave 31 commands, the same 31 the dispatch named. All 31 were run and every one exited 0. - `pnpm check:pm-dispatch-gates` passed 1976 cases (856s). - `pnpm check:docs-image-tag` (112 assertions, then OK on the live tree) and `pnpm check:docs-image-tag-sync` (45 assertions) passed. - `check:entry-guard`, `check:nul-bytes`, `check:scripts-symbol-anchors` and `check:declaration-mirrors` passed. - `dispatch-gates --ran`: 31 derived, 31 run, 0 NOT-MEASURED, 0 UNRUN. Lint was a proven narrowing to the changed files, not the repo-wide `pnpm lint`, which CI runs: - Population, read from eslint's own config: `isPathIgnored` is `false` for both files. - Count, from `--format json`: 2 results, 0 errors and 0 warnings, with `--no-inline-config`. - Invariance: this repo's eslint config never enables type-aware linting (no `parserOptions.project`), so this diff cannot change the verdict on any untouched file. No changeset is needed. The root package is private and no package's `files[]` ships `scripts/`. Labelled `skip-changeset`. ## Acceptance notes None of these blocks this PR, and none is a defect. Each is wording that this change makes slightly inaccurate, in a file outside this PR's declared surface. Carrier for each: none. - `scripts/check-changeset-no-major.mjs`'s entry-guard comment says "Nothing imports this file today". `check-docs-image-tag.mjs` now imports `readPre`. The guard already makes the import inert, as `check:entry-guard` confirms. - `release.yml`'s post-version comment ("check:docs-image-tag — the 3 doc surfaces against packages/cli's NEW version") and `lint.yml`'s step name "Docs image tags track packages/cli's version" are true outside pre mode only. In pre mode the expectation is the newest GA. - `cut-rc.yml`'s staging comment (around line 497) lists "the 3 doc surfaces" among a cut's paths. An rc cut now leaves them unchanged (premise 5). - dispatch-gates now routes `.changeset/pre.json` to `check:docs-image-tag`, because the gate spells it. It does not route `packages/spec/CHANGELOG.md`, which is read only through an import from a gate module. That file is release-owned and only the version pass writes it. --- _Generated by [Claude Code](https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fec87e7 commit ec8f37c

2 files changed

Lines changed: 548 additions & 39 deletions

File tree

0 commit comments

Comments
 (0)