Skip to content

Commit 0c8c27e

Browse files
feat(console): Setup → Integrations → Connector Credentials, and a credential step after a marketplace install (#28200)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao2zgfxhwFdVq6dfqdLqxCuS)_ Fixes #27238 Clause-②: yes (widening: `@objectstack/service-automation` exports `CONNECTOR_CREDENTIALS_UI_BUNDLE`, `CONNECTOR_CREDENTIALS_COMPONENT_REF` and `CONNECTOR_CREDENTIALS_NAV_CAPABILITY`, and registers a new Setup navigation entry `nav_connector_credentials` resolving component ref `automation:connector-credentials`; no existing key, route or shape changes) Metadata-protocol: none Skills-docs: updated here (`content/docs/automation/connectors.mdx`, a Console paragraph pair in the existing "Setting a credential" section rather than a new page, which would have duplicated that section) ## 维护者速读 - 改了什么:管理员现在可以在 Setup → Integrations → Connector Credentials 页面里查看、设置、替换和清除已安装应用的连接器凭据;从市场安装应用后,安装结果里会直接列出这个应用还缺的凭据,可以当场填,也可以「暂时跳过」,跳过的会给出去设置页的链接。 - 为什么改:按 #27230 裁决 A,托管环境的管理员碰不到服务器环境变量,以前只能调 API 才能填凭据;服务端接口(#27237)已在 main 上,这次补上 Console 这一半。 - 风险与回滚:值永远不回显,输入框始终为空,留空即「不改」,空值不会发出请求;拒绝(多组织隔离形态、无权限、旧服务端没有这些路由)显示为服务器原话的配置说明,不当作红色错误。回滚即 revert 本 PR,服务端行为不变。 - 先看:`ui/packages/app-shell/src/views/setup/connectorCredentials.ts`(读写规则与安装步骤的过滤),然后是 `ConnectorCredentialsPage.tsx` 和 `console/marketplace/ConnectorCredentialsStep.tsx`。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/app-shell/src/views/setup/connectorCredentials.test.ts packages/app-shell/src/views/setup/ConnectorCredentialsPage packages/app-shell/src/console/marketplace/__tests__/ConnectorCredentialsStep-27238.test.tsx packages/app-shell/src/console/marketplace/__tests__/MarketplacePackagePage.credentialStep-27238.test.tsx`,预期 5 个文件 44 passed。 - 需要你做的:无,席位落地。 ## What changes **Setup → Integrations → Connector Credentials** (`ui/packages/app-shell/src/views/setup/ConnectorCredentialsPage.tsx`) - One row per reference from `GET /api/v1/automation/connectors/credential-refs`: the reference, the connectors naming it (with their package), its status badge (`set` / `missing` / `rejected`, with the upstream's reason for `rejected`), its source (`env`, `vault`) and when and by whom it last changed, plus when it was last seen working. - A masked input (`CredentialValueForm.tsx`, shared with the install step) for each writable, declared reference. It starts empty whatever is stored, Save stays disabled while it is empty, and a save sends exactly `{ value }` with `PUT`. The row then shows the entry the door answered after its reconcile, and the typed value is dropped from state. - **Clear**, behind a confirmation dialog, for a value stored here (`source: 'object'`). It sends `DELETE` with no body. - A reference an environment variable supplies is read-only: no input and no Clear, with a sentence saying the variable wins. - Honesty: `404` / `405` reads "This server does not manage connector credentials"; `403` (missing capability, or the `group` / `isolated` posture), `501` and `503` show the server's own sentence as an informational notice. Only anything else is a red failure, with Try again. **The entry ships with the door** (triage `6469948452`, cloud ADR-0009 shape) - `packages/services/service-automation/src/connector-credentials-ui.ts` declares `CONNECTOR_CREDENTIALS_UI_BUNDLE`: one `navigationContributions` row into Setup's `group_integrations`, a `component` item `nav_connector_credentials` naming `automation:connector-credentials`, gated on `manage_metadata` (the capability every verb of the door asks). - `AutomationServicePlugin` registers it through the `manifest` service at `kernel:ready`, after the inert-mode return. A one-shot tool registers nothing, and a kernel with no manifest service boots with no entry. - App-shell registers the component for that ref in `builtinComponents.tsx`, eagerly like `automation:packaged`. The nav label is translated in the four platform locales, and `check-app-nav-i18n.mjs` gains the bundle as a contributor row. **The credential step after a marketplace install** (`ui/packages/app-shell/src/console/marketplace/ConnectorCredentialsStep.tsx`) - It reads the same listing, filtered to the package by `connectors[].packageId`, and offers each reference that is not `set`, still declared and writable (so `rejected` is offered and `env` never is). Each has the masked input and **Skip for now**. A value the door answers `set` closes its row; any other answer keeps the row open with its new status. Skipped references stay listed with a link to the Setup page. - It is mounted in three install results: the package page's install-local banner when the running kernel loaded the package (`hotLoaded: true`), the org-package install on the marketplace page in the same case, and the cloud install dialog / org deploy strip once the app is served in this runtime. - Where the listing cannot be read (a walled posture), it shows the install answer's `credentialNote` instead. **Docs and i18n** - `content/docs/automation/connectors.mdx`: what the Console page shows, the read-only env rows, the walled conclusion, and the install step with skip. - A `connectorCredentials` namespace (43 keys) in all ten Console packs. - `ui/packages/i18n/src/locales/index.ts`: `builtInLocales` gets an explicit type. The 43 keys tipped the inferred type of that map past what `tsc` will serialize into the declaration file (`TS7056` on the `@object-ui/i18n` build). Each property is `typeof <pack>`, so the type is unchanged and is emitted as a reference. ## Tests - `views/setup/connectorCredentials.test.ts` (18): read rules per status, a malformed row dropped, a `200` without `data.credentialRefs` read as a failure and never as an empty list, a network error read as a failure, `PUT` body exactly `{ value }` at the encoded ref, an empty value not sent, `DELETE` with no body, the install-step filter. - `views/setup/ConnectorCredentialsPage.test.tsx` (13): rows, env read-only, inputs masked and empty, an undeclared stored ref, empty state, Save disabled when empty, save round-trip with the typed value absent from the DOM afterwards, a refusal under the input, clear confirm and cancel, `403` / `404` as notices with no `role="alert"`, `500` with Try again. The posture sentence is transcribed from `connectorCredentialWalledMessage('group')`. - `views/setup/ConnectorCredentialsPage.navContribution.test.tsx` (3): the production registration, the URL the ref builds, and the page mounting through `ComponentNavView`. - `console/marketplace/__tests__/ConnectorCredentialsStep-27238.test.tsx` (7) and `MarketplacePackagePage.credentialStep-27238.test.tsx` (3): the package filter, set / rejected / skip, the `credentialNote` fallback, and a real install-local round trip through `marketplaceApi` with `hotLoaded: true`, walled, and `hotLoaded: false` (no step, no listing read). - `packages/services/service-automation/src/connector-credentials-ui.test.ts` (5): the bundle parses under `ManifestSchema`; its contribution exactly; a `LiteKernel` boot registers it once at `kernel:ready`; an inert boot and a boot without a manifest service register none. ## Acceptance notes - **The step is after the install, not before it** (the card says "prompts for the missing refs of the package being installed"). Measured: the door answers `404` for a reference no installed package declares, so a value typed before the install could not be stored. The step opens in the install result, where the package's connectors already exist degraded and materialize as soon as a credential is set, with no restart. - **Cloud ask `6473504855`: adopted.** The step reads the same `GET …/credential-refs` route the page uses, filtered by package, after any install landing in this runtime: install-local, and a cloud install once its app is served here (the same condition the suggested-bindings panel already uses). It needs no cloud change and no new route. The hosted install path itself stays objectstack-ai/cloud#3352's (triage `6522868103`). - `Clause-②` measured `yes (widening)` from the diff: three new exports on the published `@objectstack/service-automation` entry and a new Setup nav entry. The seat's claim said `no`. - Not changed: the dogfood roster `packages/qa/dogfood/test/platform-app-object-entry-views.test.ts` judges object entries only, and this entry is a component, so it needs no row. - jsdom covers every acceptance bullet above. Only a live stack can show three things: the entry appearing in a real Setup sidebar for an admin and not for a member, a real install-local flipping a degraded connector to ready through the step, and the walled posture's sentence on a `group` deployment. None of the three was run here. ## Verification Head `e7b058e5`, merged with `origin/main` `139bc181`. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: 97 derived, all exit 0. `--ran` reconciliation: 97 run, 0 NOT-MEASURED, 0 UNRUN. Two of them (`check:skill-examples`, `check:dual-build-cjs-loads`) first exited 3 on missing dists and passed after a full workspace build. - Builds: `env -u OS_SKIP_DTS pnpm turbo run build` for the console closure plus service-automation, platform-objects and cli, then a full workspace build and `pnpm --filter @object-ui/console build`. - Outside the derivation, all exit 0: ``` pnpm --filter @objectstack/service-automation run typecheck :: exit 0 pnpm --filter @objectstack/service-automation run check:test-typecheck :: exit 0 pnpm --filter @objectstack/platform-objects run typecheck :: exit 0 pnpm --filter @objectstack/cli run typecheck :: exit 0 pnpm --filter @object-ui/i18n run type-check :: exit 0 pnpm --filter @object-ui/app-shell run type-check :: exit 0 pnpm check:app-nav-i18n :: exit 0 (13 contributors, setup: 58 merged nav ids, every id labelled in every locale) cd ui && node scripts/check-eager-closure-budget.mjs :: exit 0 (2006.9 KB of 2431.6 KB) cd ui && node scripts/check-i18n-call-site-keys.mjs :: exit 0 cd ui && node scripts/check-i18n-en-drift.mjs :: exit 0 pnpm --dir ui run check:new-line-citations :: exit 0 (0 new) pnpm --dir ui exec vitest run packages/app-shell/src/views/setup packages/app-shell/src/console/marketplace :: exit 0 (50 files, 487 tests) pnpm --dir ui exec vitest run packages/i18n :: exit 0 packages/services/service-automation: vitest run connector-credentials-ui, activation-ledger-registration, connector-credential-door :: exit 0 (33 tests) eslint on the new and changed source files :: exit 0 (no warnings in the new files) ``` --- _Generated by [Claude Code](https://claude.ai/code/session_01L9qibvG8uA9GWnnZkscgFF)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 35ffb77 commit 0c8c27e

36 files changed

Lines changed: 2493 additions & 13 deletions
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
'@objectstack/service-automation': patch
5+
'@objectstack/platform-objects': patch
6+
---
7+
8+
feat(console): Setup → Integrations → Connector Credentials, and a credential step after a marketplace install
9+
10+
Clause-②: yes (widening: `@objectstack/service-automation` exports `CONNECTOR_CREDENTIALS_UI_BUNDLE`, `CONNECTOR_CREDENTIALS_COMPONENT_REF` and `CONNECTOR_CREDENTIALS_NAV_CAPABILITY`, and registers a new Setup navigation entry `nav_connector_credentials` resolving component ref `automation:connector-credentials`; no existing key, route or shape changes)
11+
Metadata-protocol: none
12+
13+
An administrator can now set a connector credential without the API or the server environment.
14+
15+
- **Setup → Integrations → Connector Credentials** lists every reference the credential routes list (`GET /api/v1/automation/connectors/credential-refs`): its status (`set`, `missing`, `rejected` with the upstream's reason), the connectors naming it, and when and by whom it last changed. Each writable reference has a masked input that starts empty, whatever is stored; an empty input means "keep the current value", so nothing is sent for it. Saving sends `PUT` and the row takes the entry the server answers after its reconcile. A reference with a value stored here has **Clear**, behind a confirmation, which sends `DELETE`. A reference an environment variable supplies is read-only.
16+
- Where the routes refuse (`403` for a missing capability or a multi-organization tenancy posture, `501`, `503`), the page shows the server's own sentence as a configuration notice, and a server without the routes (`404` / `405`) gets "this server does not manage connector credentials". Neither is drawn as an error.
17+
- **After a marketplace install** into this runtime — install-local, an org package installed here, or a cloud install once its app is served — the install result lists the credentials the package's connectors still need (the same listing, filtered by `connectors[].packageId`), each with the same masked input and **Skip for now**. A skipped reference stays listed with a link to the Setup page. The step comes after the install because the routes refuse a reference no installed package declares (`404`); the connectors materialize as soon as a credential is set, with no restart. Where the listing cannot be read, the install answer's `credentialNote` is shown instead.
18+
- The navigation entry is contributed by `@objectstack/service-automation` at `kernel:ready` through the manifest service, so it appears only where the automation service runs, and only for a viewer holding `manage_metadata`. The label is translated in the four platform locales; the page and the step are translated in all ten Console packs.

‎content/docs/automation/connectors.mdx‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,24 @@ that names the reference, and a dispatch to it fails with the same reason.
313313

314314
#### Setting a credential
315315

316-
Three routes, each requiring the `manage_metadata` capability:
316+
**In the Console**, an administrator sets them in **Setup → Integrations →
317+
Connector Credentials**. The page lists every reference with its status, the
318+
connectors that name it, and when it last changed. Each writable reference has
319+
a masked input that starts empty, whatever is stored: typing a value and saving
320+
stores it, and leaving the input empty keeps the current one. A reference with
321+
a value stored here also has **Clear**. One that an environment variable
322+
supplies is read-only, since the variable wins, and a `rejected` one shows the
323+
upstream's reason. Where the routes below refuse (a multi-organization
324+
environment, or a server without them), the page shows the server's sentence
325+
instead of a list.
326+
327+
Installing an app from the marketplace ends on the same inputs: once the
328+
install lands, the credentials its connectors still need are listed, each with
329+
**Skip for now**. A skipped one leaves its connectors degraded until it is set
330+
on the Setup page. The step comes after the install, not before it, because the
331+
routes below refuse a reference that no installed app declares.
332+
333+
Over the API, three routes, each requiring the `manage_metadata` capability:
317334

318335
```http
319336
GET /api/v1/automation/connectors/credential-refs

‎content/docs/deployment/marketplace-handbook.mdx‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,12 @@ administrator, not for a contributor:
107107
install looks like.
108108
- **Every credential it needs** — for each one, its name, where the
109109
administrator obtains it, and which permissions or scopes it must carry. You
110-
write the names; the administrator supplies the values. The platform does not
111-
prompt for credentials at install yet: the install-time prompt and the
112-
credential store arrive with
113-
[#27237](https://github.com/objectstack-ai/objectstack/issues/27237) and the
114-
Console credentials page with
115-
[#27238](https://github.com/objectstack-ai/objectstack/issues/27238). Until
116-
then the README is the whole hand-off. An app that ships declarative
110+
write the names; the administrator supplies the values. Once the app is
111+
installed, the Console lists the credentials its connectors still need in the
112+
install result, and **Setup → Integrations → Connector Credentials** holds
113+
every one of them ([Setting a credential](/docs/automation/connectors#setting-a-credential)).
114+
The README is still where the administrator learns what each value is and
115+
where to get it. An app that ships declarative
117116
connectors gains an offline spec-normalization tool with
118117
[#26709](https://github.com/objectstack-ai/objectstack/issues/26709).
119118

@@ -203,8 +202,12 @@ the detail.
203202
4. **The dependency check.** An app that declares a dependency the environment
204203
lacks is refused before anything is written, one line per unmet entry
205204
([App dependencies](/docs/deployment/app-dependencies)).
206-
5. **Credentials.** No screen yet — the administrator follows your README (see
207-
above, and #27237 / #27238).
205+
5. **Credentials.** Once the install lands, its result lists the credentials
206+
the app's connectors still need, each with a masked input and **Skip for
207+
now**. A skipped one leaves its connectors disconnected until it is set in
208+
**Setup → Integrations → Connector Credentials**
209+
([Setting a credential](/docs/automation/connectors#setting-a-credential)).
210+
Your README says where each value comes from (see above).
208211
6. **Sample data.** The **⋯** (More install options) menu on the app's page
209212
offers **Add sample data**, then **Re-seed sample data**, and **Purge sample
210213
data**, which deletes only the records your seed created. Behind them are

‎packages/cli/scripts/check-app-nav-i18n.mjs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,18 @@ const CONTRIBUTORS = [
416416
return { manifests: [CONNECT_AGENT_UI_BUNDLE] };
417417
},
418418
},
419+
{
420+
// `nav_connector_credentials` (Setup → Integrations). Registered by
421+
// `AutomationServicePlugin` at `kernel:ready`, which the shared context
422+
// below never fires, so the bundle the plugin registers is read directly —
423+
// the `@objectstack/mcp` row's shape.
424+
source: '@objectstack/service-automation (CONNECTOR_CREDENTIALS_UI_BUNDLE)',
425+
apps: ['setup'],
426+
async load() {
427+
const { CONNECTOR_CREDENTIALS_UI_BUNDLE } = await import('@objectstack/service-automation');
428+
return { manifests: [CONNECTOR_CREDENTIALS_UI_BUNDLE] };
429+
},
430+
},
419431
{
420432
source: '@objectstack/cloud-connection (cloud + marketplace UI bundles)',
421433
apps: ['setup'],

‎packages/platform-objects/src/apps/translations/en.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export const en: TranslationData = {
155155
nav_webhooks: { label: 'Webhooks' },
156156
nav_http_deliveries: { label: 'HTTP Deliveries' },
157157
nav_datasources: { label: 'Datasources' },
158+
nav_connector_credentials: { label: 'Connector Credentials' },
158159

159160
// Advanced
160161
//

‎packages/platform-objects/src/apps/translations/es-ES.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export const esES: TranslationData = {
106106
nav_webhooks: { label: 'Webhooks' },
107107
nav_http_deliveries: { label: 'Entregas HTTP' },
108108
nav_datasources: { label: 'Fuentes de datos' },
109+
nav_connector_credentials: { label: 'Credenciales de conectores' },
109110

110111
nav_oauth_apps: { label: 'Aplicaciones OAuth' },
111112
},

‎packages/platform-objects/src/apps/translations/ja-JP.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export const jaJP: TranslationData = {
106106
nav_webhooks: { label: 'Webhooks' },
107107
nav_http_deliveries: { label: 'HTTP 配信' },
108108
nav_datasources: { label: 'データソース' },
109+
nav_connector_credentials: { label: 'コネクタ認証情報' },
109110

110111
nav_oauth_apps: { label: 'OAuth アプリケーション' },
111112
},

‎packages/platform-objects/src/apps/translations/zh-CN.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export const zhCN: TranslationData = {
116116
nav_webhooks: { label: 'Webhooks' },
117117
nav_http_deliveries: { label: 'HTTP 投递' },
118118
nav_datasources: { label: '数据源' },
119+
nav_connector_credentials: { label: '连接器凭据' },
119120

120121
nav_oauth_apps: { label: 'OAuth 应用' },
121122
},
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#27238] The Setup entry for the connector credential door (ruling A on
5+
* #27230) ships with the door: `CONNECTOR_CREDENTIALS_UI_BUNDLE`, registered
6+
* through the `manifest` service at `kernel:ready` (cloud ADR-0009 shape).
7+
*
8+
* • the bundle is a valid manifest whose one contribution puts a component
9+
* item in Setup's Integrations group, naming the console component
10+
* `automation:connector-credentials` and asking `manage_metadata` — the
11+
* capability every verb of the door asks;
12+
* • a booted kernel registers it once, after `kernel:ready`;
13+
* • an inert boot (`armRuntime: false`, a one-shot tool) serves no console
14+
* and registers no entry; neither does a kernel with no manifest service,
15+
* and that boot does not fail.
16+
*/
17+
import { describe, expect, it } from 'vitest';
18+
import { LiteKernel } from '@objectstack/core';
19+
import { ManifestSchema } from '@objectstack/spec/kernel';
20+
21+
import { AutomationServicePlugin } from './plugin.js';
22+
import {
23+
CONNECTOR_CREDENTIALS_COMPONENT_REF,
24+
CONNECTOR_CREDENTIALS_NAV_CAPABILITY,
25+
CONNECTOR_CREDENTIALS_UI_BUNDLE,
26+
} from './connector-credentials-ui.js';
27+
28+
describe('[#27238] the connector credentials UI bundle', () => {
29+
it('is a valid manifest', () => {
30+
expect(() => ManifestSchema.parse(CONNECTOR_CREDENTIALS_UI_BUNDLE)).not.toThrow();
31+
});
32+
33+
it('contributes one Setup → Integrations component item, gated on manage_metadata', () => {
34+
expect(CONNECTOR_CREDENTIALS_COMPONENT_REF).toBe('automation:connector-credentials');
35+
expect(CONNECTOR_CREDENTIALS_NAV_CAPABILITY).toBe('manage_metadata');
36+
expect(CONNECTOR_CREDENTIALS_UI_BUNDLE.navigationContributions).toEqual([
37+
{
38+
app: 'setup',
39+
group: 'group_integrations',
40+
priority: 120,
41+
items: [
42+
{
43+
id: 'nav_connector_credentials',
44+
type: 'component',
45+
label: 'Connector Credentials',
46+
componentRef: 'automation:connector-credentials',
47+
icon: 'key-round',
48+
requiredPermissions: ['manage_metadata'],
49+
},
50+
],
51+
},
52+
]);
53+
});
54+
});
55+
56+
/** Boot a kernel whose `manifest` service (when given) records every registration. */
57+
async function boot(opts: { withManifest: boolean; armRuntime?: boolean }) {
58+
const registered: Array<{ id?: string }> = [];
59+
const kernel = new LiteKernel({ logger: { level: 'silent' } } as never);
60+
// Registered FIRST, so the manifest service exists before the automation
61+
// plugin's init() — the order a real composition gives it.
62+
kernel.use({
63+
name: 'test.manifest',
64+
type: 'standard',
65+
version: '1.0.0',
66+
dependencies: [] as string[],
67+
async init(ctx: any) {
68+
if (opts.withManifest) ctx.registerService('manifest', { register: (m: { id?: string }) => { registered.push(m); } });
69+
},
70+
async start() {},
71+
} as never);
72+
kernel.use(new AutomationServicePlugin(opts.armRuntime === false ? { armRuntime: false } : {}));
73+
await kernel.bootstrap();
74+
return { kernel, ids: () => registered.map((m) => m.id) };
75+
}
76+
77+
describe('[#27238] the automation plugin registers the bundle at kernel:ready', () => {
78+
it('a booted kernel registers it exactly once', async () => {
79+
const { kernel, ids } = await boot({ withManifest: true });
80+
try {
81+
expect(ids().filter((id) => id === CONNECTOR_CREDENTIALS_UI_BUNDLE.id)).toHaveLength(1);
82+
} finally {
83+
await kernel.shutdown();
84+
}
85+
});
86+
87+
it('an inert boot registers no entry', async () => {
88+
const { kernel, ids } = await boot({ withManifest: true, armRuntime: false });
89+
try {
90+
expect(ids()).not.toContain(CONNECTOR_CREDENTIALS_UI_BUNDLE.id);
91+
} finally {
92+
await kernel.shutdown();
93+
}
94+
});
95+
96+
it('a kernel with no manifest service boots, with no entry to carry', async () => {
97+
const { kernel, ids } = await boot({ withManifest: false });
98+
try {
99+
expect(ids()).toEqual([]);
100+
} finally {
101+
await kernel.shutdown();
102+
}
103+
});
104+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* Setup → Integrations → "Connector Credentials" — the navigation entry of the
5+
* connector credential door, carried by the automation service that owns the
6+
* door (ruling A on #27230; cloud ADR-0009 shape, as `packages/cloud-connection`
7+
* restates it: the nav entry lives and dies with the capability — no automation
8+
* service, no entry).
9+
*
10+
* The page body is the console's `automation:connector-credentials` component
11+
* (`@object-ui/app-shell`), the way Setup's packaged-automation entry names
12+
* `automation:packaged`. It reads `GET /api/v1/automation/connectors/credential-refs`
13+
* and writes through `PUT` / `DELETE …/credential-refs/:ref`; ⛔ no value ever
14+
* reaches it, since none of the three routes answers one.
15+
*
16+
* Registered by {@link AutomationServicePlugin} at `kernel:ready`. The entry
17+
* declares the door's own capability, `manage_metadata`, which every one of its
18+
* three routes asks before anything else: the rule the page's routes enforce
19+
* is stated by the plugin that enforces it, not borrowed from the group.
20+
*/
21+
22+
/** The console component the entry opens. */
23+
export const CONNECTOR_CREDENTIALS_COMPONENT_REF = 'automation:connector-credentials';
24+
25+
/** The capability the credential door asks on all three routes. */
26+
export const CONNECTOR_CREDENTIALS_NAV_CAPABILITY = 'manage_metadata';
27+
28+
/**
29+
* `priority` orders contributions within a group, lowest first. The group's
30+
* other contributors, read where they are declared: Webhooks / HTTP Deliveries
31+
* (`@objectstack/plugin-webhooks`) and Datasources
32+
* (`@objectstack/service-datasource`) at 100, Connect an Agent
33+
* (`@objectstack/mcp`) at 110, Cloud Connection (`@objectstack/cloud-connection`)
34+
* at 200 — so 120 sorts this entry after the connection surfaces it serves.
35+
*/
36+
export const CONNECTOR_CREDENTIALS_UI_BUNDLE = {
37+
id: 'com.objectstack.service-automation.connector-credentials-ui',
38+
namespace: 'sys',
39+
version: '0.1.0',
40+
type: 'plugin',
41+
scope: 'system',
42+
name: 'Connector Credentials UI',
43+
description: 'Setup navigation for the connector credentials the installed apps declare: set, replace or clear each one.',
44+
navigationContributions: [
45+
{
46+
app: 'setup',
47+
group: 'group_integrations',
48+
priority: 120,
49+
items: [
50+
{
51+
id: 'nav_connector_credentials',
52+
type: 'component',
53+
label: 'Connector Credentials',
54+
componentRef: CONNECTOR_CREDENTIALS_COMPONENT_REF,
55+
icon: 'key-round',
56+
requiredPermissions: [CONNECTOR_CREDENTIALS_NAV_CAPABILITY],
57+
},
58+
],
59+
},
60+
],
61+
};

0 commit comments

Comments
 (0)