Skip to content

Commit 60f9baf

Browse files
docs(spec): the routes / App.apis retire text stops sending app authors to an imperative http.server route (ruling C′ item 2) (#23785)
Fixes #23216 Clause-②: no Ruling C′ on #23128 (`6142279866`), item 2: the imperative `http.server` route is retired as an app-author surface. This PR changes the retire text and the published copies so they no longer send app authors to a `kernel:ready` handler. An app declares `defineStack({ apis })` (`type: 'object_operation'` or `'flow'`, ADR-0121), computes in a flow, and registers code as a stack function (`defineStack({ functions })`) or a connector. The `http.server` service, `core-service-contracts.ts` and the platform plugins that mount on it are untouched. No key is added or removed. ## What changed | Site | Change | |:---|:---| | `packages/spec/src/kernel/manifest.zod.ts` (`contributes.routes` retire message + TSDoc) | The imperative sentence is replaced by the app-author rule, with the reason: a code-mounted route gets no caller identity, auth gate, rate limit, namespace carve-out or OpenAPI entry. | | `packages/spec/src/ui/app.zod.ts` (`App.apis` retire message) | **Surface extension, declared here.** The dispatch hypothesis said this row was a different key and should be edited only if it said the same thing. It does: it said "A route that genuinely needs handler CODE is mounted imperatively instead: resolve the `http.server` service … register the route on `kernel:ready`". This is the source of the `metadata.mdx` / `app.mdx` hits that the acceptance grep covers, and the claim names `metadata.mdx` "where it renders the same text". Code now goes to a flow plus a stack function or connector. | | `packages/spec/src/migrations/entries/semantic/18.plugin-manifest-contributes-routes-retired.ts` | The published `replacement` no longer gives the imperative advice or names the deleted showcase `POST /api/v1/showcase/recalc` route. `acceptanceCriteria` now describes the current teaching sites. `reason` is kept as is, because it records the earlier ruling. | | `packages/spec/src/api/plugin-rest-api.zod.ts:34-39` TSDoc | The example now says it shows a **platform** plugin mounting its own doors, and states the app-author rule. The deleted recalc-endpoint "real consumer" claim is removed. | | `packages/spec/src/api/dispatcher.zod.ts` docblock item 4 (+ generated `api/dispatcher.mdx`) | Patch round (contract review FAIL `6206636299` on `3c31cd913e`): `http.server` is the platform plugins' own door-building primitive; an app declares `defineStack({ apis })`, computes in a flow, and registers code as a stack function or connector. The `contributes.routes` removal clause is kept. | | `packages/spec/src/kernel/metadata-plugin.zod.ts:119,127`, `manifest.zod.ts:753`, `retired-keys/18.kernel__Manifest__contributes.routes.ts:10`, migration 18 `reason` | Sweep fixes: the comments no longer present the imperative mount as the app answer, and the 2026-08-22 provenance gains one sentence saying ruling record 6142279866 narrowed the redirect. | | `packages/spec/liveness/manifest.json` row note | Same correction: the recalc-endpoint mention is removed and `http.server` is described as the platform plugins' primitive. | | `scripts/check-optional-error-sink-contract.mjs:164` scope comment | `examples/**` now holds zero further red sinks, not one, because the recalc route was deleted. Comment only. | | `packages/spec/src/kernel/manifest.test.ts` | The pin's regex used to match `…http\.server` (the old advice). It now asserts the new rule's substance (`does not mount routes on http.server` … `defineStack({ apis })` … flow … `defineStack({ functions })` … connector) and that `kernel:ready` / the imperative wording does not come back. The pin is changed, not deleted. | | `content/docs/references/**` (5 pages) | **Generated, not hand-edited:** `pnpm --filter @objectstack/spec check:generated --fix` (it proved only `check:docs` stale) rewrote `kernel/manifest.mdx`, `api/package-api.mdx`, `api/metadata.mdx`, `ui/app.mdx`, `api/plugin-rest-api.mdx`. | | `.changeset/23216-routes-retire-text.md` | `@objectstack/spec` `patch`. | Each of the four pointer sites from `6147168221` was re-read on `main` at `4fe8b3def`. All four were still stale, and all four are edited here; none had been fixed already. ## Acceptance - `git grep -n "mounted imperatively" -- packages/spec content/docs` finds nothing (exit 1). - The manifest reference page renders the new sentence (`content/docs/references/kernel/manifest.mdx:92`): "… Delete the key. An app does not mount routes on `http.server` — that service is the platform's own door-building primitive, … Declare the endpoint under `defineStack({ apis })` (`type: 'object_operation'` or `'flow'`, ADR-0121), compute in a flow, and register code as a stack function (`defineStack({ functions })`) or a connector." - `pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated`: "All 14 generated artifacts are up to date", exit 0. - `pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test`: 680 files, 20784 passed, 1 todo, exit 0. This includes `retired-key-migrate-sentence.test.ts` and its family, and `manifest.test.ts`. - Derived gates at head `846feb0c` (134 families): 131 exit 0, 3 NOT MEASURED (exit 3, declared to CI: teardown-shape self-test, dual-build-cjs-loads, lean-entry-closure); `--ran` reconciles 134 / 131 / 3 / 0 UNRUN, exit 0. ## Imperativ sweep (patch round) git grep -n -i imperativ -- packages/spec/src content/docs (releases/ excluded) at 846feb0. FIXED (app-author route advice): api/dispatcher.zod.ts:17 + references/api/dispatcher.mdx:19; kernel/metadata-plugin.zod.ts:119,127; kernel/manifest.zod.ts:753 (provenance, supersede note added); migrations/entries/retired-keys/18.kernel__Manifest__contributes.routes.ts:10 (same); semantic/18.plugin-manifest-contributes-routes-retired.ts reason (closing sentence added). KEPT, platform-only: api/plugin-rest-api.zod.ts:32 + plugin-rest-api.mdx:32 heading, now 'Serving routes from a platform plugin (imperative http.server mount)'; the example is a platform plugin mounting its own doors, which the ruling keeps. KEPT, provenance of the 2026-08-22 ruling, with the narrowing now stated in the same field: semantic/18…routes-retired.ts reason lines 23/33/41. KEPT, different capability (not routes or http.server): manifest.zod.ts:811 and semantic/18.plugin-manifest-contributes-dead-members-retired.ts:15/36 plus retired-keys/18…contributes.events.ts:10 (event subscription in plugin code); manifest.zod.ts:851 + package-api.mdx:233/manifest.mdx:88 (engine.registerAction); studio/plugin.zod.ts + references/studio/plugin.mdx (Studio component activation). KEPT, unrelated word sense: concepts/glossary/protocol prose 'imperative code' vs declarative; retired-key.ts:303, retired-key.test.ts:159, ui/component.test.ts:1375/1382, ui/form-option-enum-derive.test.ts:86, kernel/manifest.test.ts:406/552 ('an imperative fix' = an imperative-mood prescription); manifest.test.ts:462 is the negative pin itself. kernel:ready beside http.server sweep: only content/docs/kernel/events.mdx:22/35 and contracts/plugin-lifecycle-events.ts:77, which describe kernel lifecycle and platform HTTP middleware, not app routes; kept. git grep 'mounted imperatively' -- packages/spec content/docs: exit 1. ## Acceptance notes - The published skill `skills/objectstack-api/SKILL.md:119` still has a decision-table row: "`http.server` mount (plugin code) — The endpoint needs real handler CODE … Mount it on `http.server`." It is the same retired advice, and its eval `skills/objectstack-api/evals/endpoints-auth-routes.json` may pin it. Both are outside this card's claimed surface, and `skills/**` is a Tier H governed surface, so they are not edited here. They are reported to the seat to file. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017dvSKLgBT3sjuE98di6PW1 --- _Generated by [Claude Code](https://claude.ai/code/session_017dvSKLgBT3sjuE98di6PW1)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9b799e6 commit 60f9baf

17 files changed

Lines changed: 108 additions & 64 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
Clause-②: no
6+
7+
The `manifest.contributes.routes` and `App.apis` retire messages (and the published
8+
`plugin-manifest-contributes-routes-retired` migration text) no longer send app authors to an
9+
imperative `http.server` route registered on `kernel:ready`. An app declares its endpoint under
10+
`defineStack({ apis })` (`type: 'object_operation'` or `'flow'`, ADR-0121), computes in a flow,
11+
and registers code as a stack function (`defineStack({ functions })`) or a connector. The
12+
`http.server` service itself is unchanged: platform plugins keep mounting their own doors on it.
13+
No key is added or removed.

‎content/docs/references/api/dispatcher.mdx‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ The dispatcher is the central routing component that:
1515
1. Matches incoming request URLs against registered route prefixes
1616
2. Delegates to the corresponding CoreService implementation
1717
3. Returns 503 Service Unavailable when a service is not registered
18-
4. Serves prefixes registered by the kernel services above. Plugins that need
19-
a code handler mount it imperatively on the `http.server` service (resolve
20-
it from the plugin context on `kernel:ready`) — the manifest's
21-
`contributes.routes` key was removed in @objectstack/spec 17 (commit bc56e1881):
22-
nothing ever read it, and authoring it is now a tsc error and a parse
23-
error carrying that prescription.
18+
4. Serves prefixes registered by the kernel services above. The `http.server`
19+
service is the PLATFORM plugins' own door-building primitive, not an
20+
app-author surface: an app declares its endpoint under `defineStack({ apis })`
21+
(`type: 'object_operation'` or `'flow'`, ADR-0121), computes in a flow, and
22+
registers code as a stack function (`defineStack({ functions })`) or a
23+
connector. The manifest's `contributes.routes` key was removed in
24+
@objectstack/spec 17 (commit bc56e1881): nothing ever read it, and
25+
authoring it is now a tsc error and a parse error carrying that
26+
prescription.
2427

2528
Architecture alignment:
2629
- Kubernetes: API server aggregation layer

‎content/docs/references/api/metadata.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ const result = AppDefinitionResponseSchema.parse(data);
9999
| **homePageId** | `never` | optional | [REMOVED] `app.homePageId` was removed in @objectstack/spec 17.0.0 (ADR-0049). objectui's console did read it before v17 (`resolveLandingRoute`), so this key had a consumer — it was retired because the capability is better expressed on the navigation item itself than as an ID cross-reference that silently falls back when it dangles. An app's landing page IS its first navigation item (by `order`), and the root landing follows `isDefault` routing. Delete the key; to change where an app opens, reorder `navigation` so the intended entry is first, and set `isDefault` on the app that should own the root landing. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
100100
| **requiredPermissions** | `string[]` | optional | Permissions required to access this app |
101101
| **objects** | `never` | optional | [REMOVED] `App.objects` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit — never read; the spec itself labelled it "config file convenience"). Objects belong to the stack (`defineStack({ objects })`); an app reaches them through its navigation items. Delete the key. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
102-
| **apis** | `never` | optional | [REMOVED] `App.apis` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit — never read). Delete the key and declare the endpoint one level up, on the STACK: `defineStack({ apis })`. That surface EXECUTES from protocol 17. Before the executor landed it was refused wholesale — nothing mounted a declared path, so every key including `authRequired` parsed and gated nothing — and that blanket refusal is now narrowed to five per-endpoint publish gates (namespace, supported target, mapping, policy, uniqueness): an endpoint that passes them is mounted and serves traffic as soon as the stack is published. Two things to get right when you move it: the path must sit inside your own carve-out, `/api/v1/apps/<manifest.namespace>/<subpath>` with an explicit `manifest.namespace` (ADR-0121 D1/D2), and `authRequired` defaults to `true` — an explicit `false` is the only thing that opens anonymous access, and ADR-0121 D6 then requires an armed `rateLimit: { enabled: true, windowMs, maxRequests }`. Read the `declarative-apis-endpoints-live` entry of the protocol upgrade guide first; it is a security review, not a rename. A route that genuinely needs handler CODE is mounted imperatively instead: resolve the `http.server` service from your plugin context and register the route on `kernel:ready` (NOT the manifest `contributes.routes` key — removed in @objectstack/spec 17: nothing ever read it, and authoring it is now rejected with its own prescription). Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
102+
| **apis** | `never` | optional | [REMOVED] `App.apis` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit — never read). Delete the key and declare the endpoint one level up, on the STACK: `defineStack({ apis })`. That surface EXECUTES from protocol 17. Before the executor landed it was refused wholesale — nothing mounted a declared path, so every key including `authRequired` parsed and gated nothing — and that blanket refusal is now narrowed to five per-endpoint publish gates (namespace, supported target, mapping, policy, uniqueness): an endpoint that passes them is mounted and serves traffic as soon as the stack is published. Two things to get right when you move it: the path must sit inside your own carve-out, `/api/v1/apps/<manifest.namespace>/<subpath>` with an explicit `manifest.namespace` (ADR-0121 D1/D2), and `authRequired` defaults to `true` — an explicit `false` is the only thing that opens anonymous access, and ADR-0121 D6 then requires an armed `rateLimit: { enabled: true, windowMs, maxRequests }`. Read the `declarative-apis-endpoints-live` entry of the protocol upgrade guide first; it is a security review, not a rename. An endpoint whose work needs CODE still lands here: compute in a flow and register the code as a stack function (`defineStack({ functions })`) or a connector — an app does not mount routes on `http.server`, and the manifest `contributes.routes` key was removed in @objectstack/spec 17 (nothing ever read it; authoring it is now rejected with its own prescription). Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
103103
| **sharing** | `never` | optional | [REMOVED] `App.sharing` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit / ADR-0049 enforce-or-remove) — no public-app route ever read it, so it declared sharing that did not exist. Public access is granted per FORM VIEW (`FormView.sharing`, the public-data-collection surface). Delete the key. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
104104
| **embed** | `never` | optional | [REMOVED] `App.embed` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit / ADR-0049) — no iframe route ever read it. Embedding is a per-form-view surface (`FormView.sharing`), not an app-level switch. Delete the key. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
105105
| **mobileNavigation** | `never` | optional | [REMOVED] `App.mobileNavigation` was removed in @objectstack/spec 17.0.0 (2026-06 liveness audit — fully unimplemented; no renderer, including packages/mobile, ever read it). Delete the key; the block returns if/when a real mobile navigation ships. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |

‎content/docs/references/api/package-api.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ Install package request
234234
| **drivers** | `never` | optional | [REMOVED] `manifest.contributes.drivers` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: a storage driver is wired by registering a kernel SERVICE named `driver.*` (the objectql plugin picks it up and calls `registerDriver`), and its only in-repo author was registered that way, not by this declaration. Delete the key. |
235235
| **fieldTypes** | `never` | optional | [REMOVED] `manifest.contributes.fieldTypes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — there is no `registerFieldType` seam anywhere: the declaration advertised an extension point the platform does not have, so authoring it configured nothing. Delete the key. The field-type vocabulary is the spec `FieldType` enum; extending it is a spec change, not a manifest declaration. |
236236
| **functions** | `never` | optional | [REMOVED] `manifest.contributes.functions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it; ObjectQL functions declared here were never registered. Delete the key. Declare functions on the stack (`defineStack({ functions: […] })`), which the hook binder registers via `engine.registerFunction`. |
237-
| **routes** | `never` | optional | [REMOVED] `manifest.contributes.routes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: the HttpDispatcher never registered a prefix from the declaration, so an entry here parsed cleanly and served nothing while published material kept recommending it. Delete the key. A route that needs real handler CODE is mounted imperatively: resolve the `http.server` service from the plugin context and register the handler on `kernel:ready`. A declarative endpoint over a pipeline the platform already runs (query/return records, trigger a flow) is `defineStack({ apis })`. |
237+
| **routes** | `never` | optional | [REMOVED] `manifest.contributes.routes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: the HttpDispatcher never registered a prefix from the declaration, so an entry here parsed cleanly and served nothing while published material kept recommending it. Delete the key. An app does not mount routes on `http.server` — that service is the platform's own door-building primitive, and a code-mounted route gets no caller identity, auth gate, rate limit, namespace carve-out or OpenAPI entry. Declare the endpoint under `defineStack({ apis })` (`type: 'object_operation'` or `'flow'`, ADR-0121), compute in a flow, and register code as a stack function (`defineStack({ functions })`) or a connector. |
238238
| **commands** | `never` | optional | [REMOVED] `manifest.contributes.commands` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the CLI never resolved commands from this declaration: commands are auto-discovered through oclif's native plugin system (the plugin package declares an `oclif` section in its own `package.json`; see `cli-extension.zod.ts`), and the `objectstack.config.ts` plugins array no longer determines CLI commands. Delete the key. |
239239

240240
### Nested Shape: `PackageInstallBody[option 2].data[number]`

‎content/docs/references/api/plugin-rest-api.mdx‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ Architecture Alignment:
2929
- Microsoft Dynamics: Web API with entity operations
3030
- Strapi: Auto-generated REST endpoints from schemas
3131

32-
**Serving routes from a plugin (imperative `http.server` mount)**
32+
**Serving routes from a platform plugin (imperative `http.server` mount)**
3333
```typescript
34-
// Routes are mounted in CODE — resolve the `http.server` service from the
35-
// plugin context and register handlers on `kernel:ready` (the service is
36-
// registered by plugin-hono-server; `examples/app-showcase`'s
37-
// recalc-endpoint is a real consumer of this exact shape). The worked
34+
// A PLATFORM plugin mounts its routes in CODE — resolve the `http.server`
35+
// service from the plugin context and register handlers on `kernel:ready`
36+
// (the service is registered by plugin-hono-server). This is the platform's
37+
// own door-building primitive, not an app-author surface: an app declares
38+
// `defineStack({ apis })`, computes in a flow, and registers code as a stack
39+
// function or a connector. The worked
3840
// manifest example that used to sit here declared `contributes.routes`,
3941
// which was removed in @objectstack/spec 17 (commit bc56e1881): nothing ever read
4042
// it, so every route it showed parsed cleanly and served nothing.

‎content/docs/references/kernel/manifest.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Install mode: managed (registered as code, upgradeable, sealed) | unmanaged (cop
8989
| **drivers** | `never` | optional | [REMOVED] `manifest.contributes.drivers` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — it never had an effect: a storage driver is wired by registering a kernel SERVICE named `driver.*` (the objectql plugin picks it up and calls `registerDriver`), and its only in-repo author was registered that way, not by this declaration. Delete the key. |
9090
| **fieldTypes** | `never` | optional | [REMOVED] `manifest.contributes.fieldTypes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — there is no `registerFieldType` seam anywhere: the declaration advertised an extension point the platform does not have, so authoring it configured nothing. Delete the key. The field-type vocabulary is the spec `FieldType` enum; extending it is a spec change, not a manifest declaration. |
9191
| **functions** | `never` | optional | [REMOVED] `manifest.contributes.functions` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it; ObjectQL functions declared here were never registered. Delete the key. Declare functions on the stack (`defineStack({ functions: […] })`), which the hook binder registers via `engine.registerFunction`. |
92-
| **routes** | `never` | optional | [REMOVED] `manifest.contributes.routes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: the HttpDispatcher never registered a prefix from the declaration, so an entry here parsed cleanly and served nothing while published material kept recommending it. Delete the key. A route that needs real handler CODE is mounted imperatively: resolve the `http.server` service from the plugin context and register the handler on `kernel:ready`. A declarative endpoint over a pipeline the platform already runs (query/return records, trigger a flow) is `defineStack({ apis })`. |
92+
| **routes** | `never` | optional | [REMOVED] `manifest.contributes.routes` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: the HttpDispatcher never registered a prefix from the declaration, so an entry here parsed cleanly and served nothing while published material kept recommending it. Delete the key. An app does not mount routes on `http.server` — that service is the platform's own door-building primitive, and a code-mounted route gets no caller identity, auth gate, rate limit, namespace carve-out or OpenAPI entry. Declare the endpoint under `defineStack({ apis })` (`type: 'object_operation'` or `'flow'`, ADR-0121), compute in a flow, and register code as a stack function (`defineStack({ functions })`) or a connector. |
9393
| **commands** | `never` | optional | [REMOVED] `manifest.contributes.commands` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — the CLI never resolved commands from this declaration: commands are auto-discovered through oclif's native plugin system (the plugin package declares an `oclif` section in its own `package.json`; see `cli-extension.zod.ts`), and the `objectstack.config.ts` plugins array no longer determines CLI commands. Delete the key. |
9494

9595
### Nested Shape: `Manifest.data[number]`

0 commit comments

Comments
 (0)