# Phase 3 (ADR-0033) — phase-exit verification report

**Date:** 2026-05-23
**Sub-phase:** 3k (verification sweep)
**Driving ADR:** [ADR-0033 — full SQL DML grammar](../adr/0033-sql-dml-grammar.md)
(+ Amendments 1–3)
**Plan:** [docs/plans/20260520-adr-0033-phase-3.md](../plans/20260520-adr-0033-phase-3.md)

This report closes Phase 3. It records the test totals, the filled
cross-cut verification matrix (in the plan doc), the
requirements-to-test mapping, the autonomous decisions made during
3k (both user-confirmed), and the Devil's-Advocate final review.

---

## 1. Test-suite totals

| Metric   | Phase-2 baseline | Phase-3 exit (3k) | Δ |
|----------|------------------|-------------------|---|
| passed   | 1446             | **1645**          | +199 |
| failed   | 0                | **0**             | 0 |
| skipped  | 0                | **0**             | 0 |
| ignored  | 1 (doctest)      | **1** (doctest)   | 0 |

- `cargo test` → **1645 passed; 0 failed; 0 skipped; 1 ignored**.
  The single ignored test is the unchanged `src/friendly/mod.rs`
  doctest (a non-executable illustrative snippet), as in every prior
  phase.
- `cargo clippy --all-targets -- -D warnings` → **clean**.
- No regression from the Phase-2 baseline; every prior test stays
  green.

3k itself added **19 tests** over the handoff-33 count (1626):

- `tests/sql_dml_e2e.rs` — 11 Tier-3 end-to-end DML tests (new file),
  including the OOS parse-rejections (extended to the full §13 table —
  OOS-1…6 — during the `/runda` round) and the single-statement
  trailing-`;` guard.
- `src/dsl/walker/mod.rs::tests` — 5 inherited-diagnostic cross-cuts
  (I3 DELETE/UPDATE WHERE, I6 like-numeric on DELETE WHERE, I7 the
  corrected row-scoped predicate slots).
- `src/dsl/walker/highlight.rs::tests` — 1 DML-keyword highlight test.
- `src/completion.rs::tests` — 1 `INSERT INTO` target-table
  completion test.
- `src/input_render.rs::tests` — 1 advanced-mode DML hint-panel test
  (added in the `/runda` round; see §5.1).

---

## 2. Cross-cut verification matrix

The full matrix is filled in the plan doc:
[docs/plans/20260520-adr-0033-phase-3.md → "Cross-cut verification
matrix"](../plans/20260520-adr-0033-phase-3.md). Every row carries a
verified `file::function` (the legend maps the abbreviations) and a
status:

- **85 rows ✅** — a green test whose INPUT matches the claim source
  (SQL claims take SQL-syntax input, per the handoff-29 §4.4 pin,
  cross-checked at attribution time by reading each test).
- **1 row N/A** — auto-snapshot (ADR-0006), unimplemented for both
  paths; see §4 / footnote [e].
- **0 rows ❌.**

Five rows carry footnotes recording resolutions surfaced during 3k
(matrix footnotes [a]–[e]): the Amendment-1 dispatch mechanism, the
Amendment-3 simple-mode pointer, the Amendment-2 cascade count-diff,
the corrected predicate-in-VALUES claim, and the auto-snapshot
deferral.

**Attribution integrity note.** The initial catalog pass (sub-agent
survey) produced several fabricated function names and mis-attributed
the dispatch rows to the 3a synthetic smoke tests. Every row in the
final matrix was re-verified by reading the named test, per the
project's "probe, don't reason" discipline. In particular the
inherited-diagnostic rows (I1–I7) and the dispatch rows now point at
the real tests in `src/dsl/walker/mod.rs::tests` and
`src/dsl/parser.rs::tests`.

---

## 3. Requirements-to-test mapping (ADR-0033 §§1–13)

| § | Decision | Proven by |
|---|----------|-----------|
| §1 | INSERT/UPDATE/DELETE statement shapes | `ins::single_row_…`, `ins::multi_row_…`, `ins::no_column_list_…`, `upd::*`, `del::*` |
| §2 | Dispatch on shared entry words (Amendments 1 & 3) | `parser::advanced_ambiguous_{insert,update,delete}_routes_to_sql`, `parser::advanced_dsl_only_delete_falls_back_to_dsl`, `parser::simple_*` |
| §3 | Multi-row VALUES | `ins::multi_row_insert_persists_both_rows`, `e2e::e2e_multirow_insert_all_ten_types_…` |
| §4 | INSERT … SELECT (+ WITH-prefixed row source) | `ins::insert_select_copies_rows_and_persists`, `ins::with_prefixed_insert_select_runs_and_persists`, `e2e::e2e_insert_select_cross_table_…` |
| §5 | RETURNING on all three kinds | `ins::insert_returning_*`, `upd::update_returning_*`, `del::delete_returning_*`, `e2e::e2e_returning_on_insert_update_delete` |
| §6 | shortid auto-fill (worker post-fill) | `ins::values_autofills_*`, `ins::*_distinct_shortids`, `ins::combined_serial_and_shortid_autofill` |
| §7 | Cascade summary on DELETE (Amendment 2 — count-diff) | `del::cascade_parity_with_dsl`, `del::r2_where_with_subquery`, `del::cascade_delete_reports_summary_and_repersists_child` |
| §8.1 | `insert_arity_mismatch` (ERROR, per-row) | `walker::insert_arity_mismatch_*` |
| §8.2 | `auto_column_overridden` (WARNING) | `walker::auto_column_overridden_*` |
| §8.3 | `not_null_missing` (WARNING) | `walker::not_null_missing_*` |
| §8.4 | Inherited Phase-2 diagnostics on DML slots | `walker::insert_column_list_unknown_column_is_flagged`, `walker::sql_update_*`, `walker::sql_delete_where_*`, `walker::insert_select_where_predicate_warns` |
| §9 | UPSERT (`ON CONFLICT … DO NOTHING/UPDATE`, `excluded`) | `ins::on_conflict_*`, `completion::excluded_prefix_completes_to_target_columns`, `walker::excluded_*` |
| §10 | Three typed Command variants + worker handlers + persistence | `ins`/`upd`/`del` worker round-trips, `e2e::*` |
| §11 | Engine-error translation (reuses existing keys) | `friendly::enrich_unique_*`, `friendly::enrich_not_null_*`, `del::delete_violating_fk_fails_and_persists_nothing` |
| §12 | Result-column type recovery on RETURNING | `e2e::e2e_multirow_insert_all_ten_types_…` (all ten), `ins::insert_returning_recovers_multiple_bare_column_types` |
| §13 | OOS rejections (DEFAULT VALUES, OR REPLACE, UPDATE FROM, WITH UPDATE/DELETE) | `e2e::e2e_out_of_scope_dml_forms_parse_reject` |

---

## 4. Autonomous decisions during 3k (both user-confirmed)

Per `CLAUDE.md`: an autonomous decision without explicit user
confirmation is a verdict FAIL. Two ADR-vs-implementation mismatches
surfaced while filling the matrix; both were escalated and resolved
**before** any test or matrix edit landed (`AskUserQuestion`,
2026-05-23):

1. **Matrix row I7 — "predicate warning fires inside INSERT VALUES
   sql_expr".** Probing showed the predicate-warning pass fires on
   every DML `sql_expr` slot that carries a resolvable column binding
   (UPDATE/DELETE WHERE, UPDATE SET incl. CASE, INSERT…SELECT
   projection & WHERE) but **not** in plain `INSERT … VALUES`, because
   a VALUES literal has no row scope (a bare-column predicate there is
   meaningless). ADR-0033 §8.4 itself describes the pass as covering
   "WHERE and CASE expressions." **User decision: correct the matrix
   claim** to the realizable, row-scoped slots. Backed by new tests
   `walker::sql_update_set_case_predicate_warns` and
   `walker::insert_select_where_predicate_warns`. No code change.

2. **Matrix row "Auto-snapshot fires for SQL DML the same way as DSL"
   (ADR-0006 / ADR-0033 §10).** ADR-0006 auto-snapshot/undo is
   unimplemented for **both** the DSL and SQL paths (the U-series, per
   `CLAUDE.md` "Things deliberately deferred"). **User decision: mark
   N/A — deferred.** The "same as DSL" parity holds vacuously and
   Phase 3 introduces no regression; ADR-0033 §10's auto-snapshot
   consequence is contingent on the deferred ADR-0006 work.

No other autonomous decisions were made in 3k. (The 3d Option-A/B
escalation and the Amendment 1/2/3 decisions were resolved in their
own sub-phases with user approval, recorded in ADR-0033.)

---

## 5. Devil's-Advocate final review

Specific critiques first; verdict last (handoff-28/29 §4.1 pin — a
rubber-stamp PASS is a process failure).

### 5.1 `/runda` round (second-pass DA, user-invoked)

A user-invoked `/runda` round re-checked the matrix attributions
against the actual test inputs/modes and re-swept the requirement
sources. It found and closed two issues, and re-verified the rest:

1. **A6 (hint-panel) was attributed to Simple-mode tests under an
   advanced (§8) heading.** The hint-prose tests call `ambient_hint`,
   which defaults to `Mode::Simple` (the DSL value-slot prose) — so the
   §8 *advanced*-surface claim had no advanced-mode attribution.
   *Fixed:* added `input_render::advanced_mode_ambient_offers_dml_slot_candidates`
   (column candidates at an advanced-mode `UPDATE … SET` LHS and inside
   an `INSERT … (` column list); matrix footnote [f].

2. **OOS-5 / OOS-6 (ADR-0033 §13) had no tests.** The plan's matrix
   scoped OOS to §13's first four rows; OOS-5 (`INDEXED BY` /
   `NOT INDEXED`) and OOS-6 (multi-statement batch) were unverified.
   *Probed:* all reject in Advanced mode; a single statement with a
   trailing `;` still parses. *Fixed:* extended
   `e2e_out_of_scope_dml_forms_parse_reject` to the full §13 table and
   added `e2e_single_dml_statement_with_trailing_semicolon_parses`;
   added matrix rows OOS-5/OOS-6.

Re-verified clean (no change needed):

- **requirements.md** — Q1/Q2 (SQL subset + clear out-of-subset
  rejection message) and M1/M2 (mode acceptance) are multi-phase and
  remain `[ ]` by design; Phase 3 contributes the DML slice without
  claiming closure. Q2's *polished* rejection message is explicitly
  "Implementation pending" — the 3k OOS tests assert only that the
  forms parse-**reject** (ADR-0033 §13), not message quality, so no
  over-claim. M4 is the deferred side-channel (Amendment 3).
- **Issue trackers** — `origin` is GitHub (`gh issue list` empty); the
  configured gitea instance does not host this repo. The handoff's
  "#8/#9/#12" are internal task/finding numbers, not external issues.
  No external acceptance criteria exist beyond the ADRs / plan /
  requirements.md.
- **Engine-neutrality** — all new Phase-3 catalog strings
  (`also_valid_sql`, `insert_arity_mismatch`, `auto_column_overridden`,
  `not_null_missing`, plus the reused `eq_null`/`like_numeric`/
  `sql_in_simple`) are engine-neutral; a full scan of
  `src/friendly/strings/en-US.yaml` for `sqlite`/`rusqlite`/`pragma`/
  `strict` finds only a code comment and the internal `sqlite:` key
  whose value ("database error: …") is neutral. `engine_vocabulary_audit`
  is green.
- The catalog-attribution sub-agents' fabrications (noted below) were
  the original trigger for verifying every row by hand; the `/runda`
  pass confirmed no fabricated or mode-mismatched attribution survived.

### 5.2 Final critiques

### Critiques raised and their resolution

1. **The catalog sub-agents fabricated test names and mis-attributed
   the dispatch rows.** Real risk of a matrix that points at
   non-existent or wrong tests. *Resolved:* every row was re-verified
   by reading the named test; the I1–I7 and dispatch rows now point at
   the real `walker::` / `parser::` tests, not the fabricated names or
   the 3a smoke registry.

2. **Two matrix rows over-claimed vs the implementation** (predicate
   warnings in VALUES; auto-snapshot). The Phase-2 lesson was "expect
   at least one gap from filling the matrix" — Phase 3 surfaced two.
   *Resolved:* both escalated to the user and dispositioned (§4); the
   corrected predicate claim gained two backing tests, and the
   auto-snapshot row is N/A-deferred, not silently dropped.

3. **R5 (ten-type recovery via RETURNING) was only 5/10 before 3k.**
   The existing `insert_returning_recovers_multiple_bare_column_types`
   covered int/text/decimal/real/bool only. *Resolved:* the new
   `e2e::e2e_multirow_insert_all_ten_types_…` asserts column-origin
   recovery for **all ten** types via a `RETURNING` list.

4. **`blob` is inserted NULL in the all-types e2e.** Honest limitation:
   `src/dsl/value.rs` has no blob value-literal yet (a pre-existing,
   tracked DSL gap — not a Phase-3 regression). The blob column's
   *type* still round-trips through the RETURNING column-origin path,
   which is what §12 claims. The data round-trip is exercised for the
   other nine types.

5. **The Tier-3 e2e tests go parse → worker, not through the full
   Tokio event loop.** *Mitigation:* the App dispatch seam is covered
   separately (`parser::*` dispatch tests + the `sql_select.rs`
   App-driven tests), and the runtime replay loop is exercised
   end-to-end by `e2e::e2e_replay_phase3_dml_forms_from_a_script`
   (`run_replay` → parse-in-advanced → worker → persisted state). The
   full stack is covered across these seams.

6. **No requirement was silently downgraded to "later."** The only
   deferrals (auto-snapshot, blob literals) are pre-existing,
   user-confirmed, and tracked — not new scope reductions introduced
   by 3k.

### Verdict

**PASS.** All 1645 tests green, zero failed, zero skipped, one
unchanged ignored doctest; clippy clean; no regression from the
Phase-2 1446 baseline. Every cross-cut matrix row is ✅ (85) or
N/A-deferred (1, user-confirmed), with verified `file::function`
attributions whose inputs/modes match the claim source — re-checked
in the `/runda` round (§5.1), which corrected the A6 mode mismatch and
closed the OOS-5/6 coverage gap. The two over-claims the matrix
carried (predicate-in-VALUES, auto-snapshot) were surfaced, escalated,
and resolved with the user rather than papered over.

---

## 6. State at handoff

- **Branch:** `main`. **Tests: 1645 passing, 0 failing, 0 skipped, 1
  ignored.** Clippy clean.
- **Uncommitted** (this session's 3k work, pending the user's commit
  approval):
  - `tests/sql_dml_e2e.rs` (new — 11 Tier-3 e2e tests)
  - `src/dsl/walker/mod.rs` (5 cross-cut tests)
  - `src/dsl/walker/highlight.rs` (DML-keyword highlight test)
  - `src/completion.rs` (INSERT INTO target-table completion test)
  - `src/input_render.rs` (advanced-mode DML hint-panel test — `/runda`)
  - `docs/plans/20260520-adr-0033-phase-3.md` (matrix filled +
    footnotes)
  - `docs/handoff/20260523-phase-3-verification.md` (this report)
- **ADR-0033** can move from *Proposed* to *Accepted* once the user
  accepts this report (the plan's §5 hand-back point). Its three
  amendments are already settled.

### Follow-ups (tracked, not blocking Phase 3)

Per the user's standing "extra tasks after phase 3 is complete":

- **ADR-0006 auto-snapshot/undo** (U-series) — implement for both the
  DSL and SQL DML worker handlers; satisfies the N/A matrix row.
- **M4 — execution-time mode side-channel** (ADR-0033 Amendment 3;
  `requirements.md`). Its own future ADR.
- **TASK #8 — ADR-0034 history journal**; **TASK #9 — replay line
  format** (`<ts>|<status>|<source>`). Orthogonal to Phase 3.
- **blob value-literal** in the DSL/SQL value grammar
  (`src/dsl/value.rs`) — pre-existing gap.
- The cosmetic `tests/sql_*.rs` helper names (`run_sqlinsert` etc.)
  retain their dev-word-era spelling though they now parse the real
  words.
