Skip to content

feat(console): Connector connections page — connection status and Connect / Reconnect / Disconnect for OAuth connector instances - #28651

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-203pk8
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-203pk8

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

Requested by Leehom · project thread

Fixes #26702 (item 7, the Console half and its Setup navigation entry; item 7 was the card's last open item). What stays open: nothing.

Clause-②: yes (widening: new Setup entry nav_connector_connections resolving automation:connector-connections; no existing key, route or shape changes)
Metadata-protocol: none

维护者速读

  • 改了什么:Setup → 集成里新增「连接器连接」导航项(仅组织管理员可见);Console 新增「连接器连接」页(automation:connector-connections):列出每个 oauth2 + authorization_code 连接器实例在本组织的连接状态(未连接 / 已连接 / 需要重新连接,附服务端记录的原因),降级实例的原因与它的连接同行显示;提供连接、重新连接、断开连接三个操作。「连接器凭据」页加了一条指向它的链接。
  • 为什么改:connectors: outbound OAuth in the open tier — connection-scoped authorization-code / client-credentials / app-credential tokens, refreshed per dispatch, for the openapi, mcp and rest providers #26702 第 7 项。服务端的连接接口(状态 / 开始授权 / 回调 / 撤销)已全部上线,但 Console 没有入口,组织管理员授权过期或被撤销后无法在界面上重新连接。
  • 风险与回滚:低。只新增 Console 页面与文案,不改服务端、不改 spec;页面只调用已上线的四个路由。回滚即还原本 PR。
  • 先看:ui/packages/app-shell/src/views/setup/ConnectorConnectionsPage.tsx(连接按钮:同步打开登录窗口、清空 opener、再导航到服务端返回的授权地址);ui/packages/app-shell/src/views/setup/connectorConnections.ts(只接受 http(s) 授权地址)。
  • 两分钟验证:cd ui && pnpm exec vitest run packages/app-shell/src/views/setup/ConnectorConnectionsPage.test.tsx,预期 11/11 通过。
  • 需要你做的:无,席位落地。

What changes

  • ui/packages/app-shell/src/views/setup/connectorConnections.ts (new): the Console's client for the connection door. It finds the instances an administrator connects from their declaration (GET /api/v1/meta/connector, entries with a provider and auth.type: 'oauth2' + grant: 'authorization_code'), joins each instance's registry state from GET /api/v1/automation/connectors, and reads GET …/connectors/:name/connection for each. beginConnection posts with no body; revokeConnection deletes. Every answer is read into a ConnectorConnectionState or the server's own refusal sentence (actionErrorDetail), rendered, never parsed. The begin leg's authorizationUrl is accepted only when it parses as https: or http:.
  • ui/packages/app-shell/src/views/setup/ConnectorConnectionsPage.tsx (new): the page. One row per instance: label and name; a Degraded badge with the descriptor's degradedReason when the instance is degraded, in the same row as its connection; the connection's status badge and statusReason; who connected it and when, the scopes, when the access token renews; and the actions (Connect for not_connected, Reconnect and Disconnect otherwise).
    • Connect / Reconnect: the sign-in window is opened on the click (window.open('about:blank', …), since a window opened after an await is blocked), its opener is cleared before it leaves this origin, and only then is the POST sent; the window is then sent to the answered URL. The page re-reads the status every 2 s until the connection is connected with a new connectedAt (then it closes the window and shows the new status), the window is closed (it shows the status read then), or the begun connect's expiresAt passes. A blocked window sends nothing; a refused begin closes the window and shows the server's sentence.
    • Disconnect: behind a confirmation that says the grant stays valid at the vendor (the server revokes locally only).
  • ui/packages/app-shell/src/services/builtinComponents.tsx: registers the page under automation:connector-connections.
  • ui/packages/app-shell/src/views/setup/ConnectorCredentialsPage.tsx: one line under the list linking to the new page.
  • ui/packages/i18n/src/locales/*.ts (all ten packs): a connectorConnections namespace (32 keys) and two connectorCredentials keys for the link.
  • content/docs/automation/connectors.mdx: one paragraph under the authorization_code connect calls, describing the Console page.
  • .changeset/26702-console-connector-connections.md: @object-ui/app-shell and @object-ui/i18n, patch.

⛔ No packages/spec edit and no server change: the page reads and drives only what the server already serves.

Server surface inventory (read on origin/main a798ae8f)

What Where What the Console uses
GET /automation/connectors/:name/connection packages/runtime/src/domains/automation.ts (respondToConnectorConnectionDoor), ledger row in route-ledger.ts ConnectorConnectionState: status (not_connected / connected / reconnect_required), statusReason, scopes, expiresAt, connectedAt, connectedBy. Never a token.
POST …/:name/connection same { authorizationUrl, expiresAt }; the pending connect lives ten minutes
DELETE …/:name/connection same the state after revoke (not_connected); 404 when nothing was connected
GET /automation/connectors/oauth/callback same not called by the Console: the vendor redirects the sign-in window there; it answers the connection state as JSON and does not redirect back
capability manage_connector_connections (org-scoped), held by the shipped organization-admin set (plugin-security default-permission-sets.ts) a 403 shows the server's sentence in the row
delegated callers begin and revoke refuse an agent, OAuth token or API key by name shown as the refusal sentence
degraded cause holdConnectorUntilConnected registers the instance degraded "not connected" / "reconnect required" with { kind: 'connection' } as its hold; getConnectorDescriptors serves state + degradedReason but not the hold the reason is shown beside the connection; the page knows the instance is grant-bearing from its declaration, not from the reason text
reconnect_required writers markReconnectRequired in connector-connections.ts: invalid_grant on refresh, and the discovery refusal from PR #28598 shown as Reconnect required with statusReason

Tests

New: ConnectorConnectionsPage.test.tsx, 11 tests (red on main: the module does not exist there)

cd ui && pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/views/setup/ConnectorConnectionsPage.test.tsx at head 5986e268:

 Test Files  1 passed (1)
      Tests  11 passed (11)

Over stubbed /meta/connector, /automation/connectors and connection doors:

  • the status view lists only authorization_code instances (a bearer and a client_credentials instance are absent), with status badges, statusReason, connectedBy;
  • a degraded instance shows its degradedReason beside the connection;
  • a refused status read (403) shows the server's sentence and offers no action;
  • a failed declaration read is a failure with Try again; no connectable instance is the empty state;
  • Connect opens about:blank on the click, clears opener, sends one POST with no body, navigates the window to the answered URL, closes it once a re-read answers connected with a new connectedAt, and the URL's state value never appears in the page text;
  • a window closed early stops the wait and shows the status read then;
  • a blocked window sends no POST; a 503 begin closes the window and shows the sentence; a javascript: URL is never navigated to;
  • Disconnect: Cancel sends nothing, confirming sends one DELETE and shows Not connected; a 404 keeps the dialog open with the sentence.
Ablations of the two security guards (each red, then restored by blob hash)

node scripts/ablation-replace.mjs on the final head, the source imported relatively (no dist/ hop):

  • signIn.opener = null; replaced by a comment: × opens a detached window on the click, … · Tests 1 failed | 10 passed (11) · restored, git diff HEAD empty.
  • if (protocol !== 'https:' && protocol !== 'http:') replaced by a never-true test: × an authorization URL that is not http(s) is never navigated to · Tests 1 failed | 10 passed (11) · restored, git diff HEAD empty.
Neighbouring suites, typecheck, lint
  • cd ui && pnpm exec vitest run packages/app-shell/src/views/setup/ packages/app-shell/src/__tests__/ packages/app-shell/src/services/: Test Files 48 passed (48) · Tests 719 passed (719) (includes the unchanged Connector credentials page suite and the product-neutral ratchet).
  • cd ui && pnpm exec vitest run packages/i18n/: Test Files 94 passed (94) · Tests 1438 passed | 13 skipped (includes all-locales key parity).
  • ui/packages/app-shell: pnpm run type-check exit 0, 0 errors (after building the @object-ui/app-shell^... closure). ui/packages/i18n: pnpm run type-check exit 0.
  • pnpm exec eslint --no-inline-config on the four touched app-shell files: exit 0, no problems.

Acceptance notes

  • Navigation entry for an organization administrator (added after seat ruling B, claim amendments on connectors: outbound OAuth in the open tier — connection-scoped authorization-code / client-credentials / app-credential tokens, refreshed per dispatch, for the openapi, mcp and rest providers #26702). Setup entries are contributed by server UI bundles, never by the shell. Connector Credentials is gated on manage_metadata, which the organization-admin set does not hold, so this PR adds nav_connector_connections to the same contribution in packages/services/service-automation/src/connector-credentials-ui.ts (componentRef: automation:connector-connections, requiredPermissions: ['manage_connector_connections'], group_integrations, priority 120), its label in packages/platform-objects (en, es-ES, ja-JP, zh-CN; check:app-nav-i18n), and the Setup → Integrations order pin in packages/cli. The bundle test was red first (2 of 6) and is 6/6.
  • The callback answers JSON in the sign-in window. It does not redirect back to the Console, so the page watches the status instead and closes the window itself once the connect lands. If the window is left open on a refused callback, the administrator sees the server's JSON refusal there and closes it.
  • "Expired" and "revoked" are not separate server statuses. An expired or refused grant arrives as reconnect_required with the server's statusReason; a revoke (here or by another administrator) arrives as not_connected. The page renders exactly what the server answers.
  • Size. About 1.3k added lines: 370 are the ten locale packs, about 250 are tests, and the product code (page, client, registration, link) is about 600. Splitting the status view from Connect would have shipped a page that names a broken grant with no way to fix it, so it is one PR.
  • No live probe: the showcase declares no authorization_code instance, and a connect needs a vendor authorization server. The server's door is pinned in domains/automation-connector-connection-door.test.ts; the Console's behaviour is pinned over the same shapes above.

Verification

  • Derived gate list (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 66 families) run locally on the final head 5986e268, exit codes captured before any pipe: all 66 exit 0. --ran reconciliation: 66 run, 0 NOT-MEASURED (a DERIVED zero). On the first pass, at 8538b112, check:skill-examples exited 3 (its own refusal on a stale @objectstack/client-react dist, before measuring); after pnpm --filter @objectstack/client-react build it exited 0, and on the final-head pass it exited 0 directly. Run record: pm-orders/server/26702-item7-gates-ran.txt in the project folder.
  • CI runs the full farm on the PR.

Generated by Claude Code

… Reconnect / Disconnect over the connection door

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…s rules

Co-Authored-By: Claude <noreply@anthropic.com>
…gated on manage_connector_connections (red)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the tests label Nov 1, 2026
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/service-automation, touching 6 documentable anchor(s).

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

  • content/docs/automation/connectors.mdx (via manage_connector_connections (literal, a string literal in CONNECTOR_CONNECTIONS_NAV_CAPABILITY), /api/v1/automation/connectors/:name/connection (route, a path literal in a comment on a changed line))
  • content/docs/deployment/install-modes.mdx (via navigationContributions (symbol, a field of const object CONNECTOR_CREDENTIALS_UI_BUNDLE))
  • content/docs/deployment/marketplace-handbook.mdx (via navigationContributions (symbol, a field of const object CONNECTOR_CREDENTIALS_UI_BUNDLE))
  • content/docs/getting-started/examples.mdx (via navigationContributions (symbol, a field of const object CONNECTOR_CREDENTIALS_UI_BUNDLE))
  • content/docs/protocol/kernel/i18n-standard.mdx (via navigationContributions (symbol, a field of const object CONNECTOR_CREDENTIALS_UI_BUNDLE))
  • content/docs/protocol/kernel/plugin-spec.mdx (via navigationContributions (symbol, a field of const object CONNECTOR_CREDENTIALS_UI_BUNDLE))
  • content/docs/ui/apps.mdx (via navigationContributions (symbol, a field of const object CONNECTOR_CREDENTIALS_UI_BUNDLE))
What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 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; 100 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 — 11 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 5114b0e1d56f8a21edcbf8623a92f8c3dbda8de6 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 20fa0b10d5463f6d5282d4fb5dfd1a9eaca18d64 — the merge of head 0537d24f8a110e6e66c056dcdb2530c442ce1107 into base 5114b0e1d56f8a21edcbf8623a92f8c3dbda8de6, 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 20fa0b10d5463f6d5282d4fb5dfd1a9eaca18d64 && git checkout 20fa0b10d5463f6d5282d4fb5dfd1a9eaca18d64
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5114b0e1d56f8a21edcbf8623a92f8c3dbda8de6 0537d24f8a110e6e66c056dcdb2530c442ce1107 && git checkout -B drift-repro 5114b0e1d56f8a21edcbf8623a92f8c3dbda8de6 && git merge --no-ff 0537d24f8a110e6e66c056dcdb2530c442ce1107

node scripts/docs-audit/affected-docs.mjs --json 5114b0e1d56f8a21edcbf8623a92f8c3dbda8de6

⚠️ 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 5114b0e1d56f8a21edcbf8623a92f8c3dbda8de6 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

… on manage_connector_connections

Co-Authored-By: Claude <noreply@anthropic.com>
…ns in the service-automation tier

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH

席位核对 · ACCEPT · PR #28651 · 2026-11-01T13:05Z

Head-sha: 0537d24f8a110e6e66c056dcdb2530c442ce1107

维护者速读(席位终稿)

核对六项

  1. 形态:draft → main;Fixes #26702,第 7 项为最后未完项,合并关卡正确;闭合关键词另无命中(已扫)。文件 24 个,在认领与两次修订(6557489910 后的 B 裁决、第二次修订)申报面内;packages/spec 未碰,无新路由。
  2. 前提实测:dev 在 origin/main 上盘点了四个连接路由、能力 manage_connector_connections(组织管理员集持有)与降级原因的服务形状(PR 正文表格)。页面测试 11 例在 main 上不存在即红;导航项 bundle 测试红先 2/6,现 6/6。
  3. 门禁:本 head check-runs 58 success、7 skipped(路径未命中)、0 失败或待跑。dev 本地:96 条派生门禁全部 exit 0,--ran 0 NOT-MEASURED(4 条先因缺 dist / 浅克隆 exit 3,补前置后复跑 exit 0);service-automation 类型检查(src + test 层)、lint、platform-objects src/apps 37/37。
  4. 接受的偏差:文件面两次增量(导航文案 4 个翻译包、CLI 排序钉子)均为门禁所需,已修订认领;Clause-② 由 no 改 yes (widening),席位要求,changeset 与正文已一致。
  5. 范围外去向:无。Metadata-protocol: none,无 Skills-docs: 欠账;文档 content/docs/automation/connectors.mdx 一段,已逐句对照 diff(页面入口、Connect/Reconnect/Disconnect、断开只在本地撤销)。
  6. 落地条款:Clause-②: yes (widening),非 spec、非收窄、非 governed ⇒ 车道席位 TIER_DEFAULT 复核,即本 ACCEPT。① 公开面变化:Setup 贡献多 nav_connector_connections(componentRef: automation:connector-connections、requiredPermissions: [manage_connector_connections],与门的能力一致,不借组的能力);@objectstack/service-automation 多导出 CONNECTOR_CONNECTIONS_COMPONENT_REF、CONNECTOR_CONNECTIONS_NAV_CAPABILITY;@object-ui/app-shell 注册一个组件。无现有键、路由或形状变化。判对。② semver:四个包 patch,与 changeset 一致。③ 边界旗:dev 提的 Clause-② 定值已答(改 yes);作者 objectstack-fleet[bot]。check-governed-merges --pr 28651:NOT governed,1354 行。落地授权,维护者原话:「按照 pm-dispatch 规则复核过的都授权」(2026-10-25T11:35Z)。

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/xl tests tooling

Projects

None yet

1 participant