Skip to content

meta(/meta door): the draft read serves no version token, so no client can pin its first draft save with If-Match; the 409 METADATA_CONFLICT body names the current version only in prose #22114

Description

@objectstack-fleet

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:

  1. 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.
  2. "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.
  3. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:specpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions