Repository navigation
fix(driver-sql): a SQLite file that is not a database fails connect() at once, naming it, and a dev boot no longer renames and replaces it - #25006
Conversation
…ct, naming it SqlDriver.connect() swallowed SQLITE_NOTADB from its first PRAGMA and resolved over a file it could not read. It now refuses it as a SqliteDatabaseFileError, as SQLITE_CANTOPEN already was, and the step-down in service-datasource classifies the code as a file failure, so a development boot no longer hands the file to wasm SQLite, whose open moved it aside and wrote an empty database in its place. Claude-Session: https://claude.ai/code/session_01QK6pdbxKZiHdFApL8RYVh9 Co-authored-by: Claude <noreply@anthropic.com>
…tabase as refused and untouched A development boot of a declared datasource on a plain-text file is refused with the driver's SQLITE_NOTADB refusal, steps down to nothing, warns nothing, and leaves the file's bytes and directory exactly as they were; production refuses at connect too. Adds the patch changeset for both packages. Claude-Session: https://claude.ai/code/session_01QK6pdbxKZiHdFApL8RYVh9 Co-authored-by: Claude <noreply@anthropic.com>
…lite-notadb-connect
📓 Docs Drift CheckThis PR changes 2 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 13 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 befe1313369ede18acf6da6e40349e2cb62f6063 && git checkout befe1313369ede18acf6da6e40349e2cb62f6063
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 62bc1293080968f0ec8c3d31dc1f9ed8da4077df 69f54e9130bebfc69d274ae74b10fb27e50a250f && git checkout -B drift-repro 62bc1293080968f0ec8c3d31dc1f9ed8da4077df && git merge --no-ff 69f54e9130bebfc69d274ae74b10fb27e50a250f
node scripts/docs-audit/affected-docs.mjs --json 62bc1293080968f0ec8c3d31dc1f9ed8da4077df
|
|
Seat review ·
Generated by Claude Code |
…ed narrowing ships in the launch window The PR declares Clause-② as no (narrowing), which the Check Changeset level axis reads as a breaking change. A breaking change takes minor during the launch window, so both packages move from patch to minor and the banner sentence states that level and where the breaking-ness is carried. Claude-Session: https://claude.ai/code/session_01QK6pdbxKZiHdFApL8RYVh9 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs: card #24960 (body, Claim 6303624171, os-dev-reports 6304560281 and 6305467682), PR #25006 (body, 6-file list, net diff base...head = +302/-22, identical to the file list), and the check-runs on the head, read ONCE at 2026-10-21T01:20Z and not polled. Read-only throughout: no worktree, build, test, gate or ablation was run; the branch head and the PR base were fetched into review refs of this reader's own and diffed there. Check-runs on the head, exactly as read (38 runs): 15 ① Derived judgmentsEach accept-set or public-surface change the diff implies, named right or wrong:
② Semver level
Clause-②: no (narrowing) ③ Boundary flagsEvery dev flag and open question on #24960, answered or escalated: Round 1 deviations (6304560281).
Round 2 deviations (6305467682). The 21-command path-scoped re-run with 51 not re-run (code and tests byte-identical to the head that measured them): accepted, and the Open question (SQLITE_CORRUPT, same defect one code over). Measured by the dev on this head through the card's own door: a header-truncated database answers the first PRAGMA Out-of-scope findings.
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对Seat review ·
Generated by Claude Code |
|
Queue kick · Signature: merge-group CI run 39528984460 (base Initial reading: a slow runner, not this PR. All of this comes from a read-only log diagnosis of the reachable window (the last 5,000 of 44,158 lines) plus the Jobs API step timings:
The three facts for one re-queue:
So this seat re-queues once. If the same shard fails again, it is read as real and diagnosed in full, not re-queued. Generated by Claude Code |
Fixes #24960
Clause-②: no (narrowing)
Narrowing face: a
SqlDriver.connect()that resolved now refuses. On a SQLite file whose bytes are not a SQLite database,connect()used to log and swallowSQLITE_NOTADBand resolve; it now rejects withSqliteDatabaseFileError(code: 'SQLITE_NOTADB',filename, a message naming the file), in development and production alike. On a development boot,resolveSqliteDriverre-throws that refusal instead of stepping down to wasm SQLite, so the operator's file is no longer renamed toFILE.corrupt-TIMESTAMPand replaced with an empty database. Nothing exported changes shape.Route A, the card's seat ruling, as dispatched: the producer refuses the file, and the step-down classifies the code. B (consumer-only sniffing) and C (keep the wasm quarantine as dev recovery) are not taken.
What changed
packages/drivers/driver-sql/src/sql-driver.ts, theapplyAutoVacuumIncrementalregion only. The first-statement codes that are a verdict on the file now sit in one table,SQLITE_FILE_REFUSALS:SQLITE_CANTOPEN(as runtime: a declared sqlite datasource whose file cannot be created never settles its connect, so the boot is refused only by a plugin start timeout (30 s, 120 s after PR #23694) that names neither the datasource, the path nor the open error #23696 shipped it, same words) andSQLITE_NOTADB(new, "is not a SQLite database, and was left as it is"). Either throwsSqliteDatabaseFileErrorbefore anything is written. Every other PRAGMA failure keeps today's warning andreturn false.packages/drivers/driver-sql/src/sqlite-database-file.ts: the class doc names the new code. Doc only.packages/services/service-datasource/src/sqlite-driver-fallback.ts:isSqliteFileOpenFailureaddsSQLITE_NOTADB, with its doc list and one sentence in the module note. This is the cross-lane line fordomain:servicesthat the dispatching seat declared.resolveSqliteAbsentFileTarget,knexConfigForand the probe body are untouched, because driver-sql: the read-only probe's absent-file test (existsSync) reads an unopenable SQLite path as absent, so os migrate plan opens an empty in-memory database, reports the datasource connected, and plans DDL into a file that cannot exist #24961 is changing those in parallel. A localgit merge-treeof this head with that branch's head28d5dd67eis clean..changeset/24960-sqlite-notadb-connect.md:patchfor@objectstack/driver-sqland@objectstack/service-datasource. It carries theClause-②: no (narrowing)line, the BREAKING banner with the operator's fix, and an ADR-0087not-required (no-migration-prescription)disposition.packages/drivers/driver-sql/src/sql-driver-24960-sqlite-not-a-database.test.tsandpackages/services/service-datasource/src/__tests__/sqlite-driver-fallback-not-a-database.test.ts.Premise, measured (better-sqlite3 13.0.3, Node v22.22.0)
new Database(file)succeeds, and the first statement,PRAGMA auto_vacuum, throwscode: 'SQLITE_NOTADB'("file is not a database"). So doPRAGMA journal_modeandSELECT 1. A file whose 100-byte header was overwritten past the magic string answers the same code. On base,applyAutoVacuumIncrementalmatched onlySQLITE_CANTOPEN, logged "Failed to set PRAGMA auto_vacuum=INCREMENTAL" and returnedfalse, soconnect()resolved.isSqliteFileOpenFailure, the development probe re-throws at the native rung, and wasm'squarantineCorruptFileis never reached for a declared datasource whose addon loads. The pins assert that the file's bytes are unchanged and that the directory holds only that file: no.corrupt-*sibling, no journal, no WAL.NATIVE_SQLITE_WASM_FALLBACK_WARNING("dev using wasm SQLite"). The driver's similar warning fires only for a native load failure, whichSQLITE_NOTADBis not. With this change the step-down never runs on this path, so the warning no longer prints: the pin assertswarnwas never called, and the public-door run below prints neither line.connect()resolving over a file that is not a database? I found none. I grepped the tree forNOTADB/ "not a database" /SQLITE_CORRUPT/ "disk image is malformed", for tests that write arbitrary bytes into a.dbfile, and read everyresolveSqliteDrivercaller.os db clean: it calledresolveSqliteDriver({ dev: true })and then VACUUMed. On base, a garbage file was moved aside, and the "clean" reported the empty replacement's size. Now it refuses the file, which is the better answer.telemetry-datasource.ts): it already drops its separation on any throw,catchthenundefined, the same as for fix(cli,service-datasource): under bin/run-dev.js (NODE_ENV=development) an unopenable SQLite datasource steps down to an in-memory engine, so a dev-mode os migrate audit-mail-bodies reports an unreachable archive as examined and clean #24950's codes. The boot continues.dev-organization-ownership-upgrade.test.tswrites text intodev.dbbut drives a fake CLI, and no driver opens it.TursoDriverlocal and replica modes open the file through thisSqlDriver.connect(). Measured: a file written by@libsql/clientwith anencryptionKeyanswers better-sqlite3 withSQLITE_NOTADB. Such a datasource is now refused at connect, naming the file. Before, connect resolved and every read through Knex failed with the same code, so nothing that worked stops working.Evidence
Pins (all at head
6d3dd6247, underos-verify-lock):driver-sql,
sql-driver-24960-sqlite-not-a-database.test.ts: a plain-text file is refused withSqliteDatabaseFileError. The pin checkscode=SQLITE_NOTADB,filename, a message naming the file and carrying the cause, the same bytes after, and the directory listing equal to the one file. It runs under bothNODE_ENV=developmentandNODE_ENV=production. Controls: a real database reconnects and answers, and an empty file connects. Result: 8/8 with the runtime: a declared sqlite datasource whose file cannot be created never settles its connect, so the boot is refused only by a plugin start timeout (30 s, 120 s after PR #23694) that names neither the datasource, the path nor the open error #23696 file.service-datasource,
sqlite-driver-fallback-not-a-database.test.ts:resolveSqliteDriverrejects with the driver's refusal, warns nothing, and leaves the file untouched;connect()refuses the file;DatasourceConnectionService.connectDeclaredreportsavailability: 'failed'with a reason naming the file and registers no driver, in development and production, and the file is untouched.Result: 24/24 with the fix(cli,service-datasource): under bin/run-dev.js (NODE_ENV=development) an unopenable SQLite datasource steps down to an in-memory engine, so a dev-mode os migrate audit-mail-bodies reports an unreachable archive as examined and clean #24950 file and
sqlite-driver-fallback.test.ts.Whole packages at
c5b499cf0, before mergingmain: driver-sql "Test Files 226 passed | 12 skipped (238) · Tests 3696 passed | 209 skipped (3905)" (the skips are the live PG/MySQL cells); service-datasource "Test Files 51 passed (51) · Tests 912 passed (912)".tsc --noEmitexit 0 for both.tsc --listFilesincludes each new test file (1 hit each).The merge of
origin/mainat4fe3af9d7brought feat(spec,objectql): report a caller value a stored expression replaced, as DroppedFieldsEvent reason stored_expression #24778, feat(spec)!: addRecord says what renders — enabled required, position top, mode/formView retired #24970 and docs(deployment): branding env vars follow the runtime — OS_PRODUCT_NAME defaults to ObjectOS, six readers added, precedence per posture #24990. They touch spec, objectql, ui and docs, none of these packages, and the targeted pins above were re-run on the merge head.Ablation (
scripts/ablation-replace.mjsWRAP with a trap restore, atc5b499cf0):SQLITE_NOTADBtable row was mutated toABLATION_24960_NOTADB: anchor 1 to 0, blobb99ad91eeb7eto3eb1e25b29ac. driver-sql was rebuilt, andablation-dist-preflight.mjsfound the marker in 2 built files.connect() resolved for a file that is not a SQLite database). Green: the controls and the runtime: a declared sqlite datasource whose file cannot be created never settles its connect, so the boot is refused only by a plugin start timeout (30 s, 120 s after PR #23694) that names neither the datasource, the path nor the open error #23696 file.sqlite-wasm; the production refusal wasundefined; bothDatasourceConnectionServicecases read'available'.git diff HEADempty. After the rebuild, preflight--absentfound the marker in none of the 6 built files, and the tree was clean.|| code === 'SQLITE_NOTADB'was mutated toABLATION_24960_SD: blob165d52e2d851tobda6c0c91769. The subject is a relativesrcimport, so this leg needs no dist step.git diff HEADempty.Public door, the card's own:
bin/run-dev.js migrate apply --yes --database-url file:main.dbover adist/objectstack.jsonthat declaresarchive(driver: sqlite,autoConnect: true) on a plain-text file, at head6d3dd6247after aturbo run build --filter=@objectstack/cli^...(60/60).archive: the run printsdatasource 'archive': connect failed — SQLite database file '…/data/archive.db' is not a SQLite database, and was left as it is: PRAGMA auto_vacuum - file is not a database. (1 object(s) bind to it explicitly … fail-fast per ADR-0062 D5)and exits 1. The md5 is unchanged anddata/holdsarchive.db main.db.WARN datasource 'archive': connect failed — … is not a SQLite database … — degrading (datasource left unconnected), thenApplied 0 change(s), and exits 0. The md5 is unchanged, there is no.corrupt-*sibling, and no step-down warning prints.connected.Driver ledger (
pnpm check:driver-conformance):4b8f597ba: "55 covered cell(s), 0 in the DEBT ledger, 0 exempt"; dialect axis 9 suites, 8 matrix and 1 named, 0 in the DIALECT ledger.6d3dd6247: the same counts. The ledger did not move.Gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths) at6d3dd6247derived 72 commands: the seat's 55 plus 17 for the changeset and the new test files. I ran each one once, sequentially, with its exit captured before any pipe, and all 72 exit 0. Two answered exit 3 (PREREQUISITE NOT MET) on the first pass and were re-run after their prerequisite:check-plugin-teardown-shape.mjs --self-testneeded its pinned fixture commit, fetched at depth 1;check:dual-build-cjs-loadsneeded every package'sdist/, from aturbo run buildthat was 75 of 76 cache hits.--ranreconciliation: "72 derived, 72 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero because all 72 recorded an exit code. CI owns the full farm andtest:repo.Acceptance notes
SQLITE_CORRUPT, the same defect one code over, is not in this PR. It is reported to the seat; this PR leaves it alone. A SQLite database truncated inside its 100-byte header answers the firstPRAGMAwithSQLITE_CORRUPT("database disk image is malformed"), whichconnect()still swallows. I ran the same door at this head with an object bound toarchive:archive.dbwas moved toarchive.db.corrupt-1792539192447, an empty database was written in its place, the run readdatasource 'archive': connectedunder the "native better-sqlite3 unavailable" warning, and it exited 0. The fix would be one more row inSQLITE_FILE_REFUSALSand one more code inisSqliteFileOpenFailure. It stays out because the ruling and the claim nameSQLITE_NOTADB, andsqlite-driver-fallback.tsis also held by the driver-sql: the read-only probe's absent-file test (existsSync) reads an unopenable SQLite path as absent, so os migrate plan opens an empty in-memory database, reports the datasource connected, and plans DDL into a file that cannot exist #24961 claim, so the in-place exemption does not hold.acquireRawConnectionanswering a realERR_DLOPEN_FAILED): the run resolved tosqlite-wasm, anddata/heldarchive.dbandarchive.db.corrupt-TIMESTAMP. It lives in the wasm quarantine, which the claim scopes out.sqlite-driver-fallback.ts("refuses a file it cannot open or create") still name only the open-failure half. I left them unedited to stay off the driver-sql: the read-only probe's absent-file test (existsSync) reads an unopenable SQLite path as absent, so os migrate plan opens an empty in-memory database, reports the datasource connected, and plans DDL into a file that cannot exist #24961 region.maingained fix(plugin-auth): the register form's Application Type reaches the OAuth provider as application_type #24992 (plugin-auth), which this diff does not touch. I did not merge it, so the gate union stays measured on the head it names.Generated by Claude Code