Repository navigation
Commit b399cb8
fix(spec): the owner-stamp build door judges a packages[] artifact's top level the way the boot does (#29303)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9J4iPJLcsx6kVWHxTPk6cwZ)_
Fixes #28695
Clause-②: yes (widening) — the build door stops refusing shapes the boot
accepts: a foreign-stamped top-level seed dataset, `datasets` entry or
job of a `packages[]` artifact, and a top-level copy of a body's view
container spelled with another binding. The one shape it now refuses
that it accepted before (a top-level list-bound container no body
declares, wearing a foreign stamp, while a body carries any list-bound
container) is one the boot already refused, and the pending build-door
changeset for item stamps already declares it refused.
Metadata-protocol: added (a foreign `_packageId` on a top-level `data`,
`datasets` or `jobs` item of a `packages[]` artifact, and on a top-level
copy of a body's view container, is accepted at the build doors as it is
at boot)
## 维护者速读
- **改了什么**:`defineStack` / `os validate` / `os build`
对多包制品(`packages[]`)顶层"残余项"的 `_packageId` 判定,改成和启动时 `unclaimedTopLevel`
完全同一套认领规则;另补 30 个逐集合的构建侧钉子和一行文档。
- **为什么改**:构建门和启动门对"哪些顶层项算残余"判法不一:顶层 seed `data`、`datasets`、`jobs`
构建拒、启动收;视图容器按名字认领,换一种绑定写法就误判(两个方向都有)。
- **风险与回滚**:只动 spec 里一个私有校验函数,无导出、无键变化;回滚即 revert 本 PR。唯一新拒的形状启动本来就拒。
- **先看**:`packages/spec/src/stack.zod.ts`(`isResidualTopLevelItem` 与
`TOP_LEVEL_COLLECTIONS_NEVER_RESIDUAL`)、`packages/metadata/src/owner-stamp-residual-build-boot.test.ts`(两门等价钉)、`.changeset/28695-owner-stamp-residual-walk.md`。
- **两分钟验证**:`pnpm --filter @objectstack/metadata exec vitest run
src/owner-stamp-residual-build-boot.test.ts`,应见 27 个用例全绿(在 main 上是 11
红)。
- **需要你做的**:无,席位落地(落地前需一次契约复审档复核,由席位转交分诊)。
## What changes
**S1: the residual walk.** `applyCollectionItemOwnerStampGate`
(`packages/spec/src/stack.zod.ts`) judges a `packages[]` stack's top
level through a new private predicate, `isResidualTopLevelItem`, that
mirrors the boot's residual rule (`unclaimedTopLevel` /
`artifactItemSlot` / `claimedArtifactSlots`, `@objectstack/metadata`),
which spec cannot import:
- top-level `data`, `datasets` and `jobs` are never residual
(`TOP_LEVEL_COLLECTIONS_NEVER_RESIDUAL`). The boot's rule walks only the
collections the metadata door registers as items, and these three are
not among them; `AppPlugin` seeds, consumes and schedules them off the
stack whole, judging no stamp. A package body's items of these
collections are still judged at both doors;
- an aggregated view container's slot is the object it binds to
(`deriveViewContainerObject`: own `object`, then `list.data.object`,
then `form.data.object`, then `name`), and a body's container also
claims each view it expands into (`expandViewContainer`). Any other
item's slot is its `name`; an item with no slot is not judged, since no
door registers it.
The single-package branch and the body judgement are unchanged.
**Measured against the card's mechanism assumption.** Seed `data` held:
refused at build, accepted at boot. **Picklists did not hold:** the
boot's rule lists a residual picklist, and the metadata door registers
it under `manifest.id` and refuses a foreign stamp on it
(`registerUnclaimedTopLevel` → `registerArtifactBodyCollections`,
measured in the new metadata pin). Skipping picklists would have made
the build door looser than the boot, so the build door keeps judging
them. Measuring the whole list showed two more collections in the `data`
class: `datasets` and `jobs`, which the boot's rule does not walk
either.
**S3: build-side pins key by key.**
`stack-collection-item-owner-stamp.test.ts` gains one pin per member of
`OWNER_STAMP_JUDGED_COLLECTIONS`, iterated from the constant, with a
fixture table typed as a `Record` keyed by `OwnerStampJudgedCollection`
(a new member does not compile until it has a fixture) and a test
holding the table's keys equal to the constant. Each pin requires the
unstamped stack to parse, then exactly one custom issue at
`KEY.INDEX._packageId` for a foreign stamp, and none for a stamp equal
to `manifest.id`.
**The equality pin.**
`packages/metadata/src/owner-stamp-residual-build-boot.test.ts` is where
both rules are reachable. For every judged collection, a foreign-stamped
top-level item no body declares: the build door raises the issue exactly
when `unclaimedTopLevel` lists the item, and
`MetadataPlugin.registerUnclaimedTopLevel` refuses exactly those. A 5 ×
5 matrix of body/top-level view container spellings holds the same
equality.
**S4: docs.** One row in the doors table of
`content/docs/deployment/validating-metadata.mdx` for the owner-stamp
refusal. The connectors failure-mode row
(`content/docs/automation/connectors.mdx`) was read and left as is: its
sentence ("an entry carrying a `_packageId` other than its own package's
is refused at the install") is about the install door, and it is true
there.
## Tests
- New tests only. No existing test was edited (no forced test edits).
- `packages/spec/src/stack-collection-item-owner-stamp.test.ts`: +31
(S3, 30 collections + table/constant equality) and +6 (S1 residual
walk).
- `packages/metadata/src/owner-stamp-residual-build-boot.test.ts`: new,
27 tests.
## Acceptance notes
- S2 (`apps` at the residual door) rode #28697, as ruled;
`packages/objectql/src/plugin.ts` is untouched.
- The pending changeset
`.changeset/28627-item-owner-stamp-build-door.md` still says "a
top-level item no body declares judged against `manifest.id`" without
the `data` / `datasets` / `jobs` exception. Not edited here (editing
another PR's pending changeset reds Check Changeset by design); this
PR's own changeset states the exception, and both ship in the same
release notes.
- A foreign `_packageId` on a top-level `data`, `datasets` or `jobs`
item of a `packages[]` stack is now judged at no door. Nothing reads a
stamp on those three today (they are applied or scheduled straight off
the stack), so it is noted here, not filed.
## Verification
<details><summary>S1 red on main, green after (spec)</summary>
`src/stack-collection-item-owner-stamp.test.ts` with the S1 suite on
main's gate: `Tests 5 failed | 43 passed (48)` (data, datasets, jobs,
both container cases). After the fix: `Tests 48 passed (48)`.
</details>
<details><summary>Equality pin red on main, green after
(metadata)</summary>
`stack.zod.ts` restored from main (`47ee57fe`), restore proved by blob
hash and an empty `git diff HEAD`: `Tests 11 failed | 16 passed (27)`,
first divergence `datasets: build verdict == unclaimedTopLevel lists it
(false): expected true to be false`. On the head: `Tests 27 passed
(27)`.
</details>
<details><summary>Ablation: an item schema that stops spreading
MetadataProtectionFields reds a pin</summary>
`node scripts/ablation-replace.mjs` removed
`...MetadataProtectionFields` from `PositionSchema` (anchor 1 → 0, blob
`5ca91b5a` → `75b8039e`): `× positions: one foreign-stamped item raises
the gate's issue …`, `Tests 1 failed | 47 passed (48)`. Restored, blob
equal to HEAD, `git diff HEAD` empty.
</details>
<details><summary>Consumer suites</summary>
All on head `2562bce2`, each under `os-verify-lock` (`VERDICT
command-exit 0` unless named).
- `pnpm --filter @objectstack/spec test`: `Test Files 790 passed (790)`,
`Tests 23407 passed | 1 todo`.
- `pnpm --filter @objectstack/spec typecheck`: exit 0.
`check:generated`: exit 0 (nothing stale).
- `pnpm --filter @objectstack/metadata test`: `Test Files 68 passed
(68)`, `Tests 956 passed` (the `unclaimedTopLevel` consumer).
`typecheck`: exit 0.
- `@objectstack/cli` `vitest --project unit`: `370 passed`, 2 files
failed first on a missing `packages/cli/dist` (a prerequisite: the round
built only cli's dependencies). After building the workspace, those two
files (`published-subpath-console.pin`,
`published-subpath-hook-body.pin`): `Tests 29 passed (29)`.
- `@objectstack/cli` `vitest --project integration
test/collection-item-owner-stamp-validate-door.test.ts` (the `os
validate` door of this gate): `Tests 2 passed (2)`.
- Dogfood: n/a, no `!` in the title and no `(narrowing)` arm.
</details>
<details><summary>Gates</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on `2562bce2`: 105 families, all run after
the last commit.
- 102 exit 0 on the first pass. 3 exit 3 (prerequisite):
`check-plugin-teardown-shape --self-test` (pinned commit missing from
the shallow clone; fetched, then exit 0), `spec check:skill-examples`
and `check:dual-build-cjs-loads` (workspace dist missing; after `turbo
run build --filter='./packages/**'`, both exit 0).
- `dispatch-gates --ran`: `105 derived famil(ies) accounted for — 105
run, 0 NOT-MEASURED (a DERIVED zero)`.
- `check-changeset-no-major.mjs --base origin/main --event` with this
body: `LEVEL AXIS: … clause-② yes (widening) and metadata-protocol
added`, exit 0.
- Local-only `check:dev-prereqs`: exit 1 on machine state (35 `ui/`
packages have no dist in this container), not on this diff.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [ ] 3. No formatter ran; `pnpm lint` (repo-wide, CI-owned) not run
locally.
- [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: `check:generated` is clean (nothing to
regenerate).
- [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.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmQPQBHfN1JR1ZSJUUa)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0138641 commit b399cb8
5 files changed
Lines changed: 458 additions & 14 deletions
File tree
- .changeset
- content/docs/deployment
- packages
- metadata/src
- spec/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 | + | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
509 | 509 | | |
510 | 510 | | |
511 | 511 | | |
| 512 | + | |
512 | 513 | | |
513 | 514 | | |
514 | 515 | | |
| |||
| 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 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
0 commit comments