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
feat(spec,client): declare the publish door's response contract (#7294) (#7356)
`POST /api/v1/meta/:type/:name/publish` is served by `@objectstack/rest`
(`rest-server.ts` registers it and hands `publishMetaItem`'s return straight to
`res.json()`), and had no declaration behind it: `PublishMetaItem` appeared
nowhere under `packages/spec/src/`, and the route was absent from
`plugin-rest-api.zod.ts`'s metadata table. So `version` on the publish response
sat in exactly the state `version` on the save response sat in before #5745 —
the ADR-0008 OCC token, echoed back as `If-Match` to get a 409 instead of a
lost update, on a public wire surface with nothing declaring it.
This carries the #5745 "declared = returned" discipline one door over, with the
same three artifacts:
- `PublishMetaItemResponseSchema` declares the FULL measured body: `success` /
`version` / `seq` required, `message` plus the three conditional side-effect
receipts (`seedApplied` / `materializeApplied` / `projectionApplied`)
optional. Measured from the producer, not assumed — its single response
literal always sets the first three and attaches each receipt only when the
matching side effect ran, so an absent receipt means "did not run", never
"failed".
- The endpoint declaration, matching the five sibling metadata entries. No
`requestSchema`: the body's only read key is `message`, taken only when
already a string, so the route cannot 400 a malformed body and declaring one
would advertise a gate that does not run (#3899).
- `packages/objectql/src/publish-meta-response-conformance.test.ts` — the
producer-side gate mirroring the save door's, driving a real
`publishMetaItem` against a real ObjectQL engine through the schema across
the plain shape and every receipt path.
Also:
- `client.metadata.publishItem()` is typed `Promise<PublishMetaItemResponse>`
and the type re-exported, matching `saveItem` / `SaveMetaItemResponse`. It
resolved to `any` before, for want of a declaration to point at.
- `publishMetaItem`'s own `Promise<...>` annotation omitted `projectionApplied`
while the body assigned it — the same declared-≠-returned gap one layer down.
No behavior change: nothing about the response body moved.
Closes#7294
Claude-Session: https://claude.ai/code/session_01DuV91PKtmJ8UFUtDjqXy5h
Co-authored-by: Claude <noreply@anthropic.com>
|**success**|`boolean`| ✅ | Always true on a 2xx — the draft was promoted. It does NOT cover the best-effort side effects below, each of which reports its own `success`. |
1209
+
|**version**|`string`| ✅ | Content hash of the just-promoted body, and the token the ADR-0008 optimistic-concurrency chain runs on: send it back as the `If-Match` request header on the next write to that item and a concurrent edit is reported as 409 `metadata_conflict` instead of silently overwritten. Opaque to callers — echo it verbatim, never parse it. Currently emitted as `sha256:<64 hex chars>`, but the format is not part of this contract. |
1210
+
|**seq**|`integer`| ✅ | Monotonic sequence number of the `op='publish'` metadata event this promotion appended to the item history (sys_metadata_history.event_seq). Orders writes; unlike `version` it is not an OCC token. |
1211
+
|**seedApplied**|`{ success: boolean; inserted: integer; updated: integer; error?: string; … }`| optional | Outcome of materializing a published `seed` body into data rows. Present ONLY when the published type is `seed` — publishing a seed is what makes its rows live, so the load rides along with the metadata promotion. Best-effort: a seed-load problem is surfaced here, never thrown, so a caller must check `seedApplied.success` instead of assuming the 200 covered the data. Absent on the batch path, which suppresses the per-item apply and loads every seed body in one later pass. |
1212
+
|**materializeApplied**|`{ success: boolean; inserted: integer; updated: integer; error?: string }`| optional | Outcome of the ADR-0086 P2 publish-time materializer — the step that projects the published body into its data-plane row (e.g. `permission` → `sys_permission_set`, under the owning package). Present ONLY when a materializer is registered for this metadata type, which is why it is optional: its absence means "no materializer ran", never "it failed". Best-effort, same contract as `seedApplied`. |
1213
+
|**projectionApplied**|`{ success: boolean; error?: string }`| optional | Outcome of the awaited ADR-0094 mutation projector — the post-persist step that materializes this metadata into its derived data-plane read model. The same receipt `{@link SaveMetaItemResponseSchema}` carries, because the projector runs on BOTH write doors: a direct active save and this draft→active promotion. Present ONLY when a projector is registered for this metadata type. Best-effort — a projector failure is reported here and logged, never thrown. |
1214
+
|**message**|`string`| optional | Human-readable receipt, e.g. `Published draft — type=view, name=cases [seq=3]`. The producer sets it on every publish today; it stays optional to match the producer's own signature and its `SaveMetaItemResponse` twin, and because an absent human-readable string strips no data — the failure mode #5745 exists to prevent. |
0 commit comments