feat: ADR-0035 Amendment 1 follow-up — enrich replay errors + close message gaps
- F2-broad: replay failures now render with real schema context instead of
a contextless friendly_message(). Extract App::build_translate_context into
the shared App::translate_context_for(command, facts, verbosity); run_replay
enriches via enrich_dsl_failure + that builder. ctx_* fallbacks degrade to
neutral prose so the rare non-replay contextless callsites can't leak raw
{name} either. (SQL INSERT/UPDATE values aren't retained — ADR-0033 verbatim
— so those show real table/column + neutral "that value".)
- Gap C: SQL ALTER … ADD FOREIGN KEY on a missing child column refuses with an
SQL-appropriate "add it first", not the DSL-only --create-fk flag.
- Gap B: dropping a single-column-UNIQUE column refuses with a pointer to
`drop constraint unique from T.col` (was an opaque generic refusal).
- Gap D: 4e drop/rename CHECK-guard + 4f change-type FK-guard refusals reworded
to explain why; static_refusal reasons left as-is.
Tests: +4, 3 strengthened. 1926 pass / 0 fail / 0 skip; clippy clean.
This commit is contained in:
@@ -108,3 +108,80 @@ API (Tier-1/3) and the friendly-layer unit tests + insta snapshots.
|
||||
Full `cargo test` + clippy; compare to baseline; every checklist item
|
||||
addressed; engine-neutral vocab held (no SQLite/STRICT/PRAGMA in new
|
||||
user-facing strings); ADR + README + this plan lockstep.
|
||||
|
||||
**Shipped 2026-05-26** as commit `cb8ff8a` — 1922 pass / 0 fail / 0 skip,
|
||||
clippy clean.
|
||||
|
||||
## Follow-up (2026-05-26, user-approved) — broad F2 + message gaps B/C/D
|
||||
|
||||
After the F1–F3 commit the user asked to take the broader F2 leak plus the
|
||||
remaining message gaps. Scope (user-decided):
|
||||
|
||||
- **F2-broad — enrich replay + neutral-prose safety net.** The constraint
|
||||
templates (`error.unique.*`, `error.foreign_key.*`, `error.check.*`)
|
||||
carry `{table}`/`{column}`/`{value}` in the **headline**, so they leak
|
||||
whenever rendered via contextless `friendly_message()`. The realistic
|
||||
surface is **replay of a constraint-violating scripted command**
|
||||
(`run_replay`'s failure branch, `runtime.rs`, calls bare
|
||||
`e.friendly_message()`). Fix: (a) replay reuses `enrich_dsl_failure` +
|
||||
the operation-from-`Command` mapping so a replayed failure shows the
|
||||
**real** table/column/value (best UX); (b) the `ctx_*` fallback markers
|
||||
become neutral prose (`{table}` → "the table", etc.) so the rare
|
||||
non-replay contextless callsites (undo/rebuild/export) can't leak raw
|
||||
`{name}` either. Requires extracting `App::build_translate_context` into
|
||||
a `pub(crate)` free fn (parameterised by verbosity) so replay and the
|
||||
App share one Command→context mapping.
|
||||
- **Gap C — `--create-fk` leak.** SQL `ALTER … ADD FOREIGN KEY` on a
|
||||
missing child column reuses `do_add_relationship`'s DSL-flavoured error
|
||||
suggesting `--create-fk` (a DSL flag, meaningless in SQL). Fix:
|
||||
`do_alter_add_foreign_key` pre-validates the child column and emits an
|
||||
SQL-appropriate "add it first" refusal with no flag mention.
|
||||
- **Gap B — single-column UNIQUE column drop.** Parallel to F1 but a
|
||||
different mechanism: a single-column UNIQUE rides on the column `unique`
|
||||
flag (ADR-0029), not `unique_constraints`. Characterise current
|
||||
behaviour with a test, then add a friendly, actionable refusal pointing
|
||||
at the column-level `drop constraint unique from T.col`.
|
||||
- **Gap D — terse CHECK-guard / type-conversion wording.** Polish the 4e
|
||||
drop/rename-column CHECK-guard refusals and the 4f type-conversion
|
||||
diagnostics for clarity, staying engine-neutral. Conservative — wording
|
||||
only, no behaviour change.
|
||||
|
||||
### DA critique (follow-up)
|
||||
|
||||
1. **Refactor risk.** Extracting `build_translate_context` from `App` is a
|
||||
pure move + signature change (add `verbosity`); the App method becomes
|
||||
a thin delegator. Covered by the existing app tests + a new replay
|
||||
render test.
|
||||
2. **`ctx_*` neutral prose looks odd backtick-wrapped** (`` `the table` ``)
|
||||
— accepted by the user as a last-resort safety net; it renders only in
|
||||
the near-impossible non-replay constraint case (replay is enriched).
|
||||
3. **Gap B may be a non-issue** if the engine drops a single-column-UNIQUE
|
||||
column cleanly — characterise first, only guard if it refuses.
|
||||
4. **No marker pinned anywhere.** No test/snapshot asserts a literal
|
||||
`{table}`/`{column}` as expected output, so changing the fallbacks is
|
||||
low-risk (verified by grep).
|
||||
|
||||
### Outcome (implemented 2026-05-26)
|
||||
|
||||
- **F2-broad** — `App::build_translate_context` extracted to the shared
|
||||
`App::translate_context_for(command, facts, verbosity)`; `run_replay`'s
|
||||
failure branch now enriches via `enrich_dsl_failure` + that builder, so a
|
||||
replayed failure shows the real table/column (and value/parent/rule
|
||||
where resolvable). `ctx_*` fallbacks are neutral prose. **Discovered
|
||||
limitation:** replay parses in advanced mode → SQL `INSERT`/`UPDATE`,
|
||||
whose values are raw SQL text (ADR-0033 verbatim), not retained — so the
|
||||
offending *value* degrades to "that value" (no leak), while table/column
|
||||
are real. DSL `insert`/`update` still show the value. (Same gap exists
|
||||
on the interactive SQL-DML path; the safety net covers it.)
|
||||
- **Gap C** — `do_alter_add_foreign_key` pre-validates the child column
|
||||
and emits an SQL-appropriate "add it first" refusal (no `--create-fk`).
|
||||
- **Gap B** — `do_drop_column` guards a single-column UNIQUE
|
||||
(`col_info.unique`) with a refusal pointing at `drop constraint unique
|
||||
from T.col`.
|
||||
- **Gap D** — polished the 4e drop/rename CHECK-guard refusals and the 4f
|
||||
change-type FK guard to explain *why*; left `static_refusal` reasons
|
||||
as-is (already clear — avoided gratuitous churn).
|
||||
|
||||
Tests +4 (replay no-leak, safety-net unit, FK-missing-column, single-col
|
||||
UNIQUE drop) + 3 strengthened (2× CHECK-guard wording, 1× change-type FK
|
||||
wording). **1926 pass / 0 fail / 0 skip**, clippy clean.
|
||||
|
||||
Reference in New Issue
Block a user