Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions .changeset/compound-meta-door-mode-draft.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,13 +28,17 @@ unrecognised `mode=`, an empty `mode=` and no `mode` at all are all unchanged:
they publish, exactly as before. The spelling test is the twin's, `draft`
case-insensitive.

⚠️ **The draft you can now stage has no per-item REST promotion door in this
arity.** `POST /meta/:type/:name/publish` is mounted for single-segment names
only, while its read twin `GET /meta/:type/:section/:name/published` is mounted
for both — so a compound-named draft is writable and readable over REST and not
promotable there. Until that route exists, promote through
`POST /packages/:id/publish-drafts` (whole-package) or the runtime dispatcher's
own `meta.publish` verb. Tracked in #11932; this release does not change it.
The draft you can now stage **is** promotable per item over REST, as of the
sibling entry in this same release: `POST /meta/:type/:section/:name/publish`
is mounted (#11932). This paragraph used to say the opposite — that the
promotion door existed for single-segment names only, so a compound-named draft
was writable and readable over REST and not promotable there — and it was true
when this entry was written. It is corrected here rather than left standing,
because both entries compile into one release and a reader would otherwise be
told in one paragraph that the door does not exist and in the next that it does.
`POST /packages/:id/publish-drafts` (whole-package) and the runtime dispatcher's
own `meta.publish` verb remain available and unchanged; they were never the
per-item door.

**2. A repeated `?mode` is now REFUSED where it was accepted.** This narrows
what the door takes. `?mode=draft&mode=draft` arrives as an array; the
Expand Down
50 changes: 50 additions & 0 deletions .changeset/compound-meta-publish-door.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
---
'@objectstack/rest': minor
---

feat(rest): mount the compound-name per-item promotion door `POST /api/v1/meta/:type/:section/:name/publish`

**A new public route.** `POST /api/v1/meta/:type/:section/:name/publish` — four
segments after `/meta`, the compound-name arity of the per-item promotion door
that has been mounted as `POST /api/v1/meta/:type/:name/publish` all along. Both
arities now come out of one two-entry registration loop, exactly as the ADR-0033
read twin `GET /api/v1/meta/:type/:section/:name/published` has since #7526.

**What it now makes possible: promoting a compound-named draft over REST, per
item.** A metadata item addressed by a compound name — `views/all_leads`,
`crm/task`, the spelling the SDK documents for `getPublished('lead',
'views/all_leads')` — could already be **staged**
(`PUT /api/v1/meta/:type/:section/:name?mode=draft`, shipped in the
`?mode=draft` entry beside this one) and **read back**
(`GET /api/v1/meta/:type/:section/:name/published`). It could not be promoted:
no registered route matched the four-segment promotion path, so the request
reached the transport's `notFound` and answered `404`, byte-identical to a path
that does not exist. The draft was writable, readable, and not publishable, by
the same caller, over the same transport.

| Request | Before | After |
| --- | --- | --- |
| `POST /meta/object/crm/task/publish` | `404` — no registered route matched | `200`, the staged body is now the live overlay |
| `POST /meta/object/crm_task/publish` | `200` | `200` — unchanged |

The workarounds that entry named remain available and are unchanged:
`POST /packages/:id/publish-drafts` promotes a whole package's drafts at once,
and the runtime dispatcher's own `meta.publish` verb is reachable without any
REST route. What they were not is a **per-item** door.

**Nothing below the route changed, and no accept set widened anywhere else.**
`publishMetaItem` keys the draft on type/name/organization/package and reads the
name's spelling nowhere, so a compound name was always a valid draft key — this
release mounts the route that had been missing, it does not add a capability.
The two arities share one handler, so the compound door inherits, unchanged:
the `manage_metadata` authoring gate (ADR-0066 D1), the `?package=` binding and
its repeated-parameter refusal, the `X-Actor`-ignoring write-actor resolution,
the organization scoping, the `404 [no_draft]` answer when nothing is staged,
and the `501` envelope when a kernel does not implement promotion. The
single-segment door's behaviour is untouched in every one of those respects.

**For SDK callers there is no API change — only a route that now answers.**
`client.meta.publishItem(type, name)` already built this URL: it interpolates
the name unencoded and its own documentation says "Compound names pass through
unencoded, like `getItem`". Calling it with a compound name returned `404`
before this release and promotes the draft after it.
7 changes: 6 additions & 1 deletion content/docs/kernel/contracts/metadata-service.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -420,7 +420,11 @@ const draft = await metadataService.get('object', 'opportunity');

The REST layer mounts package routes under `/api/v1/packages` and per-item metadata
routes under `/api/v1/meta`. Publishing a single metadata item's pending draft is done
via the `/meta/:type/:name/publish` route.
via the `/meta/:type/:name/publish` route — or, for an item addressed by a compound
name such as `views/all_leads`, via the compound arity
`/meta/:type/:section/:name/publish`. Both arities are one handler and behave
identically; the compound spelling is how every other read and write on this surface
addresses a sub-resource.

| Method | Path | Description |
|:---|:---|:---|
Expand All@@ -429,4 +433,5 @@ via the `/meta/:type/:name/publish` route.
| `GET` | `/api/v1/packages/:id` | Get a specific package |
| `DELETE` | `/api/v1/packages/:id` | Delete a package |
| `POST` | `/api/v1/meta/:type/:name/publish` | Promote a metadata item's pending draft to live |
| `POST` | `/api/v1/meta/:type/:section/:name/publish` | The same promotion, for a compound-named item |
| `POST` | `/api/v1/meta/:type/:name/rollback` | Restore a historical version as the live overlay |
Loading
Loading