Skip to content

feat(providers): libSQL over the Hrana protocol, one type-id for sqld and Turso Cloud (#424) - #511

Merged
cevheri merged 6 commits into
mainfrom
probe/libsql-provider
Aug 27, 2026
Merged

feat(providers): libSQL over the Hrana protocol, one type-id for sqld and Turso Cloud (#424)#511
cevheri merged 6 commits into
mainfrom
probe/libsql-provider

Conversation

@cevheri

@cevheri cevheri commented Aug 27, 2026

Copy link
Copy Markdown
Member

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/pipeline and the answer comes back through the runtime's own fetch.

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 from sqlite for the reason the two cannot share a provider: the SQLite one holds a file handle through a synchronous driver, reads sizes with fs.statSync and enforces the agent read-only profile with PRAGMA query_only. None of those three exist here.

Turso Database — the Rust rewrite — earns no row. It publishes no server image (tursodatabase/turso, tursodatabase/tursodb and ghcr.io/tursodatabase/turso-server were 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-server reporting sqld 0.24.33 (f8fb14f3 2026-08-11), and a Turso Cloud database in aws-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:

  • A failed statement answers HTTP 200, with the failure inside results[]. response.ok says 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 why executeBatch hands 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.
  • The two deployments word the same refusal differentlyunsupported statement: VACUUM against SQL not allowed statement: VACUUM, both SQL_PARSE_ERROR. Nothing keys on wording; matching on text would have been wrong on one of them from the first day.
  • The server refuses VACUUM, ANALYZE, PRAGMA optimize, PRAGMA wal_checkpoint and PRAGMA query_only, and accepts REINDEX and PRAGMA integrity_check. So maintenanceOperations is ["reindex", "check"], and runMaintenance refuses the rest here rather than relaying a server error for a statement the user never typed. The query_only refusal is why this provider implements no queryReadOnly: the agent read-only profile stays PostgreSQL + SQLite, and the engine-side answer, if it is ever wanted, is a read-only Turso token.
  • notnull is a SQLite keyword, so SELECT cid, name, type, notnull, … FROM pragma_table_info(…) is near 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.
  • dbstat answers on both, which bun:sqlite cannot 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 /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 — neither is "Unknown", because in both cases the engine answered.

Verified in a real browser

Object browser: probe_customers 3 rows with id INTEGER / name TEXT / country TEXT and its index, probe_orders 2.0k. Editor: a GROUP BY returned 3 rows. Monitoring, all seven tabs, no failed request anywhere — version as above, Connections N/A · not published, DB Size 64 KB, Cache Hit N/A · Not measured, Tables 2 / 1 index, per-table bytes on the Tables tab, WAL N/A on 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 drafted SELECT 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 answered de 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 connectionFields names neither. That was wrong: those fields decide what a save writes, while ConnectionModal draws 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 CONSTRAINT for the sqlite dialect, 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 (the readme-check gate 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 --strict passes.

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) and tests/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.

… 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".
cevheri added a commit that referenced this pull request Aug 27, 2026
… 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`.
@cevheri

cevheri commented Aug 27, 2026

Copy link
Copy Markdown
Member Author

Review response — every finding re-measured, then acted on

Two external reviews landed on this PR. I verified each claim against the tree and the live
servers before changing anything; e9acddd3 carries what survived.

Confirmed and fixed

Claim Verification Fix
Three probe artifacts committed at repo root git ls-tree HEAD lists probe-libsql.ts, probe-results-cloud.json, probe-results-self.json; .gitignore named /probe-424.ts literally, matching none of them; tsc --listFiles confirms **/*.ts pulled the harness into typecheck untracked, patterns globbed to /probe-*.ts and /probe-results*.json
Compose comment claims a pin, image says :latest measured pinned v0.24.33, see below
Chart.yaml description still "fourteen engines", libSQL absent correct on main pre-PR, so this PR made it stale; ArtifactHub reads it both chart copies now "fifteen engines" and name libSQL
Paste-failure toast omits libsql:// connection-string-parser.ts accepts libsql:// (line 172) and the toast lists the other nine added
compatibility.ts comments still say "the other fourteen" and quote a README sentence that no longer exists SHIPPED 16, EXTERNAL 15, relatives 26, connectableProductCount() 41; README says "fifteen drivers reach forty-one named engines" corrected

On the pin, the reviewer's reasoning was right and the conclusion was incomplete. v0.24.33
does exist (the first page of the GHCR tag list stops at v0.23.7; ?n=1000 reaches it), and
pinning it matters more than the report suggested: :latest is a rolling rebuild of the same
version
. Measured 2026-08-27, :latest answers sqld 0.24.33 (f8fb14f3 2026-08-11) and
v0.24.33 answers sqld 0.24.33 (40a151bd 2025-12-19) — one version number, two digests. So the
build the doc's measurements came from is not the build the tag names, and I re-probed the pinned
one surface by surface rather than assert equivalence: 17 of 19, the same two intended refusals,
the four unsupported statement: wordings byte-identical, and the quoted "notnull" projection
correct.
The doc records both builds and that the pin reproduces every measurement.

Found while verifying, not in either review

The chart's own comment says "a new engine's keyword belongs in the release that ships it"
(added for Cassandra in 0.1.45, because ArtifactHub search matches on keywords). libSQL had none.
Added libsql and turso — the term an evaluator types is the product name. No further chart
bump: 0.1.53 is unreleased.

Confirmed, and deliberately not fixed here

  • D35 — five fetch-based transports (ClickHouse, Druid, Elasticsearch/OpenSearch, Trino,
    libSQL) read ssl.mode and discard caCert/clientCert/clientKey/rejectUnauthorized.
    Verified by grep across all six HTTP transports: Couchbase is the one that honours them, through
    node:https (D26), and its CouchbaseTlsMaterial mapping is the pattern the other five need.
    libSQL is the fifth instance of one gap, so fixing it here would leave four.
  • D36getConnectionInfo masks :pass@ and would print a libSQL ?authToken= in full.
    Confirmed dead: the only callers are in tests/unit/db/base-provider.test.ts. Pre-existing on
    main, unchanged by this PR.

Verified as accurate, no change needed

The review's own no-bug list held under checking: Hrana envelope handling, integer fidelity, the
notnull regression pin, the resolveConnection/transport split, provider triad lockstep, agent
integration via captureFromProvider, and runMaintenance("check") reading the integrity answer
rather than the status. The "AGENT docs say fourteen" figures are also correct and were left alone:
there the denominator is SHIPPED (16) minus the two CATALOG_PLANS dialects.

Not confirmed

  • "probe files carry a live Turso hostname" — they carry none. The harness takes host, port and
    token from argv; no URL, hostname or token appears in any of the three files. The reason to
    remove them is the convention, not a leak.
  • The login signature rotation now cycles libsql://user@db.internal:8080/app. Real, and generic
    by design — the component builds every engine's line from ENGINE_URI_SCHEMES plus its default
    port, and mongodb://user@db.internal:27017/app is equally synthetic. Per-engine templates are a
    product change, not a review fix.

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.

… 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.
@cevheri
cevheri force-pushed the probe/libsql-provider branch from e9acddd to 2227419 Compare August 27, 2026 10:01
… 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.
@sonarqubecloud

Copy link
Copy Markdown

@cevheri
cevheri merged commit e4367d6 into main Aug 27, 2026
19 checks passed
@cevheri
cevheri deleted the probe/libsql-provider branch August 27, 2026 10:59
cevheri added a commit that referenced this pull request Aug 27, 2026
…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.
cevheri added a commit that referenced this pull request Aug 27, 2026
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.
cevheri added a commit that referenced this pull request Aug 27, 2026
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.
cevheri added a commit that referenced this pull request Aug 27, 2026
* 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant