Skip to content

Commit 535d1d2

Browse files
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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
fix(metadata-protocol): a view container saved for an object another package ships no longer replaces that package's views or its default
6+
7+
Clause-②: no
8+
9+
- **What was wrong.** A runtime view container expands each member to `<object>.<key>`. A `list` that names no key becomes `<object>.default`, a `form` becomes `<object>.form`, and every member that names a key uses 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 `GET /api/v1/meta/view?object=<object>`. The replacements were still stamped with the shipping package's `_packageId` and `_provenance: 'package'`.
10+
- On an environment-scoped kernel, the by-name read `GET /api/v1/meta/view/<name>` kept the packaged view, so the two reads disagreed.
11+
- On an unscoped kernel, the by-name read served the replacement too, for a container saved into a package or environment-wide.
12+
- The container's own default kept `isDefault: true`. It either replaced the object's default view or stood beside it as a second list default.
13+
- **What it does now.** For an object a code package ships, a container that belongs to another package, or to none, expands every member under its own name:
14+
- a `list` that names no key becomes `<object>.<container name>`;
15+
- every other member becomes `<object>.<container name>.<key>`. That covers a `list` that names its key, each `listViews` and `formViews` entry, and `form`.
16+
17+
None of these views carries `isDefault`. Every name the shipping package serves answers its packaged view on both reads, unchanged, and the only `isDefault` views the object lists are the shipping package's.
18+
- **One exception.** When the shipping package itself serves `<object>.<container name>` (a container named after one of that package's keys), the container's default list becomes `<object>.<container name>.<container name>` instead.
19+
- **A container with no name of its own** expands nothing on such an object.
20+
- **What these views carry.** The container's own package as `_packageId` (none for a package-less container), and no other package's `_provenance` or protection envelope.
21+
- **What stays.** Three kinds of container expand exactly as before, `isDefault` included:
22+
- a container bound to the package that ships the object;
23+
- a package-less overlay of that package's own container, saved under that container's name;
24+
- a container on an object no code package ships.
25+
26+
A write to `<object>.default` by its own name still overrides it on both reads.
27+
- **What changes for a caller.** Such a container's views are now served under new names:
28+
- its default list as `<object>.<container name>`, instead of `<object>.default`;
29+
- each keyed member as `<object>.<container name>.<key>`, instead of `<object>.<key>`.
30+
31+
A navigation `viewName` or a form-action `target` that used an old name to reach one of these views now reaches the shipping package's view. Use the new name instead.

‎packages/metadata-protocol/src/protocol.ts‎

Lines changed: 170 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8363,6 +8363,13 @@ export class ObjectStackProtocolImplementation implements
83638363
// through), and re-deriving here from the row this call just
83648364
// read is a byte-identical, never-stale restatement of the
83658365
// same items — not a duplicate.
8366+
//
8367+
// [#21334] An upsert by name replaces whatever the merge seated
8368+
// under that name, so the name has to be one the container may
8369+
// write. {@link expandRuntimeViewContainer} decides it: on
8370+
// another package's object every name a container expands
8371+
// derives from the container's own name, never one of that
8372+
// package's `<object>.<key>` names.
83668373
if (isView) {
83678374
const byName = new Map<string, unknown>();
83688375
for (const it of items as any[]) {
@@ -16131,6 +16138,45 @@ export class ObjectStackProtocolImplementation implements
1613116138
* expanded under the WRONG key or not at all. The three-deep fallback is
1613216139
* kept, unchanged, for every container written before this field was
1613316140
* consulted here.
16141+
*
16142+
* ## A container on ANOTHER package's object (#21334)
16143+
*
16144+
* The spec names every expanded view `<object>.<key>`: a bare `list` (one
16145+
* that names no key) takes `<object>.default`, a `form` `<object>.form`,
16146+
* and each named member its own key. For a container of the object's own
16147+
* package those are that package's names, and it still expands there. For
16148+
* a container saved under any other name, in another package or in none,
16149+
* on an object a code package owns, they are the OTHER package's names:
16150+
* ADR-0005 keys an overlay by its own name, and ADR-0126 rules out a silent
16151+
* override, so a row named `x` may not replace an item named
16152+
* `<object>.<key>`. Both callers set each expansion by name — the list's
16153+
* inline pass over the merged items, the registry's bare key — so the
16154+
* expansion used to replace the packaged view on the object door (and, on
16155+
* an unscoped kernel, on the by-name read too), wearing the shadowed
16156+
* artifact's `_packageId` and protection.
16157+
*
16158+
* So, on another package's object, every name the container expands
16159+
* derives from its own name (triage's ruling, and the seat's answer that
16160+
* extends it to the keyed members): the bare `list` is
16161+
* `<object>.<container name>`, and every other member is
16162+
* `<object>.<container name>.<key>` — the spec's own key rule and its
16163+
* in-container de-duplication, run under the container's name (see
16164+
* {@link expandUnderOwnName}). The qualified forms are the ones the spec
16165+
* accepts for a ViewItem (`ViewItemNameSchema`); the flat container name on
16166+
* an expanded item is refused there, so the list door would serve it
16167+
* badged invalid. The view container contract (`view.zod.ts`, ADR-0017
16168+
* §3.2) names the container after its object and states no arm for a name
16169+
* another package owns, which is why the arm taken is the container's own
16170+
* name rather than a refusal at save.
16171+
*
16172+
* Such a container adds views to the object; it never declares the
16173+
* object's default, so none of its views carries `isDefault` — the
16174+
* switcher's default stays the owning package's (the by-name override and
16175+
* a user's own saved default are the routes that change it).
16176+
*
16177+
* Every expanded item carries the container's OWN package and, where that
16178+
* package ships an artifact of the same name, that artifact's envelope —
16179+
* never the envelope of an artifact another package ships.
1613416180
*/
1613516181
private expandRuntimeViewContainer(
1613616182
type: string,
@@ -16146,25 +16192,138 @@ export class ObjectStackProtocolImplementation implements
1614616192
?? container?.form?.data?.object
1614716193
?? (typeof container.name === 'string' ? container.name : undefined);
1614816194
if (!viewObject) return [];
16195+
const ownPackageId = this.runtimeViewContainerPackage(type, container, options);
16196+
const crossPackage = this.isAnotherPackagesObject(viewObject, ownPackageId);
16197+
const expanded: ReadonlyArray<Record<string, unknown>> = crossPackage
16198+
? this.expandUnderOwnName(type, viewObject, container, ownPackageId)
16199+
: (expandViewContainer(viewObject, container) as unknown as Record<string, unknown>[]);
1614916200
const out: Record<string, unknown>[] = [];
16150-
for (const vi of expandViewContainer(viewObject, container)) {
16201+
for (const vi of expanded) {
1615116202
// Carry the container's package provenance onto each expanded item
1615216203
// so the package-disable filter and ADR-0048 artifact scoping judge
1615316204
// them by the same owner the container has.
16154-
const item: Record<string, unknown> = { ...(vi as any) };
16155-
if (container._packageId !== undefined && item._packageId === undefined) {
16156-
item._packageId = container._packageId;
16157-
}
16158-
const viArtifact = this.lookupArtifactItem(
16159-
type,
16160-
vi.name,
16161-
(item._packageId as string | undefined) ?? options.packageId ?? undefined,
16162-
);
16163-
out.push(mergeArtifactProtection(item, viArtifact) as Record<string, unknown>);
16205+
const item: Record<string, unknown> = { ...vi };
16206+
// [#21334] Not the object's default: its owning package's is.
16207+
if (crossPackage) delete item.isDefault;
16208+
if (ownPackageId !== undefined) item._packageId = ownPackageId;
16209+
// [#21334] Only the container's own package's artifact lends its
16210+
// envelope; another package's artifact of this name is not this
16211+
// item's to wear.
16212+
const viArtifact = ownPackageId === undefined
16213+
? undefined
16214+
: this.lookupArtifactItem(type, String(item.name), ownPackageId);
16215+
const ownArtifact = (viArtifact as { _packageId?: unknown } | undefined)?._packageId === ownPackageId
16216+
? viArtifact
16217+
: undefined;
16218+
out.push(mergeArtifactProtection(item, ownArtifact) as Record<string, unknown>);
1616416219
}
1616516220
return out;
1616616221
}
1616716222

16223+
/**
16224+
* [#21334] The package a runtime view container row belongs to: the
16225+
* package its row is bound to, else — for a package-less row that is the
16226+
* name-keyed overlay of a packaged item (ADR-0005), such as a tenant's
16227+
* overlay of a package's `<object>` container — the package of the
16228+
* artifact it overlays, which is the slot the package-aware merge seats
16229+
* it in. `undefined` for a package-less row that overlays nothing.
16230+
*/
16231+
private runtimeViewContainerPackage(
16232+
type: string,
16233+
container: Record<string, any>,
16234+
options: { packageId?: string | null },
16235+
): string | undefined {
16236+
for (const bound of [container._packageId, options.packageId]) {
16237+
if (typeof bound === 'string' && bound !== '' && bound !== 'sys_metadata') return bound;
16238+
}
16239+
if (typeof container.name !== 'string' || container.name === '') return undefined;
16240+
const overlaid = (this.lookupArtifactItem(type, container.name) as { _packageId?: unknown } | undefined)
16241+
?._packageId;
16242+
return typeof overlaid === 'string' && overlaid !== '' ? overlaid : undefined;
16243+
}
16244+
16245+
/**
16246+
* [#21334] True when a code package owns `object` and it is not
16247+
* `ownPackageId`. The discriminator is `getPackagedObjectOwner`, the same
16248+
* "does a code package ship this?" test {@link classifyObjectContribution}
16249+
* asks. A runtime-authored object has no packaged owner, so a container on
16250+
* one keeps today's `<object>.<key>` names; so does a registry that cannot
16251+
* answer.
16252+
*/
16253+
private isAnotherPackagesObject(object: string, ownPackageId: string | undefined): boolean {
16254+
const registry: any = (this.engine as any)?.registry;
16255+
const owner = typeof registry?.getPackagedObjectOwner === 'function'
16256+
? registry.getPackagedObjectOwner(object)
16257+
: undefined;
16258+
const ownerPackageId: unknown = owner?.packageId;
16259+
return typeof ownerPackageId === 'string' && ownerPackageId !== '' && ownerPackageId !== ownPackageId;
16260+
}
16261+
16262+
/**
16263+
* [#21334] Expand a container on another package's object under its own
16264+
* name. The spec's expander runs with `<object>.<container name>` as its
16265+
* base, so every member it knows — today a named `list`, `listViews`,
16266+
* `formViews`, `form` — comes out as `<object>.<container name>.<key>`,
16267+
* de-duplicated by the spec's own rule, and a member kind the spec adds
16268+
* later is placed the same way. Each item's `object` is set back to the
16269+
* object it binds.
16270+
*
16271+
* The bare `list` is lent the container's name as its key, then served as
16272+
* `<object>.<container name>` itself, its `config` the list as authored.
16273+
* Where the owning package ships that very name (a container named after
16274+
* one of that package's keys), it stays at the spelling the spec gave it,
16275+
* `<object>.<container name>.<container name>`, so it never takes the
16276+
* packaged view's name.
16277+
*
16278+
* A container with no name of its own has nothing to expand under, and
16279+
* expands nothing.
16280+
*/
16281+
private expandUnderOwnName(
16282+
type: string,
16283+
object: string,
16284+
container: Record<string, any>,
16285+
ownPackageId: string | undefined,
16286+
): Record<string, unknown>[] {
16287+
const ownName = typeof container.name === 'string' && container.name !== '' ? container.name : undefined;
16288+
if (ownName === undefined) return [];
16289+
const under = `${object}.${ownName}`;
16290+
const expandAt = under;
16291+
const list = container.list;
16292+
const bare = !!list && typeof list === 'object' && !(typeof list.name === 'string' && list.name !== '');
16293+
const source = bare ? { ...container, list: { ...list, name: ownName } } : container;
16294+
const bareSpelled = `${expandAt}.${ownName}`;
16295+
const underIsShipped = this.isShippedByAnotherPackage(type, under, ownPackageId);
16296+
return expandViewContainer(expandAt, source).map((vi) => {
16297+
const item: Record<string, any> = { ...vi, object };
16298+
const name = String(item.name);
16299+
const fromBare = bare
16300+
&& item.viewKind === 'list'
16301+
&& item.config?.name === ownName
16302+
&& name.startsWith(bareSpelled)
16303+
&& /^(_\d+)?$/.test(name.slice(bareSpelled.length));
16304+
if (fromBare) {
16305+
const authored = { ...item.config };
16306+
delete authored.name;
16307+
item.config = authored;
16308+
if (!underIsShipped) {
16309+
item.name = under;
16310+
delete item._diagnostics;
16311+
}
16312+
}
16313+
return item;
16314+
});
16315+
}
16316+
16317+
/**
16318+
* [#21334] True when a code package other than `ownPackageId` ships an
16319+
* artifact named `name` — the one case where the bare list's own name is
16320+
* not free to take.
16321+
*/
16322+
private isShippedByAnotherPackage(type: string, name: string, ownPackageId: string | undefined): boolean {
16323+
const shipped = (this.lookupArtifactItem(type, name) as { _packageId?: unknown } | undefined)?._packageId;
16324+
return typeof shipped === 'string' && shipped !== '' && shipped !== ownPackageId;
16325+
}
16326+
1616816327
/**
1616916328
* [#7736] Register an aggregated `defineView` container's expansion
1617016329
* ({@link expandRuntimeViewContainer}) into the SchemaRegistry — at the

0 commit comments

Comments
 (0)