Repository navigation
[Decision] Where a customer administrator supplies a declarative connector credential: a platform credentials page over an encrypted object, an app-shipped settings manifest, env-only as today, or the OAuth connection record #27230
Description
Filing gate ③ (maintainer-directed). The maintainer in this session, 2026-10-28, verbatim: 「你说的几个问题立卡,立决策卡。」 then 「不过具体的决策细节你现在直接和我沟通就行。」 — the letter is ruled in the session thread and recorded here; this card is the board's copy of the question, the options and the analysis, so triage can cut the execution cards from it.
Filed by session_01J8VVAjRKqdoEYQ4hXxtBuf. ⛔ Not a claim. No domain:* until the ruling; the execution cards split across domain:server (resolver, object, install prompt), domain:ui (the page, the install dialog) and, under option A, domain:spec (a stack-level settings: metadata type).
维护者速读
- 一句话问题:应用作者在连接器里只写一个密钥的名字(
credentialRef),装应用的客户管理员要在哪里把真正的密钥填进去?今天的答案是"只能由运维在服务器上设环境变量",托管云上的客户管理员没有这个权限,所以 B 波(Stripe、GitHub、Google、支付宝…)的连接器对客户是装了也用不了的。 - 谁撞上了(实测):每一个带连接器的应用的每一次安装。HotCRM 现有的 Slack 连接器、hotclm 设计里"提供商与凭证在
sys_setting"的假设,都落在这个缺口上。 - 选项:A 平台通用"连接器凭证"页:一个加密对象按已声明的
credentialRef自动列出待填项,解析链 env → 该对象 → vault,安装时问必填项;B 应用随包携带设置清单(新增settings:元数据类型),credentialRef指向设置键,Setup 自动生成该应用的设置页;C 维持 env-only,托管云在 cloud 侧做环境变量管理;D 并入 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 的连接记录,静态密钥也当作一种"连接"。 - 子问题 S1 已裁:共享环境(group / isolated 姿态)下的"每个组织各自的密钥"不支持。维护者原话(本会话,2026-10-28):「暂时不考虑 一个环境里住多个组织的情况,这种只有我们的云控制台在用,这种模式下可以不支持。」
- 推荐 A 先做,B 作为第二张卡、以 hotclm 为第一消费者。 回退 B。
- 请回:
A / B / C / D。
Background
- A declarative connector carries
auth: { type, credentialRef }— a name, never a secret (ADR-0097 §3). In the open tier the name is resolved once, at boot, fromprocess.env(packages/services/service-automation/src/credential-resolver.ts,defaultEnvCredentialResolver); an unresolved name is a hard boot error. The enterprise tier may inject a vault resolver throughAutomationServicePluginOptions.credentialResolver. - The platform already has an encrypted secret store and a settings UI: ADR-0007's settings manifests render Setup pages from a closed specifier set; a
passwordspecifier is encrypted,sys_settingholds a handle andsys_secretthe ciphertext (AES-256-GCM underOS_SECRET_KEY, KMS adapters), with audit and an env-override convention (stripe.secret_key→STRIPE_SECRET_KEY). Manifests are registered by code only (new SettingsServicePlugin({ manifests }), the built-in mail / auth / branding / ai / company manifests);defineStackhas nosettings:key, so an app cannot ship one. - The automation service already runs a write-only encrypted credential channel for flow nodes:
sys_flow_credential(security(flows): move a flow's inbound-hook secret out of flow metadata into the write-only secret seam #7799 established — no read, the generic data door included, returns it #20790),Field.secret()encrypted on write through the hostICryptoProvider, masked on read, dereferenced only byresolveSecretField()at execution. That is the precedent for a connector credential object. - The marketplace install request carries
settings("User-provided settings at install time",packages/spec/src/marketplace/marketplace.zod.ts:498); install-local does not read it. - Two different people: the metadata author declares the name; the customer's administrator supplies the value; the platform resolves. The question is only where the value lives and how the administrator reaches it.
Governing text
- ADR-0097 §3: "
credentialRefresolves through the secrets service at materialization time. Inline secrets in stack metadata are rejected at authoring/publish … static credentials resolved from env/config are open; managed vaulting, OAuth2 refresh, and per-tenant connection lifecycle are enterprise." Re-check:git grep -n "resolved from env/config" origin/main -- docs/adr/0097-declarative-connector-instances.md. - ADR-0007 consequences: "Plugin-friendly. Third-party plugins get the entire settings UI for free by exporting one TS constant" and "Env override is enforced, not advisory. Plugins cannot bypass it because they don't query
sys_settingdirectly." Re-check:git grep -n "Plugin-friendly" origin/main -- docs/adr/0007-settings-manifest-and-kv-store.md. - ADR-0131 tenancy postures:
managedis the only install mode ongroup/isolated; every regime there is environment-wide and operator-gated. - AGENTS.md Prime Directive chore: version packages #10: never advertise a capability the runtime does not deliver — a connector an administrator cannot credential is such a capability on hosted environments.
packages/services/service-automation/src/credential-resolver.tsheader: "a reference is the name of an environment variable. This is the degraded-but-honest story for environments without a managed secrets service". Re-check:git grep -n "degraded-but-honest" origin/main -- packages/services/service-automation/src/credential-resolver.ts.
Protocol statement. A, C and D add no spec key (A adds a sys_* object and a resolver; the install prompt reads the already-declared credentialRefs). B is Metadata-protocol: added: a settings: entry on defineStack and a settings metadata type in the registry. S1 is ruled: environment-level only, no organization scope.
Premises (each with a re-check; read at c3fb185a)
- Open-tier resolution is env-only and boot-time. Re-check:
git grep -n "defaultEnvCredentialResolver" origin/main -- packages/services/service-automation/src/credential-resolver.ts packages/services/service-automation/src/plugin.ts. - No
settings:key on the stack; manifests are code-registered. Re-check:git grep -n "settings:" origin/main -- packages/spec/src/stack.zod.ts(no manifest key) andgit grep -n "manifests" origin/main -- packages/services/service-settings/src/index.ts. - The encrypted secret channel exists and is used by flows. Re-check:
git grep -n "resolveSecretField" origin/main -- packages/services/service-automation/src/flow-credential-channel.ts. - The install request's
settingsfield is unread by install-local. Re-check:git grep -n -i "settings" origin/main -- packages/cloud-connection/src/marketplace-install-local-plugin.ts(only unrelated hits). - Settings scope today is
tenant | user | runtime(ADR-0007 storage schema); no organization scope.
Options
| Option | What it does | What the administrator sees | Effort |
|---|---|---|---|
| A platform credentials page | A sys_connector_credential object (Field.secret() value, the sys_flow_credential seam), one row per declared credentialRef; Setup → Integrations → "Connector credentials" lists every ref the installed apps declare, with its connector and status (set / missing); resolver chain env → object → vault, re-resolved when a row changes (reconcile on the existing hot-reload signature); install-local prompts for the missing refs of the package being installed. |
One page listing "HotCRM · stripe · STRIPE_SECRET_KEY · missing"; fills it; the connector comes up without a restart. | domain:server 1–2 PRs (object, resolver, reconcile, install prompt) + domain:ui 1 PR (page, install dialog) · precedent: sys_flow_credential (#20790), lines not measured. |
| B app-shipped settings manifest | defineStack({ settings: [...] }) carries ADR-0007 manifests; credentialRef names a manifest key (stripe.secret_key), env override per ADR-0007; Setup renders a per-app page with descriptions, validation and action-button ("Test connection"); the same resolver chain. |
A "Stripe" page under the app's own settings, with explanation and a test button. | domain:spec 1 PR (type + stack key) + domain:server 1–2 PRs + domain:ui 1 PR · no precedent for an app-shipped manifest. |
| C env-only as today | Nothing in this repository; hosted environments expose env management in the cloud console. | Self-host: edit the server env and restart; hosted: a cloud page, when it exists. | 0 here; cloud card unknown. |
| D everything is a connection (#26702) | Static keys stored as a degenerate "connection" of the OAuth connection record; one Connect / Reconnect UI for all. | "Connect Stripe" asks for a key instead of redirecting. | Blocked on #26702's record; couples static keys to a design still being ruled. |
S1 — per-organization credentials on shared environments: ruled, not supported. The maintainer, verbatim: 「暂时不考虑 一个环境里住多个组织的情况,这种只有我们的云控制台在用,这种模式下可以不支持。」 Connector instances and their credentials stay environment-level (ADR-0097 today). A hosted tenant that needs its own vendor accounts gets its own environment; the shared-environment postures (group / isolated) are the cloud console's own, and connector credentials there are out of scope.
In business terms.
- A: a key cabinet in the admin console, with one drawer per key the installed apps ask for.
- B: each app brings its own settings page, keys included.
- C: keys live on the server; only whoever operates the server can set them.
- D: every key is a "connection", static or OAuth alike.
四维分析(业务立场)
- 项目长远合理性(领起)。 两年后的样子:管理员在一处看到"装了哪些应用、各要哪些凭证、哪些还没填",凭证加密落库、审计可查、换密钥不重启;应用作者只写名字。A 直接到达这个终态,且复用两条已存在的平台机制(
Field.secret()加密通道、设置的解析与审计),不加协议键。B 也到达,并多给应用一个非密钥配置的面(hotclm 设计里"提供商选择"这类),但它是另一种能力:应用设置页;把它和凭证绑在一起会让"没写设置清单的应用"连密钥都填不了。C 把托管云客户挡在门外,违反第 10 条。D 把静态密钥的形状押在 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 未裁的连接记录上。主流平台同形:Salesforce 的 Named Credentials 是一个独立于应用的凭证对象,应用只引用名字;Power Platform 的 connection 同理。A 就是 Named Credentials。 - 实际业务拉动。 有实测拉动:每一个带连接器的应用的每一次托管安装;HotCRM 的 Slack 连接器今天就只能靠环境变量;hotclm 设计已假设凭证在
sys_setting。有拉动 ⇒ 一次付清长远形态,选 A。 - 防 AI 犯错。 出错时谁看到什么:A 下待填项由声明自动推导,作者无法漏声明,缺密钥在安装时和凭证页上都响亮;B 下作者要在连接器和设置清单两处写同一个键,漂移时实例启动失败、页面却显示正常,需要一条 lint 才能闭合;D 下 OAuth 与静态密钥共用一个形状,Agent 和作者容易选错类型。
- 创业阶段不扩散。 A 无新协议键、无新门禁;B 新增一个元数据类型与一个 stack 键,是永久义务,等第一个需要非密钥配置的应用(hotclm 的提供商选择)再立;S1 今天零拉动,延后并写明触发条件。
os-decision-facets
- ① 项目长远合理性:A 到达"一处看全部凭证、应用只写名字"的终态并复用两条既有机制;B 同终态但把凭证绑在应用设置页上;C 违反第 10 条;D 押在未裁的连接记录上。
- ② 实际业务拉动:每一个带连接器应用的每一次托管安装今天都卡住;HotCRM 的 Slack 与 hotclm 的设计假设是现成的两例。
- ③ 防 AI 犯错:A 待填项由声明推导、缺项响亮;B 两处写同一个键会漂移;D 两种鉴权一个形状易选错。
- ④ 创业阶段不扩散:A 无新键无新门;B 加一个元数据类型;S1 已裁不支持,不扩散。
Prior rulings read: "connector credentialRef resolve settings secret store admin settings page app-shipped settings manifest install wizard credentials per organization" → 2 hits (#24530 closed, sys_secret / sys_flow_credential to deployment level; #7902 closed, credential-in-JSON survey), neither this; "app ships settings manifest defineStack settings key Setup page generated for an installed app" → 2 hits (#25334, #7266), unrelated; ADR-0097 §3, ADR-0007, ADR-0131 D6; thread: none.
Recommendation
- A, fallback B. B is filed as its own card when the first app needs non-secret configuration with validation (hotclm is the candidate).
- 只看①选 A;②③④ 是否翻转:否。
- Confidence gap: (i) whether the hot-reload reconcile can be triggered by a credential-row change without a restart was not measured (the signature mechanism exists; the trigger does not); (ii) whether hosted runtimes ship the
openapi/mcp/restprovider plugins by default was not read — if not, A credentials a connector that cannot boot there, and that is a separate card; (iii) the install prompt assumes install-local can reach the automation service's declared instances before registration, not verified.
After the ruling
- A: triage files
domain:server(object + resolver chain + reconcile + install prompt) anddomain:ui(credentials page under Setup → Integrations; install dialog fields) cards; acceptance: install HotCRM on a fresh environment with no env vars, fill the Stripe key on the page, the connector materializes without restart, the key appears nowhere in logs orGET /api/v1/automation/connectors. - B: the same plus a
domain:speccard for thesettings:type; the resolver reads manifest keys. - C: close not_planned; a cloud card for env management.
- D:
Blocked-by: #26702; the connection record's ADR gains a static-key variant. - S1 is ruled above (not supported); no card.
Related
#26702 (OAuth connections) · #26709 · #26710 · #27223 (skills) · #25747 · ADR-0097 · ADR-0007 · ADR-0131 · #20790 (sys_flow_credential).
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 28, 2026
Ruled: letter A · 2026-10-28T11:55Z
Maintainer ruling (this session, 2026-10-28, verbatim, not translated): the recommendation on this card was presented to the maintainer directly in the session thread, per 「不过具体的决策细节你现在直接和我沟通就行」; the reply was 「同意」. Sub-question S1 was ruled earlier in the same thread: 「暂时不考虑 一个环境里住多个组织的情况,这种只有我们的云控制台在用,这种模式下可以不支持。」
What is decided.
sys_connector_credentialobject on thesys_flow_credentialseam (Field.secret()value, encrypted on write through the hostICryptoProvider, masked on read, dereferenced only byresolveSecretField()), one row per declaredcredentialRef; the open-tier resolver chain becomes env → that object → (enterprise) vault; a credential row change triggers the connector instance's reconcile without a restart; install-local prompts for the missing refs of the package being installed and lets the administrator skip; Setup → Integrations gains a "Connector credentials" page listing every ref the installed apps declare with its connector and status.Execution cards (filed by this seat on the ruling, triage routes): a
domain:servercard for the object, the resolver chain, the reconcile trigger and the install prompt; adomain:uicard for the credentials page and the install dialog. Acceptance across both: install HotCRM into a fresh environment with no env vars; fill the Stripe key on the page; the connector materializes without restart; the key appears nowhere in logs or inGET /api/v1/automation/connectors.Confidence gaps carried into the execution cards, each a premise the dev re-checks first: whether a credential-row change can trigger the existing hot-reload reconcile; whether hosted runtimes ship the
openapi/mcp/restprovider plugins by default; whether install-local can read a package's declaredcredentialRefs before registration.Closed as completed by
session_01J8VVAjRKqdoEYQ4hXxtBuf.Generated by Claude Code