From a1a0b5f65a35b7639616580f003d9b72b8aede61 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 05:22:18 +0000 Subject: [PATCH] docs(api): correct the environment-routing scoping instructions for standalone `os serve` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Server configuration" section told authors to enable environment-scoped routing by declaring `api.enableProjectScoping: true` in `objectstack.config.ts`, and the info Callout below it reinforced that promise. On the default `os serve` path neither is true: a bare `defineStack()` config is not host-shaped, so the CLI boots `createStandaloneStack()` and `mergeBootConfig` lets the boot result win those two scoping keys. The section now states the override, separates the two keys (one is contradicted, the other merely redundant), names the config shape in which the flag is actually live, and corrects the Callout's claim about when the CLI reads `config.api`. The `os:check` marker and the snippet under it are unchanged — the block is valid authoring, and it still type-checks. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6 --- content/docs/api/environment-routing.mdx | 42 ++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/content/docs/api/environment-routing.mdx b/content/docs/api/environment-routing.mdx index 1c26bca997..e20cf466bc 100644 --- a/content/docs/api/environment-routing.mdx +++ b/content/docs/api/environment-routing.mdx @@ -20,7 +20,9 @@ depending on request context. ## Server configuration -Enable scoped route registration in `objectstack.config.ts`: +Environment scoping is a **host** decision, not an application one. The two keys +are declared on the stack's top-level `api` block, but the host that boots the +stack is what decides whether they take effect: {/* os:check */} ```typescript @@ -35,18 +37,54 @@ export default defineStack({ }); ``` + +**On the default `os serve` path this block does not turn scoping on.** A bare +`defineStack()` config carries no instantiated plugins, so the CLI boots +`createStandaloneStack()` and merges that boot result over the authored config +(`mergeBootConfig`). The boot builder ships +`api: { enableProjectScoping: false, projectResolution: 'auto' }` and wins on +exactly those two keys — deliberately, because scoping is not the author's call +on a standalone host. + +The two keys are not overridden the same way: + +- `enableProjectScoping: true` is **contradicted**. The value forwarded to the + REST and dispatcher plugins is `false`, and no scoped routes are registered. +- `projectResolution: 'auto'` is **redundant**. The boot builder pins the same + value, so writing it changes nothing; declaring any *other* strategy here is + silently replaced by `'auto'`. + +Every other authored `api` key — `enforceProjectMembership`, for example — +survives the merge untouched. + + +The block above is live when the CLI does **not** boot the standalone stack: +when the exported config is host-shaped, meaning its `plugins` array already +carries instantiated plugin objects. That is the shape a multi-environment host +assembles, and this open-core CLI supports the `standalone` boot mode only — +cloud / multi-environment hosts ship from a separate distribution. Setting +`OS_MODE=off` (or `bootMode: 'off'` on the exported config) also skips the +standalone boot, but it drops the CLI to its legacy lightweight assembler rather +than producing a scoped standalone host; `bootMode` is additionally not a +declared stack key, so `defineStack()` rejects it as an unrecognized key. + `api` is a declared top-level field on `ObjectStackDefinitionSchema`, so it survives `defineStack`'s strict parsing — you can pass it directly inside the `defineStack({ ... })` call as shown above. (Older stacks that instead spread it onto the exported config object, e.g. `export default { ...stack, api: {...} }`, still work the same way.) The CLI reads the resolved value from the exported -config (`config.api`) when registering the REST and dispatcher plugins. +config (`config.api`) when registering the REST and dispatcher plugins — but it +reads it *after* the boot result has been merged in, which is why the standalone +path forwards the boot builder's scoping decision rather than the author's. The option names are historical for compatibility with existing config files; the route, header, env var, and request context all use `environment`. +Once scoping is enabled by the host, `projectResolution` selects the route +surface: + | Strategy | Behavior | When to use | |:---|:---|:---| | `auto` | Registers both unscoped `/api/v1/...` and scoped `/api/v1/environments/:environmentId/...` routes. | Default migration mode. |