Repository navigation
fix(runtime): an explicit sqlite / sqlite-wasm selection refuses a URL that is not a file path, on both hosts - #23896
Conversation
… that is not a file path The rows go red on 34b740f, where an explicit selection opens `jdbc:postgresql://…`, `redis:h` and (on sqlite-wasm) a `postgres://…` server URL as a SQLite file at a path made of the URL. Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT Co-authored-by: Claude <noreply@anthropic.com>
…plicit-sqlite-refuses-non-file
…L that is not a file path `sqliteFilenameFromUrl` is the only check an explicit selection's URL meets, and it refused only a `SCHEME://` URL on the `sqlite` kind. Both kinds now refuse an opaque scheme (`jdbc:postgresql://…`, `redis:h`) on the value as given, and the `sqlite-wasm` kind refuses a `SCHEME://` URL too. A value whose `file:` (or `wasm-sqlite:`) prefix the rule strips stays a file, so `file:backup:2026.db` still names `backup:2026.db`. One sentence for both kinds, naming the kind and the scheme only. The opaque-scheme expression `detectDriverFromUrl` gained in the merged opaque-scheme fix is now one module-private helper, `schemeOf`, that both functions call. Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT Co-authored-by: Claude <noreply@anthropic.com>
…te / sqlite-wasm selection; changeset Explicit-driver rows in the driver vocabulary parity pin: an opaque scheme, an unrecognised SCHEME:// and a server URL are refused by both hosts under both kinds with one sentence (the CLI host's typed and untagged), no own property of either error carries the placeholder credentials, and every file value names the same file on both hosts. Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check3 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 45 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 7d9853680114d6a9a6e0742ec503a0b7f26fdfa9 && git checkout 7d9853680114d6a9a6e0742ec503a0b7f26fdfa9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 497518c3f67d8cc022fc6663d2e08f97117298a4 c80f0ceaaa8d9f7d1fb3982a386d97fd48dc23eb && git checkout -B drift-repro 497518c3f67d8cc022fc6663d2e08f97117298a4 && git merge --no-ff c80f0ceaaa8d9f7d1fb3982a386d97fd48dc23eb
node scripts/docs-audit/affected-docs.mjs --json 497518c3f67d8cc022fc6663d2e08f97117298a4 |
席位核对ACCEPT · #23863 ·
Out of scope, filed by this seat: the refusal ends "…, or set OS_DATABASE_DRIVER explicitly." to an operator who did set it (finding 1, measured at the door). It is older than this card on the Landing: once every check on the head is green or an expected skip, this seat flips the PR ready and arms auto-merge through the relay, then follows it to Generated by Claude Code |
Fixes #23863
Clause-②: no
Under an explicit
sqliteorsqlite-wasmdriver selection, the one SQLite file-name rule both hosts call (sqliteFilenameFromUrlin@objectstack/runtime) now refuses every value that is not a file path. It refuses an opaque scheme (jdbc:postgresql://…,redis:h) on both kinds, and on thesqlite-wasmkind it also refuses aSCHEME://URL (postgres://…,redis://h/x.db), as thesqlitekind already did. The refusal names the selected kind and the scheme only, never the URL, and the boot stops before it creates anything on disk.Why
An explicit selection skips the URL classification (
resolveStandaloneDatabase:explicitDriver || detectDriverFromUrl(url)), so the opaque-scheme refusal that PR #23861 added to that classification for #23765 (merged) never sees it. The file-name rule was the only check left, and it recognised onlySCHEME://, and only for thesqlitekind. Measured at the public door on base34b740f2d1(each boot in a fresh directory outside the repo,OS_HOMEpinned inside it):os serve --dev, plain config)34b740f2d1c80f0ceaaOS_DATABASE_DRIVER=sqlite OS_DATABASE_URL=jdbc:postgresql://127.0.0.1:5999/db✓ Server is ready,Driver: SqlDriver(better-sqlite3) → jdbc:postgresql://127.0.0.1:5999/db, created./jdbc:postgresql:/127.0.0.1:5999/dbplus its.telemetry.db,-shmand-walsiblings✗ [StandaloneStack] sqlite driver was selected but the URL does not look like a file path (its scheme is "jdbc:"). Use file:/path/to/db.sqlite, or set OS_DATABASE_DRIVER explicitly., directory emptyOS_DATABASE_DRIVER=sqlite-wasm OS_DATABASE_URL=postgres://PLACEHOLDER-USER:PLACEHOLDER-SECRET@127.0.0.1:5999/db✓ Server is ready, created./postgres:/PLACEHOLDER-USER:PLACEHOLDER-SECRET@127.0.0.1:5999/db(the credentials in a directory name)✗ [StandaloneStack] sqlite-wasm driver was selected but the URL does not look like a file path (its scheme is "postgres://"). …, directory empty, 0 placeholder hits in the logOS_MODE=off OS_DATABASE_DRIVER=sqlite-wasm OS_DATABASE_URL=jdbc:postgresql://127.0.0.1:5999/db✗ sqlite-wasm driver was selected but the URL does not look like a file path (its scheme is "jdbc:"). …(the typedUnsupportedDriverError, untagged), directory emptyChanges
packages/runtime/src/standalone-stack.tsschemeOf(new, module-private, no new export): the opaque-scheme expression PR fix(runtime): the standalone host refuses an opaque-scheme OS_DATABASE_URL, and a .wasm.db ending no longer rescues an unrecognised scheme #23861 added inline todetectDriverFromUrl(urlSchemeOf(dbUrl) ?? /^[a-z][a-z0-9+.-]+:/i.exec(dbUrl)?.[0]), now one helper.detectDriverFromUrlcalls it in place of the inline expression; nothing else in that function changed.sqliteFilenameFromUrl: both kinds strip their prefixes as before, then askschemeOfof a value the strip left as given, andurlSchemeOfof what afile:(orwasm-sqlite:) strip left. Sofile:backup:2026.dbstill namesbackup:2026.db. One sentence for both kinds, naming the kind; thesqlitekind's words are unchanged. An empty name is still:memory:on the WASM kind.packages/cli/src/utils/storage-driver.ts:sqliteFileFor's docblock only. Its "Two inputs reach that" sentence was no longer true. No CLI code change: the CLI host already calls the runtime rule and re-raises its refusal asUnsupportedDriverError.packages/runtime/src/standalone-stack-explicit-sqlite-non-file-refusal.test.ts(new, 80 cases): both kinds through both selection doors (databaseDriver+databaseUrl,OS_DATABASE_DRIVER+OS_DATABASE_URL) and through the exported rule itself. The refused rows assert a plainErrornaming the kind and the scheme, and that no own property of the error carriesPLACEHOLDER-USER/PLACEHOLDER-SECRET/PLACEHOLDER-TOKEN, with a positive control that the serialisation holds the message. The boot (createStandaloneStack) is refused in an empty cwd and leaves it empty. CONTROL rows, the one changed file name and the un-normalized rungs (Acceptance notes) are pinned too.packages/cli/src/utils/driver-vocabulary-parity.test.ts: explicit-driver rows under both kinds. Both hosts refuse the same values in dev AND prod, with the CLI host's message equal to the runtime host's minus its[StandaloneStack]tag, anddriverTypeequal to the kind. Placeholder credentials are absent from every own property of both errors. Every CONTROL value names the same file on both hosts. The rows of the classification path (PR fix(runtime): the standalone host refuses an opaque-scheme OS_DATABASE_URL, and a .wasm.db ending no longer rescues an unrecognised scheme #23861) are untouched..changeset/23863-explicit-sqlite-non-file-refusal.md:@objectstack/runtimepatch,Clause-②: no. It names the one file name that changes answer under an explicit selection:backup:2026.dbis now refused, and./backup:2026.dbandfile:backup:2026.dbstill name it.Verification (head
c80f0ceaa)pnpm turbo run build --filter=@objectstack/cli... --concurrency=2→ 61/61 tasks, exit 0 (the CLI's dependency closure plus the CLI itself).dist, under each kind, in dev and prod (resolveStorageDefinitionvsresolveStandaloneDatabase). Both refusejdbc:postgresql://127.0.0.1:5999/db(jdbc:),redis:h(redis:),redis://h/x.db(redis://),postgres://PLACEHOLDER-USER:PLACEHOLDER-SECRET@…(postgres://) andbackup:2026.db(backup:). Both openfile:./x.db,./x.db,C:\data\x.db,C:/data/x.db,:memory:,file:backup:2026.db,./backup:2026.db,sqlite:./x.dbandsqlite:///abs/x.dbas the same file. Undersqlite-wasm, both openwasm-sqlite:///tmp/x.dbas/tmp/x.db. On base, both hosts opened every refused value as a file undersqlite-wasm, and every opaque one undersqlitetoo.pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2with the new file,standalone-stack-opaque-scheme-refusal.test.tsandstandalone-stack-url-refusal-credentials.test.ts→ 3 files, 157 passed, exit 0.pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2withdriver-vocabulary-parity.test.tsandstorage-driver.test.ts→ 2 files, 204 passed, exit 0.pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2(the runtime's whole local project) → 402 files, 6121 passed, 19 skipped, exit 0.pnpm --filter @objectstack/runtime typecheck→ exit 0.pnpm --filter @objectstack/cli typecheck→ exit 0.pnpm exec eslint --no-inline-configover the four changed source and test files → exit 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 65 commands. 63 exited 0.pnpm check:dual-build-cjs-loadsandpnpm check:i18n-coverageexited 3 (PREREQUISITE NOT MET: packages outside the CLI closure, such asconnector-slack, have nodist/in this worktree), so they are NOT MEASURED locally and left to CI.--ranreconciliation: 65 derived, 63 run, 2 NOT-MEASURED, 0 UNRUN. The repo-wide lint run is CI'sLint & Repo Gates.Ablation (each new refusal once; the CLI suite resolves
@objectstack/runtimethroughdist/, so every leg rebuilt it)Each mutation went through
scripts/ablation-replace.mjs: the anchor hit once, the blob changed, and the restore was proven by blob == HEAD with an emptygit diff HEAD. Each leg rebuilt the runtime and ranscripts/ablation-dist-preflight.mjs.filename === dbUrl ? schemeOf(dbUrl) : urlSchemeOf(filename)→urlSchemeOf(filename)backup:2026.db, thesqlite:boundary rows, and thesqliteboot overjdbc:.SCHEME://rows and CONTROL greenif (!filename || scheme)→if (!filename || (scheme && kind.length === 6))sqlite-wasmrefusal row (opaque andSCHEME://), thesqlite-wasmboot, and thesqlite-wasmboundary rows. Allsqliterows and CONTROL greensqlite-wasmrows. The rest greenAfter both legs: file blob
f3f84355f733== HEAD,git status --porcelainempty.Acceptance notes
resolveProjectDatabaseUrlappliesnormalizeDatabaseUrlon the explicit andOS_DATABASE_URL/DATABASE_URLrungs. A declared default datasource'surl(non-SQLite drivers only; a declaredsqlitedatasource is always expressed asfile:) andTURSO_DATABASE_URLreach the rule as written. Under an explicit selection there,sqlite:./x.dbis now refused as the schemesqlite:. On base it opened a file namedsqlite:./x.db, which meant asqlite:.directory.sqlite:///…was already refused there undersqlite, and is now refused undersqlite-wasmtoo. The one-line alternative, callingnormalizeDatabaseUrlinsidesqliteFilenameFromUrl, would have madesqlite:///abs/x.dbopen on those rungs where it is refused today. That widens the accept set, against this card's ruledClause-②: no, so the boundary is pinned instead (the boundary: rungs that do not normalize the URL). The classification path answers these values the same way since PR fix(runtime): the standalone host refuses an opaque-scheme OS_DATABASE_URL, and a .wasm.db ending no longer rescues an unrecognised scheme #23861, whose own Acceptance notes record the un-normalized declared-datasource tier ("No in-repo producer writes it"). No producer was found here either.createStandaloneStackin an empty cwd).mainmoved after the merge. Six commits landed pastc5cf546a6(spec, plugin-audit, docs, CI header). None touchesstandalone-stack.ts,resolve-project-database.tsorpackages/cli/src/utils/. PR CI runs on the merge ref.Generated by Claude Code