Skip to content

Commit fef09c1

Browse files
committed
feat(rest): mount the compound-name per-item promotion door POST /meta/:type/:section/:name/publish (#11932)
A compound-named metadata draft could be STAGED through `PUT /meta/:type/:section/:name?mode=draft` (#11712 / PR #11933) and READ BACK through `GET /meta/:type/:section/:name/published` (#7526), and had no per-item REST door to PROMOTE it: `POST /meta/:type/:name/publish` was mounted in one arity only. Writable, readable, not publishable — same caller, same transport. The capability was never missing. Measured against the real `ObjectStackProtocolImplementation` on a seeded `crm/task` draft before the route existed, `publishMetaItem` promoted it normally: it keys the draft on type/name/organization/package and reads the name's spelling nowhere. It was the route that was missing. Both arities now come out of one two-entry registration loop, the same shape the `/published` read twin has carried since #7526, so the compound door inherits the `manage_metadata` gate, the `?package` binding and its repeated-parameter refusal, the write-actor resolution, the organization scoping, the `404 [no_draft]` answer and the `501` envelope unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019siH5jDmk5hrayvfyojUqR
1 parent 1e79aa4 commit fef09c1

10 files changed

Lines changed: 962 additions & 190 deletions

‎.changeset/compound-meta-door-mode-draft.md‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,17 @@ unrecognised `mode=`, an empty `mode=` and no `mode` at all are all unchanged:
2828
they publish, exactly as before. The spelling test is the twin's, `draft`
2929
case-insensitive.
3030

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

3943
**2. A repeated `?mode` is now REFUSED where it was accepted.** This narrows
4044
what the door takes. `?mode=draft&mode=draft` arrives as an array; the
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
'@objectstack/rest': minor
3+
---
4+
5+
feat(rest): mount the compound-name per-item promotion door `POST /api/v1/meta/:type/:section/:name/publish`
6+
7+
**A new public route.** `POST /api/v1/meta/:type/:section/:name/publish` — four
8+
segments after `/meta`, the compound-name arity of the per-item promotion door
9+
that has been mounted as `POST /api/v1/meta/:type/:name/publish` all along. Both
10+
arities now come out of one two-entry registration loop, exactly as the ADR-0033
11+
read twin `GET /api/v1/meta/:type/:section/:name/published` has since #7526.
12+
13+
**What it now makes possible: promoting a compound-named draft over REST, per
14+
item.** A metadata item addressed by a compound name — `views/all_leads`,
15+
`crm/task`, the spelling the SDK documents for `getPublished('lead',
16+
'views/all_leads')` — could already be **staged**
17+
(`PUT /api/v1/meta/:type/:section/:name?mode=draft`, shipped in the
18+
`?mode=draft` entry beside this one) and **read back**
19+
(`GET /api/v1/meta/:type/:section/:name/published`). It could not be promoted:
20+
no registered route matched the four-segment promotion path, so the request
21+
reached the transport's `notFound` and answered `404`, byte-identical to a path
22+
that does not exist. The draft was writable, readable, and not publishable, by
23+
the same caller, over the same transport.
24+
25+
| Request | Before | After |
26+
| --- | --- | --- |
27+
| `POST /meta/object/crm/task/publish` | `404` — no registered route matched | `200`, the staged body is now the live overlay |
28+
| `POST /meta/object/crm_task/publish` | `200` | `200` — unchanged |
29+
30+
The workarounds that entry named remain available and are unchanged:
31+
`POST /packages/:id/publish-drafts` promotes a whole package's drafts at once,
32+
and the runtime dispatcher's own `meta.publish` verb is reachable without any
33+
REST route. What they were not is a **per-item** door.
34+
35+
**Nothing below the route changed, and no accept set widened anywhere else.**
36+
`publishMetaItem` keys the draft on type/name/organization/package and reads the
37+
name's spelling nowhere, so a compound name was always a valid draft key — this
38+
release mounts the route that had been missing, it does not add a capability.
39+
The two arities share one handler, so the compound door inherits, unchanged:
40+
the `manage_metadata` authoring gate (ADR-0066 D1), the `?package=` binding and
41+
its repeated-parameter refusal, the `X-Actor`-ignoring write-actor resolution,
42+
the organization scoping, the `404 [no_draft]` answer when nothing is staged,
43+
and the `501` envelope when a kernel does not implement promotion. The
44+
single-segment door's behaviour is untouched in every one of those respects.
45+
46+
**For SDK callers there is no API change — only a route that now answers.**
47+
`client.meta.publishItem(type, name)` already built this URL: it interpolates
48+
the name unencoded and its own documentation says "Compound names pass through
49+
unencoded, like `getItem`". Calling it with a compound name returned `404`
50+
before this release and promotes the draft after it.

‎content/docs/kernel/contracts/metadata-service.mdx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,11 @@ const draft = await metadataService.get('object', 'opportunity');
420420

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

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

0 commit comments

Comments
 (0)