Filing gate: ① product defect, class (a), a wrong result at a public entry point: the HTTP /api/v1/meta door, measured on a running published 17.7.0. Reader: objectstack triage first-touch (grade and route). The client half is objectstack-ai/objectui#11773 (PR objectstack-ai/objectui#11826, Part of). That card's remaining half waits on this card and will carry Blocked-by: this card.
Dedupe (MCP search_issues, objectstack, open + closed):
None covers the read serving no token.
Filed by the objectui domain:ui execution seat 3 (session_01CGZy1BGCjdN5cXqL9cnvB8) from the objectui#11773 dev report (objectstack-ai/objectui comment 6043958901, out-of-scope finding 1). ⛔ Not graded or routed here; ⛔ not a claim.
What happens
ADR-0008's opt-in OCC on PUT /api/v1/meta/:type/:name works: a stale If-Match is refused with 409 METADATA_CONFLICT. But a client can only send a token it was served, and the draft read serves none. So Studio, or any /meta client, cannot protect the first save after it loads an item: two editors who each load and save once are still last-writer-wins.
Measured by the objectui#11773 dev on published @objectstack/cli 17.7.0 (objectstack dev --seed-admin --fresh --no-watch -p 4773, writable package com.probe.studio, 2026-10-07T16:38Z to 16:41Z):
| Request |
Answer |
PUT /meta/object/pst_ticket?mode=draft&package=…, no If-Match (create) |
200 {"success":true,"version":"hmac-sha256:…","seq":1,"state":"draft",…}, no ETag |
GET /meta/object/pst_ticket?state=draft&package=… |
200 {type, name, sortability, item}. No version key in the envelope or the item, and no ETag |
GET /meta/object/occprobe_ticket (active, cached) |
ETag: "2d68dba9", a cache validator, not the version token |
| PUT draft with a stale token |
409 {"error":"… The version token sent is not the current version (current is hmac-sha256:…).","code":"METADATA_CONFLICT"}. The current version appears only inside the sentence |
any If-Match while no draft row exists (after a publish) |
409 METADATA_CONFLICT, "current is null" |
first draft after a publish, no If-Match |
200 |
Three gaps, all at this door:
- The read serves no token.
GET /meta/:type/:name (with state=draft, and stored-row reads generally) carries no version in its body and no ETag equal to the token. Only the save receipt carries version. On objectstack main bafb58bb, GetMetaItemResponseSchema (packages/spec/src/api/protocol.zod.ts:422) declares type, name, item, sortability and the protection envelope fields, and no version member.
- "Expect no draft" cannot be said. With no draft row, every
If-Match is refused, and omitting it is unguarded. So a create, and the first draft after a publish, cannot be pinned. An If-None-Match: *, or an equivalent, is missing.
- The conflict body has no structured current version. The protocol's conflict refusal sets the actual head on the error, but the REST door serializes only
{error, code}. So a client offering "overwrite" must either parse prose or re-send unguarded. The objectui half re-sends unguarded after a confirmation.
Seam: spec:GetMetaItemResponseSchema (no version member) → runtime:rest-server GET /meta/:type/:name draft branch, plus the PUT door's METADATA_CONFLICT envelope
Done when
- A
/meta item read (at least state=draft, and the stored-row read a client edits from) serves the same version token the save receipt serves, declared in the response schema, as a body member, an ETag, or both.
- A client can make a guarded write that expects no draft row, and a create or a first draft after a publish is refused when a row has appeared.
- The
METADATA_CONFLICT body carries the current version as a structured, schema-declared field.
- Pins at the HTTP door:
- read, then save with the read's token: 200;
- two readers save in turn: the second gets 409;
- expect-no-draft refused once a draft exists;
- the 409 body's version field equals the next read's token;
- control: a write with no
If-Match keeps last-writer-wins.
Note for whoever claims the remaining half of objectui#11773: once a release carries this, the objectui guard records the read's version at each load that today calls forget(). Its unlock criterion is a published release objectui can install, not the merge.
Dedupe words: meta read version token · draft read ETag If-Match · If-None-Match expect no draft · METADATA_CONFLICT current version body
Generated by Claude Code
Filing gate: ① product defect, class (a), a wrong result at a public entry point: the HTTP
/api/v1/metadoor, measured on a running published 17.7.0. Reader: objectstack triage first-touch (grade and route). The client half is objectstack-ai/objectui#11773 (PR objectstack-ai/objectui#11826,Part of). That card's remaining half waits on this card and will carryBlocked-by:this card.Dedupe (MCP
search_issues, objectstack, open + closed):If-Match: ""silently DISABLES optimistic concurrency — a quoted-empty entity-tag is read as "no token" and the guarded write proceeds unguarded #13576 is the positive control: anIf-MatchOCC card on this door, a different defect (an empty entity-tag disabling the guard).None covers the read serving no token.
Filed by the objectui
domain:uiexecution seat 3 (session_01CGZy1BGCjdN5cXqL9cnvB8) from the objectui#11773 dev report (objectstack-ai/objectuicomment6043958901, out-of-scope finding 1). ⛔ Not graded or routed here; ⛔ not a claim.What happens
ADR-0008's opt-in OCC on
PUT /api/v1/meta/:type/:nameworks: a staleIf-Matchis refused with409 METADATA_CONFLICT. But a client can only send a token it was served, and the draft read serves none. So Studio, or any/metaclient, cannot protect the first save after it loads an item: two editors who each load and save once are still last-writer-wins.Measured by the objectui#11773 dev on published
@objectstack/cli17.7.0 (objectstack dev --seed-admin --fresh --no-watch -p 4773, writable packagecom.probe.studio, 2026-10-07T16:38Z to 16:41Z):PUT /meta/object/pst_ticket?mode=draft&package=…, noIf-Match(create){"success":true,"version":"hmac-sha256:…","seq":1,"state":"draft",…}, noETagGET /meta/object/pst_ticket?state=draft&package=…{type, name, sortability, item}. No version key in the envelope or the item, and noETagGET /meta/object/occprobe_ticket(active, cached)ETag: "2d68dba9", a cache validator, not the version token{"error":"… The version token sent is not the current version (current is hmac-sha256:…).","code":"METADATA_CONFLICT"}. The current version appears only inside the sentenceIf-Matchwhile no draft row exists (after a publish)METADATA_CONFLICT, "current is null"If-MatchThree gaps, all at this door:
GET /meta/:type/:name(withstate=draft, and stored-row reads generally) carries no version in its body and noETagequal to the token. Only the save receipt carriesversion. On objectstackmainbafb58bb,GetMetaItemResponseSchema(packages/spec/src/api/protocol.zod.ts:422) declarestype,name,item,sortabilityand the protection envelope fields, and no version member.If-Matchis refused, and omitting it is unguarded. So a create, and the first draft after a publish, cannot be pinned. AnIf-None-Match: *, or an equivalent, is missing.{error, code}. So a client offering "overwrite" must either parse prose or re-send unguarded. The objectui half re-sends unguarded after a confirmation.Seam: spec:GetMetaItemResponseSchema (no version member) → runtime:rest-server
GET /meta/:type/:namedraft branch, plus thePUTdoor'sMETADATA_CONFLICTenvelopeDone when
/metaitem read (at leaststate=draft, and the stored-row read a client edits from) serves the same version token the save receipt serves, declared in the response schema, as a body member, anETag, or both.METADATA_CONFLICTbody carries the current version as a structured, schema-declared field.If-Matchkeeps last-writer-wins.Note for whoever claims the remaining half of objectui#11773: once a release carries this, the objectui guard records the read's version at each load that today calls
forget(). Its unlock criterion is a published release objectui can install, not the merge.Dedupe words: meta read version token · draft read ETag If-Match · If-None-Match expect no draft · METADATA_CONFLICT current version body
Generated by Claude Code