Commit 535d1d2
fix(metadata-protocol): a bare-list view container on another package's object expands under its own name (#21430)
Fixes #21334
Clause-②: no
## What this changes
A runtime view container expands each member to `OBJECT.KEY`: a bare
`list` (one that names no key) to `OBJECT.default`, a `form` to
`OBJECT.form`, and every member that names a key to that key. Saved
under another name, in another package or in none, for an object a code
package ships, those expansions replaced that package's views of the
same names on the object door (`GET /api/v1/meta/view?object=OBJECT`),
still stamped with the shipping package's `_packageId` and `_provenance:
'package'`. The container's own default also kept `isDefault: true`. It
either replaced the object's default view or stood beside it as a second
list default.
`expandRuntimeViewContainer` in
`packages/metadata-protocol/src/protocol.ts` now applies triage's ruling
(5946423948), as the seat's answer (5955628428) extends it:
- **On another package's object** (a code package owns the object, per
`getPackagedObjectOwner`, and it is not the container's own package),
every name the container expands derives from its own name:
- the bare `list` expands to `OBJECT.CONTAINER_NAME`;
- every keyed member expands to `OBJECT.CONTAINER_NAME.KEY`: a `list`
that names its key, each `listViews` and `formViews` entry, and `form`.
The spec's own expander produces these names: `expandUnderOwnName` runs
it with `OBJECT.CONTAINER_NAME` as its base. So the spec's key rule and
in-container de-duplication still apply, and a member kind the spec adds
later is placed the same way. Each item's `object` is set back to the
object it binds. The bare `list` is lent the container's name as its
key, then served as `OBJECT.CONTAINER_NAME` with the lent `name` taken
back off its `config`.
- **No default claimed.** None of these views carries `isDefault`. The
object's defaults stay its owning package's.
- **One exception.** When the owning package itself ships
`OBJECT.CONTAINER_NAME` (a container named after one of that package's
keys), the bare `list` stays at the spelling the spec gives it,
`OBJECT.CONTAINER_NAME.CONTAINER_NAME`.
- **A container with no name of its own** expands nothing on such an
object.
- **The container's own package** is the package its row is bound to.
For a package-less row that is the name-keyed overlay of a packaged item
(ADR-0005), it is the package of the artifact that row overlays.
- **Provenance.** Each expanded item carries the container's own package
as `_packageId`. It merges an artifact's protection envelope only when
that artifact belongs to the container's own package.
- **Unchanged, `isDefault` included:** a container of the object's own
package, a package-less overlay of that package's own container, and a
container on an object no code package ships.
Both callers use this one function: the list read's inline per-request
expansion, and the registry hydration (`hydrateExpandedViewItems`) on an
unscoped kernel. Nothing in `packages/spec`, `packages/rest` or the save
path changes.
## Patch round 1: the seat's answer (5955628428), OQ1 → A and OQ2 → A
`protocol.ts` is blob `237a0530d7ad` from `73da9273f3` to HEAD
`8976a86f94`. The pins and ablations below ran against `f233f22fd7`'s
test files.
**OQ1. A cross-package container never claims the object's default.**
- **Change.** One line in `expandRuntimeViewContainer`: `if
(crossPackage) delete item.isDefault;`.
- **Measured in-process**, on the `env_local` and the unscoped kernel,
for a package-scoped, an environment-wide and an organization-scoped
container:
- no item from the container carries `isDefault`;
- the only `isDefault` items on the object door are the showcase's
`showcase_task.default` (list) and `showcase_task.form` (form).
- **Measured over REST** (the real showcase, unscoped harness) for the
package-scoped probe, the package-less probe and the keyed probe: `GET
/api/v1/meta/view?object=showcase_task` serves exactly ONE `isDefault`
list view, `showcase_task.default`.
- **objectui's reading, NOT browser-measured.** objectui `main`
`MetadataProvider.applyViewItem` sets `bucket.primary` to the last
`isDefault` list view it is served. The door now serves one, the
packaged `showcase_task.default`, so that is the view objectui would
take as the object's primary tab.
**OQ2. Every member derives its name from the container's own name.**
- **Change.** `expandUnderOwnName`. Every keyed member spells under
`OBJECT.CONTAINER_NAME`, as listed above.
- **Measured in-process, the enumeration:** five member kinds (bare
`list`, named `list`, `listViews`, `formViews`, `form`) × 3 containers ×
2 kernels. Each case aims its key at a name the showcase ships. Every
case finds:
- every shipped name answering the packaged view, once, on the object
door and on the by-name read;
- the container's own name served with its own `_packageId` (none when
package-less), `_provenance` not `package`, `_diagnostics.valid: true`,
and no `isDefault`.
- **Measured over REST:** the keyed probe (`listViews.in_progress`, in
`com.example.repairassets`) leaves `showcase_task.in_progress` unchanged
on both doors. Its own `showcase_task.os_qa_keyed_probe.in_progress`
carries `com.example.repairassets`, `_diagnostics.valid: true`, and no
`isDefault`.
- **The final governance pass needed no edit.** `lookupArtifactItem` +
`mergeArtifactProtection` at the end of `readFlattenedMetaItems` is
untouched. It finds no artifact under a name derived from the
container's own name, so it grafts nothing. Measured: `_packageId` is
the container's own and `_provenance` is absent on every derived name,
on both kernels.
**"Both doors answer the same row" for a derived name: a fork,
reported.**
- The by-name read answers the container's own name (`CONTAINER_NAME`)
with its row on every kernel.
- For a derived name (`OBJECT.CONTAINER_NAME.KEY`), it answers the same
row only where the registry hydrates: an unscoped kernel, for a
package-scoped or environment-wide container. On an `env_local` kernel,
and for an organization-scoped container on any kernel, it answers
nothing. No stored row carries that name, and the by-name read expands
no container.
- Measured for all three containers on both kernels. This is how every
runtime container's expansion reads by name, and it predates this card.
It is reported in the dev report, not changed here.
**The member-kind enumeration is derived, not listed.** Each top-level
key of the spec's container schema (`ViewSchema.shape`) is offered a
single view and a record of views. A key that yields an expanded item is
a member kind. A single-view kind is enumerated bare, and also named
when its own schema declares `name`: a `list` does, while a `form` does
not, so a named `form` is not authorable through the save door. The test
fails when the derived set differs from the placed cases.
**Reverse verification, one arm at a time.** Each arm was mutated with
`scripts/ablation-replace.mjs` from the committed state, and the restore
was proved: blob `237a0530d7ad` = the HEAD blob, and `git diff HEAD`
empty.
- **OQ1 arm reverted** (`if (crossPackage) delete item.isDefault;`
deleted):
- In-process: 36 failed / 26 passed of 62. Red: every member-kind case
and the card's probe, × 3 containers × 2 kernels. Green: 16
pre-existing, the enumeration, the 6 controls, and the de-duplication,
shipped-key and nameless cases.
- Dogfood (rebuilt; the preflight found `delete item.isDefault` absent
from all 24 built files): 3 failed / 1 passed. Red: steps 1 to 7, step 8
and the keyed probe, each on the single-default assertion. Green: the
override control.
- **OQ2 arm reverted** (`const expandAt = under;` changed to `const
expandAt = object;`, which is round 0's naming):
- In-process: 26 failed / 36 passed. Red: the four keyed kinds × 3
containers × 2 kernels, the de-duplication case and the shipped-key
case. Green: the bare-`list` kind and the card's probe on every
container and kernel, the controls, the enumeration and the nameless
case.
- Dogfood (rebuilt; the preflight found `expandAt = object` present in 2
built files): 1 failed / 3 passed. Red: the keyed probe, with
`showcase_task.in_progress` labelled 'Probe keyed'. Green: the control,
steps 1 to 7, and step 8.
- **Both restore legs** rebuilt `metadata-protocol`; the preflight
confirmed the marker restored, and the tree clean against HEAD.
## Zone 2 measurements (round 0, at base `ceb4a939b`)
**H1, the baseline: reproduced, and wider than the card.** Probe body:
`{name, object: 'showcase_task', list: {type: 'grid', columns:
['title','status']}}`.
| kernel | container | object door `showcase_task.default` | by-name
`showcase_task.default` |
|:--|:--|:--|:--|
| environment-scoped (`env_local`, in-process) | package-scoped /
env-wide / org-scoped | shadow: 2 columns, `_packageId:
com.example.showcase`, `_provenance: package` | packaged: "All Tasks", 7
columns |
| unscoped (in-process and the real showcase over REST) | package-scoped
/ env-wide | shadow | **shadow too**, through the registry's bare key |
| unscoped (in-process) | org-scoped | shadow | packaged |
Measured over REST at round 0's head `f79124a005`, before round 1. A
container in another package with `listViews.in_progress` still replaced
`showcase_task.in_progress` on both doors (keyed members were unchanged
from base). After the bare probe was saved, `GET /api/v1/meta/view`
served two list defaults for `showcase_task`.
**H2, the contract: silent.** The container contract (`view.zod.ts`, its
header and the `ViewSchema.name` describe text; ADR-0017 §3.2) names an
object-scoped container after its object. It states no arm for a name
another package owns, so the ruling's arm applies. `view.zod.ts` is not
edited.
**H3, the fix site: confirmed, with a second site.** The fix site is the
inline `byName.set` in `readFlattenedMetaItems` together with
`expandRuntimeViewContainer`, which grafted the shadowed artifact's
`_packageId` and `_provenance`. The second site is
`hydrateExpandedViewItems`, which registered the expansion under the
bare key on an unscoped kernel.
**H4, the spelling.** An expanded ViewItem named with the flat container
name is badged `_diagnostics.valid: false` by the list door, because
`ViewItemNameSchema` requires a dot. The qualified spellings above are
`valid: true`.
## Pins
-
`packages/metadata-protocol/src/view-container-runtime-expansion.test.ts`,
block `#21334`: 46 cases, plus 16 pre-existing.
- It reuses the file's pinned engine double, with a registry double in
the real `SchemaRegistry`'s key shapes.
- The packaged views (`default`, `in_progress`, `form`, `edit`) come
from the spec's own `expandViewContainer`.
- The cases: the member-kind enumeration, 30 member cases, 6 card-probe
cases, the controls for each kernel (same-package row; package-less
overlay of the package's own container; sanctioned by-name override),
the in-container de-duplication, the shipped-key fallback, and the
nameless container.
-
`packages/qa/dogfood/test/view-container-cross-package-default.dogfood.test.ts`:
the card's steps over the real showcase through REST. The override
control runs first, on the pristine stack. Then steps 1 to 7, step 8,
and the keyed probe. Each case asserts both doors and the single list
default.
## Gates
Readings at HEAD `8976a86f94`. Round 1 merged `origin/main` `bdd3654f29`
first (merge commit `3551aede34`, fast-forward push). Dists were built
through the verify lock, from the closure `@objectstack/dogfood^...` at
the round-1 head. The marker `expandAt = under` is in
`metadata-protocol/dist`.
- **Suites, run at `f233f22fd7`.** The diff from there to `8976a86f94`
is one changeset line.
- `pnpm --filter @objectstack/metadata-protocol exec vitest run
--maxWorkers=2`: exit 0. 202 files passed, 3 skipped; 3034 tests passed,
19 skipped.
- `pnpm --filter @objectstack/metadata-protocol typecheck`: exit 0.
- `pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2`
(the full suite): exit 0. 172 files passed, 1 skipped; 1400 tests
passed, 9 skipped.
- `pnpm --filter @objectstack/dogfood typecheck`: exit 0.
- **Gates, at `8976a86f94`.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derives 68 commands, the same
list as at `f233f22fd7`. All 68 ran at `8976a86f94` and exited 0.
`--ran`, with the exit codes recorded, reports "68 derived, 68 run, 0
NOT-MEASURED, 0 UNRUN".
- `pnpm check:dual-build-cjs-loads` first exited 3 (PREREQUISITE NOT
MET). It went green after the 8 packages it names were built through the
lock.
- `pnpm check:type-check-debt` ran through the lock: exit 0.
- **NOT MEASURED:** the CI-only families that take a workflow value
(`check-issue-citations --census`, the shard attestations).
- **Lint, a declared narrowing.** `eslint --no-inline-config --format
json` on the 3 touched source and test files gives 3 files, 0 errors, 0
warnings. eslint's `calculateConfigForFile` shows none of the 3 ignored,
with `parserOptions.project` and `projectService` both null. Type-aware
linting is off, so no untouched file's verdict can move. The repo-wide
`pnpm lint` is CI's.
- **Main since round 1's merge.** `origin/main` moved 6 commits since
round 1's merge, to `3a6d92f78b`. None of them touches
`packages/metadata-protocol`, this PR's dogfood file or `view.zod.ts`,
so main was not merged again.
## Acceptance notes
1. **Stale registry expansions on an unscoped kernel.** An expansion
hydrated into the registry is not unregistered when its container is
deleted. Its name is now the container's own, so the stale item no
longer covers a packaged name. Reach: the `@objectstack/verify` harness
only. Noted, not filed (5955628428).
2. **Same-name collapse in `byName`.** The inline expansion's `byName`
map keys by bare name, so it collapses two packages' same-name items
(ADR-0048) whenever any view row exists for the type. Read, not
measured. Noted, not filed (5955628428).
3. **By-name read of a derived name.** See the fork above: it answers
only through the registry's hydration. This predates the card.
4. **Rename warning.** `stampRenameWarning`'s `_diagnostics` is replaced
by the list door's decoration.
5. **`getViewsByObject` not covered.**
`MetadataManager.getViewsByObject` (`packages/metadata`) expands
containers with its own first-wins rule. It was not touched or measured
here.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent d7d5b4f commit 535d1d2
4 files changed
Lines changed: 812 additions & 12 deletions
File tree
- .changeset
- packages
- metadata-protocol/src
- qa/dogfood/test
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8363 | 8363 | | |
8364 | 8364 | | |
8365 | 8365 | | |
| 8366 | + | |
| 8367 | + | |
| 8368 | + | |
| 8369 | + | |
| 8370 | + | |
| 8371 | + | |
| 8372 | + | |
8366 | 8373 | | |
8367 | 8374 | | |
8368 | 8375 | | |
| |||
16131 | 16138 | | |
16132 | 16139 | | |
16133 | 16140 | | |
| 16141 | + | |
| 16142 | + | |
| 16143 | + | |
| 16144 | + | |
| 16145 | + | |
| 16146 | + | |
| 16147 | + | |
| 16148 | + | |
| 16149 | + | |
| 16150 | + | |
| 16151 | + | |
| 16152 | + | |
| 16153 | + | |
| 16154 | + | |
| 16155 | + | |
| 16156 | + | |
| 16157 | + | |
| 16158 | + | |
| 16159 | + | |
| 16160 | + | |
| 16161 | + | |
| 16162 | + | |
| 16163 | + | |
| 16164 | + | |
| 16165 | + | |
| 16166 | + | |
| 16167 | + | |
| 16168 | + | |
| 16169 | + | |
| 16170 | + | |
| 16171 | + | |
| 16172 | + | |
| 16173 | + | |
| 16174 | + | |
| 16175 | + | |
| 16176 | + | |
| 16177 | + | |
| 16178 | + | |
| 16179 | + | |
16134 | 16180 | | |
16135 | 16181 | | |
16136 | 16182 | | |
| |||
16146 | 16192 | | |
16147 | 16193 | | |
16148 | 16194 | | |
| 16195 | + | |
| 16196 | + | |
| 16197 | + | |
| 16198 | + | |
| 16199 | + | |
16149 | 16200 | | |
16150 | | - | |
| 16201 | + | |
16151 | 16202 | | |
16152 | 16203 | | |
16153 | 16204 | | |
16154 | | - | |
16155 | | - | |
16156 | | - | |
16157 | | - | |
16158 | | - | |
16159 | | - | |
16160 | | - | |
16161 | | - | |
16162 | | - | |
16163 | | - | |
| 16205 | + | |
| 16206 | + | |
| 16207 | + | |
| 16208 | + | |
| 16209 | + | |
| 16210 | + | |
| 16211 | + | |
| 16212 | + | |
| 16213 | + | |
| 16214 | + | |
| 16215 | + | |
| 16216 | + | |
| 16217 | + | |
| 16218 | + | |
16164 | 16219 | | |
16165 | 16220 | | |
16166 | 16221 | | |
16167 | 16222 | | |
| 16223 | + | |
| 16224 | + | |
| 16225 | + | |
| 16226 | + | |
| 16227 | + | |
| 16228 | + | |
| 16229 | + | |
| 16230 | + | |
| 16231 | + | |
| 16232 | + | |
| 16233 | + | |
| 16234 | + | |
| 16235 | + | |
| 16236 | + | |
| 16237 | + | |
| 16238 | + | |
| 16239 | + | |
| 16240 | + | |
| 16241 | + | |
| 16242 | + | |
| 16243 | + | |
| 16244 | + | |
| 16245 | + | |
| 16246 | + | |
| 16247 | + | |
| 16248 | + | |
| 16249 | + | |
| 16250 | + | |
| 16251 | + | |
| 16252 | + | |
| 16253 | + | |
| 16254 | + | |
| 16255 | + | |
| 16256 | + | |
| 16257 | + | |
| 16258 | + | |
| 16259 | + | |
| 16260 | + | |
| 16261 | + | |
| 16262 | + | |
| 16263 | + | |
| 16264 | + | |
| 16265 | + | |
| 16266 | + | |
| 16267 | + | |
| 16268 | + | |
| 16269 | + | |
| 16270 | + | |
| 16271 | + | |
| 16272 | + | |
| 16273 | + | |
| 16274 | + | |
| 16275 | + | |
| 16276 | + | |
| 16277 | + | |
| 16278 | + | |
| 16279 | + | |
| 16280 | + | |
| 16281 | + | |
| 16282 | + | |
| 16283 | + | |
| 16284 | + | |
| 16285 | + | |
| 16286 | + | |
| 16287 | + | |
| 16288 | + | |
| 16289 | + | |
| 16290 | + | |
| 16291 | + | |
| 16292 | + | |
| 16293 | + | |
| 16294 | + | |
| 16295 | + | |
| 16296 | + | |
| 16297 | + | |
| 16298 | + | |
| 16299 | + | |
| 16300 | + | |
| 16301 | + | |
| 16302 | + | |
| 16303 | + | |
| 16304 | + | |
| 16305 | + | |
| 16306 | + | |
| 16307 | + | |
| 16308 | + | |
| 16309 | + | |
| 16310 | + | |
| 16311 | + | |
| 16312 | + | |
| 16313 | + | |
| 16314 | + | |
| 16315 | + | |
| 16316 | + | |
| 16317 | + | |
| 16318 | + | |
| 16319 | + | |
| 16320 | + | |
| 16321 | + | |
| 16322 | + | |
| 16323 | + | |
| 16324 | + | |
| 16325 | + | |
| 16326 | + | |
16168 | 16327 | | |
16169 | 16328 | | |
16170 | 16329 | | |
| |||
0 commit comments