From 978ba15bd3aff0d7f1094d917959dc0d90524543 Mon Sep 17 00:00:00 2001 From: Brummel Date: Fri, 3 Jul 2026 22:32:06 +0200 Subject: [PATCH] fix(campaign): walk_forward window-fit refusal speaks ms prose, not Debug (fieldtest 0107 F8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The roller refusal leaked its raw Rust form through ExecFault::Window (SpanTooShort { span: (Timestamp(...), ...), need: ... }) — the one break in the cycle's Debug-free prose seam. The detail now phrases both roller refusals in the doc's own ms unit: 'in_sample_ms + out_of_sample_ms = N ms exceeds the campaign window [a, b] (L ms)'. RED-first: the wf_roller_refusal_maps_to_window_fault pin was flipped to the prose form (observed RED on the old Debug output), then the map_err phrased; live repro confirmed against the fieldtest fixture. Gates: workspace 987/0, clippy -D warnings clean. refs #198 --- crates/aura-campaign/src/exec.rs | 21 +++++++++++++++++++-- crates/aura-campaign/src/lib.rs | 9 +++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/crates/aura-campaign/src/exec.rs b/crates/aura-campaign/src/exec.rs index 2e46410..6cf3c75 100644 --- a/crates/aura-campaign/src/exec.rs +++ b/crates/aura-campaign/src/exec.rs @@ -13,7 +13,7 @@ use aura_analysis::{FamilySelection, SelectionMode}; use aura_core::{zip_params, Cell, ParamSpec, Scalar, Timestamp}; use aura_engine::{ sweep, walk_forward, GridSpace, ListSpace, RollMode, RunManifest, RunMetrics, RunReport, - Space, SweepFamily, SweepPoint, WindowBounds, WindowRoller, WindowRun, + Space, SweepFamily, SweepPoint, WalkForwardError, WindowBounds, WindowRoller, WindowRun, }; use aura_registry::{ optimize, optimize_deflated, optimize_plateau, sweep_member_reports, @@ -508,7 +508,24 @@ fn run_walk_forward_stage( *step_ms as i64, roll_mode, ) - .map_err(|e| ExecFault::Window { stage, detail: format!("{e:?}") })?; + .map_err(|e| ExecFault::Window { + stage, + // The detail string reaches user-facing seams verbatim — phrase both + // roller refusals in the doc's ms unit, never a Debug form. + detail: match e { + WalkForwardError::SpanTooShort { .. } => format!( + "in_sample_ms + out_of_sample_ms = {} ms exceeds the campaign window [{}, {}] ({} ms)", + in_sample_ms + out_of_sample_ms, + cell.window_ms.0, + cell.window_ms.1, + cell.window_ms.1 - cell.window_ms.0 + 1, + ), + // Zero lengths are doc-tier faults (preflight); phrased anyway. + WalkForwardError::NonPositiveLength { field, value } => { + format!("walk_forward {field} must be > 0 (got {value})") + } + }, + })?; // A second identical roller enumerates the bounds up front: the engine's // walk_forward consumes its roller, and the parallel closure needs each // window's roll index for deterministic fault attribution. diff --git a/crates/aura-campaign/src/lib.rs b/crates/aura-campaign/src/lib.rs index 050ae9d..d31c289 100644 --- a/crates/aura-campaign/src/lib.rs +++ b/crates/aura-campaign/src/lib.rs @@ -861,8 +861,13 @@ mod wf_tests { ExecFault::Window { stage, detail } => { assert_eq!(stage, 1, "walk_forward is pipeline stage 1 here"); assert!( - detail.contains("SpanTooShort"), - "detail carries the roller refusal: {detail}" + detail.contains("in_sample_ms + out_of_sample_ms = 60 ms") + && detail.contains("(50 ms)"), + "detail phrases the refusal in doc-unit ms prose: {detail}" + ); + assert!( + !detail.contains("SpanTooShort") && !detail.contains("Timestamp("), + "no Debug form leaks through the detail seam: {detail}" ); } other => panic!("expected ExecFault::Window, got {other:?}"),