fix(runner): contain mc synthetic member panics (GREEN)
blueprint_mc_family joins its sweep/walkforward siblings on the
51096a3 machinery: catch_member_panic around the bare member run in
the monte_carlo draw closure, per-seed capture, lowest-seed
resolution after the join (seed-input order, thread-order-independent,
C1), exit via the shared aura: warning: + exit 3 path. Ordering is
load-bearing and documented at the site: the captured fault resolves
BEFORE the vacuous-mc guard, so metrics-identical placeholder reports
from faulted draws cannot masquerade as a vacuous run. All three
containment e2es green; clippy clean. refs #278
This commit is contained in:
@@ -354,6 +354,20 @@ fn lowest_point_fault(family: &SweepFamily, faults: Mutex<Vec<(Vec<Cell>, String
|
|||||||
.find_map(|pt| captured.iter().find(|(p, _)| *p == pt.params).map(|(_, m)| m.clone()))
|
.find_map(|pt| captured.iter().find(|(p, _)| *p == pt.params).map(|(_, m)| m.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The LOWEST seed's captured member-panic message (#278), found by walking
|
||||||
|
/// `family.draws` — `monte_carlo`'s own contract guarantees seed-**input**
|
||||||
|
/// order regardless of thread completion (C1) — for the first draw whose
|
||||||
|
/// seed matches a captured fault. Thread-order-independent by construction,
|
||||||
|
/// mirroring [`lowest_point_fault`]'s and `lowest_window_fault`'s identical
|
||||||
|
/// convention one level over (per-point sweep / per-window walk-forward).
|
||||||
|
fn lowest_seed_fault(family: &McFamily, faults: Mutex<Vec<(u64, String)>>) -> Option<String> {
|
||||||
|
let captured = faults.into_inner().expect("fault capture lock");
|
||||||
|
family
|
||||||
|
.draws
|
||||||
|
.iter()
|
||||||
|
.find_map(|d| captured.iter().find(|(s, _)| *s == d.seed).map(|(_, m)| m.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Render + exit(3) on a contained member panic (#278: the deliberate
|
/// Render + exit(3) on a contained member panic (#278: the deliberate
|
||||||
/// failed-cells exit, C14, never the raw 101 an uncontained panic would
|
/// failed-cells exit, C14, never the raw 101 an uncontained panic would
|
||||||
/// yield) — reuses `aura_campaign::member_fault_prose`'s established
|
/// yield) — reuses `aura_campaign::member_fault_prose`'s established
|
||||||
@@ -772,11 +786,35 @@ pub fn blueprint_mc_family(
|
|||||||
// re-runs the shared reduce-mode member path over its own seeded synthetic walk.
|
// re-runs the shared reduce-mode member path over its own seeded synthetic walk.
|
||||||
let seeds: Vec<u64> = (1..=n_seeds).collect();
|
let seeds: Vec<u64> = (1..=n_seeds).collect();
|
||||||
let base_point: Vec<Scalar> = Vec::new();
|
let base_point: Vec<Scalar> = Vec::new();
|
||||||
|
// #278: `run_blueprint_member` ran bare here, the one family builder without
|
||||||
|
// the #272 fault boundary its sweep/walk-forward siblings gained in 51096a3
|
||||||
|
// — a member-compile panic (e.g. an `Sma::new` length assert) would otherwise
|
||||||
|
// unwind straight through `monte_carlo`'s `run_indexed` to an uncaught exit
|
||||||
|
// 101. Contained the same way: `catch_member_panic` + a per-seed capture,
|
||||||
|
// resolved to the LOWEST seed's message after the join (thread-order-
|
||||||
|
// independent, C1) via `lowest_seed_fault`.
|
||||||
|
let faults: Mutex<Vec<(u64, String)>> = Mutex::new(Vec::new());
|
||||||
let family = monte_carlo(&base_point, &seeds, |seed, _base| {
|
let family = monte_carlo(&base_point, &seeds, |seed, _base| {
|
||||||
let sources = synthetic_walk_sources(seed);
|
let sources = synthetic_walk_sources(seed);
|
||||||
let window = window_of(&sources).expect("non-empty synthetic walk");
|
let window = window_of(&sources).expect("non-empty synthetic walk");
|
||||||
run_blueprint_member(reload(doc), &[], &space, sources, window, seed, pip, &topo, env, DEFAULT_STOP, &binding, &[], NO_INSTRUMENT_CONTEXT)
|
match catch_member_panic(|| {
|
||||||
|
run_blueprint_member(reload(doc), &[], &space, sources, window, seed, pip, &topo, env, DEFAULT_STOP, &binding, &[], NO_INSTRUMENT_CONTEXT)
|
||||||
|
}) {
|
||||||
|
Ok(report) => report,
|
||||||
|
Err(msg) => {
|
||||||
|
faults.lock().expect("fault capture lock").push((seed, msg));
|
||||||
|
axis_grid_probe_report()
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
// The captured fault must win BEFORE the vacuous-mc guard below: a faulted
|
||||||
|
// draw's placeholder report is metrics-identical across every faulted seed,
|
||||||
|
// so a run with >= 2 faulted seeds (or one faulted + one real draw sharing
|
||||||
|
// its placeholder's zero metrics) could otherwise trip the vacuous refusal
|
||||||
|
// instead of surfacing the real member fault.
|
||||||
|
if let Some(msg) = lowest_seed_fault(&family, faults) {
|
||||||
|
exit_on_member_panic(&msg);
|
||||||
|
}
|
||||||
// Silent-vacuous MC guard (refuse-don't-guess, C10): with >= 2 seeds, if every draw's
|
// Silent-vacuous MC guard (refuse-don't-guess, C10): with >= 2 seeds, if every draw's
|
||||||
// metrics are bit-identical to the first, no seed reached a distinguishable realization —
|
// metrics are bit-identical to the first, no seed reached a distinguishable realization —
|
||||||
// the strategy never warmed over the fixed synthetic walk (e.g. a lookback as deep as the
|
// the strategy never warmed over the fixed synthetic walk (e.g. a lookback as deep as the
|
||||||
|
|||||||
Reference in New Issue
Block a user