Measured while implementing #11391. Same defect shape as that card — the SDK prescribing something its own surface cannot do — one parameter over, so filed separately rather than folded: #11391's ruling enumerated exactly three query parameters (force, packageId, mode) and this is a fourth carrier, a request header, which is a contract decision of its own.
What was measured
The docstring directly above meta.saveItem in packages/client/src/index.ts reads:
The resolved version is the ADR-0008 optimistic-concurrency token: echo it back as the If-Match request header on the next write to the same item and a concurrent edit is reported as 409 metadata_conflict instead of silently overwriting.
Neither saveItem declaration accepts a header, an ifMatch option, or anything else that becomes one:
$ grep -n "If-Match\|ifMatch" packages/client/src/index.ts
696: * echo it back as the `If-Match` request header on the next write to the
939: * `If-Match` on the next write to the item. It is nameable here only since
1295: * `version` is the ADR-0008 OCC token (echo as `If-Match`), and the
4689: opts?: { ifMatch?: string },
4698: if (opts?.ifMatch) headers['If-Match'] = String(opts.ifMatch);
4787: opts?: { ifMatch?: string },
4792: if (opts?.ifMatch) headers['If-Match'] = String(opts.ifMatch);
Lines 696 / 939 / 1295 are the three places the SDK tells a caller to use If-Match. Lines 4689–4792 are two other methods that actually implement it — so this is an absent option on saveItem, not a client that cannot send the header.
The server side is live: the REST PUT handler reads it and threads it as parentVersion:
const ifMatchHeader = req.headers?.['if-match'] ?? req.headers?.['If-Match'];
const parentVersion = typeof ifMatchHeader === 'string'
? ifMatchHeader.replace(/^"|"$/g, '')
: undefined;
So the ADR-0008 optimistic-concurrency protection the docstring describes is reachable over raw HTTP and unreachable through @objectstack/client — a first-party SDK caller who follows the instruction has nowhere to put the token, and their concurrent edit silently overwrites instead of answering 409 metadata_conflict. The sibling first-party client MetadataClient in @object-ui/data-objectstack carries ifMatch in its own save options for exactly this reason.
Why it was not folded into #11391
That card's ruling names the three query parameters and the options bag that carries them. If-Match is a header rather than a query parameter, and adding it widens the published SDK surface a fourth time beyond what was ruled — so it is reported rather than taken. The bag landed by #11391 (SaveMetaItemOptions) is the natural place for it if that is ruled in.
Where it would land
packages/client/src/index.ts — both saveItem declarations, which must stay in step — plus pins in packages/client/src/client.test.ts on the headers the methods build (the existing #11391 pins measure the URL only).
Related: #11391 (the query-string half), and ADR-0008 for the OCC contract itself.
Measured on origin/main at 4ceae8ab0.
Generated by Claude Code
Generated by Claude Code
Measured while implementing #11391. Same defect shape as that card — the SDK prescribing something its own surface cannot do — one parameter over, so filed separately rather than folded: #11391's ruling enumerated exactly three query parameters (
force,packageId,mode) and this is a fourth carrier, a request header, which is a contract decision of its own.What was measured
The docstring directly above
meta.saveIteminpackages/client/src/index.tsreads:Neither
saveItemdeclaration accepts a header, anifMatchoption, or anything else that becomes one:Lines 696 / 939 / 1295 are the three places the SDK tells a caller to use
If-Match. Lines 4689–4792 are two other methods that actually implement it — so this is an absent option onsaveItem, not a client that cannot send the header.The server side is live: the REST
PUThandler reads it and threads it asparentVersion:So the ADR-0008 optimistic-concurrency protection the docstring describes is reachable over raw HTTP and unreachable through
@objectstack/client— a first-party SDK caller who follows the instruction has nowhere to put the token, and their concurrent edit silently overwrites instead of answering 409metadata_conflict. The sibling first-party clientMetadataClientin@object-ui/data-objectstackcarriesifMatchin its own save options for exactly this reason.Why it was not folded into #11391
That card's ruling names the three query parameters and the options bag that carries them.
If-Matchis a header rather than a query parameter, and adding it widens the published SDK surface a fourth time beyond what was ruled — so it is reported rather than taken. The bag landed by #11391 (SaveMetaItemOptions) is the natural place for it if that is ruled in.Where it would land
packages/client/src/index.ts— bothsaveItemdeclarations, which must stay in step — plus pins inpackages/client/src/client.test.tson the headers the methods build (the existing #11391 pins measure the URL only).Related: #11391 (the query-string half), and ADR-0008 for the OCC contract itself.
Measured on
origin/mainat4ceae8ab0.Generated by Claude Code
Generated by Claude Code