Skip to content

meta.deleteItem sends none of the three carriers the REST reset door reads — no If-Match, no ?state=draft, no ?dropStorage, on either declaration #12181

Description

@os-trump

Measured while implementing #11713 (the meta.saveItem If-Match half). Same defect shape, one method over — the SDK's write surface cannot reach parameters its own server door implements and its sibling first-party client already sends — so it is reported rather than folded in: #11713's ruling is scoped to saveItem and its SaveMetaItemOptions bag, and widening deleteItem is a contract decision of its own.

What was measured

On origin/main at 22c42c9b.

Both deleteItem declarations take exactly (type, name) — no options bag, no header, nothing that becomes one:

$ grep -n "deleteItem: async" packages/client/src/index.ts
955:    deleteItem: async (type: string, name: string): Promise<{ type: string; name: string; deleted: boolean }> => {
5600:    deleteItem: async (type: string, name: string): Promise<{ type: string; name: string; deleted: boolean }> => {

(955 = unscoped ObjectStackClient.meta; 5600 = the environment-scoped ScopedProjectClient.meta twin. Both build a bare path and a method: 'DELETE'.)

The door reads three carriers. DELETE /meta/:type/:name — "Reset metadata item to artifact default", registered at packages/rest/src/rest-server.ts:5766:

// the ADR-0008 OCC pin, rest-server.ts:5831
const ifMatchHeader = req.headers?.['if-match'] ?? req.headers?.['If-Match'];
const parentVersion = typeof ifMatchHeader === 'string'
    ? ifMatchHeader.replace(/^"|"$/g, '')
    : undefined;

// [#6877] `?state=` and the destructive `?dropStorage=`
if (refuseRepeatedQueryParams(req, res, ['state', 'dropStorage'])) return;
const stateParam = typeof req.query?.state === 'string'
    && req.query.state.toLowerCase() === 'draft'
const dropStorage = req.query?.dropStorage === 'true' || req.query?.dropStorage === '1';

parentVersion is threaded into the protocol call at rest-server.ts:5895.

The spec names the header on this exact door, in DeleteMetaItemRequest's parentVersion (packages/spec/src/api/protocol.zod.ts:1150):

ADR-0008 optimistic-concurrency pin: the version token the caller believes is current (on the REST door, the If-Match request header). Present, a concurrent edit is reported as a 409 conflict instead of silently reset; absent = last-write-wins against the current row (Studio's "Reset" button is unpinned).

The sibling first-party client already sends it. MetadataClient.reset in @object-ui/data-objectstack (objectui packages/data-objectstack/src/metadata-client.ts, measured at 090927f) carries both the header and the state parameter:

const qs = options.state === 'draft' ? '?state=draft' : '';
const headers: Record<string, string> = { ...this.headers };
if (options.ifMatch) headers['If-Match'] = options.ifMatch;

So a first-party @objectstack/client caller cannot:

  1. pin a reset — a concurrent edit is silently destroyed instead of answering 409, on a verb whose whole job is destroying an overlay row;
  2. discard only the pending draft (?state=draft) — the SDK's only reachable reset is the full one, which also drops the published overlay;
  3. reach ?dropStorage, whose destructiveness is the reason #6877 gave it a repeated-parameter refusal.

Item 1 is the sharpest: the reset door is the one write where last-write-wins costs the most, and it is the one the spec text explicitly describes as pinnable.

Not taken here

#11713's maintainer ruling (2026-08-25) enumerates ifMatch joining SaveMetaItemOptions on the two saveItem declarations. deleteItem is a different method with no bag at all, and the three carriers above are three separate widenings of the published SDK surface — plausibly rulable separately (a header pin is a data-protection gap; ?state=draft is a feature). Reported rather than taken, exactly as #11713 was reported out of #11391.

Related: #11713 (the saveItem header half), #11391 (the saveItem query half), ADR-0008 for the OCC contract, #6877 for the repeated-parameter refusals on this door.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions