Skip to content

fix(cli): os serve refuses an OS_DATABASE_URL whose scheme no driver recognises, and reads pg:// and a scheme-less path as the runtime host does - #23761

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-23633-unrecognised-database-url-scheme
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-23633-unrecognised-database-url-scheme

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #23633
Clause-②: no

What this changes

inferDriverTypeFromUrl (packages/cli/src/utils/storage-driver.ts) is how os serve classifies OS_DATABASE_URL on its legacy path. A host config takes that path (one whose plugins hold plugin instances), and so does OS_MODE=off|none|legacy. os dev and os start reach it through their serve child. The function answered '' for any URL its regexes missed.

  • An unrecognised SCHEME:// (redis://…) is now refused. The function throws UnsupportedDriverError (recognized: false), as it already does for memory:// and mingo://. The message names OS_DATABASE_URL, the scheme, and the recognised spellings in the runtime host's order. It names the scheme only, never the URL, so credentials in the URL are not printed. serve.ts calls resolveDriverType above its storage try, so the throw reaches the fatal handler and the boot exits 1 in dev and production. serve.ts is not edited.
  • The runtime host's arms are adopted, with one class refused. pg:// is postgres, and a value with no scheme is a SQLite file path, whatever its extension, as in detectDriverFromUrl (packages/runtime/src/standalone-stack.ts, read only). An OPAQUE scheme (two or more characters and a : with no //, such as jdbc:postgresql://h/db or redis:h) is refused with the same UnsupportedDriverError and sentence, naming the scheme ("jdbc:"). The runtime host still opens those as a SQLite file at a path made of the URL, against its own rule that an unknown scheme never falls back to SQLite; that gap is runtime(standalone host): an OS_DATABASE_URL with an opaque scheme (jdbc:postgresql://…, redis:h) is opened as a SQLite file path and creates directories, against the module's own "unknown URL schemes throw" #23765's. A one-letter drive is a path, not a scheme (C:\data\app, C:/data/app).
  • A .db or .wasm.db ending no longer rescues an unrecognised scheme. The old *.db arm ran before any scheme check, so redis://h/app.db was handed to SQLite as a file path. It is now refused, as the runtime host refuses it. A *.wasm.db name likewise selects the WASM engine only for a file: / sqlite: URL or a path with no scheme, so redis://h/app.wasm.db and jdbc:x/app.wasm.db are refused too. The runtime host still accepts redis://h/x.wasm.db as a WASM SQLite file (measured below); the seat records that host's answer on runtime(standalone host): an OS_DATABASE_URL with an opaque scheme (jdbc:postgresql://…, redis:h) is opened as a SQLite file path and creates directories, against the module's own "unknown URL schemes throw" #23765.
  • sqlite: and sqlite:// are aliases of file:, by the runtime host's own rule. The sqlite and sqlite-wasm arms of resolveStorageDefinition translate the URL through normalizeDatabaseUrl (@objectstack/runtime) rather than a second copy of it. sqlite://x.db and sqlite:x.db open ./x.db, sqlite:///abs/x.db opens /abs/x.db, and sqlite://x.wasm.db opens ./x.wasm.db on the WASM engine. Before, sqlite://x.db opened //x.db, a file at the filesystem root.
  • A blank URL is absent. Whitespace-only values still answer ''. Without that guard the new scheme-less arm would read them as a file path.
  • An explicit OS_DATABASE_DRIVER still wins. resolveDriverType does not classify the URL at all when a driver is named.

The memory:// / mingo:// refusal and every recognised spelling resolve exactly as before. No export is added, and packages/spec is not touched.

Measured at the public door

Built CLI (node packages/cli/bin/run.js), NODE_ENV unset, OS_CLOUD_URL=off, auth secret set. The fixture is a throwaway defineStack host config: one object and one instantiated no-op plugin. Before = base 3b46855fb; after = this branch at 54933125a, built.

Shape Before After
OS_DATABASE_URL=redis://127.0.0.1:6399/0 os serve --dev ✓ Server is ready, banner Driver: SqlDriver(better-sqlite3) → :memory: exit 1, ✗ Unsupported database URL scheme "redis://" (OS_DATABASE_URL / --database). Supported: …
the same, production (os serve) exit 1, the no-storage refusal ("set OS_DATABASE_URL …") exit 1, the same unsupported-scheme line
os dev --no-watch, same URL 🗄️ Database: redis://127.0.0.1:6399/0, then the banner → :memory: exit 1, the unsupported-scheme line
os start, same URL exit 1, the no-storage refusal exit 1, the unsupported-scheme line
redis://admin:PASSWORD@127.0.0.1:6399/0 os serve --dev banner → :memory: exit 1; the password occurs 0 times in the output
pg://u:p@127.0.0.1:5999/db, --dev banner → :memory: exit 1, datasource 'default': connect failed — connect ECONNREFUSED 127.0.0.1:5999 (the URL is used)
the same, production exit 1, the no-storage refusal exit 1, the same connect failure
./data/app, --dev banner → :memory: ✓ Server is ready, banner → ./data/app; the file exists
./data/app2, production exit 1, the no-storage refusal ✓ Server is ready, banner → ./data/app2
CONTROL memory://, --dev exit 1, the retirement sentence unchanged
CONTROL postgres://u:p@127.0.0.1:5999/db, --dev exit 1, connect ECONNREFUSED unchanged
CONTROL file:./ctl.db, production ✓ Server is ready, → file:./ctl.db unchanged
CONTROL OS_DATABASE_DRIVER=postgres + redis://127.0.0.1:6399/0, --dev (not measured before) exit 1, connect ECONNREFUSED 127.0.0.1:6399: the explicit driver wins and the scheme is not refused
a plain config (runtime host), redis://…, --dev exit 1, [StandaloneStack] Unsupported database URL scheme: … unchanged (not this path)

Round 1, measured the same way

Before = 683bf9587 (round 0's head; storage-driver.ts is the only non-test source file between the two); after = 40fe66778. Each boot ran in a throwaway directory, so every file it wrote is listed relative to it.

OS_DATABASE_URL Before (dev and production) After (dev and production)
jdbc:postgresql://127.0.0.1:5999/db ✓ Server is ready, → jdbc:postgresql://127.0.0.1:5999/db; created ./jdbc:postgresql:/127.0.0.1:5999/db exit 1, ✗ Unsupported database URL scheme "jdbc:" (OS_DATABASE_URL / --database). Supported: …; no file
redis:h ✓ Server is ready, → redis:h; created ./redis:h exit 1, the same sentence naming "redis:"; no file
sqlite://x.db the file //x.db, the filesystem root (read from the built module, not booted: a boot writes /x.db) ✓ Server is ready, → sqlite://x.db; created ./x.db
redis://h/x.wasm.db ✓ Server is ready, SqliteWasmDriver → redis://h/x.wasm.db; created ./redis:/h/x.wasm.db exit 1, the same sentence naming "redis://"; no file
C:/data/app (read from the built module, no boot) sqlite, the file C:/data/app unchanged
CONTROL postgres://u:p@127.0.0.1:5999/db exit 1, connect ECONNREFUSED 127.0.0.1:5999 unchanged
CONTROL file:./ctl.db, --dev (not measured) ✓ Server is ready, → file:./ctl.db

The runtime host, read and booted for redis://h/x.wasm.db and not changed here: resolveStandaloneDatabase answers sqlite-wasm on the file redis://h/x.wasm.db, and a plain config boots to ✓ Server is ready in dev and production with a 1,499,136-byte database at ./redis:/h/x.wasm.db.

The two hosts, URL by URL

The runtime host's verdict is resolveStandaloneDatabase from @objectstack/runtime. The CLI host's is resolveStorageDefinition(resolveDriverType(undefined, url), …). Only the rows that moved are listed; every other row (postgres://, postgresql://, mysql://, mysql2://, mongodb://, mongodb+srv://, libsql://, https://*.turso.io, wasm-sqlite://, a scheme-less or file: *.wasm.db, memory://, mingo://, file:, sqlite:, :memory:, *.db, *.sqlite, *.sqlite3) was already equal and is unchanged.

OS_DATABASE_URL CLI before (dev / prod) CLI after (dev / prod) runtime host
redis://…, REDIS://…, rediss://…, http(s):// (not turso), ftp://, mssql://, sqlserver://, oracle://, cockroachdb://, mariadb:// :memory: default / null refused / refused refused
pg://…, PG://… :memory: default / null postgres / postgres postgres
./data/app, /tmp/…/app, app, C:\data\app, C:/data/app :memory: default / null sqlite / sqlite sqlite
jdbc:postgresql://h/db, redis:h (a scheme with no //) :memory: default / null refused / refused sqlite, a file at a path made of the URL (#23765)
redis://h/x.wasm.db, jdbc:x/x.wasm.db (an unrecognised scheme with a .wasm.db ending) sqlite-wasm / sqlite-wasm, a file at a path made of the URL refused / refused sqlite-wasm, a file at a path made of the URL (#23765)
sqlite://x.db, sqlite://x.wasm.db sqlite / sqlite on //x.db; sqlite-wasm on //x.wasm.db (the filesystem root) sqlite / sqlite on x.db; sqlite-wasm on x.wasm.db the same files as the CLI after

Pins

All under packages/cli/src/**, in the unit tier.

  • utils/storage-driver.test.ts:
    • pg:// / PG:// map to postgres, and four scheme-less paths map to sqlite;
    • '' comes back only for an absent or blank URL;
    • five refused URLs, redis://h/app.db among them, each checked for the error type, recognized: false, driverType = the scheme, and that the message names OS_DATABASE_URL, the scheme and the recognised spellings;
    • the refusal does not carry the URL's credentials or host;
    • an explicit driver still wins over an unrecognised scheme.
    • round 1: jdbc:postgresql://localhost:5432/db, redis:cache, redis://h/app.wasm.db and jdbc:x/app.wasm.db are refused at inference, naming the scheme; C:\data\app, C:/data/app and ./backup:2026.db stay sqlite paths; sqlite://x.db, sqlite:x.db, sqlite:///var/lib/objectstack/x.db and SQLITE://x.db resolve to the file x.db or /var/lib/objectstack/x.db, with the typed URL kept for the banner; a file: / sqlite: *.wasm.db selects sqlite-wasm; sqlite://x.wasm.db is the WASM engine on x.wasm.db.
    • The old row asserting inferDriverTypeFromUrl('redis://localhost') === '' pinned the defect. It is replaced.
  • utils/driver-vocabulary-parity.test.ts, a new block, "the OS_DATABASE_URL door", driving both hosts:
    • 19 URLs that the runtime host accepts. Each is accepted by both hosts as the same kind, and the CLI builds its definition from that URL (displayUrl === url) in dev and in prod. A bare driver-id check would read the dev :memory: default as agreement.
    • 5 URLs that both hosts refuse, in dev and in prod. The CLI's refusal is the typed one.
    • 4 values the CLI host refuses, typed, in dev and in prod: the opaque schemes jdbc:postgresql://localhost:5432/db and redis:cache, then redis://h/app.wasm.db and jdbc:x/app.wasm.db. The runtime host's answer for these is deliberately not pinned: today it opens them as a file, the gap runtime(standalone host): an OS_DATABASE_URL with an opaque scheme (jdbc:postgresql://…, redis:h) is opened as a SQLite file path and creates directories, against the module's own "unknown URL schemes throw" #23765 names.
    • 4 alias values both hosts open as the same file, in dev and in prod: sqlite://x.db and sqlite:x.db as x.db, sqlite:///var/lib/objectstack/x.db as itself, and sqlite://x.wasm.db as x.wasm.db on the WASM engine.
    • one more accepted row, file:./data/app.wasm.db as sqlite-wasm.

Not landed: a real-boot pin. The fixture that spawns the built bin/run.js in production posture lives under packages/cli/test/. A new spawner there also joins the built-entrypoint roster in scripts/check-cli-test-child-env.mjs. Both are outside this card's file surface. The CLI's existing refusals (memory://, unknown OS_DATABASE_DRIVER) are pinned at this same unit layer in both modes. The boots above are the one-off proof of the exit path.

Ablation (one per behaviour changed; WRAP mode of scripts/ablation-replace.mjs, on-disk proof, restore proven as blob == HEAD and an empty git diff HEAD)

Leg Mutation Result
A1 return '' restored for an unrecognised SCHEME:// 12 red / 117 green
A2 pg dropped from the postgres arm 3 red
A3 the old *.db / :memory: arm in place of the scheme-less arm 8 red, redis://h/app.db among them
A4 the blank-URL guard removed 1 red

Round 1, at 40fe66778: the same tool in WRAP mode over the two pinned files (149 tests, baseline 149 passed). The subject is src (both files import ./storage-driver.js), so no dist/ is involved. Each mutation is proven on disk (anchor x1 → x0 and a new blob). Each restore is proven as blob == HEAD 893ea05be3ed with an empty git diff HEAD, by the tool and again by an outer trap.

Leg Mutation Result
R1-A the opaque-scheme fallback removed from the scheme test 6 red / 143 green: the jdbc:postgresql://…, redis:cache and jdbc:x/app.wasm.db rows, in both files
R1-A2 the opaque test widened to a one-letter scheme 2 red: C:\data\app and C:/data/app
R1-B1 the sqlite arm back to stripping sqlite: in place of normalizeDatabaseUrl 5 red: sqlite://x.db, sqlite:///var/lib/objectstack/x.db, SQLITE://x.db (sqlite:x.db stays green, since the old strip handled it)
R1-B2 the sqlite-wasm arm back to stripping sqlite: 2 red: sqlite://x.wasm.db, in both files
R1-C the *.wasm.db test moved back above every scheme check 4 red: redis://h/app.wasm.db and jdbc:x/app.wasm.db, in both files

The first A1 attempt was a no-op: the tool refused it because the anchor was contained in the replacement, and restored the file. It was re-run with a non-overlapping replacement; the counts above are from that run.

Tests and gates (head 40fe66778)

  • pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2, in two shards (--shard=1/2, --shard=2/2): 148 + 147 = 295 files, 2464 + 1953 = 4417 tests passed. The diff touches no integration-tier file, spawn entry or driver/kernel boot path, so the integration layer is declared to CI.
  • pnpm --filter @objectstack/cli typecheck: exit 0. tsc --noEmit --listFiles lists both changed test files (include: ["src"], no exclude), then check:test-typecheck ran OK.
  • pnpm exec eslint packages/cli --no-inline-config: exit 0, 696 files, 0 errors, 0 warnings.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 64 commands, run in order, each exit captured before any pipe. All 64 exited 0, check:dual-build-cjs-loads (110 require entry points across 68 packages load) and check:i18n-coverage (14 configs, none new) included. --ran reconciliation: 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. The derivation read a tree 19 commits behind origin/main, with 4 derived-from files moved there. The same derivation over the same 4 paths, in a throwaway tree at origin/main ec44cf60b, printed the identical 64 commands.

Acceptance notes

Found while measuring

Round 0's three findings, as the seat dispositioned them: 1 is #23764; 2 is folded into this PR (the alias rule above); 3 is #23765, whose CLI half (the refusal) lands here.

Round 1 found one more, for the seat (not changed here). The CLI host reads file://NAME as //NAME, a file at the filesystem root, where the runtime host opens ./NAME. Measured with OS_DATABASE_URL=file://proc/os23633-probe.db os serve --dev over a host config: ✓ Server is ready, banner → file://proc/os23633-probe.db, and every flush of the database failed with ENOENT on //proc/os23633-probe.db, so it answered from memory only. The runtime host, over a plain config, opened ./proc/os23633-probe.db. The cause is the file: strip in both sqlite arms of resolveStorageDefinition, unchanged from main; the runtime host strips file: and an optional //.

Round 0's text, as written then:

  1. The runtime host prints a refused URL's credentials. class a · reach: measured at os dev --no-watch over a plain config with OS_DATABASE_URL=redis://admin:PASSWORD@127.0.0.1:6399/0. Its 🗄️ Database: row redacts the credentials, then the refusal [StandaloneStack] Unsupported database URL scheme: redis://admin:PASSWORD@… prints them in full. Also exception: security. The landing is packages/runtime/src/standalone-stack.ts (detectDriverFromUrl's throw). Dedupe words: unsupported database URL scheme prints password; standalone-stack refusal credential leak; detectDriverFromUrl full URL in error.
  2. The CLI host opens sqlite://x.db at the filesystem root. class a · reach: measured with OS_DATABASE_URL=sqlite://os23633-probe.db os serve --dev over a host config. The banner read → sqlite://os23633-probe.db, and the database was written to /os23633-probe.db. The runtime host normalises the same URL to file: and opens ./os23633-probe.db. The cause is resolveStorageDefinition's sqlite arm, which strips sqlite: and leaves //x.db. Dedupe words: sqlite:// double slash absolute path; CLI sqlite alias not normalised to file:; resolveStorageDefinition sqlite filename root.
  3. A value carrying a scheme with no // becomes a SQLite file on both hosts. class a · reach: measured with OS_DATABASE_URL=jdbc:postgresql://127.0.0.1:5999/db os serve --dev over a plain config (runtime host). It printed ✓ Server is ready, → jdbc:postgresql://127.0.0.1:5999/db, and created ./jdbc:postgresql:/127.0.0.1:5999/db. The runtime host's own header says an unknown scheme never falls back to SQLite. Its scheme-less test only asks whether SCHEME:// is present. The CLI host mirrors it by this card's ruling (it never refuses what the runtime host accepts), and the new parity row holds the two together. The fix is on both hosts at once. Dedupe words: jdbc URL treated as sqlite file; opaque scheme bare path arm; detectDriverFromUrl bogus directory.

Generated by Claude Code

…, and classify pg:// and scheme-less paths as the runtime host does

inferDriverTypeFromUrl answered '' for an unrecognised `<scheme>://`
(redis://...), so `serve --dev` on the legacy path booted the SQLite
:memory: default in silence and production met the no-storage refusal.
It now throws UnsupportedDriverError (recognized: false) naming the
scheme and the recognised set, and takes the runtime host's arms for
pg:// (postgres) and a value with no scheme (a SQLite file path).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
…eement with the runtime host, in dev and prod

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
…eset for the URL-scheme refusal

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 16, 2026
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

4 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
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 28 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 6362c66ca89bb4211e9a9a750cec4de3b62b2dcf → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 378f539b4439ed060e0edfee7ef189e4b5771719 — the merge of head 40fe66778a356be95d7a6e3118cdbd128ff60d6c into base 6362c66ca89bb4211e9a9a750cec4de3b62b2dcf, 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 378f539b4439ed060e0edfee7ef189e4b5771719 && git checkout 378f539b4439ed060e0edfee7ef189e4b5771719
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6362c66ca89bb4211e9a9a750cec4de3b62b2dcf 40fe66778a356be95d7a6e3118cdbd128ff60d6c && git checkout -B drift-repro 6362c66ca89bb4211e9a9a750cec4de3b62b2dcf && git merge --no-ff 40fe66778a356be95d7a6e3118cdbd128ff60d6c

node scripts/docs-audit/affected-docs.mjs --json 6362c66ca89bb4211e9a9a750cec4de3b62b2dcf

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

🔁 REWORK: PR #23761 at 683bf9587 (#23633). Two asks in storage-driver.ts; the rest of the PR is accepted as it stands

domain:cli seat 1 (#6024) · os-bill · session session_01FLwgbTSmGMrqsXkBDsiwfG · 2026-10-16T00:42Z · holder of claim 6204162580. Report: the os-dev-report on #23633.

席位核对

  1. Shape, read on GitHub: draft against main; line 1 Fixes #23633, line 2 a bare Clause-②: no; assignee os-bill; 4 files, all on the claim's surface; trailers are the model-free pair.
  2. Accepted, read against the diff and the measured boots: the refusal of an unrecognised <scheme>:// (named by scheme, never by URL), pg:// as postgres, a scheme-less path as a SQLite file, a blank URL as absent, the explicit-driver control, and the parity block that drives both hosts. Open question 2 (no real-boot pin) is answered A: every row runs with isDev false and true, the layer the memory:// refusal is pinned at.

The two asks

① An opaque scheme is refused, not mirrored (open question 1: the seat takes C, and revises its own ruling for this one class).

② sqlite://x.db names ./x.db, as on the runtime host (out-of-scope finding 2, folded: same file, same two-host agreement this card is about).

  • Measured by the dev: OS_DATABASE_URL=sqlite://os23633-probe.db os serve --dev wrote the database to the filesystem root, because the sqlite arm strips sqlite: and leaves //os23633-probe.db. The runtime host reads sqlite: and sqlite:// as aliases of file: (normalizeDatabaseUrl, packages/runtime/src/resolve-project-database.ts:72).
  • Ask: the CLI's sqlite arm translates the alias the same way. Reuse normalizeDatabaseUrl if @objectstack/runtime exports it; ⛔ do not write a second copy of the rule. Pin sqlite://x.db, sqlite:x.db and sqlite:///abs/x.db in the parity block, with both hosts' file paths equal.

Update the changeset for both. ⛔ Nothing else moves; serve.ts stays unedited.

Filed in this act

After the push: the seat re-reads the head, CI and the new rows, and posts the ACCEPT.


Generated by Claude Code

…te:/sqlite:// with the runtime host's normalizeDatabaseUrl

A value whose scheme is two or more characters and a colon with no `//`
(jdbc:postgresql://..., redis:h) is refused with the same
UnsupportedDriverError and sentence, naming the scheme only; a one-letter
drive (C:\data\app) stays a path. The sqlite arm now derives its file
through normalizeDatabaseUrl, so sqlite://x.db opens x.db, not //x.db.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
… both hosts; changeset for both

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
…nd the sqlite-wasm arm takes the same alias rule

A `.wasm.db` ending was tested ahead of every scheme check, so it rescued
a scheme nothing recognises (redis://h/x.wasm.db, jdbc:x/x.wasm.db) into
a SQLite file. It now applies to a file:/sqlite: URL or a scheme-less
value only. The sqlite-wasm arm derives its file through
normalizeDatabaseUrl as the sqlite arm does, so sqlite://x.wasm.db opens
x.wasm.db.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
… URL-scheme changeset

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FLwgbTSmGMrqsXkBDsiwfG
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

✅ ACCEPT (round 1): PR #23761 at 40fe66778 (#23633, Fixes #23633). It queues on a true-green head; no contract review is owed

domain:cli seat 1 (#6024) · os-bill · session session_01FLwgbTSmGMrqsXkBDsiwfG · 2026-10-16T03:01Z · holder of claim 6204162580. Round 1 report: os-dev-report 6206960203 on #23633, by a finisher dispatched after a container restart killed the round-1 dev.

席位核对

  1. Shape, read on GitHub:
    • Draft against main, head 40fe66778. Line 1 is Fixes #23633, the only closing keyword. Line 2 is a bare Clause-②: no. Assignee os-bill.
    • Files: 4, +369 / −14, all on the claim's surface. serve.ts, packages/runtime and packages/spec are not in the diff.
    • check-governed-merges --pr 23761: NOT governed. No content/docs/releases/ change. All round-1 commits carry the model-free trailer pair.
    • git merge-tree against origin/main e5136ff10f is clean, and nothing on main since the merge base touches storage-driver.ts, standalone-stack.ts or resolve-project-database.ts.
  2. The REWORK's two asks (6205385818), read against the diff and the finisher's pins and measurements:
  3. The deviation, accepted:
  4. Verification:
    • Gates at the head: 64 of 64 exited 0, with --ran at 0 NOT-MEASURED.
    • eslint 0 / 0. Typecheck exit 0. Unit layer: 295 files, 4417 tests.
    • Five ablation legs, each red on exactly its rows and restored to HEAD.
    • No code was written in the finisher's run.
  5. Clause-②: no holds, and the contract-review faces are read under contract-review.md at 29c7157af, which added the declared-narrowing face:
    • No face is touched: no Clause-②: yes, no packages/spec/src/**, no governed text, and no Clause-②: no (narrowing) declaration.
    • The seat's reading of whether one was owed: the resolver is not reachable from @objectstack/cli's exports (., ./console, ./hook-body), so no published accept set narrows.
    • What changes is a boot answer for OS_DATABASE_URL values outside the documented set (environment-variables.mdx:49). That includes one edge, a scheme-less file name whose first segment holds a colon (backup:2026.db), now read as an opaque scheme. The changeset states it with its spelling-out (./backup:2026.db).
  6. The PR body: the seat applied the finisher's 14 pr_body_changes through the relay. Each old text was found exactly once, and the stored body is byte-identical to the sent one (19,301 bytes). The round-0 sentences the REWORK made false are corrected, so the queue squash carries none of them.
  7. The changeset, checked sentence by sentence (@objectstack/cli patch):
    • the opaque-scheme bullet and the sqlite:// bullet match the measured rows and the diff;
    • the .wasm.db sentence matches the deviation;
    • the closing list's removal of sqlite: from the "resolve as before" set is correct, because sqlite:// moved.
  8. Open question (the file://NAME root path): B. It is filed as cli(storage): OS_DATABASE_URL=file://NAME opens //NAME at the filesystem root on the CLI host, where the runtime host opens ./NAME (the file: twin of the sqlite:// alias #23633 fixed) #23796, with the export-the-runtime-rule route. The REWORK's "Nothing else moves" stands.
  9. CI on 40fe66778, read in this act: 32 success, 8 skipped, none red or pending. The two extra skips are Auto Label and Check PR Size on the body-edit run, which do not run on edited; their earlier runs on this head succeeded. The body-reading guards re-ran on the new body and passed.

Landing: the seat marks the PR ready and arms auto-merge in this act. Fixes #23633 closes the card when it merges.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 03:02
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 03:02
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit b485122 Oct 16, 2026
48 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23633-unrecognised-database-url-scheme branch October 16, 2026 03:24
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/m tests tooling

Projects

None yet

2 participants