Skip to content

deleteMetaItem is a declared member whose request schema declares 2 of the 8 members the REST reset door sends, so the call-site cast cannot come off #11679

Description

@huangyiirene

Found while implementing #11145 (the publishMetaItem consumer cleanup). This is the sharper of the two siblings that turned up, because it reads as already done and is not.

Measured on origin/main @ c251ef4213

MetadataProtocoldoes declare the member (packages/spec/src/api/protocol.zod.ts, in the same optional family as getMetaItemLayered):

deleteMetaItem?(request: DeleteMetaItemRequest): Promise<DeleteMetaItemResponse>;

But DeleteMetaItemRequestSchema (line 1121) declares exactly two members:

exportconstDeleteMetaItemRequestSchema=lazySchema(()=>z.object({type: z.string().describe('Metadata type name'),name: z.string().describe('Item name'),}));

The REST reset door (packages/rest/src/rest-server.ts:5704) sends eight:

constresult=await(pasany).deleteMetaItem({type: req.params.type,name: req.params.name,
organizationId,
...(environmentId ? { environmentId } : {}),
...(parentVersion!==undefined ? { parentVersion } : {}),
...(actor ? { actor } : {}),
...(stateParam ? {state: stateParam} : {}),
...(dropStorage ? {dropStorage: true} : {}),});

Six of the eight — organizationId, environmentId, parentVersion, actor, state, dropStorage — are on the wire today with no declaration behind them. The guard at 5640 (if (!(p as any).deleteMetaItem)) is cast for the same reason the call is.

Why the declared member is not enough here, and why that is the trap

#11145 removed the publishMetaItem cast the moment the member was declared, because the cast there was load-bearing on member existence — measured as TS2339, not TS2353. This door is the opposite half: the member exists, so TS2339 is already gone, and what remains is genuinely a request-shape problem. Removing the cast here would surface TS2353 on six keys rather than compiling clean.

That difference matters for triage: a reader scanning for undeclared members finds this door declared and moves on, while its request surface is emptier than any other write door's. organizationId in particular is load-bearing for tenant scoping — the comment above the call site (~5690) records that passing no organization would reset the env-wide row for every tenant, i.e. the one member most worth having a contract is among the undeclared six.

What the decision is

The same one #11006 took for publish, on a bigger surface: declare the request members that are really part of the contract, and decide per member which are transport-level rather than protocol (environmentId is already ruled transport-level by #9741, 2026-08-18, and belongs in packages/rest's TransportScopedMetaRequest envelope, not in the schema). _skipSeedApply-style internal coordination keys, if any exist here, stay out on the same reasoning #11426 recorded for publish.

domain:spec decision; #11145's engine-lane PR deliberately did not touch it — the bounded in-place exemption does not apply, since the correct shape is not pinned by existing evidence. ⛔ Nothing about this door is addressed in that PR.

Related: #11006 (the ruling this mirrors) · #11426 (its spec landing) · #9741 / #9726 (the meta-read schemas' equivalent catch-up) · #9805 (the non-door helper literals) · ADR-0005 (reset-to-artifact-default semantics) · ADR-0076 D9

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions