Measured while implementing #11713 (the meta.saveItemIf-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:
constqs=options.state==='draft' ? '?state=draft' : '';constheaders: Record<string,string>={ ...this.headers};if(options.ifMatch)headers['If-Match']=options.ifMatch;So a first-party @objectstack/client caller cannot:
- pin a reset — a concurrent edit is silently destroyed instead of answering 409, on a verb whose whole job is destroying an overlay row;
- discard only the pending draft (
?state=draft) — the SDK's only reachable reset is the full one, which also drops the published overlay; - 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
Measured while implementing #11713 (the
meta.saveItemIf-Matchhalf). 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 tosaveItemand itsSaveMetaItemOptionsbag, and wideningdeleteItemis a contract decision of its own.What was measured
On
origin/mainat22c42c9b.Both
deleteItemdeclarations take exactly(type, name)— no options bag, no header, nothing that becomes one:(955 = unscoped
ObjectStackClient.meta; 5600 = the environment-scopedScopedProjectClient.metatwin. Both build a bare path and amethod: 'DELETE'.)The door reads three carriers.
DELETE /meta/:type/:name— "Reset metadata item to artifact default", registered atpackages/rest/src/rest-server.ts:5766:parentVersionis threaded into the protocol call atrest-server.ts:5895.The spec names the header on this exact door, in
DeleteMetaItemRequest'sparentVersion(packages/spec/src/api/protocol.zod.ts:1150):The sibling first-party client already sends it.
MetadataClient.resetin@object-ui/data-objectstack(objectuipackages/data-objectstack/src/metadata-client.ts, measured at090927f) carries both the header and the state parameter:So a first-party
@objectstack/clientcaller cannot:?state=draft) — the SDK's only reachable reset is the full one, which also drops the published overlay;?dropStorage, whose destructiveness is the reason#6877gave 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
ifMatchjoiningSaveMetaItemOptionson the twosaveItemdeclarations.deleteItemis 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=draftis a feature). Reported rather than taken, exactly as #11713 was reported out of #11391.Related: #11713 (the
saveItemheader half), #11391 (thesaveItemquery half), ADR-0008 for the OCC contract,#6877for the repeated-parameter refusals on this door.Generated by Claude Code