Realises ADR-0030 §10 (the DSL→SQL teaching bridge) as a /runda'd design
set, before implementation:
- ADR-0037 (new): execution-time mode side-channel — SubmissionMode
{Simple, Advanced, AdvancedOneShot} threaded Action→worker, output-only;
redeems ADR-0033 Amendment 3's deferred follow-up. Replay stays silent.
- ADR-0038 (new): the teaching echo + full catalogue (Buckets A/B/C),
the copy-paste round-trip contract, the three-category framework, and
the Value→SQL-literal renderer. DDL + show-data centric (overlapping
DML is SQL-first, so already SQL). Build-order deps recorded.
- ADR-0035 Amendment 2: standard-first dialect stance + ALTER COLUMN
SET/DROP NOT NULL, SET/DROP DEFAULT, ISO SET DATA TYPE gap-fill.
- ADR-0033 Amendment 4: reclassifies the `update … --all-rows`
non-fall-back as a bug; it now falls back to the DSL Update and echoes
(keyed on adjacent `--`; spaced arithmetic preserved).
- ADR-0039 (new): EXPLAIN over advanced SQL — decision recorded, build
deferred; supersedes ADR-0030 §13 OOS-2.
- ADR-0000: out-of-scope discipline (deferred vs rejected). README index
updated for all of the above.
Reconcile CLAUDE.md: simple-mode column ops are implemented, not pending
(requirements.md C2/B2 already [x]).
8.9 KiB
ADR-0037: Execution-time mode side-channel (the three-way submission mode)
Status
Proposed (design agreed with the user 2026-05-27; pending /runda +
implementation). Redeems the follow-up deferred by ADR-0033
Amendment 3, which named this very ADR and its motivating consumer.
Context
ADR-0033 Amendment 3 ("Execution-time mode is side-channel — deferred to its own ADR") recorded a requirement and deferred it:
every command should — at execution time — know which of three modes it ran under:
simple,advanced, oradvanced-one-shot(the:escape from Simple mode, ADR-0003), so execution can adjust output without changing identity (e.g. a Simple-modecreate tableechoing the generated SQL when run in Advanced mode, while staying silent in Simple mode).
That echo is the DSL → SQL teaching bridge (ADR-0030 §10), specified as its own Phase-5 ADR (ADR-0038). ADR-0038 is the motivating — and, at time of writing, only — consumer of this side-channel. This ADR establishes the channel; ADR-0038 consumes it.
What exists today
- The persistent input mode
Mode(src/mode.rs) is two-way:Simple/Advanced. Its doc comment is explicit that the one-shot:escape "is handled at submission time inapp::App::submit, not as additional state here." - The one-shot
:is therefore a transient, per-submission property, collapsed to an effective mode at submit time. It is not persistent state and was deliberately kept out ofMode. - The only mode information that survives past submission is a
rendering side-channel:
OutputLine.mode_at_submission: Mode(the[simple]/[advanced]echo-line tag inui.rs). It is two-way and exists purely to label the input-echo line. - Neither
Action::ExecuteDslnor the database worker (ADR-0010) carries any mode. Execution is mode-agnostic.
Why the gap blocks the echo
The teaching echo (ADR-0038) renders the equivalent SQL beneath the
[ok] summary. That summary is built in the App's outcome handler
(App::update, the Dsl*Succeeded arms) from the worker's result
event — not at dispatch time. At that point:
- The typed
Commandis available (the success events already carry it). - The submission mode is not available.
self.modeis the current persistent mode, which is unreliable for this purpose: it can change between submission and outcome, and for a:one-shot it never reflected the effective mode of that single line at all.
So the echo needs the effective submission mode delivered to
execution/outcome. And several echo forms (ADR-0038 — auto-resolved
index / relationship names, generated shortid values, lossy-conversion
counts) are only knowable after the worker has run, so the echo's
data is fundamentally worker-produced. The natural place to gate and
build mode-dependent output is therefore the execution path, with the
mode threaded to it — exactly the side-channel Amendment 3 anticipated.
Decision
Introduce a three-way effective submission mode, computed at submit
time and threaded through the Action → worker interface, available at
execution time to adjust output only. Command identity, dispatch,
and execution semantics are unchanged (ADR-0033 Amendment 3: identity
is intrinsic to the mode-rooted grammar path, not a flag).
1. A new per-submission enum, distinct from persistent Mode
/// The effective mode of a single submitted line, resolved at submit
/// time. Distinct from the persistent input `Mode` (which stays
/// two-way) because the one-shot `:` escape is a transient per-line
/// property, never persistent state (ADR-0003; `mode.rs`).
pub enum SubmissionMode {
Simple,
Advanced,
AdvancedOneShot,
}
This refines Amendment 3's framing. Amendment 3 sketched "widening
Mode to the three-way distinction." On implementation review, a
separate enum is cleaner: Mode models persistent input state,
and mode.rs deliberately keeps the one-shot out of it. Folding a
transient AdvancedOneShot into the persistent Mode would contradict
that design note and let a non-persistable value leak into persistent-
mode call sites. SubmissionMode carries the three-way distinction on
the per-submission channel where it belongs; Mode stays two-way and
untouched. The requirement Amendment 3 recorded is met; only the
shape is refined. (Flagged for /runda/user challenge — it is an
internal architecture choice with no user-facing effect.)
The effective mode is resolved at submit time from the persistent
Mode plus whether the : sigil was used:
- persistent
Simple, no:→Simple - persistent
Simple,:prefix →AdvancedOneShot - persistent
Advanced→Advanced(the:is a no-op there)
2. Threaded through Action::ExecuteDsl to the worker
Action::ExecuteDsl gains a submission_mode: SubmissionMode field;
the worker request mirrors it. Execution thus knows, per command, the
effective mode it ran under. The value is output-only: no executor
branches its effect on it (that would be a behavioural mode dependency,
which ADR-0033 Amendment 3 forbids — identity and effect are intrinsic).
3. The worker produces mode-dependent output; the App renders it
For the first consumer (ADR-0038): when the command is a DSL-form
command (Command::CreateTable/Insert/… — not the Sql* variants)
and submission_mode is Advanced or AdvancedOneShot, the worker
builds the teaching echo (equivalent SQL + any category-3 expansion
data — ADR-0038) and returns it on the result event. In Simple mode,
or for a command typed as SQL, no echo is produced. The App renders the
returned echo as de-emphasised OutputLine(s) beneath [ok].
Co-locating echo construction with execution is deliberate: the echo's
harder forms (resolved auto-names, generated shortids, conversion
counts) are facts the worker already computes. Gating on the threaded
mode means the work happens only when an echo will be shown.
Non-interactive re-execution does not echo. replay (ADR-0034)
re-runs recorded commands through the dispatch pipeline in advanced mode
(ADR-0033 Amendment 3) — but a per-line teaching echo there would bury
the replay summary in noise. So replayed lines (and any future
programmatic re-execution) dispatch with a SubmissionMode that
suppresses mode-dependent output: command identity still parses in
advanced mode (Amendment 3, unchanged), but no echo fires. The mechanism
— a fourth non-echoing context, or an interactive: bool alongside the
mode — is a build choice; the contract fixed here is replay is silent.
4. Scope of this ADR
This ADR establishes the channel and the resolution rule only. The
echo renderer, its catalogue, and the Value → SQL-literal machinery
are ADR-0038. The advanced-mode ALTER COLUMN gap-fill the echo relies
on is the ADR-0035 amendment. No echo behaviour is specified here beyond
the gating contract in §3.
Alternatives considered
- Widen
Modeto three-way (Amendment 3's literal sketch). Avoids a new type but conflates transient per-submission state with persistent input state, againstmode.rs's explicit design note. Rejected for §1's separate enum. - App-side gating, worker always returns echo data. Keep the worker
mode-agnostic; have it return echo data on every result, and let the
App decide whether to render from
mode_at_submission+ command shape. Rejected: it computes the echo unconditionally (including in Simple mode, where it is never shown), does not generalise to other mode- dependent output, and re-opens exactly the "the echo is purely render- side" framing the user has twice ruled against — the settled direction is execution-time mode awareness, per Amendment 3.
Consequences
- Additive and small: a new enum, one field on
Action::ExecuteDsland the worker request, and the submit-time resolution rule. No change to parsing, dispatch, command identity, or any executor's effect. - The persistent
Modeenum andOutputLine.mode_at_submissionare unchanged. (ADR-0038 may enrich the render tag separately; not required here.) - Future mode-dependent output has a home. Anything touching command identity or effect does not belong here (Amendment 3).
- Tests: submit-time resolution for all three cases (incl.
:one-shot and the Advanced-mode:-is-a-no-op case); the field survives theAction→ worker round-trip; a Simple-mode DSL command yields no echo request while an Advanced / one-shot one does (the gating contract).
See also
- ADR-0033 Amendment 3 — deferred this side-channel; defines the intrinsic command-identity model this ADR must not disturb.
- ADR-0030 §10 — the DSL → SQL teaching bridge (the motivating consumer).
- ADR-0038 — the teaching echo; the consumer built on this channel.
- ADR-0003 — input modes and the one-shot
:escape. - ADR-0010 — the database worker thread this mode is threaded to.