Repository navigation
Conversation
The live parity check compares the lint with Metabase on one folder, examples/v1, plus a fixed list of mutations. This adds a corpus of folders, each the transforms part of the examples with a few edits, and what a real Metabase did with each: whether it imported the folder, the tables each transform reads, and whether it accepts each transform test. - scripts/record-corpus.ts is the only writer of cases. It commits each case to a branch of the git sync repository, imports it, and probes as lint-parity.ts does. Generated cases (corpus-gen.ts, fast-check) that disagree are shrunk to the fewest edits; agreeing cases are kept when they cover a shape or answer no kept case covers. - scripts/corpus.test.ts replays every case offline in bun test. A case the lint disagrees with holds the exact differences in `known`, so a fix or a new difference fails the test. - --check records every case again; the Import workflow runs it so a change in Metabase's answers fails CI. - The Metabase API calls of lint-parity.ts move to metabase-api.ts, unchanged, so both scripts share them. The first corpus records 25 cases. Six carry known differences, including a nested optional clause whose outer table the lint misses, and false missing-input/unused-input errors on an MBQL test whose table reference has a null schema.
Their tests pass while the lint stays wrong in the recorded way, so a green run alone reads as agreement. Prefix their test names with KNOWN DIFFERENCE, and print the list after the run, where CI shows it.
The check ran after a failed import with no bun installed, and relied on the parity steps to enable transforms and read-write git sync. Gate it, and bun, on the API key, which setup creates before the examples import, and have record-corpus.ts enable both settings itself. Metabase answers the remote-sync-url setting as plain text; read it as text, and stop when it is empty.
The self-review of #40 ran no live Metabase. Six of its findings are now corpus cases, recorded from Metabase, which confirms all six: false errors on a Python source table_id: null, a field-filter tag's table and files holding [] or '', and missed refusals of an input for a field-filter tag's table, a quoted name in another case, and a source.type of native. - compare: after an import Metabase took, a lint schema error is a difference, since an error says Metabase refuses the file. - corpus-gen: quoted names in another case than the table's. A run with seed 11 found the MBQL form of the quoted-name refusal (an input PUBLIC.ORDERS) and expectation SQL with template tags, which Metabase cannot parse. - The keeper starts from the cases the corpus holds, so a new run keeps only what they do not cover. - Write edited files with YAML 1.1's rules, as Metabase and the lint read them: 1.2's writer left 2024-01-01 09:00:00 unquoted, a timestamp in 1.1. gen-7-0 failed to import only for that; drop it.
A dev build of Metabase validates rows on insert, so it refuses a transform with source.type native for the same reason as the published image, in other words. --check now compares the import status, refusal codes and tables read, and leaves messages out. Against Metabase master run from source, all 41 cases answer the same as the image recorded. The README says committed cases are recorded with the image CI uses.
galdre
force-pushed
the
galdre/lint-parity-api-probes
branch
from
November 2, 2026 23:49
6c34413 to
e85d5a8
Compare
galdre
force-pushed
the
galdre/lint-parity-api-probes
branch
from
November 3, 2026 17:27
e85d5a8 to
f723b2a
Compare
galdre re-cut the lint stack, so this merges galdre/lint-parity-api-probes at f723b2a rather than rebasing (no force push). - Every file outside this PR's own takes the re-cut's version. A merge would otherwise bring back 59 example files the re-cut reverted. - lint-parity.ts keeps the re-cut's changes (default-schema parsing, the missing-folder check, "compared nothing" for an empty folder) on top of the extraction into metabase-api.ts, which now uses the shared isStringArray from src/values.ts. - import.yml keeps the pinned setup-bun SHA from #41 with the corpus step's API_KEY condition. - refresh-known.ts recomputed each case's `known` against the re-cut lint. No difference grew: 8 cases now agree with Metabase (finding-44-nested-optional-clause, finding-46-mbql-null-schema, gen-11-131, gen-7-137, both self-review-2.2 cases, self-review-2.3, self-review-3.1), gen-11-107 and gen-7-18 each lose one difference, and the rest change only wording ("no error rule predicts"). 15 of 41 cases now hold a known difference, down from 23.
galdre
force-pushed
the
galdre/lint-parity-api-probes
branch
2 times, most recently
from
November 4, 2026 05:06
e74100d to
4632623
Compare
galdre
force-pushed
the
galdre/lint-parity-api-probes
branch
2 times, most recently
from
November 4, 2026 21:32
7dbe8ca to
625f394
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The lint has one job: give the answer Metabase gives. Today the live parity check (
scripts/lint-parity.ts) compares the two on one folder,examples/v1, plus a fixed list of mutations. A review of this stack found 17 places where the lint and Metabase may disagree, and none of them show up in that one folder.This PR adds a corpus: many small folders, each with what a real Metabase did with it, replayed against the lint in
bun test.How it works
corpus/base/(the transforms part ofexamples/v1) plus a few edited files, and Metabase's recorded answers:scripts/record-corpus.ts, the only writer of cases): it commits each case to a branch of the git sync repository, imports that branch, and probes aslint-parity.tsdoes. So the expected answers always come from Metabase, never from the lint. A server error becomes an answer only when Metabase gives it twice.scripts/corpus-gen.ts, fast-check): it rewrites transform SQL and edits test inputs and expectations, using the shapes the lint has to get right:The recorder shrinks a disagreeing case to the fewest edits that still disagree. It keeps an agreeing case only when the case covers a shape, spelling or answer that no case kept so far covers.
scripts/corpus.test.ts): it lints each case and compares the result with the recorded answers, usinglint-parity.ts's comparisons plus three more: a failed import must be explained by a lint error on the entity Metabase names, every test must have an answer, and a run where every answer was skipped fails. A case the lint disagrees with stores the exact differences inknown, so a fix or a new difference fails the test.scripts/refresh-known.tsupdatesknownafter a lint change, without touching the recorded answers.record-corpus.ts --check, which records every case again against the latest Metabase build and fails when an answer changed.lint-parity.ts's Metabase calls moved unchanged toscripts/metabase-api.ts, so both scripts share them.What the first corpus says
25 cases, recorded on Metabase enterprise head (2026-11-02): 5 hand-written from review findings, and 20 kept from 150 generated with seed 7. In the first recording, 12 cases carried known differences, from 4 causes (the first commit message says six; that is wrong):
src/sql-tables.ts:72)missing-inputsnullschema, test inputpublic.orders(src/input-match.ts:22)missing-inputandunused-inputerrors. 1 case.[[...]]with no tagunparseable-sourcereads-not-checkedwarning, although its message already says Metabase cannot parse it. 7 cases, in transforms and in expectations.sqlglot call failed: TokenError)The self-review of #40 at
6405d92(report) ran no live Metabase. Six of its findings are corpus cases, and Metabase confirms all six:table_id: null, as Metabase writes itschemaerrororders.created_at, inputordersmissing-inputproducts.category, plus an input forproductsunused-inputs[]or''schemaerror on eachFROM "Orders", inputordersunremapped-referencesource.type: nativeA second generator run (seed 11) found two more causes on its own:
PUBLIC.ORDERS. Metabase refuses it withunremapped-reference, and the lint passes it.{{tag}}or[[...]]. Metabase refuses it withunparseable-source, and the lint is silent.Two suspected differences turned out to agree with Metabase: the tables read by multi-statement SQL, and by
INSERT ... SELECT.Validation
bun test(824 pass) and build pass.knownthat doesn't match exactly fails--checkagainst the same Metabase answered all cases the same.knowncases passed on any difference--checkanswered all 25 cases the same, including the HTTP 500, which came back twice.--findingsrewrote the 5 finding cases byte for byte.bun testprints the cases that hold a known difference after the run, and their test names start withKNOWN DIFFERENCE.Not covered yet (follow-ups)
Also seen while recording:
examples/v1fails to import: Metabase refuses the data-app action "Update order".