Repository navigation
Commit e170d42
feat(spec)!: judge a collection item's
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
Fixes #28627
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no `skills/**` file teaches writing `_packageId`;
the one mention
(`skills/objectstack-data/references/examples-objects.md`, Metadata
Protection) already says the runtime populates it
## 维护者速读
- **改了什么**:在 `defineStack`、`os validate`、`os build`
和制品解析这一层,就拒绝"条目自己写了别的包的 `_packageId`"的元数据,报错定位到该条目的
`_packageId`,并给出改法(删掉这个键)。判定逻辑抽成 spec 里的一个纯函数,objectql
启动/安装时的拒绝也改为调用它,两道门不可能判得不一样。
- **为什么改**:PR #28587 只在启动或安装时拒绝这种写法,作者(通常是 AI)要等到管理员安装失败才知道;这次让作者在写的地方、跑
`os validate` 时就看到。
- **风险与回滚**:这是一次收窄(`Metadata-protocol: narrowed`),spec 升 minor,objectql
只是改为调用共享判定(patch,行为和文字都不变)。受影响的只有手写了别的包 `_packageId`
的栈,正常构建、导出、安装都不会写这个键;启动时本来就拒绝它们。回滚就是 revert 这个 PR。
-
**先看**:`packages/spec/src/kernel/collection-item-owner-stamp.ts`(判定函数与判定集合),`packages/spec/src/stack.zod.ts`
里的 `applyCollectionItemOwnerStampGate`,`packages/objectql/src/engine.ts`
里改写后的 `collectionItemOwnerStampRefusal`。
- **两分钟验证**:`pnpm --filter @objectstack/objectql exec vitest run
--maxWorkers=2 src/collection-item-owner-stamp-doors.test.ts`,预期 3
passed(其中一条逐键证明构建门判的集合与启动门拒绝的集合完全相同)。
- **需要你做的**:无,席位在契约复审 PASS 后落地。
## What changes
- **`packages/spec/src/kernel/collection-item-owner-stamp.ts`** (new):
`judgeCollectionItemOwnerStamp(item, type, name, owner, door)`, one pure
judge shaped like ADR-0086 D3's `judgePermissionSetOwnerBinding`. It
returns `{ stamped, owner, message, issueMessage }` or `undefined`,
builds no envelope and throws nothing. Doors: `build` and `register`.
The verdict table mirrors the boot exactly: an absent stamp is accepted;
a stamp equal to the owner is accepted (it is what an item re-registered
under its own package carries, and the boot accepts it); any other value
is refused, `null` and `''` included; under no owner any stamp is
refused. The two doors' words differ only in the clause naming where the
owner comes from (`it is registered under …` / `this stack ships it from
…`). Also exports `OWNER_STAMP_JUDGED_COLLECTIONS`, the 28 stack
collections the registration door judges (the `registerItem` collections
plus seed `data`).
- **`packages/spec/src/stack.zod.ts`**:
`applyCollectionItemOwnerStampGate`, a `superRefine` beside the
permission-set owner-binding gate. On the schema, so `defineStack`, `os
validate`, `os build` (`compile.ts`) and the artifact parse all run it
once; no CLI file changed. The owner is `manifest.id` (none without a
manifest); for `packages[]` each body's own `id`; a top-level item no
body declares in the same collection under the same name is the
residual, judged against `manifest.id`; a top-level copy of a body's
item is not judged.
- **`packages/objectql/src/engine.ts`**:
`collectionItemOwnerStampRefusal` calls the judge with door `register`.
Kept: its `VALIDATION_ERROR` / 400 envelope with `issues[]` at
`_packageId`, and the `REGISTRATION_WRITTEN_STAMPS` exemption, which
stays in objectql (process state, not a body rule). The boot words are
byte-identical, so no boot pin flipped.
- Regenerated by `check:generated --fix`:
`packages/spec/api-surface/kernel.json`,
`packages/spec/export-origins/kernel.json` (five additions each).
- Changesets: `@objectstack/spec` minor
(`28627-item-owner-stamp-build-door.md`), `@objectstack/objectql` patch
(`28627-item-owner-stamp-shared-judge.md`). Each carries both
declaration lines, a BREAKING banner, `adr-0087: not-required
(no-migration-prescription)` and the remedy "drop `_packageId` from the
body", in the shape #28587 used.
## Tests
- `packages/spec/src/kernel/collection-item-owner-stamp.test.ts` (9):
the verdict table row by row at both doors, the two doors' words, the
list's integrity.
- `packages/spec/src/stack-collection-item-owner-stamp.test.ts` (11):
`defineStack` refuses a foreign stamp (`STACK_SCHEMA_INVALID` / 422,
issue `custom` at `connectors.0._packageId`, message equal to the
judge's build words); the control without the stamp, and with a stamp
equal to `manifest.id`, passes; `''` refused by the judge, `null` by the
item schema's type check at the same key; no `manifest` refuses any
stamp; map format; one item per shape class (action, seed dataset, view
container) reaches the judge through the parse; a release artifact's
body judged against its own id, its residual against `manifest.id`, a
top-level copy not judged.
- `packages/objectql/src/collection-item-owner-stamp-doors.test.ts` (3):
the same stamp gets the same verdict at `registerApp` and at the stack
parse, each door's message equal to the judge's for its door; and, key
by key over all of `STACK_DEFINITION_KEYS`, the set of collections in
which `registerApp` refuses a foreign stamp equals
`OWNER_STAMP_JUDGED_COLLECTIONS`.
- `packages/cli/test/collection-item-owner-stamp-validate-door.test.ts`
(2, integration tier, spawns the source CLI): `os validate --json` on a
foreign-stamped connector exits 1 with exactly one error, `custom` at
`connectors.0._packageId`, message equal to the judge's build words, and
`ObjectQL.registerApp` refuses the same body with the judge's register
words; the control without the stamp exits 0.
- `packages/objectql/src/collection-item-owner-stamp.test.ts` (#28587's
12) is unchanged and green.
## Acceptance notes
- Independent review: none run (quota); the PR waits for the seat's
contract-review hand-off.
- Measured, the PM assumption on nested bodies: the build door judges
the stack's own collections and each `packages[]` body. A stack's
`plugins` is `z.array(z.unknown())` (runtime plugin instances), not a
body the parse can read, so a nested plugin's items are judged only by
the registration door, as before.
- Measured, parse order: all 28 judged collections' item schemas declare
`_packageId` (`MetadataProtectionFields`; the authorable-surface ratchet
holds the key), and the stack refinement reads the parsed stack. The
action schema's output JSON schema does not show the key, so the parse
was driven for an action, a seed dataset, a view container and a
connector: each reaches the judge (pinned in the spec test). Consequence
of the refinement position: like the permission-set gate, the issue
appears once the rest of the stack parses; `null` is refused by the item
schema's own type check, not the judge.
- Not judged at either door, unchanged by this PR: `objects`,
`objectExtensions`, `apps`, `picklistExtensions`, `datasources`,
`datasourceMapping`, `translations`. The objectql key-by-key test pins
that the build door's set equals the boot's.
- Words: the boot's `register` words are byte-identical to #28587's; the
build words change only the owner clause.
## Verification
All readings below at head `7f7161f2a6` (after the one merge of
`origin/main`, which touched no `packages/spec`, `packages/objectql` or
`packages/cli` file), each under `scripts/pm/os-verify-lock.sh`, exit
captured before any pipe.
<details><summary>Suites and typecheck</summary>
```text
pnpm --filter @objectstack/spec test exit 0 Test Files 784 passed (784) · Tests 23142 passed | 1 todo (23143)
pnpm --filter @objectstack/objectql test exit 0 Test Files 513 passed (513) · Tests 9901 passed (9901)
pnpm --filter @objectstack/metadata-protocol test exit 0 Test Files 291 passed | 4 skipped (295) · Tests 21944 passed | 26 skipped (21970)
pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2
exit 0 Test Files 363 passed (363) · Tests 5162 passed (5162)
pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/collection-item-owner-stamp-validate-door.test.ts
exit 0 Test Files 1 passed (1) · Tests 2 passed (2)
pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/objectql typecheck && pnpm --filter @objectstack/cli typecheck
exit 0
```
The rest of the cli integration tier is declared to CI (the diff adds
one integration file, run above).
</details>
<details><summary>Dogfood battery (the (narrowing) arm), whole
package</summary>
```text
pnpm --filter @objectstack/dogfood test VERDICT command-exit 0 (lock held 29m41s)
Test Files 369 passed | 3 skipped (372)
Tests 2981 passed | 28 skipped (3009)
no failure
```
</details>
<details><summary>Ablation of the build door (once)</summary>
`node scripts/ablation-replace.mjs --file packages/spec/src/stack.zod.ts
--anchor " .superRefine(applyCollectionItemOwnerStampGate)"
--replacement " /* ABLATED_28627 */"` (wrap mode, at `3df38f8d81`):
```text
anchor x1 -> x0 · blob 3fcb415 -> 4c4c845a6935
pnpm --filter @objectstack/spec build
ablation-dist-preflight @objectstack/spec 'superRefine(applyCollectionItemOwnerStampGate)' --absent
✓ dist/: marker absent from all 588 built files
collection-item-owner-stamp-validate-door.test.ts:
× exit 1: located at the item's `_packageId` ... -> AssertionError: expected exit 1: expected +0 to be 1
✓ CONTROL: the same body without the stamp exits 0
Tests 1 failed | 1 passed (2)
restore: blob after restore == HEAD 3fcb415, `git diff HEAD` empty
rebuild + preflight (default mode): ✓ marker present in 4 built files; ✓ tree: working tree clean against HEAD
same file re-run: Tests 2 passed (2)
```
</details>
<details><summary>Generated artifacts</summary>
```text
pnpm --filter @objectstack/spec check:generated --fix (before the merge) regenerated api-surface/ and export-origins/ (2 of 2 proved stale)
after the merge: pnpm install --frozen-lockfile; gen:schema && gen:docs && check:generated --fix
✓ All 14 generated artifacts are up to date — measured against packages/spec/dist, BUILT BY THIS RUN
(no file changed)
```
</details>
<details><summary>Local gates (the derived set; CI runs the full
farm)</summary>
```text
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (tree 7f7161f) -> 88 commands
first pass: 87 exit 0; 1 exit 3 PREREQUISITE NOT MET
pnpm check:dual-build-cjs-loads (seven dists outside this diff missing) -> built via turbo (cache) -> re-run exit 0
(115 published require entry points across 70 packages load)
dispatch-gates --ran (record with exit codes, the re-run's code for that one gate):
✓ 88 derived famil(ies) accounted for — 88 run, 0 NOT-MEASURED
```
Local-only: `pnpm check:dev-prereqs` exit 1, "35 of 106 workspace
packages declare an entry point under dist/ that is not on disk"
(`ui/**` packages outside this diff's closure): a reading of this
container's build state, not of the change. Not run, declared:
`check-platform-checklist` (no export renamed or deleted; five exports
added); `test:repo` (no repo-project test touched; CI's Test Core runs
it).
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_017ihL2fi9zfJhUTubyJYb6V
---------
Co-authored-by: Claude <noreply@anthropic.com>_packageId against its owning body at defineStack, os validate and os build (#28685)1 parent da6f0ce commit e170d42
12 files changed
Lines changed: 824 additions & 12 deletions
File tree
- .changeset
- packages
- cli/test
- objectql/src
- spec
- api-surface
- export-origins
- 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 | + | |
| 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 | + | |
| 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 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 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 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
0 commit comments