Skip to content

Commit 73da927

Browse files
committed
fix(metadata-protocol): a container on another package's object expands every member under its own name and claims no default
Patch round 1, the seat's answer to the two open questions. On an object a code package owns, a container of another package (or of none) now expands every member under its own name: the bare list as `<object>.<container name>`, and each keyed member (a named list, `listViews`, `formViews`, `form`) as `<object>.<container name>.<key>`, by the spec's own key rule run under that name. None of its views carries `isDefault`. Where the owning package ships `<object>.<container name>`, the bare list stays at the spelling the spec gives it under that name. Claude-Session: https://claude.ai/code/session_017xfMoEjKUuSh2xYB8sCozp Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3551aed commit 73da927

1 file changed

Lines changed: 102 additions & 74 deletions

File tree

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

Lines changed: 102 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -8367,9 +8367,9 @@ export class ObjectStackProtocolImplementation implements
83678367
// [#21334] An upsert by name replaces whatever the merge seated
83688368
// under that name, so the name has to be one the container may
83698369
// write. {@link expandRuntimeViewContainer} decides it: on
8370-
// another package's object a bare `list` expands under the
8371-
// container's own name, never over that package's
8372-
// `<object>.default`.
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.
83738373
if (isView) {
83748374
const byName = new Map<string, unknown>();
83758375
for (const it of items as any[]) {
@@ -16139,30 +16139,40 @@ export class ObjectStackProtocolImplementation implements
1613916139
* kept, unchanged, for every container written before this field was
1614016140
* consulted here.
1614116141
*
16142-
* ## A bare `list` on ANOTHER package's object (#21334)
16143-
*
16144-
* The spec names a bare `list` (one that names no key) `<object>.default`.
16145-
* For a container that belongs to the object's own package that is the
16146-
* object's default view, and it still expands there. For a container
16147-
* saved under any other name, in another package or in none, on an object
16148-
* a code package owns, `<object>.default` is the OTHER package's item:
16149-
* ADR-0005 keys an overlay by its own name, and ADR-0126 rules out a
16150-
* silent override, so a row named `x` may not replace an item named
16151-
* `<object>.default`. Both callers set each expansion by name — the list's
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
1615216153
* inline pass over the merged items, the registry's bare key — so the
16153-
* expansion used to replace the packaged default on the object door
16154-
* (and, on an unscoped kernel, on the by-name read too), wearing the
16155-
* shadowed artifact's `_packageId` and protection.
16156-
*
16157-
* So, on another package's object, the bare `list` expands under the
16158-
* container's own name: `<object>.<container name>`, the spec's own
16159-
* spelling of a `list` that names its key. The qualified form is the one
16160-
* the spec accepts for a ViewItem (`ViewItemNameSchema`); the flat
16161-
* container name on an expanded item is refused there, so the list door
16162-
* would serve it badged invalid. The view container contract
16163-
* (`view.zod.ts`, ADR-0017 §3.2) names the container after its object and
16164-
* states no arm for a name another package owns, which is why the arm
16165-
* taken is the container's own name rather than a refusal at save.
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).
1616616176
*
1616716177
* Every expanded item carries the container's OWN package and, where that
1616816178
* package ships an artifact of the same name, that artifact's envelope —
@@ -16183,23 +16193,25 @@ export class ObjectStackProtocolImplementation implements
1618316193
?? (typeof container.name === 'string' ? container.name : undefined);
1618416194
if (!viewObject) return [];
1618516195
const ownPackageId = this.runtimeViewContainerPackage(type, container, options);
16186-
const ownKey = this.isAnotherPackagesObject(viewObject, ownPackageId)
16187-
? this.bareListOwnKey(container)
16188-
: undefined;
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>[]);
1618916200
const out: Record<string, unknown>[] = [];
16190-
for (const vi of expandViewContainer(viewObject, this.withBareListKey(container, ownKey))) {
16201+
for (const vi of expanded) {
1619116202
// Carry the container's package provenance onto each expanded item
1619216203
// so the package-disable filter and ADR-0048 artifact scoping judge
1619316204
// them by the same owner the container has.
16194-
const item: Record<string, unknown> = { ...(vi as any) };
16195-
if (typeof ownKey === 'string') this.restoreAuthoredBareList(item, viewObject, ownKey);
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;
1619616208
if (ownPackageId !== undefined) item._packageId = ownPackageId;
1619716209
// [#21334] Only the container's own package's artifact lends its
1619816210
// envelope; another package's artifact of this name is not this
1619916211
// item's to wear.
1620016212
const viArtifact = ownPackageId === undefined
1620116213
? undefined
16202-
: this.lookupArtifactItem(type, vi.name, ownPackageId);
16214+
: this.lookupArtifactItem(type, String(item.name), ownPackageId);
1620316215
const ownArtifact = (viArtifact as { _packageId?: unknown } | undefined)?._packageId === ownPackageId
1620416216
? viArtifact
1620516217
: undefined;
@@ -16235,7 +16247,7 @@ export class ObjectStackProtocolImplementation implements
1623516247
* `ownPackageId`. The discriminator is `getPackagedObjectOwner`, the same
1623616248
* "does a code package ship this?" test {@link classifyObjectContribution}
1623716249
* asks. A runtime-authored object has no packaged owner, so a container on
16238-
* one keeps today's `<object>.default`; so does a registry that cannot
16250+
* one keeps today's `<object>.<key>` names; so does a registry that cannot
1623916251
* answer.
1624016252
*/
1624116253
private isAnotherPackagesObject(object: string, ownPackageId: string | undefined): boolean {
@@ -16248,52 +16260,68 @@ export class ObjectStackProtocolImplementation implements
1624816260
}
1624916261

1625016262
/**
16251-
* [#21334] The key a container's bare `list` takes when it may not take
16252-
* `default`: the container's own name. `undefined` when the container has
16253-
* no bare `list` (none, or one that names its own key, which already
16254-
* expands under the name its author gave it). `null` when it has one but no
16255-
* name of its own to expand under, so the bare `list` is not expanded at
16256-
* all rather than under another package's name.
16257-
*/
16258-
private bareListOwnKey(container: Record<string, any>): string | null | undefined {
16259-
const list = container.list;
16260-
if (!list || typeof list !== 'object') return undefined;
16261-
if (typeof list.name === 'string' && list.name !== '') return undefined;
16262-
return typeof container.name === 'string' && container.name !== '' ? container.name : null;
16263-
}
16264-
16265-
/**
16266-
* [#21334] The container the spec expands: unchanged when `ownKey` is
16267-
* `undefined`; its bare `list` naming `ownKey`, so the spec's own key rule
16268-
* (and its in-container de-duplication) spells `<object>.<ownKey>`; or, for
16269-
* `null`, without the bare `list`. A shallow copy — the caller's container
16270-
* is never mutated.
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.
1627116280
*/
16272-
private withBareListKey(
16281+
private expandUnderOwnName(
16282+
type: string,
16283+
object: string,
1627316284
container: Record<string, any>,
16274-
ownKey: string | null | undefined,
16275-
): Record<string, any> {
16276-
if (ownKey === undefined) return container;
16277-
if (ownKey === null) return { ...container, list: undefined };
16278-
return { ...container, list: { ...container.list, name: ownKey } };
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+
});
1627916315
}
1628016316

1628116317
/**
16282-
* [#21334] Take back the `name` {@link withBareListKey} lent the bare
16283-
* `list`, so the expanded item's `config` is the list as authored. Matches
16284-
* only the item that key produced: a `list` item whose `config.name` is
16285-
* `ownKey` and whose name is `<object>.<ownKey>` or the spec's
16286-
* de-duplicated `<object>.<ownKey>_N`.
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.
1628716321
*/
16288-
private restoreAuthoredBareList(item: Record<string, unknown>, object: string, ownKey: string): void {
16289-
const config = item.config as Record<string, unknown> | undefined;
16290-
if (item.viewKind !== 'list' || !config || config.name !== ownKey) return;
16291-
const requested = `${object}.${ownKey}`;
16292-
const name = String(item.name);
16293-
if (!name.startsWith(requested) || !/^(_\d+)?$/.test(name.slice(requested.length))) return;
16294-
const authored = { ...config };
16295-
delete authored.name;
16296-
item.config = authored;
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;
1629716325
}
1629816326

1629916327
/**

0 commit comments

Comments
 (0)