# Session handoff — 2026-05-27 (46) Forty-sixth handover. **We are mid-feature** — the DSL → SQL teaching echo (ADR-0038) is partially built (a working walking skeleton). Read this carefully before continuing; §4 and §5 are the load-bearing parts. ## §1. State at handoff **Branch:** `main`. **HEAD `04c8e42`.** **Tests: 1970 passing, 0 failing, 0 skipped, 1 ignored.** **Clippy: clean** (`--all-targets -D warnings`, nursery). This session designed the **DSL → SQL teaching echo** (ADR-0030 §10) as a `/runda`'d design set and began building it. Commits since handoff-45's `2bcc55f`: ``` 04c8e42 feat: DSL→SQL teaching echo — channel + create-table slice (ADR-0037 + ADR-0038) 9a23e28 fix: update … --all-rows falls back to the DSL instead of misparsing (ADR-0033 Am4) 338dc8a feat: advanced ALTER COLUMN SET/DROP NOT NULL & DEFAULT, SET DATA TYPE (ADR-0035 Am2) 9f15f38 docs(adr): design the DSL→SQL teaching echo (ADR-0038) + dependencies ``` ## §2. The feature & its build plan **Goal:** when a **DSL-form** command runs **in advanced mode**, echo the equivalent SQL beneath `[ok]`, so a learner reads off the SQL spelling (ADR-0030 §10). Authoritative spec: **ADR-0038** (read it — the catalogue in §7, the copy-paste contract §1, the three-category framework §6). Five ADRs, all **Accepted/Proposed and `/runda`'d** (the design `/runda` verdict was PASS with fixes applied — see the ADRs): | ADR | What | Build status | |---|---|---| | **ADR-0035 Am2** | standard-first dialect + `ALTER COLUMN SET/DROP NOT NULL`, `SET/DROP DEFAULT`, ISO `SET DATA TYPE` | ✅ **done** (`338dc8a`) | | **ADR-0033 Am4** | `update … --all-rows` falls back to DSL (was a misparse) | ✅ **done** (`9a23e28`) | | **ADR-0037** | `EffectiveMode` execution-time side-channel (the echo's gate) | ✅ channel **done** (`04c8e42`) | | **ADR-0038** | the echo + full catalogue | 🚧 **skeleton only** — `create table` echoes; rest of the catalogue pending | | **ADR-0039** | EXPLAIN over advanced SQL | ⏸ **deferred follow-up** — decision recorded, NOT this feature | Build order was Am2 → Am4 → (0037+0038 merged). **0037 and 0038 were merged into one build step** because the channel is dead code without its consumer (`-D warnings` rejects it) — see ADR-0037 Implementation notes. ## §3. The doc-drift fix (already committed, FYI) `9f15f38` also reconciled stale notes: **simple-mode column ops (B2/C2) are implemented**, not pending. `requirements.md` C2/B2 were already `[x]`; the project `CLAUDE.md` was corrected. (Don't re-flag this.) ## §4. The echo architecture — how it works NOW (read before editing) Data flow for the **interactive** path (replay is separate — see below): 1. **`app.rs::submit`** computes the three-way **`EffectiveMode`** (`Simple` / `AdvancedPersistent` / `AdvancedOneShot`) — *reusing the pre-existing `EffectiveMode` enum*, NOT a new `SubmissionMode` (ADR-0037 was corrected to reuse it). Passes it to `dispatch_dsl`. 2. **`app.rs::dispatch_dsl(input, submission_mode: EffectiveMode)`** parses with `submission_mode.as_mode()` (the two-way `Mode` the walker needs), and emits **`Action::ExecuteDsl { command, source, submission_mode }`** (new field, `action.rs`). 3. **`runtime.rs` Action::ExecuteDsl handler** → `spawn_dsl_dispatch(…, submission_mode)`. 4. **`runtime.rs::spawn_dsl_dispatch`**: computes `echo = crate::echo::echo_for(&command, submission_mode)` → `Option`, executes the command, and attaches `echo` to the **`AppEvent::DslSucceeded { …, echo }`** (new field, `event.rs`). 5. **`app.rs` update() DslSucceeded arm** stashes `self.pending_echo = echo` just before calling `handle_dsl_success`. 6. **`app.rs::note_ok_summary`** (the shared `[ok]` pusher, called by every success handler) pushes `[ok] …` then, if `pending_echo` is `Some`, pushes the echo line `Executing SQL: ` (i18n key `echo.executing_sql`). The stash is set+consumed within one synchronous `update()`, so it's safe (no interleaving). **The renderer:** `src/echo.rs`. `echo_for(command, mode)` gates (advanced effective mode + DSL-form) and calls `command_to_sql(command) -> Option`. **`command_to_sql` currently handles only `Command::CreateTable`** → `CREATE TABLE T (id serial PRIMARY KEY)` (single inline / compound table-level PK, playground type vocabulary). Everything else returns `None` (no echo yet). **Replay is silent for free:** `run_replay` calls `execute_command_typed` **directly**, bypassing `spawn_dsl_dispatch`, so replayed lines never reach the echo path. No `interactive` flag needed. **Where the echo is built (ADR correction):** NOT in the db.rs worker — the worker gets *decomposed* calls, not the `Command`. The runtime's dispatch is where `Command` + mode + result converge. ADR-0037 §3 / ADR-0038 §4 were corrected to say so. ## §5. What's next — expanding the renderer (the actual work) Per ADR-0038 §8 phasing. **The catalogue (ADR-0038 §7) is the spec** — each row is a round-trip test (parse the echo in advanced mode → a same-effect command; the §1 contract). Order: ### Phase 1 — rest of Bucket A (single-statement) Expand `command_to_sql` for: `add column`, `drop column` (non-cascade), `rename column`, `change column` (→ `ALTER COLUMN c SET DATA TYPE ty` — **now runnable, Am2 shipped**), `add constraint` (NotNull→`SET NOT NULL`, Default→`SET DEFAULT v`, Unique→`ADD UNIQUE (c)`, Check→`ADD CHECK (e)`), `drop constraint` (NotNull/Default only; Unique/Check column-level → Bucket C), `show data` (→ `SELECT * FROM T [WHERE …] [ORDER BY LIMIT n]`), and the `delete … --all-rows` / `update … --all-rows` fall-throughs (**now fall back, Am4 shipped**). For EACH new command, also: - add an `echo: Option` field to its success event (`DslAddColumnSucceeded`, `DslDropColumnSucceeded`, `DslChangeColumnSucceeded`, `DslDataSucceeded`, `DslUpdateSucceeded`, `DslDeleteSucceeded`), - set it from `echo` in `spawn_dsl_dispatch`'s matching arm, - stash `self.pending_echo = echo` in the App's matching update() arm. (`note_ok_summary` already renders it for all.) **Needs Expr→SQL + Value→SQL-literal renderers** (ADR-0038 §5 literal table): for `show data` WHERE filters, `add constraint check`/`default` values, etc. `blob` has no literal (moot — no blob literal syntax). Auto- gen columns omitted from INSERT echoes. ### ⚠️ CRITICAL gotcha for Bucket B + category-3 The skeleton computes `echo` **BEFORE** `execute_command_typed` (it only needs the `Command`). **Bucket B (resolved auto-names) and category-3 (generated shortids, conversion counts) need the *execution result*.** So when you implement those, **move/augment the echo build to AFTER execution**, reading the `CommandOutcome`/result (the resolved index name, the `client_side.*` notes — ADR-0017 §6 / ADR-0018 §9). The runtime has both (it builds the event from command + outcome). This is the one place the current skeleton structure must change. ### Phase 2 — Bucket B Resolved names (`drop index on T(cols)` → `DROP INDEX `, drop/ add relationship, auto-named `add index`) and **multi-line** echoes (one statement per line: `drop column --cascade`, `add relationship --create-fk`). ADR-0038 §6 category 2. The echo payload likely becomes `Vec` (one per statement) — generalise `Option`. ### Phase 3 — category-3 prose expansion shortid generation, type-conversion transforms, and the `change column --dont-convert` **caveat** (ADR-0038 §6). De-emphasised prose from the worker's `client_side.*` notes. ### Polish (not yet done) The echo line is currently a **plain `[system]` line** via `note_system`. ADR-0038 §4 wants it **de-emphasised** (styled-runs, ADR-0028). Refine the rendering (an `OutputLine` with `styled_runs`, dimmed `Executing SQL:` prefix) — a TODO, deferred so the skeleton stayed small. ## §6. Catalogue cheat-sheet (authoritative: ADR-0038 §7) - **Echoes** (DSL-only spellings in advanced mode): all DDL forms + `show data` + `delete/update … --all-rows`. DSL column-op syntax is ` column in/from/to T: c (type)` (**colon + parens**); index/rel naming is `as N` (not `named`). - **NOT echoed**: `insert`/`update … where`/`delete … where` (SQL-first → `Sql*` = already SQL), `drop table`, `drop index ` (→ `Sql*`), `show table`, `explain`, `replay`, app commands, column-level UNIQUE/CHECK *drop* (residual gap, ADR-0035 Am2). `change column --dont-convert` → echoes the headline + a category-3 **caveat**. ## §7. Process pins (unchanged) - **Confirm every commit** (propose message, wait). **No AI attribution.** - **Test-first**; green + clippy-clean is the only acceptable end state; baseline **1970 / 0 / 1**. - **Keep docs lockstep**: ADR + `README.md` index + `requirements.md`. Amend ADRs, don't re-litigate. **Raise implementation deviations and update the ADR** (this session corrected ADR-0035 Am2's SET DEFAULT executor, ADR-0037's enum + build-layer, ADR-0038 §4 in-place). - **Round-trip every catalogue row** (the §1 copy-paste contract). ## §8. How to take over 1. **Read, in order:** this file → **ADR-0038** (§1 contract, §6 categories, §7 catalogue, §8 phasing) → **ADR-0037** (channel + Implementation notes) → the four commits above → **`src/echo.rs`** (the renderer to expand). 2. **Baseline:** `cargo test` (1970 / 0 / 1) + `cargo clippy --all-targets -- -D warnings` (clean). 3. **Continue** with §5 Phase 1 (rest of Bucket A), test-first, one command at a time, round-tripping each echo. Heed the §5 ⚠️ gotcha before touching Bucket B / category-3. 4. ADR-0039 (EXPLAIN over advanced SQL) is a **separate deferred follow-up**, not this feature.