Skip to content
102 changes: 102 additions & 0 deletions .changeset/apis-loud-reject-and-apiregistry-retirement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
"@objectstack/spec": major
"@objectstack/core": major
"@objectstack/plugin-hono-server": major
"@objectstack/runtime": major
"@objectstack/metadata-protocol": patch
---

feat(spec,core,runtime)!: declarative `apis:` refuses loudly instead of parsing into silence; the `ApiRegistry` family retires (#4936, #4939)

The declarative API-endpoint surface was **zero-execution end to end**, and said nothing
about it. Metadata loading worked perfectly — a stack declared `apis:`, `defineStack`
accepted it, and `GET /api/v1/meta/api` returned every endpoint with every key intact.
The execution side never fired once. On a real boot (showcase, 47 plugins) both declared
paths answered a bare `404 {"error":"Not found"}` — not even the dispatcher's semantic
404, because **no route was ever mounted** for a declared path, so the request died at
Hono's `notFound`. Behind that, the dispatcher's `handleApiEndpoint` branch resolved the
metadata service and called `matchEndpoint` on it — a method **no implementation in the
repo has ever provided**. The branch returned "not handled" on every request ever served.

So every key on `ApiEndpointSchema` was declared ≠ enforced: `path`/`method` (never
mounted), `type`/`target`/`objectParams` (never executed), `cacheTtl`,
`inputMapping`/`outputMapping`, `rateLimit`, `summary`/`description` — and
**`authRequired`**, a security semantic that parsed green and gated nothing at all. That
is false compliance, the failure ADR-0049 exists to stop, not debt.

## BREAKING — a non-empty `apis:` is now rejected

Metadata that parsed cleanly before is now **refused at publish/validate**, with the
prescription in the rejection itself:

```
apis: `apis:` (declarative ApiEndpoint) is DECLARED BUT NOT EXECUTABLE in this runtime,
so a non-empty array is rejected instead of silently accepted (#4936). …
```

**FROM → TO.** `apis: [ …endpoints… ]` → `apis: []` (or delete the key; both are still
accepted, and an empty array is not a special case). To actually serve the route today,
mount it **in code** — a plugin manifest `contributes.routes` entry, or an `http.server`
route. That is now the only honest path, and the one `examples/app-showcase` uses
(`src/system/server/recalc-endpoint.ts`).

The refusal lives on `ObjectStackDefinitionSchema` itself, which is the single choke
point every path runs through — `defineStack`, the metadata plugin's artifact ingestion,
`os validate`, the lint scorer and `EnvironmentArtifactSchema`. There is no path that
forgot to check.

**The `ApiEndpoint` vocabulary is deliberately KEPT.** Retiring it was considered and
rejected: endpoint shapes are an industry-stable form, so a retirement would only mean
re-introducing the identical schema later. Your endpoint definitions stay valid TypeScript
and stay in the spec; only *authoring them into a stack* is refused, and only until the
executor lands. Keep them commented next to your stack — that is what the showcase does.
The executor (route mounting + endpoint matching + per-key wiring for
`authRequired`/`cacheTtl`/`inputMapping`/`outputMapping`/`rateLimit`) is tracked by
**#5040**, which replaces this rejection with real execution.

## BREAKING — the `ApiRegistry` / `ApiEndpointRegistration` family is removed (#4939)

The repo carried a **second**, unrelated declaration shape for "an API endpoint":
`ApiEndpointRegistrationSchema` and the ~500-line `ApiRegistry` service that
`createApiRegistryPlugin()` registered under `api-registry`. Nothing composed it — every
assembly site lived in `packages/core/examples/`, with no registration in
`packages/runtime`, `packages/cli` or any `examples/app-*`, and a real boot carried no
such service. The whole family was therefore inert, including
`ApiEndpointRegistration.requiredPermissions`, whose docs promised **in the present tense**
that "the gateway layer automatically validates these permissions" while no gateway read
it. Two declaration shapes, both dead; this retirement converges them on one.

Removed from `@objectstack/spec/api`: `ApiEndpointRegistration(Schema)`,
`ApiRegistry(Schema)`, `ApiRegistryEntry(Schema)`, `ApiMetadataSchema`,
`ApiParameterSchema`, `ApiResponseSchema`, `ApiDiscoveryQuerySchema`,
`ApiDiscoveryResponseSchema`, `ApiProtocolType`, `HttpStatusCode`,
`ObjectQLReferenceSchema`, `SchemaDefinition` (12 JSON-Schema defs, 67 authorable keys).
Removed from `@objectstack/core`: `ApiRegistry`, `createApiRegistryPlugin`.
Removed from `@objectstack/plugin-hono-server`: the `useApiRegistry` option — it was
defaulted to `true` and read by nothing, configuring a service that was never composed.

**FROM → TO.** There is no replacement shape to migrate to, because nothing executed the
old one: delete the registration objects. If you were assembling an `ApiRegistryEntry`,
you were building a value only your own code read — keep it as your own type. Declarative
endpoints have one vocabulary now, `ApiEndpointSchema`.

`ConflictResolutionStrategy` **survives** the removal and moved to
`@objectstack/spec/api`'s `router.zod` — same name, same four values
(`error`/`priority`/`first-wins`/`last-wins`), same import path. It is pinned there by two
independent ratchets and is not part of the retired surface.

## Also in this change

- **BREAKING (`@objectstack/runtime`):** `HttpDispatcher.handleApiEndpoint()` is deleted,
along with its now-orphaned private `callData` delegate, and `/__api-endpoint` leaves
`LEGACY_CHAIN_PREFIXES` and the route ledger. The method was public, so this is an API
removal — but it returned `{ handled: false }` for every call it ever received, so no
caller can observe a behaviour change beyond the missing symbol. Delete the call.
Absence is now loud (ADR-0076): the surface is refused at authoring rather than 404ing
at runtime with dead code behind it.
- `examples/app-showcase` no longer declares endpoints, and its coverage manifest no
longer claims the capability is `demonstrated` — that entry read "executed by the runtime
dispatcher (handleApiEndpoint)", which was exactly the advertise-what-you-don't-deliver
claim Prime Directive #10 forbids.
- The endpoint-level `rateLimit` tracking pointers left by #4910/#5006 now name **#5040**,
the live executor card, instead of #4936, which closes with this change.
3 changes: 1 addition & 2 deletions content/docs/kernel/services.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const myPlugin: Plugin = {
```

Take init-time configuration from the plugin's own options (as
`CacheServicePlugin` and `createApiRegistryPlugin()` do) rather than from the
`CacheServicePlugin` does) rather than from the
`settings` service: that service is an async, namespaced resolver —
`await settings.get(namespace, key)` returns a `{ value, source, locked, … }`
envelope, not a synchronous config bag keyed by dotted paths — and it is only
Expand Down Expand Up @@ -128,7 +128,6 @@ The core ecosystem defines several standard service contracts:
| `http-server` | `IHttpServer` | `plugin-hono-server` |
| `data` | `IDataEngine` | `@objectstack/objectql` (drivers implement `IDataDriver`) |
| `auth` | `IAuthService` | `plugin-auth` |
| `api-registry` | `ApiRegistry` | `@objectstack/core` |
| `cache` | `ICacheService` | `@objectstack/service-cache` (memory adapter; its Redis adapter is still a skeleton that throws) — otherwise the kernel's in-memory fallback |
| `lifecycle` | `LifecycleService` (`@objectstack/objectql`) | Registered by `ObjectQLPlugin` — enforces object `lifecycle` declarations (ADR-0057 retention/rotation/archival); call `sweep()` for an on-demand pass |

Expand Down
11 changes: 8 additions & 3 deletions content/docs/protocol/kernel/http-protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1037,9 +1037,14 @@ is the declared budget multiplied by the number of nodes.
**Not implemented, deliberately named rather than implied.** ObjectStack does **not**
emit `X-RateLimit-Limit` / `-Remaining` / `-Reset` headers on successful responses —
only `Retry-After` on a 429. And the per-endpoint `rateLimit` key on
`ApiEndpointSchema` / `ApiEndpointRegistrationSchema` is **not wired to anything**;
declaring it changes nothing today. Its fate travels with the declarative `apis:`
surface as a whole, tracked by [#4936](https://github.com/objectstack-ai/objectstack/issues/4936).
`ApiEndpointSchema` is **not wired to anything**; declaring it changes nothing today.
It travels with the declarative `apis:` surface as a whole: since
[#4936](https://github.com/objectstack-ai/objectstack/issues/4936) that surface has no
executor and a **non-empty `apis:` is rejected at publish/validate**, so the key cannot
be reached at all. Wiring it is part of the endpoint executor, tracked by
[#5040](https://github.com/objectstack-ai/objectstack/issues/5040).
(The second spelling this callout used to name, `ApiEndpointRegistrationSchema`, was
retired outright in [#4939](https://github.com/objectstack-ai/objectstack/issues/4939).)
</Callout>

## Best Practices
Expand Down
1 change: 0 additions & 1 deletion content/docs/references/api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ This section contains all protocol schemas for the api layer of ObjectStack.
<Card href="/docs/references/api/query-adapter" title="Query Adapter" description="Source: packages/spec/src/api/query-adapter.zod.ts" />
<Card href="/docs/references/api/realtime" title="Realtime" description="Source: packages/spec/src/api/realtime.zod.ts" />
<Card href="/docs/references/api/realtime-shared" title="Realtime Shared" description="Source: packages/spec/src/api/realtime-shared.zod.ts" />
<Card href="/docs/references/api/registry" title="Registry" description="Source: packages/spec/src/api/registry.zod.ts" />
<Card href="/docs/references/api/rest-server" title="Rest Server" description="Source: packages/spec/src/api/rest-server.zod.ts" />
<Card href="/docs/references/api/router" title="Router" description="Source: packages/spec/src/api/router.zod.ts" />
<Card href="/docs/references/api/storage" title="Storage" description="Source: packages/spec/src/api/storage.zod.ts" />
Expand Down
1 change: 0 additions & 1 deletion content/docs/references/api/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"endpoint",
"errors",
"protocol",
"registry",
"router",
"versioning",
"---Transport & Realtime---",
Expand Down
Loading
Loading