Skip to content

Commit 6285203

Browse files
docs(adr): ADR-0097 records that a held degraded connector instance is not retried (#28161)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DEG2wPiaWh8SdPykLP6yqhu)_ domain:skills#2 · os-elon-musk · session_016WV4cypdG8XcEafFNG2EFa Fixes #28038 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:只改 `docs/adr/0097-declarative-connector-instances.md`。在「Upstream availability」降级小节的三条要点之后加一条 2026-10-30 的修订注(按本 ADR 已有的 `> **Amendment note (日期, #卡)**` 惯例),原句保留不动;头部 `Amended by` 追加一条对应记录。Status 行不记修订,未改。 - 为什么改:原文说每个降级实例的派发拒绝都写「平台会自动重试」。#27992(PR #28014)和 #27237 之后,有些实例是「挂起」的:重试时遇到配置错误,或上游拒绝了已存储的或写在声明里的凭据。这些实例退避定时器不重试,签名不变的 reconcile 也跳过;只有编辑声明、重新设置凭据或重启才会再试。PR #28037(#28016)已让拒绝文案写明解锁方式。ADR 与运行时不一致,本 PR 补记。 - 风险与回滚:纯文档,零代码。新增 7 个符号锚,`check:adr-symbol-anchors` 全部可解析(2843→2850,symbol 623→630)。回滚即 revert。 - 先看:修订注本身(第三条要点之后),对照 `plugin.ts#scheduleDeclarativeRetry` 的 `!d.held` 过滤与 `builtin/connector-nodes.ts` 的两种拒绝文案。 - 两分钟验证:`pnpm check:adr-symbol-anchors`,预期 exit 0、2850 个锚全部解析。 - 需要你做的:本 PR 改受管规则文本,欠一次契约复审(CONTRACT_REVIEW_TIER);登记册判为 Tier H(`docs/adr/**`),落地需 GOVERNED_APPROVER 批准。 ## What changes - **A dated amendment note (2026-10-30, #28038)** under the degraded-instance bullets of "Upstream availability: degrade, don't die". The first bullet ("a `connector_action` dispatch fails with the reason and 'the platform retries automatically'") and the third (the backoff, and "immediately by any reconcile") are kept as written, per Prime Directive #13; the note says they now hold only for an instance on the backoff. It records: - which instances are **held**: a configuration fault first met on a backoff retry (#27992, `plugin.ts#failDegradedConnectorInstance`), and a credential its upstream refused when the value is stored in `sys_connector_credential` or written into the declaration (#27237, `plugin.ts#holdConnectorForRejectedCredential`); - that a held instance is not retried: the backoff timer skips it (`plugin.ts#scheduleDeclarativeRetry`) and so does a reconcile while its signature is unchanged (`plugin.ts#reconcileDeclaredConnectors`); only an edit of the declaration, setting the stored credential again (the row's revision is part of the signature), or a restart re-attempts it; - the exact refusal wording PR #28037 ships for a held instance ("Dispatch is unavailable until its declaration is edited (or the server restarts); the platform does not retry it." / "… until credential '<ref>' is set again …"), anchored at `builtin/connector-nodes.ts#registerConnectorNodes`, `plugin-extension-api.ts#describeHold` and `engine.ts#getConnectorDegradedHold`; - that an instance on the backoff (an unreachable upstream, a refused environment or resolver credential) keeps "the platform retries automatically". - **`Amended by` header**: one entry for #28038 beside the existing PR #26798 and #27237 entries. The Status line records no amendments in this ADR, so it is unchanged. ## Why #28038 (triage 6521083653): since PR #28014 and #27237 the ADR's degraded section contradicted the runtime, and PR #28037 (merged `b59bf60c8`) made the refusal itself say so. Measured on `origin/main` `27e0bd584`: `scheduleDeclarativeRetry` filters `!d.held`; `reconcileDeclaredConnectors` `continue`s on `held?.held && held.signature === signature`; the two hold sites set `held: true` and pass a `ConnectorHold` to `registerDegradedConnector`; the dispatch refusal branches on `engine.getConnectorDegradedHold`. ## Verification All readings at head `f4345a9d`, in a fresh worktree after `pnpm install --frozen-lockfile --ignore-scripts` (and `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` for the doc-formula gate). - `check:adr-symbol-anchors`: exit 0 on base `27e0bd584` (2843 anchors, 623 symbol: 597 declaration / 26 literal) and on head (2850 anchors, 630 symbol: 604 declaration / 26 literal). The 7 new anchors all resolve as declarations. - `check:adr-index`: exit 0 (derived; the ADR's title is unchanged). - `check-governed-merges.mjs --test docs/adr/0097-declarative-connector-instances.md`: GOVERNED, 1 of 1 path on the register (Tier H). - `dispatch-gates --ran`: 21 derived families accounted for, 21 run, 0 NOT-MEASURED. No changeset family is derived (docs only), hence `skip-changeset`. <details><summary>dispatch-gates families (21 derived, 21 run)</summary> ```text node scripts/check-adr-links.mjs :: exit 0 node scripts/check-adr-links.mjs --self-test :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 pnpm check:adr-anchors :: exit 0 pnpm check:adr-index :: exit 0 pnpm check:adr-symbol-anchors :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:pm-governed-merges :: exit 0 pnpm check:pm-prior-rulings :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:watch-hint-literal :: exit 0 ``` </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. (no names added) - [x] 3. No formatter ran. (`pnpm lint` not run: the diff touches no lintable source, only one ADR .md) - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (docs only, publishes nothing: `skip-changeset`) - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (not touched) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4cypdG8XcEafFNG2EFa)_ 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016WV4cypdG8XcEafFNG2EFa --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3fd4d6b commit 6285203

1 file changed

Lines changed: 28 additions & 1 deletion

File tree

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

Lines changed: 28 additions & 1 deletion
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".
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".
99

1010
---
1111

@@ -196,6 +196,33 @@ that one instance instead of failing the run:
196196
`registerConnector`; an instance deleted while degraded is dropped, husk and
197197
pending retry included.
198198

199+
> **Amendment note (2026-10-30, #28038 — after #27992 / PR #28014, #27237, and #28016 / PR
200+
> #28037).** The first and third bullets above are kept as written; they now hold only for an
201+
> instance **on the backoff**. Some degraded instances are **held** instead: a configuration
202+
> fault met by an instance that is already degraded, on its backoff retry or on a
203+
> `metadata:reloaded` reconcile (#27992 — the instance is held with the configuration reason,
204+
> `packages/services/service-automation/src/plugin.ts#failDegradedConnectorInstance`),
205+
> and a credential its upstream refused when the value is stored in `sys_connector_credential`
206+
> or written into the declaration (#27237,
207+
> `packages/services/service-automation/src/plugin.ts#holdConnectorForRejectedCredential`). A
208+
> held instance is not retried: the backoff timer skips it
209+
> (`packages/services/service-automation/src/plugin.ts#scheduleDeclarativeRetry`), and so does a
210+
> reconcile while its signature is unchanged
211+
> (`packages/services/service-automation/src/plugin.ts#reconcileDeclaredConnectors`). An edit
212+
> of its declaration, setting its stored credential again (the row's revision is part of the
213+
> signature), or a restart re-attempts it. Clearing the stored credential also ends the hold,
214+
> since the reference's fingerprint changes: the instance then degrades for the missing
215+
> reference and goes back on the backoff. Its husk carries the hold
216+
> (`packages/services/service-automation/src/engine.ts#getConnectorDegradedHold`), and a
217+
> `connector_action` dispatch to it names what unblocks it instead of promising a retry
218+
> (#28016, `packages/services/service-automation/src/builtin/connector-nodes.ts#registerConnectorNodes`,
219+
> `packages/services/service-automation/src/plugin-extension-api.ts#describeHold`): "Dispatch is
220+
> unavailable until its declaration is edited (or the server restarts); the platform does not
221+
> retry it.", or "… until credential '`<ref>`' is set again (or the server restarts); …". An
222+
> instance on the backoff (an unreachable upstream, or a refused credential read from an
223+
> environment variable or the host's resolver) keeps "Dispatch is unavailable until that is
224+
> resolved; the platform retries automatically."
225+
199226
The `mcp` provider classifies connect / `tools/list` failures as unavailable;
200227
transport-shape validation stays a plain (fatal) throw. Lazy connect-on-first-
201228
dispatch was rejected: MCP actions ARE the server's `tools/list`, so a

0 commit comments

Comments
 (0)