Repository navigation
fix(scripts): in Changesets pre mode the doc image and install pins follow the newest GA, not the prerelease - #22134
Merged
objectstack-fleet[bot] merged 2 commits intoOct 8, 2026
Conversation
…s pre mode expectedVersion() is the one function that answers which version the doc surfaces must pin: packages/cli/package.json outside pre mode, as before, and in pre mode (.changeset/pre.json in mode pre or exit) the newest GA in the spec CHANGELOG, read through check-release-section-coverage.mjs's gaVersions/newestGaOfMajor, with pre.json read by readPre. Unreadable pre-mode states are refused rather than falling back. Messages name the source used; outside pre mode the output is byte-identical. Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw Co-authored-by: Claude <noreply@anthropic.com>
…xpectedVersion() syncRepo() is what main() runs: the target from check-docs-image-tag.mjs's expectedVersion(), then syncSurfaces() over it, so in Changesets pre mode the version pass leaves the doc surfaces at the newest GA instead of stamping the prerelease, and outside pre mode it moves them to packages/cli's version as before. The self-test runs syncRepo() over pre-mode fixture trees, each case with its positive control. Claude-Session: https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw Co-authored-by: Claude <noreply@anthropic.com>
objectstack-fleet
Bot
deleted the
claude/issue-22131-docs-image-tags-pre-mode
branch
October 8, 2026 03:27
This was referenced Oct 8, 2026
fix(lint)!: the object save door gives the build's option visibleWhen verdict (#22032 pass 3)
#22151
Merged
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Oct 9, 2026
… verdict (objectstack-ai#22032 pass 3) (objectstack-ai#22151) Part of objectstack-ai#22032 Clause-②: no (narrowing) This is pass 3 of objectstack-ai#22032: a field option's `visibleWhen`. Pass 4 (the object's own action predicates) stays fenced, and the card stays open for it. ## What changes **The object save door gives the build's verdict on a field option's `visibleWhen`.** `formulas.mdx` says "the same `validateExpression` validator backs `os build` and metadata registration". After pass 2 the object door ran the whole field walk except the per-option loop, which kept its own guard. So an object whose option carried `visibleWhen: 'amount > 1'` (a bare field reference) still saved with a 200, while `os build` refused it at error. The server's option check cannot evaluate such a predicate and fails open (logged, allowed through), so the gate it declares is never enforced (read from `evaluateOptionVisibility` in `packages/objectql/src/validation/rule-validator.ts`). - **The lift is the guard, nothing else (H1 held).** On `origin/main` `8fc50b7647` the loop read `for (const [oi, opt] of (objectWrite ? [] : recordsOf(f.options)).entries())` (`validate-expressions.ts:2072`), under a `[objectstack-ai#22032] FENCED on an object write (pass 3 …)` comment. It now reads `recordsOf(f.options)`. On an object write the loop runs at the build's own position in the field walk, so the door gets both of the option's checks: - `check(optionWhere, opt.visibleWhen, objectName, 'record')`; - `refuseFieldTraversal(optionWhere, 'option visibleWhen', …)`, the refusal of a read through a reference field on `record` or `previous`. - **`current_user` keeps the build's two verdicts (H2).** An option's evaluator binds the acting user (ADR-0068 D1), so the build accepts `current_user` on an option and refuses it on the field-rule slots one level up. The door now gives both verdicts as the build does. The showcase's role gate, `'org_admin' in current_user.positions`, still saves on an option, and the same text on the field's own `visibleWhen` is refused at both doors. Both are pinned. - **No registry change (H3 re-verified).** The `validateStackExpressions` entry declares `runtimeTypes: ['flow', 'action', 'hook', 'object']` (`authoring-rules.ts`), and `runtimeAuthoringRulesFor('object')` (`runtime-gate.ts`) dispatches it. `runtime-gate.ts` is untouched. - **Docblocks made true.** `StackExpressionOptions.runtimeWriteType` now names four admitted passes and one fenced pass. `AuthoringRuleContext.runtimeWriteType` in `authoring-rules.ts`, the one line that reaches a built `.d.ts`, names the per-option pass. The function-head comment and the field walk's two comments move with it. In `authoring-rules.ts` the registry entry gains a `[objectstack-ai#22032, pass 3]` measurement comment, as passes 1 and 2 added theirs. Comments in four sibling test files are corrected so that none of them still says option `visibleWhen` is fenced. - **The door's verdict is the build's finding (H4).** The door's 422 issue and `runAuthoringRules('build', …)` give the same rule (`expression-invalid`), location (`object 'fx_option' · field 'province' option 'zj' visibleWhen`), path, message and hint. The pins compare these key by key. - **No code change in `packages/metadata-protocol`.** Only its test file gains the door-level pins. ## Pins - **Lint door:** `packages/lint/src/runtime-gate.object-option-visibility-writes.test.ts` (new, 14 tests). - LIT: one refused body per finding the pass gives. These are a bare `amount > 1`, an unregistered `sqrt(record.amount) > 1`, an unknown field `record.amont > 1`, a syntax error `record.country ==`, and the traversal refusal through `record.account` and through `previous.account`. Each is located at the option and asserted on its named subject. - CONTROL (the still-accepted cases): the `record.country` cascade, the showcase's `current_user.positions` role gate, a grant check plus role gate (`current_user.can(…) && …`), and a reference compared as a value (`record.account != null`). Each is clean at the door and at the build. - CONTRAST: `current_user` on the option and on the field's own `visibleWhen` in one body. The build and the door both give exactly one finding, at the field slot. - PARITY: for each refused body, the door's findings equal the build's. - The differential: a stored sibling's broken options are not this write's to answer for. - **The fence (enumeration pin)** in `packages/lint/src/runtime-gate.object-formula-writes.test.ts`. The fenced site is now pass 4's alone (an action `visible`). The option `visibleWhen` site moves to the lifted sites, beside the validation rule and the `requiredWhen`. The build flags all four sites. The object door flags the three lifted sites in the build's order, and `runStackExpressionPasses` on an object write returns exactly the build's findings for the admitted passes. - **Protocol door:** a new pass-3 block in `packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts`, through the real `saveMetaItem`, `publishMetaItem` and `publishPackageDrafts`. - (a) A bare reference, an unregistered function and a read through a reference field are each refused on an active save. The answer is a 422 `INVALID_METADATA` carrying the build's located finding, and nothing lands. - (a) The card-shaped body is refused on a draft's promotion and on a package draft publish (`outcome: 'refused'`, `failed` naming the object with `INVALID_METADATA`, the row left a draft). The draft saves themselves still succeed. - (b) The showcase's cascade and its `current_user` role gate still save, and the row lands active. The role gate rides every refused body too, which each yield exactly one finding. - (d) For each refused body, the door and `os build` give the same finding on rule, where, path, message and hint. ## Reverse verification (one-off, from committed HEAD `23ce6c494c`) - **What was mutated.** `scripts/ablation-replace.mjs` (wrap mode) put the guard back on the option loop. The new text was `const ablationFence22032p3 = objectWrite;` followed by `for (const [oi, opt] of (ablationFence22032p3 ? [] : recordsOf(f.options)).entries()) {`. The anchor was hit once, 1 to 0, and the blob went `879fcb828037` to `73bd026d1554`. The outer script carried `trap restore EXIT INT TERM` on the absolute path. - **Rebuild and dist proof.** `@objectstack/lint` was rebuilt, and `ablation-dist-preflight` found the marker in 4 built files. - **Lint suites (source): 9 failed and 14 passed, as predicted.** - Red: the 6 LIT tests, PARITY, and the two fence tests that assert the lifted sites. - Green: the 4 CONTROL tests, CONTRAST, the differential, the registry test, the build-flags-each-site test and the six formula-door tests. - **Protocol pass-3 block (dist-mediated): 6 failed and 1 passed, as predicted.** Red: the three (a) saves, (a) on promotion, (a) on package publish, and (d). Green: (b). - **Restore.** The tool restored the file: blob `879fcb828037` equals HEAD, and `git diff HEAD` is empty. The whole tree had 0 changed paths. Lint was rebuilt, and `--absent` found the marker gone from all 14 built files. Both suites went green again: lint 23 of 23, and the protocol file 99 of 99. ## Measurements - **Corpus first: the stop condition was not met (H5).** Every object this tree ships was judged before the door changed. That is every `*.object.ts` under `packages/**` and `examples/**` (111 files) plus the two `app-multi-package` sub-stacks: 118 objects in 18 groups. Each was judged at the raw shape and at the `ObjectSchema.parse` shape (0 parse failures), with its own group as context. - 5 option predicates on 2 fields of 1 object, all on `showcase_cascade`: `province`'s four `record.country` cascades (`zj`, `gd`, `ca`, `tx`) and `tier`'s `restricted` role gate (`'org_admin' in current_user.positions`). - At base `8fc50b7647`: 0 build errors and 0 build warnings for the option pass, through `validateStackExpressions` and through `runAuthoringRules('build')`, at both shapes. There were 0 door expression findings over all 118 objects. - Non-vacuity: the 5 sites are judged. Mutating one cascade to a bare `country` and the role gate to `sqrt(record.amount) > 1`, in a copy, gave 2 build errors at those two options. - At head `23ce6c494c`, and again at the merged heads `06d3cad963` and `3c1d3262ee`: 0 door errors and 0 door advisories over every object, through `runRuntimeAuthoringRules` with type `object`, at both shapes. The harness passes each object's own group as context, so at `3c1d3262ee` every one of those saves is an update and also runs the stored-universe pass that objectstack-ai#22118 added. - Positive control in the same harness (an option `visibleWhen: 'amount > 1'`): 1 build error at every head. The door gave 0 at base and 1 at head. - **Which doors newly answer 422.** The active publish save, a draft's promotion, and a package draft publish. Each was measured through the real methods above. A draft save stays ungated, measured by the same pins. ## Clause-② (measured) - **Accept set: narrowing.** An object write in publish mode answered 200 for an option `visibleWhen` the validator refuses. It now answers 422 on the three doors above. - **Built entry declarations.** In `@objectstack/lint` one doc comment moves (`AuthoringRuleContext.runtimeWriteType`). `StackExpressionOptions` and `runStackExpressionPasses` are not in the built declarations. No exported signature moves. - **Changeset.** `.changeset/22032-object-save-door-option-visible-when.md` covers `@objectstack/lint` and `@objectstack/metadata-protocol` as `minor`. It carries `fix(lint)!`, the `Clause-②: no (narrowing)` line, a BREAKING section with the remedy, and ADR-0087 `not-required (no-migration-prescription)`. `@objectstack/metadata-protocol` is listed as passes 1 and 2 listed it, although no code moves there: its save, promotion and package-publish doors are where the BREAKING behaviour can be seen. `.changeset/pre.json` is absent on `origin/main` (read at `8fc50b7647`, 2026-10-08T01:54Z, at `ef1fcb26a2`, 2026-10-08T02:40Z, and at `7ef50a4fbb`, 2026-10-08T03:39Z), so the bump is `minor` with the BREAKING banner, as in passes 1 and 2. The changeset says it supersedes the earlier objectstack-ai#22032 entries' line that option `visibleWhen` is not judged at this door. ## Merges - **`06d3cad963`** merges `origin/main` `ef1fcb26a2` (PR objectstack-ai#22103), through `scripts/pm/os-regen-merge.sh`. It was clean. - **`3c1d3262ee`** merges `origin/main` `7ef50a4fbb` (parents `06d3cad963` and `7ef50a4fbb`), through `scripts/pm/os-regen-merge.sh`. That brought PR objectstack-ai#22129 (objectstack-ai#21982), PR objectstack-ai#22094, PR objectstack-ai#22134, PR objectstack-ai#22133 (objectstack-ai#22118, the gate's object-write baseline keeps the written item's stored self), PR objectstack-ai#22126 and PR objectstack-ai#22140. - `validate-expressions.ts` auto-merged. PR objectstack-ai#22129's edit is in the flow `script` / `subflow` region; the option loop's lift is unchanged. - One conflict: `packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts`, one hunk. Both sides had added a new top-level `describe` block at the same place, after the pass-2 block. It was resolved by stacking: the pass-3 block first, then objectstack-ai#22118's stored-self block, and every line of both was kept. Against `7ef50a4fbb` the file shows only this branch's lines; against `06d3cad963` it shows only objectstack-ai#22118's 114 lines. - No generated path was touched by this branch. The rerun of the script took `origin/main`'s side of every generated path main moved, and that left nothing to commit. - This branch's own changes are the same before and after the merge: for each of the 9 files, the added and removed lines against the merge base are identical. The delta against `7ef50a4fbb` is 9 files, +467 / −45. - **The interaction with objectstack-ai#22118, measured.** objectstack-ai#22118 adds a third, stored-universe pass on an update into a context collection. A finding whose path names no entry is judged as one on the written item. The expression rule's paths are `where` strings, so an option finding is always judged that way. - A probe ran the real gate (`runRuntimeAuthoringRules`, type `object`) over 20 cases, against this branch's lint source before the merge (`06d3cad963`) and after it (`3c1d3262ee`). The cases are a create, an update over a stored self that is broken and over one that is clean, and a stored sibling that is broken, each for three refused bodies, plus two still-accepted bodies. - The verdicts are identical, case for case. Re-saving a broken option is still refused, including over a broken stored self, because re-saving a row is writing it. A clean save over a broken stored self passes. A broken sibling is never charged. - The pass-3 pins (differential, PARITY, LIT, CONTROL, CONTRAST) and objectstack-ai#22118's pins all pass at `3c1d3262ee`: lint 48 of 48 across the three files, and the protocol file 103 of 103. - This matches the code. The option pass reads only the written object's own field index, and binds `record` and `previous`, never `parent`, so the stored universe has nothing extra to offer it. ## Tests and gates (all at `3c1d3262ee`, the merge of `origin/main` `7ef50a4fbb`) - **`main` moved during the run (H6).** PR objectstack-ai#22103 landed as `ef1fcb26a2` and touched two files this pass edits, `authoring-rules.ts` and `runtime-gate.object-writes.test.ts`, in other hunks. It was merged with `scripts/pm/os-regen-merge.sh` as a merge commit. The merge was clean, and no generated path was taken from either side. After the merge: `pnpm install --frozen-lockfile`, a full turbo build (72 tasks), and `@objectstack/spec check:generated` ("All 15 generated artifacts are up to date"). objectstack-ai#22118 and objectstack-ai#21982 had not landed at that read (2026-10-08T02:40Z). Both have since landed, and they are merged at `3c1d3262ee` (see Merges). After that merge the same sequence ran: a full turbo build (72 tasks) and `check:generated` ("All 15 generated artifacts are up to date"). - **`@objectstack/lint`:** 125 files and 5729 tests passed. `typecheck` exit 0, with its test-typecheck included (`--listFiles`: the five touched or new lint test files are in the `tsconfig.test.json` program). - **`@objectstack/metadata-protocol`:** 221 files passed and 3 skipped; 28260 tests passed and 19 skipped. `typecheck` exit 0 (`--listFiles`: the door test file is in the program). - **Consumer readings.** Every package was built at the head named. - `@objectstack/objectql`, 8 files and 282 tests passed: `publish-package-drafts-response-conformance`, `save-meta-response-conformance`, `publish-meta-response-conformance`, `plugin.integration`, `engine-field-predicate-fault`, `engine-option-permission-predicate`, `validation/rule-validator.option-visibility` and `engine`. - `@objectstack/rest`, 11 files and 197 tests passed: every `meta-object-*` file and `meta-publish-package-scope`. - `@objectstack/cli`, 3 files and 16 tests passed, run as `--project integration` because the tier predicate puts them there: `validate-field-predicate-traversal` (which asserts an option `visibleWhen` `expression-invalid` finding), `authoring-rule-command-parity` and `verify-author-time-stage`. The three nightly-tier `*.e2e` files that assert `expression-invalid` are left to CI. - The search for other consumers covered every test file in the repository carrying `visibleWhen`, matched against the save-door entry points. Only the two packages above save an option `visibleWhen` through a door. - **Gates.** `dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 63 commands, the same set at `23ce6c494c`, `06d3cad963` and `3c1d3262ee`. At `3c1d3262ee` all 63 exit 0, each exit code captured before any pipe. `--ran` reconciles 63 derived, 63 run, 0 NOT-MEASURED and 0 UNRUN, with an exit code recorded for each. The printed artifact-roster block names 51 families, and all 51 ran at `3c1d3262ee`, each with exit 0. That is 47 in the same battery, one (`check:engine-double-contract`) already among the 63, and the three PR-scoped ones (`check-partof-closing-keyword`, `check-closing-target-claim`, `check-single-claim-paths`) run with this PR's number and this body. The same 63 also ran at `06d3cad963`, all exit 0. At `23ce6c494c`, before the merge, the same 63 ran: 61 exited 0 on the first run. `check:dual-build-cjs-loads` and `check:lean-entry-closure` first exited 3 (PREREQUISITE NOT MET: no full build) and exited 0 after the full build. - **ESLint, narrowed to the 8 touched TypeScript files** (`--no-inline-config --format json`): 8 files, 0 errors and 0 warnings. Each file is matched by `eslint.config.mjs` (`--print-config`), and none was ignored. The config enables no type-aware linting (no `parserOptions.project`), so this diff cannot move the verdict on any untouched file. ## Acceptance notes - **Out of this pass, reported for the seat: an option `visibleWhen` reading `parent` gets no verdict at the build, so none at the door either.** - Measured at `23ce6c494c` with scratch tests that were deleted afterwards. Through the real `saveMetaItem`, an object whose option carries `visibleWhen: "parent.status == 'closed'"` saved with success, and the row landed `active`. `runAuthoringRules('build')` gave 0 findings. - The server's option check (`evaluateValidationRules`, authenticated caller, the option picked) then logged `failed to evaluate (authenticated caller) — allowed through`, with `Unknown variable: parent`, and admitted the value. The option evaluator binds `record`, `previous`, the user and permissions only. - This is a gap in the build, which the door now mirrors. This card's contract is parity with the build, so it is not changed here. - **A code comment names an old spelling.** The comment above the option loop calls the showcase's legal usage `'admin' in current_user.positions`. The showcase now writes `'org_admin' in current_user.positions`, and the pins use that spelling. The comment is not changed here: per the contract review, it rides pass 4. - **The pending objectstack-ai#22032 changesets.** Pass 1's and pass 2's changesets each list option `visibleWhen` under "Unchanged", which was true at their heads. This pass's changeset says it supersedes that line rather than editing them, the same way pass 2 left pass 1's changeset alone. Per the contract review, reconciling those lines rides pass 4. - `formulas.mdx` could name the object save door. That would be a docs addition, not a correction of a false line. - **Contract review.** Triage's grade asks for one per pass. A PASS is on record for head `06d3cad963` (`6051573476`). The head has since moved to `3c1d3262ee` by the merge above, so the review for this head is the seat's. --- _Generated by [Claude Code](https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
What changed
Two files, both under
scripts/, nothing published:scripts/check-docs-image-tag.mjsgainsexpectedVersion(root), the ONE function that answers which version the doc surfaces must pin..changeset/pre.json:packages/cli/package.json's version (VERSION_SOURCE, kept with its name), as before..changeset/pre.jsonin modepreorexit: the newest GA inpackages/spec/CHANGELOG.md, overall (not of the CLI's major).SPEC_CHANGELOG,gaVersionsandnewestGaOfMajorfromcheck-release-section-coverage.mjs(unchanged), andreadPrefromcheck-changeset-no-major.mjs(unchanged). No second GA reader and no thirdpre.jsonreader.pre.jsonthat 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 notpackages/cli's prerelease. Outside pre mode the gate's output is byte-identical tomain's.scripts/sync-docs-image-tags.mjsgainssyncRepo({ root }), whichmain()runs: the target comes from the gate'sexpectedVersion(), followed by the existingsyncSurfaces(). The rewriter and the gate therefore read one value. A pre-mode run logs why the docs did not followpackages/cli.Not touched:
content/docs/**,release.yml,cut-rc.yml,lint.yml, the rootversionscript,.changeset/**, #21988.Premises measured on
origin/main8fc50b7647versionscript (package.json:20) ischangeset version && sync-protocol-version && sync-template-versions && sync-docs-image-tags && sync-release-index-currency. Holds.VERSION_SOURCE,SURFACESandPROSE_CLAIMSare as the card states, and the rewriter imports them from the gate. Holds.check-release-section-coverage.mjsexportsSPEC_CHANGELOG,gaVersions(ascending, GA-only, end-anchored) andnewestGaOfMajor. Holds.nextcut the CHANGELOG's top heading is## 18.0.0-next.0and there is no GA of 18, sonewestGaOfMajor(versions, 18)isnull.@objectstack/specand@objectstack/cliare in onefixedgroup in.changeset/config.json, so the spec CHANGELOG's newest GA is also the CLI's..changeset/pre.json. Refined: Changesets writes it in two modes, and both are the prerelease line.changeset pre exitonly rewrites the mode to"exit", so the tree stays on18.0.0-next.N.changeset versionthen computes the GA and deletespre.json. Measured in@changesets/apply-release-plan8.1.1, which removes the file when the mode isexit; replayed below.exitcommit reachesmainbefore the Version Packages PR does. Keying on"pre"alone would expect18.0.0-next.Nin that window while the docs read the GA, which reds this required gate on every PR in between, the exiting PR included.exitcounts as pre mode here. The self-tests pin this case.release.yml:569andcut-rc.yml:605importSURFACESonly. Re-run after the change, the import prints the same 3 paths and exits 0; the two new imports are side-effect-free, andcheck:entry-guardis green.lint.yml:2649andlint.yml:2672run the two gates. Outside pre mode the output is identical.cut-rc.yml: it does not refuse pre mode. It refuses unlesspre.jsonis modeprewith tagrc(cut-rc.yml:232-236). So it never runs on thenextline, but it does run in pre mode on an rc line.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.git add -A -- … "${DOCS_SURFACES[@]}"), so a cut that leaves them unchanged passes. The self-test pins the rc case: CLI17.8.0-rc.0, expected17.7.0.The pin, replayed
Each run used a throwaway
git worktree add --detachunder the session scratch directory, thenpnpm install --frozen-lockfile --offline --ignore-scriptsandpnpm run version. All trees were removed afterwards. PR #22084's two files were copied from its heada630de657d:.changeset/pre.json({"mode": "pre", "tag": "next"}) and.changeset/22080-v18-line-opens.md.Tree 1: this branch (
a4b1b5106d) plus PR #22084's two.changesetfiles.pnpm run versionexit 0.packages/cliwent from17.7.0to18.0.0-next.0, and the spec CHANGELOG's top heading is now## 18.0.0-next.0, above## 17.7.0.Tree 1, continued: the exit window and the GA pass. These are the states premise 4 is about.
Tree 2: this branch on plain
main, withoutpre.json.pnpm run versionexit 0.packages/cliwent from17.7.0to17.8.0(the 58 pending changesets), and the surfaces move to the CLI's version, as they do today.Control tree:
mainat8fc50b7647, without this change, plus PR #22084's two files. This is the state the ruling rejects (⛔ A):Self-tests and the ablation
New batteries, each case with its positive control:
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.Control I: in pre mode the target is the newest GA, from the gate's one functionhas 10 cases and runssyncRepo(). 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:
18.0.0-next.0, newest GA17.7.017.7.018.0.0-next.0, and the CHANGELOG has no GA of 18pre.json18.0.0-next.0pre.jsonis the only difference between the two treesFurther cases cover mode
exitand 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.mjsrewrote the pre-mode test inexpectedVersion()(if (!existsSync(join(root, PRE_STATE))) {becameif (true) {), which is the old logic:pre.jsonignored.e27045098c80→97307ba4e54a.node scripts/check-docs-image-tag.mjs --self-testexited 1 with 13 failures, every one a new pre-mode case.node scripts/sync-docs-image-tags.mjs --self-testexited 1 with 6 failures, every one a new pre-mode case.e27045098c80, equal toHEAD:scripts/check-docs-image-tag.mjs, andgit diff HEADis empty.Gates, at
a4b1b5106dnode scripts/pm/dispatch-gates.mjs --commandswas 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-gatespassed 1976 cases (856s).pnpm check:docs-image-tag(112 assertions, then OK on the live tree) andpnpm check:docs-image-tag-sync(45 assertions) passed.check:entry-guard,check:nul-bytes,check:scripts-symbol-anchorsandcheck:declaration-mirrorspassed.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:isPathIgnoredisfalsefor both files.--format json: 2 results, 0 errors and 0 warnings, with--no-inline-config.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[]shipsscripts/. Labelledskip-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.mjsnow importsreadPre. The guard already makes the import inert, ascheck:entry-guardconfirms.release.yml's post-version comment ("check:docs-image-tag — the 3 doc surfaces against packages/cli's NEW version") andlint.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)..changeset/pre.jsontocheck:docs-image-tag, because the gate spells it. It does not routepackages/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