Skip to content

feat(cli,metadata-core)!: emit the protocol version under protocolVersion, not a runtime-shaped name - #17261

Draft
claude[bot] wants to merge 1 commit into
mainfrom
claude/issue-15585-protocol-version-key-rename
Draft

feat(cli,metadata-core)!: emit the protocol version under protocolVersion, not a runtime-shaped name#17261
claude[bot] wants to merge 1 commit into
mainfrom
claude/issue-15585-protocol-version-key-rename

Conversation

@claude

@claude claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #15585

Clause-②: yes

Executes the 2026-09-06 ruling (director seat, maintainer batch #53, verbatim 「以上同意」) — option B, both faces in one PR. PR #16058 landed the human line on 2026-09-06; this is the machine half the card calls the worse one.

This PR is expected to PARK: draft, needs:contract-review, not flipped ready, not armed. The ruling's own landing constraint says construction may proceed at judgment tier but 「⛔ review does not take that exemption」.

What changed

Surface Was Now
os migrate meta --json payload key runtime protocolVersion
OS_PROTOCOL_INCOMPATIBLE diagnostic member runtimeVersion protocolVersion
checkProtocolCompat() / assertProtocolCompat() 2nd parameter runtimeVersion protocolVersion

Removed outright — no alias, no dual-key window (governing axis quoted in the ruling: 创业阶段不渐进, maintainer 2026-08-27 — 「项目在创业阶段,用户也很少,短期不考虑渐进。」).

Four files plus a changeset. packages/spec/src/kernel/protocol-version.ts is not touched: PROTOCOL_VERSION is correct as a protocol version — the defect was always the name it was printed under.

The pin was RE-POINTED, never deleted (ruling item 2). The e2e case that used to assert runtime unchanged now asserts both halves: the new key carries the value and the old spelling is absent. Its docblock records why both are needed — a pin holding only the new key stays green if the old spelling is quietly re-added alongside, which is exactly the dual-key state this rename was ruled against.

runtimeMajor on the same diagnostic is deliberately unchanged — an integer major in a protocol-major position, which is the same criterion the previous round's sweep used to clear migrate/meta.ts's major-valued sites. Flagged here rather than acted on, so review can rule if it disagrees.

Evidence — the command was driven, not reasoned about

Real CLI through bin/run-dev.js, NO_COLOR=1, same temp stack before and after.

BEFORE keys: ["from","to","runtime","applied","todos","specChanges","schemaValid","dataMigrations","duration"]
AFTER  keys: ["from","to","protocolVersion","applied","todos","specChanges","schemaValid","dataMigrations","duration"]
removed: [ 'runtime' ]          added: [ 'protocolVersion' ]
before.runtime = "17.0.0"       after.protocolVersion = "17.0.0"   after.runtime = undefined
all OTHER keys byte-identical (duration excluded): true
key ORDER preserved at the renamed position: true
CONTROL - a deliberately wrong comparison must be false: false

Exactly one key renamed; everything else identical. The previous round proved its payload byte-identical across its change — this one shows the opposite, by construction.

Ablation — every direction predicted in writing before any leg ran

All three legs matched their written prediction; none is being reported as a surprise. Each mutation was proven on disk by occurrence counts and a blob hash off the HEAD blob; each restore by git checkout HEAD -- ABSOLUTE_PATH (never bare), blob equal to HEAD's, empty git diff HEAD and empty git status --porcelain; every script under trap ... EXIT INT TERM with absolute paths.

Leg A — the decisive one. Restore runtime at the emit site and show the re-pointed pin reddens.
Predicted RED on the pin case only, GREEN on the three human-line cases — a discrimination, not a blanket red.
Observed: Tests 1 failed | 18 passed (19), the single failure being the re-pointed case with AssertionError: expected undefined to be '17.0.0'. The three human-line cases stayed green. The pin followed the key rather than being deleted — a deleted pin would have left this mutation entirely green.

Leg B — the handshake face. Emit the diagnostic member under the old spelling, leaving the TS declaration on the new one, so the red must be the runtime read (vitest strips types).
Predicted RED at runtime, everything else green. Observed: Tests 1 failed | 19 passed (20), AssertionError: expected undefined to be '11.0.0'.

Leg C — cross-package reverse verification. @objectstack/metadata-core's published type face moved, so consumers owe a check, and that check is only trustworthy if it reads the rebuilt declaration file. Pasted a key the new type must reject into a real downstream consumer.
Predicted RED naming runtimeVersion. Observed: error TS2339: Property 'runtimeVersion' does not exist on type 'ProtocolIncompatibleDiagnostic'. — so the consumer sweep below read the rebuilt output, not a cache.

Verification

  • Suites@objectstack/metadata-core src/protocol-handshake.test.ts: 20 passed. @objectstack/cli test/migrate-meta.e2e.test.ts: 19 passed.
  • Typecheck@objectstack/cli and @objectstack/metadata-core both green.
  • Consumer sweep, direction stated: DOWNSTREAM (dependents). @objectstack/runtime, @objectstack/metadata-protocol, @objectstack/service-package all typecheck green after @objectstack/metadata-core was rebuilt. Every call site is positional and the diagnostic is passed on by spread, so no consumer names the renamed member. Leg C is what makes this green a measurement.
  • Gate family — derived on the final tree with dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 62 families. All 62 run, all green. Five first returned exit 3 PREREQUISITE NOT MET and were not recorded as passes (check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity, check:type-check-debt — the last OOM-killed at 137 inside its own build on a shared box); after a full pnpm build (73/73 tasks) all five re-ran green. Reconciliation with --ran: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN.
  • Repo-wide lint — the full run, not a narrowing: eslint . --no-inline-config --format json, 6448 files, 0 errors, 0 warnings, exit 0. All four changed files are in that population.
  • Every exit code captured by redirect-then-dollar-question, never through a pipe.

Measurements taken at the final commit; no commit followed the gate union.

Single-writer

packages/cli/src/commands/migrate/meta.ts, packages/cli/test/migrate-meta.e2e.test.ts and packages/metadata-core/src/protocol-handshake.ts are touched by no other open PR. Established by reading each PR's own file list rather than its title — packages/metadata-core is a different package from packages/metadata, and the three PRs carrying metadata in their titles (#17073, #17198, #17194, plus #17223 on the CLI) touch packages/metadata and packages/metadata-protocol only. Zero hits on all four, with a control that fires on each list. git ls-remote shows one branch naming this card: this one.

Docs

No page names either renamed key. content/docs is covered by check-affected-docs.mjs (green); docs/ was swept by hand on the final head, since a renamed --json key is stated by its output — the emitter-only blind spot. The published skills/objectstack-upgrade/SKILL.md documents --json by way of .specChanges and .todos and never names this field. Both zero readings carry controls that fire over the identical pathspec. content/docs/releases/** is untouched.

Changeset

@objectstack/cli minor and @objectstack/metadata-core minor, a **BREAKING** banner, and an ADR-0087 disposition marker committed with the change (the gate reads changesets from git revs, not from disk). check-adr-0087-registration judged it rather than skipping it: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition. check-changeset-no-major green.

Acceptance notes — noted, not filed

  • Where the re-pointed pin actually fires. test/migrate-meta.e2e.test.ts is nightly-tier by name — *.e2e.test.* and *.live.test.* are selected only under OS_TEST_TIERS=nightly, so the pin runs in the nightly lane and not in the merge queue's population. That is a deliberate, documented platform decision covering every .e2e file, not a defect and not this card's business; recorded so nobody reads "pin re-pointed" as "the queue will catch it". The handshake pin is an ordinary unit test and does run in the queue.
  • runtimeMajor on the diagnostic, discussed above: left alone as an integer major, flagged for review rather than acted on.
  • docs/qa/platform-checklist/areas/platform-core.json describes the refusal as carrying "the runtime version it was judged against" — prose about the value's role, not a field name, and still accurate. No edit.
  • No card was filed from this run: nothing met the reproducible-defect, contract-violation or authoring-trap bar.

Known advisory red — Part-of PR must not also close its card

This red is a READ, not a repair. ⛔ Do not amend, rebase or force-push this branch to clear it, and ⛔ do not push an empty commit — that joins the commit list too and changes nothing.

The finding is real and correct: commit 255c27432's message carries Fixes #15585, and the contract is that the card relation is declared once, in the PR body — a commit carries no card trailer. The trailer was removed from the message too late; the branch was already pushed.

The gate names its own remedy and it turns on that one fact. Its words:

BRANCH ALREADY PUSHED — no author action clears this red, and that is expected rather than a problem to solve. This gate reads the PR's COMMIT LIST, so a new commit on top JOINS that list and leaves the message above in it; the only thing that would remove it is the rewrite forbidden above.

Three facts the gate supplies for reading it, each re-verified here against the workflow and the script rather than taken from the log:

  1. Advisory at the branch-protection layer. partof-closing-keyword-guard.yml subscribes to pull_request only and to no merge_group event — a queue build carries no PR body to judge — which is what keeps it out of the required-context registry.
  2. The squash message is assembled from commit messages, not from the body. This repository reads squash_merge_commit_message: COMMIT_MESSAGES (squash is the only merge method enabled).
  3. The card relation is safe either way — the PR body's keyword is what actually closes the card.

What the body declares, stated plainly for the lander: the body says Fixes #15585, the same direction as the commit. So the relation is stated twice and the body's copy is the one that acts — body and commit do not contradict each other. This is the gate's rule about a commit carrying a card trailer at all, not the Part of-body-versus-closing-commit contradiction.

⚠️ Residue for whoever lands this. Because the trailer is a closing keyword rather than Refs or Part of, it lands on the surface GitHub's parser reads. At COMMIT_MESSAGES the Fixes #15585 trailer reaches permanent history unless the lander replaces the assembled commit list with the PR body by hand at the merge button; a queue merge edits nothing, so there the residue lands. Flagged for the PM to weigh at landing — it is not discharged by anything an author can do from here.

Landing

⛔ Do not flip ready. ⛔ Do not arm auto-merge. needs:contract-review is hung on the PR; it releases when an at-tier contract review exists. #16056 is not addressed here and remains open — it unblocks when this card closes, and a code PR may never touch content/docs/releases/.

Authored in session session_015QE8qk46e5CHJxyQEUjbf8.


Generated by Claude Code

…rsion`, not a runtime-shaped name

`PROTOCOL_VERSION` is the protocol major padded to a semver ('17.0.0') and never
tracks the installed package version. Emitted under the key `runtime`, a machine
consumer read it as the runtime's own version with no prose to disambiguate --
the half of #15585 that the human-line repair (#16058) could not reach.

- `os migrate meta --json` emits `protocolVersion`; `runtime` is removed
  outright, with no alias and no dual-key window.
- `packages/metadata-core/src/protocol-handshake.ts` moves the same class of
  field in the same change: the `checkProtocolCompat` / `assertProtocolCompat`
  parameter and the `OS_PROTOCOL_INCOMPATIBLE` diagnostic member rename off
  `runtimeVersion` to the protocol spelling. `runtimeMajor` is deliberately
  unchanged -- an integer major carries no version-position ambiguity.
- `PROTOCOL_VERSION` itself does not move; it is correct as a protocol version.
- The existing e2e pin at the emit site is RE-POINTED at the new key rather
  than deleted, and now asserts both halves: the new key carries the value AND
  the old spelling is absent.

Fixes #15585

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/metadata-core, touching 7 documentable anchor(s).

12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/connect-mcp.mdx (via protocolVersion (symbol, a field of interface ProtocolIncompatibleDiagnostic; a field of type ProtocolCompatResult))
  • content/docs/automation/flows.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/automation/hook-bodies.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/data-modeling/fields.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/data-modeling/objects.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/data-modeling/queries.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/deployment/cli.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/deployment/index.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/protocol/objectql/query-syntax.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/protocol/objectui/actions.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/ui/apps.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/upgrading.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))

4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx (via assertProtocolCompat (symbol, a top-level function), checkProtocolCompat (symbol, a top-level function), os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/releases/v17/17-0.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/releases/v17/17-1.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))
  • content/docs/releases/v17/17-3.mdx (via os migrate meta (command, read off packages/cli/src/commands/migrate/meta.ts))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 — 26 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 470746ae4511db2f7c754d8aa6c247c3ce2120c4packageMentionDocs.

Which tree this was computed on

This run read content/docs from 161fb324c072cc572fd512bf6e6e376fef4e3015 — the merge of head 255c274321d3fb69ea46779f6a894c9ac92fc580 into base 470746ae4511db2f7c754d8aa6c247c3ce2120c4, 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 161fb324c072cc572fd512bf6e6e376fef4e3015 && git checkout 161fb324c072cc572fd512bf6e6e376fef4e3015
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 470746ae4511db2f7c754d8aa6c247c3ce2120c4 255c274321d3fb69ea46779f6a894c9ac92fc580 && git checkout -B drift-repro 470746ae4511db2f7c754d8aa6c247c3ce2120c4 && git merge --no-ff 255c274321d3fb69ea46779f6a894c9ac92fc580

node scripts/docs-audit/affected-docs.mjs --json 470746ae4511db2f7c754d8aa6c247c3ce2120c4

⚠️ 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 470746ae4511db2f7c754d8aa6c247c3ce2120c4 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator

Tier notice — the contract-review-tier requirement on this PR is lifted (skills seat, session session_01MoTv7pn338AZ71owsp19gQ, 2026-09-10T03:13Z; record and rule-text change in flight: #17285).

Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on #17285), the contract-review tier is reserved for the skills seat (protocol files + the published skills/**), the spec seat's clause-② review, and the maintainer-summoned director; triage and every other seat run the default tier.

For this PR: its Clause-②: yes gate no longer calls for a contract-review-tier review; its files do not touch packages/spec (measured 2026-09-10T03:13Z). The lane seat's own default-tier review, plus the gates (widening tells, pin tests, dispatch-gates --tier), is the review of record, and the build stays at the default tier. Unchanged: the Clause-② declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touching packages/spec goes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants