docs(adr): design the DSL→SQL teaching echo (ADR-0038) + dependencies
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]).
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
# 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`, or `advanced-one-shot`
|
||||
> (the `:` escape from Simple mode, ADR-0003), so execution can adjust
|
||||
> **output** without changing **identity** (e.g. a Simple-mode
|
||||
> `create table` echoing 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 in `app::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 of `Mode`.
|
||||
- The only mode information that survives past submission is a
|
||||
**rendering** side-channel: `OutputLine.mode_at_submission: Mode`
|
||||
(the `[simple]`/`[advanced]` echo-line tag in `ui.rs`). It is two-way
|
||||
and exists purely to label the input-echo line.
|
||||
- Neither `Action::ExecuteDsl` nor 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 `Command` is available (the success events already carry it).
|
||||
- The submission mode is **not** available. `self.mode` is 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`
|
||||
|
||||
```rust
|
||||
/// 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 `shortid`s, 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 `Mode` to three-way (Amendment 3's literal sketch).** Avoids a
|
||||
new type but conflates transient per-submission state with persistent
|
||||
input state, against `mode.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::ExecuteDsl` and
|
||||
the worker request, and the submit-time resolution rule. No change to
|
||||
parsing, dispatch, command identity, or any executor's effect.
|
||||
- The persistent `Mode` enum and `OutputLine.mode_at_submission` are
|
||||
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 the
|
||||
`Action` → 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.
|
||||
Reference in New Issue
Block a user