Skip to content

feat(cli)!: rename the --json payload key specVersionGap to protocolVersionGap - #17240

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-14261-protocol-version-gap-rename
Sep 9, 2026
Merged

feat(cli)!: rename the --json payload key specVersionGap to protocolVersionGap#17240
os-project-manager merged 2 commits into
mainfrom
claude/issue-14261-protocol-version-gap-rename

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #14261

Clause-②: no

Renames the published --json payload key specVersionGap to protocolVersionGap in one
stroke — no alias, no dual-key window, value shape unchanged.

Why this is a correctness change, not a tidy-up

Because ManifestSchema is not .strict() and silently drops unknown keys (#14192), a
reader who infers a writable manifest.specVersion from the key's old name does not get an
error — they get 「一份看起来正常、而那一行从未生效的 manifest」. The output-side breadcrumb
is the same ghost-key mechanism that caused the parent card #13860; this removes it.

Which option was ruled, and by whom (history, not a question)

Maintainer, 2026-09-02, comment 5507416523, verbatim:

14361 你不处理;14261 现在就改,定位为 minor;其他同意

Option B, now, graded minor. Option C (dual keys for a transition window) stays refused,
consistent with the 2026-08-27 ruling 「项目在创业阶段…短期不考虑渐进」.

The occurrence set, re-derived rather than inherited

specVersionGap on origin/main at bccf311100, measured with a control (protocolGap,
which fires 5/4/5 in compile.ts / doctor.ts / validate.ts):

site disposition
packages/cli/src/commands/compile.ts — the os build --json emit renamed
packages/cli/src/commands/validate.ts — the os validate --json emit, plus its --strict scope comment renamed
packages/cli/test/build-json-advisory-parity.e2e.test.ts pin moved to the new name
packages/cli/test/build-json-undeclared-key-parity.e2e.test.ts pin moved to the new name
packages/cli/test/validate-json-warning-parity.e2e.test.ts — header prose, payload type member, assertion moved
scripts/pm/check-half-states.mjs:22779 ⛔ untouched — row H54's anchor fixture is about a card TITLE, not a consumer of the key
packages/cli/CHANGELOG.md (3 rows) ⛔ untouched — published release history records what shipped under the old name

os doctor is NOT a third emitter, measured. The parent card touched three commands, so
this was checked rather than assumed: doctor.ts calls checkProtocolVersionGap() and prints
a human-readable line; it has no --json face at all. Two emitters, not three.

There is no pending changeset naming the old key — the one the 2026-09-02 ruling asked to move
has since been released into packages/cli/CHANGELOG.md, which is history and stays as written.

⚠️ Per-PR CI does not run the pins that protect this rename

All three suites carry *.e2e.test.*, which test-nightly-tiers.yml moved to the nightly
tier (#16455). ci.yml's Test Core and the merge queue run under OS_TEST_TIERS=queue, which
excludes them. So a reviewer reading a green merge queue has not seen these pins pass. They
were driven by hand here, under OS_TEST_TIERS=nightly:

OS_TEST_TIERS=nightly pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 \
  test/build-json-advisory-parity.e2e.test.ts \
  test/build-json-undeclared-key-parity.e2e.test.ts \
  test/validate-json-warning-parity.e2e.test.ts

Test Files 3 passed (3) · Tests 17 passed (17) · lock VERDICT command-exit 0.

Ablation — prediction recorded BEFORE any leg ran

For a rename the interesting leg is the one proving the pins bind the new name. Predicted,
in writing, before the first mutation: restoring specVersionGap at the two emit sites reddens
all three suites by three distinct mechanisms — exact key-set equality in the two build-parity
suites, and expect(payload.protocolVersionGap).toBeNull() reading undefined in the third
(toBeNull() rejects undefined, which is what makes that pin non-vacuous).

Observed: exactly that, all three mechanisms. Test Files 3 failed (3) · Tests 4 failed | 13 passed:

- "protocolVersionGap",
+ "specVersionGap",              (both build-parity suites, key-set equality)
AssertionError: fixture must raise no protocol-version gap: expected undefined to be null

On-disk proof of the mutation, and of the restore, under trap ... EXIT INT TERM with absolute
paths:

compile.ts validate.ts
HEAD blob bbb743b63ef26f27b74c09976993caf838424dc1 057339febb0da7c707ae442ec0acf64acd54524d
mutated blob (differs ⇒ reached disk) b4fca212193e5e0fa8cfe895d33bc3b0643f6ce2 125c64e99ba4b9074e7cd92b9e511ec69a53fcfe
restored blob bbb743b6… — equal to HEAD 057339fe… — equal to HEAD

Anchor counts moved 1→0 (new name) and 0→1 (old name) per file and back. Restore was
git checkout HEAD -- ABSPATH, never bare; git diff HEAD empty, git status --porcelain empty.
No dist leg: these suites spawn bin/run-dev.js under tsx, which reads src/.

Verification

Every exit code captured by redirect-then-$?, never through a pipe.

what result
dependency closure — pnpm --filter '@objectstack/cli^...' build --concurrency=2 lock VERDICT command-exit 0
pnpm --filter @objectstack/cli build VERDICT command-exit 0
pnpm --filter @objectstack/cli typecheck VERDICT command-exit 0 — and check:test-typecheck compiles the test layer, with all three edited e2e files proven in the program by tsc -p tsconfig.test.json --listFiles
vitest run --project unit 190 files / 2606 tests pass
nightly-tier e2e (the three pins) 3 files / 17 tests pass
pnpm lint (eslint . --no-inline-config) exit 0 — not a narrowing: the full repo-wide run, 6442 files by eslint's own config, 0 errors / 0 warnings, on 141f5faf66
derived gate family — dispatch-gates.mjs --commands --repo objectstack-ai/objectstack 60 derived, reconciled with --ran: 60 run, 0 NOT-MEASURED, 0 UNRUN
the 5 artifact-roster gates whose roster sits under a path in this diff all exit 0

Two of the 60 returned exit 3 = PREREQUISITE NOT MET, which is ⛔ not a pass and ⛔ not a
finding — check:dual-build-cjs-loads and check:i18n-coverage both need a full-repo pnpm build
(they name absent dist/ in studio, client-react, the connectors and several plugins — none of
them a surface this diff touches). Declared as NOT MEASURED locally and left to CI, which builds
the repo fresh.

check-adr-0087-registration initially reported a real problem against this diff — a
declared-breaking changeset with no ledger disposition — and is now green with
not-required (no-migration-prescription): the renamed member is an output key on an inline
object literal, with no Zod schema, no packages/spec declaration and no stored representation,
so objectstack migrate meta has nothing to reach.

Docs-drift round, on the final head

check-affected-docs and check-drift-comment both exit 0. Because a published --json key is
the kind of thing a page states by its output, this was also swept by hand rather than left to
the emitter-only tool: no page under content/docs/ or docs/ prints or enumerates either
payload's key set, and the prose-class sweep over pages naming os validate --json /
os build --json found only flag lists. docs/ is outside the tool's walk, so it was grepped
directly. No release page is wrong, so there is nothing to hand back as a docs-only PR.

Single-writer

Measured from the open PR list (26 open PRs, 359 (PR, file) rows), each PR against its own
merge base via pulls/{n}/files, never remote branches. None of the five faces is held by any
open PR. Control fired: packages/cli/test/i18n-walk-output-parity.test.ts matched on PR #17223,
so the exact-path matcher was live.

⚠️ The claim-time control named at 5607961637packages/cli/src/commands/generate.ts held by
#17230 — did not fire on re-measure. Reason established rather than assumed: #17230 merged at
2026-09-09T20:45:29Z
, during this run. Reported as observed; a replacement control was used.

验收备注

  • packages/cli/CHANGELOG.md carries three historical rows naming specVersionGap. Left exactly
    as written — they record what shipped under that name and rewriting release history would make
    them false. Not a finding.
  • scripts/pm/check-half-states.mjs:22779 uses this card's own title as row H54's anchor fixture.
    Deliberately untouched: it is a fixture about a title string, not a consumer of the key.
  • The source comments at both emit sites previously asserted that the key keeps its published name
    because renaming it is "a break nobody asked for". They are rewritten, not deleted — a stale
    comment asserting the opposite of the code is worse than none.

Generated by Claude Code

…ersionGap

The advisory's axis moved to `manifest.engines.protocol` in #13860; the
published key name lagged one release behind it. A key spelled
`specVersion*` invites the inference that a writable `manifest.specVersion`
exists, and because `ManifestSchema` is not `.strict()` and drops unknown
keys with nothing said (#14192), acting on that inference yields a manifest
that looks normal and whose line never took effect.

One stroke, no alias, no dual-key window. Value shape unchanged. The three
in-repo e2e suites that pinned the old key move with it; zero external
consumers were measured.

Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
Co-authored-by: Claude <noreply@anthropic.com>
…y rename

`check-adr-0087-registration` requires a declared-breaking changeset to state
its ledger disposition in writing. The renamed member is a CLI `--json` output
key emitted from an inline object literal: no Zod schema, no `packages/spec`
declaration, no stored representation, so `objectstack migrate meta` has
nothing to reach.

Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/s label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 1 documentable anchor(s).

17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 559e531a94dab4938d1ab54fd6631acbc5303d61.

3 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: os validate (command, 49 pages)
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • 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.

Coarse fallback — 23 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 559e531a94dab4938d1ab54fd6631acbc5303d61packageMentionDocs.

Which tree this was computed on

This run read content/docs from c07714e5486635f908918c9d334cd5a4abd17475 — the merge of head 141f5faf66d5de0fe1b73fbd62b373189b9a6fa8 into base 559e531a94dab4938d1ab54fd6631acbc5303d61, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c07714e5486635f908918c9d334cd5a4abd17475 && git checkout c07714e5486635f908918c9d334cd5a4abd17475
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 559e531a94dab4938d1ab54fd6631acbc5303d61 141f5faf66d5de0fe1b73fbd62b373189b9a6fa8 && git checkout -B drift-repro 559e531a94dab4938d1ab54fd6631acbc5303d61 && git merge --no-ff 141f5faf66d5de0fe1b73fbd62b373189b9a6fa8

node scripts/docs-audit/affected-docs.mjs --json 559e531a94dab4938d1ab54fd6631acbc5303d61

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 559e531a94dab4938d1ab54fd6631acbc5303d61 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Decision] os validate --json / os compile --jsonspecVersionGap 键是否重命名为 protocolVersionGap

2 participants