Skip to content

[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

Closed

Description

@objectstack-fleet

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, from process.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 through AutomationServicePluginOptions.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 password specifier is encrypted, sys_setting holds a handle and sys_secret the ciphertext (AES-256-GCM under OS_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); defineStack has no settings: 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 host ICryptoProvider, masked on read, dereferenced only by resolveSecretField() 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: "credentialRef resolves 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_setting directly." Re-check: git grep -n "Plugin-friendly" origin/main -- docs/adr/0007-settings-manifest-and-kv-store.md.
  • ADR-0131 tenancy postures: managed is the only install mode on group / 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.ts header: "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)

  1. 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.
  2. 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) and git grep -n "manifests" origin/main -- packages/services/service-settings/src/index.ts.
  3. 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.
  4. The install request's settings field 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).
  5. 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 / rest provider 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

  1. A: triage files domain:server (object + resolver chain + reconcile + install prompt) and domain: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 or GET /api/v1/automation/connectors.
  2. B: the same plus a domain:spec card for the settings: type; the resolver reads manifest keys.
  3. C: close not_planned; a cloud card for env management.
  4. D: Blocked-by: #26702; the connection record's ADR gains a static-key variant.
  5. 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

  1. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    • A — platform credentials page. A sys_connector_credential object on the sys_flow_credential seam (Field.secret() value, encrypted on write through the host ICryptoProvider, masked on read, dereferenced only by resolveSecretField()), one row per declared credentialRef; 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.
    • B (app-shipped settings manifest) is not taken here; it is filed as its own card when the first app needs non-secret configuration with validation (HotCLM's provider selection is the candidate).
    • S1: not supported. Connector instances and their credentials stay environment-level; the shared-environment postures are the cloud console's own.

    Execution cards (filed by this seat on the ruling, triage routes): a domain:server card for the object, the resolver chain, the reconcile trigger and the install prompt; a domain:ui card 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 in GET /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 / rest provider plugins by default; whether install-local can read a package's declared credentialRefs before registration.

    Closed as completed by session_01J8VVAjRKqdoEYQ4hXxtBuf.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions