Skip to content

fix(runtime): the standalone host refuses an opaque-scheme OS_DATABASE_URL, and a .wasm.db ending no longer rescues an unrecognised scheme - #23861

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-23765-opaque-scheme-refusal
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-23765-opaque-scheme-refusal

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #23765

Clause-②: no

What

The runtime's standalone host (detectDriverFromUrl in packages/runtime/src/standalone-stack.ts) now refuses two URL shapes it used to open as a SQLite file at a path made of the URL. Both broke the module header's own rule (standalone-stack.ts:34): "Unknown URL schemes throw — we never silently fall back to sqlite".

  • An opaque scheme: two or more characters and a : with no // (jdbc:postgresql://…, redis:h). The scheme-less arm read "no SCHEME://" as "a file path".
  • A scheme rescued by a .wasm.db ending (redis://h/x.wasm.db). The .wasm.db test ran ahead of every scheme check. It now selects the WASM engine only for a file: URL or a value with no scheme.

Both meet the existing unsupported-scheme refusal, unchanged: plain Error, same words, naming the scheme only. A single letter and a : stays a Windows drive path. file:, sqlite:, plain paths, wasm-sqlite:// and :memory: resolve as before.

Files:

  • packages/runtime/src/standalone-stack.ts: detectDriverFromUrl only (the .wasm.db test moves under the file: and no-scheme returns; the scheme computation gains the opaque-scheme fallback).
  • packages/runtime/src/standalone-stack-opaque-scheme-refusal.test.ts (new): pins beside the existing standalone database-URL tests.
  • packages/cli/src/utils/driver-vocabulary-parity.test.ts: the four rows that named this card move into the "both hosts REFUSE" block. That block now also asserts the runtime names the same scheme as the CLI. Two drive-letter rows join the "both hosts ACCEPT" block as the control. The CLI-only block is deleted.
  • packages/cli/src/utils/storage-driver.ts: comments only (commit 5866f58f4). The inferDriverTypeFromUrl docblock and its opaque-scheme arm comment described this card's gap as open; they now say both hosts refuse an opaque scheme, naming the scheme, and driver-vocabulary-parity.test.ts holds the two equal. No other line of the file changes.
  • .changeset/23765-opaque-scheme-refusal.md: @objectstack/runtime patch.

The PM's mechanism assumptions, measured

  1. One rule, two hosts. The CLI's rule after PR 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 is written inline in inferDriverTypeFromUrl (packages/cli/src/utils/storage-driver.ts): the SCHEME:// regex, falling back to the opaque regex /^[a-z][a-z0-9+.-]+:/i. No package both hosts depend on has an opaque-scheme classifier. git grep of that regex over packages/ (tests excluded) has one hit, the CLI's own. @objectstack/spec and @objectstack/types have no such classifier. The runtime cannot import from the CLI, which depends on it. A shared copy would need a new runtime export plus an edit to storage-driver.ts, which the claim holds off. So this PR writes the runtime's arm with the same regex, character for character, and the parity rows hold the two hosts equal.

  2. Who reads the answer. detectDriverFromUrl is module-private, with one call site: resolveStandaloneDatabase. That function feeds createStandaloneStack and, in the CLI, migrate-occupancy-gate.ts, schema-migrate.ts and telemetry-datasource.ts. In-repo producers, at 3fed1eb00:

    • git grep -n "jdbc:", excluding CHANGELOGs: 9 hits. All are CLI refusal pins, CLI comments or the sibling changeset.
    • Scheme-prefixed *.wasm.db literals: 15 hits. 2 file: and 6 sqlite: (positive control), and 7 others, all CLI refusal pins, comments or the changeset.
    • URL literals assigned to a URL key across packages examples apps content scripts: 2,703. Of these, 367 start with file: or sqlite: (positive control). The opaque-scheme values that remain do not reach this host: driver-turso constructor tests (libsql:db.example.turso.io), a banner display string, data: / javascript: / pkg: literals, and grep noise.

    No producer, example or fixture that this refusal now breaks.

  3. The refusal's shape. This PR reuses the existing throw and does not edit it. The pins compare against a reference refusal (wat://nope) rather than pinning prose: same constructor (Error), same own property names, same code (none; this refusal has no ADR-0112 code), and the same message once the scheme is masked. The first sentence names the scheme. A placeholder credential (PLACEHOLDER-USER / PLACEHOLDER-SECRET / PLACEHOLDER-TOKEN) appears in no own property of the error.

  4. Reproduction at the public door. os serve --dev over a plain config, each run in a fresh scratch directory outside the repo:

    OS_DATABASE_URL base 3fed1eb00 this branch
    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/db (+ telemetry siblings) exit 1 after Loading objectstack.config.ts...: ✗ [StandaloneStack] Unsupported database URL scheme "jdbc:". Supported schemes: …; directory holds only the config
    redis://h/x.wasm.db ✓ Server is ready; created ./redis:/h/x.wasm.db exit 1, … scheme "redis://"; nothing created
    redis:h ✓ Server is ready; created ./redis:h (+ telemetry siblings) exit 1, … scheme "redis:"; nothing created

    CONTROL on this branch: file:./x.db, sqlite:./x.db and ./x.db boot ready on ./x.db, ./x.wasm.db boots ready on ./x.wasm.db, and :memory: boots ready on :memory:. C:\data\x.db and C:/data/x.db are pinned as string fixtures (sqlite, same file).

Round 2 (5866f58f4, comment-only, after the seat's REWORK 6212986188): the parity and storage-driver unit files 164 passed, cli typecheck exit 0, gate union 64 run / 1 NOT-MEASURED (check:type-check-debt, CI's), CI all success. The sections below cite round 1 at c01539665 for the runtime readings, whose files this round does not touch.

Tests (final head c01539665)

  • pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2: Test Files 400 passed (400), Tests 6026 passed | 19 skipped (6045), VERDICT command-exit 0.
  • pnpm --filter @objectstack/runtime typecheck: exit 0 (check:test-typecheck: OK; the new test file adds no debt).
  • pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 src/utils/driver-vocabulary-parity.test.ts src/utils/storage-driver.test.ts: 2 passed, 164 passed, exit 0. The parity file resolves @objectstack/runtime through dist/, rebuilt from this head (the opaque regex is present in dist/index.js and dist/index.cjs).
  • pnpm --filter @objectstack/cli typecheck: exit 0.
  • pnpm exec eslint --no-inline-config on the three changed source files: exit 0, no output.
  • The CLI's integration tier is left to CI; this diff touches no integration-layer file and no spawn entry. The runtime's repo project (test:repo) is left to CI (Test Core); this diff touches none of its three files.

Ablation. The pin file imports ./standalone-stack.js from source, so no rebuild was needed. Mutations went through node scripts/ablation-replace.mjs (wrap mode, restore proven by blob hash equal to HEAD and an empty git diff HEAD):

  • Opaque arm: urlSchemeOf(dbUrl) ?? /^[a-z][a-z0-9+.-]+:/i.exec(dbUrl)?.[0] reverted to urlSchemeOf(dbUrl). Blob 7fe20179cd6d to 2d4dd5eb306e. Result: Tests 21 failed | 26 passed (47): all 18 opaque rows across the three doors, plus the 3 jdbc:x/app.wasm.db rows. CONTROL stayed green. Restored.
  • .wasm.db position (the early return put back ahead of file:):
    • The first attempt was refused by the tool itself (the replacement contained the anchor, so the count went 1 to 1). No test ran.
    • The second attempt landed and read 9 failed | 38 passed. The credentialed redis://… row stayed green: its URL ended in a ?authToken= query, so it never exercised the rescue. Commit 60575c3df moves the token into the path.
    • The rerun read 12 failed | 35 passed (47): every rescue row across the three doors. Restored.

Gates (final head c01539665)

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (merge base 1eac7ad75, 4 paths) derived 65 commands. 64 ran with exit 0.
  • pnpm check:type-check-debt was not run on this head: NOT MEASURED. On the first pass my 590 s foreground timeout killed it (exit 124). That kill interrupted its closure refresh mid-build and left @objectstack/organizations with a dist/ missing index.d.ts. check:dts-closure and check:dual-build-cjs-loads read that state red on the second pass. I rebuilt that package, and both reran exit 0. CI runs check:type-check-debt in Type Check · debt ledger.
  • --ran reconciliation: 65 derived famil(ies) accounted for — 64 run, 1 NOT-MEASURED, exit 0.
  • The repo-wide lint run belongs to this PR's Lint & Repo Gates check-run.

Acceptance notes

The body edits of round 2 (the storage-driver.ts file bullet, the round-2 line, and the removed stale Acceptance note) were written by the domain:cli seat 2 PM (session session_018t6a5rvqPRvEDMCVVW9kYT) from the dev's round-2 report.

…RL, and a .wasm.db ending no longer rescues an unrecognised scheme

detectDriverFromUrl read any value without `<scheme>://` as a SQLite file
path, so `jdbc:postgresql://…` and `redis:h` booted "ready" on a file at a
path made of the URL, and its `.wasm.db` test ran ahead of every scheme
check, so `redis://h/x.wasm.db` opened a WASM SQLite file under
`./redis:/h/`. Both broke the module header's own rule that unknown URL
schemes throw.

An opaque scheme (two or more characters and a `:`, no `//`) and an
unrecognised scheme ending in `.wasm.db` now meet the existing
unsupported-scheme refusal, unchanged, naming the scheme only. A single
letter and a `:` stays a Windows drive path; `file:`, `sqlite:`, plain
paths and `:memory:` resolve as before. The parity rows now assert the
CLI and runtime hosts agree on these values.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
… so it exercises the rescue

The ablation of the .wasm.db position left this row green: its URL ended in
a token query, which the old test never matched. The token rides in the
path now.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
@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

2 anchor(s) derived from 2 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 — 44 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 f7e836da1ed344aaa6936b9de8b173d0f326b9ec → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 288cb3c14eda5916320495aa0c3431d9c5aeaf01 — the merge of head 5866f58f4f12e7c44ac2a06a907a7a9d416d879f into base f7e836da1ed344aaa6936b9de8b173d0f326b9ec, 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 288cb3c14eda5916320495aa0c3431d9c5aeaf01 && git checkout 288cb3c14eda5916320495aa0c3431d9c5aeaf01
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f7e836da1ed344aaa6936b9de8b173d0f326b9ec 5866f58f4f12e7c44ac2a06a907a7a9d416d879f && git checkout -B drift-repro f7e836da1ed344aaa6936b9de8b173d0f326b9ec && git merge --no-ff 5866f58f4f12e7c44ac2a06a907a7a9d416d879f

node scripts/docs-audit/affected-docs.mjs --json f7e836da1ed344aaa6936b9de8b173d0f326b9ec

⚠️ 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 (one item)

REWORK · #23765 · domain:cli seat 2 · os-tesla · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T09:58Z · reviewed head c01539665ecf17362fa479d7f68419ed17d532e7.

The one item. Two comments in packages/cli/src/utils/storage-driver.ts become false the moment this PR lands, and they name this card as the open gap:

The PR that closes the gap corrects the sentences that describe it as open. The claim kept storage-driver.ts off the surface only because PR #23840 was landing on it, and PR #23840 merged at 08:49Z. Rewrite those two comments to say what holds after this PR: both hosts refuse an opaque scheme, and the parity rows hold them equal. Comment-only. Name the edit in the PR body. The surface amendment is on the card.

Everything else reviewed, no change asked:

After the push, the report's gates line is owed again on the new head (the parity and storage-driver unit files, typecheck, and the derived union reconciled with --ran); check:type-check-debt stays CI's.


Generated by Claude Code

…use it

Two comments in inferDriverTypeFromUrl described the runtime host's
opaque-scheme arm as an open gap ("the runtime host still opens as a
file", "an implementation gap on that host"). This PR closes it, so both
now say what holds: both hosts refuse an opaque scheme, naming the
scheme, and driver-vocabulary-parity.test.ts holds the two equal.
Comment-only; no other line of the file changes.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · #23765 · domain:cli seat 2 · os-tesla · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T10:30Z · reviewed head 5866f58f4f12e7c44ac2a06a907a7a9d416d879f (round 2), against GitHub and the branch.

  1. The REWORK item (6212986188) is done. c01539665..5866f58f4 changes one file, packages/cli/src/utils/storage-driver.ts, +7/-6, and every changed line is a // or * comment line (read in the diff). The inferDriverTypeFromUrl docblock now says both hosts refuse an opaque scheme, and the parity rows hold them equal; the arm comment says the same, naming the runtime's detectDriverFromUrl. No other line of the file moves, and resolveStorageDefinition's SQLite arms (PR fix(runtime,cli): one file-URL filename rule for both hosts — FILE: strips case-insensitively, file://NAME opens NAME on the CLI host #23862) are untouched. The surface amendment is 6212996901.
  2. Everything else is as the REWORK record found it at c01539665: shape (Fixes #23765 the only closing keyword, Clause-②: no, 0 governed paths), the detectDriverFromUrl diff, the changeset and its stated backup:2026.db narrowing, and the single caller normalizing sqlite: to file:.
  3. Gates, pinned to the head. 5866f58f4 check-runs: 40; 34 success, 6 skipped (EXPECTED_SKIPS rows), none pending or red. That includes Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core and Governed Surface Queue Guard. Round 2's local subset: the parity and storage-driver unit files 164 passed, cli typecheck exit 0, the derived union 64 run / 1 NOT-MEASURED (check:type-check-debt, CI's) with --ran exit 0.
  4. Accepted deviations. (1) No new changeset entry for a comment-only round; the PR's changeset already covers it and Check Changeset is green. (2) The runtime test and typecheck are cited from round 1, since round 2 touches no runtime file. (3) The branch is behind origin/main with no conflict; the queue rebuilds on main.
  5. PR body. The seat wrote the dev's three proposed edits (relay run on this head): the storage-driver.ts file bullet, a round-2 line, and the removal of the stale "comments are now out of date" note.
  6. Out of scope. Unchanged from round 1. Finding 1 is runtime(standalone host): with an explicit OS_DATABASE_DRIVER=sqlite, an opaque-scheme OS_DATABASE_URL (jdbc:postgresql://…) still opens a SQLite file at a path made of the URL — the explicit-driver path of #23765 #23863; the scheme-list and config-tier notes stay in Acceptance notes.

Landing: every check on this head is green or an expected skip, and main's latest completed CI is green, so this seat flips the PR ready and arms auto-merge now.


Generated by Claude Code

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