Repository navigation
fix(service-datasource)!: Test connection and Save refuse a SQLite filename that is neither :memory: nor a usable file path - #30395
Conversation
…is neither :memory: nor a usable path Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Utp6Cb83ajcseWprEF9C9g
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Utp6Cb83ajcseWprEF9C9g
📓 Docs Drift Check7 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): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6acaa8e7ce86f83ddd7bf2b3090272617a78b8aa && git checkout 6acaa8e7ce86f83ddd7bf2b3090272617a78b8aa
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8fad7b19389c913efbdf00a24965bec024509d60 cca010a640e31e8591d1aea9caeca53ba1a45fc2 && git checkout -B drift-repro 8fad7b19389c913efbdf00a24965bec024509d60 && git merge --no-ff cca010a640e31e8591d1aea9caeca53ba1a45fc2
node scripts/docs-audit/affected-docs.mjs --json 8fad7b19389c913efbdf00a24965bec024509d60 |
|
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF Contract-review-claim: session_01Utp6CZMkHvuDeVYgTD73qF · head cca010a · 2026-11-05T09:35Z |
|
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF Contract reviewServed-tier: Inputs: card #30331 (body, triage 6652062778, claim 6652710575, dev report 6654592689), PR #30395 (body, file list, net diff against ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentThe narrowing read against the published spec on
Findings:
Implemented-by: VERDICT: PASS Gate verdicts, the check-runs on |
|
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF 席位意见:ACCEPT(#30331 服务端一半)。数据源「测试连接」「保存」现在拒收既不是 席位核对Head-sha:
|
|
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF 维护者速读
席位意见:ACCEPT(#30331 服务端一半)。数据源「测试连接」「保存」现在拒收既不是 席位核对Head-sha:
|
Requested by Bill · project thread
domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF
维护者速读
:memory:也不是可用文件路径(例如:memory:/srv/data/parts.db、空字符串、建不出来的路径),“测试连接”现在判失败,保存也拒收,提示里写出这个值和原因。:memory:是真实值,用户在后面接着打路径就成了:memory:/…,测试连接却显示成功;存下去后原生 SQLite 每次查询都报错,sqlite-wasm 则开了个内存库,写进去的数据重启就没了。管理员会以为连上了公司数据库。:memory:、已存在的数据库文件、还不存在但能创建的路径都照旧。已存的旧数据源照常启动,只有改它的配置时才会被判。回滚即撤 PR。packages/services/service-datasource/src/sqlite-database-filename.ts(判定本身);datasource-admin-service.ts的assertValidConfig(测试、新建、更新三处共用的那一处)。pnpm --filter @objectstack/service-datasource exec vitest run --maxWorkers=2 src/__tests__/sqlite-filename-neither-memory-nor-path.test.ts,预期 14 个全绿。:memory:做成占位符而非真实值,是 ui 车道的另一半,卡留开。Refs #30331 (server half)
Clause-②: yes (narrowing) —
POST /api/v1/datasources/test,POST /api/v1/datasourcesand aPATCH /api/v1/datasources/:namethat touchesconfigordriverrefuse asqlite/sqlite-wasmconfig.filenamethat is empty, starts with:without being exactly:memory:, or names a path that can be neither opened nor created. The test answeredok: truefor the first two and the save stored all three; both now answer with the value and the reason (result.ok: falsefrom the test,400 DATASOURCE_ADMIN_ERRORfrom the save), the envelope every invalidconfigalready gets.Metadata-protocol: none
Skills-docs: none —
skills/**andcontent/docs/**teachfilenameas "a database file path, or:memory:" (content/docs/references/data/driver-sqlite.mdx,content/docs/data-modeling/drivers.mdx), which is what the doors now enforce; no line turns false. follow-up owed —packages/spec/src/data/driver/sqlite.zod.tstypesfilenameas any placeholder-free string, so the build door andPUT /api/v1/meta/datasource/:namestill accept these values; the refinement belongs in the spec.What changes
Measured first, on
main8589756, through the realPOST /api/v1/datasources/testwithconfig.filename: ':memory:/tmp/x30331/parts.db':sqliteanswered{ ok: true, latencyMs: 13 }andsqlite-wasm{ ok: true, serverVersion: '3.49.1' }. Why::as fileless: driver-sql'sresolveSqliteAbsentFileTargetandsqliteFilename, the wasm connection'sisEphemeral. So the probe's missing-file refusal (datasources: the draft probe POST /datasources/test creates the SQLite database file on disk although nothing is saved #27512) skipped it, andSqlDriver.connect()made no directory for it.sqlite(better-sqlite3) does not treat it as in-memory (only exactly:memory:is). It opens it as a relative path under the server's working directory, whose:memory:/…directory does not exist.connect()only warns (Failed to set PRAGMA auto_vacuum), andcheckHealth()answersfalsewithout throwing, which the probe does not read as a failure.sqlite-wasmopens an ephemeral in-memory database.What a saved datasource with that value reads (not measured by the card; measured here through the default factory, connect, write, disconnect, connect again):
sqlite: every statement fails withCannot open database because the directory does not exist; healthfalse. Nothing is written to disk.sqlite-wasm: healthtrue; a table created and a row inserted read back in the same connection; after reconnecting,no such table. Writes succeed and are lost at the next connect or restart, with no error.The fix, in the one place the test and both save doors pass through.
DatasourceAdminService.assertValidConfigalready judgedconfigagainst the driver contract fortestConnection,createDatasourceandupdateDatasource(when the patch touchesconfigordriver). It now also askssqliteDatabaseFilenameIssue(new,sqlite-database-filename.ts) once the contract has no issues, and becomes async. Forsqlite/sqlite-wasm(viaresolveDriverId) the filename is usable when it is exactly:memory:or absent (the schema's default), or a path that driver-sql's write-free judgement (resolveSqliteAbsentFileTarget(…, 'empty-in-memory')) does not refuse. Refused, each with aconfig.filename:line underInvalid configuration for driver '…':config.filename: '' is empty and names no database file. …:-prefixed but not:memory::config.filename: ':memory:/…' is neither ':memory:' nor a file path. A name that starts with ':' is not read as a file, so this datasource would store nothing: …config.filename:+ driver-sql'sSqliteDatabaseFileErrortext, which names the file and the operating system's answer.Same envelope and code as every invalid
config(400 DATASOURCE_ADMIN_ERRORfrom save;result.ok: falsefrom the test). No new error code.Unchanged: exactly
:memory:tests ok and saves; an existing database file tests ok and saves; a creatable path that does not exist yet is refused by the test (which never creates it, #27512) and accepted by Save, which creates it on connect.tursonames its target as afile:url and is not judged here. The probe indatasource-admin-plugin.ts, the driver factory, and the boot restore are not touched: a stored row is not re-judged at boot.Tests
packages/services/service-datasource/src/__tests__/sqlite-filename-neither-memory-nor-path.test.ts, 14 cases (7 per driver,sqliteandsqlite-wasm), through the realHonoHttpServer, the real admin routes, the realDatasourceAdminServicePlugin(its probe and its service) and the default driver factory, with a map-backed metadata service so a save is observable::memory:/…is refused by Test connection, naming the value and why; nothing is written under the test root.:memory:/…is refused by create (400 DATASOURCE_ADMIN_ERROR, nothing stored) and by an update of a saved:memory:datasource (stored value unchanged).:memory:tests ok and saves (201); an existing database file tests ok and saves; a creatable missing path is refused by the test with "does not exist", nothing created, and is saved.Ablation (
sqliteDatabaseFilenameIssueshort-circuited toundefined, source imported by relative path so nodist/leg): 8 failed / 6 passed, the 8 refusal cases red and the 6 controls green. Restored withgit checkout, then 14/14.Acceptance notes
:memory:as the Filename field's value instead of a placeholder (ui/packages/app-shell/src/views/metadata-admin/datasource/DatasourceResourcePage.tsx). With this PR the mistake it invites is refused with a message, instead of passing. The card stays open for that half.falsehealth check.DatasourceAdminServicePlugin.probeawaitsping()(the handle'scheckHealth) and reportsok: truewhatever it answers. That is how the native:memory:/…draft passed; this PR closes the input, not that reading. No other public input reaching it was measured.configmust fix the filename; alabel/active-only edit is not judged, so it can still be taken out of service.os migrate planalready make.Verification
Head
cca010a64, the merge oforigin/maininto the branch; the merge touches none of this PR's 4 files.@objectstack/service-datasourcesuite: 72 files, 1183 passed.pnpm --filter @objectstack/service-datasource run typecheckexit 0.node scripts/pm/dispatch-gates.mjs --commandsderived 61 families; each ran at head with its exit recorded before any pipe: 61 of 61exit 0(check-plugin-teardown-shape --self-testafter fetching its fixture commit,check:dual-build-cjs-loadsafter the full build).--ran: 61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN, exit 0.pnpm lintexit 0.turbo run build --filter='!@objectstack/docs'119/119 under the verify lock (VERDICT command-exit 0).pnpm --filter @objectstack/dogfood testunder the verify lock: VERDICT command-exit 0, Test Files 419 passed / 3 skipped (422), Tests 3492 passed / 36 skipped (3528).Expect 14 passed.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Utp6Cb83ajcseWprEF9C9g