Skip to content

fix(datasource): an explicitly-bound datasource that cannot connect refuses the boot (#3758) - #3816

Merged
os-zhuang merged 1 commit into
mainfrom
claude/explicit-datasource-connection-fallback-5b83d0
Jul 28, 2026
Merged

fix(datasource): an explicitly-bound datasource that cannot connect refuses the boot (#3758)#3816
os-zhuang merged 1 commit into
mainfrom
claude/explicit-datasource-connection-fallback-5b83d0

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3758.

问题

DatasourceConnectionService.handleFailure() 的 fail-fast 只覆盖「external + validation.onMismatch: 'fail'」,其余一律降级成一条 warn——包括同文件 D2 gate 自己注释里点名「没有任何回落路径」的那一类:被 object.datasource 显式绑定的 datasource。这些对象不会落到 default driver,engine.getDriver 对它们直接抛 Datasource 'x' is not registered

于是一个声明 datasource: 'analytics' 并绑了 20 个对象的 app,在 ANALYTICS_URL 配错时:进程正常启动、退出码为 0,然后那 20 个对象的每一次读写都失败,报的错跟「analytics 库连不上」相距很远;而其余部分工作正常,反而比整体宕机更难定位。这跟 #3741 / #3751ObjectQLEngine.init() 那一层修掉的是同一个决定,只是这一层的边界还画在旧位置。

改动

判据从 onMismatch 换成「有没有回落路径」。 declared-auto(启动)触发下,连接失败在两种情况下终止启动:

Gate 连不上时
(a) external + validation.onMismatch: 'fail' fail-fast
(b) ≥1 个对象通过 object.datasource 显式绑定 fail-fast — 无回落路径
(c) autoConnect: true 且无对象绑定 降级 warning — 语义是「能连就连」

runtime-admin 创建/更新与启动期 rehydration 行为不变,始终降级:一次 UI 操作不能弄挂正在跑的服务器。

覆盖全部失败原因,而不只是连不上。 无法解析的 external.credentialsRef(D3)和不受支持的 driver,对绑定对象来说一样是死的,走同一判决。DatasourceConnectPolicy 拒绝不算失败,仍然停在 metadata-only —— 多租户 host 按套餐挡 egress 是在「决定」,不是在「失败」,把它变成 fail-fast 会让共享 runtime 上所有租户一起起不来。

错误信息直接列出绑定对象(最多 10 个,其余记 +N more),连同底层原因一起给出。connectDeclared() 会先尝试完所有通过 gate 的 datasource 再抛聚合错误,一次启动报全部问题,而不是修一个重启一次(与 ObjectQLEngine.init()DriverConnectError 同形)。

逃生阀与引擎层共用 OS_ALLOW_DRIVER_CONNECT_FAILURE=1 —— 操作者意图相同,两个旗标只会保证有一个被漏设。它现在也覆盖 (a),而 (a) 此前完全没有 opt-out。设置后继续启动,并把 DEGRADED BOOT banner 同时写到 stderr(os serve 的 boot-quiet 会吞掉 stdout)。emitDegradedBootBanner 移到 @objectstack/types,两个调用点共用一份实现;@objectstack/objectql 原样再导出,公开 API 不变。

文档

  • ADR-0062 D5 增补修订说明:新判据、共用旗标、policy 拒绝的边界。
  • content/docs/data-modeling/external-datasources.mdx 新增「When auto-connect fails」小节;drivers.mdx / environment-variables.mdx / production-readiness.mdx 同步旗标覆盖范围。
  • examples/app-showcaseshowcase_external 注释原本声称 onMismatch: 'warn' 能挡住 boot 被弄挂——那句话现在只对 schema drift 成立,已改写为准确表述。

验证

  • packages/services/service-datasource — 106 passed(新增 13 个用例:managed + 绑定对象 fail-fast、消息内容与截断、credential/unsupported-driver 同判决、autoConnect 与 runtime-admin 保持宽容、旗标 opt-in、stderr banner、falsy 值仍 fail-fast)
  • packages/runtime — 663 passed(新增 2 个端到端用例:kernel.bootstrap() 因绑定 datasource 连不上而拒绝启动并点名依赖对象;设了旗标则降级启动)
  • packages/objectql — 1128 passed;packages/types — 26 passed
  • pnpm build 全量通过;service-datasource typecheck 通过

Migration

配置正确的部署无需改动。此前静默带着一个已死的、被显式绑定的 datasource 启动的部署,现在会启动失败,并给出 datasource、原因和依赖它的对象;修配置即可。若确实要在明知每个相关请求都会失败的前提下继续启动,设 OS_ALLOW_DRIVER_CONNECT_FAILURE=1


Generated by Claude Code

…efuses the boot (#3758)

`DatasourceConnectionService.handleFailure()` fail-fasted only for an `external`
datasource with `validation.onMismatch: 'fail'`; everything else degraded to one
`warn` line — including the case the D2 auto-connect gate itself identifies as
having no fallback path: a datasource that objects bind to explicitly via
`object.datasource`. Those objects never fall through to the `default` driver;
`engine.getDriver` throws `Datasource 'x' is not registered` for them.

An app declaring `datasource: 'analytics'` with 20 objects bound, booted against
a wrong `ANALYTICS_URL`, therefore started clean and exited zero, then failed
every read and write of those objects with an error that reads nothing like "the
analytics database is unreachable" — and the rest of the app kept working, which
made it harder to locate than a total outage. Same decision #3741/#3751 fixed one
layer up in `ObjectQLEngine.init()`; this boundary was still drawn in the old
place.

- Fail-fast is keyed on "no fallback path", not on `onMismatch` alone: at the
  `declared-auto` trigger a connect failure aborts the boot when the datasource
  is external + `onMismatch:'fail'` OR when >=1 object binds to it explicitly.
  `autoConnect: true` with nothing bound stays lenient; runtime-admin and
  rehydration triggers are unchanged and still always degrade.
- Every failure mode counts, not just an unreachable socket: an unresolvable
  `external.credentialsRef` and an unsupported `driver` leave the bound objects
  exactly as dead. A `DatasourceConnectPolicy` denial is NOT a failure and stays
  metadata-only — a host refusing egress is deciding, not failing.
- The error names the bound objects (up to 10, then `+N more`) alongside the
  underlying cause; `connectDeclared()` attempts every gated datasource before
  throwing an aggregate, so one boot reports all of them.
- The escape hatch is shared with the engine guard,
  `OS_ALLOW_DRIVER_CONNECT_FAILURE=1` — one operator intent, one flag. It now
  covers `onMismatch:'fail'` too, which previously had no opt-out. When set, boot
  continues and a DEGRADED BOOT banner goes to stderr as well as the logger.
  `emitDegradedBootBanner` moved to `@objectstack/types` so both call sites share
  one implementation; `@objectstack/objectql` re-exports it unchanged.

ADR-0062 D5 amended with the new criterion, the shared flag, and the policy-denial
boundary; deployment + federation docs updated to match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQVM3A9Yd6N2eZS8ZcdnMk
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Jul 28, 2026 7:14am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests tooling size/l labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/objectql, @objectstack/runtime, packages/services, @objectstack/types.

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

  • content/docs/api/client-sdk.mdx (via packages/runtime)
  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/automation/webhooks.mdx (via packages/services)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/runtime)
  • content/docs/kernel/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/kernel/runtime-services/index.mdx (via packages/services)
  • content/docs/kernel/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/permissions/authorization.mdx (via packages/runtime)
  • content/docs/plugins/index.mdx (via @objectstack/objectql)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @objectstack/runtime, packages/services, @objectstack/types)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/services)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql, @objectstack/runtime)
  • content/docs/releases/v9.mdx (via @objectstack/objectql)

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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 28, 2026 08:22
@os-zhuang
os-zhuang merged commit 87aca93 into main Jul 28, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/explicit-datasource-connection-fallback-5b83d0 branch July 28, 2026 08:22
os-zhuang added a commit that referenced this pull request Jul 28, 2026
…when queried (#3827, #3828) (#3836)

#3816 made an explicitly-bound datasource that cannot connect refuse the boot.
Two gaps survived it, in the cases that still boot — a policy denial, an
`autoConnect` datasource, or a failure waved through with
OS_ALLOW_DRIVER_CONNECT_FAILURE: the datasource was invisible
(`DatasourceSummary.status` was a hardcoded 'unvalidated'; `checkDriversHealth()`
cannot see a driver that was never registered), and the query-time error said
only "is not registered" for four different situations.

Both from one root: `connect()` produced a ConnectResult per attempt and every
caller threw it away.

- Retain the last verdict per datasource (available / blocked / failed /
  unattempted); `getConnectionState`, `listConnectionStates`.
- `DatasourceSummary.status`: ok | error | blocked | unvalidated, + statusReason.
- New DatasourceUnavailableError (ERR_DATASOURCE_UNAVAILABLE, HTTP 503) naming
  which situation it is. An undeclared name keeps the original message.
- Privileged/public split: the error never carries the underlying cause;
  DatasourceConnectDecision gains an opt-in `publicReason` for tenant-facing text.
- Readiness stays ungated on this.

Also lands the #3826 drift guard (degraded-boot-parity.test.ts) and corrects
ADR-0062's status: D1 is partially implemented, not complete.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

显式绑定的 datasource 连不上只降级成一条 warning:绑上去的对象在启动后全废,直到查询时才炸

2 participants