Skip to content

fix(service-datasource): Test connection answers ok:false when the driver's health check answers false (#30407) - #30566

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-me6fjl
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-me6fjl

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:server#3 · os-bill · session_01Utp6CXKK9SECcrtZHzxjTK

Requested by Bill · project thread

Fixes #30407

Clause-②: no — a served answer comes to match the driver's own health answer; no accept set changes.
Metadata-protocol: none

维护者速读

  • 改了什么:数据源「测试连接」现在读取驱动健康检查的返回值;连接成功但健康检查答 false 时,答 ok: false 并给出一句说明,不再答成功。
  • 为什么改:探测只 await 了 ping(即驱动的 checkHealth())却丢掉了它的布尔值,一个只告警不抛错、随后自报不健康的连接会被报成「连接成功」。
  • 风险与回滚:只改 datasource-admin-plugin.ts 的 probe 一处判断;只有字面量 false 才拒,抛错路径与 ping 无返回值的句柄行为不变;回滚即 revert 本 PR。
  • 先看:packages/services/service-datasource/src/datasource-admin-plugin.ts 的 probe(health === false 那一行);其测试文件末尾新增的 describe。
  • 两分钟验证:pnpm --filter @objectstack/service-datasource exec vitest run src/__tests__/datasource-admin-plugin.test.ts -t "probe reads the health answer",预期 4 条全绿。
  • 需要你做的:无,席位落地。

What changes

DatasourceAdminServicePlugin.probe awaited the handle's ping (or the checkHealth fallback) and dropped the answer. Both the shared factory's toHandle and createPrebuiltDriverFactory map ping to the driver's own checkHealth(), which reports an unhealthy connection by answering false rather than throwing, so a connect that resolved and then reported itself unhealthy answered Test connection ok: true.

The probe now keeps that answer and, when it is a literal false, returns ok: false with the sentence "The driver connected, but its health check reported the database as unhealthy, so this connection is not usable. Check that the database exists and accepts these settings, then test again." The teardown in finally still runs. Only false refuses: ping is typed as a promise of unknown on the handle contract, and a host-built handle whose ping answers nothing keeps answering ok: true (the existing fakeFactory probe test pins that). The thrown-error path is unchanged.

Other callers of the handle's ping: none. git grep over packages/** and ui/** (non-test) finds the probe as the only reader of a DatasourceDriverHandle.ping. The neighbours read the boolean already: ExternalDatasourceService.testConnection (saved-datasource Test) checks await driver.checkHealth() and answers ok: false on false; engine-lifecycle-health.ts maps false to healthy: false. packages/runtime/src/turso-driver-factory.ts and prebuilt-driver-factory.ts are producers of ping, not readers.

Serial hold: the card was serial after PR #30395 (same package). Seat 3 lifted the hold at 15:00Z because this diff shares no file with #30395; #30395 then merged (22f0d65) and origin/main is merged into this branch before opening, so the PR is measured against a tree that already contains it.

Tests

New top-level describe('DatasourceAdminServicePlugin: probe reads the health answer') in the existing harness src/__tests__/datasource-admin-plugin.test.ts:

  • a stub DRIVER (connect resolves, checkHealth() answers false) wrapped through createPrebuiltDriverFactory, the producer that mirrors toHandle: ok: false, the error names the health check, no latencyMs, and connect / checkHealth / disconnect each ran once;
  • a handle carrying only checkHealth (no ping) answering false: ok: false;
  • CONTROL: the same stub with checkHealth() answering true: ok: true with a latency;
  • CONTROL: a throwing connect keeps its answer exactly ({ ok: false, error: THROWN_MESSAGE }), health never asked, teardown still ran.

Measured first, on fa9c0aa2 before any source edit: the two false pins red (expected true to be false), both controls green.

Ablation (one, on the committed fix 2cc82972): scripts/ablation-replace.mjs --delete on the if (health === false) return … line, anchor x1 → x0, blob 9c49cde4 → ae6fa722; the file run went 2 failed | 33 passed (35) (exactly the two false pins), then restored to blob == HEAD with git diff HEAD empty. The suite imports the plugin by relative src path, so no dist leg applies.

Acceptance notes

Verification

All on head 28086960 (the fix 2cc82972 plus a merge of origin/main 8e5fabe5, which contains PR #30395).

  • pnpm --workspace-concurrency=2 --filter '@objectstack/service-datasource...' build && pnpm --filter @objectstack/service-datasource typecheck && pnpm --filter @objectstack/service-datasource test under os-verify-lock.sh: VERDICT command-exit 0; Test Files 72 passed (72), Tests 1187 passed (1187).
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 59 derived families, each run and its exit recorded; --ran reconciliation: 59 derived famil(ies) accounted for — 58 run, 1 NOT-MEASURED.
    • 58 exit 0. check-plugin-teardown-shape.mjs --self-test first exited 3 on the shallow clone (fixture commit absent); after git fetch origin 621a4876… it exited 0 (48 cases pass).
    • NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET, it reads every package's dist/ and only the service-datasource closure was built here. CI runs it in Lint & Repo Gates.
  • pnpm --filter @objectstack/spec check:liveness: exit 0.
  • Not run locally, declared to CI: test:repo (no file of that project changed), repo-wide pnpm lint, the full farm.

Generated by Claude Code

…swer

The probe awaited the handle's ping (the driver's checkHealth()) and
dropped its boolean, so a connect that resolved and then reported itself
unhealthy answered Test connection ok: true. A literal false now answers
ok: false with a refusal sentence; the thrown-error path is unchanged.

Claude-Session: https://claude.ai/code/session_01Utp6CXKK9SECcrtZHzxjTK
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 2808696072495b9eaf6309bdfc592fefdeee31c6
Local-runs: none
Scope: packages/services/service-datasource/src/datasource-admin-plugin.ts (probe, the new UNHEALTHY_PROBE_REFUSAL), its test file, .changeset/30407-test-connection-reads-health-answer.md.
Face: no packages/spec/**, skills/**, docs/adr/**, .claude/** or governed path; check-governed-merges NOT governed, 97 lines. Declaration Clause-②: no ⇒ TIER_DEFAULT.

① Derived judgments

  1. probe now keeps the ping / checkHealth answer and returns { ok: false, error: UNHEALTHY_PROBE_REFUSAL } only on a literal false. A handle whose ping answers nothing or any other value keeps answering ok: true, and the ping contract is Promise<unknown>, so no host-built handle changes meaning. The thrown-error path is unchanged, and the early return still runs the finally teardown (disconnect). Correct against the card's ask.
  2. The refusal sentence names no tracker id (the [#30407] anchors are in // and docblock comments only), so the doc-authoring guard holds.
  3. Pins: a stub driver wrapped as production wraps it (toHandle: ping and checkHealth both answer the driver's checkHealth()) answers ok: false with the sentence when connect resolves and health answers false. CONTROL: healthy answers ok: true. CONTROL: a throwing connect keeps its message. The dev's ablation (delete the health === false line) turned exactly the two refusal pins red.

② Semver level

patch on the service-datasource package, the only package moved: a served answer comes to match the driver's own health answer, and no accept set or export changes. Clause-②: no and Metadata-protocol: none are right.

③ Boundary flags

None. ping has no other reader that drops the boolean: external-datasource-service reads checkHealth itself, as the dev measured.

Implemented-by: claude/project-thread-me6fjl
Reviewed-by: session_01Utp6CbPxYq1wQmUNWvMZkC

VERDICT: PASS

@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see

Coarse fallback — 3 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a4c3e738b5b18d265f89a84a2dc003cedc31ed1b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6a3729bbd497c2ac78924d783d70c745c626ccd0 — the merge of head 2808696072495b9eaf6309bdfc592fefdeee31c6 into base a4c3e738b5b18d265f89a84a2dc003cedc31ed1b, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6a3729bbd497c2ac78924d783d70c745c626ccd0 && git checkout 6a3729bbd497c2ac78924d783d70c745c626ccd0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a4c3e738b5b18d265f89a84a2dc003cedc31ed1b 2808696072495b9eaf6309bdfc592fefdeee31c6 && git checkout -B drift-repro a4c3e738b5b18d265f89a84a2dc003cedc31ed1b && git merge --no-ff 2808696072495b9eaf6309bdfc592fefdeee31c6

node scripts/docs-audit/affected-docs.mjs --json a4c3e738b5b18d265f89a84a2dc003cedc31ed1b

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-bill · session_01Utp6CbPxYq1wQmUNWvMZkC

维护者速读(席位终稿)

  • 改了什么:数据源「测试连接」现在读取驱动健康检查的答复:连接成功但健康检查明确答 false 时,返回 ok: false 和一句说明,不再报成功。连接失败、健康答其他值的行为不变,探测结束照常断开。
  • 为什么改:以前只要连接不抛错就答 ok: true,驱动自己说不健康也被报成功,管理员看到假的「连接正常」(service-datasource: Test connection answers ok when the driver's health check returns false, because the probe drops checkHealth()'s boolean #30407)。
  • 风险与回滚:只有健康检查明确答 false 时结论改变;ping 约定是 Promise<unknown>,答空值或别的值仍算成功,主机自建的 handle 含义不变。没有接口或导出改动。回滚即 revert。
  • 先看:packages/services/service-datasource/src/datasource-admin-plugin.ts 的 probe 与 UNHEALTHY_PROBE_REFUSAL。
  • 两分钟验证:pnpm --filter @objectstack/service-datasource exec vitest run src/__tests__/datasource-admin-plugin.test.ts -t "probe reads the health answer",4 条全过。
  • 需要你做的:无。技能定档:非受管面、97 行、非 BREAKING,不需人审;席位复核 6662864015(Head-sha 2808696072495b9eaf6309bdfc592fefdeee31c6,PASS),席位落地。

席位意见:同意落地。只在驱动明说不健康时改答,范围窄,有对照组守住。

席位核对

ACCEPT · head 2808696072495b9eaf6309bdfc592fefdeee31c6 · card #30407 · 2026-11-05T16:40Z.

  • Shape: draft PR to main, relay author, Fixes #30407 only. 3 files, +95 −2. check-governed-merges.mjs --pr 30566 → exit 0, NOT governed, 97 ≤ 3000. No merge=os-regen path.
  • Premise measured: the dev's ablation (remove the health === false line) turned exactly the two refusal pins red; review 6662864015 read the pins against the production toHandle wrapping.
  • Gates (check-runs on this head): 46 runs, 35 success, 11 path-gated skipped, 0 failed, 0 in progress (read 2026-11-05T16:40Z).
  • Accepted deviation: none. Not BREAKING (Clause-②: no), so no dogfood battery is owed.
  • Out of scope: none filed. Skills-docs: none — no page under content/docs or skills states what Test connection answers on an unhealthy driver (grep at origin/main).
  • Landing: not BREAKING, not governed, ≤ 3000 ⇒ the skill requires no human merge; seat lands it now.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 16:42
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 94cef75 Nov 5, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-me6fjl branch November 5, 2026 17:04
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/s tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

service-datasource: Test connection answers ok when the driver's health check returns false, because the probe drops checkHealth()'s boolean

1 participant