# 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<String>`, 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: <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<String>`. **`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 <pk> 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<String>` 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 <resolved>`, 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<String>` (one per statement) — generalise `Option<String>`.

### 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
  `<verb> 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 <name>` (→ `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.
