Skip to content

fix(metadata-core): the protocol refusal prescribes no migrate meta --from below the migration floor - #33466

Draft
objectstack-fleet[bot] wants to merge 4 commits into
mainfrom
claude/area-A5-33031
Draft

objectstack-fleet[bot] wants to merge 4 commits into
mainfrom
claude/area-A5-33031

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #33031

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:包声明的协议主版本低于迁移链下限(16)时,安装拒绝(422 OS_PROTOCOL_INCOMPATIBLE)不再开出 objectstack migrate meta --from N 这条注定被 CLI 拒绝的命令,改为说明两条真正可行的补救:装一个针对本运行时协议的包构建,或先经其他途径升到 16 再 --from 16。
  • 为什么改:作者照着 422 提示去跑命令,CLI 立刻回"链的支持下限是 16",诊断自相矛盾;版本高于运行时的分支早已这样处理,本次让低于下限的分支对齐。
  • 风险与回滚:只动诊断:低于 16 时 details.migrateCommand 缺省、文案换成补救说明;状态码、错误码、准入判定和 16 及以上的提示一字不变。回滚即还原这一个提交。已知一处他包测试(packages/runtime/src/app-plugin.test.ts)断言了 --from 13,正是本缺陷的旧行为,需随本 PR 改一行断言(超出派发文件面,已上报席位)。
  • 先看:① packages/metadata-core/src/protocol-handshake.ts 的 belowFloor 分支与文件顶部 MIGRATE_META_SUPPORT_FLOOR 常量(为何不直接 import 见下文);② 同目录 protocol-handshake.test.ts 末尾新增的 describe 与被改写的旧 fixture;③ changeset。
  • 两分钟验证:pnpm --filter @objectstack/metadata-core exec vitest run src/protocol-handshake.test.ts,预期 56 个测试全部通过。
  • 需要你做的:无,席位落地。

What changes

checkProtocolCompat (packages/metadata-core/src/protocol-handshake.ts) built migrateCommand = objectstack migrate meta --from TARGET for every declared major that is not ahead of the runtime, with no floor check. The CLI's chain refuses every --from N under MIGRATION_SUPPORT_FLOOR (16, ADR-0087 D3; MigrationFloorError), so a manifest declaring engines.protocol: '^15' or '^0' was told to run a command known to fail.

Below the floor the refusal now mirrors the AHEAD arm (#26480): no migrateCommand member (so no details.migrateCommand on the HTTP answer), and the message names the remedies that apply. Built output on an 18 runtime:

^15 -> migrateCommand: (absent)
  ... This is a major-version break. The package is built for protocol 15, older than the oldest protocol objectstack migrate meta replays from (16, ADR-0087 D3). Install a build of 'com.example.qa-floor' for protocol 18, or bring its metadata to protocol 16 by another path first and then migrate it with objectstack migrate meta --from 16
^0  -> same shape, "built for protocol 0"
^16 -> migrateCommand: objectstack migrate meta --from 16   (unchanged, ends "Run: objectstack migrate meta --from 16")
^17 -> migrateCommand: objectstack migrate meta --from 17   (unchanged)

Choices made on the dispatch's axes (author-facing correctness, then smallest public surface, then effort):

  • Omitted, not replaced with --from 16. --from 16 on protocol-15 metadata runs: it starts at the floor, never replays the hops below it, and --write still moves the declared range (planProtocolRange in the CLI presumes the floor was reached another way). As a machine-actionable migrateCommand it would hand an agent a run that reports success over metadata it never migrated. The prose keeps the conditional path for a human.
  • The details shape does not change. migrateCommand was already optional on ProtocolIncompatibleDiagnostic and ProtocolIncompatibleAnswer.details, and already absent on the AHEAD arm; no member is added or dropped, only the set of cases where the optional one is present shrinks. Status 422 and code OS_PROTOCOL_INCOMPATIBLE are unchanged. Hence Clause-②: no; the open question is recorded in the os-dev report.
  • The floor is spelled locally (MIGRATE_META_SUPPORT_FLOOR = 16), not imported. MIGRATION_SUPPORT_FLOOR lives only on @objectstack/spec/migrations, whose built entry is about 2.9 MB (the migration registry, split off the spec root for exactly this reason) and measured at +11 MB heap on load. metadata-core is reached by every install door and by browser bundles (@object-ui/app-shell), so importing it to read one number is the wrong trade. The test file imports the spec's constant and sweeps majors 0 to floor+2, so a release-policy ruling that moves the spec floor reds this package until the copy follows (ablation below).

Tests

  • New describe('[#33031] ...') in protocol-handshake.test.ts: below the floor (^15, ^0, a union whose lowest alternative is 15) carries no migrateCommand, never offers --from N or a Run: line, and names the build for protocol 18 and the floor; AT the floor ^16 keeps --from 16; CONTROL ^17 keeps --from 17 and an undeterminable target keeps the bare command; the HTTP answer for ^15 carries the four applicable members and still declares 422; a sweep pins the local floor equal to the spec's MIGRATION_SUPPORT_FLOOR.
  • Fixture triage: existing pins that prescribed --from under the floor (--from 10, --from 6 on an 11 runtime; >=15 <18 on 18) pinned the defect itself. They are re-spelled onto an above-floor runtime (RT_ABOVE_FLOOR = '18.0.0', or 21 for the one-or-five-behind shape test) so each keeps asserting what it was written for.

Acceptance notes

  • Known red outside this dispatch's file surface: packages/runtime/src/app-plugin.test.ts ("refuses an incompatible engines.protocol range BEFORE registering the manifest") declares ^(PROTOCOL_MAJOR - 5) = ^13 and asserts migrateCommand: objectstack migrate meta --from 13, the very prescription this card removes. Measured red on this branch (1 failed, 83 passed across the three runtime protocol suites). The one-line fix is to assert targetMajor: PROTOCOL_MAJOR - 5 instead (the seam's point is that the diagnostic arrives before registration; which remedy it names is metadata-core's pin). Not edited here: the file surface is metadata-core plus one changeset, and that edit is the seat's to authorize.
  • The platform checklist clause for platform-core.manifest-install-contract (docs/qa/platform-checklist/areas/platform-core.json, acceptance[1]) still says every target at or below the runtime carries a migrateCommand; it now holds only from the floor up. Checklist accuracy follow-up, not edited here.
  • MIGRATE_META_SUPPORT_FLOOR is a second spelling of the spec's number, held by a test rather than an import. A lighter spec export of the floor (for example beside PROTOCOL_VERSION on @objectstack/spec/kernel) would remove the copy; that is a spec public-surface addition and out of this card's surface.

Verification

metadata-core: tests, typecheck, lint (head 16f58bf)
pnpm --filter @objectstack/metadata-core test       Test Files 27 passed (27) · Tests 1031 passed (1031)
pnpm --filter @objectstack/metadata-core typecheck  exit 0 (tsc --noEmit && tsc --noEmit -p tsconfig.test.json; --listFiles counts protocol-handshake.test.ts once)
npx eslint --no-inline-config (both changed files)  exit 0
Dependents that consume the diagnostic (head 16f58bf)
runtime: app-plugin, packages-install-protocol-incompatible, artifact-reference   1 failed | 83 passed (the --from 13 pin above)
metadata-protocol: protocol-handshake-install                                       4 passed
cloud-connection: marketplace-install-local-protocol-handshake                     26 passed
metadata: plugin-artifact-forward-conversion                                        7 passed

The full affected set (108 packages by select-shard-packages.sh) is Test Core's in CI.

Ablations (one-off, committed fix first, restored by scripts/ablation-replace.mjs)
belowFloor forced false   -> 5 failed | 51 passed (every below-floor pin red; at-floor and in-range controls green); restored, blob == HEAD 2cdd168b50c6, git diff HEAD empty
local floor 16 -> 17      -> 8 failed | 48 passed (the spec-equality sweep and the at-floor pin red); restored, blob == HEAD, git diff HEAD empty

The derived gate families (dispatch-gates.mjs --commands, 65 commands) and their readings are in the os-dev report comment on #33031.

🤖 Generated with Claude Code

…in the protocol refusal

OS_PROTOCOL_INCOMPATIBLE prescribed `objectstack migrate meta --from N` for
every declared major behind the runtime, a command the chain refuses for
every N under MIGRATION_SUPPORT_FLOOR (ADR-0087 D3). Below the floor the
refusal now carries no migrateCommand and names the remedies that apply,
as the AHEAD arm does. Fixtures that pinned a --from under the floor are
re-spelled onto an above-floor runtime; new pins cover 15, 0, the floor
(16), an in-range major and the HTTP answer.

Claude-Session: https://claude.ai/code/session_016WV4cxHwXNcGcAHNerZsom
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 14, 2026
@github-actions

github-actions Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-core, touching 3 documentable anchor(s).

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

  • content/docs/releases/v12.mdx (via checkProtocolCompat (symbol, a top-level function))

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 65 of 247 client-bound route-ledger rows — the other 182 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 182: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 59 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 123 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.
  • 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 — 5 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 f068cde84d272d25c451003996f3752bd016001b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 040158c750c448a14fd82b39724661641d9a6837 — the merge of head 1faa38a990d9f2538af62cecd3b7f41a262c048e into base f068cde84d272d25c451003996f3752bd016001b, 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 040158c750c448a14fd82b39724661641d9a6837 && git checkout 040158c750c448a14fd82b39724661641d9a6837
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f068cde84d272d25c451003996f3752bd016001b 1faa38a990d9f2538af62cecd3b7f41a262c048e && git checkout -B drift-repro f068cde84d272d25c451003996f3752bd016001b && git merge --no-ff 1faa38a990d9f2538af62cecd3b7f41a262c048e

node scripts/docs-audit/affected-docs.mjs --json f068cde84d272d25c451003996f3752bd016001b

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

…a below-floor migrateCommand (#33031)

A target below the migrate-meta support floor no longer carries a
migrateCommand, so the AppPlugin init pin asserts targetMajor instead.

Claude-Session: https://claude.ai/code/session_016WV4cxHwXNcGcAHNerZsom
Co-authored-by: Claude <noreply@anthropic.com>
… collection (#33031)

check:test-source-alias judges edited test files: app-plugin.ts loads
@objectstack/metadata lazily, so the first load belongs at module top,
not inside a clocked case.

Claude-Session: https://claude.ai/code/session_016WV4cxHwXNcGcAHNerZsom
Co-authored-by: Claude <noreply@anthropic.com>
…oor major, which still names a migrate command (#33031)

Below the migrate-meta support floor the refusal carries no
migrateCommand, so the pin moves its stale range from ^10 to ^16.

Claude-Session: https://claude.ai/code/session_016WV4cxHwXNcGcAHNerZsom
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-elon-musk · session_016WV4cxHwXNcGcAHNerZsom

Re-run · area A5 · head 1faa38a · 2026-11-14T23:15Z

Test Core (5/8) went red on plugin-approvals src/action-page-member.integration.test.ts (Test timed out in 5000ms). This PR does not reach plugin-approvals (metadata-core handshake + three test pins). Same signature as the queue-flake anchor #33508 (also popped #33439, #33452, #33463). Head unchanged; failed jobs re-run once.

This branch has not been deployed

No deployments
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/m tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OS_PROTOCOL_INCOMPATIBLE prescribes objectstack migrate meta --from N for a declared major below the migration floor, a command the CLI then refuses

2 participants