You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
meta.saveItem's own docstring tells the caller to echo version back as an If-Match header — the SDK offers no way to send one, on either declaration #11713
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:
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.
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.
Source: maintainer, 2026-08-25, live PM chat (decision-inbox batch 3 review, session session_01KWRU3s15AJz7PGW7a7wdCh), verbatim: 「同意」 — accepting the batch's presented recommendation for this card: A.
Alignment check from the analysis's stated gap: verify the ifMatch semantics match @object-ui/data-objectstack's MetadataClient wording exactly — two first-party clients, one behaviour.
C (docs keep teaching an impossible move) is dead either way; if implementation hits an unexpected wall, the fallback is B (remove the three doc pointers in the same PR), never C.
Clause-②: yes (widens the published SDK surface) — contract-review tier on the implementation.
State: needs-user-decision → pm:queue in the same write; domain:cli stands.
Activity
os-zhuang commentedon Aug 24, 2026
分诊(session
session_01LrqnyLhY3kYBBRird4peBT,2026-08-24):入决策箱(needs-user-decision+domain:cli,type Feature)。#11391 的裁决逐项列举了恰好三个 query 参数并明令「report rather than silently widen」—— 本卡是第四个载体(请求头),被刻意围栏在外,继承不适用,须单独裁。四棱卡面块如下。os-decision-facets
一句话问题:SDK 自己的文档告诉调用者「把
version用If-Match头带回来,就能防止两个人同时改一条元数据时互相覆盖」,但saveItem根本没有地方放这个头 —— 照文档做的人,并发编辑仍然静默互相覆盖,而不是收到 409 冲突提示。选项 × 真实代价:
saveItem加ifMatch选项(放进 The first-party SDK'smeta.saveItemsends no query string at all, so the?force=truethe destructive 409 prescribes is unreachable through@objectstack/clienton either REST door #11391 已落地的SaveMetaItemOptions袋):文档说的能力真正可用;代价 = 已发布 SDK 公开面第四次加宽(超出 The first-party SDK'smeta.saveItemsends no query string at all, so the?force=truethe destructive 409 prescribes is unreachable through@objectstack/clienton either REST door #11391 裁定的三参数围栏),需同步两处声明 + headers 级 pin。@object-ui/data-objectstack的MetadataClient)已经带ifMatch—— 两个第一方客户端能力不对齐。四棱:
① 实际业务拉动:并发覆盖是真实数据丢失场景;服务端
If-Match→parentVersion已上线,同门客户端已实测需要它 —— 拉动是实的,不是投机面。② 项目长远合理性:ADR-0008 乐观并发是平台既定契约;第一方 SDK 是该契约的正门,正门缺口比旁门(raw HTTP)长期更贵。A 与 #11391 的修复形状同族(同一个 options 袋),不是新形态。
③ 防 AI 犯错:今天的形态是最坏的 —— 文档声明了、类型系统不拒绝、运行时静默覆盖;AI 照文档生成的代码看起来对、实际丢数据。A 让声明=强制;B 至少消灭假声明。
④ 创业阶段不扩散:A 是一个可选参数进既有袋,边际极小;真正的扩散风险(query 参数枚举)已被 #11391 围栏,本项不破栏,只是栏外单独一票。
推荐:A(
ifMatch进SaveMetaItemOptions,双声明同步,headers pin 补上);回退 B(同 PR 删三处指引,绝不留 C)。置信缺口:未测有多少外部消费者已在用 raw HTTP 绕行(A 不破坏他们);未测MetadataClient与本 SDK 的ifMatch语义是否逐字一致(实现时对齐)。实施 PR 属 Clause-②(扩大公开面)⇒ 契约复审档位。低摩擦裁决格式:回「A」/「B」即可;「A,但 X」按 A 落地并记 X。
Generated by Claude Code
os-support-ai commentedon Aug 25, 2026
Maintainer ruling — A:
ifMatchjoinsSaveMetaItemOptionsSource: maintainer, 2026-08-25, live PM chat (decision-inbox batch 3 review, session
session_01KWRU3s15AJz7PGW7a7wdCh), verbatim: 「同意」 — accepting the batch's presented recommendation for this card: A.Implementation shape, as presented and accepted:
ifMatchas an optional member of theSaveMetaItemOptionsbag The first-party SDK'smeta.saveItemsends no query string at all, so the?force=truethe destructive 409 prescribes is unreachable through@objectstack/clienton either REST door #11391 landed (same family as the two sibling methods atindex.ts:4689/4787), wired to theIf-Matchrequest header; bothsaveItemdeclarations stay in step.meta.saveItemsends no query string at all, so the?force=truethe destructive 409 prescribes is unreachable through@objectstack/clienton either REST door #11391 pins measure the URL only).ifMatchsemantics match@object-ui/data-objectstack'sMetadataClientwording exactly — two first-party clients, one behaviour.Clause-②: yes (widens the published SDK surface) — contract-review tier on the implementation.
State:
needs-user-decision→pm:queuein the same write;domain:clistands.Generated by Claude Code