Repository navigation
fix(service-datasource): testing a datasource draft no longer creates its SQLite database file - #27603
Conversation
…e instead of creating it POST /api/v1/datasources/test opened a file-backed SQLite draft the way a writing boot does, so the file and its missing directories were created although nothing was saved. The probe now sets fileMustExist on the connection spec, and the default factory refuses a missing file-backed target (sqlite, sqlite-wasm, a file: turso url) before any driver is built. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgTUuJysc6ThTQ8Xb1r
… OS's own ENOENT answer The refusal stamped a literal 'ENOENT', which the dispatcher error-vocabulary gate reads as an unregistered ADR-0112 code. It now takes code and cause from the operating system's stat answer, as driver-sql's SqliteDatabaseFileError does. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgTUuJysc6ThTQ8Xb1r
📓 Docs Drift Check2 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 — 2 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 b034227a3daa25f924c2665ea0ae1de122a13f1f && git checkout b034227a3daa25f924c2665ea0ae1de122a13f1f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9c5131479d56426dd2c61ca78f8da6271de13171 d3b198ecf6f538fc43df9b4dfdf11c870c284913 && git checkout -B drift-repro 9c5131479d56426dd2c61ca78f8da6271de13171 && git merge --no-ff d3b198ecf6f538fc43df9b4dfdf11c870c284913
node scripts/docs-audit/affected-docs.mjs --json 9c5131479d56426dd2c61ca78f8da6271de13171 |
…aft probe; the factory contract stays as on main The first shape added fileMustExist to DatasourceConnectionSpec, an exported type, which widened the published surface. The probe now judges the draft's file itself, before the factory builds anything, and answers the refusal as 200 ok:false; the contract and the default factory are back to main's bytes. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6bhgTUuJysc6ThTQ8Xb1r
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F 维护者速读(席位终稿)
席位意见: ACCEPT。返工后不碰公开面,对宿主注册的第三方工厂同样成立。 席位核对ACCEPT · head
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Requested by Sales · project thread
Fixes #27512
Clause-②: no
Metadata-protocol: none
Skills-docs: none — Metadata-protocol is none and there is no narrowing arm; nothing an author writes changes.
维护者速读
POST /api/v1/datasources/test)遇到不存在的 SQLite 文件时,改为回报「文件不存在」,不再在磁盘上创建这个文件和它缺失的目录。file:三种驱动实测都会建)。datasource-admin-plugin.ts。datasource-admin-plugin.ts的probe(调工厂前的absentDatabaseFileRefusal一行)及文件尾部的probedDatabaseFile/absentDatabaseFileRefusal。pnpm --filter @objectstack/service-datasource exec vitest run src/__tests__/draft-probe-creates-no-database-file.test.ts,预期 15/15 通过。What changes
DatasourceAdminServicePlugin.probe(the draft probe behindPOST /api/v1/datasources/test) judges a file-backed draft before it asks the driver factory for anything:sqlite/sqlite3andsqlite-wasm(config.filename, absent ⇒:memory:), andturso/libsqlwith afile:url (scheme in any letter case, path as driver-turso's local engine opens it). A draft whose file does not exist is answered on the probe's existing verdict door, HTTP200withresult.ok: falseand an error naming the file (SQLite database file 'PATH' does not exist. Testing a connection never creates it: …); a path that can be neither opened nor created gets driver-sql's ownSqliteDatabaseFileErrortext. Nothing is written to find out.resolveSqliteAbsentFileTarget(an absent file readsopenedEmptyInMemory; an unopenable one throws), so no secondexistsSyncis open-coded.datasource-admin-plugin.ts.contracts/datasource-driver-factory.ts,default-datasource-driver-factory.tsand every export are byte-identical tomain. Because the check is the probe's own, it holds whichever driver factory the host registers.DatasourceConnectionServicebuild through the factory exactly as before and still create the file;:memory:and server databases are never judged; the Setup page already rendersresult.error.Tests
New
src/__tests__/draft-probe-creates-no-database-file.test.ts, through the real door (realHonoHttpServer, real admin routes, real plugin probe, default driver factory unless the case says otherwise):sqlite,sqlite3,sqlite-wasm,turso,libsql× a missing flat path and a missing nested path →200,success: true,result.ok: false, error names the file, the file does not exist and the temp root is still empty (no directory made).createis a spy) is never asked to build any of the five drafts on a missing path; all answerok: false; nothing created.ok: falsenaming the file; only the blocker file remains.ok: trueand nothing else appears beside it; a:memory:draft probesok: true; the default factory, used directly as Save uses it, still creates the file on connect.Acceptance notes
sqlite,sqlite-wasmandturso(file:url); measured throughDatasourceAdminServicePlugin.probewith the default factory on the built tree (all three answeredok: trueand leftnested/dir/X.dbbehind).memorywith file persistence creates nothing on a probe (measured), so it is not judged.sqliteAbsentFile: 'empty-in-memory'option was not used, as the order said: it is factory-wide and answersokon a missing file.d9b68b90). The first shape carried afileMustExistintent onDatasourceConnectionSpec, an exported type, which widened the published surface (Clause-② would have been yes). The judgement now lives inside the probe; the contract and the default factory are back tomain's bytes, soClause-②: noholds.200 { result: { ok: false, error } };TestConnectionResulthas nocodefield, and adding one is a response-shape addition this card did not ask for, so it was not made. The door pin asserts status200,ok: false, the named file and the untouched filesystem; the kind is carried by the error's text, as for every other probe failure.SqlDriver.connect()already writes nothing to a file whose pragmas are set (driver-sql: connect() always runs PRAGMA auto_vacuum = INCREMENTAL, so read-only probes (os migrate duplicates) bump the SQLite header — the "no-op" docblock and "writes nothing at all" doc are untrue #21734); a read-only open would instead fail the probe on a legacyauto_vacuum=NONEfile, whose connect issues the setter (read from the code, not measured). Left as is.tursodraft whose local replica is afile:url that does not exist yet is now refused by the probe too (a first connect would have created the replica file). Consistent with the card: the probe creates nothing.does not exist: the card's acceptance is literally that the probe reports the file does not exist, and the door carries no code, so that phrase is the one kind-bearing fact the Setup page shows.claude/project-thread-yhiysu(the claim names it).Verification
All readings on head
d3b198ecunless the line says otherwise.Red first, then green (the new test file)
72b08eec+ the first version of the test):Tests 11 failed | 3 passed (14), every door caseexpected true to be false(the probe answeredok: trueand left the file).Tests 15 passed (15).Package suite and typecheck
pnpm --filter @objectstack/service-datasource test:Test Files 59 passed (59)·Tests 999 passed (999);typecheckexit 0 (tsconfig includessrc/**, tests included). Lock VERDICT command-exit 0.Ablation (scripts/ablation-replace.mjs, restored blob == HEAD)
if (fileRefusal) return …) deleted ond3b198ec: mutation landed (anchor 1 → 0, blob9c6083e9→abcba580) →Tests 11 failed | 4 passed (15): the ten door cases and the host-factory pin red → restored, blob == HEAD9c6083e9,git diff HEADempty.src/by relative path: no rebuild in the path.Derived gates and the --ran reconciliation
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackond3b198ec→ 59 commands, each run with its exit captured before any pipe: 59 ×exit 0(full build present,service-datasourcerebuilt on this head, socheck:dual-build-cjs-loadsmeasured).--ran:59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN.bf1d2ab4) caughtcheck:dispatcher-error-vocabularyreading a literalENOENTclass field as an unregistered ADR-0112 code; that refusal class no longer exists in this shape.Lint, conformance ledger, public face, merge-tree
eslint packages/services/service-datasource --no-inline-configond3b198ec: exit 0. Repo-widepnpm lintexit 0 on the first headbf1d2ab4.check:driver-conformancebefore (merge base) and after:55 covered cell(s), 0 in the DEBT ledger, 0 exemptboth times. No driver package is touched.git diffofcontracts/,default-datasource-driver-factory.tsandindex.tsagainst the mergedmain(1ed33609): empty.git merge-treeagainst the one other open PR onservice-datasource(fix(service-datasource)!: POST /datasources/:name/test answers an unknown name 404 RESOURCE_NOT_FOUND #27574): clean, no shared file.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F