Repository navigation
fix(cloud-connection)!: a runtime holding the env service key is platform-managed: no Setup page, disconnect refused; the entry moves to Integrations - #25110
Conversation
…form-managed: no Setup page, unbind refused; the entry moves to Integrations Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
…elf-hosted shapes; docs, checklist and changeset follow the move Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
…y bindings, which never reach it Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
…e, as what Setup Access alone still admits; rewrap two edited docs lines Claude-Session: https://claude.ai/code/session_01LmjU2x1faLZ39DVTFyVca6 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3aeea260f65650ba3964fd03d4116beabc422d4c && git checkout 3aeea260f65650ba3964fd03d4116beabc422d4c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ba244f1b487ffdf036c1b239757656801144cffe b5445185ddd2dad21abda0696bd295ee55a20166 && git checkout -B drift-repro ba244f1b487ffdf036c1b239757656801144cffe && git merge --no-ff b5445185ddd2dad21abda0696bd295ee55a20166
node scripts/docs-audit/affected-docs.mjs --json ba244f1b487ffdf036c1b239757656801144cffe
|
Contract reviewServed-tier: ① Derived judgmentsCheck-runs on the head. Read twice (the 11:55Z batch, and again at record time): 31 Net diff. Merge-base
② Semver levelThe diff publishes from Level: ③ Boundary flagsThe dev's report is comment 6315317491 on #25097: eight deviations, two out-of-scope findings, no open questions.
Out-of-scope finding 1 — bind/start and bind/poll on a managed runtime — escalated as a follow-up card for the lane seat, not a block. It is the symmetric half of what this card fixed: a hand-driven bind on a keyed runtime presents the service key to the control plane's Out-of-scope finding 2 — Not measured — Test Core (1/6) through (4/6) were Implemented-by: VERDICT: PASS |
席位核对 · ACCEPT
Accepted deviations:
Out-of-scope dispositions (one line each):
Landing: through the merge queue by this seat once every check on this head reads Generated by Claude Code |
Fixes #25097
Clause-②: no (narrowing)
Ruling 甲 (the maintainer, in the triage chat, as the card records it: 「一、云连接 甲」), the platform half. The pair objectstack-ai/cloud#3137 carries the hosted per-env kernel's own registration and is not touched here.
What changes
A runtime is platform-managed when its effective control-plane credential is the environment service key (
controlPlaneApiKey, orOS_CLOUD_API_KEY, which the cloud sets on the environments it hosts).CloudConnectionPluginnow reads that as one predicate,platformManaged, and the existing credential resolver is written in terms of it, so the two cannot disagree. It is used in two places:cloud_connection_settingspage nor thenav_cloud_connectionentry is served. It logs oneinfoline at boot saying so.409RESOURCE_CONFLICT. The refusal comes after the sign-in, held-session and Setup Access checks, which keep their answers and their order, and before the control-plane revoke and the local credential clear. The message names the platform as the connection's manager and offers no remedy, because the runtime has none to offer.Everywhere else (self-hosted or local, connected through the device-code flow), the page and disconnect are unchanged. The nav entry moves from
group_appstogroup_integrations. It keeps priority 200, which sorts it last there. The other entries' priorities, read where they are declared: Webhooks and HTTP Deliveries (plugin-webhooks) 100, Datasources (service-datasource) 100, Connect an Agent (mcp) 110. Measured once on a merged Setup app with Cloud Connection registered first:group_integrationsservednav_webhooks, nav_http_deliveries, nav_datasources, nav_connect_agent, nav_cloud_connection.Error code, and the precedent followed.
RESOURCE_CONFLICT/ 409 is the standard catalog's 409 member. No code is minted, on the rule this plugin's ownSETUP_ACCESS_REFUSAL(PERMISSION_DENIED) already follows: the ADR-0112 ledger sends a generic condition to the standard catalog rather than registering a synonym. It is also the answer this package's install-local door gives for a refusal that comes from runtime state rather than from the request (the writable-base namespace conflict). A dedicated code would need a ledger entry underpackages/spec, which is out of this lane.error-envelope.conformance.test.tsgains the case.Contract
Every input whose answer changes, before and after. Nothing else moves: the connection status answer is unchanged on every runtime, and bind start and bind poll are unchanged.
409RESOURCE_CONFLICTin the declared envelope, with a message naming the platform as the connection's manager. Nothing is revoked or cleared.PERMISSION_DENIED403cloud_connection_settingsnav_cloud_connectioningroup_appsnav_cloud_connectionanywherenav_cloud_connectioningroup_apps, an ungated groupnav_cloud_connectioningroup_integrations, last. That group requiresmanage_platform_settings, so a caller holding Setup Access without it (the defaultorganization_adminset) no longer sees the entry. The connect and disconnect requests still take Setup Access alone.infoline: platform-managed, page and entry not registered, disconnecting refusedCLOUD_CONNECTION_NAV_CONTRIBUTIONS/CLOUD_CONNECTION_UI_BUNDLEgroup: 'group_apps'group: 'group_integrations'(same type, same priority, same item)The claim's Clause-② basis said the nav move "only regroups an entry". As measured, it also narrows who is served the entry on a runtime without the key: that is the fifth row. The row narrows a served answer, so the declaration stays
no (narrowing).Changeset level. The card says
patch. The changeset gradesminor. AGENTS.md (Post-Task Checklist step 3) reads "(narrowing)is BREAKING". The header ofscripts/check-changeset-no-major.mjsreads "During the launch window we ship breaking changes asminor". All 19 pending changesets that carryClause-②: no (narrowing)grade their primary packageminor. The fixed group already holds pendingminorstock, so the next release's version number does not move. The changeset carries the Clause-② line, a BREAKING banner, and an ADR-0087 disposition of not-required (no-migration-prescription). The level axis stands down on anodeclaration.Tests
New pin:
packages/cloud-connection/src/cloud-connection-platform-managed.pin.test.ts, 8 cases.ObjectKernel+ObjectQLPluginthrough the metadata protocol. Managed throughcontrolPlaneApiKeyand throughOS_CLOUD_API_KEY: no page, and nonav_cloud_connectionanywhere in Setup's tree. Not managed: the page is served and the entry is the only child ofgroup_integrations, absent fromgroup_apps. Each reading has a lit control in the same composition: a control page that IS served, andnav_marketplace_browseingroup_apps.409RESOURCE_CONFLICT,BaseResponseSchema+envelopeViolations+ApiErrorSchemaall clean, and the message's first sentence pinned. The control plane gets zero calls, so no revoke, and the stored credential is untouched. Control, not managed: exactly one call, the revoke, and the credential is cleared.Red then green. Run against
origin/main's plugin (5050442bb): 6 failed, 2 passed (the two not-managed disconnect controls). After the fix: 8 passed. Hash-equal ablation atadac487886: both source files restored to their5050442bbblobs (233061ff…,d84ed969…, landing verified bygit hash-object), then 5 suites run. Result: 10 failed, 69 passed, and every failure is a new assertion. Restored to the HEAD blobs (be039a06…,beffc3d7…) bygit checkout HEAD --, withgit diff HEADempty and status clean.Existing pins, what each asserts and what changed:
cloud-connection-auth-gate.pin.test.ts: the UN-HELD control ("the same caller is served by all seven doors") ran on the service-key mount, where disconnect is now the 409. It now reads both shapes: self-hosted (all seven served, disconnect driven last because it really clears the credential the other doors present), and managed (six served, disconnect is the 409 with no control-plane call). HELD cases are unchanged.cloud-connection-setup-access.pin.test.ts: the CONTROL ("a setup.access holder is served by all three doors") likewise reads both shapes. Bind start and bind poll keep every prior assertion in both. Disconnect is served in the self-hosted shape, and in the managed shape it is the 409 with the credential kept. The 403 refusals are unchanged and still run on the managed mount, so they still prove the 403 precedes the 409.marketplace-installed-ui-entry.pin.test.ts: its "plugin absent" control usednav_cloud_connectionas the plugin-contributed entry ingroup_apps. That entry left the group, so the control is nownav_marketplace_browse(the browse bundle is registered in the composition), and the shell gains thegroup_integrationsanchor. The page-listing control (cloud_connection_settingsserved) is unchanged.packages/cli/test/platform-page-i18n-parity.test.ts(plugin page i18n parity, read from the static bundle),packages/cli/scripts/check-app-nav-i18n.mjs(every merged Setup/Account nav id labelled in every locale),packages/qa/dogfood/test/platform-app-object-entry-views.test.ts(object entries' first list view): unchanged and green. Translation keys are per app and nav id (apps.setup.navigation.nav_cloud_connection), so the regroup changes none:check:app-nav-i18nOK, 12 contributors, setup 52 merged ids.At
b5445185dd(the final commit):pnpm --filter @objectstack/cloud-connection exec vitest run, 54 files / 637 tests passed.typecheckexit 0. The three cross-package pins above were re-run green at the same commit (27, 49, andcheck:app-nav-i18nOK).Docs that the change would otherwise falsify
content/docs/deployment/cloud-connection.mdx: Setup, Integrations, Cloud Connection paths; a managed server has no entry and refuses to disconnect; the entry's group gate; the reference row for the disconnect request.content/docs/ui/setup-app.mdx: the Apps and Integrations rows.docs/qa/platform-checklist/areas/platform-core.json:platform-core.marketplace-console-honestysteps[3] and acceptance[0] re-pointed, revision 6.Acceptance notes
Noted, not filed:
content/docs/ui/setup-app.mdxwas already missing Datasources (service-datasource) and Connect an Agent (mcp) from the Integrations row. Pre-existing doc drift, not widened here. Carrier: none.origin/main(Zone 2 of the dispatch). Status reads bound on a runtime holding the key, both when the control plane answers not-bound and when it is down. A disconnect there asked the control plane to revoke the connection, emptied the stored token, and status still read bound afterwards.Gates
Read at
b5445185dd(the final commit).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 97 commands. Each was run with its exit code captured before any pipe: all 97 exit 0. The 10 families under "Declared WIDE population" were also run: all exit 0. Reconciliation: "Run reconciliation — 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN."pnpm lint(the whole-repoeslint . --no-inline-config): exit 0 atb5445185dd, no output. It ran with a 6 GB heap rather than 4 GB, to give the whole-repo scan headroom.Generated by Claude Code