13 KiB
Session handoff — 2026-05-28 (47)
Forty-seventh handover. Phase 1 of the DSL → SQL teaching echo
(ADR-0038) is complete. Bucket A — every single-statement DDL row plus
show data and the --all-rows fall-throughs — is implemented,
round-tripped, and verified. Read §4 if you'll touch the echo path
(there's one structural addition since handoff-46 you need to know
about); §5 is the next-slice plan.
§1. State at handoff
Branch: main. HEAD 90479cb. Tests: 2000 passing, 0 failing,
0 skipped, 1 ignored. Clippy: clean (--all-targets -D warnings,
nursery).
Commits since handoff-46's 04c8e42:
90479cb feat: DSL→SQL teaching echo — Bucket A renderer (ADR-0038 Phase 1)
(+1049 / −25, five files: src/echo.rs src/event.rs
src/runtime.rs src/app.rs tests/walking_skeleton.rs.)
§2. ADR status
| ADR | What | Build status |
|---|---|---|
| ADR-0035 Am2 | standard-first dialect + ALTER COLUMN gap-fill |
✅ done (handoff-46 338dc8a) |
| ADR-0033 Am4 | update … --all-rows falls back to DSL |
✅ done (handoff-46 9a23e28) |
| ADR-0037 | EffectiveMode execution-time side-channel |
✅ done (handoff-46 04c8e42) |
| ADR-0038 | the echo + full catalogue | 🚧 Phase 1 done (90479cb); Phase 2 (Bucket B) + Phase 3 (cat-3 prose) + the styled-runs polish remain |
| ADR-0039 | EXPLAIN over advanced SQL | ⏸ deferred follow-up — separate feature |
ADR-0037 / ADR-0038's status fields still read Proposed. The designs
are unchanged and /runda'd (handoff-46 §2); whether to promote either
to Accepted with an implementation note (matching the pattern of
ADR-0033 / ADR-0035) is a small status-bookkeeping decision worth
raising explicitly rather than amending unilaterally.
§3. What Phase 1 delivered (the renderer, in code)
The echo now covers the full single-statement catalogue. Every row round-trips through the advanced walker (ADR-0038 §1 copy-paste contract):
add column to T: c (ty) [not null][unique][default v][check e]→ALTER TABLE T ADD COLUMN c ty …drop column from T: c(non-cascade) →ALTER TABLE T DROP COLUMN crename column in T: a to b→ALTER TABLE T RENAME COLUMN a TO bchange column in T: c (ty)→ALTER TABLE T ALTER COLUMN c SET DATA TYPE ty(headline only — every conversion mode; the--dont-convertcaveat is category-3 prose, Phase 3)add constraint not null/default/unique/check→SET NOT NULL/SET DEFAULT v/ADD UNIQUE (c)/ADD CHECK (e)drop constraint not null/default→DROP NOT NULL/DROP DEFAULTshow data T [where …] [limit n]→SELECT * FROM T [WHERE …] [ORDER BY <pk> LIMIT n]delete from T --all-rows→DELETE FROM Tupdate T set … --all-rows→UPDATE T SET …
Bucket C correctly returns None: drop constraint unique/check
(anonymous, ADR-0035 Am2 residual gap), Sql* / Select (already SQL),
and where-filtered update/delete (route SQL-first in advanced mode
per ADR-0033 Am3).
Supporting machinery added:
Value → SQL-literal(ADR-0038 §5):NULLuppercase, otherwise reusesValue::Display('O''Hara', bare numbers,true/false, quoted ISO dates).Expr → SQLmirrors the worker'scompile_exproperator spellings (<>,LIKE,BETWEEN,IN,IS NULL, parenthesisedAND/OR/NOT) but emits bare identifiers and inlined literals — the echo's unquoted / runnable style (matching the existingrender_create_tableaesthetic).- One
append_constraintshelper shared bycreate tableandadd columnfor theNOT NULL/UNIQUE/DEFAULT/CHECKsuffix.
§3a. Two execution-time decisions worth flagging
- Create-table echo gap fixed (deviation from handoff-46's "done").
The handoff-46 skeleton's
render_create_tablesilently dropped per-columndefault/check— but simple-modecreate table T with pk age(int) check (age >= 0)does parse those (ADR-0029), so the echo was non-equivalent. The fix: render the full ADR-0029 constraint suffix on create-table columns too, sharing the newappend_constraintshelper withadd column. Not new scope — it's catalogue row 1 done correctly. show data … limit nprimary-key sourcing. TheORDER BY <pk>isn't on theCommand. The pure renderer (echo::show_data_to_sql/echo::echo_for_query) takes the PK as a parameter; the runtime sources it post-execution viadatabase.describe_table— gated on advanced mode +limit.is_some(), mirroring the existingenrich_dsl_failuredescribe pattern (zero struct churn, localised one-call lookup, rare path). An end-to-end test (runtime::tests::show_data_echo_orders_by_resolved_primary_key_when_limited) pins the describe → PK →ORDER BYglue against a real worker.
§4. The architectural shift you need to know about
Handoff-46's skeleton computed the echo before execution
(echo_for(&command, mode) once, threaded into every success arm).
That's still the path for everything except show data, where
Phase 1 added a second, post-execution path:
- Pre-execution (
echo::echo_for,echo::command_to_sql) builds the echo from theCommandalone. Covers every Bucket A row whose echo is a pure function ofCommand. - Post-execution for Query outcomes (
echo::echo_for_query):show datais the only DSL-form query that echoes; its limited form needs the table PK. The runtime'sQueryarm callsbuild_show_data_echo(&database, &command, submission_mode)— which early-returnsNonefor simple mode, thendescribe_tables only when the query is limited, then hands(command, mode, pk)toecho_for_query.Command::SelectreturnsNone(already SQL).
This is the architecture the handoff-46 ⚠️ gotcha foreshadowed for
Bucket B / category-3 ("move/augment the echo build to AFTER
execution"). Phase 1 needed it one row early (for show data's PK),
and it's there now — the pattern is established. Phase 2 will need to
move the resolved-name and multi-statement renders to similar
post-execution paths (see §5).
Event wiring
The six remaining success events now carry an echo field:
DslDataSucceeded, DslUpdateSucceeded, DslDeleteSucceeded,
DslChangeColumnSucceeded, DslAddColumnSucceeded,
DslDropColumnSucceeded. (RenameColumn / AddConstraint /
DropConstraint flow through the already-wired DslSucceeded.) Each
runtime arm sets echo from the pre-execution echo var (or the
post-execution build_show_data_echo for the Query arm); each update()
arm stashes self.pending_echo = echo before its handler runs. All
handlers call note_ok_summary first — the existing consumer of
pending_echo. One App-level test
(bucket_a_success_events_render_the_teaching_echo_beneath_ok) guards
every new arm's stash + ordering.
§5. What's next (the actual work)
Phase 2 — Bucket B (resolved names + multi-statement)
Per ADR-0038 §7. Each row needs an addition to the post-execution
arm of the relevant outcome — the runtime has both Command and the
worker's result there.
| DSL command | Echo | Sources |
|---|---|---|
add index on T (cols) (auto-named) |
CREATE INDEX <auto> ON T (cols) |
resolved name from CommandOutcome::Schema(desc) — desc.indexes matching the requested column set |
add index as N on T (cols) |
CREATE INDEX N ON T (cols) |
pure Command — could land in Phase 1 by name, kept in Phase 2 here for uniformity |
drop index on T(cols) (positional) |
DROP INDEX <resolved> |
resolved name from the pre-execution describe (the executor knows it) |
add 1:n relationship [as N] … |
ALTER TABLE C ADD CONSTRAINT <name> FOREIGN KEY (cc) REFERENCES P (pc) [ON DELETE X] [ON UPDATE Y] |
resolved name; ADR-0013 metadata |
drop relationship (from P.pc to C.cc | N) |
ALTER TABLE C DROP CONSTRAINT <name> |
resolved name + child table |
add 1:n relationship … --create-fk (child col created) |
ALTER TABLE C ADD COLUMN cc <ty> ⏎ ALTER TABLE C ADD CONSTRAINT <name> FOREIGN KEY (cc) REFERENCES P (pc) … |
category-2 multi-statement (one line if column already existed) |
drop column T.c --cascade (drops covering indexes) |
DROP INDEX <ix1> ⏎ … ⏎ ALTER TABLE T DROP COLUMN c |
category-2 multi-statement; index names from DropColumnResult::dropped_indexes |
Mechanical changes Phase 2 will need:
- Echo payload likely becomes
Vec<String>(one statement per line) to generaliseOption<String>for category-2 multi-statement rows.note_ok_summarywould render each line beneath[ok]. Some alternatives — e.g. one joined string with embedded newlines, or keepingOption<String>and joining with\n— work but are less flexible if the styled-runs polish (§7) wants to style each line separately. Decide at the start of Phase 2. - Resolved-name sources vary by command:
DropColumnResultalready carriesdropped_indexes; foradd relationshipthe resolved name comes from the post-execution describe of the child table; fordrop index(positional) anddrop relationship(by endpoints) the executor resolves a name during execution but doesn't surface it in the current result types — those may need smallCommandOutcomeenrichments (a name field), or a post-execution lookup. Choose per row.
Phase 3 — category-3 prose expansion
Three lines (ADR-0038 §6):
shortidgeneration (illuminating) — built fromclient_side_notesonAddColumnResult/InsertResult/ChangeColumnTypeResult(the existing auto-fill notes).- type-conversion transform (illuminating) — from
ChangeColumnTypeResult::client_side(transformed,lossy). change column … --dont-convert(caveat) — the headline is not equivalent in this case; the prose line states the divergence.
All three render from existing worker client_side.* notes — no new
worker plumbing needed; just route the notes into the echo channel.
Polish — de-emphasised styled-runs rendering
The echo line is currently a plain [system] line via note_system.
ADR-0038 §4 wants it de-emphasised (ADR-0028 styled-runs: a dimmed
Executing SQL: prefix; the SQL in a code-ish run). This is a
rendering change in app.rs, not a renderer change. Still pending.
ADR status bookkeeping (small)
ADR-0037 and ADR-0038 still read Proposed. Now that ADR-0037 is
fully implemented and ADR-0038 has its first phase shipped, an
Accepted (implemented …) / Accepted (Phase 1 implemented …) update
in the style of ADR-0033 / ADR-0035 would match the repo's pattern.
Not done in this session — a one-line decision worth raising with the
user before touching either ADR's Status block.
§6. Catalogue cheat-sheet (authoritative: ADR-0038 §7)
Unchanged since handoff-46. Echoes (DSL-only spellings in advanced
mode): all DDL forms + show data + delete/update … --all-rows.
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 (ADR-0035 Am2 residual).
change column --dont-convert will echo the headline plus a category-3
caveat (Phase 3).
§7. Process pins (unchanged)
- Confirm every commit (propose message, wait). No AI attribution.
- Test-first; green + clippy-clean is the only acceptable end state; current baseline 2000 / 0 / 1.
- Keep docs lockstep: ADR +
README.mdindex +requirements.md. M4 was updated this session to reflect the now-implemented side-channel + Phase 1 echo. - Round-trip every catalogue row (the §1 copy-paste contract).
- Heed the §4 architectural pattern: Bucket B / category-3 rows
build their echo in the post-execution arm of the outcome they
produce, reading the result —
show datais the established example.
§8. How to take over
- Read, in order: this file → ADR-0038 (§1 contract, §6
categories, §7 catalogue, §8 phasing) →
src/echo.rs(the renderer; the Phase 1 surface is the model for Phase 2) →src/runtime.rs'sspawn_dsl_dispatch+build_show_data_echo(the post-execution pattern). - Baseline:
cargo test(2000 / 0 / 1) +cargo clippy --all-targets -- -D warnings(clean). - Continue with §5 Phase 2 (Bucket B), test-first, one command at
a time, round-tripping each catalogue row. Decide the
Option<String>→Vec<String>echo payload question up front. Phase 3 (category-3 prose) and the styled-runs polish follow. - ADR-0039 (EXPLAIN over advanced SQL) is a separate deferred follow-up, not this feature.