Skip to content

fix(cli): mount RuntimeConfigPlugin on the offline marketplace arm so an air-gapped runtime's install-local route is discoverable (#8389) - #8431

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-8389-offline-arm-runtime-config
Aug 13, 2026
Merged

fix(cli): mount RuntimeConfigPlugin on the offline marketplace arm so an air-gapped runtime's install-local route is discoverable (#8389)#8431
os-zhuang merged 1 commit into
mainfrom
claude/issue-8389-offline-arm-runtime-config

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#8389

An OS_CLOUD_URL=off runtime had a working/api/v1/marketplace/install-local
route and served no/api/v1/runtime/config at all. From the Console's side
that is indistinguishable from "the feature does not exist": it cannot learn the
route is there, and renders no install affordance for a capability that works.

#8343 mounted install-local alone on purpose, not carelessly. RuntimeConfigPlugin
hardcoded features.marketplace: true, so reporting install-local truthfully would
have asserted a browse capability definitively absent on a proxy-less runtime —
trading the reported bug for its mirror image. #8356 (PR #8387) removed that
constraint by deriving features.marketplace from the route table of the app
serving the response. That is what unblocks this mount, and it is why this is
completion of #8343's ruled outcome rather than a new decision.

What changed

packages/cli/src/commands/serve.ts only.

  • planMarketplaceWiring gains a third flag, offlineRuntimeConfig, and the
    offline arm mounts RuntimeConfigPlugin under it. The decision stays in the pure
    static for the reason that function's own doc gives: the call site sits deep
    inside run() behind a dynamic import, where observing a mounting rule means
    booting a kernel.
  • The two offline flags are computed independently rather than sharing one
    gate. A host that wires its own install-local (so the existing rule leaves it
    alone) may still have no runtime-config at all — that box has this defect in
    full, and one shared gate would silently exclude it.
  • The mount is guarded like its neighbour. Kernel.use() does
    this.plugins.set(name, meta), so an unguarded mount does not double-mount, it
    replaces — and RuntimeConfigPlugin carries the host's branding and the
    open-core resolveFeatures seam, so the replacement would answer with framework
    defaults and no distribution policy. New RUNTIME_CONFIG_IDENTITIES + a drift
    check, mirroring INSTALL_LOCAL_IDENTITIES.
  • Both arms now construct the plugin from one shared frozen
    RUNTIME_CONFIG_OPTIONS. The difference between the arms must come from what is
    mounted (the derived marketplace flag), never from a second copy of the
    options drifting away from the first.

Note the two neighbouring mounts in this arm deliberately spell "no cloud"
differently: RuntimeConfigPlugin special-cases controlPlaneUrl: '' as "stay on
this origin", while MarketplaceInstallLocalPlugin re-resolves what it is handed
and would substitute the public default cloud, hence OFFLINE_CONTROL_PLANE.
Harmonising them in either direction breaks one of them; both are commented and one
is pinned.

The pin is written against the served payload

packages/cli/test/serve-marketplace-offline-runtime-config.test.ts reads the body
out of the really-mounted handler and asserts installLocal: trueand
marketplace: false. It deliberately does not assert "the constructor was handed
installLocal: true": #8388 makes features.installLocal derived the same way
marketplace now is, keeping the constructor option only as an explicit override,
so a pin on the mechanism would break when it lands and a pin on the payload will
not. #8388 is not addressed here.

marketplace: false is the assertion most at risk of being vacuous — a runtime
serving nothing would also "report" false. Three guards:

  • the read helper throws when /api/v1/runtime/config was never registered, so
    "nothing served" can never read as a passing false;
  • the same body must simultaneously report installLocal: true, which an unserved
    payload cannot do;
  • a positive control mounts the realMarketplaceProxyPlugin on the same app
    and requires marketplace: true, proving the flag is observed and not pinned
    false by the fixture.

Ablation, run from the committed state: reverting serve.ts to origin/main turns
11 of 11 cases red, and the acceptance case dies on
THE #8389 DEFECT: nothing mounted GET /api/v1/runtime/config, not on a
false-looking false. Restored from the commit, all 20 cases across both offline
files pass again.

Verification

  • packages/cli full suite: 117 files, 1276 tests, all passed.
  • Targeted: 20/20 across the new file and OS_CLOUD_URL=off unmounts the air-gapped install-local endpoint — the offline install path is gated on the cloud URL it is designed not to need #8343's existing pins.
  • pnpm --filter @objectstack/cli typecheck clean.
  • Gate families derived from the changed paths via scripts/pm/dispatch-gates.mjs
    (the dispatch prompt named none): check:changeset-gate-self-tests,
    check:cross-package-test-inputs, check:objectui-changeset,
    check:startup-registry-verdict, check:query-options-erasure,
    check:type-check-coverage, check:type-check-debt,
    check-changeset-no-major.mjs, check:nul-bytes — all green.
  • check:objectui-pin-fresh reports a stale .objectui-sha. Pre-existing repo
    state, not from this branch: .objectui-sha is not in this diff, so the gate's
    inputs are identical to origin/main.

Scope

packages/cloud-connection is untouched — that is #8388's file surface. The cloud
arm's own unguarded mounts are out of scope: already filed as #8357, which names
RuntimeConfigPlugin explicitly, and making the host win there is a real behaviour
change for connected runtimes.


Generated by Claude Code

…8389)
An `OS_CLOUD_URL=off` runtime had a working
`/api/v1/marketplace/install-local` route and served no
`/api/v1/runtime/config` at all, so the Console could not learn the route
existed and rendered no install affordance for a capability that works.
#8343 could not mount RuntimeConfigPlugin here because the plugin
hardcoded `features.marketplace: true` — reporting install-local
truthfully would have asserted a browse capability definitively absent on
a proxy-less runtime. #8356 derived that flag from the serving app's
route table, which removes the constraint.
The mount is guarded like its neighbour: `kernel.use` keys by name, so an
unguarded mount would REPLACE a host's own instance and drop its branding
and `resolveFeatures` policy. The two offline guards are independent, so
a host providing only one surface still gets the other.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P7vaLs7bhBPi9m3JyzkhDj
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 12:52pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli.

17 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx(via packages/cli)
  • content/docs/api/client-sdk.mdx(via @objectstack/cli)
  • content/docs/api/data-flow.mdx(via @objectstack/cli)
  • content/docs/api/environment-routing.mdx(via @objectstack/cli)
  • content/docs/api/error-catalog.mdx(via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx(via packages/cli)
  • content/docs/deployment/backup-restore.mdx(via @objectstack/cli)
  • content/docs/deployment/cli.mdx(via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx(via @objectstack/cli)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx(via @objectstack/cli)
  • content/docs/kernel/runtime-services/index.mdx(via packages/cli)
  • content/docs/permissions/authentication.mdx(via @objectstack/cli)
  • content/docs/plugins/index.mdx(via @objectstack/cli)
  • content/docs/plugins/packages.mdx(via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx(via @objectstack/cli)

3 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/cli)
  • content/docs/releases/v16.mdx(via @objectstack/cli)
  • content/docs/releases/v17.mdx(via @objectstack/cli)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 13, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 13, 2026 13:12
@os-zhuang
os-zhuang added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit ed56cbaAug 13, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8389-offline-arm-runtime-config branch August 13, 2026 13:28
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-zhuang@claude