Twenty-sixth handover. Design session, no code touched. Tests unchanged at 1260 / 0 / 1. Captures: the Phase 2 grammar decisions (ADR-0032 accepted), the implementation plan at docs/plans/20260520-adr-0032-phase-2.md with seven sub-phases and a cross-cut verification matrix that explicitly names every "X comes for free" claim from ADR-0030/ 0031/0032, and the Phase-1 carry-over finding the warning/error guideline check surfaced — SQL WHERE expressions currently emit no LIKE-on-numeric / = NULL / type-mismatch warnings because sql_expr builds no AST. ADR-0032 §11.6 closes the gap; the plan's cross-cut matrix has a named row to prevent regression. The next session is Phase 2 sub-phase 2a (grammar fragment) per the plan; standing authorizations apply. Status: ready to hand over.
14 KiB
Session handoff — 2026-05-20 (26)
Twenty-sixth handover. Design session. ADR-0032 (the full
SQL SELECT grammar — ADR-0030 Phase 2) is Accepted and an
implementation plan sits next to it under a new
docs/plans/ directory. No code changed this session; the next
session is implementation territory.
§1. State at handoff
Branch: main. Working tree clean. One unpushed commit
(be8a4f5..a7db7dd).
Tests: 1260 passing, 0 failing, 1 ignored — unchanged from handoff-25 (no code touched).
Clippy: clean (last verified at handoff-25; no code changed since).
New commit since handoff-25:
a7db7dd docs: ADR-0032 + Phase 2 plan — full SQL SELECT grammar
A single docs commit; ADR + plan + index update ship as a coherent unit because the plan is Phase-2's mechanics for ADR-0032's decisions.
§2. What got designed — the shape now
Read in order:
docs/adr/0032-sql-select-grammar.md— the grammar decisions for Phase 2.docs/plans/20260520-adr-0032-phase-2.md— the implementation plan walking the work in seven sub-phases.
2.1. ADR-0032 in one screen
Phase 2's grammar surface (§§1–9 of the ADR):
- Five JOIN flavours:
INNER,LEFT [OUTER],RIGHT [OUTER],FULL [OUTER],CROSS. NATURAL/USING/comma-FROM are OOS. - All four set ops:
UNION,UNION ALL,INTERSECT,EXCEPT. - CTEs:
WITHandWITH RECURSIVE, with optional(col-list). - Subquery expressions as additive
sql_exprbranches: scalar(SELECT …)primary,IN (SELECT …),[NOT] EXISTS (…). Redeems ADR-0031 §7 OOS-1. - Qualified column refs
t.c/alias.cas aname_or_calltail. Redeems ADR-0031 §7 OOS-2. LIMIT n [OFFSET m]; legacy comma form OOS.DISTINCT/ALL,t.*projection, bare-alias projection (lifts Phase-1 §4.2's autonomous decision).- One new grammar file:
src/dsl/grammar/sql_select.rs, parallel tosql_expr.rs, exportingSQL_SELECT_STATEMENTandSQL_SELECT_COMPOUND.
Walker-capability honesty (§10) — the part where ADR-0030 §8's "ambient assistance comes for free" got softened:
- Grammar recursion still needs nothing new —
Subgrammar- ADR-0026's depth cap unchanged.
- Completion scope needs a new node variant
Node::ScopedSubgrammar(&Node)alongside the existingNode::Subgrammar, plus afrom_scope_stack: Vec<ScopeFrame>onWalkContext. EachScopeFrameholdsfrom_scope,cte_bindings,projection_aliases. Existing DSLExprandsql_exprrecursion stays on the non-scope-pushing variant and is unaffected. - CTE column resolution (§10.3):
SELECT *and explicit- projection CTE bodies both yield real column completion pastcte_alias.|via a body-projection derivation rule that runs at the body'sScopedSubgrammarexit. The earlier "honest limitation" posture is replaced. - Qualified-prefix completion (§10.5): at
t.|, candidates are scoped tot's binding. - Projection-before-FROM problem (§10.6): the debounced
re-walk catches up via a post-walk fixup pass that re-
resolves projection-list identifiers against the final
from_scope, rewriting highlight class and validity diagnostic in the walker's accumulated output. Runs as a final stage of the walk itself — same convention applies to the §11.6 predicate-warnings pass.
Diagnostics (§11) — every Phase-2 validation case classified against ADR-0027's ERROR/WARNING guideline:
- Five new
diagnostic.*keys (parse-time-detectable):unknown_qualifier,ambiguous_column,projection_alias_misplaced,cte_arity_mismatch,compound_arity_mismatch. - Eight new
engine.*keys for friendly-error layer translations of engine messages. - Three diagnostic passes by end of Phase 2 (§11.7): schema-existence (extended for multi-binding scope), arity- check (new), predicate-warnings (extended with a MatchedPath-walking variant — §11.6).
Result-column type resolution (§12):
rusqlite0.39.0 exposescolumn_table_name/column_origin_name/column_database_nameonRawStatementbehind acolumn_metadatafeature. Verified during this session by reading the rusqlite source.Cargo.tomlchange is one line — add"column_metadata"to the feature list.- Bare column refs recover their playground type via that metadata; partially lifts Phase-1 §4.5's bool→0/1 deferral.
2.2. The implementation plan
docs/plans/20260520-adr-0032-phase-2.md (881 lines) breaks
Phase 2 into seven sub-phases:
- 2a — Grammar fragment (
sql_select.rs, no walker changes yet). - 2b —
Node::ScopedSubgrammarvariant + the scope accumulators onWalkContext. - 2c — Phase-1 grammar migration: move Phase-1 SELECT
nodes from
data.rsintosql_select.rs; the 7 Phase-1 integration tests are the safety net. - 2d — Diagnostics passes + catalog keys.
- 2e — Completion + post-walk fixup pass.
- 2f — Type resolution via rusqlite
column_metadata. - 2g — Integration sweep + the cross-cut verification matrix (the Phase-1-gap-prevention mechanism).
Each sub-phase has explicit exit gates with required tests and a DA-prompt checklist for the gate review. The plan also encodes the user's standing authorizations:
- Walk uninterrupted between gates unless there's a real ambiguity, autonomous decision, blocker, or in-the-plan-open- question trigger.
- Commits pre-authorized at gate boundaries and natural
break points within a sub-phase. Standard
area: subjectmessage style, detailed body, no AI attribution. - Pushes remain user-only.
- End-of-plan hand-back is the one explicit pause: the verification report + DA PASS verdict get surfaced to the user before Phase 2 is declared complete.
The cross-cut verification matrix (29 rows) names every "X comes for free" claim from ADR-0030/0031/0032 with a test- location placeholder. The Phase-1 SQL-expression predicate- warning gap is row 20 by name — making sure an analogous silent gap cannot ship undetected.
§3. The Phase-1 finding worth carrying forward
While walking ADR-0027 Amendment 1's existing-cases inventory during this session, the warning-vs-error guideline check surfaced a Phase-1 carry-over gap:
ADR-0027 Amendment 1's LIKE-on-numeric warning — together
with ADR-0026 §7's = NULL and type-mismatch warnings — is
emitted by a pass that walks the DSL Expr AST. Phase 1's
sql_expr.rs deliberately builds no AST (ADR-0031 §2).
The consequence: SQL WHERE expressions today emit none of
these warnings. select * from t where name like 5 parses,
the engine runs it, and the learner gets the engine's verdict,
not the friendly pre-flight nudge Amendment 1 promised.
Phase 1 shipped structurally green (1260 / 0 / 1) but the validity indicator was effectively dormant on SQL expressions — the test suite never asked "does this warning fire on a SQL WHERE input?" because there was no test for the cross-cut "the free-for-free promise still holds for SQL" claim.
ADR-0032 §11.6 closes the gap (Phase 2's diagnostics work
implements a MatchedPath-walking predicate-warnings variant
covering every sql_expr slot — WHERE, HAVING, ON, CASE,
projection, ORDER BY) and the implementation plan's cross-cut
matrix has it as a named row to prevent a regression. The plan
is structured so this kind of gap cannot ship undetected again.
Worth carrying forward to future phase planning: every "comes for free" claim in any ADR is also a candidate for an unnoticed silent gap. The cross-cut matrix pattern in the plan generalises.
§4. Autonomous decisions during this session
Per CLAUDE.md, decisions outside ADR-0030's settled scope are flagged here. None are urgent; all were either confirmed by the user via explicit AskUserQuestion gates or are routine detail.
- Catalog key naming.
diagnostic.*for parse-time diagnostics andengine.*for friendly-error translations — chosen by following the existing pattern (diagnostic.unknown_table,select.internal_table); no new naming convention introduced. - Subgrammar push-trigger mechanism (§10.2) —
Node::Scoped Subgrammar(&Node)chosen via explicit user AskUserQuestion gate over two alternatives (flag on existing variant; driver-side identity registry). Recommended option; user picked Recommended. - CTE body column derivation (§10.3) — six derivation rules
covering
*,t.*, bare refs, qualified refs, aliased expressions, and computed-no-alias; first leg / non-recursive leg dictates for compound / recursive bodies. Per standard SQL throughout. Node::ScopedSubgrammardata model —ScopeFrameis a struct holdingfrom_scope/cte_bindings/projection_aliasestogether rather than three parallel stacks. Cleaner data model; no semantic difference. Settled during writing.
Every scope question that affected expressiveness or surface
area went through an AskUserQuestion gate. The full list of
those gates appears in the conversation log; the answers shape
the ADR's §§1–10 directly.
§5. What's next — Phase 2 implementation
Per ADR-0030's phasing, Phase 2 is the next slice. The user has signed off on the plan and authorized walking it without interruption between gates. The implementer for Phase 2 should:
- Read in order: this handoff, ADR-0032, the Phase 2
plan, then
CLAUDE.md(working-style rules unchanged) anddocs/requirements.md(Q1/Q2advance further;Q4already ticked). - Begin sub-phase 2a — the grammar fragment. The plan's "Scope (in)" / "Build steps" / "Exit gate" / "DA gate" sections for 2a are the spec.
- Commit at gate boundaries with detailed messages in the
area: subjectstyle (recent precedent:grammar: SQL SELECT end-to-end (ADR-0030 Phase 1), etc.). - Escalate when the plan says to — see §2 of the plan's "Standing authorizations" for the four trigger conditions. The plan's "Open questions to escalate before code starts" section pre-identifies three known triggers (subquery type- resolution through scalar subqueries; duplicate CTE-name detection; function name allowlist).
- At end of 2g, produce the verification report and surface to the user; do NOT declare Phase 2 complete without the user's confirmation. This is the one explicit pause-point per §5 of the plan's standing authorizations.
After Phase 2 ships and is accepted, ADR-0030 Phase 3 (DML — INSERT / UPDATE / DELETE in SQL) is the next focus area.
§6. Seams for the implementer
These are the file-and-section pointers for Phase 2 work, copied across from the plan for fast access:
src/dsl/grammar/sql_select.rs(new) — the §1 grammar. Parallel tosql_expr.rs(ADR-0031). ExportsSQL_SELECT_STATEMENT(the full statement with optionalWITH) andSQL_SELECT_COMPOUND(the embedded form subqueries recurse into).src/dsl/grammar/sql_expr.rs— three additiveChoicebranches (scalar subqueryprimary,IN (subquery),[NOT] EXISTS (subquery)) and one additivename_or_calltail (qualified reft.c). All recurse throughNode::ScopedSubgrammar(&SQL_SELECT_COMPOUND).src/dsl/grammar/mod.rs—Nodeenum gets the newScopedSubgrammar(&'static Node)variant alongside the existingSubgrammar.src/dsl/walker/context.rs—WalkContextgainsfrom_scope_stack: Vec<ScopeFrame>.current_table/current_table_columnsbecome derived helpers over the top frame (DSL paths must stay green — the existing 1240+ test surface verifies this).src/dsl/walker/driver.rs(or wherever the Subgrammar match arm lives) — new arm forNode::ScopedSubgrammarthat pushes/pops aScopeFrame.src/dsl/walker/mod.rs—schema_existence_diagnosticsextended to walk everyfrom_scopebinding; new arity-check pass; existingpredicate_warningsgets a MatchedPath-walking variant forsql_exprslots.src/db.rs—do_run_selectcalls a newresolve_select_column_typeshelper before constructing theDataResult. The helper usesRawStatement::column_table_name(i)/column_origin_name(i)per result column.Cargo.toml— add"column_metadata"to rusqlite's feature list. One-line change.src/dsl/grammar/data.rs— Phase-1 SQL SELECT static nodes either move intosql_select.rsor get removed outright (thedata::SELECTCommandNodeis rebuilt againstSQL_SELECT_STATEMENT); see plan sub-phase 2c.- Catalog file (wherever the i18n keys live; the existing
diagnostic.unknown_tableneighbours) — five newdiagnostic.*keys + eight newengine.*keys per ADR-0032 §11.5.
§7. How to take over
- Read this file, then
docs/adr/0032-sql-select-grammar.mdanddocs/plans/20260520-adr-0032-phase-2.md. ThenCLAUDE.mdanddocs/requirements.md. cargo test— expect 1260 passing, 0 failed, 1 ignored (the baseline).cargo clippy --all-targets -- -D warnings— clean.- Start sub-phase 2a per the plan. Standing authorizations (plan §§1–5) apply.
- Escalate per CLAUDE.md when the plan's triggers fire; never decide silently on a question the ADR/plan doesn't settle.
§8. What else is open
Unchanged from handoff-25 §8 (prioritisation is a user
decision): snapshot/undo U-series (ADR-0006 written,
unbuilt); m:n convenience C4; modify-relationship C3a;
show family V5; rename-table C1 (may fall out of ADR-0030
Phase 4); friendly-error sweep H1; CI TT5; session-log /
Markdown export V4.
ADR-0030's later phases — DML (Phase 3), DDL (Phase 4), the DSL→SQL teaching echo (Phase 5), polish (Phase 6) — remain on the roadmap behind Phase 2.
Phase-1's specific deferrals (§4.1–§4.5 in handoff-25) are addressed as follows by ADR-0032:
- §4.1 —
FROMoptional. Stands. ADR-0032 §1 notesSELECT 1andSELECT upper('x')continue to parse. - §4.2 — implicit projection aliasing (
select a x). Lifted by ADR-0032 §1 — admitted in Phase 2. - §4.3 —
help_id: Noneon SELECT. Stands; Phase 6. - §4.4 — walker entries defaulting to internal
Mode::Simple. Stands; revisited when advanced mode grows its own ambient assistance (Phase 6). - §4.5 — bool SELECT results render as
0/1. Partially lifted by ADR-0032 §12 — bare bool column refs now recover their type via engine column-origin metadata. Computed bool expressions stay typeless until a future enhancement.