Skip to content

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

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-15585-protocol-version-key-rename
Sep 10, 2026
Merged

feat(cli,metadata-core)!: emit the protocol version under protocolVersion, not a runtime-shaped name#17261
os-project-manager merged 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

Copy link
Copy Markdown
Collaborator

ACCEPT — 车道免除契约复审后解除停靠,已撤 draft 并武装入队

派发席(domain:cli 执行 PM,座位帖 #6024)。head 255c274321d3fb69ea46779f6a894c9ac92fc580,⛔ 先撤 draft 再武装,顺序如常。武装于 06:50:50Z。

解除停靠的依据,以及它带来的代价

本 PR 正文写着「⚠️ This PR is expected to be parked outside the merge queue」,理由是 Clause-②: yes 需要席内 CONTRACT_REVIEW_TIER PASS。该要求已由维护者对 domain:cli 车道整体免除(记录在 #16950 5613863544,附维护者原话)。⛔ 放宽闸门是维护者地板上的动作,不是本席的判断。

⚠️ 代价是交付席专门留给复审档的两点,最终无人复核。 它们是交付席自己申报的,不是被发现的问题 —— 但它们本来预期有第二双眼睛,现在没有了,所以在此点名:

  1. 发射点的 pin 属 nightly 档。 test/migrate-meta.e2e.test.ts 按文件名归入 nightly(*.e2e.test.* 只在 OS_TEST_TIERS=nightly 下被选中),⇒ ⛔ 它不在合并队列的测试 population 内。裁定第 2 项的意图是「将来静默改名要红」,而它是在 nightly 兑现,不是在队列。handshake 那个 pin 是普通单测,确实在队列里跑。
  2. runtimeMajor 保留未动。 交付席的理由:裁定精确点名的是 runtimeVersion,而 runtimeMajor整数 major,不是「版本位上的填充 semver」—— 正是上一轮普查放过其他站点时用的同一条判据,所以它不是本卡要关的那个歧义。⚠️ 记录在此是因为改了它的同胞而留下它,读起来会略不齐。

围栏,交付时逐条验过

围栏 读数
PROTOCOL_VERSION 本身 ⛔ 未动 —— 移动的只是键名
别名 / 双键窗口 ⛔ 无。runtime 直接移除,裁定的 option B
protocol-handshake.ts ✅ 同一个 PR 内一起改,⛔ 未拆
发射点 pin 重新指向而非删除,且同时断言旧拼写缺席Object.keys(parsed) 而非真值判断 —— 一个显式的 runtime: undefined 会骗过后者,却仍会被 JSON.stringify 省略)

CI:43 个 check run 全部读完。

⚠️ 唯一的红,以及它为什么不挡队列

Part-of PR must not also close its card failure(同一 head 上跑了两次:102708097349 · 102707228320)。⛔ 这条按设计无法清除:commit 255c27432 的提交信息带 Fixes #15585,而分支已推送 —— 闸门自己的文本说得很直白:

BRANCH ALREADY PUSHED — no author action clears this red, and that is expected rather than a problem to solve.

唯一能抹掉它的手段是被本仓禁止的历史改写。⛔ 未 amend、未 rebase、未 force-push、未压空提交。它 ⛔ 不在 required-context 名单里,其工作流也不订阅 merge_group(队列构建没有 PR 正文可判),⇒ 不参与合并决策

其余 41 项全绿或 skipped:Lint & Repo Gates success(01:39:51Z)、Governed Surface Queue GuardCheck ChangesetTest Core 全量、TypeScript Type CheckTemporal ConformanceBuild CoreDogfood 全部 success。

⚠️ 一条会进永久历史的残渣

因为那个 trailer 带的是闭合关键字Fixes)而不是 Refs / Part of,它落在 GitHub 解析器会读的那个面上;而队列合并不改写 squash 信息。⇒ Fixes #15585 这一行会进入 main 的永久历史。闸门文本给的唯一规避是落地者在合并按钮上手动把提交列表换成 PR 正文 —— 队列不做这件事。记录在此,⛔ 不为它做被禁止的改写。

#15585 由正文的 Fixes 关闭,⛔ 无需手动收口 —— 合并后本席确认它确实关了。


Generated by Claude Code

@os-project-manager
os-project-manager added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit cca1dc0 Sep 10, 2026
43 of 45 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-15585-protocol-version-key-rename branch September 10, 2026 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

3 participants