diff --git a/.changeset/23216-routes-retire-text.md b/.changeset/23216-routes-retire-text.md new file mode 100644 index 00000000000..86dccd979ca --- /dev/null +++ b/.changeset/23216-routes-retire-text.md @@ -0,0 +1,13 @@ +--- +'@objectstack/spec': patch +--- + +Clause-②: no + +The `manifest.contributes.routes` and `App.apis` retire messages (and the published +`plugin-manifest-contributes-routes-retired` migration text) no longer send app authors to an +imperative `http.server` route registered on `kernel:ready`. An app declares its endpoint under +`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 itself is unchanged: platform plugins keep mounting their own doors on it. +No key is added or removed. diff --git a/content/docs/references/api/dispatcher.mdx b/content/docs/references/api/dispatcher.mdx index 39b5281a0c0..0c8bede54c9 100644 --- a/content/docs/references/api/dispatcher.mdx +++ b/content/docs/references/api/dispatcher.mdx @@ -15,12 +15,15 @@ The dispatcher is the central routing component that: 1. Matches incoming request URLs against registered route prefixes 2. Delegates to the corresponding CoreService implementation 3. Returns 503 Service Unavailable when a service is not registered -4. Serves prefixes registered by the kernel services above. Plugins that need - a code handler mount it imperatively on the `http.server` service (resolve - it from the plugin context on `kernel:ready`) — the manifest's - `contributes.routes` key was removed in @objectstack/spec 17 (commit bc56e1881): - nothing ever read it, and authoring it is now a tsc error and a parse - error carrying that prescription. +4. Serves prefixes registered by the kernel services above. The `http.server` + service is the PLATFORM plugins' own door-building primitive, not an + app-author surface: an app declares its endpoint under `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 manifest's `contributes.routes` key was removed in + @objectstack/spec 17 (commit bc56e1881): nothing ever read it, and + authoring it is now a tsc error and a parse error carrying that + prescription. Architecture alignment: - Kubernetes: API server aggregation layer diff --git a/content/docs/references/api/metadata.mdx b/content/docs/references/api/metadata.mdx index 4d41a86f73b..fb3353566e9 100644 --- a/content/docs/references/api/metadata.mdx +++ b/content/docs/references/api/metadata.mdx @@ -99,7 +99,7 @@ const result = AppDefinitionResponseSchema.parse(data); | **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. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this app | | **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. | -| **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//` 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. | +| **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//` 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. | | **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. | | **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. | | **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. | diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx index b541fc0d0da..3e1807d279f 100644 --- a/content/docs/references/api/package-api.mdx +++ b/content/docs/references/api/package-api.mdx @@ -234,7 +234,7 @@ Install package request | **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. | | **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. | | **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`. | -| **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 })`. | +| **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. | | **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. | ### Nested Shape: `PackageInstallBody[option 2].data[number]` diff --git a/content/docs/references/api/plugin-rest-api.mdx b/content/docs/references/api/plugin-rest-api.mdx index 4f0c769b076..0af82eb9b56 100644 --- a/content/docs/references/api/plugin-rest-api.mdx +++ b/content/docs/references/api/plugin-rest-api.mdx @@ -29,12 +29,14 @@ Architecture Alignment: - Microsoft Dynamics: Web API with entity operations - Strapi: Auto-generated REST endpoints from schemas -**Serving routes from a plugin (imperative `http.server` mount)** +**Serving routes from a platform plugin (imperative `http.server` mount)** ```typescript -// Routes are mounted in CODE — resolve the `http.server` service from the -// plugin context and register handlers on `kernel:ready` (the service is -// registered by plugin-hono-server; `examples/app-showcase`'s -// recalc-endpoint is a real consumer of this exact shape). The worked +// A PLATFORM plugin mounts its routes in CODE — resolve the `http.server` +// service from the plugin context and register handlers on `kernel:ready` +// (the service is registered by plugin-hono-server). This is the platform's +// own door-building primitive, not an app-author surface: an app declares +// `defineStack({ apis })`, computes in a flow, and registers code as a stack +// function or a connector. The worked // manifest example that used to sit here declared `contributes.routes`, // which was removed in @objectstack/spec 17 (commit bc56e1881): nothing ever read // it, so every route it showed parsed cleanly and served nothing. diff --git a/content/docs/references/kernel/manifest.mdx b/content/docs/references/kernel/manifest.mdx index 5eb1aae8ea2..f7d1ca21e72 100644 --- a/content/docs/references/kernel/manifest.mdx +++ b/content/docs/references/kernel/manifest.mdx @@ -89,7 +89,7 @@ Install mode: managed (registered as code, upgradeable, sealed) | unmanaged (cop | **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. | | **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. | | **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`. | -| **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 })`. | +| **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. | | **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. | ### Nested Shape: `Manifest.data[number]` diff --git a/content/docs/references/ui/app.mdx b/content/docs/references/ui/app.mdx index 423cc6b8c5d..86f643d2932 100644 --- a/content/docs/references/ui/app.mdx +++ b/content/docs/references/ui/app.mdx @@ -73,7 +73,7 @@ const result = ActionNavItemSchema.parse(data); | **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. | | **requiredPermissions** | `string[]` | optional | Permissions required to access this app | | **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. | -| **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//` 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. | +| **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//` 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. | | **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. | | **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. | | **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. | diff --git a/packages/spec/liveness/manifest.json b/packages/spec/liveness/manifest.json index e0cadf3d8d8..f4461d2e39d 100644 --- a/packages/spec/liveness/manifest.json +++ b/packages/spec/liveness/manifest.json @@ -191,7 +191,7 @@ "status": "dead", "verifiedAt": "2026-08-26", "evidenceScope": "cross-repo", - "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit bc56e1881, ADR-0049; maintainer-ruled Option B 2026-08-22, cloud precondition discharged by the 2026-08-24 reading), ADR-0087 D3 entry `plugin-manifest-contributes-routes-retired`, retired-key entry `kernel/Manifest:contributes.routes`. Prior verdict kept as provenance: the census verdict recorded in commit be21955ba — zero readers, same as its siblings — but a DIFFERENT disposition, which is why it was the one member split onto its own card: four published surfaces presented it as working machinery, one a customer-published skill, so an author following the shipped skill got a clean parse and served nothing. Per the ruling's own sequencing the author-facing corrections landed FIRST (PR #11327: the skill's decision table, dispatcher.zod.ts protocol doc, ADR-0088:40, app.mdx), and the two remaining teaching sites (the plugin-rest-api.zod.ts worked manifest example, metadata-plugin.zod.ts `router` delivered-form comments) were redirected in the removal PR itself. The capability survives in its working forms: the imperative `http.server` mount (plugin-hono-server registers the service; examples/app-showcase recalc-endpoint mounts on kernel:ready) and declarative `defineStack({ apis })` for pipeline projections." + "note": "RETIRED, and the row must STAY: `retiredKey()` keeps the key in the walked/drilled shape (a tombstone, not a strict removal; the `loading` precedent). Removed in @objectstack/spec 17.x (commit bc56e1881, ADR-0049; maintainer-ruled Option B 2026-08-22, cloud precondition discharged by the 2026-08-24 reading), ADR-0087 D3 entry `plugin-manifest-contributes-routes-retired`, retired-key entry `kernel/Manifest:contributes.routes`. Prior verdict kept as provenance: the census verdict recorded in commit be21955ba — zero readers, same as its siblings — but a DIFFERENT disposition, which is why it was the one member split onto its own card: four published surfaces presented it as working machinery, one a customer-published skill, so an author following the shipped skill got a clean parse and served nothing. Per the ruling's own sequencing the author-facing corrections landed FIRST (PR #11327: the skill's decision table, dispatcher.zod.ts protocol doc, ADR-0088:40, app.mdx), and the two remaining teaching sites (the plugin-rest-api.zod.ts worked manifest example, metadata-plugin.zod.ts `router` delivered-form comments) were redirected in the removal PR itself. The capability survives in its working forms: for an app, declarative `defineStack({ apis })` (`object_operation` or `flow`, ADR-0121), with code in a flow-run stack function or a connector; the imperative `http.server` mount (plugin-hono-server registers the service) stays the platform plugins' own door-building primitive, not an app-author surface." }, "commands": { "status": "dead", diff --git a/packages/spec/src/api/dispatcher.zod.ts b/packages/spec/src/api/dispatcher.zod.ts index 73932ed4c8d..428ebca0044 100644 --- a/packages/spec/src/api/dispatcher.zod.ts +++ b/packages/spec/src/api/dispatcher.zod.ts @@ -13,12 +13,15 @@ import { CoreServiceName, ServiceCriticalitySchema } from '../system/core-servic * 1. Matches incoming request URLs against registered route prefixes * 2. Delegates to the corresponding CoreService implementation * 3. Returns 503 Service Unavailable when a service is not registered - * 4. Serves prefixes registered by the kernel services above. Plugins that need - * a code handler mount it imperatively on the `http.server` service (resolve - * it from the plugin context on `kernel:ready`) — the manifest's - * `contributes.routes` key was removed in @objectstack/spec 17 (commit bc56e1881): - * nothing ever read it, and authoring it is now a tsc error and a parse - * error carrying that prescription. + * 4. Serves prefixes registered by the kernel services above. The `http.server` + * service is the PLATFORM plugins' own door-building primitive, not an + * app-author surface: an app declares its endpoint under `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 manifest's `contributes.routes` key was removed in + * @objectstack/spec 17 (commit bc56e1881): nothing ever read it, and + * authoring it is now a tsc error and a parse error carrying that + * prescription. * * Architecture alignment: * - Kubernetes: API server aggregation layer diff --git a/packages/spec/src/api/plugin-rest-api.zod.ts b/packages/spec/src/api/plugin-rest-api.zod.ts index b161ec3044d..57dc0768659 100644 --- a/packages/spec/src/api/plugin-rest-api.zod.ts +++ b/packages/spec/src/api/plugin-rest-api.zod.ts @@ -29,12 +29,14 @@ import { retiredKey } from '../shared/retired-key'; * - Microsoft Dynamics: Web API with entity operations * - Strapi: Auto-generated REST endpoints from schemas * - * @example Serving routes from a plugin (imperative `http.server` mount) + * @example Serving routes from a platform plugin (imperative `http.server` mount) * ```typescript - * // Routes are mounted in CODE — resolve the `http.server` service from the - * // plugin context and register handlers on `kernel:ready` (the service is - * // registered by plugin-hono-server; `examples/app-showcase`'s - * // recalc-endpoint is a real consumer of this exact shape). The worked + * // A PLATFORM plugin mounts its routes in CODE — resolve the `http.server` + * // service from the plugin context and register handlers on `kernel:ready` + * // (the service is registered by plugin-hono-server). This is the platform's + * // own door-building primitive, not an app-author surface: an app declares + * // `defineStack({ apis })`, computes in a flow, and registers code as a stack + * // function or a connector. The worked * // manifest example that used to sit here declared `contributes.routes`, * // which was removed in @objectstack/spec 17 (commit bc56e1881): nothing ever read * // it, so every route it showed parsed cleanly and served nothing. diff --git a/packages/spec/src/kernel/manifest.test.ts b/packages/spec/src/kernel/manifest.test.ts index b69d8ac3f06..c3ee1a7fee5 100644 --- a/packages/spec/src/kernel/manifest.test.ts +++ b/packages/spec/src/kernel/manifest.test.ts @@ -430,7 +430,7 @@ describe('contributes dead-member retirement (ADR-0049 — tombstoned, not delet }); }); -describe('contributes.routes retirement (ADR-0049 — removed for the `http.server` mount, maintainer-ruled 2026-08-22)', () => { +describe('contributes.routes retirement (ADR-0049 — removed; apps declare `apis:`, never an `http.server` mount)', () => { // The one `contributes` member split onto its own card: zero readers like // its nine siblings retired by commit be21955ba (that commit's controlled census, cloud leg closed // clean on 2026-08-24), but four published surfaces taught it as THE way to @@ -439,7 +439,9 @@ describe('contributes.routes retirement (ADR-0049 — removed for the `http.serv // the since-closed block's bare unknown-key refusal would not); the pin // asserts the SPECIFIC zod // issue — located at the key, carrying the removal record and the - // imperative `http.server` fix — never just "it threw". + // app-author rule (declare `apis:`, compute in a flow, register code as a + // stack function or connector; never mount on `http.server`) — never just + // "it threw". const base = { id: 'com.example.routes', version: '1.0.0', type: 'plugin', name: 'Routes' }; it('REJECTS an authored `contributes.routes` with the prescription as the issue', () => { @@ -454,8 +456,10 @@ describe('contributes.routes retirement (ADR-0049 — removed for the `http.serv ); expect(issue).toBeDefined(); expect(issue!.message).toMatch( - /manifest\.contributes\.routes.*removed in @objectstack\/spec 17.*Delete the key.*http\.server/s, + /manifest\.contributes\.routes.*removed in @objectstack\/spec 17.*Delete the key.*does not mount routes on `http\.server`.*defineStack\(\{ apis \}\).*flow.*defineStack\(\{ functions \}\).*connector/s, ); + // The retired advice — an app mounting a handler on `kernel:ready` — must not come back. + expect(issue!.message).not.toMatch(/kernel:ready|imperativ/); }); it('still parses the surviving `kinds` member with `routes` simply absent', () => { diff --git a/packages/spec/src/kernel/manifest.zod.ts b/packages/spec/src/kernel/manifest.zod.ts index 06fbf3e2771..ebb1f2158b4 100644 --- a/packages/spec/src/kernel/manifest.zod.ts +++ b/packages/spec/src/kernel/manifest.zod.ts @@ -751,7 +751,10 @@ export const ManifestSchema = strictObject({ * `drivers`, `fieldTypes`, `functions`, `commands` — and `routes` at * commit bc56e1881, its own enforce-or-remove fork, maintainer-ruled Option B * 2026-08-22 (remove; author-facing materials redirect to the imperative - * `http.server` mount) once the cloud census closed clean (2026-08-24). The + * `http.server` mount — narrowed by ruling record 6142279866: that mount is the + * platform's own primitive, and an app declares `defineStack({ apis })`, + * computes in a flow, and registers code as a stack function or a connector) + * once the cloud census closed clean (2026-08-24). The * census behind them (recorded in commit be21955ba, re-verified at claim time, three repos with * control probes) measured exactly ONE non-test read of * `manifest.contributes` in the entire monorepo — @@ -880,16 +883,18 @@ export const ManifestSchema = strictObject({ '`engine.registerFunction`.', ), - /** REMOVED (commit bc56e1881) — mount code-handler routes on the `http.server` service; declarative endpoints are `defineStack({ apis })`. */ + /** REMOVED (commit bc56e1881) — an app declares `defineStack({ apis })`, computes in a flow, and registers code as a stack function or a connector; it does not mount routes on `http.server` (the platform's own door-building primitive). */ routes: retiredKey( '`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 })`.', + '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.', ), /** diff --git a/packages/spec/src/kernel/metadata-plugin.zod.ts b/packages/spec/src/kernel/metadata-plugin.zod.ts index 5a652a7c247..980d32ae3d1 100644 --- a/packages/spec/src/kernel/metadata-plugin.zod.ts +++ b/packages/spec/src/kernel/metadata-plugin.zod.ts @@ -116,17 +116,18 @@ export const MetadataTypeSchema = lazySchema(() => z.enum([ 'external_catalog', // Cached remote schema snapshot for federated datasources (ADR-0015) — RUNTIME-CREATED by the Sync wizard (ADR-0062/0088); packages never ship one 'translation', // i18n resources (TranslationSchema) // ADR-0088: `router`/`function`/`service` are NOT metadata kinds — they are - // code contributions: imperative `http.server` mounts + declarative `apis:` - // (router — the manifest `contributes.routes` spelling never delivered and - // was retired, commit bc56e1881), `defineStack({ functions })` (function — + // code contributions: declarative `apis:` for an app, while `http.server` + // mounts stay the platform plugins' own primitive and are not an app-author + // surface (router — the manifest `contributes.routes` spelling never + // delivered and was retired, commit bc56e1881), `defineStack({ functions })` (function — // `contributes.functions` was retired unread, commit be21955ba), and the // plugin/service registry itself (service). // // [#5271, part of #5206] `api` is the ONE declarative endpoint ITEM kind, and // it is NOT a reversal of the `router` retirement above: `router` was retired - // as a KIND because its delivered form (the imperative `http.server` mount; - // the `contributes.routes` spelling never delivered and was retired, commit bc56e1881) - // is a code contribution. A single `ApiEndpoint` — a + // as a KIND because its delivered form (the platform's imperative `http.server` + // mount; the `contributes.routes` spelling never delivered and was retired, + // commit bc56e1881) is a code contribution. A single `ApiEndpoint` — a // stable URL plus a policy layer over an existing pipeline — is a declarative // artifact, and it passes all three clauses of ADR-0088's admission test: // 1. INDEPENDENT LIFECYCLE — the endpoint matcher indexes, invalidates and diff --git a/packages/spec/src/migrations/entries/retired-keys/18.kernel__Manifest__contributes.routes.ts b/packages/spec/src/migrations/entries/retired-keys/18.kernel__Manifest__contributes.routes.ts index b6901ceb2e7..37d899f828c 100644 --- a/packages/spec/src/migrations/entries/retired-keys/18.kernel__Manifest__contributes.routes.ts +++ b/packages/spec/src/migrations/entries/retired-keys/18.kernel__Manifest__contributes.routes.ts @@ -8,7 +8,10 @@ // them — taught it as working machinery. Maintainer ruled Option B 2026-08-22 // (「接受所有」 on the decision batch carrying the four-axis analysis): remove // the key; author-facing materials redirect to the imperative `http.server` -// mount, the form that actually works. The ruling's cloud precondition was +// mount, the form that actually works. Ruling record 6142279866 later narrowed +// that redirect: the mount is the platform's own door-building primitive, and an +// app declares `defineStack({ apis })`, computes in a flow, and registers code +// as a stack function or a connector. The ruling's cloud precondition was // discharged 2026-08-24 (the cloud census: cloud @ 5b5925a, zero `manifest.contributes` // reads, controls green), completing commit be21955ba's three-repo census at exactly // one live read (engine.ts, member `kinds` — now the block's sole survivor). diff --git a/packages/spec/src/migrations/entries/semantic/18.plugin-manifest-contributes-routes-retired.ts b/packages/spec/src/migrations/entries/semantic/18.plugin-manifest-contributes-routes-retired.ts index 1783647954b..749bb0441ac 100644 --- a/packages/spec/src/migrations/entries/semantic/18.plugin-manifest-contributes-routes-retired.ts +++ b/packages/spec/src/migrations/entries/semantic/18.plugin-manifest-contributes-routes-retired.ts @@ -8,14 +8,14 @@ export const entry: SemanticMigration = { 'manifest.contributes.routes (the one member the nine-member retirement deliberately ' + 'left to its own fork; `kinds` is now the block\'s sole surviving live member)', replacement: - '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` (plugin-hono-server registers the service; ' - + '`examples/app-showcase` mounts POST /api/v1/showcase/recalc that way). A ' - + 'declarative endpoint over a pipeline the platform already runs — query/return ' - + 'records, trigger a flow — is `defineStack({ apis })` (live since protocol 17, ' - + 'once the declarative endpoint executor was built and the loud refusal of a ' - + 'non-empty `apis:` became execution)', + '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; live since protocol 17, once the declarative endpoint ' + + 'executor was built and the loud refusal of a non-empty `apis:` became ' + + 'execution), compute in a flow, and register code as a stack function ' + + '(`defineStack({ functions })`) or a connector', reason: 'ADR-0049 enforce-or-remove; maintainer ruling 2026-08-22 (Option B of the ' + 'enforce/remove/enforce-later fork, accepted verbatim 「接受所有」 on the decision ' @@ -40,15 +40,21 @@ export const entry: SemanticMigration = { + 'declarative spelling with zero measured authors, while the capability is already ' + 'reachable imperatively. Why D3 semantic and not a D2 conversion: a manifest is ' + 'not a stack collection member (`PLURAL_TO_SINGULAR` has no `packages`/`plugins` ' - + 'entry), so a conversion would be a transform with no seam that ever runs.', + + 'entry), so a conversion would be a transform with no seam that ever runs. A ' + + 'later ruling narrowed the redirect: the imperative `http.server` mount is the ' + + 'platform plugins\' own door-building primitive, not an app-author surface — a ' + + 'code-mounted route gets no caller identity, auth gate, rate limit, namespace ' + + 'carve-out or OpenAPI entry — so an app declares `defineStack({ apis })`, ' + + 'computes in a flow, and registers code as a stack function or a connector.', acceptanceCriteria: 'An authored `contributes.routes` is a loud rejection through every spec-validating ' + 'path — `retiredKey()` types it `never` (tsc error at the authoring site) and the ' + 'parse raises the prescription itself (`os plugin build` exits non-zero printing ' + 'it). `contributes.kinds` — the block\'s sole surviving member — keeps parsing and ' + 'registering (engine → `registry.registerKind`). No author-facing material still ' - + 'recommends the key: every former teaching site points at the imperative ' - + '`http.server` mount (and `defineStack({ apis })` for declarative projections). ' + + 'recommends the key, and none sends an app author to an imperative `http.server` ' + + 'mount: every former teaching site points at `defineStack({ apis })`, a flow, and a ' + + 'stack function or connector for code. ' + '⚠️ Runtime behaviour is deliberately UNCHANGED and must be verified as such: ' + 'nothing ever read the member, so removing it removes no behaviour. A package ' + 'ALREADY INSTALLED whose stored manifest carries one degrades to a single ' diff --git a/packages/spec/src/ui/app.zod.ts b/packages/spec/src/ui/app.zod.ts index 6e21d3b1cff..809076a0e70 100644 --- a/packages/spec/src/ui/app.zod.ts +++ b/packages/spec/src/ui/app.zod.ts @@ -1620,11 +1620,12 @@ export const AppSchema = lazySchema(() => strictObject( '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). ' + + '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.', ), diff --git a/scripts/check-optional-error-sink-contract.mjs b/scripts/check-optional-error-sink-contract.mjs index 72ac0ba5c09..1eeef39d548 100644 --- a/scripts/check-optional-error-sink-contract.mjs +++ b/scripts/check-optional-error-sink-contract.mjs @@ -160,11 +160,12 @@ * * ### Scope: `packages/**` only * - * These are the contracts plugins and services PUBLISH. `examples/**` holds one - * further red sink (`app-showcase/src/system/server/recalc-endpoint.ts`) — a - * consumer's local shape, not a contract anyone implements. Stated with its - * number rather than left to silence, so widening the scan is a decision - * someone can make with the count in hand. + * These are the contracts plugins and services PUBLISH. `examples/**` holds no + * further red sink today (its one, the showcase's imperative recalc route, was + * deleted) — and any it gains would be a consumer's local shape, not a + * contract anyone implements. Stated with its number rather than left to + * silence, so widening the scan is a decision someone can make with the count + * in hand. * * ## The baseline is a shrink-only ledger, and it is NOT an exemption list *