|
| 1 | +--- |
| 2 | +'@objectstack/spec': major |
| 3 | +'@objectstack/metadata-protocol': major |
| 4 | +'@objectstack/objectql': major |
| 5 | +--- |
| 6 | + |
| 7 | +refactor(spec)!: `api` is code-only — withdraw a runtime create door the endpoint matcher could never read (#5488, ADR-0049 remove side) |
| 8 | + |
| 9 | +<!-- adr-0087: registered api-runtime-create-withdrawn --> |
| 10 | + |
| 11 | +**FROM → TO:** `PUT /api/v1/meta/api/{name}` (200 "Saved") → declare the endpoint as a |
| 12 | +stack artifact (`**/*.api.ts`, or `defineStack({ apis })`) and ship it through |
| 13 | +`publishPackage`. The runtime write now answers **403 `NOT_CREATABLE`**, in `?mode=draft` |
| 14 | +as well as direct-active. The artifact route is **unchanged** — a `**/*.api.ts` file valid |
| 15 | +before this release is valid after it, byte for byte. |
| 16 | + |
| 17 | +`DEFAULT_METADATA_TYPE_REGISTRY`'s `api` entry declared `allowRuntimeCreate: true` and the |
| 18 | +runtime never honoured it. Measured on a real showcase boot (`objectstack dev --fresh`, 47 |
| 19 | +plugins): |
| 20 | + |
| 21 | +``` |
| 22 | +PUT /api/v1/meta/api/e8_backdoor → 200 {"success":true,…,"message":"Saved …"} |
| 23 | +GET /api/v1/apps/showcase/backdoor → 404 (anonymous AND authenticated) |
| 24 | +``` |
| 25 | + |
| 26 | +…and **no** `[EndpointMatcher] … EXCLUDED` line anywhere in the boot log: the endpoint was |
| 27 | +not gated out, it was never in the index at all. The serving criterion belongs to |
| 28 | +`IMetadataService.matchEndpoint` → `EndpointMatcher` → `MetadataManager.listForIndex('api')`, |
| 29 | +which reads the manager's own registry plus its registered loaders |
| 30 | +(`["filesystem","memory"]` on dev/serve). A runtime write lands in `sys_metadata`, which is |
| 31 | +in neither. So the declaration promised a capability that could not exist. |
| 32 | + |
| 33 | +A declared-but-unhonoured capability is ADR-0049 false compliance, and "answers Saved, then |
| 34 | +404s forever" is its most dangerous shape for the AI authors ADR-0033 targets. The |
| 35 | +maintainer ruled REMOVE on 2026-08-07 rather than converge the read path: making the matcher |
| 36 | +read `sys_metadata` re-opens cache, invalidation, tenancy and the ADR-0110 D3 |
| 37 | +miss-vs-outage distinction on a new read path, and there is no business pull for |
| 38 | +Studio-authored endpoints today — 17.x serves declarative endpoints through stack artifacts, |
| 39 | +which is what showcase uses (#5040 E8, LIVE). |
| 40 | + |
| 41 | +## The retirement kit |
| 42 | + |
| 43 | +- **`allowRuntimeCreate: false`** on the `api` registry entry. With `allowOrgOverride` |
| 44 | + already `false`, the type is now **code-only** — the `job` / `agent` / `capability` shape — |
| 45 | + so the existing #5086 inlet refuses before persistence, on every kernel, with |
| 46 | + `code: 'NOT_CREATABLE'`, `status: 403` and a prescription derived from the entry's own |
| 47 | + `filePatterns[0]`. No new refusal mechanism was written for this. |
| 48 | +- **`gateApiDraftsForPublish` is retired** (`metadata-protocol`), together with its nine |
| 49 | + tests and the `PUBLISH_DRAFTS_NAMESPACE_REMEDY` string only it appended. It landed two |
| 50 | + days earlier in PR #5279 and is removed **deliberately and on the record**, not lost in a |
| 51 | + refactor: it gated a draft→active promotion into a state the matcher can never read, and |
| 52 | + with the inlet closed no `api` draft can exist for it to judge. The in-place comment at |
| 53 | + its old call site carries the reasoning. |
| 54 | +- **The `metadata-plugin.zod.ts` decision block is rewritten as a recorded overturn.** It |
| 55 | + used to record CODE-ONLY as "considered and rejected"; its three bullets are kept verbatim |
| 56 | + with what became of each, so the reversal is auditable rather than silently contradicted. |
| 57 | +- **The `api` create seed is removed** and `api` joins `KNOWN_UNSEEDED`. A pre-filled "New |
| 58 | + API Endpoint" form whose save can only 403 is the UI half of the same false compliance. |
| 59 | +- **Pins, not deletions.** The two #5271 tripwire pins that asserted |
| 60 | + `allowRuntimeCreate: true` are **replaced** by retirement pins asserting the new verdict — |
| 61 | + their comments predicted this exact consequence, and both predictions were correct. Every |
| 62 | + rejection case asserts `code` **and** `status` (ADR-0112 envelope), never `toThrow()` |
| 63 | + alone (#6142). |
| 64 | + |
| 65 | +## What did NOT change |
| 66 | + |
| 67 | +`validateApiEndpointDeclarations` / `identityFreeEndpointGateFailure` remain the one judge |
| 68 | +of what is servable, on the route that serves: the stack schema, `publishPackage` (#5189), |
| 69 | +and again at load in `buildEndpointIndex` (PR #5203). ADR-0121's "publish REJECTS" ruling is |
| 70 | +intact. `deleteMetaItem` stays ungated so pre-existing rows can be cleaned up, and |
| 71 | +`OS_METADATA_WRITABLE=api` remains the single operator escape hatch — note it unlocks the |
| 72 | +**write** only; the endpoint still will not be served, which is why it is a diagnostic |
| 73 | +rather than a workaround. |
| 74 | + |
| 75 | +**Re-entry path**, recorded by the ruling: if #2657 Part B promotes `apis` to a registered |
| 76 | +type **with a real consumption path**, the flag and the publish gate come back together — |
| 77 | +implementation first, declaration second. |
0 commit comments