Skip to content

feat(connectors): degrade + retry declarative instances on unreachable upstream (#3017)#3049

Merged
os-zhuang merged 1 commit into
mainfrom
claude/adr-0097-mcp-nonfatal-boot
Jul 16, 2026
Merged

feat(connectors): degrade + retry declarative instances on unreachable upstream (#3017)#3049
os-zhuang merged 1 commit into
mainfrom
claude/adr-0097-mcp-nonfatal-boot

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

实现 #3017(ADR-0097 后续):区分配置错误(维持 boot 致命)与运行时上游不可达(降级 + 自动重试),使一个 provider: 'mcp' 声明式实例的 MCP 服务器瞬时不可达不再拖垮整个应用启动。

问题

ADR-0097 的 fail-loud 契约把所有 materialization 失败都定为 boot 致命。对авторing 错误(未知 provider、非法 providerConfigcredentialRef 解析失败、命名冲突)这是正确的;但 mcp provider 在 materialize 时必须连接远端 MCP 服务器(tools/list),一次网络抖动就等于全站启动失败——一个集成的降级被放大成整体不可用。这也是 showcase 演示至今只敢用 provider: 'rest' 的原因。

方案:结构化的故障分类

spec(新文件 integration/connector-provider-errors.ts,+3 导出,api-surface 已再生):

  • ConnectorUpstreamUnavailableError / CONNECTOR_UPSTREAM_UNAVAILABLE / isConnectorUpstreamUnavailable。守卫按 code结构判断而非 instanceof,跨包重复安装也能正确分类。生产者(connector 插件)与消费者(service-automation)都只依赖 spec,保持 ADR-0097 的解耦设计。

service-automation(reconcile 的降级路径):

  • 工厂抛出带标记的错误时——boot 与 reload 两种模式一致——该实例降级而非失败:
    • 名下没有存活连接器时,注册一个无 action 的"壳"(descriptor 上 state: 'degraded' + degradedReason,def 上 status: 'error'),GET /api/v1/automation/connectors 诚实展示而不是静默缺失;connector_action 派发时报出指向性错误(含原因 + "平台自动重试"),不再是笼统的"插件没注册?"提示。
    • 变更配置的 re-materialize 失败时,旧连接器继续服务(与其他 reload 失败同一保证),不注册壳。
  • 指数退避重试:5s 起、每次翻倍、封顶 5 分钟;配置编辑重置退避;任何 metadata:reloaded reconcile 立即重试;恢复时经 registerConnector 原子替换壳;降级期间被删除的实例连壳带重试一起清理。定时器 unref(),destroy() 取消。
  • reconcile 运行(boot / reload / 重试定时器)现在串行化(promise 链互斥),boot 的致命错误仍向调用方传播。
  • 引擎侧:RegisteredConnector/ConnectorDescriptor 新增 state: 'ready' | 'degraded' + degradedReason?(加法字段;GET /connectors 路由原样透传,objectui 选择器容忍未知字段);新增 registerDegradedConnector / getConnectorDegradedReason;跨源冲突断言抽取共用(§4 规则不变)。

connector-mcp:

  • 连接 / tools/list 失败 → 分类为 upstream-unavailable(保留 cause);transport 形状校验错误保持普通抛出(致命)。发现失败时连接不泄漏(沿用既有 close)。

为什么不是"懒连接"

Issue 里的另一选项(首个 dispatch 时才连接)被否决:MCP 连接器的 action 列表就是服务器的 tools/list——不连接就 materialize 只能注册零 action 的 def,恰好是 ADR-0097 要消灭的 plausible-but-dead 形态。已在 ADR 中记录论证。

刻意的范围边界(已写入 ADR-0097)

  • showcase 的 live provider: 'mcp' 演示仍延后:天然的仓内目标(@objectstack/mcp 平台自身端点)在 automation start() 时尚未监听,演示会确定性地先降级、数秒后自愈,使 Dogfood CI 门时序敏感。应与仓内 MCP fixture 服务器(或自连接的启动次序方案)一起落地。
  • openapi provider 的远程 URL spec 拉取仍是普通抛出(boot 致命);机制与 provider 无关,需要时一行采用。

测试(16 新增,均通过;四包全套 28 任务绿)

  • connector-materialization.test.ts +10:降级可见性(descriptor 状态/原因/空 actions)、降级实例的 connector_action 指向性错误、假定时器下退避重试恢复、指数退避节奏(5s→10s)、reload 立即重试并恢复、降级中被删除→壳与重试全清、变更配置上游不可达→旧连接器继续服务→恢复后原子替换(旧连接仅在新 bundle 成功后关闭)、回滚到存活配置→取消挂起重试、shutdown 取消重试、普通抛出仍 boot 致命。
  • mcp-provider.test.ts +3:连接失败/tools-list 失败 → unavailable(cause 保留、客户端关闭);transport 配置错误 → 非 unavailable。
  • spec +3:错误契约与结构守卫。
  • 本地已跑:spec tsc --noEmit ✓、check:api-surface ✓(+3 导出)、全仓 ESLint ✓、turbo test(spec / service-automation / connector-mcp / runtime,28 任务)✓。

Refs #3017(机制部分;showcase 演示按上述边界延后——若维护者认可,建议合并后关闭 #3017 并为演示单开小任务)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01Pbmw3pMqNJfPbkvwh9Fkjs


Generated by Claude Code

…e upstream (#3017)

ADR-0097 made every declarative-connector materialization failure fatal at
boot — right for configuration faults, wrong for operational ones: a
provider: 'mcp' instance must contact its MCP server (tools/list) to
materialize, so a transient network blip aborted the whole app boot.

- spec: ConnectorUpstreamUnavailableError (code CONNECTOR_UPSTREAM_UNAVAILABLE,
  structural guard isConnectorUpstreamUnavailable) lets a provider factory mark
  a failure as 'upstream temporarily unreachable — degrade and retry' instead
  of fatal. New integration/connector-provider-errors.ts; api-surface +3.
- service-automation: the reconcile degrades such instances in BOTH modes.
  With no live connector under the name it registers an action-less husk —
  state: 'degraded' + degradedReason on the GET /connectors descriptor,
  status: 'error' on the def — so the instance stays visible instead of
  silently missing; connector_action dispatch fails with the reason and a
  'retries automatically' pointer. On a changed-config re-materialization the
  old connector keeps serving. Degraded instances retry on an exponential
  backoff (5s doubling to 5min, reset by config edits) and on every
  metadata:reloaded reconcile; recovery swaps the husk atomically. Reconcile
  runs (boot / reload / retry timer) are serialized; destroy() cancels the
  retry loop.
- connector-mcp: connect / tools/list failures are classified unavailable;
  transport-shape validation stays a plain (fatal) throw.

Configuration faults (unknown provider, invalid providerConfig, unresolvable
credentialRef, name conflicts) keep the ADR-0097 fail-loud contract, verified
by the existing tests. ADR-0097 gains an 'Upstream availability' section; the
deferred live-mcp showcase demo and the openapi remote-URL classification are
recorded as scope boundaries.

Tests: 10 new reconcile cases (degrade visibility, pointed dispatch error,
fake-timer recovery, exponential backoff, reload retry, removal while
degraded, changed-config keeps old serving, revert cancels retry, shutdown
cancels retry, plain-throw still fatal); 3 new mcp-provider classification
cases; 3 new spec error-contract cases.

Refs #3017 (mechanism; showcase demo deferred — see ADR scope boundaries)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pbmw3pMqNJfPbkvwh9Fkjs
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): packages/connectors, packages/services, @objectstack/spec.

100 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via packages/services, @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/services, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via packages/services, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/services, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 16, 2026 11:35am

Request Review

@os-zhuang
os-zhuang marked this pull request as ready for review July 16, 2026 13:15
@os-zhuang
os-zhuang merged commit 4f8c2d1 into main Jul 16, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/adr-0097-mcp-nonfatal-boot branch July 16, 2026 13:15
os-zhuang pushed a commit that referenced this pull request Jul 16, 2026
#3049's squash (4f8c2d1) is content-identical to this branch's base commit,
so this merge only rebases the PR surface — the three-dot diff vs main now
shows the #3055 gate alone.

# Conflicts:
#	docs/adr/0097-declarative-connector-instances.md
#	packages/connectors/connector-mcp/src/mcp-provider.test.ts
#	packages/connectors/connector-mcp/src/mcp-provider.ts
os-zhuang added a commit that referenced this pull request Jul 18, 2026
…c URL (#3049 follow-up) (#3179)

The openapi provider's remote spec-URL fetch now classifies faults like connector-mcp's connect path: a network error or transient HTTP status (408/429/5xx) throws ConnectorUpstreamUnavailableError so the materializer degrades + retries the instance, while a wrong URL (non-retryable 4xx) or unparseable document stays a fatal config fault. Inline/file-path specs unaffected; no service-automation change (the reconcile already routes the marker generically). +14 provider-layer tests; ADR-0097 scope-boundary list trued up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants