Skip to content

feat(cli,cloud-connection): OS_CONSOLE_MAP_STYLE_URL names the deployment's default map style - #26162

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-wt2tkk
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-wt2tkk

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #26001

Clause-②: no
Metadata-protocol: none

The server half of #26001 under ruling 6413991421 (letter A, operator env only). One environment variable names a deployment's default map style, so an intranet deployment, or one that forbids a third-party tile host, can stop depending on the public demo style host:

OS_CONSOLE_MAP_STYLE_URL=https://tiles.example.com/styles/basic/style.json

What the one value does

  1. Runtime-config. RuntimeConfigPlugin serves it verbatim as the top-level mapStyleUrl on GET /api/v1/runtime/config (and its legacy alias). Unset or empty, the key is absent, the same default-absent shape as branding.stage and upgradeUrl, and every other key is byte-identical. The payload's doc block declares the field.
  2. Console CSP. The Console mount adds the URL's origin to the document's connect-src, read once at boot beside OS_CONSOLE_CSP_CONNECT_SRC. The default demo host stays allowed when the env is set: a view may still name it in its own mapStyle, and a Console build that does not read mapStyleUrl yet still loads its built-in default.
  3. Refusals. The value must be an absolute https: URL on a host name, with no user name, password or whitespace. Anything else refuses the boot with a message that names OS_CONSOLE_MAP_STYLE_URL (credentials are left out of the message). RuntimeConfigPlugin refuses from start(), not the constructor: the CLI's cloud wiring in serve.ts wraps the construction in a try/catch that turns a throw into a warning and a missing door.

No Setup setting, no settings-manifest key, no spec key, no per-request CSP, no ui/**, no docs/adr/**. Only the operator can widen the Console's connect-src.

Name. OS_CONSOLE_MAP_STYLE_URL follows AGENTS.md Prime Directive #9 for a config value (OS_{DOMAIN}_{NAME}), in the CONSOLE domain beside OS_CONSOLE_CSP_CONNECT_SRC. The payload key is mapStyleUrl: the renderer sub-card reads it as its fallback before the demo style.

One contract, two readers

The order asked for one value read by both sides. They cannot share code: packages/cli/src/utils/console.ts is published as @objectstack/cli/console and imports Node built-ins only, which test/published-subpath-console.pin.test.ts pins against the packed tarball (an import of @objectstack/cloud-connection reds it with ERR_MODULE_NOT_FOUND, measured on the first push). So the contract lives in packages/cloud-connection/src/console-map-style.ts and the Console mount restates it as readMapStyleOrigin. console.content-security-policy.test.ts holds the two readers to the same verdict, refusal text included, on 11 values (4 accepted, 7 refused), and asserts that the CSP names the origin of the very URL the door serves.

Two processes

  • Single os start: the parent spawns os serve with its env, and both RuntimeConfigPlugin (CLI-wired or the host config's own) and the Console static plugin run in that child, reading the same process.env at start. They agree.
  • --no-console / OS_DISABLE_CONSOLE: the door serves the URL and there is no Console document, so nothing can disagree.
  • A runtime-host kernel (ObjectOSEnvironmentPlugin present): the CLI wires no RuntimeConfigPlugin, the host wires its own. They disagree only when the process that serves the Console document is not the one that serves /api/v1/runtime/config and the two carry different env. Then the CSP can name an origin the door does not serve (harmless), or the door can serve a URL the CSP blocks (a grey map). No such split exists in this repository.

Tests (red first)

  • packages/cloud-connection/src/runtime-config-map-style.test.ts (new): on main 10 failed / 3 passed (the 3 absence controls pass there, as they should); with the fix 13/13.
  • packages/cli/src/utils/console.content-security-policy.test.ts (extended): on main 7 failed / 21 passed; with the fix 39/39, including the 11 parity cases.
  • Ablation (one-off, not kept): let the Console reader accept http: (scripts/ablation-replace.mjs, anchor 1 to 0, blob 912ed07b6027 to aac41d764ec4): 2 red (the boot refusal and the parity case for http:); restored, blob back to HEAD's, git diff HEAD empty.
  • At head ef163a1bb: pnpm --filter @objectstack/cloud-connection test 59 files / 745 tests passed; typecheck exit 0. pnpm --filter @objectstack/cli exec vitest run --project unit 333 files / 4873 tests passed (the integration tier is declared to CI); typecheck exit 0. eslint over packages/cli and packages/cloud-connection with --no-inline-config: exit 0.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on this diff, run at head ef163a1bb: 99 derived, 99 run, all exit 0. --ran reconciliation: 99 accounted for, 0 NOT MEASURED, 0 unrun. Four first exited 3 on prerequisites and were re-run once those were met: check-plugin-teardown-shape --self-test (the positive-control commit fetched on this shallow clone), check:i18n-coverage (the example closure built), check:skill-examples and check:dual-build-cjs-loads (a full pnpm build, 119/119 tasks).

Live probe

pnpm dev:crm -- --fresh after a full build and pnpm console:build, each server on its own port and stopped afterwards:

  • OS_CONSOLE_MAP_STYLE_URL=http://tiles.example.com/style.json: the boot exits 1 with Plugin com.objectstack.runtime.runtime-config failed to start - rollback complete: OS_CONSOLE_MAP_STYLE_URL is "http://tiles.example.com/style.json", which uses http: rather than https: ....
  • OS_CONSOLE_MAP_STYLE_URL=https://tiles.example.com/styles/basic/style.json: GET /api/v1/runtime/config serves mapStyleUrl with that value (keys cloudUrl, mapStyleUrl, singleEnvironment, features, branding, telemetry), and GET /_console/ answers 200 with connect-src 'self' https://demotiles.maplibre.org https://tiles.example.com.
  • Env unset (control): no mapStyleUrl key, and connect-src 'self' https://demotiles.maplibre.org, as on main.

Changeset

.changeset/26001-console-map-style-url.md, patch for @objectstack/cloud-connection and @objectstack/cli. The order said minor, but Metadata-protocol: none grades every entry patch under the level axis of check-changeset-no-major.mjs (none-minor is exit 1), and the change adds no authorable key.

Acceptance notes

  • Not done here, by scope: the ui/packages/plugin-map renderer reading mapStyleUrl before its demo default. Until that lands, map views keep the built-in style. The seat files it as a domain:ui sub-card blocked by this card.
  • A style whose tiles, sprites or glyphs live on other hosts still needs those hosts in OS_CONSOLE_CSP_CONNECT_SRC; the env reference row says so.
  • content/docs/deployment/environment-variables.mdx gains the OS_CONSOLE_MAP_STYLE_URL row, and the OS_CONSOLE_CSP_CONNECT_SRC row now lists its origin among the sources already allowed.

Generated by Claude Code

…onfig and the Console CSP (red)

Claude-Session: https://claude.ai/code/session_011ApYCfy4gzYfuYeXX38EcT
Co-authored-by: Claude <noreply@anthropic.com>
…ment's default map style

RuntimeConfigPlugin serves it as mapStyleUrl on /api/v1/runtime/config and
the Console mount adds its origin to the document's connect-src, both read
at boot through one exported reader. A value that is not an absolute https:
URL on a host name, without credentials, refuses the boot naming the env.

Claude-Session: https://claude.ai/code/session_011ApYCfy4gzYfuYeXX38EcT
Co-authored-by: Claude <noreply@anthropic.com>
…ap style readers to one verdict

The published console subpath imports nothing but Node built-ins, so the
Console mount restates the OS_CONSOLE_MAP_STYLE_URL contract instead of
importing it from cloud-connection; the CSP suite holds the two readers to
the same verdict and refusal text on every value it names.

Claude-Session: https://claude.ai/code/session_011ApYCfy4gzYfuYeXX38EcT
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/cloud-connection, touching 11 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/environment-variables.mdx (via RuntimeConfigPlugin (symbol, a top-level class), mapStyleUrl (symbol, a field of class RuntimeConfigPlugin), OS_CONSOLE_MAP_STYLE_URL (literal, a string literal in CONSOLE_MAP_STYLE_URL_ENV), /api/v1/runtime/config (route, a path literal in a comment on a changed line))
What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 34 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a335f1defbea57ffe552d2e9efc5ad8d56a2e76a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from fc338fadc31d2d5d2b9a17d1570ac11cf0977ff6 — the merge of head ef163a1bbc2a9d7cc1d62197461494446143791c into base a335f1defbea57ffe552d2e9efc5ad8d56a2e76a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin fc338fadc31d2d5d2b9a17d1570ac11cf0977ff6 && git checkout fc338fadc31d2d5d2b9a17d1570ac11cf0977ff6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a335f1defbea57ffe552d2e9efc5ad8d56a2e76a ef163a1bbc2a9d7cc1d62197461494446143791c && git checkout -B drift-repro a335f1defbea57ffe552d2e9efc5ad8d56a2e76a && git merge --no-ff ef163a1bbc2a9d7cc1d62197461494446143791c

node scripts/docs-audit/affected-docs.mjs --json a335f1defbea57ffe552d2e9efc5ad8d56a2e76a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a335f1defbea57ffe552d2e9efc5ad8d56a2e76a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-10-26T14:32Z

Head ef163a1bbc, report 6418186488 (on #26001), claim 6416452509, ruling 6413991421 (letter A). No contract review owed: there is no packages/spec/** diff, and no narrowing. A refusal fires only for a value of the new env, so no configuration that ran before is refused.

item reading
Shape Draft, base main, Fixes #26001, patch changeset, mergeable clean.
Files packages/cli/src/utils/console.ts (+ CSP test); packages/cloud-connection/src/console-map-style.ts (new) and runtime-config-plugin.ts (+ test); content/docs/deployment/environment-variables.mdx; one changeset.
Fix One operator env, OS_CONSOLE_MAP_STYLE_URL. RuntimeConfigPlugin serves it as the top-level mapStyleUrl, absent when unset. The Console mount adds the value's origin to CSP connect-src at boot. Both readers refuse the same values: whitespace, a non-absolute URL, a non-https: scheme, embedded credentials (the anonymous door would publish them, and the refusal text redacts them), and a host a CSP cannot allow. Demotiles stays allow-listed. Per ruling A there is no Setup setting, no settings key, no spec key and no per-request CSP.
Departures (accepted) (1) Two readers plus a parity test instead of one shared reader: @objectstack/cli/console may import Node built-ins only (published-subpath-console.pin.test.ts). (2) RuntimeConfigPlugin refuses from start(), because serve.ts swallows constructor throws. (3) The changeset is graded patch, as Metadata-protocol: none grades it.
Evidence 38 check runs succeeded and 10 were expected skips on this head, 0 failed. Dev: 99/99 derived gates after a full build, cli unit 4,873/4,873, cloud-connection 745/745, and a live dev:crm probe of the refusal, the served value and connect-src.
Follow-ups filed domain:ui sub-card #26163 (plugin-map reads mapStyleUrl, Blocked-by: #26001). Cloud-lane card #26164 (status:parked): whether cloud's objectos-runtime serves the Console and runtime-config from one process env, plus cloud's env docs.
Landing 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

map view: a deployment-wide default map style (setting + runtime-config + Console CSP), so an intranet deployment can replace demotiles.maplibre.org

2 participants