Skip to content

docs(deployment): Cloud Connection operator page — connect a self-hosted server to ObjectStack Cloud - #24147

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-24066-cloud-connection-guide
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-24066-cloud-connection-guide

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #24066
Clause-②: no

Body refreshed by the domain:devx seat 2 at 2026-10-17T05:06Z, after patch round 1.

A new operator page, Cloud Connection — connect a self-hosted server to ObjectStack Cloud, under content/docs/deployment/, plus one-line cross-links from the three pages that already name @objectstack/cloud-connection, and the page's generated line in scripts/docs-audit/handwritten-docs.json. Docs only; no product code.

Published path and anchors

  • Path: /docs/deployment/cloud-connection (file content/docs/deployment/cloud-connection.mdx, listed in content/docs/deployment/meta.json after install-modes).
  • Explicit heading ids (Fumadocs [#id] suffix, so a heading rewording cannot move them): #connect, #rename, #disconnect; also #what-you-get, #before-you-start, #what-the-server-stores, #reference.
  • Ids verified by compiling the page through @mdx-js/mdx 3.1.1 + remark-gfm + fumadocs-core's own remarkHeading (the slug authority the site uses), and by pnpm check:doc-anchors (exit 0).

Each section and what it rests on

Code reads are at origin/main 9ffe8af (this branch changes no code). The Setup panel was read at the pinned .objectui-sha f8267df93aba. The branch later merged main with the pin at 5e15928b4631, and between the two pins packages/app-shell/src/console/cloud-connection/ has no diff and the en cloudConnection strings are identical.

Page section Source line or measurement
Intro, RFC 8628 packages/cloud-connection/src/cloud-connection-plugin.ts header and bind/start, bind/poll; README.md "Boundary"
What connecting gives you bind/poll stores runtimeId, organizationId, accountEmail, boundAt from the cloud's answer; GET /org-packages forwards with the stored bearer (empty, connected:false, unbound); marketplace-install-local-plugin.ts sends this.credentials.read()?.runtimeToken on the cloud manifest fetch; README table rows "Unbound (anonymous)"
Before you start: plugin mounted packages/cli/src/commands/serve.ts step 5 and Serve.planMarketplaceWiring (cloud arm only when resolveCloudUrl() is truthy, skipped when the host composes CLOUD_CONNECTION_IDENTITIES, skipped in runtime host-kernel mode); cloud-url.ts DEFAULT_CLOUD_URL and the four decline spellings. Measured: OS_CLOUD_URL=off boot answers GET /api/v1/cloud-connection/status 404 ENDPOINT_NOT_FOUND, Setup Apps group has no Cloud Connection entry, page read 404
Before you start: reachability every cloud call is a server-side fetch in cloud-connection-plugin.ts; the panel only calls same-origin /api/v1/cloud-connection/* (CloudConnectionPanel.tsx BASE)
Before you start: working directory connection-credential-store.ts DEFAULT_CONNECTION_CREDENTIAL_PATH resolved against process.cwd(); docker/Dockerfile WORKDIR /srv/app
Before you start: who Setup app requiredPermissions: ['setup.access'] (platform-objects/src/apps/setup.app.ts); setup.access in ADMIN_FULL_ACCESS_CAPABILITIES (spec/src/identity/eval-user.zod.ts) and organization_admin (plugin-security/src/objects/default-permission-sets.ts); routes check session.userId only (resolveSession), held sessions refused 403 (refuseHeldSession). Measured (below)
Connect: labels page and nav metadata cloud-connection-ui.ts (label: 'Cloud Connection', header subtitle, group: 'group_apps'); panel states in CloudConnectionPanel.tsx; strings from objectui packages/i18n/src/locales/en.ts cloudConnection.*. Measured: merged Setup nav Apps group = Packages, Packaged Automation, Browse Marketplace, Installed Apps, Cloud Connection
Connect: code request, defaults, approval link bind/start: client_id default objectstack-cli, scope openid profile email, interval ?? 5, expires_in ?? 600, withContext adds runtime_name and runtime_version. Measured on the wire
Connect: polling and finish panel poll interval Math.max(code.interval, 2); bind/poll pending on authorization_pending and slow_down; runtime token persisted then deleted from the answer
Connect: failure table panel translateFailureCode (expired_token, access_denied), client-side expiry, getJson message precedence; server messages from bind/start and bind/poll. Measured: denial answers 400 DEVICE_CODE_FAILED with declaredCode: access_denied; cloud down answers 502 DEVICE_CODE_FAILED fetch failed
Connect: Cancel panel Cancel calls stopPolling(); refreshStatus() only; the connection completes only inside bind/poll
What the server stores ConnectionCredentialStore.write (mode 0o600) and the fields bind/poll writes; credential() reads the store per request. Measured file mode 600 and fields
Rename name: hostname() in the bind body and runtime_name in the link; no rename route exists among the mounted /api/v1/cloud-connection/* routes; stored runtime_id re-sent on bind; runtime_version = OS_RUNTIME_VERSION ?? this.version with version = '0.3.0'. Measured
Disconnect POST /unbind: revoke first (best effort), then rewrite to { runtimeToken: '', runtimeId } or store.clear(); panel disconnect ignores revoked. Measured both cloud-up and cloud-down
Reference the four routes as mounted; status answers without a session and passes the cloud's data.connection through; credential() prefers OS_CLOUD_API_KEY

Measurement

Showcase dev boot (OS_CLOUD_URL=http://127.0.0.1:39871 pnpm dev -- --fresh -p 38517, workspace built at 9ffe8af) against a throwaway stand-in control plane in the session scratchpad that answers the five cloud endpoints the plugin calls and logs every request it receives. Readings:

  • Anonymous: status 200 bound:false; bind/start 401 UNAUTHENTICATED "Sign in to this environment to connect a cloud account."; unbind 401 "Sign in to this environment."
  • Admin bind/start 200: user_code, interval: 5, verification_uri carrying runtime_name=vm&runtime_version=0.3.0 (this boot's /api/v1/discovery version reads 18.0.0-next.2).
  • Cloud-side request log: device code body client_id objectstack-cli, scope openid profile email; bind body name vm, runtime_version 0.3.0, scope, token; a re-bind adds runtime_id; status and revoke carry Bearer oscc_ (prefix only logged).
  • bind/poll pending 200 {pending:true}; after approval 200 with runtime_id and connection, and no runtime_token in the answer; credential file mode 600 with runtimeToken, runtimeId, controlPlaneUrl, organizationId, accountEmail, boundAt.
  • unbind (cloud up) {revoked:true, cleared:true}; file becomes {runtimeToken:'', runtimeId}; status bound:false with runtimeId kept.
  • Cloud stopped while connected: status bound:true, connection:null; unbind {revoked:false, cleared:true}.
  • A signed-in user without Setup Access (GET /api/v1/meta/app/setup 403 PERMISSION_DENIED) received 200 from unbind and from bind/start. The page names this as a known defect and links cloud-connection: bind/start, bind/poll and unbind admit any signed-in user (no Setup Access check), and GET /status serves the approving account's email and organization id to anonymous callers #24148, which the seat filed from this run, together with the anonymous status read of the connection.

Both dev servers, the stand-in and the run's examples/app-showcase/.objectstack/ directory were removed after the run.

Cloud-side statements left unverified

objectstack-ai/cloud is not readable in this session, so the page describes the cloud account only generically ("open the link, sign in to the cloud account, approve the request whose code matches"). Not verified, for the cloud seat:

  1. Which cloud accounts may approve a device request for an organization, and with what permissions.
  2. Whether, and under what name, the cloud account lists the connected server (the server sends its host name).
  3. Whether the cloud account offers its own rename, and whether a re-bind carrying the same runtime_id updates the stored name.
  4. That a re-bind carrying the stored runtime_id reattaches to the same registration (the page says the server sends it "so the cloud can" do so).
  5. What revoke does cloud-side, and how an operator removes a connection from the cloud account after revoked: false.
  6. That the cloud serves the organization's own packages to the oscc_ bearer on the org catalog and manifest routes.
  7. The issue's "the same governance": not written on the page, because nothing in the framework determines it.
  8. The real interval and expires_in values (the page states only the server's fallbacks of 5 s and 600 s).

Patch round 1

The seat widened the file surface (claim amendment 6229839180 on #24066), and the dev made these changes:

Gates

Derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; 41 commands) and run on the final head a6e9135 (6 paths, 52 commands): all 52 exit 0, check:docs-audit-scope included. Round 0 (head 4d2f5c6, 41 commands) had check:docs-audit-scope at exit 1, which patch round 1 resolved. check:skill-examples first refused with exit 3, PREREQUISITE NOT MET (no .d.ts for spec and client-react), and gave exit 0 after those packages were built. --ran reconciliation with exit codes: 52 derived, 52 run, 0 NOT-MEASURED, 0 UNRUN. node scripts/pm/check-governed-merges.mjs --branch claude/issue-24066-cloud-connection-guide: NOT governed, 239 changed lines.

Acceptance notes

  • CloudConnectionPluginConfig.credentialPath is not shared with MarketplaceInstallLocalPlugin, which builds new ConnectionCredentialStore() at the default path. A host that moves the credential gets an install-local that reads the old location. Nothing in the repo composes credentialPath. Carrier: none.
  • The plugin's mount log line and file header list the routes without unbind. Carrier: none.
  • content/docs/deployment/environment-variables.mdx says OS_CLI_CLIENT_ID defaults to nothing and serves os login, while the plugin defaults it to objectstack-cli for the device flow. OS_RUNTIME_VERSION is not on that page. Not edited: outside this card's surface. Carrier: none.
  • The examples/app-showcase/objectstack.config.ts comment "the CLI no longer auto-injects these" contradicts serve.ts step 5, which auto-wires them again. Carrier: none.
  • slow_down is read as pending, but the panel keeps its interval (RFC 8628 says to add 5 s). Not measured against a cloud that sends it. Carrier: none.

Generated by Claude Code

…rver

A new page under content/docs/deployment/ walks a self-hosted operator
through connecting their server to an ObjectStack Cloud account from
Setup -> Apps -> Cloud Connection: what the connection gives, the
prerequisites, the connect steps and the panel's states, what the server
stores, rename and disconnect. Every server-side statement is read from
packages/cloud-connection (and the pinned objectui panel) or measured on
a showcase dev boot against a stand-in control plane. Stable anchors:
#connect, #rename, #disconnect.

One-line cross-links from the three pages that already name
@objectstack/cloud-connection.

Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t
Co-authored-by: Claude <noreply@anthropic.com>
check:role-word (ADR-0090 D3) flagged one new use; the sentence now says
which cloud accounts may approve, and with what permissions.

Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t
Co-authored-by: Claude <noreply@anthropic.com>
….json

Generated by `node scripts/docs-audit/check-audit-scope.mjs --write`;
the one added line is the new content/docs/deployment/cloud-connection.mdx,
which check:docs-audit-scope requires for every hand-written page.

Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t
Co-authored-by: Claude <noreply@anthropic.com>
…ontract

The session-only check on connect/disconnect is now a known-defect note
pointing at its card, with interim advice for operators; the status row
no longer lists the approver's email and organization as fields anonymous
callers read, and points at the same card; the OS_RUNTIME_VERSION
fallback is named a known defect with its card. #disconnect unchanged.

Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 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 265ef64ba7047919460862c4e798cd5651dd92a7 → packageMentionDocs.

@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 17, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · PR #24147 (draft, head a6e913553, Fixes #24066) · domain:devx seat 2 · session_016PVjjuVPte5XEJvaQx1E4t · 2026-10-17T05:08Z

Checked on GitHub and against the sources, not on the report's say-so.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 17, 2026 05:13
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 17, 2026 05:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit 2889760 Oct 17, 2026
52 of 53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24066-cloud-connection-guide branch October 17, 2026 05:35
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 skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants