| # Backlog |
|
|
| Single living tracker for code/eval work items. Supersedes the scattered per-audit |
| lists β new findings land here with a status so nothing rots undone. |
|
|
| Status: β
done Β· β gated (needs a human decision or an external host) Β· π won't-fix (verified) |
|
|
| ## Done (2026-07-11) |
|
|
| Security / public surface |
| - β
AST guard: `SELECT β¦ INTO` denied; `pg_sleep_for/until`, `set_config`, `setval` banned; adversarial tests per vector. |
| - β
Streamlit `Sentence` renderer HTML-escaped (stored-XSS class closed). |
| - β
API: rate limit unconditional (anonymous bucketed by IP); key via `Settings` (`.env` honoured); `secrets.compare_digest`; 500s redacted to a `trace_id`; `truncated` surfaced in `AskResponse`. |
|
|
| Metric integrity |
| - β
BIRD rescue hints gated behind `PipelineConfig.enable_bird_rescue_hints` (default **off**); product path no longer serves canned per-question answers. `eval_baseline.py --bird-rescue-hints` applies them to the single-run β measured at 62.5%, *not* the archive's 94.0% (see the audit section below). |
| - β
Reproducible single-run measured and committed (config E, codestral β `eval/baselines/reproducible_n200.json`; 57.5% as measured then, 58.0% after the comparator fix below). |
| - β
Metric split into labelled tiers in README + methodology Β§13, each marked by whether it runs in the product. (Corrected in the third pass below: the tiering claimed the hints flag reproduced 94.0%. It does not β it yields 62.5%.) |
| - β
Machine-readable artifacts synced: v31 report summary 185/0.925 β 188/0.94; `/eval/latest` serves the reproducible baseline (not a stale 60.5%); FastAPI description honest; regression test pins `overall == recompute(records)`. |
|
|
| Postgres path |
| - β
Read-only made real: `postgresql_readonly` execution option (was a no-op `SET` inside an open transaction); DSN normalised to `psycopg` v3 (path had never run under psycopg2). Loader `scripts/load_postgres.py`; registry registers PG from `NL_SQL_PG_DSN`. Live PG16 proof in CI (`tests/test_postgres_readonly.py`). |
|
|
| Docs / hygiene / CI |
| - β
README/DEPLOY truthful: Langfuse + vcr.py claims removed; unused langfuse compose service dropped; DEPLOY.md rewritten for the real HF Space (tracked-only + prune). |
| - β
CWD-independent paths (`nl_sql.paths`); `ProviderName` synced with the factory (+`perplexity`, +`gracekelly`). |
| - β
Nine voting scripts on the unsafe comparator frozen in `scripts/archive/` + CI gate (`check_no_raw_compare.py`). |
| - β
CI: mypy over `src app scripts`; coverage floor (`--cov-fail-under=85`, currently 91%); aligned on Python 3.13. |
| - β
`docs/SESSION_HANDOFF.md` un-tracked (kitchen); eval trace moved into methodology Β§13. |
|
|
| ## Done (2026-07-11, second pass β the Postgres claim, actually run) |
|
|
| - β
**PG data load + PG-backed eval.** `codebase_community` (StackExchange-mini, 741 646 rows, 385 MB) loaded into a live Postgres 16 from BIRD's own PG dump via `scripts/extract_pg_dump_slice.py` (the dump ships all eleven databases in one `public` schema; the slice keeps one). Row counts verified against SQLite table by table. The product pipeline then ran on BIRD's **Postgres gold**: **49.0% EA (24/49)** vs 44.9% on SQLite for the same questions, 100% validity, 88% agreement between engines β the engine ports. Artefact: `eval/baselines/postgres_codebase_community_n49.json`; method: `docs/03_eval_methodology.md` Β§14. |
| - β
**Two product bugs that only a live Postgres could expose** (both invisible on SQLite): |
| - `execute_readonly` ran statements through `exec_driver_sql`, which hands the driver an empty parameter set β psycopg (`pyformat`) then parsed `%` as a placeholder and killed every `LIKE '%x%'` and every `%` modulo, *including BIRD's gold* (qids 586/587 couldn't even be scored). Now executed on a DBAPI cursor with no parameters, so neither `%` (psycopg) nor `:__` (SQLAlchemy `text()`) is interpreted; driver exceptions are re-wrapped into `sqlalchemy.exc` so error classification is unchanged. |
| - `_normalise_cell` never converted `Decimal` β `float` (its docstring claimed it did), so Postgres `numeric` results (`AVG`, `SUM`, any division) skipped the float-tolerance grid and correct answers scored misses. Cost 4 EA points on the PG run (40.8% β 49.0%). |
| - β
SQLite headline unaffected by *these two* fixes: re-measured n=200 β still 57.5% at the time. (A third comparator bug, found in the audit below, later moved it to 58.0%.) |
|
|
| ## Done (2026-07-11, third pass β adversarial audit of the fixes themselves) |
|
|
| The code written during the two passes above had not itself been reviewed. Five real defects, all found by reading it back β the first one by *running* the command the docs told reviewers to run: |
|
|
| - β
**The docs claimed a number the command does not produce.** README, methodology Β§13 and this backlog all said the hint-assisted tier reproduces with `eval_baseline.py --config E --n 200 --bird-rescue-hints`. Run it: **62.5%** (125/200), not 94.0%. The 94.0% artefact (`eval/reports/2026-05-26/v31-v30-plus-p3f-q37-merged.json`) is a *merge of ~20 runs* β its own `sql_model` field lists codestral + Sonnet 4.6 + GPT-5.2 + Grok-4.1 + Kimi-K2 + Llama-4-Scout + Qwen3 + gpt-oss voting across Groq/OpenRouter/Perplexity bridges, plus archive-rescore, plus the hints. Presenting that as "the pipeline with hints on" both overstated what the flag does and *understated* what the number cost. Tiers are now four, each labelled reproducible-or-archive, and the two reproducible ones carry the numbers their commands actually print. |
| - β
**The rate limit could be dodged with one header.** `require_api_key` bucketed on `X-API-Key` even when *no* key was configured β which is exactly the keyless public deploy the limiter was added to protect. The header is then unauthenticated, caller-controlled input: a fresh random key per request minted a fresh bucket, so the 60/min limit never fired, and `_hits` grew without bound (an OOM vector driven by remote input). It now buckets on the header only when a key is configured, and therefore verified; otherwise it falls back to the client IP. Stale buckets are swept once per window. |
| - β
**One accented byte in `X-API-Key` returned 500 instead of 401.** `secrets.compare_digest` raises `TypeError` on a `str` holding any codepoint above 127, and Starlette decodes headers as latin-1. Keys are compared as bytes now. |
| - β
**The comparator scored correct answers as misses (int vs float).** `_hashable` quantised floats onto the tolerance grid but passed ints through untouched: gold `5` hashed to `5`, pred `5.0` to `5_000_000`, and the set comparison called it a mismatch. That is *stricter than BIRD's own scorer*, which sets raw Python tuples β where `5 == 5.0` collapses β and it contradicted the apples-to-apples claim in the docstring. The ordered path compares through a tolerance and always got these right, so one (gold, pred) pair scored two different ways depending on whether gold had `ORDER BY`. Every number β int, float, and (post-`_normalise_cell`) Decimal β now shares one grid. Re-measured: **SQLite n=200 β 58.0% (116/200), up from 57.5%**; the recovered question is a `challenging` one (41.2% β 44.1%). Postgres n=49 unchanged (49.0%), SQLite control n=49 unchanged (44.9%), Chinook still 100%. The fix is monotone β it can only merge buckets that were wrongly split β so no previously-passing question can regress. |
| - β
**Engine leak on the hot path.** `DatabaseSpec.make_engine()` called `create_engine` on *every* invocation β once per `/ask`, once per eval question β so no pool was ever reused and none disposed, while `connect()`'s docstring promised reuse. On Postgres that opens a fresh backend per question and walks straight into `max_connections` under sustained load. Engines are built once per spec and pooled now (`dispose_engines()` clears the cache). Paired fix: the SQLite statement-timeout progress handler is disarmed on the way out β pooled connections get handed to the next caller (schema introspection never arms one of its own), and a handler left behind carries an already-expired deadline that would interrupt that caller's first non-trivial query. |
|
|
| ## In progress β lifting the reproducible number (2026-07-11) |
|
|
| The reproducible single-run is the only number that matters here: 58.0% at the |
| start of this pass. Every step below is measured on the same n=200 BIRD Mini-Dev |
| slice, config E, codestral, hints off. |
|
|
| | Step | EA | Ξ | Verdict | |
| |---|---:|---:|---| |
| | Baseline (after the comparator fix) | 58.0% | β | β | |
| | **Question + `evidence` moved to the top of the prompt** | **61.0%** | **+3.0** | β
kept | |
| | + BIRD's `database_description/*.csv` in the schema block | 59.5% | β1.5 | β gated off | |
| | + self-consistency (config F: 4 candidates at T=0.2β0.8, execution voting) | 58.0% | β3.0 | β not taken | |
| | **+ few-shot from BIRD train (k=3)** | **61.5%** | **+0.5** | β
kept β **the product number** | |
|
|
| The few-shot result is one question, which is noise on n=200 β but it is worth |
| keeping for a reason that has nothing to do with the delta: **the product was |
| already running with it.** `api/main.py` builds its pipeline with |
| `fewshot_top_k=3`, while config E hard-coded 0 despite being named |
| `E_dense_fewshot_repair`. The eval harness was measuring a pipeline the product |
| does not run, and undercounting itself. Now the command in the README and the |
| live demo evaluate the same thing. |
|
|
| Self-consistency is worth a word, because it is the technique everyone reaches |
| for. Config F turns *repair off* (`Repair fired: 0/200`) on the theory that |
| voting replaces it, and samples at T=0.2β0.8. Both halves hurt: every candidate |
| is worse than the greedy pass, and the vote does not recover the difference. F |
| tops out at its own first pass (58.0%), while E's first pass is 59.5% and repair |
| carries it to 61.0%. Diversity through *temperature* does not pay on codestral. |
| Diversity through *different models* did β that is what took the archive to |
| 85.5% β and it is now blocked on provider keys, not on code: |
| `scripts/ensemble_providers.py` is written and waiting (Groq 403, GitHub Models |
| 401, OpenRouter's free tier 429). |
|
|
| **What the two results say together.** The win did not come from telling the |
| model something new β `evidence` was always in the prompt. It came from moving it |
| out of the middle, where models attend least: it sat after the schema block and |
| the few-shot block. The project's own residue analysis had said so |
| (`docs/kimi_v9_residue_analysis.md:27`) and nobody had acted on it. |
|
|
| The column-descriptions result is the same mechanism running backwards. They are |
| real information β BIRD's own gloss on its cryptic identifiers β and feeding them |
| is standard practice in BIRD systems. But they add thousands of characters per |
| table, the schema block swells, and the question drifts back toward the middle. |
| The information gained did not pay for the attention lost. Loader and renderer |
| are kept and tested; the flag is off. The version worth trying is column-level: |
| retrieve descriptions only for the columns a question plausibly touches. |
|
|
| Rule of thumb this pass earned: **space at the top of the prompt is a scarce |
| resource, and any lever that lengthens the prompt must pay for the room it takes.** |
|
|
| ## Next levers (research, 2026-07-12 β see `docs/quality_levers_research_2026-07.md`) |
| |
| After the lever-exhaustion day (eight levers measured dead or parity), three |
| research passes mapped what is left: training-free techniques through July 2026, |
| free API tiers, and a completeness sweep over the established named systems |
| (DIN/DAIL/C3/MAC/PET/TA/E-SQL/RSL/MCS/CHASE/OpenSearch/Alpha/XiYan + the |
| NL2SQL360 taxonomy). Ranked worklist β every measurement is a full n=200, |
| flag-gated, one lever per run: |
| |
| - β **Gemini 3 Flash as generator** β AI Studio free tier fits n=200 in a day |
| (1,500 req/day, no card); `_openai_compat.py` already speaks the protocol. |
| Gated on creating an API key. Highest-variance option; pilot rule binding. |
| - **Question-driven value retrieval (CHESS-style)** β ~5 pp in CHESS's ablation; |
| honest estimate here +0.5β2 pp (static top-K samples are already in the |
| prompt; only part of the miss class is literal grounding). No keys needed. |
| - **DAIL-style few-shot selection** β mask schema tokens before embedding, |
| optional SQL-skeleton re-rank (MCS-SQL ablation +4.8%). Cheap upgrade of the |
| existing dense top-3 retrieval. |
| - **Instance-aware synthetic few-shot** (CHASE, +9.3 in its single-generator |
| ablation β the largest legal number in the literature, discounted by the |
| DAC transfer precedent: their +6.2 measured β6.5 here). |
| - **Question enrichment** (E-SQL, ~+5% on challenging, single-candidate by |
| design) and **error-class micro-prompts** (output-column completeness, yes/no |
| format, LIMIT discipline β priced at 1β2 questions each by the v18 audit). |
| - **Intermediate representation / query-plan CoT** β held back on codestral |
| (CoT transfer risk); first in line for re-test if the generator changes. |
| - β **Qwen3 Coder 480B via OpenRouter `:free`** β 50 req/day makes n=200 a |
| multi-day run; gated on an account/key and an endpoint canary. Devstral β |
| a cheap probe on the existing key, low prior. |
| - Not worth runs (evidence in the research doc): DecoSearch-style decomposition, |
| reflective self-refinement, semantic post-repair, SEED auto-evidence, |
| deterministic AST normalization, and every component that maps onto an |
| already-measured-dead channel (MAC/RSL/PET/Alpha/SQL-o1 parts). |
| |
| Also queued: an Arcwise rescore of the current reproducible baseline (no-LLM, |
| ~90 s) so the honest triplet exists for the product number, and a public |
| ceiling-anatomy write-up. Side-fact for the latter: ReViSQL's BIRD-Verified |
| found defects in 61.1% of BIRD Train instances β a second team independently |
| confirming the annotation-noise thesis behind the ~61β62% free-stack ceiling. |
| |
| ## Gated (needs a decision or an external host) |
| |
| - β **Sustained-load benchmark.** The data-migration half is done (above); a throughput/latency run under sustained load still wants a host that isn't the Windows dev box. |
| - β **Space cleanup + git history.** `SESSION_HANDOFF.md` is un-tracked now, so future deploys won't republish it; removing it from the live Space and any git-history rewrite are force/deploy actions β owner's call. |
| - β **External pen-test of the public API.** Self-attestation is not a substitute; needs an outside pass. |
|
|
| ## Won't-fix (verified) |
|
|
| - π `execution_accuracy._hashable` float-vs-float bucket boundaries β 0 set-mismatch across v22βv31 baselines; O(nΒ²) replacement not justified. (This verdict looked only at float-vs-float. The int-vs-float half of the same function *was* broken β see the audit section above.) |
| - π `cache.py` miss/fill race β fires only under parallel eval workers, which aren't used. |
| - π Chasing BIRD annotation-quirk residue above 94% β saturation shown by 3-model reasoning sweeps. |
|
|