Repository navigation
Commit a9782b3
fix(objectql,metadata,spec)!: an installed package's apps and objects never keep a self-written
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X3KriyGEkCr9KSnUGjBS7sj)_
Fixes #28697
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none (no `skills/**` file teaches writing `_packageId` on
an object or app; the one mention,
`objectstack-data/references/examples-objects.md`, says REST returns it,
which is unchanged)
## 维护者速读
- **改了什么**:包里的应用(apps)和对象(objects)如果自己写了别的包的 `_packageId`,现在一律被拒,和
#28534 拒其余 28
个集合用的是同一个判定函数。覆盖四处:引擎注册(含嵌套插件)、残余(residual)注册、元数据插件的包体注册(启动同步读的就是它写下的归属),以及
`defineStack` / `os validate` / `os build`。
- **为什么改**:实测(卡上的探针)一个通过 install-local 安装的包,把宿主的包 id 写进自己的 app,安装返回
200,这个 app 就被算成宿主的:按包筛选时列在宿主名下,停用该包时 app 仍在。
- **风险与回滚**:这是一次收窄(`Metadata-protocol: narrowed`),objectql、metadata、spec
升 minor。只影响手写了别的包 `_packageId` 的对象或应用,仓库里没有任何构建、导出或安装会写这个键。回滚就是 revert
这个 PR。
- **先看**:`packages/objectql/src/engine.ts` 的
`packageBodyObjectAndAppOwnerStampRefusal` 及其在 `registerApp`
开头的调用(在任何注册之前),`packages/metadata/src/plugin.ts` 的
`artifactItemOwnerStampRefusal`,`packages/spec/src/kernel/collection-item-owner-stamp.ts`
的集合清单。
- **两分钟验证**:`pnpm --filter @objectstack/dogfood exec vitest run
--maxWorkers=1
test/install-local-object-app-owner-stamp.dogfood.test.ts`,预期 5 passed。
- **需要你做的**:无,契约评审 PASS 后由席位落地。
## What changes
One judge, now over `objects` and `apps` too.
`judgeCollectionItemOwnerStamp` (`@objectstack/spec/kernel`) is still
the only comparison. Every door below calls it, and no door spells a
second one.
- **Body seam (`ObjectQL.registerApp`).** The new
`packageBodyObjectAndAppOwnerStampRefusal` (`engine.ts`) judges every
`objects` entry (array and name-keyed map), every `apps` entry the loop
registers, and the manifest when it registers as its own app. It goes
through `collectionItemOwnerStampRefusal`, so the envelope
(`VALIDATION_ERROR` / 400, `issues[]` at `_packageId`), the words and
the registration-written-stamp exemption are #28534's. It runs
**before** `installPackage`, so a refused body leaves no package record,
object or app. Apps now register through `registerAppNotingStamp`, which
records the stamp `registerItem` writes onto the caller's own object
(the #28534 exemption). Without that, a module constant re-registered
under a second package would be refused.
- **Nested plugins (`registerPlugin`).** The same helper judges the
plugin's objects and its plugin-as-app against the parent package. It
runs before, and outside, the two `try` blocks that log a registry
failure and continue.
- **Residual door (`registerUnclaimedTopLevelObjects`,
`objectql/src/plugin.ts`).** Every residual object is judged before any
registers. Residual apps were already judged by
`registerUnclaimedTopLevelItems`. With this change the residual door,
the body seam and the build door agree on `apps` (#28695 S2).
- **Build door (stack schema).** `OWNER_STAMP_JUDGED_COLLECTIONS` gains
`objects` and `apps`, so `applyCollectionItemOwnerStampGate` raises an
issue at `objects.N._packageId` / `apps.N._packageId`. The judge's
docblock no longer lists either as unjudged, and it names the metadata
door. The existing key-by-key test
(`collection-item-owner-stamp-doors.test.ts`) holds the list equal to
what `registerApp` refuses, and it still passes with both members added.
- **Boot path.** `registerArtifactBodyCollections`
(`@objectstack/metadata`) is reachable with a body no schema parse
judged: a config boot's residual, through `AppPlugin` →
`MetadataPlugin.registerUnclaimedTopLevel`. It now refuses an item of a
judged collection whose body `_packageId` differs from the body's
package, before `applyProtection` and the sink. That door writes the
stamp that `ObjectQLPlugin.loadMetadataFromService` later files `app` /
`view` / `flow` / `hook` under. `loadMetadataFromService` itself is
unchanged apart from a comment: it reads items, not the body that
registered them, so it has nothing to judge against. Ignoring the stamp
there would flip `isArtifactBacked` for every package-shipped item it
syncs.
- **`objects`: refuse, as the order rules.** No first-party fixture
relied on a foreign object stamp. The objectql (9911), metadata (924)
and spec (23202) suites are green, and so is the full dogfood battery
below. `registry.ts`'s `schema._packageId ?? packageId` therefore never
reads a stamp a body wrote about another package.
- **Versioned apps keep the exemption (S-a).**
`registerAppNotingStamp(registry, app, ownerId, ownerVersion)` now calls
`stampPackage` itself and reads the stamp BEFORE it, the order the
collection and seed loops keep, so an app object re-registered after a
versioned registration is recorded as registration-stamped, not refused.
All three call sites (`registerApp` steps 3 and 4, `registerPlugin`'s
plugin-as-app) use it.
- **Nested plugins are judged before the parent registers (S-b).**
`registerApp`'s pre-pass walks `manifest.plugins[]` (objects and
plugin-as-app, against the parent id), so a nested plugin's foreign
stamp is refused with no package record, object or app of the parent
left behind. `registerPlugin` keeps its own pre-pass for the plugin it
is handed.
- **Changeset (S-c)** states the upgrade behaviour: a config boot
carrying such a stamp fails to start with the judge's message; a package
installed earlier that carries one is not loaded at the next start and
is reported at `error`.
- `origin/main` merged once (#28728 moved `engine.ts`; clean merge).
## Tests
-
`packages/qa/dogfood/test/install-local-object-app-owner-stamp.dogfood.test.ts`
(new) boots the showcase with `MarketplaceInstallLocalPlugin` and pins
the card's probe through `POST /api/v1/marketplace/install-local`:
- an install whose app wears the host's id answers 422
`PLUGIN_REGISTER_FAILED` in the judge's words, and its (unstamped)
object and its ledger entry are absent too;
- the same for an install whose object wears the host's id.
- Controls: an app and object stamping their own package, or nothing,
install 200 and list under their own package; the host's apps list as at
boot; the uninstall is keyed on the registering package and removes
both.
- `packages/objectql/src/package-object-app-owner-stamp.test.ts` (new)
covers:
- `apps[]`, the manifest-as-app, objects in array and map form, a nested
plugin's object and plugin-as-app, and `null` / `''`, each with no
package record left;
- controls: absent and equal stamps, and the in-place stamp exemption
for a re-registered app object;
- the residual door for objects (new) and apps (already judged).
- `packages/metadata/src/plugin-artifact-owner-stamp.test.ts` (new): the
metadata door refuses a residual app, flow or view container with a
foreign stamp before the sink stores anything. Controls: absent and
equal stamps are stored under the owner, and `objectExtensions` (not
judged by the build door) is stored as before.
- `packages/metadata/src/metadata.test.ts`: the
`@objectstack/spec/kernel` mock now spreads the real module and narrows
only the type registry, because the artifact door reads the judge from
that module.
<details><summary>Red first: the dogfood and unit tests on the unfixed
tree (every fixed source file checked out from origin/main,
spec/metadata/objectql rebuilt, dist preflight absent)</summary>
```
spec dist judges apps: false
× 1: an app stamping the host's package id is refused at the install, nothing of the package registered
AssertionError: {"success":true,"data":{"manifestId":"com.dogfood.appstamp",…,"hotLoaded":true,…}}: expected 200 to be 422
× 2: an object stamping the host's package id is refused at the install, nothing of the package registered
AssertionError: {"success":true,"data":{"manifestId":"com.dogfood.objstamp",…,"hotLoaded":true,…}}: expected 200 to be 422
× CONTROL: the host's own apps list as they did at boot, before and after the installs
AssertionError: expected [ 'appstamp_app', 'showcase_app' ] to deeply equal [ 'showcase_app' ]
Tests 3 failed | 2 passed (5)
objectql package-object-app-owner-stamp.test.ts: 7 failed (every refusal case; the residual-app case passes: that door already judged apps)
metadata plugin-artifact-owner-stamp.test.ts: 3 failed | 2 passed (5)
restore: blob == HEAD for all 5 files, git diff HEAD empty
```
</details>
<details><summary>Ablations (one judge at a time, `ablation-replace.mjs`
+ `ablation-dist-preflight.mjs`, each restored to the HEAD
blob)</summary>
```
A1 apps judge dropped (registerApp scope apps:false, selfAsApp:false) · objectql rebuilt · dist marker present
× 1: an app stamping the host's package id … × CONTROL: the host's own apps … Tests 2 failed | 3 passed (5)
A2 objects judge dropped · objectql rebuilt · dist marker present
(first attempt `false && objects` was a no-op reading: esbuild folded the marker away and the DTS build failed, so
the preflight refused it; re-run with a marker that survives the build)
× 2: an object stamping the host's package id … Tests 1 failed | 4 passed (5)
A3 residual objects judge dropped · objectql rebuilt
× a residual object wearing another package's id: refused, not served Tests 1 failed | 9 passed (10)
A4 metadata door judge dropped (the boot path)
× residual apps / flows / views entry … Tests 3 failed | 2 passed (5)
restore: each blob == HEAD, git diff HEAD empty; rebuilt; preflight: every ablation marker absent, the fix present
```
</details>
## Acceptance notes
- **Out of scope, measured on the engine seam:** a refusal raised in
`registerApp`'s collection loop (#28534's owner stamp, the
view-container and permission-set refusals) fires after the package
record, objects and apps have registered, and they stay in the live
registry. Probed on this branch with an unstamped object and app beside
a foreign-stamped connector: refused 400, package record, object and app
all still held. The objects and apps judged here run before anything
registers, so this PR does not add to it. Not measured through a public
door.
- `loadMetadataFromService` still reads a stamp written by doors other
than the metadata plugin's body registration: FS-loaded project files
and stored `sys_metadata` rows. Rows echoing a body `_packageId` are
seat 1's #28673. Neither path is an installed package's body.
- #28695 S2 is settled here. S1, S3 and S4 stay on that card.
## Verification
All local readings are on `498f5c70`. The pushed head `83e32c91` has the
identical tree: the three commits were reworded (model-free trailer)
with a tree-hash check.
- **Package suites:** `pnpm --filter @objectstack/objectql test`: 514
files, 9911 tests passed. `@objectstack/metadata`: 66 files, 924 passed.
`@objectstack/spec`: 784 passed, 1 skipped (23202 tests). `typecheck`
for objectql, metadata and spec: exit 0.
- **Full dogfood battery** (`pnpm --filter @objectstack/dogfood test`
under the verify lock): `VERDICT command-exit 0`, Test Files 372 passed
/ 3 skipped (375), Tests 2999 passed / 28 skipped (3027). No failure.
- **Derived gates:** `dispatch-gates --commands` derived 84 families; 83
exit 0. `pnpm check:dual-build-cjs-loads` exited 3 (PREREQUISITE NOT
MET: it needs every package's `dist/`, a full `pnpm build`), so it is
NOT MEASURED locally and left to CI. `dispatch-gates --ran`: 84
accounted, 83 run, 1 NOT-MEASURED, 0 UNRUN.
- **Not run locally:** `test:repo` (the vitest `repo` projects), which
CI's Test Core runs, and the repo-wide lint farm, which belongs to CI.
Every other CI family is read from this PR's check-runs.
**Round 2 (seat review should-fixes), head `8f56596d`:**
- Red first on the pre-fold engine (`engine.ts` +
`engine-package-registration.ts` from `7e796f0dd`): the nested-plugin
case (parent's package record still held) and the versioned control
(`version 1.0.0: expected Error: Drop _packageId from app ...`) red, 2
failed / 9 passed. Ablations on HEAD, each restored to the HEAD blob:
dropping the `plugins[]` walk reds the nested case; reading the stamp
after `stampPackage` reds the versioned control.
- On `8f56596d`: objectql 516 files / 9935 tests, metadata 66 / 924,
objectql typecheck exit 0; full dogfood Test Files 375 passed / 3
skipped, Tests 3039 passed / 28 skipped; derived gates 84, 83 exit 0,
`check:dual-build-cjs-loads` NOT MEASURED (exit 3, needs a full build).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5pevHhCDHNc3eWbkAg)_
---------
Co-authored-by: Claude <noreply@anthropic.com>_packageId (#28745)1 parent b357250 commit a9782b3
10 files changed
Lines changed: 735 additions & 17 deletions
File tree
- .changeset
- packages
- metadata/src
- objectql/src
- qa/dogfood/test
- spec/src/kernel
| 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 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
548 | 548 | | |
549 | 549 | | |
550 | 550 | | |
551 | | - | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
552 | 556 | | |
553 | 557 | | |
554 | 558 | | |
| |||
| 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 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
459 | 459 | | |
460 | 460 | | |
461 | 461 | | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 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 | + | |
462 | 499 | | |
463 | 500 | | |
464 | 501 | | |
| |||
496 | 533 | | |
497 | 534 | | |
498 | 535 | | |
| 536 | + | |
| 537 | + | |
499 | 538 | | |
500 | 539 | | |
501 | 540 | | |
| |||
537 | 576 | | |
538 | 577 | | |
539 | 578 | | |
| 579 | + | |
| 580 | + | |
540 | 581 | | |
541 | 582 | | |
542 | 583 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
40 | | - | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
41 | 45 | | |
42 | 46 | | |
43 | 47 | | |
| |||
50 | 54 | | |
51 | 55 | | |
52 | 56 | | |
53 | | - | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
54 | 62 | | |
55 | 63 | | |
56 | 64 | | |
| |||
143 | 151 | | |
144 | 152 | | |
145 | 153 | | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
146 | 166 | | |
147 | 167 | | |
148 | 168 | | |
| |||
172 | 192 | | |
173 | 193 | | |
174 | 194 | | |
175 | | - | |
| 195 | + | |
176 | 196 | | |
177 | 197 | | |
178 | 198 | | |
| |||
0 commit comments