Skip to content

fix(cloud-connection): derive features.installLocal from what is mounted, keep the option as a ceiling (#8388) - #8466

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-8388-derive-install-local-flag
Aug 13, 2026
Merged

fix(cloud-connection): derive features.installLocal from what is mounted, keep the option as a ceiling (#8388)#8466
os-zhuang merged 1 commit into
mainfrom
claude/issue-8388-derive-install-local-flag

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#8388

GET /api/v1/runtime/config built its response with two capability flags in one
object, answered by different rules:

features: {installLocal: this.installLocal,// a constructor flagmarketplace: hasMarketplaceBrowseMount(rawApp),// observed (#8356)
...features,},

Nothing checked that MarketplaceInstallLocalPlugin was mounted on the kernel
serving the response, so new RuntimeConfigPlugin({ installLocal: true }) on a
runtime that never mounted it announced a capability whose route 404s. That is
the second symptom measured on a real self-hosted deployment in #8343:
installLocal: true in the payload, with both verbs of
/api/v1/marketplace/install-local returning 404 {"error":"Not found"}.

What changed

features.installLocal is now observed per request off the serving app's route
ledger, the same seam #8356 built.

A sibling predicate, not a reuse. The browse predicate deliberately
subtracts the install-local paths, so no single predicate can answer both
questions. What genuinely had to be shared is smaller and more precise:

  • MARKETPLACE_INSTALL_LOCAL_PREFIX — the one definition of "what install-local
    is", now read in opposite directions by the two predicates (negatively by
    browse, positively by install-local). Neither flag can claim, or disown, a
    route the other one does if the prefix ever moves.
  • the route-ledger read itself, extracted to one someRoutePattern helper.

The predicates stay separate because they answer different questions about the
same ledger. The install-local one uses a segment boundary rather than a bare
startsWith, so .../install-local/:manifestId counts and
.../install-locality does not.

The constructor option is kept, as a ceiling

Per the triage ruling, the published option is not removed — hosts pass it today.
It is now a bound on the derived answer rather than its source:

passedresult
omitted or truereport what is actually mounted
falsefalse, even where the plugin IS mounted (operator opt-out)

Why a ceiling and not a plain override — this is the one judgement call in
the card, and it is load-bearing. A plain override honours true upward, and the
CLI's own frozen RUNTIME_CONFIG_OPTIONS passes installLocal: true
unconditionally. So an override would keep "declared true, route 404s"
reachable on exactly the product path #8343 reported, leaving the derivation
inert precisely where it is needed. The card itself lists the ceiling as one of
the three triage-time options, and it is the only one that satisfies the stated
acceptance ("nothing mounted ⇒ false") on the real CLI path.

Nothing is lost: resolveFeatures still merges over the derived base and over
the ceiling, so a host whose adapter exposes no route ledger declares the
capability it knows it serves, exactly as for marketplace.

Also note the constructor now reads config.installLocal !== false rather than
!!config.installLocal: once the flag is derived, an omitted option must not
read as an opt-out.

The cross-package repair

This change correctly invalidates one pin that landed in #8389 twenty minutes
before this card was dispatched — packages/cli/test/serve-marketplace-offline-runtime-config.test.ts,
the case "the two offline guards are INDEPENDENT". That fixture handed the
resolver an install-local identity without mounting a route; because
offlineInstallLocal resolves false, bootOfflineArm never started the real
plugin, so the app had no install-local route at all. It passed only because the
flag was the constructor value.

Neither side was wrong: the derived answer is correct for that app, and the
fixture was faithful while the flag was declarative. The fixture now mounts the
real plugin via the same preMounted seam the positive control already uses, so
"the host wires its own" means the host really mounts one. The assertions are
unchanged
— one was added (the route really is on the ledger), none weakened.

Measured rather than assumed: before the repair, exactly one case in that
file went red (:322), and the other 10 stayed green — including the acceptance
case and the positive control, which both mount the real plugin. That confirms
the dispatch note's prediction about the rest of the file.

Verification

  • packages/cloud-connection — 19 files, 138 tests, all pass.
  • packages/cli — 117 files, 1276 tests, all pass. Run in full, because this
    is the shape where PR CI runs the affected subset and two individually-green
    changes first meet in the merge queue.
  • Ablation (both directions), on the committed fix reverted to the pre-fix
    expression: 12 of the 15 new cases go red. The three that stay green are
    exactly the ones that should — "no mount at all" (the old flag also said
    false), the ceiling-false case (it pins the option, not the derivation),
    and the resolveFeatures escape hatch. Both pre-existing runtime-config suites
    stayed green under the ablation too, so neither ever depended on the derivation.
  • Gates: check:type-check-debt (green after building the closure it demands —
    it reports "surplus: none", so the new test file moved nothing),
    check:type-check-coverage, check:query-options-erasure,
    check:startup-registry-verdict, check:changeset-gate-self-tests,
    check:objectui-changeset, check:nul-bytes, check-changeset-no-major,
    check-empty-changeset.

Gate families were re-derived from the actual changed paths with
scripts/pm/dispatch-gates.mjs; beyond the named check:type-check-debt it
surfaced check:type-check-coverage and check:query-options-erasure (both
convention-triggered by adding a test file) plus the changeset family. All run
and green.

Scope note

One file outside the declared surface: packages/cloud-connection/README.md.
Its wiring example carries a comment stating that marketplace is derived "so
there is nothing here to keep in sync" while still presenting
installLocal: true as a declaration — and that README is one of the two places
the card names as having failed to keep a hand-maintained flag in step with its
own mounting. The edit is comment-only, in this card's own package, zero
behaviour. Flagged here rather than done silently.


Generated by Claude Code

…ted, keep the option as a ceiling (#8388)
`GET /api/v1/runtime/config` reported `installLocal` straight from the
constructor option, beside a `marketplace` key #8356 had just made an
observation — two flags in one object answered by different rules. The
declared one is the key #8343 measured wrong on a real self-hosted
deployment: `installLocal: true` with both verbs of
`/api/v1/marketplace/install-local` returning 404.
The flag is now observed per request off the serving app's route ledger, by
a sibling predicate rather than a shared one: the browse predicate subtracts
exactly the paths this one requires. The two share the prefix constant, so
"what counts as install-local" has a single definition.
The constructor option is kept (hosts pass it today) as a ceiling: omitted
or `true` defers to the observation, `false` lowers it. It cannot raise the
answer — the CLI's own frozen RUNTIME_CONFIG_OPTIONS passes `installLocal:
true` unconditionally, so honouring `true` upward would have left the
derivation inert on exactly the path #8343 reported.
Repairs one pin in packages/cli that this change correctly invalidates: the
"two offline guards are INDEPENDENT" case handed the resolver an
install-local identity without mounting a route, which was faithful while
the flag was declarative. It now mounts the real plugin; its assertions are
unchanged.
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 2:18pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cloud-connection.

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

  • content/docs/protocol/kernel/metadata-service.mdx(via @objectstack/cloud-connection)

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

  • content/docs/releases/implementation-status.mdx(via @objectstack/cloud-connection)

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 15:35
@os-zhuang
os-zhuang added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit 4018fc1Aug 13, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-8388-derive-install-local-flag branch August 13, 2026 15:53
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