feat: ADR-0034 — history journal records err + replay parses/filters the journal
Replay (§3): run_replay parses <ts>|<status>|<source> journal records — runs ok, skips non-ok — while still accepting bare .commands scripts (prefix-detected so a | inside a bare command isn't misread). Fixes replay history.log, which died on line 1. Journal failures (§1/§2): failed commands are recorded err via a new Action::JournalFailure, emitted by the pure-sync App for both parse failures and worker-execution failures (runtime appends best-effort, never fatal). Hydration reads all records so typo'd/rejected commands are recallable across sessions. Amendment 1 — replay filters app-lifecycle commands: a working replay history.log exposed that the journal also records save as/load/new/export/import/rebuild/mode (which would panic the worker dispatch or abort replay). Replay now re-applies only schema/data writes and skips every app-lifecycle command + nested replay, classified by entry word so modal/incomplete forms (save as, bare mode) and quit skip uniformly rather than aborting. All skips continue (reversing the nested-replay refusal); import and nested replay warn. replay.error_nested removed; replay.skipped_import/_replay added; ReplayCompleted carries warnings. requirements.md U3/U4 updated; app-command runtime-failure journalling tracked as a follow-up. 1659 passing / 0 failing / 0 skipped / 1 ignored. Clippy clean.
This commit is contained in:
+43
-1
@@ -272,13 +272,30 @@ impl Persistence {
|
||||
}
|
||||
}
|
||||
|
||||
/// Append one record to `history.log`.
|
||||
/// Append one successful-command record to `history.log`.
|
||||
pub fn append_history(&self, command_text: &str) -> Result<(), PersistenceError> {
|
||||
let path = self.project_path.join(HISTORY_LOG);
|
||||
let line = history::format_record(command_text, history::utc_iso8601_now());
|
||||
history::append(&path, &line)
|
||||
}
|
||||
|
||||
/// Append a failed-command record to `history.log`, tagged
|
||||
/// `err` (ADR-0034 §1). Used by the runtime's error path so a
|
||||
/// command that failed to parse or to execute is still
|
||||
/// recallable across sessions (it never reaches the worker's
|
||||
/// transactional `ok` journal). Best-effort at the call site:
|
||||
/// a failure to record a failure must never escalate a user
|
||||
/// error into a fatal (ADR-0034 §4).
|
||||
pub fn append_history_failure(&self, command_text: &str) -> Result<(), PersistenceError> {
|
||||
let path = self.project_path.join(HISTORY_LOG);
|
||||
let line = history::format_record_with_status(
|
||||
command_text,
|
||||
history::utc_iso8601_now(),
|
||||
history::STATUS_ERR,
|
||||
);
|
||||
history::append(&path, &line)
|
||||
}
|
||||
|
||||
/// Read the most-recent `max_n` sources out of
|
||||
/// `history.log` for input-history hydration on project
|
||||
/// open (ADR-0015 §12). Returned in chronological order
|
||||
@@ -289,6 +306,31 @@ impl Persistence {
|
||||
}
|
||||
}
|
||||
|
||||
/// How `run_replay` should treat one already-trimmed,
|
||||
/// non-blank, non-`#` line (ADR-0034 §3).
|
||||
pub(crate) enum ReplayLine {
|
||||
/// Run this command text — either a journal `ok` record's
|
||||
/// extracted source, or a bare command verbatim.
|
||||
Run(String),
|
||||
/// A journal record whose status is not `ok` — skip it
|
||||
/// silently (a skipped failure is not a replay failure).
|
||||
Skip,
|
||||
}
|
||||
|
||||
/// Classify one replay input line (ADR-0034 §3). A journal
|
||||
/// record (`<ts>|<status>|<source>`) runs its source only when
|
||||
/// `ok` and is skipped otherwise; any other line is a bare
|
||||
/// command run verbatim. Detection is by the leading
|
||||
/// timestamp+status prefix, so a bare command that merely
|
||||
/// contains `|` (e.g. `select 'a|b' from t`) is run as-is.
|
||||
pub(crate) fn classify_replay_line(line: &str) -> ReplayLine {
|
||||
match history::parse_journal_record(line) {
|
||||
Some(rec) if rec.status_is_ok => ReplayLine::Run(rec.source),
|
||||
Some(_) => ReplayLine::Skip,
|
||||
None => ReplayLine::Run(line.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write `body` to `path` atomically via temp file + fsync +
|
||||
/// rename. The temp file is named `<final>.tmp` in the same
|
||||
/// directory so the rename stays on the same filesystem.
|
||||
|
||||
Reference in New Issue
Block a user