Repository navigation
Commit 7dd5a2f
fix(metadata-core)!: a package ahead of the runtime is told to upgrade, not to run migrate meta (#26530)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X5mffbTLuxZU5VwREBF7frY)_
Fixes #26480
Clause-②: no (narrowing)
Metadata-protocol: none
## 维护者速读
- **改了什么**:安装一个协议主版本比运行时新的包(如在 18 上装 `^19`)时,`OS_PROTOCOL_INCOMPATIBLE`
拒绝不再建议 `migrate meta --from 19`,改为提示升级运行时或安装适配本主版本的包;`details` 里不再带
`migrateCommand`。
- **为什么改**:`migrate meta` 只能把旧主版本的元数据往前迁,对“包比运行时新”的情况是死路,运维会被误导。
-
**风险与回滚**:低,但按规则记为对外类型收窄(BREAKING):`ProtocolIncompatibleDiagnostic.migrateCommand`
从必有变为可选,仓外若有 TypeScript 代码直接取用它会在编译时报错。拒绝的 code 与 422 状态不变,旧主版本(如
`^17`)的提示完全不变。回滚即 revert 本 PR。
-
**先看**:`packages/metadata-core/src/protocol-handshake.ts`(`checkProtocolCompat`
里 `ahead`
分支);`packages/runtime/src/domains/packages-install-protocol-incompatible.test.ts`(HTTP
门的新用例)。
- **两分钟验证**:`pnpm --filter @objectstack/metadata-core exec vitest run
src/protocol-handshake.test.ts`,预期 37/37 通过,其中 `[#26480]` 四条。
- **需要你做的**:无,席位落地。
## What changes
`checkProtocolCompat` (`@objectstack/metadata-core`) now compares the
declared floor major (`declaredFloorMajor`) with the runtime's protocol
major before it writes the prescription:
- **Floor major above the runtime's** (the package is ahead of the
runtime): the diagnostic carries no `migrateCommand`, and the message
ends `The package is built for a newer protocol than this runtime
implements. Upgrade the runtime to protocol 19, or install a build of
'ID' for protocol 18`.
- **Floor major at or below the runtime's, or not determinable**:
unchanged, `Run: objectstack migrate meta --from N` and `migrateCommand`
as before.
`protocolIncompatibleAnswer` leaves `migrateCommand` out of `details`
when the diagnostic has none (never `undefined`), so both HTTP doors,
`POST /api/v1/packages` and `POST /api/v1/marketplace/install-local`,
answer four members for the ahead case.
`ProtocolIncompatibleDiagnostic.migrateCommand` becomes optional in the
exported type. The refusal's code and status (422
`OS_PROTOCOL_INCOMPATIBLE`) do not change. Two comments that said the
diagnostic always names `migrate meta` (`runtime/src/app-plugin.ts`, the
rehydrate line in
`cloud-connection/src/marketplace-install-local-plugin.ts`) now name
both remedies. Changeset: `@objectstack/metadata-core` patch, with the
BREAKING banner and the ADR-0087 disposition `not-required
(runtime-interface-only
packages/metadata-core/src/protocol-handshake.ts#ProtocolIncompatibleDiagnostic)`.
**BREAKING** public-surface narrowing, shipped as `patch` because it is
not a metadata-protocol change: `ProtocolIncompatibleDiagnostic` is
exported from `@objectstack/metadata-core`'s entry (`export * from
'./protocol-handshake.js'`), and its `migrateCommand` member goes from
`string` to optional. A guaranteed output member that can now be absent
narrows what the package exposes. pm-dispatch `execution-duties.md`:
"收窄已发布接受集的卡是 `Clause-②: no`,不移车道;入队前欠一次契约复审档复核。" So the declaration is
`Clause-②: no (narrowing)`, and the PR owes a contract-review pass
before it is queued.
No FROM → TO line, on purpose. AGENTS.md Post-Task Checklist step 3 asks
for one when a change is "removing or renaming anything an author can
write (a spec key, an export, a config field)"; this change removes and
renames nothing, it makes one output member optional. The disposition it
takes, `runtime-interface-only`, "ALSO inherits the
`no-migration-prescription` refusal"
(`scripts/check-adr-0087-registration.mjs` header), so a rewrite
prescription in the changeset would refuse the category. What a reader
does instead is stated in prose: treat an absent `migrateCommand` as "no
command applies; read `message`"; a TypeScript reader that dereferences
it is told by its compiler.
## Why
The card's reproduction: on a protocol-18 runtime, `POST
/api/v1/packages` with `engines.protocol: '^19'` answered
`details.migrateCommand = objectstack migrate meta --from 19` and "Run:
objectstack migrate meta --from 19". Migration only moves metadata
forward from an older major, so the operator was sent to a command that
cannot apply. Ruling: triage 6434187705.
## Tests
Red first on `main` (`00874b9e09`), then green on the fix:
| Suite | On `main` | On the fix |
|---|---|---|
| `metadata-core` `protocol-handshake.test.ts` | 3 failed, 34 passed |
37/37 |
| `runtime` `packages-install-protocol-incompatible.test.ts` (both door
arms) | 2 failed, 6 passed | 8/8 |
| `cloud-connection`
`marketplace-install-local-protocol-handshake.test.ts` (install door and
rehydrate log line) | 2 failed, 11 passed | 13/13 |
- The new `[#26480]` blocks assert the ADR-0112 envelope (`code` +
`status` 422), `details` without `migrateCommand`, no `migrate meta` in
the message, and the runtime upgrade named. The `^17`-style control
(`^10` on an 11 runtime in the unit suite, and every existing
older-major test) keeps `--from N`.
- Ablation (committed fix, `scripts/ablation-replace.mjs`): `const ahead
= targetMajor !== null && targetMajor > runtimeMajor;` replaced by
`const ahead = false;` turns the 3 unit tests red (34 still pass, the
control included); restored from the HEAD blob `de811bf3863`, `git diff
HEAD` empty afterwards.
Whole packages at `43b9bd0d9`: `metadata-core` test 827/827 + typecheck
exit 0; `runtime` 8371 passed, 19 skipped + typecheck exit 0;
`cloud-connection` 747/747 + typecheck exit 0. `eslint
--no-inline-config` on the six touched `.ts` files: exit 0.
After merging `origin/main` (`d2ca7079b`), the three targeted suites
again at `a08584332`: 37/37, 8/8, 13/13.
**Dogfood battery** (owed by the `(narrowing)` arm):
`@objectstack/dogfood^...` closure built, then `pnpm --filter
@objectstack/dogfood test` under the lock at `17bb14466`: VERDICT
command-exit 0, Test Files 300 passed, 3 skipped (303); Tests 2494
passed, 18 skipped (2512); no failures. The final head `a08584332`
differs from `17bb14466` only in the changeset's `Clause-②` line (`git
diff --stat`: 1 file, 1 line).
## Acceptance notes
- **Who reads `details.migrateCommand`.** No in-repo code outside tests
reads it. The console's not-loaded marker reads only `code` and
`requiredRange`; `os package install` prints the server's
`error.message`; the artifact boot refusal
(`runtime/src/artifact-reference.ts`) and the `service-package`
rehydrate log embed `diagnostic.message`; the `cloud-connection`
rehydrate line embeds the message and already says "install a version of
the package built for protocol N". The CLI's `checkProtocolVersionGap`
already stays silent when the declared major is at or above the
installed one. None hard-codes the old text.
- **Documented field.** The spec error-code ledger comment for
`OS_PROTOCOL_INCOMPATIBLE`
(`packages/spec/src/api/error-code-ledger.zod.ts`) lists
`migrateCommand` among the `error.details` members. That stays true for
older majors and is now incomplete for the ahead case; it is a comment
under `packages/spec`, outside this lane, so it is left as is.
- **Same-class edge not changed here.** A range whose floor major equals
the runtime's but still refuses it keeps `--from N`: on an 18 runtime,
`'>18'` (which means 19 and up, so the package is ahead) and `'<18'`
both answer `objectstack migrate meta --from 18`. Measured on the built
`dist/` of this branch. The ruling covers a floor ABOVE the runtime's
only, so it is reported to the seat rather than fixed here.
## Verification
<details><summary>Derived gate families (`dispatch-gates.mjs
--commands`, run at 17bb144 after the main merge)</summary>
62 derived, 61 run with exit 0, 1 NOT MEASURED: `pnpm
check:dual-build-cjs-loads` exit 3 (prerequisite not met, needs a full
build; CI runs it). `dispatch-gates.mjs --ran`: "62 derived famil(ies)
accounted for — 61 run, 1 NOT-MEASURED". The changeset families re-run
on the final head `a08584332`: `check-adr-0087-registration --base
origin/main` exit 0 ("1 declared-breaking changeset(s), each carrying an
ADR-0087 disposition", `runtime-interface-only` verified on
`ProtocolIncompatibleDiagnostic`); `check-changeset-no-major` with this
body as the event exit 0 (LEVEL AXIS: `no (narrowing)`, `none`,
`patch`); `check-empty-changeset` exit 0.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No `packages/spec` change.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (Scoped to the touched
files.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (Not touched.)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5kXo3UyYbZ5BSu2Zge
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5kXo3UyYbZ5BSu2Zge)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a3009cb commit 7dd5a2f
7 files changed
Lines changed: 180 additions & 13 deletions
File tree
- .changeset
- packages
- cloud-connection/src
- metadata-core/src
- runtime/src
- domains
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4147 | 4147 | | |
4148 | 4148 | | |
4149 | 4149 | | |
4150 | | - | |
| 4150 | + | |
| 4151 | + | |
4151 | 4152 | | |
4152 | 4153 | | |
4153 | 4154 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
355 | 355 | | |
356 | 356 | | |
357 | 357 | | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
397 | 397 | | |
398 | 398 | | |
399 | 399 | | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
87 | | - | |
88 | | - | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
89 | 97 | | |
90 | 98 | | |
91 | 99 | | |
| |||
107 | 115 | | |
108 | 116 | | |
109 | 117 | | |
110 | | - | |
| 118 | + | |
| 119 | + | |
111 | 120 | | |
112 | 121 | | |
113 | 122 | | |
| |||
163 | 172 | | |
164 | 173 | | |
165 | 174 | | |
166 | | - | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
167 | 179 | | |
168 | 180 | | |
169 | 181 | | |
| |||
182 | 194 | | |
183 | 195 | | |
184 | 196 | | |
185 | | - | |
| 197 | + | |
| 198 | + | |
186 | 199 | | |
187 | 200 | | |
188 | 201 | | |
| |||
202 | 215 | | |
203 | 216 | | |
204 | 217 | | |
205 | | - | |
| 218 | + | |
206 | 219 | | |
207 | 220 | | |
208 | 221 | | |
| |||
367 | 380 | | |
368 | 381 | | |
369 | 382 | | |
370 | | - | |
| 383 | + | |
| 384 | + | |
371 | 385 | | |
372 | 386 | | |
373 | 387 | | |
| |||
438 | 452 | | |
439 | 453 | | |
440 | 454 | | |
441 | | - | |
442 | | - | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
443 | 462 | | |
444 | 463 | | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
445 | 468 | | |
446 | 469 | | |
447 | 470 | | |
448 | | - | |
| 471 | + | |
449 | 472 | | |
450 | 473 | | |
451 | 474 | | |
| |||
461 | 484 | | |
462 | 485 | | |
463 | 486 | | |
464 | | - | |
| 487 | + | |
465 | 488 | | |
466 | 489 | | |
467 | 490 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
572 | 572 | | |
573 | 573 | | |
574 | 574 | | |
575 | | - | |
| 575 | + | |
| 576 | + | |
576 | 577 | | |
577 | 578 | | |
578 | 579 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
204 | 204 | | |
205 | 205 | | |
206 | 206 | | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
0 commit comments