Repository navigation
spec(changes): generate the per-major spec-changes section and the upgrade guide at publish; the pull request generates both in memory and renders the diff (#22449 B′, condition 1) - #22533
Conversation
…heck and write anywhere with --out check:spec-changes and check:upgrade-guide stop comparing a committed copy: they generate both projections from the registries, write nothing, and exit 1 naming the projection when generation fails. --out <file> writes the generated bytes elsewhere, for the publish lane and the pull-request diff renderer. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
--check reads no committed copy, a generation failure is red in every mode and writes nothing, --out writes only where it is told, and the two real generators are deterministic across runs. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…for the pull request render-projection-diff.ts generates spec-changes.json and the upgrade guide on both sides with the same --out generators the publish lane runs (the base in a git archive of the base commit, borrowing this checkout's node_modules), and renders the diff into a job summary, a notice annotation and a diff file for the workflow to upload. A side that cannot be generated is red. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…at publish --prepare and the new rc-lane --generate write spec-changes.json and the upgrade guide into packages/spec from the registries; files[] ships the guide. --verify packs the artifact and refuses it when either packed projection differs from a fresh generation (check-release-spec-changes --registry), on top of the unchanged per-release section check. --attach uploads both files. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…ackfill D4 steps The stub pnpm answers the guide generator, the stub gh copies every uploaded file by tag and basename, and two new cases pin that the backfill generates the guide in the version commit's tree and attaches the publish job's bytes. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
… the rc lane generates and verifies both projections lint.yml's source-gates lane keeps the two in-memory checks, then renders the generated diff against HEAD^1 into the job summary and a notice annotation and uploads it as an artifact. cut-rc.yml runs release-spec-changes.sh --generate and --verify before its publish, so it no longer packs the tree's copy. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
…jections are generated at publish Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check
What this run could not see
Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
|
Standing down on a failure that is not this PR's.
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 37992689182 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #22482
Clause-②: no
The per-major section of
spec-changes.jsonand the protocol upgrade guide are now generated from the ADR-0087 registries at publish, verified there, and shipped in the@objectstack/spectarball and on the GitHub Release, by the lane thereleasesection already uses (scripts/release-spec-changes.sh).check:spec-changesandcheck:upgrade-guidestop comparing a committed copy: they generate both in memory and go red when generation fails. Every pull request renders the generated diff against its base. This is ruling6078203801on #22449 (B′), its condition (1), and the pin in triage6081379232.The committed copies and their
merge=os-regenroutes stay; deleting them is #22485, which remains open. The guide's public address and the pointer stub are #22483. ADR-0087 D4 is #22484.What changed
One generation entry point per projection.
build-spec-changes.tsandbuild-upgrade-guide.tskeep theirbuild()functions and now share one command-line contract,packages/spec/scripts/lib/projection-cli.ts:--check--out FILEFILE. The publish lane and the diff renderer use itThe publish lane, the pull-request check and the pull-request diff all run these same two scripts, so they cannot disagree.
At publish (
scripts/release-spec-changes.sh):--prepare(release.yml) now also writes the guide into the package:build-upgrade-guide.ts --out packages/spec/protocol-upgrade-guide.md. It already rewrote all ofspec-changes.jsonfrom the registries, per-major records included. That part is unchanged.--generate(new, for cut-rc.yml) writes both registry projections and no per-release section. That is what an rc tarball has always carried. It reads nothing from npm.--verifypacks the artifact and runscheck-release-spec-changes.mjswith a new--registry DIR: a fresh generation of both projections. The packed copies must equal it. For the manifest that covers everything except the publish-time fields: thereleasesection and the aggregate's one-release export diff. For the guide it is byte equality. After--preparethe existing per-release checks run unchanged. After--generatethe lane declares--no-release-section.--attachuploads both files in onegh release uploadcall.packages/spec/package.jsonfiles[]listsprotocol-upgrade-guide.md.scripts/check-published-files.mjsregisters it, as its REGISTERED invariant requires.cut-rc.ymlruns--generateand--verifybefore its publish. Before this change it packed whatever copy the tree held.At pull request (lint.yml,
Type Check · source gates, a required lane with no paths filter):packages/spec/scripts/render-projection-diff.ts --base HEAD^1. On a pull request,HEAD^1is the base tip the merge ref sits on. The step generates both sides with the same--outgenerators: this checkout's, and the base's own generators in agit archiveof the base. It renders the diff into the job summary and a::noticeannotation. A per-record headline names the registry ids each record gained or lost. It writesspec-projections.diff, and the next step uploads that file as thespec-projections-diffartifact, only when the diff is non-empty. A side that cannot be generated is red. A diff, however large, is never red.The PM's hypotheses, measured first
H1, the pre-pack hook: confirmed, and the guide half was missing. Dry run on
9411faa1ba(the base),RELEASE_VERSION=17.7.0, previous17.6.0read from npm.--prepareexit 0,--verifyexit 0.CHANGELOG.md LICENSE README.md api-surface liveness llms.txt package.json prompts spec-changes.json srcand no guide:docs/ships in no package.perMajorequalled the committed one, and so did the aggregate's registry half.--preparealready regenerates the whole manifest, per-major records included, at the point the ruling asks for.--prepareand--verifyrun beforePublish to npm + push version tags(pnpm run release= build + build-console +changeset publish).--attachruns afterCreate GitHub Releases. The spec build (gen:schema,gen:openapi, tsup) writes neither projection.Same dry run on this branch,
--prepareexit 0 and then--verifyexit 0:protocol-upgrade-guide.md.--verifyprints✓ registry projections verified against a fresh generation: spec-changes.json (2 per-major record(s), 121 converted / 406 migrated) and protocol-upgrade-guide.md (1590904 bytes) match the artifact.✓ release 17.6.0 → 17.7.0 verified against both tarballs: 83 added, 28 removed, 64 converted, 329 migrated. aggregate export diff 17.6.0 → 17.7.0 verified: 83 added, 28 removed.That line is byte-identical to the base run's.H2, every reader of the committed copy, at
9411faa1ba. For each: what it reads after this PR, before #22485 deletes the copies.check:spec-changes,check:upgrade-guidepackages/spec/scripts/check-generated.ts(GATED rows for both)--fixnever regenerates the copies. The rows'artifacttext still names the copies (wording for #22485)scripts/regen-artifacts.mjs+.githooks/pre-commit(merge=os-regen)scripts/pm/os-regen-merge.shand the pm-dispatch hot-file row (.claude/skills/pm-dispatch/SKILL.md:296)check:generated --fix) no longer regenerates the two copiesscripts/check-adr-0087-registration.mjs:2184spec-changes.jsonat HEAD, as its parser-rot witness (projection ⊆ source). It now reads an aging copy. That copy stays a subset while registration is insertion-only, but a withdrawn id would false-red it. #22485 must give it a generated witness before deleting the copyscripts/check-future-spec-major.mjs:351,:363coverscounts over both tracked files. The frozen copies keep their countsscripts/check-doc-authoring.mjs:1597docs/protocol-upgrade-guide.md(#22483 turns it into a stub)version-pr(run_gate … check:spec-changes / check:upgrade-guide)--prepareoverwrites it in the tree. Now also writes the guide--generateplus--verifypackages/spec/package.jsonfiles[]packages/spec/spec-changes.jsonat pack time, which is now always the lane's generation.claude/skills/spec-property-retirement/SKILL.md:336gen:spec-changes+gen:upgrade-guide. That is no longer needed. Governed.claude/**, not touched herepr-automation.yml:905,scripts/pm/dispatch-gates.mjs:5667, and the retirement pins'EXCLUDED_PREFIXESCloud was moved by cloud#2750. It runs
gen:spec-changesin its pinned checkout, and this PR leaves that script's default output path as it was.H3, the window before #22485: the lanes that ship generate. One lane did not, and now does.
websocket-durations-unit-in-keydropped fromperMajor[17 → 18], was rewritten by--prepare, and--verifyexited 0.perMajor. After spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485 it would publish nospec-changes.jsonat all, becausenpm packsilently skips a missingfiles[]entry..changeset/pre.jsonhas tagnext, and cut-rc refuses anything butrc. The minimal guard is--generate+--verifybefore its publish. Measured on the same drifted tree:--generateoverwrote it, then--verifyexit 0 with✓ no per-release section is owed. The packed manifest keys are the same as an rc tarball's today: norelease.--verifyexit 1, namingperMajor[17 → 18].migrated: 1 id(s) the registries project and the artifact OMITS: websocket-durations-unit-in-key. A tarball without the guide gives exit 1ships no protocol-upgrade-guide.md.--verifywith neither--preparenor--generategives exit 1Nothing was verified.H4, rendering on the pull request: the job summary, a notice annotation and an artifact, with no write path. Measured in
.github/workflows:$GITHUB_STEP_SUMMARYis used in 13 workflows.actions/upload-artifact@v7is used in ci.yml (9 steps), cut-rc, merged-branch-reaper, showcase-smoke, test-nightly-tiers and coverage-nightly.issues.createCommentunderpull-requests: writein docs-drift-check.yml (advisory), merge-queue-triage.yml and cross-repo-issue-closer.yml.typecheck-source-gateslane holdscontents: readonly.So the diff renders where that lane can write without a token: its summary, an annotation, and an artifact. No new write path is added.
Pins
packages/spec/scripts/projection-cli.test.ts(11 tests):--checkneither reads nor writes a stale committed copy, and is green with none;--checkand in both write modes, and writes nothing;--outwrites only there;--verifydepends on.packages/spec/scripts/render-projection-diff.test.ts(9 tests):--outis read from the path it writes, and the head never falls back;scripts/check-release-spec-changes.mjs --self-test: batteries 23 → 31 (P1–P8, the registry half). R1–R17 are untouched and green.scripts/release-verify-npm.mjs --self-testbattery 13 floor 11 → 13 (113 → 115 cases). The backfill generates the guide in the version commit's tree and attaches the publish job's bytes.releasesection unchanged: R1–R17 are unchanged, and the H1 verdict line for the section is byte-identical before and after.One-shot proofs, not kept as tests:
gen:migration-registry(the registry's generated regions) and regenerated no projection.check:spec-changesexit 0 (407 migrated) andcheck:upgrade-guideexit 0.build-spec-changes.ts --checkon the same tree exits 1,spec-changes.json is stale.render-projection-diff.ts --base HEADexits 0:spec-changes.json +14 −0 (perMajor 17 → 18: +1 migrated (zz-demo-entry-only); aggregate 16 → 18: +1 migrated (zz-demo-entry-only)) · protocol-upgrade-guide.md +3 −0.git diff HEADempty.replacement:check:spec-changesexits 1:✗ spec-changes.json could not be generated from the ADR-0087 registries. Nothing was written., then the ZodError.NOT rendered — head spec-changes.json: build-spec-changes.ts exited 1.gh release uploadline throughscripts/ablation-replace.mjs: anchor 1 → 0, blobd9cde2bfbe94→516d5f9dc619. The self-test exited 1 on exactly that case. Restored, blob equals HEAD.Verification
All readings at
1245a3058c(this branch's head) unless a line says otherwise.node scripts/pm/dispatch-gates.mjs --commandsprinted 128 commands. All 128 ran here, each with its exit code recorded, and all exited 0.--ranreports:128 derived, 128 run, 0 NOT-MEASURED, 0 UNRUN.58d075abdcrecorded exit 3 (PREREQUISITE NOT MET, no build) on the four gates that read builtdist/:dts-closure,dual-build-cjs-loads,lean-entry-closureandsourcemap-no-sources-content.repo-project tests then built the workspace'sdist/in this worktree. The final pass measured all four green.check-declaration-mirrors(both),release-verify-npm --self-test,check:engine-double-contract,check:objectql-double-limit,check:query-options-erasure,check:ratchet-remedy-authority,check:release-spec-changesandcheck:where-matcher.scripts/pm/os-verify-lock.sh.pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: VERDICT command-exit 0, 632 files passed, 1 skipped, 18847 tests passed. Taken at58d075abdc;1245a3058cadds only a three-line comment.pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2: VERDICT command-exit 0, 54 files, 915 tests passed.pnpm --filter @objectstack/spec typecheck: VERDICT command-exit 0. The scripts program lists all four new script files.eslint.config.mjs's**/*.{ts,…}object.--no-inline-config --format jsonreports 9 files: 0 errors, 0 warnings, 0 ignored notices.parserOptions.project), so this diff cannot move a verdict on a file it does not touch.pnpm lintis CI's.origin/maind303b3e7af. One of those commits (ee8751d41e) regenerated the two committed copies, which this diff does not touch. No commit touches a file in this diff, andgit merge-treeis clean. CI runs on the merge ref.File surface
Two extensions beyond the claim's list, both forced:
packages/spec/package.jsonfiles[]: the same file as the in-surface check scripts. The ruling ships the guide "in the package", and H1 asks the tarball to carry it.scripts/check-published-files.mjsEXTRA_ENTRIES: one entry, which its REGISTERED invariant requires for any newfiles[]entry.Also touched, reading them as the lane's own parts:
scripts/check-release-spec-changes.mjs: the gate--verifyruns.scripts/release-verify-npm.mjs: battery 13 executes the lane's release.yml steps.cut-rc.yml: a release workflow step that calls the script.Acceptance notes
check-generated.tsGATED rows still call the two committed files their artifacts; the deletion card (spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485) owns that wording.check-adr-0087-registration.mjs's parser-rot witness reads the committed copy; spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485 must re-point it before deleting the copy (see H2).packages/spec/protocol-upgrade-guide.mdis written only by the lane and is not gitignored. A local--prepareor--generateleaves it untracked, the same way--preparealready leavesspec-changes.jsonmodified. Neither is ever committed by the lane.Generated by Claude Code