Skip to content

spec(changes): delete the committed spec-changes.json and its merge=os-regen route; both projections are gitignored and generated at publish only (#22449 B′, card ③) - #22638

Merged
os-zhuang merged 6 commits into
mainfrom
claude/issue-22485-delete-committed-projections
Oct 10, 2026
Merged

os-zhuang merged 6 commits into
mainfrom
claude/issue-22485-delete-committed-projections

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #22485
Clause-②: no

The committed packages/spec/spec-changes.json and its merge=os-regen route are deleted, and both ADR-0087 D4 projections are gitignored inside @objectstack/spec. This finishes ruling 6078203801 on #22449 (B′): "The committed copies and their two merge=os-regen routes are deleted; check:spec-changes and check:upgrade-guide stop comparing a committed copy." The checks stopped comparing in #22533 (#22482). The guide's copy and route went in #22556 (#22483). This PR removes what was left: the spec-changes.json copy, its route, and every reader of it.

What ships does not change. The publish lane still writes both projections into the package right before the tarball is packed, files[] still lists them, and --verify still refuses a tarball that is missing one. Proof is in the dry run below.

What changed

H1: every reader of the committed copy, and what it reads now

Reader After this PR
scripts/check-adr-0087-registration.mjs (assertInputs, formerly :2184) Generates the projection at the rev it judges (H2). It no longer reads any copy.
packages/spec/scripts/build-spec-changes.ts no-flag mode Writes the untracked, gitignored package copy (above).
packages/spec/scripts/check-generated.ts Runs check:spec-changes in memory. That check never goes stale, so --fix never regenerates. The row text names the gitignored copy.
scripts/regen-artifacts.mjs, .gitattributes, scripts/git-merge-regen.mjs, .githooks/pre-commit → check-regen-pending.mjs No row and no route. The driver never sees the path, and an old marker naming it gets the UNROUTED refusal (H3).
scripts/pm/os-regen-merge.sh Reads routes at run time (16). It has no hard-coded list. Only the prose note changed.
scripts/release-spec-changes.sh (--prepare / --generate / --verify / --attach) Unchanged. It writes into the package and verifies the packed copy (dry run below).
packages/spec/package.json files[] Unchanged. It ships what the lane wrote.
scripts/check-release-spec-changes.mjs Unchanged. It reads the packed tarball and a fresh generation, never the tree copy.
scripts/release-verify-npm.mjs (MANIFEST at :2063) A path inside its stubbed workflow fixture. Unchanged, and its self-test is green.
packages/spec/scripts/render-projection-diff.ts (legacyPath) Generates both sides with --out. The legacy path is read only inside a throwaway archive of a base that predates --out, never from this checkout. Unchanged.
packages/cli/src/utils/spec-release-changes.ts Reads the installed package's release section. The committed copy never had one, so it answered null in the workspace before this PR, and an absent file answers null too. Unchanged.
packages/spec/src/**/*-retirement.test.ts (EXCLUDED_PREFIXES) Walk the disk and still exclude the path, which is still right: a local gen:spec-changes output sits there, gitignored. Unchanged.
scripts/check-future-spec-major.mjs Scans tracked files. Its row for the copy is removed (forced, above).
scripts/check-regen-pending.mjs self-test fixture Used the path as "a real REGEN_ARTIFACTS entry". It now derives its row from the table, and uses the path as the retired-route scenario.
scripts/objectui-changeset-digest.mjs self-test sandbox Staged a fixture copy for the gate. It now stages the stand-in generator (forced, above).
cloud scripts/audit-spec-changes.mjs Reads gen:spec-changes output in its pinned checkout since cloud#2750. That output path is unchanged here.

H2: the witness is generated at the judged rev

Before this PR, assertInputs read HEAD:packages/spec/spec-changes.json as its independent witness that extractIds() still sees every id the build sees. Since #22533 that copy has been aging. A withdrawn migration id stays in an aged copy and false-reds the gate, and deleting the copy reds it outright.

generatedWitness() now lays out the judged rev's packages/spec/{src,scripts,package.json} with git archive. It borrows the checkout's node_modules and runs build-spec-changes.ts --out under the installed tsx. So the witness describes exactly the rev being judged, never the working tree. A missing runner, a missing generator, a generator that exits non-zero, or one that writes nothing is each a red input, never a skip. On this tree the real run generates 414 ids, all of them seen by the parser. The gate run is about 2 s.

  • Pins. Battery W1-W6, with 9 cases, drives the real generatedWitness over a stand-in generator. The stand-in evaluates the fixture registry under tsx, as the real one does.
    • W1: a withdrawn id with a stale committed copy at HEAD is green, and the witness the gate used holds only the kept id.
    • W2: an uncommitted edit stays green, and the same edit committed is parser drift that names the id.
    • W3 to W6: a missing generator, a failing generator, a silent generator, and no tsx are each red.
    • I1 (parser rot) and the I2 entry-point run now go through the real path too. The self-test reports 450 assertions, up from 441.
  • Ablation, via scripts/ablation-replace.mjs at 538bec7e1d. The witness read was put back to the committed-copy read.
    • The self-test exits 1 with 14 failures, including W1: a withdrawn migration id must NOT red the gate -- got: ledger parser drift … - withdrawn-one.
    • Restored: blob 7e383d48ea90 == HEAD, and git diff HEAD is empty.

H3: a pending path whose route was retired

The premise holds for the class, but not for this card's own path. The deletion does not reach the stuck marker: git runs no merge driver on a modify/delete, so a branch carrying spec-changes.json writes no marker for it (H4). The stuck marker comes from the guide's route, retired by #22483, and from any marker an earlier merge left.

Measured with the real driver and hooks, in a detached scratch worktree:

  • The scratch worktree starts at 874a38d8d9, which is 514bf3c101^1, before 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. A commit there edits docs/protocol-upgrade-guide.md. It then merges this branch (cec190b908).
  • The merge auto-completes with exit 0. The marker holds docs/protocol-upgrade-guide.md, and the tree holds the branch's generated guide: the stub was dropped.
  • The next commit exits 1:
    ✗ docs/protocol-upgrade-guide.md — UNROUTED: pending, but scripts/regen-artifacts.mjs has no row for it any more, so no gate
        can verify it and nothing clears it by itself. The merge kept THIS side of it whole and dropped
        the side it merged in: compare them (`git diff HEAD^2 -- docs/protocol-upgrade-guide.md`), keep or restore the
        right bytes, then release it:
        node scripts/check-regen-pending.mjs --release docs/protocol-upgrade-guide.md
    
  • Following it:
    • git diff HEAD^2 shows +1590 −9.
    • git checkout HEAD^2 -- docs/protocol-upgrade-guide.md restores the 15-line stub.
    • --release exits 0 with "nothing left pending, marker cleared".
    • The commit exits 0, and --pre-push exits 0.
    • The guide is byte-identical to this branch's.
  • Control: 18d999031b's check-regen-pending.mjs, run on the same marker, exits 1 with recorded as pending but absent from scripts/regen-artifacts.mjs (cannot verify) and This check clears itself the moment they are current — nothing to reset by hand.. It has no --release, and its run leaves the marker in place.

The path stays BLOCKED, never passed. Passing it would land exactly the drop the driver made, and #22556 recorded the refusal as the one thing that keeps that drop loud. --release PATH refuses a path that still has a row, because its gate discharges it, never a hand. It also refuses a path the marker does not hold, and keeps every other line and the deferral record.

  • Pins (fixtureSelfTest, in the script's own self-test harness):
    • The scenario's premise is asserted: the path has no row.
    • The refusal is exit 1 and no longer claims "nothing to reset by hand".
    • The printed command is parsed and run as printed, with no fallback spelling. It clears the marker, and the next push is accepted.
    • --release refuses a routed path and an absent path, and leaves a routed path's debt and its stale refusal intact.
    • The fixture's pending row is now derived from REGEN_ARTIFACTS, so retiring a row cannot break the fixture again.
  • Ablation, via scripts/ablation-replace.mjs at 538bec7e1d. The printed --release line was dropped.
    • The self-test exits 1: "printing a release command", "the push is refused with the same remedy", "THE PRINTED REMEDY WORKS" and "the next push is accepted" go red, both directly and in the ambient-git-env inner run.
    • Restored: blob ca127a866a97 == HEAD, and git diff HEAD is empty.

H4: a branch that still carries a regenerated copy

What the author does:

git rm packages/spec/spec-changes.json && git commit --no-edit

The conflict is modify/delete, so git runs no merge driver and leaves no marker. The deletion is the resolution, and nothing regenerates the copy in git any more. Measured on a scratch commit at 18d999031b that edited the copy, merging cec190b908:

  • the merge exits 1 with CONFLICT (modify/delete): packages/spec/spec-changes.json deleted in cec190b908… and modified in HEAD, and no marker is written;
  • the command above exits 0, and the resulting tree equals this branch.

PR #22421 (#11509, seat 2) is such a branch today. The same line is in .gitattributes and in NOT_DRIVER_MANAGED's reason, which is where a merging author meets the route list.

Publish lane dry run (no release act)

At 928bee8573, with RELEASE_VERSION=17.7.0:

  • --generate exits 0 and writes both files into packages/spec/. git status --short stays empty: --ignored shows both as !!.
  • --verify exits 0 with ✓ registry projections verified against a fresh generation: spec-changes.json (2 per-major record(s), 121 converted / 414 migrated) and protocol-upgrade-guide.md (1617979 bytes) match the artifact. and ✓ no per-release section is owed: this lane (--generate) publishes none..
  • The packed objectstack-spec-17.7.0.tgz lists package/spec-changes.json and package/protocol-upgrade-guide.md. Its spec-changes.json is byte-identical to the generated one (cmp).
  • Control: with the generated spec-changes.json removed, --verify exits 1 with the artifact about to publish ships no spec-changes.json. A consumer's node_modules cannot be handed a tarball without it.

Changeset: none, skip-changeset

No published byte changes:

So no package warrants a bump, and this PR takes skip-changeset. The dispatch asked for H4's line to go in a changeset. With no changeset, that line is in H4 above and in the two in-repo places named there.

Governed lines that name the copies or their routes (not edited here; the skills lane carries them)

Measured and not stale: .claude/skills/pm-dispatch/SKILL.md:296, the hot-file row for os-regen-merge.sh, which names no path, and references/landing-operations.md:38, which reads .gitattributes live.

Verification

Gate union read at cb1ac74e76, the final head. It is the 87 commands the dispatch named, plus what node scripts/pm/dispatch-gates.mjs --commands derives on this diff (105 today), 110 commands in all. Each ran with its exit code captured before any pipe, and all 110 exited 0. --ran reports Run reconciliation — 105 derived, 105 run, 0 NOT-MEASURED, 0 UNRUN. That includes:

  • check:merge-driver: 16 routed paths, and 5 untracked disposition(s) still hold — git tracks none of those paths;
  • check:changeset-gate-self-tests and the ADR-0087 gate itself (--base origin/main: green, with a generated witness);
  • check:generated: ✓ All 15 generated artifacts are up to date, with check:spec-changes spec-changes.json (gitignored; written into the package at publish);
  • check:objectui-changeset, check:pm-dispatch-gates, check:type-check-debt and check:nul-bytes.

Under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, at 928bee8573; the README rider after it touched no test input):

  • @objectstack/spec: build exit 0; typecheck exit 0, and tsc -p tsconfig.scripts.json --listFiles lists all four touched script and test files;
  • vitest --project local: 642 files, 19146 passed, 1 todo;
  • --project repo: 54 files, 915 passed;
  • release-verify-npm.mjs --self-test: 115 cases across 13 batteries;
  • check-release-spec-changes.mjs --self-test: 31 batteries.

Acceptance notes (not filed)

  • Size. About 8,000 changed lines, 7,272 of them the deleted generated JSON. That is over the 3,000-line threshold, so this lands the Tier H way: an authorized approval or a human merge.
  • Landing rule A relaxation. The maintainer's temporary relaxation for D3-entry PRs (6091886885, corrected by 6091996837) ends when this lands.
  • .github/workflows/lint.yml:4728 lists spec-changes.json among the artifacts merge=os-regen protects. Comment only. carrier: none.
  • .github/workflows/pr-automation.yml:959–:961: "the two scripts import only node builtins and repo-local modules, so a failed install cannot change their verdict". The import half is still true. The ADR-0087 gate now needs the installed tsx for its witness, so a failed install also reds that step, in the enforcing direction. Comment only. carrier: none.
  • scripts/check-published-files.mjs:291: the spec-changes.json registration reason could say "generated at publish (it is not in the tree)", as the guide's does. carrier: none.
  • docs/spec-generated-artifact-sharding.md:42 describes the committed file's merge shape as current. Design history. carrier: none.
  • Cost. check-adr-0087-registration --self-test costs about 7 s more on this shared box (20.7 s → 27.5 s): 8 real generator spawns.

Generated by Claude Code

…the judged rev, never read from a committed spec-changes.json (#22485)

check-adr-0087-registration read packages/spec/spec-changes.json at HEAD as
its witness that extractIds() still sees every migration id the build sees.
Since the committed copy stopped being regenerated it ages, and a migration id
withdrawn from the registry would have stayed in it and false-redded the gate;
deleting the copy would red it outright. The witness is now generated at the
rev being judged: git archive of that rev's generator inputs, the package's own
build-spec-changes.ts --out under the checkout's tsx. Every failure to produce
it is a red input, never a skip.

Self-test: battery W1-W6 drives the real generatedWitness over a fixture
generator (withdrawn id green, head-not-worktree, missing/failing/silent
generator, no tsx); I1 and the entry-point run go through the real path too.
objectui-changeset-digest's fw-gate sandbox stages the same stand-in generator.

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…s-regen route; gitignore both projections; an unrouted pending path prints a remedy that works (#22485)

Ruling 6078203801 on #22449 (B'): the committed copies and their two
merge=os-regen routes are deleted. The guide's half landed with #22483;
this is the spec-changes.json half.

- packages/spec/spec-changes.json leaves git and is gitignored, with
  packages/spec/protocol-upgrade-guide.md. The publish lane still writes
  both into the package before npm pack, and files[] still ships them.
- .gitattributes drops the last projection route; regen-artifacts.mjs
  moves gen:spec-changes to NOT_DRIVER_MANAGED as untracked output, which
  the merge-driver self-test now holds against git. Comment counts in
  git-merge-regen.mjs follow (16 routed paths, 16 rows, 14 defaulted).
- build-spec-changes.ts writes no committed copy: its no-flag mode writes
  the gitignored package copy the lane and gen:spec-changes produce
  (projection-cli's committedPath becomes defaultPath).
- check-regen-pending: a pending path with no REGEN_ARTIFACTS row stays
  blocked, but now says why and prints the remedy that settles it, and
  --release PATH removes exactly that line (refusing a routed path or one
  that is not pending). Its fixture derives its pending row instead of
  naming spec-changes.json.
- check-generated's row text, os-regen-merge.sh's measured-path note and
  check-future-spec-major's exemption row for the deleted copy (forced:
  it matched nothing once the file was gone) follow.

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…d the refusal printed (#22485)

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
… case, not a crash (#22485)

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…enerate and commit the two projections (#22485, rider from pointer 6094908270)

An entry regenerates registry.ts alone (gen:migration-registry); the two
ADR-0087 D4 projections are generated at publish and in memory on the pull
request, and neither has a committed copy any more. The #8344 merge-queue
measurement stays, marked as history kept for its registry.ts conclusion.

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 10, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 2 changed file(s) yielded no anchor (packages/spec/spec-changes.json, packages/spec/src/migrations/entries/README.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/spec-changes.json, packages/spec/src/migrations/entries/README.md) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 1b99388505d33c940757e85685ca925d6cd57f04 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

domain:spec seat 1 (#6017) · os-tesla · session session_01VZqqwTj2wsihZEbfT6yyYN · 2026-10-10T08:18Z。待批的是 head cb1ac74e76。席位复核 ACCEPT 记录是 #22485 的 6095601210;CI 39 项:32 通过,7 项按预期跳过(已核对跳过名单),0 失败。本 PR 不碰受管面,走人工合并只是因为体量:+591 / −7447,其中 7272 行是被删除的生成文件 spec-changes.json,超过 3000 行门槛。

@os-zhuang
os-zhuang marked this pull request as ready for review October 10, 2026 09:05
@os-zhuang
os-zhuang enabled auto-merge October 10, 2026 09:05
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 10, 2026
Merged via the queue into main with commit 46064df Oct 10, 2026
45 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-22485-delete-committed-projections branch October 10, 2026 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests tooling

Projects

None yet

3 participants