diff --git a/crates/aura-registry/src/lib.rs b/crates/aura-registry/src/lib.rs index 002f25a..d847202 100644 --- a/crates/aura-registry/src/lib.rs +++ b/crates/aura-registry/src/lib.rs @@ -236,6 +236,31 @@ mod tests { let _ = fs::remove_file(&path); } + /// A `runs.jsonl` line written before the cycle-0047 typed-`Scalar` params + /// migration carries each param value as a **bare JSON number** (`2.0`), + /// where the current format tags it (`{"F64":2.0}`). `load()` is the durable + /// C18 read-path and must stay back-compatible across that wire-shape change: + /// a legacy bare-float line must still load, the bare float read back as the + /// documented `f64` coercion — not erroring at the first param. (The current + /// typed shape is already covered by the round-trip test.) + #[test] + fn load_reads_a_legacy_bare_float_params_line() { + let path = temp_path("legacy_bare_float"); + let _ = fs::remove_file(&path); + // An autonomous, minimal legacy line: a single bare-float param is the + // whole trigger (the rest of the RunReport is in the current shape). This + // mirrors the real pre-0047 store, where the first param `2.0` is where + // the typed-Scalar deserializer first chokes. + let legacy = r#"{"manifest":{"commit":"c","params":[["sma_cross.fast",2.0]],"window":[1,2],"seed":0,"broker":"b"},"metrics":{"total_pips":0.0,"max_drawdown":0.0,"exposure_sign_flips":0}}"#; + fs::write(&path, format!("{legacy}\n")).expect("write legacy line"); + let reg = Registry::open(&path); + let reports = reg.load().expect("a legacy bare-float params line must still load"); + assert_eq!(reports.len(), 1); + // the bare float reads back as the documented f64 coercion + assert_eq!(reports[0].manifest.params, vec![("sma_cross.fast".to_string(), Scalar::f64(2.0))]); + let _ = fs::remove_file(&path); + } + #[test] fn rank_by_orders_best_first_per_metric() { let reports = vec![