Skip to content

fix(driver-turso): the remote face refuses a missing table or column with the local face's code instead of answering [] - #20461

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-20424-turso-remote-invented-empty
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/issue-20424-turso-remote-invented-empty

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #20424

Clause-②: no (narrowing)

Seat ruling: the amended claim 5872640432 on #20424 rules this change an accept-set narrowing: Clause-②: no (narrowing), @objectstack/driver-turso minor with the BREAKING banner, and the ADR-0087 disposition not-required (already-registered driver-sql-unresolvable-where-column-refused).

Summary

On the Turso remote face, aggregate, find, findOne and count now answer a missing table or a missing column the way the local face of the same driver answers it over the same file: DATABASE_ERROR / 500, INVALID_FIELD / 400 or INVALID_FILTER / 400, and never "no rows". Two catches in RemoteTransport read the backend's no such table / no such column as []. They now let the error out, and TursoDriver's remote read exits classify it with the local face's inherited seam, SqlDriver.aggregateBackendFault. No code is minted, nothing in driver-sql or spec is edited, and no classification is copied.

Measured head: daad09aa1 (this branch after merging origin/main at 3cf644938). Patch round 1 changed only the changeset and this body, then merged origin/main at 2304b1608: head 3b56eb769, with packages/drivers byte-identical to daad09aa1 (see "Patch round 1" below).

H1: reproduced on main before any change

Base 6e3e5462c. The remote face is a TursoDriver over a real @libsql/client on a file: database, and the local control is a TursoDriver over the same file (a throwaway probe, not committed). "Declared field, column absent" is a field the object declares and the table lacks.

read local remote
row 1: aggregate, mapped table really absent DATABASE_ERROR / 500 []
row 2: aggregate grouped by a declared field, column absent INVALID_FIELD / 400 []
row 3: find whose where names that field INVALID_FILTER / 400 []
findOne, the same where INVALID_FILTER / 400 null
find with a projection and that where INVALID_FILTER / 400 []
count, the same where INVALID_FILTER / 400 DATABASE_ERROR / 500
aggregate summing that field INVALID_FIELD / 400 []
aggregate whose where names that field INVALID_FILTER / 400 []
find ordered by that field the rows, unordered []
find projecting that field the rows the rows
find, mapped table absent DATABASE_ERROR / 500 DATABASE_ERROR / 500
controls: a real table and column the rows the rows

H1 holds for all three card rows. H4 holds too: rows 1 to 3 answered the same way for a managed object whose synced table or column was dropped under it (aggregate [], aggregate grouped [], find [], where the local face refused with 500 / 400 / 400).

H2: where the [] came from, and why each catch existed

H3: the seam. The mechanism is falsified; the seam is reachable

The hypothesis was a field check that decides before the statement runs. The local face has none for this condition: the ingress field checks pass because the field is declared. The local face decides after the statement runs, from the backend's error. count and findRows send an unresolvable column to unresolvableFilterColumnRefusal and everything else to backendStatementFault. aggregate goes through aggregateBackendFault, which attributes the column to the groupBy, the aggregation or the where from the caller's own query.

All of those compositions are protected on SqlDriver, and TursoDriver inherits them. The class predicate they share, isUnresolvableColumnError, is a module function that @objectstack/driver-sql does not export. So:

  • The remote aggregate exit calls aggregateBackendFault(object, query, error) with the caller's own query. That is the local exit verbatim.
  • The remote find / findOne / count exits call the same method with the where alone. With no groupBy and no aggregation, arm 1 cannot fire. Arm 2 is unresolvableFilterColumnRefusal(object, error, where), and the terminal is backendStatementFault, which is the local exit. The two can differ only on a recognised wording whose column name does not parse, and that cannot arise on libSQL, whose only wording is no such column: NAME.
  • A remote door compiles and executes inside one transport call, while the local face guards only the execution. So the new remoteReadFault first returns anything that already declares a numeric status unchanged. That is the same "is it already ours" gate backendStatementFault applies, so the classifier sees what the local one sees. The transport's own compile refusals and the timeout envelope keep their answers.

The where handed over is the caller's own, from before toRemoteFilter rewrote it, so the read-scope provenance marks decide whether the column is named, exactly as they do locally.

What changes

  • packages/drivers/driver-turso/src/remote-transport.ts:
    • aggregate: the catch is removed, and the backend's error leaves the method.
    • find: the ladder has the local face's two rungs, the projection and then the ORDER BY (the new one), each rebuilt with the caller's where, which neither drops. The terminal throws the last rung's error instead of answering [].
  • packages/drivers/driver-turso/src/turso-driver.ts:
    • remoteReadExit(object, query, read) ends in a new private remoteReadFault: the status gate, then aggregateBackendFault.
    • The remote aggregate arm now goes through that exit. find, findOne and count pass the caller's where.
    • One sentence in refuseRemoteColumnMap's docblock moves to the past tense: the [] it described is now a refusal, and the 501 refusal stays. A parenthetical sentence is also added after it, saying so: that read is now refused INVALID_FILTER / 400, and the 501 refusal stays the answer.
  • No change to RemoteTransport's or TursoDriver's published signatures. The constructor (PR fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447) and the write doors are untouched.

Bounded in-place fixes, declared

  1. count's remote exit. For the same where, local answers INVALID_FILTER / 400 and remote answers DATABASE_ERROR / 500. This is not one of the card's three rows. It shares remoteReadExit with find, and leaving it would reopen the split driver-sql: one unresolvable WHERE column, two answers — find() silently returns [] while count() throws a raw dialect error with no ADR-0112 envelope #8790 ruled out: a list view calls both halves. ① Same class: a remote read exit answers an unresolvable where column differently from the local face. ② Mechanical, with a pinned shape: the local seam, called. ③ Inside the claimed surface (the remote read arms of turso-driver.ts), and no other claim holds those lines. ④ Same package tests.
  2. The ORDER BY rung. Without it, removing the terminal [] turns "ordered by a column the table lacks" from [] into an INVALID_FILTER refusal that tells the caller their filter was wrong (measured, ablation leg D below). The local face answers the rows, unordered (fix(sharing): 共享规则新建页 — 自定义 widget 未国际化,且「接收方」永远无可选项 #3821). The registered migration entry driver-sql-unresolvable-where-column-refused already says the ladder drops an ORDER BY and keeps the rows. ① The same return [] line. ② Mechanical: the local ladder's second rung. ③ The claimed catch, in the claimed file. ④ Same package tests.

Tests

  • New turso-local-remote-missing-table-column-parity.test.ts: 22 cases over a real @libsql/client file: database, with a local driver over the same file.
    • The card's three rows as local-and-remote pairs, for a federated and a managed object (6 cases). Each asserts code + status on both faces.
    • The neighbours: findOne, find with projection and where, count (federated and managed), sum over the field, aggregate with that where, and ORDER BY and projection recoveries answering the literal rows on both faces (8 cases).
    • Controls: literal answers for real tables and columns on both faces (6 cases). An undeclared aggregate function is refused with the same envelope on both faces, with no statement sent. A synthetic already-enveloped refusal passes the remote exit unchanged.
  • remote-transport-unknown-select.test.ts: the [] pin is replaced as described under H2.
  • At the measured head daad09aa1: pnpm --filter @objectstack/driver-turso exec vitest run --maxWorkers=2 gave Test Files 75 passed (75) and Tests 2003 passed | 16 skipped (2019). pnpm --filter @objectstack/driver-turso typecheck exited 0, and tsc --listFiles includes both test files. Run after rebuilding the dependency closure on the merged tree.

Reverse verification

The fix was committed first (a10647239). Every leg went through node scripts/ablation-replace.mjs: the anchor hit 1 → 0, the blob changed, and the restore was proven by blob == HEAD and an empty git diff HEAD. An outer trap re-verified the restore against the HEAD blob. The subject resolves from src/ through a relative import (vitest, no alias), so no dist/ is in the path. Suites: the new parity file, the unit file and the #20107 external-object parity file (79 cases). Directions were predicted in the test header before running:

  • A, aggregate's [] catch restored: 6 failed / 73 passed, exactly the six aggregate pairs.
  • B, the find terminal back to return []: 5 failed / 74 passed. That is the find, findOne and find-with-projection pairs, plus the replaced unit pin. count, aggregate and ORDER BY stayed green.
  • C, remoteReadFault reduced to backendStatementFault: 10 failed / 69 passed, every 400 pair. Both missing-table pairs stayed green, since they answer 500 on both faces anyway.
  • D, the ORDER BY rung deleted: 1 failed / 78 passed, the ORDER BY case. The remote face refused with the unnamed INVALID_FILTER wording ("A filter on object 'ext_t' names a column the database could not resolve …") where the local face answered the rows.
  • E, the status gate deleted: 1 failed / 78 passed, the synthetic case, re-worded as INVALID_FIELD / 400. The real compile-refusal control stayed green, as predicted: no real refusal text parses as a backend column fault today.

A first harness dry run was refused by the tool itself (its replacement contained the anchor, so the anchor count did not drop), and it restored. Nothing was measured there.

Gates at daad09aa1

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 63 commands (5 paths vs merge base 3cf644938). All 63 were run, and each exit code was written to a file before any pipe.

Three gates first answered PREREQUISITE NOT MET (exit 3): check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt. I built every package with pnpm turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2: 71 of 71 tasks succeeded. All three then exited 0. The dist-sweeping check:dts-closure (71 packages), check:sourcemap-no-sources-content (68) and check:published-files were re-run on the full build and exited 0.

--ran answered: 63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED (a DERIVED zero — all 63 recorded an exit code and none of them is 3).

Driver conformance ledger:

  • Before, at 6e3e5462c: OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt.
  • After, at daad09aa1: OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt.
  • The driver-turso row is ok in every column at both readings.

Lint, narrowed: eslint --no-inline-config --format json over the 4 changed .ts files gave 4 files, 0 errors and 0 warnings.

  • The population comes from eslint.config.mjs: **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} minus its ignores.
  • The file count is from the JSON.
  • The effective parserOptions are {ecmaVersion: latest, sourceType: module}, with no project. All 6 active rules are per-file AST rules, so type-aware linting is off and this diff cannot move a verdict on any untouched file. The full pnpm lint is CI's.

Clause-② and the changeset

.changeset/20424-turso-remote-missing-table-column-refused.md ships @objectstack/driver-turso minor, with Clause-②: no (narrowing), the BREAKING banner in the launch-window form (check-changeset-no-major refuses major), a FROM → TO line with the fix, and the ADR-0087 disposition not-required (already-registered driver-sql-unresolvable-where-column-refused). This follows the seat's ruling in the amended claim 5872640432. The precedent the ruling reads:

Files

  • .changeset/20424-turso-remote-missing-table-column-refused.md (+33 / -0)
  • packages/drivers/driver-turso/src/remote-transport.ts (+45 / -22)
  • packages/drivers/driver-turso/src/turso-driver.ts (+90 / -15)
  • packages/drivers/driver-turso/src/remote-transport-unknown-select.test.ts (+15 / -4)
  • packages/drivers/driver-turso/src/turso-local-remote-missing-table-column-parity.test.ts (+345 / -0)
  • packages/drivers/driver-turso/src/turso-local-remote-external-object-parity.test.ts (+3 / -2, a header prediction line only)

In total, +531 / -43 over 6 files, under the 5,000-line threshold. No governed surface.

Patch round 1

The seat's amended claim 5872640432 answered the round-0 open question with B, and accepted the two bounded in-place fixes and the H3 route. This round changes only the changeset and this body, with no code or test change:

  • The changeset: patch → minor; Clause-②: no → Clause-②: no (narrowing); the BREAKING banner; the ADR-0087 marker not-required (already-registered driver-sql-unresolvable-where-column-refused) with its reason; a FROM → TO line naming the refusals, the fix, and RemoteTransport.find / aggregate now raising where they answered [].
  • This body: the Clause-② line, the seat-ruling line under it, the "Clause-② and the changeset" section, the Files line and one Acceptance note.
  • origin/main merged at 2304b1608 (a true merge commit, five incoming commits, none on a driver-* path). Head 3b56eb769. git diff daad09aa1 3b56eb769 -- packages/drivers is empty. After rebuilding the dependency closure on the merged tree: pnpm --filter @objectstack/driver-turso exec vitest run --maxWorkers=2 gave Test Files 75 passed (75) and Tests 2003 passed | 16 skipped (2019), and typecheck exited 0.
  • Changeset gates at 3b56eb769, each exit code recorded before any pipe: pnpm check:adr-0087-registration exit 0; node scripts/check-changeset-no-major.mjs --base origin/main --event (this body) exit 0; node scripts/check-empty-changeset.mjs --base origin/main exit 0.

Patch round 2

After the at-tier review 5873239611 (PASS at 3b56eb769) and the seat's amended claim 5873267997, this round is text only plus a merge, with no logic or test-assertion change:

Acceptance notes

  • The shared predicate is not exported. isUnresolvableColumnError is not exported from @objectstack/driver-sql, so the remote find/count exit reaches it through aggregateBackendFault with a where-only query. An exported predicate, or a protected where-exit on SqlDriver shared by count and findRows, would be the plainer seam. This card may not edit driver-sql. Carrier: none.
  • The transport keeps an inline recognizer. RemoteTransport.find still recognises the unresolvable-column class inline (no such column, or column + does not exist) to gate its ladder. That pre-existing copy of the predicate lacks the MySQL arm, which libSQL never speaks.
  • An unmeasured edge. If a ladder rung fails with an error that is not a column error, the local face refuses INVALID_FILTER (unnamed wording) and this face answers DATABASE_ERROR / 500. No producer is known.
  • Standalone transport use. RemoteTransport.find and RemoteTransport.aggregate, used on their own without TursoDriver, now raise the backend's error where they answered []. The changeset says so.
  • main moved after the measured merge. Round 0 did not re-merge b28550818 (additive in packages/spec only). Patch round 1 merged origin/main at 2304b1608 (five commits, none on a driver-* path). It then moved once more, to fbeb56e4b (one packages/spec test pin, turbo.json and scripts/cross-package-test-inputs.mjs, no driver-* path). Patch round 2 merged origin/main at acd009521, which carries both. PR fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447 (driver-turso: new TursoDriver accepts syncUrl / sync under mode: 'remote' and ignores them — isSyncEnabled() answers true, no sync runs, and sync() rejects SYNC_NOT_SUPPORTED #20200) landed as bea6d2ea3, is merged here, and driver-turso was re-tested on the combined tree (see "Patch round 2").

Generated by Claude Code

…with the local face's code instead of answering []

RemoteTransport.aggregate's catch answered `no such table` / `no such column`
with [], and the terminal of find's projection backstop answered `no such
column` with []. The transport now lets the backend's error out (find keeps
the local ladder's projection and ORDER BY rungs first), and TursoDriver's
remote read exits classify it with the local face's inherited seam,
SqlDriver.aggregateBackendFault: INVALID_FIELD / 400, INVALID_FILTER / 400 or
DATABASE_ERROR / 500, as the local face answers over the same file.

Claude-Session: https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 28, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-turso, touching 5 documentable anchor(s).

15 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/data-flow.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/automation/hook-bodies.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/automation/webhooks.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/data-modeling/drivers.mdx (via TursoDriver (symbol, a top-level class))
  • content/docs/kernel/contracts/data-engine.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/kernel/contracts/index.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/kernel/events.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/permissions/attachments-access.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/permissions/field-level-security.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/permissions/record-view-auditing.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/permissions/rls.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/permissions/system-context.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/plugins/packages.mdx (via TursoDriver (symbol, a top-level class))
  • content/docs/protocol/objectql/schema.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/ui/react-pages.mdx (via findOne (symbol, a method of class TursoDriver))

⛔ 5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/releases/v16.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/releases/v17/17-0.mdx (via findOne (symbol, a method of class TursoDriver))
  • content/docs/releases/v17/17-5.mdx (via TursoDriver (symbol, a top-level class), findOne (symbol, a method of class TursoDriver))
  • content/docs/releases/v17/index.mdx (via findOne (symbol, a method of class TursoDriver))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 7 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 acd009521e6e8e4d3cb9d6df43d84aa53c44b4b3 → packageMentionDocs.

Which tree this was computed on

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

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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs acd009521e6e8e4d3cb9d6df43d84aa53c44b4b3 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…NG banner and ADR-0087 disposition

The seat's amended claim rules the remote-face refusal an accept-set
narrowing: minor under the launch-window convention, the BREAKING banner,
a FROM -> TO line, and the ADR-0087 disposition not-required
(already-registered driver-sql-unresolvable-where-column-refused).

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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 3b56eb769755f0f364fb96a1c4578af91c25b304
Local-runs: none

Inputs read: card #20424 (body and all 5 comments, including the amended claim 5872640432 and both os-dev reports), PR #20461 (body, file list, the one docs-drift comment, no reviews), the net diff against the merge base 2304b1608 (5 files, +522 / -36, identical to the diff against the fork point), #8790 (body and 8 comments), the migration entry 18.driver-sql-unresolvable-where-column-refused.ts, SqlDriver's seams at head, @libsql/client and @libsql/core 0.17.4 as installed, the docs pages the drift bot lists plus every git grep hit, and the check-runs on the head read twice (15:07Z, then 15:28Z as the last step). Read-only git and REST GETs only; nothing built, run or re-run.

① Derived judgments

Every accept-set and public-surface change the diff implies, each named:

  1. Remote aggregate, find, findOne: [] / null to a refusal — an accept-set NARROWING, right and stated. The base code confirms the "remote before" column: RemoteTransport.aggregate's catch answered [] for no such table and no such column; RemoteTransport.find's backstop answered [] on the no-rung path and on a failed projection retry, so findOne answered null. At head both catches are gone; the error leaves the transport and TursoDriver.remoteReadExit classifies it through the new private remoteReadFault into the inherited, protected SqlDriver.aggregateBackendFault. Right.
  2. Parity, exit by exit (judge item 1) — holds. aggregate calls the local exit verbatim with the caller's own query (groupBy / aggregation names, and the where from before toRemoteFilter, so the read-scope provenance marks decide disclosure exactly as locally). find / findOne / count hand { where } alone: arm 1 cannot fire, arm 2 is unresolvableFilterColumnRefusal(object, error, where), the terminal is backendStatementFault — the local count / findRows exits, read at sql-driver.ts:7228-7290 and :9688-9692. A missing table takes the same backendStatementFault road on both faces (500). The ORDER BY and projection recoveries hold: RemoteTransport.find's ladder now has the projection rung then the ORDER BY rung, each rebuilt with the WHERE, in SqlDriver.findRows' order; the remote ORDER BY rung fires on remoteQuery.orderBy, which toRemoteReadQuery builds from the same inherited orderKeysFor the local ladder's orderKeys comes from, so the two rungs fire on the same queries. The one divergence is the dev's own: a recognised wording whose name does not parse, or a rung failing with a non-column error — local INVALID_FILTER unnamed, remote DATABASE_ERROR / 500 — unreachable on SQLite's no such column: NAME wording, and carried as an Acceptance note (③ item 4). The 22-case parity suite pins every row of the table on both faces over one libSQL file, for a federated and a managed object; the driver-turso remote: a federated object's external.remoteName is ignored — remote find queries a table named after the object and throws a bare LibsqlError (no code, no status), while the local face reads the mapped table #20107 suite's missing-table pairs for find / findOne / count still stand (aggregate was never in its EXITS list).
  3. The status gate (judge item 2) — cannot pass a driver error through. remoteReadFault returns unchanged only an error whose status is a number. LibsqlError (@libsql/core 0.17.4) declares code, extendedCode, rawCode and cause — no status; mapHranaError and mapSqliteError wrap every backend error in it, and hrana's HttpServerError.status rides only under cause, which the gate does not read. The numeric-status producers on the remote read path are the transport's own compile refusals (400 at remote-transport.ts:167/669/767/935/1001, 501 at :974/1061/1095) and the driver's remoteOperationTimedOut (TIMEOUT / 504) — all ADR-0112 envelopes. The gate is the same declared-status test backendStatementFault applies (sql-driver.ts:9650), so the classifier sees what the local one sees. Right; the synthetic pin and ablation E hold it.
  4. Remote count: DATABASE_ERROR / 500 to INVALID_FILTER / 400 for a where column — a refusal re-coded, not a narrowing (the driver-sql: an unresolvable WHERE column on aggregate() answers DATABASE_ERROR/500 where find() and count() answer INVALID_FILTER/400 — the #8790 refusal never reached the third read door #11541 shape). Before, count already went through remoteReadExit to backendStatementFault. Stated in the changeset's table. Right, and inside the seat's accepted bounded fix 1.
  5. Remote find ordered by a column the table lacks: [] to the rows, unordered — the fix(sharing): 共享规则新建页 — 自定义 widget 未国际化,且「接收方」永远无可选项 #3821 recovery, a widening of what is ANSWERED. Stated in the changeset's table and ladder bullet. Right, and the seat's accepted bounded fix 2. It is not a Clause-② yes: no key and no authorable surface widens; the driver-sql: MySQL's unresolvable-column wording is matched by neither arm of the recovery/refusal predicate, so MySQL gets no ADR-0112 envelope and no #3821 recovery #8926 precedent (17.1.0) shipped exactly this recovery direction alongside the narrowing under one already-registered disposition.
  6. RemoteTransport.find and RemoteTransport.aggregate, exported from the package root (judge item 4) — stated. index.ts exports RemoteTransport; the changeset's FROM → TO line and its last bullet both say the two now raise the backend's error where they answered []. One precision the changeset does not spell for the standalone user: RemoteTransport.find on its own also answers rows (not []) for an ORDER BY on a missing column — covered by the ladder bullet in substance, not named for the standalone case. Not a defect.
  7. No published signature moves. remoteReadExit gains a query parameter, but it is private; RemoteTransport.find / findOne / count / aggregate keep their signatures; the constructor (driver-turso: new TursoDriver accepts syncUrl / sync under mode: 'remote' and ignores them — isSyncEnabled() answers true, no sync runs, and sync() rejects SYNC_NOT_SUPPORTED #20200) and the write doors are untouched. refuseRemoteColumnMap's 501 refusal stands; its docblock moves one sentence to the past tense AND adds one parenthetical sentence (the PR body names only the first). Prose only. Right.
  8. driver-sql and spec untouched; no second copy of a classification. The one pre-existing copy is the transport's inline ladder gate (no such column, or column + does not exist), which decides whether to retry, not what to refuse; it predates the card. Right, and carried as an Acceptance note.
  9. The migration marker's honesty (judge item 3). The where legs (find / findOne / count, and aggregate's where arm, all INVALID_FILTER / 400) sit squarely inside driver-sql-unresolvable-where-column-refused: its surface names TursoDriver by name, the doors, the code, and the remedy (a real column, or schema sync). The aggregate legs share the drifted-schema CAUSE and the REMEDY family (schema sync creates the column, or the table), which is the seat's ruling and the marker's why, and it is true. But the entry's surface names a where column and INVALID_FILTER only, and its acceptanceCriteria tell an upgrader to look for INVALID_FILTER messages on reads and counts — a reader following that criterion would not check a dashboard's groupBy or aggregation field (INVALID_FIELD / 400) nor an aggregate over a dropped table (DATABASE_ERROR / 500). On the local face those legs were never a narrowing (driver-sql: an unresolvable WHERE column on aggregate() answers DATABASE_ERROR/500 where find() and count() answer INVALID_FILTER/400 — the #8790 refusal never reached the third read door #11541 was a 500-to-400 re-code, patch); the remote face is the first place aggregate narrows from [], so no registered text anywhere names them. The disposition is the gate-accepted form and the 17.1.0 precedent's form; the precedent paired it with a follow-up spec PR amending the entry's text. Escalated in ③ item 2 — a text amendment to the entry (the door, the two codes, the table remedy), which the claim forbade this PR to make.
  10. Docs (judge item 6). All 15 hand-written pages were searched for remote-Turso, RemoteTransport, missing-table / missing-column and INVALID_FILTER passages. data-modeling/drivers.mdx and plugins/packages.mdx carry no passage on remote-face read behaviour; packages.mdx:198 ("extends SqlDriver, so all … filtering and aggregation logic is inherited") is closer to true after this PR than before. automation/hook-bodies.mdx:181 (a SQL remote aborts a WRITE with an untyped no such column) is about the write doors, untouched — TRUE. The 12 pages listed only through the generic findOne hold no relevant passage. Outside the bot's list: data-modeling/queries.mdx:307-313 (the projection recovery on a dialect-recognised error) stays TRUE on both faces; references/api/sortability.mdx:124/143 (an ORDER BY on an external table's missing column is silently dropped under a 200) was FALSE on the remote face and this PR makes it TRUE; deployment/validating-metadata.mdx:380-382 ("the driver's 'no such column' is swallowed, and the list comes back empty") has been FALSE for the local SQL faces since driver-sql: one unresolvable WHERE column, two answers — find() silently returns [] while count() throws a raw dialect error with no ADR-0112 envelope #8790 and this PR removes the last face on which it was true — pre-existing drift this PR completes rather than creates, escalated in ③ item 6. The five release-owned pages hold nothing on this behaviour. This PR makes no page FALSE.
  11. PR-body and changeset sentences (judge item 5). Every contract sentence is TRUE: the H1 "remote before" column is derivable from the base code; H3's mechanism is as read at head; the precedents are as the driver-sql CHANGELOG records them (driver-sql: one unresolvable WHERE column, two answers — find() silently returns [] while count() throws a raw dialect error with no ADR-0112 envelope #8790 in 17.1.0: BREAKING accept-set narrowing, minor, registered; driver-sql: an unresolvable WHERE column on aggregate() answers DATABASE_ERROR/500 where find() and count() answer INVALID_FILTER/400 — the #8790 refusal never reached the third read door #11541 in 17.3.0 under Patch Changes, no disposition); isUnresolvableColumnError is exported by sql-driver.ts but not re-exported by index.ts, and package.json exposes only . — so "not exported from @objectstack/driver-sql" is TRUE at the package boundary; the merge facts hold (a10647239 on 6e3e5462c; daad09aa1 = b635e43f1 + 3cf644938; 3b56eb769 = c9c3293c8 + 2304b1608, five incoming commits, none on packages/drivers; packages/drivers byte-identical between the two heads); fbeb56e4b is the four-file spec pin it says; the file list and +522 / -36 match the API; 22 cases counted. Precisions, none contract-bearing: (a) H2's "arrived with the migration from the cloud repository (06ba03627)" is TRUE of the $select backstop — the retry and its list-view comment first appear in this repo's history at 06ba03627 — while the bare no such column → [] catch it wraps is older (b4b39792b, 2026-03-23, in packages/plugins/driver-turso, extracted at dc721729a); the conclusion (nothing to keep beyond the projection case) is unaffected. (b) "One sentence in refuseRemoteColumnMap's docblock moves to the past tense" — a second sentence was also added. (c) "PR fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447 had not landed at any reading" was TRUE at the round-1 report (15:14:20Z) and is superseded: fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447 merged at 15:16:42Z as bea6d2ea3 (③ item 1). The dev's gate, lint and ablation numbers are attested, not re-run; the check-runs below are the gate verdicts.

② Semver level

.changeset/20424-turso-remote-missing-table-column-refused.md: "@objectstack/driver-turso": minor, the only package the diff moves. Clause-②: no (narrowing) — right: nothing authorable or keyed widens, and the remote face's accept set narrows ([] / null to a refusal); the ORDER BY recovery is a runtime answer widening in the #8926 sense, not a clause-② widening. The BREAKING banner is in the launch-window form the repo's narrowing changesets use (check-changeset-no-major refuses major). The ADR-0087 marker not-required (already-registered driver-sql-unresolvable-where-column-refused) resolves at head and at base (registered in 17.1.0), is the exact form the #8926 changeset used for a reach extension of the same entry, and passed Check Changeset (which runs check-empty-changeset, check-adr-0087-registration and check-changeset-no-major against the merge base) twice on this head. The FROM → TO line names every leg (500 / 400 / 400), the fix, and the root-export change. Matches the seat's ruling in the amended claim 5872640432 and what the diff publishes. Level: right. The Clause-②: line: right.

③ Boundary flags

  1. PR fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447 landed after this head's runs started (escalated to the seat). bea6d2ea3 ("fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode…", merged 15:16:42Z) moves packages/drivers/driver-turso/src/turso-driver.ts (+87 / -2, the constructor and its mirror schema), two turso test files, spec/turso.zod.ts and the spec migration registry; this head's base is still 2304b1608, so every check-run below measured a merge without it. GitHub reports the merge clean; the constructor and the read arms are disjoint regions, so a textual conflict is ruled out and a semantic one is unlikely — but the combined tree is unmeasured until a re-merge or the merge-queue run. The PR body's Acceptance note on fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447 is stale as of 15:16Z.
  2. The migration entry's text (judge item 3) — escalated as a follow-up spec card. The marker's why is truthful about cause and remedy; the entry's surface and acceptanceCriteria still name only a where column and INVALID_FILTER. Owed, the way driver-sql: MySQL's unresolvable-column wording is matched by neither arm of the recovery/refusal predicate, so MySQL gets no ADR-0112 envelope and no #3821 recovery #8926's addendum was: amend 18.driver-sql-unresolvable-where-column-refused.ts to name the aggregate door, INVALID_FIELD / 400 for a groupBy or aggregation column, DATABASE_ERROR / 500 for a table that is absent, and "or the object its table" in the remedy — a packages/spec edit outside this claim's surface (⛔ Not packages/spec). Carrier: a spec card from this seat; the seat may instead rule that no amendment is owed, in which case this flag closes on that ruling.
  3. Live libsql:// wire not exercised (unmeasured). The parity suite drives the remote CODE PATH (a libsql:// driver URL with an injected file: client), as driver-turso remote: a federated object's external.remoteName is ignored — remote find queries a table named after the object and throws a bare LibsqlError (no code, no status), while the local face reads the mapped table #20107's suite does; the assumption that sqld's HTTP response text still reads no such column: NAME (the one wording the name parser and the transport's ladder gate rely on) is measured on embedded libSQL only. If a server phrased it otherwise, both faces would still refuse, but the remote face with DATABASE_ERROR / 500 instead of INVALID_FILTER / 400. No producer is known; recorded, not blocking.
  4. The dev's three Acceptance-note carriers (judge item 7) — one escalation. (a) isUnresolvableColumnError unexported: the seat ruled it stays a note; answered. (b) The transport's inline ladder gate (two of the predicate's three arms): pre-existing, decides retry not refusal, no breach of "no second copy"; noted. (c) A rung failing with a non-column error: local INVALID_FILTER unnamed, remote DATABASE_ERROR / 500; unmeasured, no known producer. All three collapse into one driver-sql seam: export the predicate or add a protected where-exit on SqlDriver shared by count and findRows, after which (b) retires and (c) gets one answer. Carrier: none today — escalated as one drivers-lane card if the seat wants the plainer seam; otherwise the notes stand as written.
  5. Stale prose in the edited file (a one-line fix, this PR or a follow-up). remote-transport.ts:2909-2911 still says "find()'s own no such column backstop swallows the error into [] anyway" — FALSE at head, outside the claimed region. Likewise the driver-turso remote: a federated object's external.remoteName is ignored — remote find queries a table named after the object and throws a bare LibsqlError (no code, no status), while the local face reads the mapped table #20107 suite's header prediction at turso-local-remote-external-object-parity.test.ts:48 ("[] in place of the sum there") describes an ablation outcome this PR changes; prediction prose, no assertion depends on it.
  6. Docs drift the bot could not list (escalated as a docs card). content/docs/deployment/validating-metadata.mdx:380-382 states that the driver swallows "no such column" and the list comes back empty — FALSE since driver-sql: one unresolvable WHERE column, two answers — find() silently returns [] while count() throws a raw dialect error with no ADR-0112 envelope #8790 on driver-sql and the local Turso face, and after this PR on the remote face too. Not this PR's edit (outside the claim's surface; the page anchors on no symbol this diff touched). Owed to a docs-only card.
  7. Bounded in-place fixes. Both (the count exit; the ORDER BY rung) are inside the amended claim's surface and accepted there. No breach.
  8. Check-runs on 3b56eb769 at the last reading, 2026-09-28T15:28:15Z: 41 runs, all completed — 36 success, 5 skipped (Auto Label, Check PR Size on the second workflow, Console Pin Gate, Build Docs, Packed-tarball smoke), 0 failure, 0 in progress. Named: Check Changeset (both) success; Lint & Repo Gates success (15:20:56Z); Test Core rollup and all six shards success (last at 15:24:32Z); Type Check · workspace / source gates / debt ledger / consumer gates and TypeScript Type Check success; Build Core, Temporal Conformance (live PG + MySQL), Dogfood Regression Gate (all three shards and the rollup), Dogfood Verify CLI, Governed Surface Queue Guard, Check Documentation Links, Flag docs affected by code changes, and the five PR-hygiene guards success. The PR reads mergeable: true, mergeable_state: clean.

Implemented-by: claude/issue-20424-turso-remote-invented-empty
Reviewed-by: session_01N8TPEsoJxPsdSdNKGnNGEN

VERDICT: PASS

…holds

remote-transport.ts: find()'s no-such-column backstop refuses under
SQLITE_DQS=0 rather than answering []. The #20107 parity suite's header
prediction for its first ablation leg: aggregate now refuses the missing
table as DATABASE_ERROR / 500. Text only.

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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0c47b7bbe7cfd4ef10c0d436ab489ce4c70b9935
Local-runs: none

Delta review over the PASS 5873239611 at 3b56eb769. Inputs added and read: the seat's amended claim 5873267997; the patch-round-2 os-dev-report 5873976994 (the newest on #20424); the edited PR body (20506 bytes, diffed against the 18144-byte body reviewed before); git diff 3b56eb769 0c47b7bbe, split into the round's own commit ecca6921c and the merge 0c47b7bbe = ecca6921c + acd009521; and the check-runs on the head, read last, twice. Read-only git and REST GETs only; nothing built, run or re-run.

① Derived judgments

  1. The PR's own delta is text only; ① and ② of record 5873239611 carry over unchanged. 3b56eb769..ecca6921c is +6 / -5 in one comment of remote-transport.ts (the $-prefixed-key block, :2905-2916) and +3 / -2 in the driver-turso remote: a federated object's external.remoteName is ignored — remote find queries a table named after the object and throws a bare LibsqlError (no code, no status), while the local face reads the mapped table #20107 suite's header (turso-local-remote-external-object-parity.test.ts:47-50). No statement, no branch, no assertion, no changeset byte moves. Against the new merge base acd009521 the net diff is 6 files, +531 / -43; its + / - lines minus the reviewed net diff (5 files, +522 / -36) are exactly those two comment edits and nothing else. Every accept-set and public-surface judgment (the narrowing on remote aggregate / find / findOne; count re-coded 500 to 400; the ORDER BY recovery; the root-export change; the status gate; the migration-marker reading) stands as written at 3b56eb769.
  2. The two corrected comments are TRUE. (a) remote-transport.ts:2917-2923 now says that under SQLITE_DQS=0 the statement fails with no such column, that find()'s backstop now refuses it as the local face does (INVALID_FILTER / 400, driver-turso remote: aggregate / find answer [] for a missing table or a missing column, where the local face refuses DATABASE_ERROR / 500 or INVALID_FIELD / INVALID_FILTER / 400 (the #8790 shape on the remote transport) #20424), and that it used to swallow that error into []. True on the driver door: the transport's ladder rethrows, TursoDriver.remoteReadFault hands it to aggregateBackendFault, arm 2 composes INVALID_FILTER / 400. One attribution precision, not a falsity: the refusal is composed by the driver, which "as the local face does" implies; a standalone transport caller receives the raw error, as the changeset states. The block is historical measurement anyway — the $-prefixed key is refused at compile time by the code the comment sits in. (b) The driver-turso remote: a federated object's external.remoteName is ignored — remote find queries a table named after the object and throws a bare LibsqlError (no code, no status), while the local face reads the mapped table #20107 header's first prediction now reads that with remoteTableFor returning the object name, aggregate refuses no such table: ext_t as DATABASE_ERROR / 500, where before driver-turso remote: aggregate / find answer [] for a missing table or a missing column, where the local face refuses DATABASE_ERROR / 500 or INVALID_FIELD / INVALID_FILTER / 400 (the #8790 shape on the remote transport) #20424 it answered [] in place of the sum. True: the transport raises, the gate sees no status, aggregateBackendFault finds no column wording, backendStatementFault composes 500 — the road the driver-turso remote: a federated object's external.remoteName is ignored — remote find queries a table named after the object and throws a bare LibsqlError (no code, no status), while the local face reads the mapped table #20107 suite's section 3 pins for the other three doors.
  3. The merge brought exactly origin/main 2304b1608..acd009521 and no hand edit (judge item 3). Four commits: fbeb56e4b (spec pin), bea6d2ea3 (PR fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447), d7537449a (lint), acd009521 (cli). Proven three ways: the combined diff of the merge commit records no conflict resolution; git diff ecca6921c 0c47b7bbe equals git diff 2304b1608 acd009521 line for line, differing only in two hunk-header offsets on turso-driver.ts (this PR's two-line docblock edit at :673 sits above fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447's hunks and shifts them by 2); and the blobs of the five PR files fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447 does not touch are identical between ecca6921c and 0c47b7bbe. On turso-driver.ts, the one file both change, this PR's + / - lines against acd009521 are identical to its lines against 2304b1608.
  4. PR fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447 does not interact with this PR's remote read arms. Its turso-driver.ts hunks are the TursoDriverConfig interface (:123), a new refuseIgnoredSyncKey with two constants (:1045), and the constructor (:1466); none names remoteReadExit, remoteReadFault, aggregateBackendFault, toRemoteReadQuery, toRemoteQuery or any remoteTransport!. read call, and its line ranges are disjoint from this PR's six hunks (:673, :1918, :1934, :2061, :2274, :2305 in base numbering). Its refusals fire only on mode: 'remote' together with syncUrl, or sync without syncUrl; this PR's parity suite constructs new TursoDriver({ url: 'libsql://…', client }) and new TursoDriver({ url: 'file:…' }), which carry neither key, so the combined tree constructs both faces as before. The dev's re-run on the combined tree (Test Files 76 passed, Tests 2036 passed | 18 skipped, the added file and cases being fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447's own) is attested; the check-runs below are the measurement. The 3b56eb769 record's boundary flag 1 (the combined tree unmeasured) is closed by this head.
  5. The edited PR body (judge item 4), sentence by sentence. The refuseRemoteColumnMap line now names the added parenthetical sentence: TRUE (matches the diff hunk at :673). The Files list: remote-transport.ts +45 / -22, the driver-turso remote: a federated object's external.remoteName is ignored — remote find queries a table named after the object and throws a bare LibsqlError (no code, no status), while the local face reads the mapped table #20107 suite +3 / -2 "a header prediction line only", totals +531 / -43 over 6 files: TRUE against acd009521 (numstat 33/0, 15/4, 45/22, 90/15, 3/2, 345/0). "Patch round 2": the two comment fixes as described, TRUE; "origin/main merged at acd009521 (a true merge commit), carrying PR fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447 (bea6d2ea3, the constructor region of turso-driver.ts). The merge was clean. Head 0c47b7bbe": TRUE (two parents, no conflict residue, content-identical to main's side); the driver-turso test, typecheck, gate, conformance and lint numbers are attested, not re-run; "the same driver-turso remote: a federated object's external.remoteName is ignored — remote find queries a table named after the object and throws a bare LibsqlError (no code, no status), while the local face reads the mapped table #20107 header's third prediction ... would now be refused INVALID_FILTER / 400 ... prediction prose, and no assertion depends on it": TRUE (judged in ③ item 1). The fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447 Acceptance note: "Patch round 2 merged origin/main at acd009521, which carries both [fbeb56e4b and fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447]. PR fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447 (driver-turso: new TursoDriver accepts syncUrl / sync under mode: 'remote' and ignores them — isSyncEnabled() answers true, no sync runs, and sync() rejects SYNC_NOT_SUPPORTED #20200) landed as bea6d2ea3, is merged here, and driver-turso was re-tested on the combined tree": TRUE (acd009521's ancestry holds both, plus d7537449a and acd009521 itself, which the sentence does not claim to enumerate). "No change to RemoteTransport's or TursoDriver's published signatures. The constructor (PR fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447) and the write doors are untouched": still TRUE of this PR's own delta — the constructor moved through the merge, by fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447, not by this PR. No sentence of the edited body is FALSE.

② Semver level

Unchanged and still right: "@objectstack/driver-turso": minor, Clause-②: no (narrowing), the BREAKING banner, the ADR-0087 marker not-required (already-registered driver-sql-unresolvable-where-column-refused). The changeset blob is identical at 3b56eb769, ecca6921c and 0c47b7bbe; the round's delta publishes nothing (two comments). Check Changeset passed on this head at 15:35:47Z; its edited-body re-run is in progress (③ item 4) and reads a body whose Clause-② line is unchanged.

③ Boundary flags

  1. The third prediction line (judge item 5) — carried, not blocking. turso-local-remote-external-object-parity.test.ts:53-54 still predicts that with the column-map refusal deleted "a filter on the renamed field answers []". After this PR that filter would be refused INVALID_FILTER / 400 on the remote face; the RED direction the line predicts for section 4 is unchanged (the section asserts NOT_IMPLEMENTED / 501), and no assertion reads the line. The canonical statement of the same fact lives in refuseRemoteColumnMap's docblock, which this PR corrected. The line is the same class as the two fixes the amended claim admitted, the dev found it, stopped at the claim's surface, and named it in the report and the PR body. It does not touch the contract, the accept set or the level, so it does not block. Carrier: the seat — one line in the same header, either widened into this PR's surface on a next round if one happens for another reason, or left as the recorded residue it now is. No round should be opened for it alone.
  2. The migration entry's text (record 5873239611, ③ item 2). The seat's amended claim files it as a spec-lane card; answered.
  3. origin/main moved again after acd009521. dc0ab6a2e (spec: object-form repeaters) and e956924e1 (spec, metadata-core: retiredAfter on retired conversions), no packages/drivers path; this head is not re-merged with them. Same class as the fbeb56e4b note at round 1; GitHub reports the merge mergeable: true. Only this PR is open on a driver-turso path (10 open PRs censused). Recorded, not blocking.
  4. Check-runs on 0c47b7bbe at the last readings, 2026-09-28T16:16:21Z and 16:16:43Z: 41 runs — 35 success, 5 skipped (Auto Label and Check PR Size on the edited re-run, Console Pin Gate, Build Docs, Packed-tarball smoke), 0 failure, and 1 in progress: the second Check Changeset (run 36449288643, started 16:12:03Z — the pr-automation re-run triggered by the PR-body edit at 16:12Z, alongside the four PR-hygiene guards that re-ran and passed at 16:12Z). The first Check Changeset on this same head passed at 15:35:47Z (run 36444649953) over an identical changeset and a body whose Clause-② line has not changed. Named, all success: Lint & Repo Gates (15:50:09Z); Test Core rollup and all six shards (last 15:49:03Z) — the measurement of the combined tree with fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447; Type Check · workspace / source gates / debt ledger / consumer gates and TypeScript Type Check; Build Core; Temporal Conformance (live PG + MySQL); Dogfood Regression Gate (three shards and rollup); Dogfood Verify CLI; Governed Surface Queue Guard; Check Documentation Links; Flag docs affected by code changes; the PR-hygiene guards on both triggers. The PR reads mergeable: true, mergeable_state: unstable — the pending run. The in-progress run is recorded as in progress, not as passed; the seat re-reads it before posting.
  5. The other flags of record 5873239611 (the live libsql:// wire unmeasured; the three Acceptance-note carriers collapsing into one driver-sql seam; the validating-metadata.mdx:380-382 docs card) stand unchanged and are not restated.

Implemented-by: claude/issue-20424-turso-remote-invented-empty
Reviewed-by: session_01N8TPEsoJxPsdSdNKGnNGEN

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 16:21
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 3e8b492 Sep 28, 2026
43 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20424-turso-remote-invented-empty branch September 28, 2026 16:47
baozhoutao pushed a commit that referenced this pull request Sep 28, 2026
…ot the swallowed empty list

The filter-position passage said the driver's "no such column" is swallowed
and the list comes back empty. driver-sql, and the local faces of
TursoDriver/SqliteWasmDriver (both extend SqlDriver), have refused an
unresolvable WHERE column with INVALID_FILTER / 400, naming the column,
since #8790. The Turso remote face refuses the same way as of 3e8b492
(#20461), through the inherited aggregateBackendFault seam. Rewrote the
passage to the refusal and its remedy (schema sync, or naming a column the
table has), and reworded the "why an error not a warning" rationale to match.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv
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/l tests tooling

Projects

None yet

2 participants