Repository navigation
[v3 migration 2/4] Land the v3 migration as one atomic PR — bump, gates, workflows and config together #9498
Description
Activity
os-project-manager commented
on Aug 18, 2026 CollaboratorAuthorMore actionsDecision required before this card can be written — the migration changes the release number
#9497's rehearsal measured it on both CLIs from identical stock, with no changeset declaring a major:
computed next RC @changesets/cli2.31.1 (today)18.0.0-rc.0 @changesets/cli3.0.017.1.0-rc.0 Traced by ablation, not inferred: deleting
@objectstack/cli'speerDependenciesblock flips v2's plan from{"major":69,"patch":7,"none":1}to{"minor":69,...}. The major digit today comes from one optional peer edge on@objectstack/driver-tursointeracting with "a prerelease never satisfies a non-prerelease range" — not from anything an author wrote.This is blocking rather than cosmetic:
cut-rc.ymlasserts the computed version equals the dispatched one, so the first cut after this card lands refuses a dispatch written from the old expectation.Not adjudicable by this seat. Release numbering is a maintainer call under the release guardrail, and it is exactly the class where the mechanical test says escalate: it changes a published version's meaning, and it is not reversible once a train ships under it.
Options
- A — accept 17.1.0. Treat v2's 18.0.0 as the artifact it is, and let the convention already enforced in
check-changeset-no-major.mjsmean what it says: a major is declared deliberately or not at all. - B — preserve 18.0.0. Land a deliberate major-declaring changeset (or an explicit version input) in this same PR, so the number the last several trains were built on carries forward.
- C — defer to [v3 migration 4/4] Verify the migrated release path inside the next real RC window #9500, settling numbering inside the next real RC window.
Four prongs
- Platform long-term coherence — A shrinks a special case: the version stops being a function of a dependency-graph detail no author can see. B keeps that coupling alive, which is the exact class of hidden mechanism this epic exists to remove.
- Measured business pull — nothing downstream was found consuming the major digit as a promise. v2's 18.0.0 was never anyone's decision. "Preserving" it preserves an accident.
- AI-agent error-resistance — under A, "what version does this cut produce" is derivable from changesets a human or agent can read. Under B the trap stays armed and undocumented, and the next person to hit it has no way to see why.
- Startup scope discipline — A costs nothing to adopt; B costs a fabricated major-declaring changeset whose stated reason would be "because the old tool computed it that way".
PM recommendation: A, with the ruling recorded here before #9498 is written. C is the weakest of the three — it defers a decision that changes what this card's own tests must assert, and #9500 cannot run until a real window opens.
This card is
needs-user-decisionuntil that ruling lands. Its other blocker (Blocked-by: #9497) is now cleared — the rehearsal is done and the scope is on #9465.
Generated by Claude Code
- A — accept 17.1.0. Treat v2's 18.0.0 as the artifact it is, and let the convention already enforced in
os-project-manager commented
on Aug 18, 2026 CollaboratorAuthorMore actionsRuling: option A — the next train is 17.1.0
Maintainer's decision, quoted verbatim:
接受 17.1.0
Recorded as binding on this card and on #9500. What it settles, so nobody re-derives it later:
- The next RC computes 17.1.0-rc.0 under v3, from the same stock that computes 18.0.0-rc.0 under v2. That difference is expected and accepted, not a defect to chase.
- v2's major digit is understood as an artifact — one optional
peerDependencyfrom@objectstack/clionto@objectstack/driver-turso, interacting with "a prerelease never satisfies a non-prerelease range". No changeset declared it and no downstream was found consuming it as a promise. - The repo's existing convention now means what it says: a major is declared, not inferred. No compensating major-declaring changeset is to be written to preserve 18.0.0 — that was explicitly rejected.
Consequences this card must carry:
- The PR states the expected next computed version and shows it — a
changeset statusreading under v3 on the migrated tree, so the number is evidence rather than a claim. - Whatever this card's tests assert about versioning must assert 17.1.0, not the old expectation.
cut-rc.ymlasserts computed == dispatched, so the first dispatch after this lands must be written from the new number. That is a note for whoever runs the next cut, and it belongs in the PR body where they will look.
needs-user-decisionis cleared; this card returns to the queue.Sequencing, decided now rather than discovered later: this card edits
cut-rc.yml's staging/allowlist block, and #9518 is in flight on that exact block (a p0 defect that blocks the next cut on today's v2, independent of this migration). Same-file work serializes: #9518 lands first, this card rebases onto it. Being unblocked by the ruling does not make it dispatchable while a sibling holds the file.
Generated by Claude Code
- added and removed
on Aug 18, 2026 os-project-manager commented
on Aug 18, 2026 CollaboratorAuthorMore actionsClaim: epic #9465, round 3
Session:session_01HDA9nN6nXQngoQUAAzRdMb
Branch:claude/issue-9498-changesets-v3-migration
Worktree:objectstack-issue-9498
Domain:domain:devx
File surface:package.json(the@changesets/clirange) ·.changeset/config.json·scripts/check-changeset-no-major.mjs·scripts/objectui-changeset-digest.mjs·scripts/check-adr-0087-registration.mjs·.github/workflows/cut-rc.yml(prose only — see below) ·.github/workflows/pr-automation.yml(prose only) ·docs/releases-maintenance.md(stop on breach; explain in the report)
Container & model:L,mode:subagent,model: fable— the epic's heaviest card: it rewrites the gates that decide whether a release commit may be pushed. If fable is measurably unavailable (quota), it falls toopusunder the maintainer's 2026-08-13 exemption and the downgrade gets recorded here with its reason; the exemption covers dispatch only.
Serial constraints cleared: #9518 / PR #9553 is MERGED (955ccf20d) — thecut-rc.ymlblock this card also touches is now settled onmain, and this branch starts from amainthat contains it. #9450 remains an ungradedfindingwith no branch and is deliberately not carried. No other open PR touches the epic's declared territory.Both blockers are cleared: #9497's rehearsal is done and its verdicts are on #9465, and the version-number question is ruled — 17.1.0.
Five constraints this dispatch carries, each bought with a measurement rather than an opinion:
- 17.1.0, proven with a
changeset statusreading under v3 on the migrated tree — shown, not asserted. - Branch from a
maincontaining955ccf20d; theSURFACE_LIST/grep -vxFblock is not to be reverted or restructured. Only the stale pre.json prose in that file changes. - The rehearsal must use [finding] Any local
pnpm run versionrehearsal hangs forever in a shallow clone —getCommitsThatAddFilesdeepens in a loop that never terminates without a reachable remote #9555's shallow-clone scaffold. Without itpnpm run versionnever terminates in this container — that cost 2.5 hours once already and must not be paid twice. .changeset/config.jsongains"privatePackages": {"version": true, "tag": false}— measured on [v3 migration 1/4] Rehearse @changesets/cli v3 in a throwaway clone and answer U1–U6 with measurements — no repo changes #9497 to reproduce v2's plan exactly, private-package versions included.scripts/check-empty-changeset.mjsis untouched. U1 measured that v3's exit-1 does not cover the all-empty-frontmatter shape, so the gate that catches 空 changeset 会静默卡死已 version 的发布:Release run 全绿,但 npm 和 Docker 什么都没发(17.0.0-rc.2 现在就卡着) #4898 stays.
Generated by Claude Code
- 17.1.0, proven with a
os-project-manager commented
on Aug 18, 2026 CollaboratorAuthorMore actionsDispatch 1 died on the model quota wall before producing anything:
You've reached your Fable 5 limit. Verified clean, so this is a re-dispatch and not a hand-over —git ls-remote origin 'refs/heads/claude/issue-9498*'returns nothing and no worktree exists.Re-dispatched at
model: opusunder the maintainer's standing quota exemption (2026-08-13), quoted verbatim:fable 如果用完了,可以用 opus
Amending this card's
Container & modelline toL,mode:subagent,model: opus. The exemption's floor is opus and nothing goes below it. This is the second fable wall today — #9228 hit the same one this morning — so the top tier should be treated as unavailable for the rest of this session rather than retried per card.What that costs, stated rather than glossed: this is the epic's heaviest card, and it is being written one tier below what it was graded for. The compensations are already in place and are the reason this is acceptable rather than merely necessary — the card carries five constraints that are measured, not judgment calls (#9497 did the thinking at the tier that was available then), and the review is mine, against
origin/main, with the rehearsal's numbers to check the PR's claims against. A card whose hard decisions were pre-made and pre-measured is exactly the kind that survives a downgrade; one that still had open design questions would not, and would have waited instead.
Generated by Claude Code
os-project-manager commented
on Aug 18, 2026 CollaboratorAuthorMore actionsDraft PR #9560 is open. Report below; the PR body carries the full evidence.
{ "issue": 9498, "status": "done", "branch": "claude/issue-9498-changesets-v3-migration", "pr": "https://github.com/objectstack-ai/objectstack/pull/9560", "premise_still_valid": true, "summary": "@changesets/cli 2.31.1 -> 3.0.0 landed atomically with every consequential change: .changeset/config.json gains privatePackages {version:true,tag:false} (U4) and loses ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH (U2, measured inert under v3), $schema moves to @changesets/config@4.0.0 (the version v3 resolves); check-adr-0087-registration.mjs excludes .changeset/pre/ from its --list / --audit-stock surfaces while the enforcing diff scan keeps judging it (PRE1-PRE3 fixtures, both ablations run); check-changeset-no-major.mjs, cut-rc.yml, pr-automation.yml and docs/releases-maintenance.md get prose only, correcting the v2 claim that consumed changesets are recorded in .changeset/pre.json. 17.1.0 is proven, not asserted: changeset status under v3 on the migrated tree reads 17.1.0 (GA) and 17.1.0-rc.0 (pre), and a full window driven in a throwaway clone lands RC1 17.1.0-rc.0, RC2 17.1.0-rc.1, GA 17.1.0 with major releases: NONE. One divergence from #9497's rehearsal, and it cuts toward the fix: with privatePackages unset the rehearsal measured lossy-but-survivable, whereas on today's 209-changeset stock changeset status EXITS 1 (Found mixed changeset default-timeout-margin-repair: @objectstack/dogfood ignored + @objectstack/types not ignored). That changeset landed after the rehearsal's snapshot, so shipping the bump without U4's key would not merely mis-version private packages, it would stop the release lane from running at all. #9450 is explicitly NOT carried and the reasoning is in the PR body. check-empty-changeset.mjs is untouched and U1 was re-measured here rather than cited. The SURFACE_LIST / grep -vxF block from 955ccf20d is untouched: only prose and one echo string change in that file. No release was performed - no publish, no tag, no pre-mode transition on origin, no workflow dispatch, no Version Packages PR touched; .changeset/pre.json stays absent on the branch.", "tests": "All gates re-derived with `node scripts/pm/dispatch-gates.mjs` over the 8 changed paths (13 families named) and run on the final commit 06e7ca712, which is PR #9560's head. GREEN: check:changeset-gate-self-tests (check-adr-0087-registration 212 assertions, check-changeset-no-major 116, check-empty-changeset), check:node-version, check:objectui-changeset, check:override-consistency, check:required-contexts, check:shard-attestation, check:workflow-status-functions, check:nul-bytes (6157 files, no raw control bytes), check-adr-0087-registration.mjs --base MERGE_BASE, check-changeset-fixed.mjs ('fixed group is in sync with 69 public workspace packages'), check-changeset-no-major.mjs --base MERGE_BASE, check-empty-changeset.mjs --base MERGE_BASE, check-osv-exemptions.mjs, and eslint on both changed scripts. REHEARSAL (throwaway clone, remote removed, #9555's scaffold; tree hash 7c25e1c0f04822a9fd29f5718b9c1ec32a509fea identical before/after the scaffold; full pnpm run version in 7.7s): changeset status -> {\"minor\":69,\"patch\":7,\"none\":1}, 209 changesets, spec 17.0.0 -> 17.1.0, major releases: NONE; in pre mode spec 17.0.0 -> 17.1.0-rc.0. Pre-mode version pass produced 366 porcelain lines = 209 D .changeset/NAME.md + 76 M package.json + 76 M CHANGELOG.md + 3 M declared doc surfaces + 2 untracked (.changeset/pre.json, .changeset/pre/), and cut-rc.yml's staging plus allowlist replayed verbatim accepted all 365 staged paths with the unstaged assertion clean. REVERSE VERIFICATION, direction predicted before running: ablating the pre/ filter turned PRE1 and PRE3 RED (PRE1 got the two .changeset/pre/consumed-*.md paths in stock; PRE3 got pending stock 1 where 0 was expected) while PRE2 stayed GREEN by design; moving the filter into isChangesetFile (the verdict path) turned PRE2 RED (the smuggled breaking changeset under .changeset/pre/ stopped being reported). U1 RE-MEASURED on v3.0.0 rather than cited: three empty-frontmatter changesets -> `changeset version` exits 0, deletes all three, bumps nothing (that is #4898, reproduced on v3); zero changesets -> exits 1. Gate exposure measured on a real v3 cut commit: `git ls-tree -r ... -- .changeset` lists 209 .changeset/pre/* entries; the AMR scan returns exactly 209 R100 rows; pr-automation.yml's --diff-filter=A counter credits 0 (still 0 with diff.renameLimit=1; 209 only under --no-renames). Post-exit GA pass: 211 deletions, 210 under .changeset/pre/ and 0 in the root, so the three gates' root-only readdirSync readers see nothing.", "open_questions": [], "out_of_scope_findings": [ "filed as #9561: two comments (pr-automation.yml:783 and check-changeset-no-major.mjs:195) still describe the launch-window major guard as dormant because the repo is in pre mode; .changeset/pre.json has been absent since the 17.0.0 GA exit, so the guard is armed. Not folded in: nothing about v3 causes it, and correcting the #6923 coverage note reverses a present-tense claim about what CI has ever executed - a judgement this card was not scoped to make.", "not filed, recorded on #9555 as a comment: `changeset status` with no --since dies in getDivergedCommit before it ever reaches the hang #9555 documents, and `--since origin/main` silently returns an EMPTY plan because @changesets/read uses sinceRef to restrict which changesets it reads. Same root cause as #9555 (shallow clone, no reachable remote), second entry point; it belongs on that card rather than a new one, and #9500 will hit it first." ] }Three things worth the PM's eye before ACCEPT
- The divergence is the interesting result. [v3 migration 1/4] Rehearse @changesets/cli v3 in a throwaway clone and answer U1–U6 with measurements — no repo changes #9497 measured
privatePackagesunset as survivable. On today's stock it is fatal (Found mixed changeset default-timeout-margin-repair-> exit 1), because one changeset that landed after the rehearsal's snapshot names@objectstack/dogfood(private) alongside@objectstack/types(public), and v3 treats unversioned private packages as ignored. The rehearsal did not mispredict; the stock moved under it. Ruling 1 gains a second, independent reason. - cut-rc.yml's pin-range preflight asserts "the digest will be complete" from an object-presence test that cannot support it #9450 is decided, not deferred silently: not carried, reasoning in PR body section 7. It stays open and unassigned.
- The next cut must be dispatched as
17.1.0-rc.0.cut-rc.ymlasserts computed == dispatched, so a dispatch written from the old 18.0.0 expectation refuses before anything is pushed. This is in the PR body where whoever runs the cut will look.
Generated by Claude Code
Generated by Claude Code
- The divergence is the interesting result. [v3 migration 1/4] Rehearse @changesets/cli v3 in a throwaway clone and answer U1–U6 with measurements — no repo changes #9497 measured
os-project-manager commented
on Aug 18, 2026 CollaboratorAuthorMore actionsACCEPT. Each constraint checked against the branch and
origin/maindirectly, not against the report.constraint how I verified it result (a) 17.1.0 proven PR body carries changeset statusoutput under v3 on the migrated tree, plus a fullpre enter → RC1 → RC2 → pre exit → GAwindow with per-step countsshown, not asserted (b) 955ccf20dnot reverteddiffed cut-rc.ymlnon-comment lines against the merge base: exactly one changed line, anechostringSURFACE_LIST/grep -vxFblock intact(c) #9555's scaffold used full pnpm run versioncompleted in 7.7s where it previously never terminatedscaffold worked (d) privatePackagesread the config.jsondiff{"version": true, "tag": false}present(e) check-empty-changeset.mjsgit diff --name-onlyfor that pathuntouched Also verified independently:
package.jsoncarries^3.0.0; the unsafe___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCHkey is gone and$schemamoved to@changesets/config@4.0.0(epic benefit 3, realized); the 8 changed paths all sit inside the declared territory, withpnpm-lock.yamlinherent to the bump rather than scope creep; andobjectui-changeset-digest.mjs's onlypre.jsoncontact really ispre?.mode === 'pre', which survives v3's slimmed file — so leaving it untouched is correct rather than an omission.The divergence is the most valuable thing in this run. #9497 measured
privatePackagesunset as lossy-but-survivable. On today's 209-changeset stock it is fatal:changeset statusexits 1 on a mixed changeset (@objectstack/typespublic +@objectstack/dogfoodprivate in one file), which landed after the rehearsal's snapshot. So constraint (d) was not a versioning nicety — without it the bump takes the release lane from "wrong numbers" to "will not run at all". The card was told the measurement wins over the rehearsal, and it did, in the direction that mattered.Two pieces of honest reporting worth recording, because both cut against the PR's own case:
- U5's payoff is less than the epic hoped. The pre/RC exemption switch, the
mode: "exit"re-arm and the merge-base/AMRscoping all stay load-bearing under v3. What v3 retires is the urgency the header was written in, not the code. The header now says so, which is better than silently shrinking a guard. - U1 was re-measured on v3 rather than cited — three empty-frontmatter changesets still exit 0 and bump nothing. That is 空 changeset 会静默卡死已 version 的发布:Release run 全绿,但 npm 和 Docker 什么都没发(17.0.0-rc.2 现在就卡着) #4898 reproduced on v3, and it is why
check-empty-changeset.mjsstays. A card that could have quietly leaned on the rehearsal's word re-ran it instead.
#9450 was answered rather than left hanging: not carried, with the reasoning (a textually disjoint objectui-reachability defect that grading would require fresh adjudication) in the PR body. That was a hard requirement of the dispatch and the failure mode it guarded against — silent folding — did not happen.
skip-changesetis right and justified from the diff: a root devDependency plus release machinery, no published surface touched. Naming a package would put a false line in 69 CHANGELOGs.CI was still converging at review time (Test Core, Temporal Conformance, Dogfood, Build in progress); the 13 gate families were re-derived and run green locally at
06e7ca712. Flipping to ready and enabling auto-merge — it cannot merge while anything is red.
Generated by Claude Code
- U5's payoff is less than the epic hoped. The pre/RC exemption switch, the
os-project-manager commented
on Aug 18, 2026 CollaboratorAuthorMore actionsLanded and verified on
origin/mainase7daea169— chore(release): migrate the release toolchain to @changesets/cli v3 (#9560).Checked by content, not by sha ancestry (the queue lands as a squash, so the reviewed sha is not an ancestor of
main):origin/main:package.json "@changesets/cli": "^3.0.0" origin/main:.changeset/config.json "privatePackages": { "version": true, "tag": false } origin/main:.changeset/config.json ___experimentalUnsafeOptions… → 0 occurrences (removed) origin/main:.github/workflows/cut-rc.yml SURFACE_LIST → 6 occurrences (955ccf20d intact underneath)That last line is the one worth having checked. This card edited the same workflow the p0 fix had just rewritten, and the constraint was that its
SURFACE_LIST/grep -vxFblock survive untouched. It did — onmain, after both landed, not merely on the branch.What is now true that was not this morning: the release toolchain runs v3; private packages keep versioning without tagging; the unsafe experimental key that upstream reserved the right to change in a patch release is gone; and the next RC cut computes 17.1.0-rc.0, which is the number whoever dispatches it must write, because
cut-rc.ymlasserts computed == dispatched.Closing as completed. The epic's remaining cards are #9499 (awaiting a scope answer) and #9500 (on hold until the next real pre-mode window, which is where this migration gets its last and only end-to-end proof).
Generated by Claude Code
Sub-issue of #9465.
Blocked-by: #9497
Do not start before #9497's measurements are on #9465. The scope of this card is those measurements; starting early means writing the migration against a guessed shape, which is the failure this epic is structured to avoid.
Ruling — one PR, not a sequence
Both half-states are broken, and each is broken silently until the next release:
cut-rc.ymlstill model v2's pre-mode semantics;.changeset/pre/while v2 never creates it.So this PR carries
@changesets/cli: ^3.0.0in the rootpackage.jsonand every consequential change. #9217 (Dependabot's raw bump) closes itself oncemaincarries^3.0.0.Expected surface — re-derive it from #9497's verdicts, do not copy this list
package.json(the bump; and theversionscript is a&&chain —changeset version && sync-protocol-version.mjs && sync-template-versions.mjs && sync-docs-image-tags.mjs— so v3's exit-1 behaviour reaches all of it) ·.changeset/config.json($schema,privatePackagesper U4, the experimental key per U2) ·scripts/check-changeset-no-major.mjs(the pre/RC exemption, per U5) ·scripts/objectui-changeset-digest.mjs(reads.changeset/pre.json, and its self-test fixtures write{"mode":"pre","tag":"rc"}) ·scripts/check-empty-changeset.mjs(only if U1 measured exact coverage; the default is that this gate stays) ·.github/workflows/cut-rc.yml(the enforced allowlist and the prose describing v2 semantics) ·.github/workflows/pr-automation.yml(prose referencing pre.json behaviour and #4898).Non-negotiable
changeset versionproduces a file setcut-rc.ymlwould reject, the allowlist is updated to the measured truth — you do not massage the tool's output to fit a list written under v2.--self-testmust model v3 and pass.changeset publish, no version tags, no release-workflow dispatch, no merging of a Version Packages PR, and no pre-mode transition onorigin. Releases are human-only here and this card does not change that.Acceptance
Fixes #<this card>, carrying the bump and every consequential change together.--self-testpasses and models v3; gates re-derived for the actual diff withnode scripts/pm/dispatch-gates.mjsand run.skip-changesetmay be correct here — decide from the diff and say why.pnpm changeset,changeset statusand a drypnpm run versionall exercised after the change, with output quoted.