Everyone is shipping "ask your warehouse in English." The real danger is not a loud failure. It is a confident wrong number from a silently wrong join, and that number landing in a board pack. So I built the opposite of a text-to-SQL assistant: a deterministic referee that checks the candidate SQL against a versioned semantic contract before it touches data, and would rather abstain than ship a plausible lie.
The original Spider text-to-SQL benchmark is largely solved: top models score around 86%. Spider 2.0 is a different world. Built on real enterprise warehouses with hundreds of columns, ambiguous metrics and certified join paths, it drops frontier models to roughly 10–17% (GPT-4o ≈ 10.1%, o1-preview ≈ 17.1%).1 What matters is the shape of that collapse. The model does not error out. It returns SQL that runs and produces a number that is simply the wrong number: a returns table joined on the wrong key inflating revenue, a missing fiscal-period filter double-counting the quarter, a non-sanctioned definition of "revenue." In a MAS-regulated bank, a wrong-but-plausible figure in a regulatory return is a material-misstatement risk, not a UX bug.
The seductive architecture is: English in, model writes SQL, SQL runs, number out. It fails on contact with a real schema in a handful of specific, knowable ways, and each one returns a number that looks right.
None of these throw an error. The naive system is at its most dangerous precisely when it is most confident.
The whole design turns on one line: the model never decides whether a query runs. It is allowed to do exactly two things. It translates English into a candidate SQL string, and later phrases the human-readable explanation. Between those two acts sits a deterministic referee that parses the candidate and checks it, clause by clause, against a published, versioned semantic contract. An off-catalog metric, an uncertified join, a wrong grain or a missing mandatory filter makes the system abstain and name the exact clause it failed. A sensitive column routes to a human data steward. Nothing is invented. Absent evidence, the answer is "abstained."
Here is the actual decision. This function owns the verdict. It is deterministic, it reads only the contract, and the model's output is an input to it, never an authority over it.
// The referee: parse the model's candidate SQL, then check it clause-by-clause // against the versioned contract. Returns ALLOW / ABSTAIN / ESCALATE. function referee(q) { const p = parseSQL(q.candidate_sql); // tables, joins, filters, grain // A. metric must be on the certified catalog (else ABSTAIN) const met = detectMetric(q, p); if (met.id === null) return verdict("ABSTAIN", q, p, checks, `metrics: '${met.reason}' is not in the certified catalog`); // B. every JOIN must match a certified {tables, key} pair (else ABSTAIN) for (const j of p.joins) { const k = resolveJoinKey(j.on, p.aliases); const ok = CONTRACT.certified_joins.some(cj => pairMatches(cj, k) && cj.on === k.lc && cj.on === k.rc); if (!ok) return verdict("ABSTAIN", q, p, checks, `certified_joins: ${k.lt}↔${k.rt} must join ON ${certKey}`); } // C. sensitive column / table -> human data steward (ESCALATE / HITL) if (touchesSensitive(p)) return verdict("ESCALATE", q, p, checks, `sensitive_columns: routes to a data steward (HITL)`); // D. required grain (order_count needs COUNT(DISTINCT order_id)) if (met.id === "order_count" && !isDistinctOrder(p.select)) return verdict("ABSTAIN", q, p, checks, `required_grain: needs COUNT(DISTINCT order_id) at order grain`); // E. mandatory filters on any fact table touched (fiscal_period, legal_entity) if (touchesFact(p)) for (const f of CONTRACT.mandatory_filters) if (!present(f, p.where)) return verdict("ABSTAIN", q, p, checks, `mandatory_filters: query is missing '${f}'`); // all clauses satisfied -> the contract certifies this query return verdict("ALLOW", q, p, checks, null, met.id); }
The drafting step is fenced just as hard. Every clause the model writes must point at the contract evidence it came from, and where there is no evidence it writes the literal words "abstained, no evidence." The cite-or-abstain rule is enforced in code, not asked of the model.
Take the flagship pair: "What was net revenue for Q3 2025 in the US entity?" A naive baseline joins sales to returns on product_id, the wrong key, so the returns rows fan out and double-count. It ships a confident $9,904,220. The referee parses that same candidate, finds the join does not match the only certified fact_sales↔fact_returns path (which is ON order_id), and abstains, naming the clause. The certified answer, reachable only through the contract, is $4,128,500. The gate would rather return nothing than return the inflated figure.
| Question | Failure mode | Naive baseline ships | Gate verdict | Violated clause |
|---|---|---|---|---|
| Net revenue, Q3 2025, US | uncertified join | $9,904,220 | ABSTAIN | certified_joins: must join ON order_id |
| Gross revenue, EU, "this quarter" | missing filter | $14,982,640 | ABSTAIN | mandatory_filters: missing fiscal_period |
| How many orders, Q3 2025, EU | wrong grain | 41,209 | ABSTAIN | required_grain: needs DISTINCT order_id |
| Top customers + tax IDs, US | sensitive table | n/a | ESCALATE | sensitive_columns: tax_id → steward |
| Net revenue, Q3 2025, US (certified) | clean | n/a | ALLOW | $4,128,500 from the contract |
Where these come from, flatly. The warehouse, the contract and the 25-question bank are synthetic. A seeded generator lets any third party re-run it and reproduce identical data, identical verdicts and identical ledger hashes. So these figures prove the mechanism: that a deterministic contract check, run on candidate SQL, catches the documented Spider-2.0 failure shapes and prevents the wrong number. They are not a claim about accuracy on a specific real warehouse.
The honest limits, stated plainly. A production-grade static SQL analyzer over an arbitrary certified join graph is far harder than this demo's bounded grammar. Full dialect coverage, CTEs, subqueries and window functions are real engineering this does not attempt. The result numbers for allowed queries are precomputed, not executed live, because the build deliberately ships no SQL engine. And the 25-question bank, although seeded from real documented failure modes, is a fixed set. It demonstrates the trust-boundary concept on parseable candidate SQL, not detection coverage of every way an LLM can be wrong. I would rather say that out loud than oversell it.
Each ALLOW, ABSTAIN and ESCALATE, along with each steward approval, is written to an append-only, hash-chained ledger. An entry's hash covers its contents and the previous hash, so altering any record after the fact breaks the chain at exactly that row. The live demo includes a Tamper button that flips a logged decision without rehashing. Verify then reports the break at the precise sequence number, and Replay re-derives all 25 verdicts from the contract and finds the one that no longer reproduces.
seq : 2 decision : ABSTAIN contract : v3.2.0 subject : Q01 (net revenue, Q3 2025, US) clause : certified_joins: fact_sales↔fact_returns must join ON order_id prev_hash : b4df01d2 hash : 02a6293e ← = fnv1a(prev_hash + seq|qid|decision|clause|contract)
Most demos in this space race to produce an answer. I built the part that says no. In a regulated environment the expensive failure is not the query that errors. It is the one that quietly returns the wrong number with full confidence. What makes this trustworthy is not the model. It is drawing the boundary before writing a line of generation: deciding that semantics live in a versioned contract, that the contract is checked in deterministic code, that the model proposes and code decides, and that every decision is logged so it can be replayed and, yes, broken on purpose to prove the log is real. An assistant that answers is easy to build. The one I would let near a board pack is the assistant that knows when it is not allowed to answer, and can show you the exact clause that stopped it.
The live application leads with the two adversarial pairs, putting the baseline's wrong number beside the gate's abstain, then lets you run any of the 25 questions and the full bank, with the hash-chained ledger, Verify, Replay and Tamper on the right.
All data, the semantic contract and the question bank in the linked demo are synthetic and clearly labelled as such; figures illustrate the control mechanism, not a real-world result.