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:
claude@clouddev1
2026-05-26 18:30:31 +00:00
parent cb8ff8a7c2
commit f8a91f41c9
8 changed files with 328 additions and 41 deletions
+15 -1
View File
@@ -1855,6 +1855,9 @@ pub async fn run_replay(
// CSVs) fires as if the user had typed each line. The
// source re-journalled is the *extracted* command, not the
// raw `<ts>|ok|…` record (ADR-0034 §3).
// Retain a clone for failure enrichment (the command is moved into
// dispatch). ADR-0035 Amendment 1, F2 follow-up.
let command_for_ctx = command.clone();
let outcome =
execute_command_typed(database, command, command_text.clone()).await;
match outcome {
@@ -1877,11 +1880,22 @@ pub async fn run_replay(
return events;
}
Err(e) => {
// Enrich like the interactive path (ADR-0019 §6) so a
// replayed failing command shows the real table/column/
// value instead of a contextless, `{name}`-leaking message
// (ADR-0035 Amendment 1, F2 follow-up). Verbose to match
// the prior `friendly_message()` rendering.
let facts = enrich_dsl_failure(database, &command_for_ctx, &e).await;
let ctx = crate::app::App::translate_context_for(
&command_for_ctx,
facts,
crate::friendly::Verbosity::default(),
);
events.push(AppEvent::ReplayFailed {
path: path.to_string(),
line_number,
command: command_text.clone(),
error: e.friendly_message(),
error: crate::friendly::translate_error(&e, &ctx).render(),
});
return events;
}