Skip to content

test(driver-sql): empty the dialect ledger, and promote the matrix-routing measurement to an invariant - #12302

Merged
os-warren merged 2 commits into
mainfrom
claude/issue-12136-dialect-ledger-resolve-and-promote
Aug 25, 2026
Merged

test(driver-sql): empty the dialect ledger, and promote the matrix-routing measurement to an invariant#12302
os-warren merged 2 commits into
mainfrom
claude/issue-12136-dialect-ledger-resolve-and-promote

Conversation

@os-warren

@os-warrenos-warren commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes#12136

The dialect axis #12134 added could see the third axis of ADR-0053 D-A3 but could not enforce it: it printed 8 of 9 dialect-scored cell(s) have a matrix-routed suite and exited 0. This resolves the two seeded DIALECT_LEDGER rows and promotes that sentence to an enforced invariant, in the order the card ruled.

1. FILTER_COMPARAND_TYPE now runs the matrix — and "compile everywhere" is now measured

FILTER_COMPARAND_TYPE_CASES was the only dialect-scored cell with no matrix-routed suite, while its own CASE_SETS entry says the six accepted types "compile everywhere". That sentence had only ever been measured on SQLite.

Measured on live PostgreSQL 16.13 (initdb'd for this change; the same version #11456's 42883 divergence was found on):

Tests 52 passed | 1 skipped (53) # with OS_TEST_POSTGRES_URL
Tests 43 passed | 2 skipped (45) # without it

The delta is exactly the nine live-Postgres tests — the fixture control plus all eight executed cases. All eight answered the case-set's row ids, so on Postgres the claim now holds as a measurement rather than as prose. Two non-vacuity proofs, because a live cell that quietly did not run would read identically:

  • pg_stat_database.xact_commit = 326 on the live database after the run — real traffic, not a skipped cell.
  • Under OS_EXPECT_LIVE_DIALECT_MATRIX=1 the run goes red on the MySQL cell by name (OS_TEST_MYSQL_URL is unset while OS_EXPECT_LIVE_DIALECT_MATRIX=1) while the other 52 pass — so the provisioned cell and the un-provisioned one are each what they claim.

⚠️No dialect divergence was found on Postgres. The card warned to expect one and to report rather than narrow the case-set if a type failed; none did, the case-set is untouched, and this is the honest result. The one cell that remains unmeasured by execution is MySQL: mysqld exits 1 with zero output in this container and no Docker daemon is reachable, so it is a declared skip — the same disclosure sql-driver-text-case-conformance.test.ts makes about its own MySQL cell. It is covered instead by the new bind block below, and named in the suite's head note rather than left to be inferred.

What is per-dialect, and what deliberately is not. The case-set has two directions and only one has a dialect. matches/compiles cases reach the driver, so they run once per cell. door-refusal cases assert parseFilterAST throws before any driver runs — a pure platform function with no dialect in it. Running those once per cell would not measure three things, it would measure one thing three times and report the repetition as coverage, so they are declared once in a block that says so in its name.

A final block asserts every accepted type binds into a statement on all three dialect clients. That is a real check rather than a restatement: this driver refuses an unbindable comparand from inside applyFilters, which throws while the statement is being built, so a type it could not bind on pg or mysql2 fails there with no server involved. It is the only layer that can speak for the un-provisioned cell.

2. sql-driver-icontains-and-retired-operators.test.ts declares dialectCell('sqlite')

The card required this be verified by operator, not by file, since #6518 makes FILTER_TEXT_CASES answer differently per dialect and a wrong call would hide a real gap. Measured off the exported case data, not the file's prose:

populationoperators
this suite (comment-masked source)$contains$endsWith$icontains$notContains$options$regex$startsWith — plus $or
FILTER_TEXT_CASES (walked from the case filters)$contains$endsWith$icontains$notContains$options$regex$startsWith

Exactly the same seven. Reading the spec file instead would have overstated it — its prose also mentions $ilike and $not, which no case exercises; that is precisely why this was measured off the data. sql-driver-text-case-conformance.test.ts runs that whole table once per cell of DIALECT_CELLS, so all seven are already answered on all three dialects. The extra $or is a combinator belonging to FILTER_LOGIC_CASES, matrix-routed by sql-driver-or-filter.test.ts.

The coverage overlaps, so the marking hides nothing. What is left over is this file's own residue — the GLOB metacharacter class, the lower(name) GLOB lower(…) construct, and the assertion that $contains and $icontains stop answering identically. GLOB is emitted on the SQLite dialects and nowhere else, so those blocks are about SQLite by construction: SQLite is the right cell, not merely a tolerable one. Option B ("convert both") remains rejected on the same measurement — converting this file adds no dialect coverage.

3. MATRIXED — the measurement, promoted

DIALECT_LEDGER is now empty, and check-driver-conformance.mjs gains a fifth invariant:

check-driver-conformance: OK — 45 covered cell(s), 0 in the DEBT ledger, 0 exempt.
check-driver-conformance: dialect axis — 7 conformance suite(s) across 1 dialect-capable
driver(s): 6 run the matrix, 1 declare named cell(s), 0 in the DIALECT ledger. 9 of 9
dialect-scored cell(s) have a matrix-routed suite. …

check:driver-conformance, before and after, and why the number moved. The covered-cell count is 45 on both trees and that is the point — no cell's CONSUMED answer moves, because nothing about coverage was wrong. What moves is the dialect line: 8 of 9 → 9 of 9, 2 in the DIALECT ledger → 0, 5 run the matrix → 6, 0 declare named cell(s) → 1. Every one of those is this PR's own doing, and none of it is a pre-existing green.

DIALECTED and MATRIXED are two different questions, and both are needed. DIALECTED is per FILE and asks for a stated stance. A tree where every suite honestly declares dialectCell('sqlite') satisfies it completely while enforcing Postgres at minimum nowhere. MATRIXED is per CELL and asks that the stance somewhere on that cell actually be the matrix.

A dialect-ledger row deliberately does NOT clear MATRIXED. The ledger excuses an undeclared file; MATRIXED is about a cell with no matrix-routed suite anywhere, which no row can supply. So the ledger now reaches exactly one shape: a second suite over a cell some sibling already routes through the matrix. That is a real narrowing of the escape hatch, it is stated in the ledger comment and in the error message itself, and it is pinned by a self-test — so nobody adds a row, watches it stay red, and concludes the gate is broken.

Ablation — the promoted invariant must be able to fail

Direction predicted in writing before the run, implementation committed first at 4fc5c3e792 so the restore had somewhere to go (#12204). Mutation: the comparand suite stops routing through the matrix and declares a named cell instead — the exact shape the promotion exists for, since it keeps DIALECTED satisfied. Proven on disk by anchored single-line grep counts before any result was read (never a multi-line -cF, which counts lines): removed text 0, injected text 1, on both edited lines.

treegateverdict
cleanfullexit 0 · 9 of 9 · 0 MATRIXED
mutatedfullexit 1 · exactly 1 MATRIXED naming FILTER_COMPARAND_TYPE_CASES · 0 DIALECTED
mutatedMATRIXED's errors.push deletedexit 0 · 8 of 9
mutated--self-test, same deletionexit 1 · 7 named failures

The second row is the claim: DIALECTED stays silent — the suite declares honestly — and only MATRIXED catches it. The third row is what the promotion bought, and it is #12014's defect reproduced with a real mutation: the pre-promotion gate prints the shortfall and passes.

Restore was verified, not trusted: trap … EXIT INT TERM, git checkout HEAD -- with absolute paths (never -- alone, which restores from the index the mutation wrote). git hash-object of both working files equals the blob id git rev-parse reports for the same path at HEAD, and git status --porcelain is empty.

Verification

Gate union derived, not recallednode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths; the script takes the changeset itself). All 21 families re-run at eee427ed4c, the final commit. Every exit code captured before any pipe (cmd > log 2>&1; ex=$?); each verdict below is the gate's own line, never $?.

gateverdict
check:driver-conformanceOK — 45 covered cell(s), 0 in the DEBT ledger, 0 exempt. + the dialect line above
check:driver-conformance --self-testOK self-test: … incl. the new MATRIXED directions
check:agent-test-spelling0 violations — 380 file(s) · 3875 bare -- token(s) …
check:cli-command-ids275 command-id literal(s) across 99 file(s) … all resolve
check:cross-package-test-inputsOK: 16 package(s) read outside themselves, all declared …
check:entry-guard164 scripts/ file(s) — every entry guard goes through invoked-as.mjs …
check:parse-guard163 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
check:pnpm-filter-targets136/173 --filter occurrence(s) across 28 file(s) resolve …
check:published-files69 publishable package(s) of 78 … admits no test …
check:slot-lookupratchet holds: 107 unswept site(s) in 25 file(s), none new
check:test-source-aliasOK — 72 packages with tests scanned …
check:type-source-resolutionOK — 93 tsc program(s) across 77 packages scanned
check-ci-filter-parityOK: all 96 declared cross-package glob(s) (81 unique) are covered …
check-cross-package-test-inputsOK: 16 package(s) read outside themselves, all declared …
check-plugin-teardown-shape63 Plugin implementation(s) across 4713 source(s) …
check-affected-docsexit 0
check:query-options-erasureratchet holds: 67 unswept non-test site(s) in 17 file(s), none new
check:type-check-coverageOK — 65/78 workspace packages type-checked …
check:engine-double-contractexit 0 · 391 (file, verb) row(s) held by the RETAINED ledger
check:where-matcher299 matcher(s) discovered, 299 answer … 0 silently-wrong
check:nul-bytesOK (scanned 6802 text file(s) … no raw ASCII control bytes).

The convention-triggered families the derivation named for "adds or edits a test file" are all in that list. check:type-check-debt is the one it named that is not run here: it needs the whole workspace closure built, and its own refusal on an unbuilt worktree means NOT MEASURED rather than not-applicable — declared rather than quietly omitted, and it runs in CI.

Package-level, under the shared verify lock: pnpm --filter @objectstack/driver-sql typechecktsc --noEmit clean, and the full suite 135 passed | 8 skipped (143) · 2061 passed | 125 skipped (2186). The typecheck is load-bearing here — it caught a FilterCondition | undefined the green vitest run could not see, which is the second commit.

Changeset

None, skip-changeset label. Two test files plus one repo gate script; nothing publishes. Measured rather than assumed: driver-sql's files whitelist is ["dist","README.md","CHANGELOG.md"], so neither test file ships, and scripts/ is not a package.

Graded against check-empty-changeset's own ledger (--self-test: 118 assertions over real temp git repos), which is one-directional on exactly this case: an empty changeset satisfies the same gate the label does, produces no CHANGELOG entry (measured), and is uniquely a real input to changesets/action — the hasChangesets && !hasNonEmptyChangesets branch that silently stalled 17.0.0-rc.2 in #4898. It buys nothing the label does not and uniquely carries the risk.

The label was applied through the whole-set fallback, declared rather than glossed: POST /issues/12302/labels (the additive endpoint) returned HTTP 403 — "GitHub access is not enabled for this session", the known per-session outage (#12123), which also took the PR-creation call. So the current set was read (size/l), the union computed, and both written together. Read back after the write; see the report comment on #12136.

Scope

Held to the declared surface — exactly the three files the card named, confirmed against origin/main:

 sql-driver-comparand-type-conformance.test.ts | 267 +++++---
sql-driver-icontains-and-retired-operators.test.ts | 45 ++-
scripts/check-driver-conformance.mjs | 267 ++++++---

PR #12287 (#11567) was re-checked at claim time: it touches sql-driver.ts, sql-driver-schema.test.ts, sql-driver-string-maxlength-varchar.test.ts and others — no overlap with these three. Nothing was re-located by a line number quoted in an older card; every anchor here was found by symbol.

Clause ② — no. Test coverage plus a gate invariant: no shipped behaviour, no public surface, no accept/reject door moves. It does change what two suites assert, which is what #12014 deliberately would not, and is why this was a separate card.


Generated by Claude Code

…atrix, and promote MATRIXED
Resolves the two seeded DIALECT_LEDGER rows and turns the census measurement
"every dialect-scored cell has at least one matrix-routed suite" into an
enforced invariant.
- sql-driver-comparand-type-conformance.test.ts now iterates DIALECT_CELLS.
Its case-set claims the six accepted comparand types "compile everywhere";
that sentence had only ever been measured on SQLite. Measured on live
PostgreSQL 16.13: all eight executed cases answer the case-set's row ids.
The door-refusal half stays declared once — parseFilterAST is upstream of
every driver and has no dialect in it.
- sql-driver-icontains-and-retired-operators.test.ts declares
dialectCell('sqlite'). Measured BY OPERATOR before marking: the seven text
operators it reaches are exactly the seven FILTER_TEXT_CASES reaches, and
sql-driver-text-case-conformance.test.ts runs that table once per cell.
- check-driver-conformance.mjs gains MATRIXED, the per-cell invariant, and
DIALECT_LEDGER is now empty. A ledger row deliberately does NOT clear
MATRIXED: it excuses an undeclared FILE, not a CELL with no matrix-routed
suite anywhere.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6HFzyH98W1YaQXhJUJt6o
…the bind probe
`parseFilterAST` is typed `FilterCondition | undefined`, which `tsc --noEmit`
caught and the vitest run could not. Asserted rather than `!`-ed: a case that
produced no condition would compile a bare select, and the binding assertions
would then be measuring an empty statement rather than a bound comparand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W6HFzyH98W1YaQXhJUJt6o
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • 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.

Coarse fallback — 0 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 5ce5f8c12479f40d548b9f3ba9f8725c14c9bf9apackageMentionDocs.

@os-warrenos-warren added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed tests labels Aug 25, 2026 — with Claude
@os-warren
os-warren marked this pull request as ready for review August 25, 2026 17:31
@os-warren
os-warren enabled auto-merge August 25, 2026 17:31
@os-warren
os-warren added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit f0beff3Aug 25, 2026
43 checks passed
@os-warren
os-warren deleted the claude/issue-12136-dialect-ledger-resolve-and-promote branch August 25, 2026 17:56
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Resolve the 2 DIALECT_LEDGER rows, then promote "every dialect-scored cell has a matrix-routed suite" to an invariant (#12014 follow-up)

2 participants

@os-warren@claude