Repository navigation
Commit 8fad7b1
fix(metadata-protocol,cli): os migrate packageless-overlays lists a package-less view container cannot_declare, and --apply never attempts it (#30380)
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgVTHCxahQmaNucVGiWhnJ8X)_
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
## 维护者速读
- 改了什么:`os migrate packageless-overlays` 的预览遇到无包的视图容器行(`list` /
`listViews` / `form` / `formViews` 写在一起的那种),不再列成"可移动",而是列成
`cannot_declare` 并给出出路(把每个视图逐个存进配对包,再删掉容器行);`--apply` 不再尝试它,也不再每次报失败。
- 为什么改:容器的 schema 没有 `overrides` 键,迁移第二步的声明式保存必被拒,预览却说可移动,`--apply`
每次失败、行原样不动,运维无从下手;另外 main 上两处关门(无包写、写名指只读基包)把容器的写也关了,403
指向一个做不到的修法,连"删掉容器行"这条出路都被拒。
- 风险与回滚:判断取自 save 门自己的 schema(不另列类型清单),实测五种叠加类型里只有视图容器装不下
`overrides`;两处关门对容器恢复为关门前的行为(无包保存、删除可用),其余类型不变。回滚即撤本 PR,无数据迁移。
- 先看:`packages/metadata-protocol/src/protocol-packaged-base-refusal.ts`
的 `overrideDeclarationRefusedByShape` 与 `overlayPairFixAdmits`
新增的一行;`protocol-packageless-overlay-migration.ts` 的
`classifyPackagelessRow`(新分支在 `no_pair` 之后、`foreign_declaration`
之前);`packages/cli/src/commands/migrate/packageless-overlays.ts` 的
`reasonOf`。
- 两分钟验证:`pnpm --filter @objectstack/objectql exec vitest run
src/protocol-packageless-overlay-migration.test.ts -t 30261`(需先 build
metadata-protocol),预期 5 条全绿;在 main 的字节上这 5 条全红。
- 需要你做的:无,席位落地。
Fixes #30261
Clause-②: yes (widening) — `PackagelessOverlayDisposition` (reachable
through the exported `PackagelessOverlayCensus`) gains `cannot_declare`;
the package-less write close and the close of a write naming the
read-only base no longer refuse a view container's write, whose named
fix cannot be taken
Metadata-protocol: semantic — an author's package-less save or delete
(and a save naming the read-only base, `?package=<base>`) of a view
container (`list` / `listViews` / `form` / `formViews`) over a name one
read-only package ships, refused `403 NOT_OVERRIDABLE` since the D11
close, succeeds again: the close names a fix (a declared save into the
pair) a container cannot take. Every other shape is closed as before.
`@objectstack/metadata-protocol` ships `minor`; `@objectstack/cli`
`patch` (seat correction at 5cbefbb, the #30168 / PR #30322 precedent).
Skills-docs: none — `skills/objectstack-platform/SKILL.md` line 128
refuses the package-less write "once movable as a declared override"; a
container is never movable as one, so the sentence stays true, and no
skill teaches writing a container over a packaged name.
## What changes
The card's Expected: "The preview and `--apply` agree: a row listed
`movable` moves, or a container row is listed with the reason it cannot
move." This PR takes the second arm (the order's fork: make the preview
honest, leave the container schema as it is; widening `ViewSchema` is a
`packages/spec` design question, not a migration fix).
**One predicate, from the save door's own schema.**
`overrideDeclarationRefusedByShape(type, body)`
(`protocol-packaged-base-refusal.ts`) parses the body with an
`overrides` probe through `resolveOverlaySchema`, the resolver the save
door validates with, and answers `true` only when the parse names
`overrides` as an unrecognized key at the body's root. In a union it
must be named by every branch still diagnosing the body (the view union
mutes the branches the body does not claim to a root kind mismatch,
`focusClaimedBranch`), so a view body that claims no member is not
answered `true`. No list of shapes is kept: a member that gains or loses
the key is answered by its schema.
**Measured over the five overlay types the ceremony moves** (pinned in
`protocol.override-declaration-shape.test.ts`, which also pins that its
fixture covers every `allowEnvironmentOverlay` type): the view container
answers `true` in all four spellings (`list`, `listViews`, `form`,
`formViews`); a single view, a list overlay, a dashboard, a report, a
translation and an email template each spread
`OverrideDeclarationFields` and answer `false`. So the view container is
the one container-shaped member with this mismatch.
**The census.** `classifyPackagelessRow` takes the canonical type and
returns `cannot_declare` (with `base` and `pair`) for such a body, after
`multi_shipped` / `no_pair` and before `foreign_declaration` /
`shadowed`, because those two dispositions' ways out end in this
ceremony moving the row. `migratePackagelessOverlays` acts on `movable`
rows only, so it never attempts the row: no outcome, no failure, no pair
ensured for it, and the CLI exits 0.
**The two closes.** On `main` both closes refused a container's write
with `403 NOT_OVERRIDABLE`, naming a fix (a declared save into the pair)
that the container schema refuses (measured, red leg below). Both closes
are built on "close only where the fix can be taken" (#28576 ruling 2;
the #28905 docblock "every write this closes has a door, the pair; every
other one keeps the write it has on `main`"):
- the package-less close classifies by the same
`classifyPackagelessRow`, so a `cannot_declare` row's package-less save
and DELETE stay open: they are the one door such a row keeps, and the
way out ends with that DELETE;
- `overlayPairFixAdmits`, the "fix can be taken" predicate both closes
ask, now also asks the shape (for a real body; a bare identity has no
shape), so the close of a write naming the read-only base leaves a
container's write as it was before that close. The row such a write
stores is listed `cannot_declare`, `boundToBase`, and a delete naming
the base removes it.
**CLI.** `reasonOf` prints the way out under "left as they are": save
each of the container's views into the pair as a single view named
`OBJECT.VIEW` (one the base ships declaring `overrides: { package,
basedOn }`, a new one declaring nothing), then delete the container row
with a delete naming no package (naming the base, for a row bound to
it). JSON rows carry `disposition: "cannot_declare"`, `outcome: "left"`.
**Docs.** Two sentences this PR made false, amended:
`content/docs/deployment/cli.mdx` (the `os migrate packageless-overlays`
row: "move each") and `content/docs/concepts/metadata-lifecycle.mdx`
(the D11 callout: every package-less write onto a ✅-type packaged item
is refused).
**Changeset:** `@objectstack/metadata-protocol` + `@objectstack/cli`
patch, both declaration lines.
## Tests
- `objectql` `protocol-packageless-overlay-migration.test.ts`, new
top-level describe `[#30261]` (5 cases, real `ObjectQL` engine and
registry): the census lists the active and draft container rows
`cannot_declare` with base and pair, `--apply` returns no outcome and no
base line, ensures no pair, and a re-run lists the same; a single view
beside it still moves; the close leaves the container's package-less
save, DELETE and first save open; the close of a write naming the base
leaves it open and the stored row is listed `cannot_declare`
`boundToBase`, removable by a delete naming the base; the way out holds
end to end (the default view saved into the pair declaring the override,
`showcase_task.mine` saved as the pair's own, the container deleted
naming no package, then the census lists nothing and the read serves the
pair's view).
- `metadata-protocol` `protocol.override-declaration-shape.test.ts`
(new; no harness for this predicate existed): the measurement above,
plus a container with an unknown key of its own still `true`, a body
claiming no view member `false`, a schema-less type `false`.
- `cli` `packageless-overlays.integration.test.ts`, new describe over a
real SQLite database: the preview lists the container `cannot_declare` /
`left`; `--apply --yes` exits 0 with `failed: 0`, `moved: 0`, the rows
unchanged and no pair stored.
## Acceptance notes
- The order asked whether ADR-0146 D11/D2/D10 requires container rows to
move through the ceremony. D11 reads "Every `sys_metadata` row with
`package_id` NULL that overlays an item a code package ships becomes a
declared override of that item"; D2 defines the declaration as a key "in
the item's body" and never names a container. A container is not itself
an item the read serves by name (it expands to `OBJECT.VIEW` items), and
the way out moves each of those items as D11 describes. Read as not
requiring the container row itself to move; reported here for the seat
rather than as `needs_decision`.
- `basedOn` is not judged and the probe's value is a fixed well-formed
declaration: only the key's acceptance is read.
## Verification
All readings on head `3413e7bb` (the fix's four commits plus a merge of
`main` at `0837fdc0`, which carries #30322's change to the same
`overlayPairFixAdmits`: the shape check runs first, then
`overrideWriteVerdict(…, fix.pendingPairScope)`), except the red/green
legs, taken before the merge on `efbca752` / `17adf261` with `main`'s
bytes at `8b117c0e`.
<details><summary>Red leg (main's bytes) and green leg</summary>
```
red: git restore --source=8b117c0e -- protocol-packaged-base-refusal.ts protocol-packageless-overlay-migration.ts
pnpm --filter @objectstack/metadata-protocol build; ablation-dist-preflight 'cannot_declare' --absent -> marker absent from all 24 built files
objectql protocol-packageless-overlay-migration.test.ts -t 30261 -> 4 failed (the first four cases):
census: expected 'showcase_task@active cannot_declare', received 'showcase_task@active movable' (and @draft)
close: package-less save of the container -> { code: 'NOT_OVERRIDABLE', status: 403 }
way out: the package-less DELETE of the container -> { code: 'NOT_OVERRIDABLE' }
second red leg (the named-base case, added after): -> 1 failed, { code: 'NOT_OVERRIDABLE' }
restored with git checkout HEAD --; git hash-object equal to HEAD's blobs (4a2a3ba, b312e97); git status clean
green: rebuild; preflight 'cannot_declare' present in 4 built files
objectql protocol-packageless-overlay-migration.test.ts -> 33/33 (34/34 with the named-base case)
metadata-protocol override-declaration-shape + named-base-overlay-write + view-expansion-reconcile-call-sites -> 61/61
```
</details>
<details><summary>Package suites and typechecks, on 3413e7b after the
merge (under os-verify-lock.sh)</summary>
```
pnpm install --frozen-lockfile :: exit 0
pnpm exec turbo run build --concurrency=2 --filter=...@objectstack/cli --filter=...@objectstack/objectql :: exit 0
pnpm --filter @objectstack/metadata-protocol test :: exit 0 Test Files 313 passed | 4 skipped; Tests 22396 passed | 26 skipped
pnpm --filter @objectstack/metadata-protocol typecheck :: exit 0
pnpm --filter @objectstack/objectql test :: exit 0 Test Files 550 passed; Tests 10956 passed
pnpm --filter @objectstack/objectql typecheck :: exit 0
pnpm --filter @objectstack/cli exec vitest run --project unit :: exit 0 Test Files 384 passed; Tests 5427 passed
pnpm --filter @objectstack/cli exec vitest run --project integration src/commands/migrate/packageless-overlays.integration.test.ts :: exit 0 Tests 6 passed
pnpm --filter @objectstack/cli typecheck :: exit 0
```
</details>
<details><summary>Derived gates (dispatch-gates --commands, 95 families)
and lint, on 3413e7b</summary>
```
94 of 95 :: exit 0 (each recorded before any pipe)
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 3 (shallow), exit 0 after git fetch origin 621a487
pnpm check:dual-build-cjs-loads :: exit 3 PREREQUISITE NOT MET (needs every package's dist) -> NOT MEASURED, CI Lint & Repo Gates runs it
dispatch-gates --ran: 95 derived famil(ies) accounted for -- 94 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)
pnpm lint :: exit 0
```
</details>
Dogfood: not owed (no `!` in the title, no `(narrowing)` arm).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6CbC5BpV41NMLrJMt8Q)_
<!-- signed: domain:server#5 · os-bill ·
session_01Utp6CYVV6fcAQrNTDQRvPb -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 38717d3 commit 8fad7b1
9 files changed
Lines changed: 330 additions & 6 deletions
File tree
- .changeset
- content/docs
- concepts
- deployment
- packages
- cli/src/commands/migrate
- metadata-protocol/src
- objectql/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
173 | 173 | | |
174 | 174 | | |
175 | 175 | | |
176 | | - | |
| 176 | + | |
177 | 177 | | |
178 | 178 | | |
179 | 179 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1317 | 1317 | | |
1318 | 1318 | | |
1319 | 1319 | | |
1320 | | - | |
| 1320 | + | |
1321 | 1321 | | |
1322 | 1322 | | |
1323 | 1323 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
105 | 122 | | |
106 | 123 | | |
107 | 124 | | |
| 125 | + | |
108 | 126 | | |
109 | 127 | | |
110 | 128 | | |
| |||
250 | 268 | | |
251 | 269 | | |
252 | 270 | | |
| 271 | + | |
| 272 | + | |
253 | 273 | | |
254 | 274 | | |
| 275 | + | |
255 | 276 | | |
256 | 277 | | |
257 | 278 | | |
| |||
384 | 405 | | |
385 | 406 | | |
386 | 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
66 | 77 | | |
67 | 78 | | |
68 | 79 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| |||
462 | 463 | | |
463 | 464 | | |
464 | 465 | | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
465 | 470 | | |
466 | 471 | | |
467 | 472 | | |
468 | 473 | | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
469 | 532 | | |
470 | 533 | | |
471 | 534 | | |
| |||
0 commit comments