Skip to content

Commit 4bc077d

Browse files
docs(automation): connector credential texts follow the degrade rule (ADR-0097 note, clearConnectorCredential 503, QA item) (#28346)
domain:spec#2 · os-tesla · session_01L9qibx5r5nhDMwpRs4SL6D <!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoBztoGKkaTTuEcZrcnG8Yhv)_ Fixes #27429 Clause-②: no (text only) Metadata-protocol: none ## 维护者速读 - 改了什么:六处文字跟上连接器凭据的现行规则:ADR-0097 加一条带日期的修订说明和一行指引,`clearConnectorCredential` 的拒绝清单补上 `503`,连接器文档与 MCP 提供方的一句注释不再把"凭据引用解析不到"归为启动失败,QA 清单的一条用例改为验收"降级"而不是"启动失败",自动化服务里连接器物化函数的说明注释同样改正。 - 为什么改:#27237 之后,凭据引用解析不到时只把该连接器实例降级(应用照常启动,管理员事后补填凭据即可恢复),清除已存凭据会把在线实例拆下;这六处仍写着旧规则,AI 和作者读到会照着错的规则写元数据、写测试。 - 风险与回滚:只改注释、文档和 QA 清单文字,不动任何运行行为;回滚即 revert 本 PR。ADR 改动使本 PR 属 Tier H,需要授权审批。 - 先看:`docs/adr/0097-declarative-connector-instances.md` 中 "Runtime reconcile" 下的新修订说明;`docs/qa/platform-checklist/areas/integration-system.json` 条目 `integration-system.connector-auth-kind-application` 的 `acceptance[2]` 与 `negative[2]`;`packages/spec/src/contracts/automation-service.ts` 的 `clearConnectorCredential` 注释。 - 两分钟验证:`pnpm check:platform-checklist && pnpm check:adr-symbol-anchors`,预期两个都通过(清单校验认可 revision 5,ADR 里引用的 `#degradeConnectorForCredential` 能解析到代码)。 - 需要你做的:以 os-zhuang 审批本 PR(Tier H,ADR 一行);之后由席位落地。 ## What changes #27237 (PR #27344) made an unresolved or refused `credentialRef` degrade its instance instead of failing the boot, and its credential door's `DELETE` tears a live instance down. Five texts still stated the old rule, and a sixth (the materializer's own docblock) was folded in by the seat's claim amendment. Each was measured against `main` at `501c864d` (`plugin.ts#degradeConnectorForCredential`, `#clearConnectorCredential`, `#doorCredentialChain`, `#scheduleDeclarativeRetry`) before it was rewritten. None of them had been fixed by a later PR; the card's line numbers had drifted (noted per item). 1. **`docs/adr/0097-declarative-connector-instances.md`** (Tier H; triage 6490151714's cross-domain exception). - A dated amendment note under "Runtime reconcile": an unresolvable `credentialRef` leaves the reload list, as it left the boot list (the §6 note). A reference no source answers for degrades its instance at boot and on a reload alike, and a live connector under that name is torn down first; that is how a cleared stored credential takes its instances down. A stored credential the deployment cannot read right now (unreadable store, no crypto provider yet) degrades too but leaves a live connector serving. Both go on the backoff. A host resolver that throws keeps the old rule (fatal at boot, logged and skipped on reload). Each fact is read from `degradeConnectorForCredential` and the reconcile's `catch` around `resolveConnectorCredential`. - A one-line pointer in the "Upstream availability" lead, after "stay fatal at boot", naming the notes under §6 and under "Runtime reconcile". - The header's `Amended by` line gains the entry for this note, as every earlier amendment of this ADR has one. 2. **`packages/spec/src/contracts/automation-service.ts`**, `IAutomationService.clearConnectorCredential`: the refusal list gains `503 SERVICE_UNAVAILABLE` (when the store cannot be read or written, `ConnectorCredentialUnavailableError` in `#clearConnectorCredential`, or the tenancy posture cannot be read, `#doorCredentialChain`), nothing changed. Docblock only. 3. **`content/docs/automation/connectors.mdx`** (card ≈L361, now L407): the inline-secret example's "a reference, resolved at boot" becomes "resolved at materialization (unset, it degrades the instance)". The failure table at L593 already states the degrade. 4. **`packages/connectors/connector-mcp/src/mcp-provider.ts`** (card ≈L243, now L273): the comment no longer groups a rejected credential "like an unresolvable credentialRef" on the fatal side; it says an unresolvable reference never reaches the factory, since the materializer degrades its instance first. 5. **`docs/qa/platform-checklist/areas/integration-system.json`**, item `integration-system.connector-auth-kind-application` (card L1943 / L1992, now L1961 / L2010), revision 4 → 5 with a history row: `acceptance[2]` grades the degrade (api oracle: the degraded descriptor naming the reference and its remedies, the boot's warn line, the dispatch failure) and `negative[2]` fails a boot that dies on it; the title, `fixtures.requires[2]` (the env → `sys_connector_credential` → host resolver chain), `steps[3]`, `steps[5]`, `automated.ref` and `source[2]` follow. The cited pin "fails boot loudly when credentialRef does not resolve" no longer exists; the item now cites "degrades the instance, naming the reference, when credentialRef does not resolve" (`connector-materialization.test.ts`) and "missing → degraded naming the reference; set → ready; clear → degraded again" (`connector-credential-door.test.ts`), both run below. 6. **`packages/services/service-automation/src/plugin.ts`**, the `materializeDeclaredConnectors` docblock (≈L2093; added by the seat's claim amendment): the boot-fatal / reload-skipped list no longer names an unresolvable `credentialRef` (it names the host resolver that throws, which does keep that rule), and a paragraph says such a reference degrades its instance in both modes, tearing a live connector down first (`{@link degradeConnectorForCredential}`). Comment only. No changeset: the diff publishes no behaviour, only comment and docblock text (`skip-changeset` declared at create). ## Tests No test changes: text only. The two pins the QA item now cites exist and pass (output under Verification). ## Acceptance notes - `@objectstack/lint` author-facing text says a `credentialRef` is "resolved at boot" (`credential-literal.ts` ≈L22, `lint-flow-credential-literals.ts` ≈L29, `rule-explanations.ts` ≈L4705, the last pinned at `lint-flow-credential-literals.test.ts` ≈L502). It describes where the secret lives rather than the failure rule, so it is loose rather than wrong; carrier: none, noted only. - ADR-0097's 2026-10-27 note (PR #26798) still says a rejected MCP credential sits "beside an unresolvable `credentialRef`" on the fatal side. The 2026-10-28 note right after it already reverses the rejected-credential half, and the new lead pointer covers the `credentialRef` half; the dated note itself is left as written (amendments are append-only). - `clearConnectorCredential` also answers `400 VALIDATION_ERROR` for an empty or oversized reference (`#credentialRefOfRequest`), which its docblock does not list either. Left out: the ruling grants this one docblock line for the `503`. - The QA item's new `acceptance[2]` was not run live (no echo-upstream fixture in this container); its evidence is this body and the item's history row, as RUNNER.md asks for a ledger edit's own verification. ## Verification <details><summary>pins the QA item cites</summary> - Under the verify lock: `pnpm --filter @objectstack/service-automation exec vitest run --maxWorkers=2 src/connector-materialization.test.ts src/connector-credential-door.test.ts -t 'TWO_CASE_NAMES' (the two names above)` → Test Files 2 passed (2), Tests 2 passed | 82 skipped (84); VERDICT command-exit 0. </details> <details><summary>derived gates</summary> - At `8b24dd6d` (after the sixth text): `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives 98 families (the 95 below plus `check-tenant-audit-census` and its `--self-test`, and `check:durability-log-level`), each run with stdin from `/dev/null`, exit code recorded before any pipe, after a dts rebuild of `@objectstack/service-automation` (record: `27429-gates-ran-8b24dd6d.list` in the seat's out folder). `--ran` reconciliation: 98 derived, 98 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. - At `eef6c15b` (the five texts): 95 families, record `27429-gates-ran-eef6c15b.list`, after a dts build of every non-ui, non-docs package (turbo, lock VERDICT command-exit 0). `check-plugin-teardown-shape --self-test` first exited 3 on the shallow clone (its pinned control `621a4876` unreachable); after `git fetch origin 621a487 --depth=1` it exited 0. `--ran`: 95 derived, 95 run, 0 NOT-MEASURED, 0 UNRUN, all exit 0. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01L9qibx4wGGSrBq1y8R1CVJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4fa4f2c commit 4bc077d

6 files changed

Lines changed: 52 additions & 22 deletions

File tree

‎content/docs/automation/connectors.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ shape on a provider-bound instance —
415415
// with `auth: { type, credentialRef }` instead (ADR-0097 §3)."
416416
authentication: { type: 'bearer', token: 'sk_live_9f…' },
417417

418-
// ✓ a reference, resolved at boot:
418+
// ✓ a reference, resolved at materialization (unset, it degrades the instance):
419419
auth: { type: 'bearer', credentialRef: 'BILLING_API_TOKEN' },
420420
```
421421

‎docs/adr/0097-declarative-connector-instances.md‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
**Builds on**: [ADR-0015](./0015-external-datasource-federation.md) (open mechanism / enterprise lifecycle split), [ADR-0018](./0018-unified-node-action-registry.md) (`connector_action` baseline dispatch + `engine.registerConnector()`), [ADR-0022](./0022-connectors-vs-messaging-channels.md) (Connector = transport/integration mechanism), [ADR-0023](./0023-openapi-to-connector-generator.md) (OpenAPI → Connector generator), [ADR-0024](./0024-mcp-connectors.md) (MCP servers as connectors)
66
**Tracking**: framework#2977 (supersedes the interim descriptor-only contract from framework#2612)
77
**Consumers**: `@objectstack/spec` (`stack.zod.ts`, `integration/connector.zod.ts`), `@objectstack/service-automation` (boot audit → provider binding), `@objectstack/connector-openapi`, `@objectstack/connector-mcp`, `@objectstack/connector-rest`, the Studio flow palette, AI authoring (stack generation)
8-
**Amended by**: [ADR-0143](./0143-outbound-connector-auth-is-open-tier.md) (2026-10-27) — OAuth2, the vendor app-credential exchange and request signing join `ConnectorInstanceAuthSchema` in the open tier, and those kinds resolve per dispatch; see the notes under §3 and under "Open / enterprise line". PR #26798 (2026-10-27, #26709 item 3(b)) — a credential the MCP server rejects (HTTP `401` / `403`) is a fatal configuration fault, not upstream-unavailable; see the note under "Upstream availability". #27237 (2026-10-28, ruling A on #27230 and the ruling on #27237) — an unresolved `credentialRef` and a rejected credential degrade the instance instead of failing the boot (a refused stored credential holds its instance until it is set again or a restart; an environment or vault one is retried on the backoff), and the open tier reads a `sys_connector_credential` store between the environment and the host's resolver, except under a walled tenancy posture; see the notes under §6 and under "Upstream availability". #28038 (2026-10-30, after #27992 / PR #28014 and #28016 / PR #28037) — a held degraded instance (a configuration fault met on a retry, or a refused stored or declared credential) is not retried, and its dispatch refusal names the edit or the credential that unblocks it; "the platform retries automatically" stays for an instance on the backoff; see the note under "Upstream availability".
8+
**Amended by**: [ADR-0143](./0143-outbound-connector-auth-is-open-tier.md) (2026-10-27) — OAuth2, the vendor app-credential exchange and request signing join `ConnectorInstanceAuthSchema` in the open tier, and those kinds resolve per dispatch; see the notes under §3 and under "Open / enterprise line". PR #26798 (2026-10-27, #26709 item 3(b)) — a credential the MCP server rejects (HTTP `401` / `403`) is a fatal configuration fault, not upstream-unavailable; see the note under "Upstream availability". #27237 (2026-10-28, ruling A on #27230 and the ruling on #27237) — an unresolved `credentialRef` and a rejected credential degrade the instance instead of failing the boot (a refused stored credential holds its instance until it is set again or a restart; an environment or vault one is retried on the backoff), and the open tier reads a `sys_connector_credential` store between the environment and the host's resolver, except under a walled tenancy posture; see the notes under §6 and under "Upstream availability". #28038 (2026-10-30, after #27992 / PR #28014 and #28016 / PR #28037) — a held degraded instance (a configuration fault met on a retry, or a refused stored or declared credential) is not retried, and its dispatch refusal names the edit or the credential that unblocks it; "the platform retries automatically" stays for an instance on the backoff; see the note under "Upstream availability". #27429 (2026-10-31, after #27237) — on a reload too, an unresolved `credentialRef` degrades its instance instead of being skipped, and a credential that is no longer set takes the live connector down; see the note under "Runtime reconcile".
99

1010
---
1111

@@ -167,11 +167,25 @@ and skipped**, never crashing the live server; a changed instance's old connecto
167167
keeps serving until its replacement materializes successfully. Boot keeps the
168168
"fail loudly" contract.
169169

170+
> **Amendment note (2026-10-31, #27429 — after #27237).** An unresolvable `credentialRef` leaves
171+
> the reload list above, as it left the boot list (the note under §6). A reference no source
172+
> answers for degrades the instance naming it, at boot and on a reload alike, and a live
173+
> connector under that name is torn down first instead of serving on: the value it was
174+
> materialized with is gone
175+
> (`packages/services/service-automation/src/plugin.ts#degradeConnectorForCredential`). That is
176+
> how clearing a stored credential (`DELETE /api/v1/automation/connectors/credential-refs/:ref`)
177+
> takes its instances down. A stored credential this deployment cannot read right now (the store
178+
> cannot be read, or no crypto provider is registered yet) degrades the instance too, but leaves a
179+
> live connector serving, since the value still exists. Both go on the backoff under "Upstream
180+
> availability". A host resolver that throws keeps the rule above: fatal at boot, logged and
181+
> skipped on a reload.
182+
170183
### Upstream availability: degrade, don't die (follow-up, landed — #3017)
171184

172185
The fail-loud contract deliberately distinguishes **configuration** faults from
173186
**operational** faults. Unknown provider, invalid `providerConfig`, unresolvable
174-
`credentialRef`, name conflict — authoring mistakes — stay fatal at boot. But a
187+
`credentialRef`, name conflict — authoring mistakes — stay fatal at boot (an unresolvable
188+
`credentialRef` no longer does: see the notes under §6 and under "Runtime reconcile"). But a
175189
provider whose factory must contact an upstream to materialize (the `mcp`
176190
provider connects and calls `tools/list`) can fail because that upstream is
177191
*temporarily unreachable*; aborting the whole app boot for one integration's

0 commit comments

Comments
 (0)