fix(campaign): walk_forward window-fit refusal speaks ms prose, not Debug (fieldtest 0107 F8)

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
This commit is contained in:
2026-07-03 22:32:06 +02:00
parent 71405d4d86
commit 978ba15bd3
2 changed files with 26 additions and 4 deletions
+19 -2
View File
@@ -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.
+7 -2
View File
@@ -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:?}"),