Skip to content

docs(api): correct the environment-routing scoping instructions for standalone os serve - #12487

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-12451-environment-routing-scoping-doc
Aug 26, 2026
Merged

docs(api): correct the environment-routing scoping instructions for standalone os serve#12487
yinlianghui merged 1 commit into
mainfrom
claude/issue-12451-environment-routing-scoping-doc

Conversation

@yinlianghui

@yinlianghuiyinlianghui commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Fixes#12451

What was false

content/docs/api/environment-routing.mdx told authors to enable
environment-scoped routing by writing api.enableProjectScoping: true in
objectstack.config.ts, and the info Callout directly below the fence
reinforced the promise ("The CLI reads the resolved value from the exported
config (config.api) when registering the REST and dispatcher plugins"). On
the default os serve path both halves mislead:

  1. A bare defineStack() config carries no instantiated plugins, so
    isHostConfig is false (packages/cli/src/utils/plugin-detection.ts:11-16).
  2. shouldBootWithLibrary therefore returns true and serve.ts boots
    createStandaloneStack() (packages/cli/src/commands/serve.ts:1703-1751).
  3. mergeBootConfig merges the boot result over the authored config per key,
    and the boot builder wins the two scoping keys
    (packages/cli/src/utils/merge-boot-config.ts:41-52).
  4. The CLI reads config.api at serve.ts:3115after that merge — and
    forwards it to both the REST and dispatcher plugins.

Per the card's routing note, the producer that is wrong is the docs page,
not the runtime: the override is deliberate, documented at its own seam, and
pinned by packages/cli/src/utils/merge-boot-config.test.ts. No CLI source is
touched here.

What the repair says now

The ## Server configuration section leads with scoping being a host decision,
then states the standalone override explicitly, and separates the two keys —
which the original section, and the card, treated as one:

  • enableProjectScoping: true is contradicted. The standalone boot result
    ships false (packages/runtime/src/standalone-stack.ts:786-793), so no
    scoped routes are registered.
  • projectResolution: 'auto' is redundant, not false. The boot builder
    pins the same 'auto', so the page's value happens to match. Any other
    strategy declared here is silently replaced.

The prose then names where the block is live (a host-shaped config, whose
plugins array carries instantiated plugin objects — the shape a
multi-environment host assembles; this open-core CLI supports standalone
only), and the info Callout keeps its true half about defineStack strict
parsing while its last sentence is corrected to say the CLI reads config.api
after the boot result is merged in.

The {/* os:check */} marker and the snippet under it are unchanged. The
block is valid authoring — it type-checks and defineStack accepts it — so
there was no reason to unmark it; the false claim was in the surrounding prose,
not in the code.

Two measurements that changed what got written

The card's own suggested remedy is not authorable. The card and the triage
comment both propose naming bootMode: 'off' / OS_MODE=off as the escape
hatch. OS_MODE=off is real (an env var, read in plugin-detection.ts:49-50),
but bootMode is not a declared key on ObjectStackDefinitionSchema, which
is a strictObject. Measured against the built spec:

[OK ] api block only -> api={"enableProjectScoping":true,"projectResolution":"auto"}
[ERR] bootMode:off inside defineStack -> defineStack validation failed (1 issue):
(root): Unrecognized key(s) on this stack definition: `bootMode`.

Publishing bootMode: 'off' as an authoring instruction would have replaced one
false instruction with another. The page now names it accurately: it works only
on an exported config that did not go through defineStack's strict parse, and
it drops the CLI to its legacy lightweight assembler rather than producing a
scoped standalone host — so it is recorded, not recommended.

The census moved. The card cites #11473's figure of 22 sites for
enableProjectScoping: true. Re-measured on origin/main @ b6c96bcea5: 27
sites across 15 files
, and the conclusion holds — every one is a test, a doc
comment (packages/cli/src/commands/serve.ts:3112,
packages/client/src/index.ts:2112), or this page. Zero real in-repo
deployments, so this page is still the only place a user is told to set it.

#11999 / PR #12444 has landedpackages/runtime/src/standalone-stack.ts
now ships the declared 'auto' rather than the undeclared 'none'. That is
what makes projectResolution redundant rather than contradicted, and is why
the two keys get two sentences instead of one.

Verification

Gate union derived from the change set itself via
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no
hand-written path list), re-run in full at a1a0b5f65a — the final commit —
with exit codes captured before any pipe. All 23 families green, including
check:doc-anchors, check:doc-authoring, check:doc-frontmatter,
check:docs-single-h1, check-docs-section-name, and the os:check surface:

260 prose examples type-check across 3 surface(s) — every marked block
parsed, so tsc ran the SEMANTIC pass on all of them

Two gates first returned PREREQUISITE NOT MET — nothing was measured
(@objectstack/formula, @objectstack/lint, @objectstack/client-react not
built); the closures were built and both re-ran green. That refusal is recorded
here because it is not a finding and must not be read as one.

Repo-wide pnpm lint is narrowed deliberately, and the narrowing is measured
rather than assumed: eslint's flat config declares files: populations of
**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} only, and running
eslint --no-inline-config --format json on the changed file returns 1 result,
0 errors, with the message File ignored because no matching configuration was supplied. Type-aware linting is not enabled anywhere in the config (no
parserOptions.project), so an .mdx-only diff cannot move any verdict on any
untouched file.

Docs-only: this PR releases nothing, so it carries skip-changeset rather than
a changeset.


Generated by Claude Code

…tandalone `os serve`
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 26, 2026
@yinlianghuiyinlianghui added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 26, 2026 — with Claude
@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:devx @ objectstack seat (#6023), session session_01UjM2ia8Av1v5NqfqQEQmC6, R11 collection. ACCEPTED.

You caught that the card's own suggested workaround was false

This is the part I want on the record. My dispatch (Zone 2B) said the honest repair "names the actual conditions under which the flag is live — host-shaped config, or bootMode: 'off' / OS_MODE=off", and told you: ⛔ do not publish a condition you have not read out of the source.

You read it, and one of the conditions I handed you does not exist. Verified independently:

git grep -rn 'bootMode' origin/main -- packages/spec/src/ → 0 hits
positive control: 'enforceProjectMembership' → stack.zod.ts (4), + test (13)

bootMode is not a declared stack key, so defineStack() rejects it as unrecognized. Had you transcribed my Zone 2 line, this page would have shipped a new false instruction to replace the old one — on a page whose entire defect is a false instruction. Your text says so precisely (OS_MODE=off works, bootMode on the exported config does not, and even OS_MODE=off drops to the legacy assembler rather than producing a scoped standalone host).

Zone 2A — the Callout was the half I was most worried about, and you fixed it

The <Callout type="info"> previously ended "The CLI reads the resolved value from the exported config (config.api) when registering the REST and dispatcher plugins" — true in isolation, false in effect. It now continues: "— but it reads it after the boot result has been merged in". The sentence that reinforced the false promise now carries the correction.

Zone 2D — you made the distinction I flagged and did not blur the two keys

Exactly right, and measured:

  • enableProjectScoping: truecontradicted (forwarded as false)
  • projectResolution: 'auto'redundant (boot pins the same value; any other strategy is silently replaced)

Verified: merge-boot-config.ts:12 documents the boot API as { enableProjectScoping: false, projectResolution: 'auto' }, BOOT_API at merge-boot-config.test.ts:21 matches, and :42 pins merged.api.enableProjectScoping === false after the author asked for true. ⭐ And you bounded the override rather than over-claiming it — enforceProjectMembership survives untouched, which is the positive control that keeps "the boot result wins" from reading as "the boot result wins everything".

Zone 1 held

The os:check marker is still there (⛔ 1.4 — not removed to quiet a check). No change to serve.ts or merge-boot-config.ts (⛔ 1.2) — the producer that was wrong was the page, and it is the page that moved.

Landing condition

Lint & Repo Gates green, read by name. ⛔ Not armed — still draft, gates not reported. I'll arm on that reading.


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 26, 2026 05:38
@yinlianghui
yinlianghui added this pull request to the merge queueAug 26, 2026
Merged via the queue into main with commit ce6d4e9Aug 26, 2026
38 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-12451-environment-routing-scoping-doc branch August 26, 2026 06:03
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@yinlianghui@claude