feat(providers): libSQL over the Hrana protocol, one type-id for sqld and Turso Cloud (#424) - #511
Conversation
… and Turso Cloud (#424) Phase 5 of #424. WORK IN PROGRESS - gates 1-6 pass locally; the browser pass (gate 5) and agent grounding (gate 7) are not yet run, and no PR is open. A self-hosted libSQL server and Turso Cloud are ONE type-id because they speak the same protocol and embed the same SQLite (3.47.0, measured on both). Zero runtime dependency: Hrana is JSON over POST /v2/pipeline through the runtime's own fetch. Measured findings that shaped the code rather than being read from documentation: - A failed statement answers HTTP 200 with the failure inside results[], and a batch's failing step does not abort the pipeline - so executeBatch hands back a per-statement outcome and one refused read costs its own panel. - The two deployments word the same refusal differently ("unsupported statement: VACUUM" against "SQL not allowed statement: VACUUM") under one code, so nothing keys on wording. - VACUUM, ANALYZE, PRAGMA optimize and PRAGMA wal_checkpoint are all refused by the server, so only reindex and check are offered. - PRAGMA query_only is refused too, which is why this provider implements no queryReadOnly: the agent read-only profile stays PostgreSQL + SQLite. - `notnull` is a SQLite keyword, so projecting it bare from pragma_table_info is a parse error that costs the COLUMNS of every table and leaves the tree intact. Only the live probe could find it; the statement text is now pinned by a test. - dbstat answers on BOTH deployments, so table and index bytes here are measured - which bun:sqlite cannot do at all. - GET /version is a sqld route Turso Cloud does not have, so the version panel reads "sqld 0.24.33 (SQLite 3.47.0)" there and "SQLite 3.47.0" on the cloud. Also filed D34: the migration generator emits ADD CONSTRAINT for the `sqlite` dialect, which SQLite cannot parse. Found through the sibling engine, and left to its own fix. Chart 0.1.52 is released, so the packaged README change bumps it to 0.1.53 (#167).
#424) Gate 5 and gate 7 for the libSQL provider, and both cost a correction rather than a tick. The spec asserted that libSQL's connection dialog renders no Username and no Database input, because `connectionFields` names neither. The browser refuted it: those two fields decide what a SAVE writes, and `ConnectionModal` draws Host, Username, Password and Database for every engine that is not file-based - so Druid and the two search engines have shown a Database box they do not take for as long as they have shipped. The assertion now pins the behaviour as it is, and the comment in `use-connection-form.ts` that claims otherwise is filed as U22. Also filed U23: the Storage tab labels its remainder "Other (TOAST, FSM)" on every engine. Both are PostgreSQL structures, and on libSQL that read 4.00 KB of a real 64 KB database under another engine's vocabulary. The login helper takes `.first()` on both inputs: a second, hidden pair exists for a moment after hydration, so a strict locator failed locally while passing in CI. The spec is now verifiable in both places - 6 passed against a live server. Gate 7 passed on a libSQL connection: plan mode grounded on the real schema (2 tables, ctx_a49a) and drafted `SELECT country, COUNT(*) ... FROM probe_customers ... LIMIT 1`, which then ran unchanged against sqld and answered `de 1`. The rail says what it should where it cannot execute: "Agent mode has no read-only statement path on libSQL".
… on, and name libSQL where a count is published Review of #511. Every claim below was re-measured rather than taken from the report. - The gate-4 harness and its two result files were COMMITTED. `.gitignore` named `/probe-424.ts` literally, and this run's harness was named per engine, so none of the three matched. Untracked and the patterns globbed (`/probe-*.ts`, `/probe-results*.json`). They carried no token and no hostname - the harness takes both from argv - but `tsconfig`'s `**/*.ts` did pull the harness into typecheck. - `database-compose.yml` claimed the image was "pinned to the exact build" while asking for `:latest`. `:latest` is a ROLLING REBUILD of the same version: measured 2026-08-27 it answers `sqld 0.24.33 (f8fb14f3 2026-08-11)` where the `v0.24.33` tag answers `sqld 0.24.33 (40a151bd 2025-12-19)` - one version number, two digests. Pinned to `v0.24.33` and re-probed against it surface by surface: the same 17 of 19, the same four refusals with byte-identical wording, the same quoted-`"notnull"` behaviour. The doc now records both builds and that the pin reproduces the measurements. - Registering a fifteenth external engine moved denominators the PR left behind: `Chart.yaml` and the operator CSV both still described "fourteen engines" without libSQL (ArtifactHub and OperatorHub read those), the showcase and hero-proof comments still explained fifteen pills under a claim of fourteen, and the wire-compatibility hint still counted fourteen driver buttons. `compatibility.ts` quoted a README sentence ("fourteen drivers reach forty named engines") that no longer exists. Each is now the measured figure: 16 type-ids, 15 external, 26 relatives, 41 named products. - The chart's own comment says a new engine's keyword belongs in the release that ships it, and libSQL had none. Added `libsql` and `turso` - the searched term is the product name. No further chart bump: 0.1.53 is unreleased. - A failed connection-string paste listed nine supported schemes and omitted the tenth, `libsql://`, which the parser accepts. Two review findings are real and belong to the codebase rather than to this PR, so they are filed instead of fixed: D35, five `fetch`-based transports read `ssl.mode` and discard the rest of the TLS panel (Couchbase already solves it through `node:https`); D36, `getConnectionInfo` masks `:pass@` but not `?authToken=`, dead today with test-only callers. Gates in the worktree: format, lint, typecheck, knip, test (13175 pass / 0 fail), build, plus chart:check, readme:check, security:check, channels:showcase:check and `helm lint charts/libredb-studio --strict`.
Review response — every finding re-measured, then acted onTwo external reviews landed on this PR. I verified each claim against the tree and the live Confirmed and fixed
On the pin, the reviewer's reasoning was right and the conclusion was incomplete. Found while verifying, not in either reviewThe chart's own comment says "a new engine's keyword belongs in the release that ships it" Confirmed, and deliberately not fixed here
Verified as accurate, no change neededThe review's own no-bug list held under checking: Hrana envelope handling, integer fidelity, the Not confirmed
Gates in the worktree: |
… on, and name libSQL where a count is published Review of #511. Every claim below was re-measured rather than taken from the report. - The gate-4 harness and its two result files were COMMITTED. `.gitignore` named `/probe-424.ts` literally, and this run's harness was named per engine, so none of the three matched. Untracked and the patterns globbed (`/probe-*.ts`, `/probe-results*.json`). They carried no token and no hostname - the harness takes both from argv - but `tsconfig`'s `**/*.ts` did pull the harness into typecheck. - `database-compose.yml` claimed the image was "pinned to the exact build" while asking for `:latest`. `:latest` is a ROLLING REBUILD of the same version: measured 2026-08-27 it answers `sqld 0.24.33 (f8fb14f3 2026-08-11)` where the `v0.24.33` tag answers `sqld 0.24.33 (40a151bd 2025-12-19)` - one version number, two digests. Pinned to `v0.24.33` and re-probed against it surface by surface: the same 17 of 19, the same four refusals with byte-identical wording, the same quoted-`"notnull"` behaviour. The doc now records both builds and that the pin reproduces the measurements. - Registering a fifteenth external engine moved denominators the PR left behind: `Chart.yaml` and the operator CSV both still described "fourteen engines" without libSQL (ArtifactHub and OperatorHub read those), the showcase and hero-proof comments still explained fifteen pills under a claim of fourteen, and the wire-compatibility hint still counted fourteen driver buttons. `compatibility.ts` quoted a README sentence ("fourteen drivers reach forty named engines") that no longer exists. Each is now the measured figure: 16 type-ids, 15 external, 26 relatives, 41 named products. - The chart's own comment says a new engine's keyword belongs in the release that ships it, and libSQL had none. Added `libsql` and `turso` - the searched term is the product name. No further chart bump: 0.1.53 is unreleased. - A failed connection-string paste listed nine supported schemes and omitted the tenth, `libsql://`, which the parser accepts. Two review findings are real and belong to the codebase rather than to this PR, so they are filed instead of fixed: D35, five `fetch`-based transports read `ssl.mode` and discard the rest of the TLS panel (Couchbase already solves it through `node:https`); D36, `getConnectionInfo` masks `:pass@` but not `?authToken=`, dead today with test-only callers. Gates in the worktree: format, lint, typecheck, knip, test (13175 pass / 0 fail), build, plus chart:check, readme:check, security:check, channels:showcase:check and `helm lint charts/libredb-studio --strict`.
…claimed `docs/BACKLOG.md` was the only conflict, and it was an ID collision rather than a text overlap: #512 RESTORED the two SSH entries #510 had overwritten and renumbered them to D34 and D35 - the exact ids this branch had taken for its own findings. Since every ID in that file must be unique, mine move rather than main's: - D34 (migration generator emits `ADD CONSTRAINT` for SQLite) -> **D36**, and its citation in `src/lib/schema-diff/migration-generator.ts` follows it. - D35 (five `fetch` transports drop the TLS panel) -> **D37**. - D36 (`getConnectionInfo` masks `:pass@` but not `?authToken=`) -> **D38**. U22 and U23 keep their ids - main claimed no U above U21. The section index line is recomputed from the surviving headings rather than merged: `D1-D38, U17, U22-U23 - 16`. Everything else auto-merged, and the two overlaps worth checking by hand were both fine. `docs/AGENT.md`: #512 rewrote the paragraph about WHEN an engine with no read-only path is refused (the route now answers 400 before a model turn) while this branch changed the counts in the same file - and the counts are the ones to keep. Measured rather than reconciled by hand: `AGENT_EXECUTION_ENGINES` is `["postgres","sqlite"]` and `SHIPPED` is 16, so "the fourteen the read-only profile refuses" is right and main's "twelve" was already stale before this branch existed. `docs/providers/README.md:231` cites D34, which is main's SSH entry and correctly unchanged. `docs/providers/libsql.md` needed nothing for #512: it says the profile is ABSENT, which is still true, and makes no claim about when the refusal lands.
e9acddd to
2227419
Compare
… operator-sdk's own wrapping `Helm Chart Lint` failed on the bundle freshness step, and the drift was purely mechanical: I hand-edited the CSV `description` in both `operator/config/.../bases` and `operator/bundle/manifests` to name libSQL, and my line breaks are not the ones `operator-sdk generate bundle` produces. The gate re-runs `make -C operator bundle` and diffs, so any hand-wrapped YAML folded scalar fails it even when the text is identical. `make -C operator bundle` run locally; the only content change is the wrapping, plus the `createdAt` stamp the gate ignores with `-I '^ *createdAt:'`. `operator-sdk bundle validate --select-optional suite=operatorframework` passes. Not a chart version question: `chart:check` is green (chart 0.1.53 / appVersion 0.13.4 / package.json 0.13.4), #167 concerns packaged files under `charts/`, and nothing under `charts/` changed here. Lesson for the next engine: the CSV description is generated, so edit `operator/config/manifests/bases/...` and re-run `make -C operator bundle` rather than editing the two copies by hand.
…h is why they were stale The #511 review found the chart description, the chart keywords and the OLM CSV description all still naming the previous engine set after libSQL had landed everywhere the compiler looks. That was not an oversight in one PR - `docs/ADDING_A_PROVIDER.md` does not mention any of them. Its "Always" and "Conditionally" blocks are the surfaces a gate or the type system catches; this block is the one with neither, so it goes in the same list rather than in a commit message nobody re-reads. Added as a third block, and each line carries the reason it bites: - ArtifactHub shows `description` and SEARCHES `keywords`, so an engine missing from the keywords is an engine nobody finds - and #167 makes a keyword-only fix cost a chart version of its own, which is why the chart's own comment asks for it in the release that ships the engine. Two names are usually right, the type-id and the product a user types (`libsql` and `turso`). - The CSV description must be edited in `operator/config/manifests/bases/` and regenerated with `make -C operator bundle`. Editing `operator/bundle/manifests/` by hand is what failed `Helm Chart Lint` on this PR: the gate re-runs the generator and diffs, so hand-wrapped YAML fails it even when the text is byte-identical in meaning. - Numerals need their denominator separated first (type-ids, external drivers, relatives, the sum), with `connectableProductCount()` as the one definition. A mechanical replace is how a CORRECT number becomes wrong - the agent docs' "the other fourteen" counts type-ids minus the two `CATALOG_PLANS` dialects and moves for a different reason than the driver count does. Verified while writing it: `tests/unit/marketplace-copy.test.ts` exists and passes (13/13), and the three listing files named are the ones this PR had to touch. Gates: format, lint, typecheck, knip.
|
…red it (#514) #511 and #513 branched from the same commit and each appended three entries to the "Drivers and connections" section. The bodies landed at different offsets, so git merged them without reporting a conflict and main carried D36, D37 and D38 twice. The file's own rule, stated in its header, is that every id is unique across the whole file because cross-references use the bare id; a duplicate makes every such reference ambiguous. #511 reached main first, so its three entries keep D36-D38 and #513's are renumbered to D39-D41. The single citation in src/lib/schema-diff/migration-generator.ts names #511's D36 and is unchanged; nothing cited the renumbered three. The one line both branches did edit, the section index for Drivers and connections, did conflict, and the resolution dropped its id range and its count altogether. A section index that states nothing cannot be wrong, which is why nothing caught that either. It is restored as the entries make it: D1-D41, U17, U22-U23, 19 entries. tests/unit/backlog-structure.test.ts is the guard that was missing. It derives the index block from the entry bodies rather than trusting it: no id appears twice, the sections listed are the sections present and their anchors resolve, every id a line names exists, each range's endpoints are the real extremes, and the trailing count is the real number of entries. The endpoints are checked as extremes rather than as one canonical rendering because the file legitimately writes a prefix two ways, U17, U22-U23 in one section and X2-X14 in another. Both defects were confirmed to fail the new test before the fix, and each of the four index assertions was confirmed to fail against a deliberately corrupted line, so none of them passes vacuously. The existing citation guard in tests/unit/agent-documentation.test.ts only ever asked whether an id exists, which a duplicate satisfies.
Deleting an entry leaves every bare id that cited it pointing at nothing. This is the third round in a row where that happened, and T4 in the backlog is the standing record of the class, so the repointing is part of the same PR rather than a follow-up. Nineteen citations across 13 files now name #515, the PR that closed the work, which a reader can actually open. They were spread across code comments (the migration generator, the Trino introspection pair, three monitoring tabs), one provider doc and seven test files. One is deliberately left alone. `tests/unit/backlog-structure.test.ts` says "main carried D36, D37 and D38 twice", which is a statement about what happened in #511 and #513, not a citation of an entry. It is still true, and D37 is still an open entry, so repointing it would have made a correct sentence wrong - the same trap as blanket-repointing the five comments about the "0 means not published" encoding, two of which describe `maxConnections` and remain correct.
Deleting an entry leaves every bare id that cited it pointing at nothing. This is the third round in a row where that happened, and T4 in the backlog is the standing record of the class, so the repointing is part of the same PR rather than a follow-up. Nineteen citations across 13 files now name #515, the PR that closed the work, which a reader can actually open. They were spread across code comments (the migration generator, the Trino introspection pair, three monitoring tabs), one provider doc and seven test files. One is deliberately left alone. `tests/unit/backlog-structure.test.ts` says "main carried D36, D37 and D38 twice", which is a statement about what happened in #511 and #513, not a citation of an entry. It is still true, and D37 is still an open entry, so repointing it would have made a correct sentence wrong - the same trap as blanket-repointing the five comments about the "0 means not published" encoding, two of which describe `maxConnections` and remain correct.
* fix(db,ui,docs): a refusal drawn as a measurement, in five places Five backlog entries close. Every one is the same defect wearing different clothes: a reading that could not be taken, or a list that was cut, reaching a reader as a confident figure. The absence rule (#477) is house law here, and these are the places it was not applied. D40 - the Overview panel's connection count. `DatabaseOverview.activeConnections` is optional for the reason its docblock gives, and SQL Server, Oracle and MongoDB all initialised a local to 0 and swallowed the read's failure into it. So a denied VIEW SERVER PERFORMANCE STATE, an unprivileged V$SESSION or a serverStatus without a connections section drew a confident "0 connections" on a server with plenty. All three now omit the key, and a real zero still reads as zero - both arms pinned per provider. The existing SQL Server test was asserting the fabrication rather than a measurement: `expect(typeof overview.activeConnections).toBe("number")` passed because the fabricated 0 is a number. Its purpose-built fixture was dead code, shadowed by a generic branch that matched the same statement, so the assertion had never seen the value it was written for. D41 - two surfaces reading a cap as a count. The Queries tab's *Queries* card summed the calls of a list every provider caps at ten and labelled it the database's query count; *Slow* was bounded by ten however many slow statements the server held. Measured against a MySQL server holding 59 digests for one schema, both cards were the ceiling wearing a measurement's label. On the provider side TRINO_MAX_STATS_TABLES cut the table-stats read inside the provider, where no marker above it could see the cut; it now refuses rather than truncating, and its refusal names the real count. D36 - the migration generator emitted `ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY` for SQLite, which has no ALTER that adds a constraint, so the file was a syntax error wherever it was run. There were two emission sites, not the one the entry described: the ALTER path and, undocumented, `generateCreateTable`, which writes a new table's foreign keys as a separate ALTER. libSQL had the same defect there, so its earlier fix was incomplete. The two sites want different answers - in the CREATE path the key belongs inside the CREATE TABLE, where SQLite accepts it and nothing is lost; in the ALTER path it cannot be expressed and is declined with a comment naming table recreation. D38 - `getConnectionInfo` masked `:secret@` in an authority and nothing else, so a libSQL connection string carrying its whole token as `?authToken=` was returned in the clear. The mask now covers the authority and the value of every credential-shaped parameter, in any position. Two rounds of review found two more shapes in the fix itself: a credential LEADING an ADO-style string was returned verbatim, and narrowing the authority pattern had stopped masking `postgres://user:p/w@host/db`, which the old one masked. What that second one cannot do is covered rather than hidden. `postgres://host:5432/tenant@acme` has the identical shape and carries no secret, so masking one masks the other, and drawing `***` over a port asserts a credential the string never carried - a fabricated reading, which this rule forbids more strongly than it forbids the miss. The docblock says so and the test file pins the gap, so it is visible rather than assumed closed. D42 carries the parse-based fix. U23 - the Storage tab labelled the remainder of its breakdown "Other (TOAST, FSM)" on every engine. TOAST and the free space map are PostgreSQL structures; SQLite, libSQL, MySQL, Oracle, SQL Server, ClickHouse and the HTTP engines have neither. On libSQL it read 4.00 KB under that label against a real 64 KB database: the number right, the words another engine's. Two defects found in the same files while fixing those, both pre-existing: - The Storage tab was the third reader of `errors.tables` and the only one that did not read it. A refused table read arrived as an empty list, `every()` was vacuously true, and the tab drew a measured 0 B for Tables and Indexes with the remainder absorbing the whole database at 100%. It now carries the engine's own sentence, the way its two sibling panels already do, while a genuinely empty database still reads 0 B. - Quick Stats published three cap-bounded figures as counts: "Slow Queries" off a list capped at 10, and "Active" and "Idle" off one list capped at 50, which the two badges split while reading as the server's totals. All three now name the rows they measure. MongoDB's `getOverview` had a second fabricated zero beside the first: `databaseSizeBytes` is optional for the same stated reason, and the Storage tab keys its entire breakdown off whether that key is present. So a denied serverStatus did not hide a number, it replaced an honest refusal with a breakdown over a zero-byte database - and with the table read still answering, the remainder went negative, which `formatBytes` renders as the literal string "NaN undefined". The success path had the same shape through `|| 0`, and so did `getHealth`, whose `databaseSize` string is the one the agent forwards to the model verbatim. Provider docs now cite code by name rather than by line, enforced by a new guard. A line number hand-copied into prose has nothing measuring it. The eight seam rows in both search docs were stale at birth: they entered in one commit as 751/811/831/844/860/873/884/898 while that same commit's declarations sat at 808/868/888/901/917/930/941/955 - a uniform +57, which is exactly why nobody noticed. The rows stayed in ascending order and read as a consistent, plausible list. Today the offset is +65. The guard bans the form, so a correct line number fails it too, and its scope statement names what is not measured yet rather than implying coverage. * docs(backlog): five entries close, seven open in their place Deleted: D36 (the migration generator's invalid SQLite DDL), D38 (the connection-string mask), D40 (the Overview panel's fabricated connection zero), D41 (a cap read as a count), U23 (PostgreSQL storage vocabulary on every engine). The three section index lines are recomputed from the surviving headings rather than edited by hand, which the structure guard added last round now checks in both directions. The seven that open were all measured while closing those five, and each one is here rather than in the PR for a stated reason: D42 - the mask cannot cover an authority password holding a character RFC 3986 reserves, because `postgres://host:5432/tenant@acme` has the identical shape and holds no secret. The fix is parsing, not a better pattern. D43 - SQLite gained `ALTER COLUMN ... SET/DROP NOT NULL` in 3.53.0, measured on 3.53.0: it rewrites the stored schema and is enforced on insert. The generator declines it on both ids. It cannot simply stop: sqld ships 3.47.0, and the `sqlite` provider runs on whichever SQLite its runtime bundles, so a migration file handed to a human would run on one deployment and fail on another. D44 - `databaseSizeBytes` is fabricated as 0 wherever the size is unknown, in 14 of 16 type-ids, and the Storage tab keys its whole breakdown off whether the key is present. Cassandra's own comment is the precedent and states the argument. 14 provider files, 14 docs, 14 test files, which is why it is an entry. D45 - on SQL Server 2019 and earlier the connection count is under-reported rather than refused. Microsoft documents `sys.dm_exec_sessions` as row-filtered, not refused, and `sys.configurations` as needing only `public` below 2022, so the statement SUCCEEDS and returns the caller's own session. Distinguishing that needs a permission probe and a live instance with an ungranted login. D46 - one fabricated connection zero survives the sweep, in the shared search provider, and three of the five comments citing the old encoding are now false while two remain true: for `maxConnections` zero and absence really are one fact, which is what Druid and Trino cite. The three false ones are corrected here; moving the encoding is the entry. U24 - two absences the Storage tab still renders as measurements: a measured zero total draws the remainder bar full, and the same zero puts the literal string "NaN undefined" in the cell beside it, because `formatBytes` takes `Math.log` of a negative. A refused overview also drops the engine's sentence in a file that carries it for two other panels. DOC4 - 62 line citations remain in the provider docs and every checkable one is stale: of 69 machine-checkable before this round, 68 were wrong; after 18 fixes, 62 remain and all 62 are wrong, over 12 docs. The other 209 point at expressions and table rows, so no heuristic can judge them and no figure is claimed for them. * docs(src,tests,docs): the five closed entries left 19 dangling citations Deleting an entry leaves every bare id that cited it pointing at nothing. This is the third round in a row where that happened, and T4 in the backlog is the standing record of the class, so the repointing is part of the same PR rather than a follow-up. Nineteen citations across 13 files now name #515, the PR that closed the work, which a reader can actually open. They were spread across code comments (the migration generator, the Trino introspection pair, three monitoring tabs), one provider doc and seven test files. One is deliberately left alone. `tests/unit/backlog-structure.test.ts` says "main carried D36, D37 and D38 twice", which is a statement about what happened in #511 and #513, not a citation of an entry. It is still true, and D37 is still an open entry, so repointing it would have made a correct sentence wrong - the same trap as blanket-repointing the five comments about the "0 means not published" encoding, two of which describe `maxConnections` and remain correct.



Phase 5 of #424. libSQL gets its own type-id, reached over the Hrana protocol with no runtime dependency: a statement is JSON in the body of a
POST /v2/pipelineand the answer comes back through the runtime's ownfetch.One type-id for two deployments. A self-hosted libSQL server (
sqld) and Turso Cloud speak the same protocol and embed the same SQLite — 3.47.0 on both, measured — so they are one id rather than two docs and two tests describing one set of measurements. It is separate fromsqlitefor the reason the two cannot share a provider: the SQLite one holds a file handle through a synchronous driver, reads sizes withfs.statSyncand enforces the agent read-only profile withPRAGMA query_only. None of those three exist here.Turso Database — the Rust rewrite — earns no row. It publishes no server image (
tursodatabase/turso,tursodatabase/tursodbandghcr.io/tursodatabase/turso-serverwere all unpullable on 2026-08-27) and ships as an in-process npm engine, so there is nothing to connect to and #424 publishes no name it has not connected to.Measured against both deployments, 2026-08-27
ghcr.io/tursodatabase/libsql-serverreportingsqld 0.24.33 (f8fb14f3 2026-08-11), and a Turso Cloud database inaws-eu-west-1. The gate-4 harness calls every surface separately: 17 of 19 answer on both arms, the two that do not being the intended refusals.Five findings shaped the code rather than being read out of a protocol document:
results[].response.oksays the pipeline was accepted, never that the statement ran. And a failing step does not abort the rest of the pipeline — measured with a three-statement batch whose middle one failed while the third still answered. That is whyexecuteBatchhands back a per-statement outcome instead of throwing: one refused read costs its own panel, which is fix(monitoring): one refused read cost the whole dashboard, and three more #477's rule applied before the fact rather than after.unsupported statement: VACUUMagainstSQL not allowed statement: VACUUM, bothSQL_PARSE_ERROR. Nothing keys on wording; matching on text would have been wrong on one of them from the first day.VACUUM,ANALYZE,PRAGMA optimize,PRAGMA wal_checkpointandPRAGMA query_only, and acceptsREINDEXandPRAGMA integrity_check. SomaintenanceOperationsis["reindex", "check"], andrunMaintenancerefuses the rest here rather than relaying a server error for a statement the user never typed. Thequery_onlyrefusal is why this provider implements noqueryReadOnly: the agent read-only profile stays PostgreSQL + SQLite, and the engine-side answer, if it is ever wanted, is a read-only Turso token.notnullis a SQLite keyword, soSELECT cid, name, type, notnull, … FROM pragma_table_info(…)isnear NOTNULL: syntax error. The cost is narrow and quiet: it fails the COLUMN read of every table and leaves the rest of the tree intact, so the object browser listed both tables and showed each as having none — with every unit test green, because a fake transport does not parse SQL. Only the live probe found it. The statement text is now pinned by a test.dbstatanswers on both, whichbun:sqlitecannot do at all, so table and index bytes here are measured rather than absent: 4096 B of table and 4096 B of index for a 3-row table, 53248 B for a 2000-row one.GET /versionis a sqld route Turso Cloud does not have, so the version panel readssqld 0.24.33 (…) (SQLite 3.47.0)there andSQLite 3.47.0on the cloud — neither is "Unknown", because in both cases the engine answered.Verified in a real browser
Object browser:
probe_customers3 rows withid INTEGER/name TEXT/country TEXTand its index,probe_orders2.0k. Editor: aGROUP BYreturned 3 rows. Monitoring, all seven tabs, no failed request anywhere — version as above, ConnectionsN/A · not published, DB Size 64 KB, Cache HitN/A · Not measured, Tables 2 / 1 index, per-table bytes on the Tables tab,WAL N/Aon Storage, and the Queries tab carrying this provider's own empty state: "libSQL keeps no statistics about finished statements, so there is nothing to enable."Gate 7 passed. Plan mode grounded on the real schema (2 tables read,
ctx_a49a) and draftedSELECT country, COUNT(*) AS customer_count FROM probe_customers WHERE country IS NOT NULL GROUP BY country ORDER BY customer_count DESC LIMIT 1, which then ran unchanged against the server and answeredde 1. Where it cannot execute, the rail says so in the engine's own name: "Agent mode has no read-only statement path on libSQL."Two claims the browser corrected, and one defect found through the sibling
The e2e spec first asserted that the dialog renders no Username and no Database input, because
connectionFieldsnames neither. That was wrong: those fields decide what a save writes, whileConnectionModaldraws Host, Username, Password and Database for every engine that is not file-based — so Druid and the two search engines have been showing a Database box they do not take for as long as they have shipped. The assertion now pins the behaviour as it is, and the comment claiming otherwise is filed as U22.Also filed: U23 (the Storage tab labels its remainder "Other (TOAST, FSM)" on every engine, and both are PostgreSQL structures) and D34 (the migration generator emits
ADD CONSTRAINTfor thesqlitedialect, which SQLite cannot parse — found through the sibling engine, and left to its own fix).Counts and chart
connectableProductCount()is now 41: 15 external drivers plus 26 wire-compatible relatives. Every place that published 14 drivers or 40 products moved with it, including both localized READMEs (thereadme-checkgate enforces that) and the marketplace listings (the explanation claim must name every engine that returns a plan). Chart 0.1.52 is released, so the packaged README change bumps it to 0.1.53 (#167);helm lint --strictpasses.Gates
format · lint · typecheck · knip · build ·
tests/run-core.sh(356 files) ·test:components(33 groups) — all green. New coverage:tests/unit/db/libsql/(99 tests across the transport, the introspection and a seam guard) andtests/integration/db/libsql-provider.test.ts(34), with all four provider files at 100% lines.e2e/libsql-provider.spec.ts: 6 passed against a live server.