You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec(changes): generate the per-major spec-changes section and the protocol upgrade guide at publish; the pull request still generates both in memory and renders the diff (#22449 B′, condition 1) #22482
Filing gate: ③ a maintainer-directed task. Ruling 6078203801 on #22449 (B′, the maintainer's 「同意」 2026-10-09T09:27Z). Filed by domain:spec seat 1 (#6017) · os-tesla · session session_01VZqqwTj2wsihZEbfT6yyYN, as that ruling's 裁后执行 card ① and triage 6078734415 item 1. Parent: #22449. ⛔ Not a claim.
What the ruling sets
"The per-major section of spec-changes.json and the protocol upgrade guide are generated from the registries at publish, verified there, and shipped in the package and on the Release by the lane the release section already uses."
Condition (1): "the pull-request stage still generates both in memory, fails loudly when generation fails, and renders the generated diff on the pull request (a check artifact or a comment), so the review value #6957 named is kept; a B without this check is not taken."
This card
At publish: extend the lane scripts/release-spec-changes.sh already runs for the per-release release section (--prepare before changeset publish, --verify from the tarballs, --attach after the Release exists). The same lane generates the per-major section of packages/spec/spec-changes.json and the upgrade guide from the ADR-0087 registries, verifies them from the tarballs, and attaches the same bytes to the Release.
At pull request:check:spec-changes and check:upgrade-guide stop comparing a committed copy. They generate both in memory and fail loudly when generation fails. The generated diff against the base renders on the pull request, as a check artifact or a comment.
⛔ Not this card: deleting the committed copies and their merge=os-regen routes (card ③, which waits for this one), and the guide's public address (card ②).
Confidence gaps the ruling names (measure first)
The hook that generates the major section before npm pack of @objectstack/spec is unmeasured; --prepare already rewrites the same file at the same point.
Which consumers read the committed copy today. In this repo, packages/spec/scripts/check-generated.ts and the two checks. In cloud, scripts/audit-spec-changes.mjs:92–:93, which is the cloud seat's card.
Acceptance
A publish dry run (--prepare then --verify) produces the per-major section and the guide from the registries alone.
A pull request that adds a step-18 entry needs no regeneration commit. Its check renders the generated diff, and a registry that fails to generate turns the check red.
Triage: first grade, priority:p2 · domain:spec · area:devpath, pm:blocked on objectstack-ai/cloud#2750. ⛔ Its PR leaves draft only after PR #22215 merges
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-09T12:59Z. ⛔ Not a claim, ⛔ not a dispatch.
Direction: the body's own. Measure the two confidence gaps first, in the PR:
the pre-npm pack hook;
every reader of the committed copy.
One more pin: a registry that fails to generate turns the pull-request check red, and the rendered diff shows on a pull request that only adds an entry.
Pointer from repo:cloud#1 (session session_01WVbr5J6u8BHh8EyFtcWciH) · 2026-10-09T14:46Z · ⛔ Not a claim, no label change.
This card's Blocked-by: objectstack-ai/cloud#2750 has landed. cloud PR #2752 merged at 2026-10-09T14:44:22Z as dce10383, and cloud#2750 is closed completed.
Cloud's changed-spec-surface guard now generates the catalog with pnpm run gen:spec-changes in the pinned framework checkout and reads only that output, never the committed spec-changes.json. A failed generation is exit 2. Both places run it identically: build-and-test and pin-smoke.
Cloud's pin is still 56bf27affb. The generator exists there, so nothing waits on a pin move. The committed copy can stop being compared here (#22482) and be deleted later (#22485) without cloud under-reporting.
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-09T14:54Z. Unlock scan. ⛔ Not a claim, ⛔ not a dispatch.
Thread-read: 6081379232
objectstack-ai/cloud#2750 closed completed. PR objectstack-ai/cloud#2752 merged as dce10383.
I read cloud main at that commit. scripts/audit-spec-changes.mjs:251 runs the framework's own gen:spec-changes, and test.yml / pin-smoke.yml read only the generated catalog. Cloud no longer reads the committed copy, so this card's change can no longer leave cloud reading a stale one.
The hold on this card's PR has lifted: PR #22215 merged as 4e9fe9ff6a at 2026-10-09T17:05Z. The ruling's "PR #22215 lands first" (6081379232) is met, so this card's PR may leave draft on its own review. objectstack-ai/cloud#2750 had already landed. Triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-09T17:56Z.
Claim: PM loop round 4 (#22482: 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) · 2026-10-09T18:31Z
Session: session_01VZqqwTj2wsihZEbfT6yyYN
Account: os-tesla (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-22482-spec-changes-at-publish
Worktree: objectstack-issue-22482
Domain: domain:spec
Seat: domain:spec#1
File surface (at origin/main9411faa1ba; stop on breach and explain in the report): scripts/release-spec-changes.sh and the release workflow step that calls it; packages/spec/scripts/build-spec-changes.ts, build-upgrade-guide.ts and the two check:* scripts in packages/spec/package.json; the CI job that renders the generated diff on a pull request; their tests and self-tests; one .changeset/22482-*.md. ⛔ Not the committed copies or their merge=os-regen routes (#22485), ⛔ not the guide's address (#22483), ⛔ not ADR-0087 (#22484).
Container & model: L, mode:subagent, model: default tier (dispatch-gates --tier: no path-derived mandate). Machine face; no contract accept/reject change is planned. ⛔ No release act: the release scripts are edited and dry-run only.
Clause-②: no
Responsibility: n/a — not a defect card (maintainer-ruled, #22449 B′ ruling 6078203801)
Thread-read: 6086378797
Serial constraints cleared: PR #22512 edits .github/workflows/ci.yml: ordinary concurrency. No open PR touches the release script or the two generators (all 11 open PRs' file lists, read 2026-10-09T18:27Z). #22483 (the guide's address) runs beside this card and shares build-upgrade-guide.ts, so it is serial after this card. The blocker cloud#2750 landed (6083391528), and PR #22215 merged (6086378797).
os-dev-report
{
"issue": 22482,
"status": "done",
"branch": "claude/issue-22482-spec-changes-at-publish",
"pr": "#22533",
"session": "session_01VZqqwTj2wsihZEbfT6yyYN",
"premise_still_valid": true,
"summary": "The per-major section of spec-changes.json and the protocol upgrade guide are now generated from the ADR-0087 registries at publish, verified there and shipped in the @objectstack/spec tarball and on the Release, by scripts/release-spec-changes.sh. --prepare writes the guide into the package, which files[] now lists. A new --generate does the same for the rc lane, with no per-release section. --verify refuses a packed copy that differs from a fresh generation: check-release-spec-changes.mjs takes a new --registry half, P1-P8. --attach uploads both files. check:spec-changes and check:upgrade-guide go through one shared CLI contract (packages/spec/scripts/lib/projection-cli.ts). --check generates in memory, reads no committed copy, and exits 1 naming the projection when generation fails. --out FILE writes the generated bytes anywhere. lint.yml's required source-gates lane renders the generated diff against HEAD^1 into the job summary, a notice annotation and an artifact, via packages/spec/scripts/render-projection-diff.ts. It uses the same generators on both sides and goes red when either side fails to generate. H1 confirmed: --prepare already regenerated the whole manifest before pack; the guide shipped nowhere and now does. H2: 13 readers are listed in the PR body. One reader (check-adr-0087-registration's parser witness) is flagged for #22485. H3: cut-rc.yml packed the committed copy with no generation step; the minimal guard (--generate + --verify) is added and measured red/green. H4: summary + annotation + artifact, no write path. The release section's behaviour is unchanged: R1-R17 untouched, and the dry-run verdict line is byte-identical before and after.",
"tests": "At 1245a30 (head) unless noted. Derived union: dispatch-gates --commands = 128. All 128 ran with recorded exit codes, all exit 0. --ran: 'Run reconciliation — 128 derived, 128 run, 0 NOT-MEASURED, 0 UNRUN'. An earlier pass at 58d075a had 4 dist-reading gates at exit 3 (no build); the spec repo-project tests then built dist in this worktree and the final pass measured them. Under os-verify-lock: spec --project local 'Test Files 632 passed | 1 skipped (633), Tests 18847 passed' at 58d075a (head adds a 3-line comment only); spec --project repo 'Test Files 54 passed (54), Tests 915 passed (915)'; pnpm --filter @objectstack/spec typecheck VERDICT command-exit 0; the two new test files 'Tests 20 passed (20)'. check-release-spec-changes --self-test: 31 batteries pass (was 23). release-verify-npm --self-test: 115 cases pass (was 113; battery 13 floor 11 -> 13). ESLint narrowed: 9 changed JS/TS files, --format json reports 9 files, 0 errors / 0 warnings / 0 ignored; the config has no type-aware linting, so the narrowing excludes nothing. H1 dry run (RELEASE_VERSION=17.7.0, previous 17.6.0 from npm): base --prepare/--verify exit 0/0 with no guide in the tarball; branch exit 0/0, guide packed, '✓ 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.' H3: a drifted tree copy is overwritten by --prepare and by --generate (verify exit 0). A stale copy packed after generation gives verify exit 1 naming websocket-durations-unit-in-key; a missing guide gives exit 1; neither --prepare nor --generate gives exit 1. One-shot proofs, restored with git diff HEAD empty. Entry-only change (new 18.* semantic entry + gen:migration-registry, no projection regenerated): check:spec-changes exit 0 (407 migrated), check:upgrade-guide exit 0. Control: the base's --check on the same tree exits 1 'spec-changes.json is stale'. The renderer exits 0: 'spec-changes.json +14 −0 (perMajor 17 → 18: +1 migrated (zz-demo-entry-only) ...) · protocol-upgrade-guide.md +3 −0'. Malformed entry (non-string replacement): check:spec-changes exits 1 '✗ spec-changes.json could not be generated from the ADR-0087 registries. Nothing was written.' + ZodError; the renderer exits 1 'NOT rendered'. Ablation via scripts/ablation-replace.mjs (guide dropped from gh release upload): anchor 1 -> 0, blob d9cde2bfbe94 -> 516d5f9dc619; release-verify-npm self-test exits 1 on the new battery-13 case; restored, blob == HEAD, git diff HEAD empty. CI on #22533: Type Check · source gates and Lint & Repo Gates in_progress at report time.",
"mcp_calls": "0",
"api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot], each one POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (#22533, draft, body read back identical 16105 bytes); (2) label-write --assign os-tesla -> POST /repos//issues/22533/assignees (read back: assignee os-tesla); (3) this os-dev-report -> POST /repos//issues/22482/comments. Plus git push (not REST).",
"gates": "128 derived / 128 run / 0 NOT-MEASURED / 0 UNRUN at 1245a30, every exit 0 (dispatch-gates --ran exit 0). Includes everything the dispatch named, plus what the re-derivation added: check-declaration-mirrors x2, release-verify-npm --self-test, check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:ratchet-remedy-authority, check:release-spec-changes, check:where-matcher.",
"line_budget": "n/a: no skills/** or line-ratcheted ledger touched. Diff +1667 / −128 = 1795 changed lines, under the 3,000 human-merge threshold.",
"files_changed": [
".changeset/22482-spec-projections-generated-at-publish.md",
".github/workflows/cut-rc.yml",
".github/workflows/lint.yml",
".github/workflows/release.yml",
"packages/spec/package.json",
"packages/spec/scripts/build-spec-changes.ts",
"packages/spec/scripts/build-upgrade-guide.ts",
"packages/spec/scripts/lib/projection-cli.ts",
"packages/spec/scripts/projection-cli.test.ts",
"packages/spec/scripts/render-projection-diff.test.ts",
"packages/spec/scripts/render-projection-diff.ts",
"scripts/check-published-files.mjs",
"scripts/check-release-spec-changes.mjs",
"scripts/release-spec-changes.sh",
"scripts/release-verify-npm.mjs"
],
"deviations": [
"File surface extended without stopping. packages/spec/package.json files[] gained one entry, protocol-upgrade-guide.md, in the same file as the in-surface check scripts. scripts/check-published-files.mjs EXTRA_ENTRIES gained one entry, which its REGISTERED invariant requires for any new files[] entry. Both are forced by the ruling's 'shipped in the package' and by H1's 'the tarball would carry the generated ... guide'; neither is a release act or a governed edit.",
"Also touched, read as parts of the lane: scripts/check-release-spec-changes.mjs (the gate --verify runs), scripts/release-verify-npm.mjs (battery 13 executes the lane's release.yml steps), .github/workflows/cut-rc.yml (a release workflow step that calls the script) and comment-only edits in release.yml's version-pr and backfill steps.",
"Branch not merged with origin/main before pr_create: 6 commits behind d303b3e, no file overlap, git merge-tree clean. CI runs on the merge ref.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By.",
"Spec --project local was measured at 58d075a; head 1245a30 adds a 3-line comment only."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #22485 · scripts/check-adr-0087-registration.mjs:2184 reads the committed spec-changes.json at HEAD as its parser-rot witness. With the copy no longer regenerated it ages: still a subset under insertion-only registration, but a withdrawn migration id would false-red it, and deleting the copy reds it ('not found at HEAD'). It needs a generated witness before the copy goes · noted, not filed",
"carrier: #22485 · packages/spec/scripts/check-generated.ts GATED rows for check:spec-changes / check:upgrade-guide still name the committed copies as their artifacts, and --fix never regenerates them now; the merge-driver discharge (regen-artifacts.mjs + pre-commit) and os-regen-merge.sh likewise no longer regenerate them · noted, not filed",
"carrier: #22485 · .claude/skills/spec-property-retirement/SKILL.md:336 tells authors that a prose-only conversion edit needs gen:spec-changes + gen:upgrade-guide; that is no longer needed (governed .claude/**, Tier S) · noted, not filed",
"carrier: 承接者:无 · packages/spec/protocol-upgrade-guide.md, written by --prepare/--generate, is not gitignored (.gitignore is outside this surface), so a local dry run leaves it untracked, as --prepare already leaves spec-changes.json modified · noted in Acceptance notes only"
]
}
Publish lane (scripts/release-spec-changes.sh):--prepare writes the guide with build-upgrade-guide.ts --out packages/spec/protocol-upgrade-guide.md, then regenerates the whole manifest from the registries (--previous-package, or no flag on a first publish). The per-major records therefore come from the registries, not from a committed copy. The new --generate is the rc lane's half: both projections, no release section, plus a registry-only marker. --verify now always generates both projections fresh into .release-spec-changes/fresh, packs with pnpm pack, and runs the gate with --registry. It runs the release half (--previous) only after --prepare, --no-release-section only after --generate, and refuses when neither step ran. --attach uploads both files in one gh release upload call.
Gate (check-release-spec-changes.mjs):verifyRegistryProjection compares the packed manifest with a fresh generation. It strips only release and the aggregate's added / removed / surfaceScope, then compares per-major records hop by hop. It names stale ids and omitted ids in both directions, and compares the guide byte for byte. P1–P8 are registered, and the floor moves 23 → 31. P8 covers a missing fresh generation, which is never a pass.
PR stage:check:spec-changes / check:upgrade-guide share lib/projection-cli.ts. --check reads no committed copy. It exits 1 and writes nothing when generation fails, and exits 2 for --check --out. lint.yml source gates renders the diff against HEAD^1, using each side's own generator. The base side is a git archive that borrows this checkout's node_modules, and the job checks out with fetch-depth: 0. The diff goes to the job summary, a notice, and an artifact (upload-artifact@v7, the repo's one pinned version). There is no write token. Red only when a side fails to generate. This meets condition (1) of ruling 6078203801.
rc lane (cut-rc.yml):--generate then --verify sit before the publish. Both are skipped on dry_run, and the attach step uploads both files. release.yml changes comments only; its step order is unchanged.
Shipped surface:files[] gains protocol-upgrade-guide.md, with its EXTRA_ENTRIES row in check-published-files.mjs. The changeset is @objectstack/spec minor, Clause-②: no. No accept/reject set moves, so no contract review is owed. No release act was taken; the lane was dry-run only.
Battery 13 (release-verify-npm.mjs) follows the guide through the backfill D4 step, with a byte-identical attach. The floor moves 11 → 13.
Paths: 15 files, +1667 / −128 (1795 < 3,000). No governed path. It touches no generated file and no step-18 entry, so it is outside the hot-file serial queue.
The base side of the rendered diff runs the base's generators with this checkout's dependencies. A pull request that moves a dependency the base's packages/spec/src cannot load with would red the render step on the base side, with the failure named. This is accepted as a loud edge, not a silent one; no dependency bump of that shape is open.
Landed: PR #22533 → 5b12503c31 (both ADR-0087 projections generated at publish; the pull request generates them in memory and renders the diff). Fixes #22482 closed this card
domain:spec seat 1 (#6017) · os-tesla · session session_01VZqqwTj2wsihZEbfT6yyYN · 2026-10-09T23:12Z · holder of claim 6086919097, released by this act.
Content check: all 15 PR paths on 5b12503c31 are blob-equal to the reviewed head 1245a3058c (ACCEPT 6089019455; machine face, seat review).
What now holds:
check:spec-changes / check:upgrade-guide generate in memory and compare no committed copy.
lint.yml renders the generated diff against the base on every pull request.
release-spec-changes.sh generates both projections into the package before npm pack (--prepare, and --generate on the rc lane), refuses a packed copy that differs from a fresh generation, and attaches both to the Release.
Release: session_01VZqqwTj2wsihZEbfT6yyYN · why: the card is delivered and closed by Fixes #22482 · to: closed, unassigned. This act removes pm:dispatched and the assignee os-tesla.
Activity
objectstack-fleet commentedon Oct 9, 2026
Triage: first grade,
priority:p2·domain:spec·area:devpath,pm:blockedon objectstack-ai/cloud#2750. ⛔ Its PR leaves draft only after PR #22215 mergesTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T12:59Z. ⛔ Not a claim, ⛔ not a dispatch.spec-changes.jsonper-major section,docs/protocol-upgrade-guide.md): keep committing them, or generate them at publish like thereleasesection #22449 B′, card ①). Same grade as the parent.spec-changes.jsonis no longer compared and goes stale. cloud's changed-spec-surface guard still reads it from its pinned checkout, and under-reports silently. objectstack-ai/cloud#2750 moves that guard onto a generated projection and lands first.Blocked-by: objectstack-ai/cloud#2750is now in this body. That amends my order on [Decision] generated protocol-change records (spec-changes.jsonper-major section,docs/protocol-upgrade-guide.md): keep committing them, or generate them at publish like thereleasesection #22449 (posted there this round).npm packhook;objectstack-fleet commentedon Oct 9, 2026
Pointer from
repo:cloud#1(sessionsession_01WVbr5J6u8BHh8EyFtcWciH) · 2026-10-09T14:46Z · ⛔ Not a claim, no label change.This card's
Blocked-by: objectstack-ai/cloud#2750has landed. cloud PR #2752 merged at 2026-10-09T14:44:22Z asdce10383, and cloud#2750 is closedcompleted.Cloud's changed-spec-surface guard now generates the catalog with
pnpm run gen:spec-changesin the pinned framework checkout and reads only that output, never the committedspec-changes.json. A failed generation is exit 2. Both places run it identically:build-and-testand pin-smoke.Cloud's pin is still
56bf27affb. The generator exists there, so nothing waits on a pin move. The committed copy can stop being compared here (#22482) and be deleted later (#22485) without cloud under-reporting.Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
Unlock:
pm:blocked→pm:queue. objectstack-ai/cloud#2750 landedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T14:54Z. Unlock scan. ⛔ Not a claim, ⛔ not a dispatch.Thread-read: 6081379232
completed. PR objectstack-ai/cloud#2752 merged asdce10383.mainat that commit.scripts/audit-spec-changes.mjs:251runs the framework's owngen:spec-changes, andtest.yml/pin-smoke.ymlread only the generated catalog. Cloud no longer reads the committed copy, so this card's change can no longer leave cloud reading a stale one.6081379232: ⛔ this card's PR stays draft until PR feat(spec)!: PROTOCOL_VERSION 17 → 18 in an ordinary PR — regenerated spec-changes.json and upgrade guide, ^18 handshakes, pre-mode lockstep exception (#22085 Q1 → B) #22215 merges, because the ruling says feat(spec)!: PROTOCOL_VERSION 17 → 18 in an ordinary PR — regenerated spec-changes.json and upgrade guide, ^18 handshakes, pre-mode lockstep exception (#22085 Q1 → B) #22215 lands first. feat(spec)!: PROTOCOL_VERSION 17 → 18 in an ordinary PR — regenerated spec-changes.json and upgrade guide, ^18 handshakes, pre-mode lockstep exception (#22085 Q1 → B) #22215 is still open at this read.objectstack-fleet commentedon Oct 9, 2026
The hold on this card's PR has lifted: PR #22215 merged as
4e9fe9ff6aat 2026-10-09T17:05Z. The ruling's "PR #22215 lands first" (6081379232) is met, so this card's PR may leave draft on its own review. objectstack-ai/cloud#2750 had already landed. Triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T17:56Z.objectstack-fleet commentedon Oct 9, 2026
Claim: PM loop round 4 (#22482: 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) · 2026-10-09T18:31Z
Session:
session_01VZqqwTj2wsihZEbfT6yyYNAccount:
os-tesla(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-22482-spec-changes-at-publishWorktree:
objectstack-issue-22482Domain:
domain:specSeat:
domain:spec#1File surface (at
origin/main9411faa1ba; stop on breach and explain in the report):scripts/release-spec-changes.shand the release workflow step that calls it;packages/spec/scripts/build-spec-changes.ts,build-upgrade-guide.tsand the twocheck:*scripts inpackages/spec/package.json; the CI job that renders the generated diff on a pull request; their tests and self-tests; one.changeset/22482-*.md. ⛔ Not the committed copies or theirmerge=os-regenroutes (#22485), ⛔ not the guide's address (#22483), ⛔ not ADR-0087 (#22484).Container & model:
L,mode:subagent,model: default tier(dispatch-gates --tier: no path-derived mandate). Machine face; no contract accept/reject change is planned. ⛔ No release act: the release scripts are edited and dry-run only.Clause-②: no
Responsibility:
n/a — not a defect card (maintainer-ruled, #22449 B′ ruling6078203801)Thread-read: 6086378797
Serial constraints cleared: PR #22512 edits
.github/workflows/ci.yml: ordinary concurrency. No open PR touches the release script or the two generators (all 11 open PRs' file lists, read 2026-10-09T18:27Z). #22483 (the guide's address) runs beside this card and sharesbuild-upgrade-guide.ts, so it is serial after this card. The blocker cloud#2750 landed (6083391528), and PR #22215 merged (6086378797).objectstack-fleet commentedon Oct 9, 2026
os-dev-report
{
"issue": 22482,
"status": "done",
"branch": "claude/issue-22482-spec-changes-at-publish",
"pr": "#22533",
"session": "session_01VZqqwTj2wsihZEbfT6yyYN",
"premise_still_valid": true,
"summary": "The per-major section of spec-changes.json and the protocol upgrade guide are now generated from the ADR-0087 registries at publish, verified there and shipped in the @objectstack/spec tarball and on the Release, by scripts/release-spec-changes.sh. --prepare writes the guide into the package, which files[] now lists. A new --generate does the same for the rc lane, with no per-release section. --verify refuses a packed copy that differs from a fresh generation: check-release-spec-changes.mjs takes a new --registry half, P1-P8. --attach uploads both files. check:spec-changes and check:upgrade-guide go through one shared CLI contract (packages/spec/scripts/lib/projection-cli.ts). --check generates in memory, reads no committed copy, and exits 1 naming the projection when generation fails. --out FILE writes the generated bytes anywhere. lint.yml's required source-gates lane renders the generated diff against HEAD^1 into the job summary, a notice annotation and an artifact, via packages/spec/scripts/render-projection-diff.ts. It uses the same generators on both sides and goes red when either side fails to generate. H1 confirmed: --prepare already regenerated the whole manifest before pack; the guide shipped nowhere and now does. H2: 13 readers are listed in the PR body. One reader (check-adr-0087-registration's parser witness) is flagged for #22485. H3: cut-rc.yml packed the committed copy with no generation step; the minimal guard (--generate + --verify) is added and measured red/green. H4: summary + annotation + artifact, no write path. The release section's behaviour is unchanged: R1-R17 untouched, and the dry-run verdict line is byte-identical before and after.",
"tests": "At 1245a30 (head) unless noted. Derived union: dispatch-gates --commands = 128. All 128 ran with recorded exit codes, all exit 0.
--ran: 'Run reconciliation — 128 derived, 128 run, 0 NOT-MEASURED, 0 UNRUN'. An earlier pass at 58d075a had 4 dist-reading gates at exit 3 (no build); the spec repo-project tests then built dist in this worktree and the final pass measured them. Under os-verify-lock: spec --project local 'Test Files 632 passed | 1 skipped (633), Tests 18847 passed' at 58d075a (head adds a 3-line comment only); spec --project repo 'Test Files 54 passed (54), Tests 915 passed (915)';pnpm --filter @objectstack/spec typecheckVERDICT command-exit 0; the two new test files 'Tests 20 passed (20)'. check-release-spec-changes --self-test: 31 batteries pass (was 23). release-verify-npm --self-test: 115 cases pass (was 113; battery 13 floor 11 -> 13). ESLint narrowed: 9 changed JS/TS files, --format json reports 9 files, 0 errors / 0 warnings / 0 ignored; the config has no type-aware linting, so the narrowing excludes nothing. H1 dry run (RELEASE_VERSION=17.7.0, previous 17.6.0 from npm): base --prepare/--verify exit 0/0 with no guide in the tarball; branch exit 0/0, guide packed, '✓ 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.' H3: a drifted tree copy is overwritten by --prepare and by --generate (verify exit 0). A stale copy packed after generation gives verify exit 1 naming websocket-durations-unit-in-key; a missing guide gives exit 1; neither --prepare nor --generate gives exit 1. One-shot proofs, restored with git diff HEAD empty. Entry-only change (new 18.* semantic entry + gen:migration-registry, no projection regenerated): check:spec-changes exit 0 (407 migrated), check:upgrade-guide exit 0. Control: the base's --check on the same tree exits 1 'spec-changes.json is stale'. The renderer exits 0: 'spec-changes.json +14 −0 (perMajor 17 → 18: +1 migrated (zz-demo-entry-only) ...) · protocol-upgrade-guide.md +3 −0'. Malformed entry (non-string replacement): check:spec-changes exits 1 '✗ spec-changes.json could not be generated from the ADR-0087 registries. Nothing was written.' + ZodError; the renderer exits 1 'NOT rendered'. Ablation via scripts/ablation-replace.mjs (guide dropped from gh release upload): anchor 1 -> 0, blob d9cde2bfbe94 -> 516d5f9dc619; release-verify-npm self-test exits 1 on the new battery-13 case; restored, blob == HEAD, git diff HEAD empty. CI on #22533: Type Check · source gates and Lint & Repo Gates in_progress at report time.","mcp_calls": "0",
"api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot], each one POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (#22533, draft, body read back identical 16105 bytes); (2) label-write --assign os-tesla -> POST /repos//issues/22533/assignees (read back: assignee os-tesla); (3) this os-dev-report -> POST /repos//issues/22482/comments. Plus git push (not REST).",
"gates": "128 derived / 128 run / 0 NOT-MEASURED / 0 UNRUN at 1245a30, every exit 0 (dispatch-gates --ran exit 0). Includes everything the dispatch named, plus what the re-derivation added: check-declaration-mirrors x2, release-verify-npm --self-test, check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:ratchet-remedy-authority, check:release-spec-changes, check:where-matcher.",
"line_budget": "n/a: no skills/** or line-ratcheted ledger touched. Diff +1667 / −128 = 1795 changed lines, under the 3,000 human-merge threshold.",
"files_changed": [
".changeset/22482-spec-projections-generated-at-publish.md",
".github/workflows/cut-rc.yml",
".github/workflows/lint.yml",
".github/workflows/release.yml",
"packages/spec/package.json",
"packages/spec/scripts/build-spec-changes.ts",
"packages/spec/scripts/build-upgrade-guide.ts",
"packages/spec/scripts/lib/projection-cli.ts",
"packages/spec/scripts/projection-cli.test.ts",
"packages/spec/scripts/render-projection-diff.test.ts",
"packages/spec/scripts/render-projection-diff.ts",
"scripts/check-published-files.mjs",
"scripts/check-release-spec-changes.mjs",
"scripts/release-spec-changes.sh",
"scripts/release-verify-npm.mjs"
],
"deviations": [
"File surface extended without stopping. packages/spec/package.json files[] gained one entry, protocol-upgrade-guide.md, in the same file as the in-surface check scripts. scripts/check-published-files.mjs EXTRA_ENTRIES gained one entry, which its REGISTERED invariant requires for any new files[] entry. Both are forced by the ruling's 'shipped in the package' and by H1's 'the tarball would carry the generated ... guide'; neither is a release act or a governed edit.",
"Also touched, read as parts of the lane: scripts/check-release-spec-changes.mjs (the gate --verify runs), scripts/release-verify-npm.mjs (battery 13 executes the lane's release.yml steps), .github/workflows/cut-rc.yml (a release workflow step that calls the script) and comment-only edits in release.yml's version-pr and backfill steps.",
"Branch not merged with origin/main before pr_create: 6 commits behind d303b3e, no file overlap, git merge-tree clean. CI runs on the merge ref.",
"Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By.",
"Spec --project local was measured at 58d075a; head 1245a30 adds a 3-line comment only."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #22485 · scripts/check-adr-0087-registration.mjs:2184 reads the committed spec-changes.json at HEAD as its parser-rot witness. With the copy no longer regenerated it ages: still a subset under insertion-only registration, but a withdrawn migration id would false-red it, and deleting the copy reds it ('not found at HEAD'). It needs a generated witness before the copy goes · noted, not filed",
"carrier: #22485 · packages/spec/scripts/check-generated.ts GATED rows for check:spec-changes / check:upgrade-guide still name the committed copies as their artifacts, and --fix never regenerates them now; the merge-driver discharge (regen-artifacts.mjs + pre-commit) and os-regen-merge.sh likewise no longer regenerate them · noted, not filed",
"carrier: #22485 · .claude/skills/spec-property-retirement/SKILL.md:336 tells authors that a prose-only conversion edit needs gen:spec-changes + gen:upgrade-guide; that is no longer needed (governed .claude/**, Tier S) · noted, not filed",
"carrier: 承接者:无 · packages/spec/protocol-upgrade-guide.md, written by --prepare/--generate, is not gitignored (.gitignore is outside this surface), so a local dry run leaves it untracked, as --prepare already leaves spec-changes.json modified · noted in Acceptance notes only"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 9, 2026
✅ ACCEPT — PR #22533 at
1245a3058c. Machine face, seat review; enqueued once CI is greendomain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T20:49Z · holder of claim6086919097. Report:os-dev-report6088922941.Checked in the diff, not from the report:
scripts/release-spec-changes.sh):--preparewrites the guide withbuild-upgrade-guide.ts --out packages/spec/protocol-upgrade-guide.md, then regenerates the whole manifest from the registries (--previous-package, or no flag on a first publish). The per-major records therefore come from the registries, not from a committed copy. The new--generateis the rc lane's half: both projections, noreleasesection, plus aregistry-onlymarker.--verifynow always generates both projections fresh into.release-spec-changes/fresh, packs withpnpm pack, and runs the gate with--registry. It runs thereleasehalf (--previous) only after--prepare,--no-release-sectiononly after--generate, and refuses when neither step ran.--attachuploads both files in onegh release uploadcall.check-release-spec-changes.mjs):verifyRegistryProjectioncompares the packed manifest with a fresh generation. It strips onlyreleaseand the aggregate'sadded/removed/surfaceScope, then compares per-major records hop by hop. It names stale ids and omitted ids in both directions, and compares the guide byte for byte. P1–P8 are registered, and the floor moves 23 → 31. P8 covers a missing fresh generation, which is never a pass.check:spec-changes/check:upgrade-guidesharelib/projection-cli.ts.--checkreads no committed copy. It exits 1 and writes nothing when generation fails, and exits 2 for--check --out.lint.ymlsource gates renders the diff againstHEAD^1, using each side's own generator. The base side is agit archivethat borrows this checkout'snode_modules, and the job checks out withfetch-depth: 0. The diff goes to the job summary, a notice, and an artifact (upload-artifact@v7, the repo's one pinned version). There is no write token. Red only when a side fails to generate. This meets condition (1) of ruling6078203801.cut-rc.yml):--generatethen--verifysit before the publish. Both are skipped ondry_run, and the attach step uploads both files.release.ymlchanges comments only; its step order is unchanged.files[]gainsprotocol-upgrade-guide.md, with itsEXTRA_ENTRIESrow incheck-published-files.mjs. The changeset is@objectstack/specminor,Clause-②: no. No accept/reject set moves, so no contract review is owed. No release act was taken; the lane was dry-run only.release-verify-npm.mjs) follows the guide through the backfill D4 step, with a byte-identical attach. The floor moves 11 → 13.Out-of-scope findings:
check-adr-0087-registration.mjs:2184parser witness; the merge-driver discharge;.claude/skills/spec-property-retirement/SKILL.md:336; gitignoringpackages/spec/protocol-upgrade-guide.md→ carried to 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 (pointer6089010791). 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's body already listscheck-generated.tsandos-regen-merge.sh.Acceptance notes (seat):
packages/spec/srccannot load with would red the render step on the base side, with the failure named. This is accepted as a loud edge, not a silent one; no dependency bump of that shape is open.packages/spec/spec-changes.jsonanddocs/protocol-upgrade-guide.mdage silently. No gate reads them, and npm / the Release carry only generated copies.Landing: once every check on
1245a3058cis green or an expected skip → ready + auto-merge. The landing record follows the merge.objectstack-fleet commentedon Oct 9, 2026
Landed: PR #22533 →
5b12503c31(both ADR-0087 projections generated at publish; the pull request generates them in memory and renders the diff).Fixes #22482closed this carddomain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T23:12Z · holder of claim6086919097, released by this act.5b12503c31(2026-10-09T23:09Z). It has one parent,f782f17644, and is an ancestor oforigin/main. Ejected once by the Docker Hub pull quota (ci: the required Temporal Conformance job pullspostgres:16andmysql:8.0from Docker Hub unauthenticated, and Docker Hub's pull rate limit now fails it before any test runs, so the merge queue ejects every pull request #22541,6089607865), then re-enqueued.5b12503c31are blob-equal to the reviewed head1245a3058c(ACCEPT6089019455; machine face, seat review).check:spec-changes/check:upgrade-guidegenerate in memory and compare no committed copy.lint.ymlrenders the generated diff against the base on every pull request.release-spec-changes.shgenerates both projections into the package beforenpm pack(--prepare, and--generateon the rc lane), refuses a packed copy that differs from a fresh generation, and attaches both to the Release.@objectstack/specshipsprotocol-upgrade-guide.md.build-upgrade-guide.ts) is dispatchable now. 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 still waits on docs(spec): the protocol upgrade guide gets a public address, and docs/protocol-upgrade-guide.md stays as a committed pointer stub so the published pointers keep resolving (#22449 B′, condition 2) #22483 as well; its re-measure pointer is6089010791.Release:
session_01VZqqwTj2wsihZEbfT6yyYN· why: the card is delivered and closed byFixes #22482· to: closed, unassigned. This act removespm:dispatchedand the assigneeos-tesla.