diff --git a/crates/aura-cli/src/main.rs b/crates/aura-cli/src/main.rs index 785115a..f7a3ed9 100644 --- a/crates/aura-cli/src/main.rs +++ b/crates/aura-cli/src/main.rs @@ -1748,9 +1748,10 @@ fn run_signal_r( let (tx_req, _rx_req) = mpsc::channel(); let (sources, window, pip_size) = resolve_run_data(&data, env, &binding); let wrapped = wrap_r(signal, tx_eq, tx_ex, tx_r, tx_req, StopRule::Vol { length: R_SMA_STOP_LENGTH, k: R_SMA_STOP_K }, false, pip_size, &binding, None); - let mut flat = wrapped - .compile_with_params(params) - .expect("signal binds + wraps to a valid harness"); + let mut flat = wrapped.compile_with_params(params).unwrap_or_else(|e| { + eprintln!("aura: this blueprint does not compile to a runnable harness: {e:?}"); + std::process::exit(1); + }); // Bind each declared tap to a fresh `Recorder` + channel, before bootstrap // — `flat.taps` already carries the signal's declared taps hoisted to the // root. Dedup is the caller's per `TapBindError::DuplicateBind`'s doc (the @@ -3585,6 +3586,25 @@ fn dispatch_run(a: RunCmd, env: &project::Env) { eprintln!("aura: {path}: {msg}"); std::process::exit(2); }); + // `wrap_r` (below, via `run_signal_r` AND the axis probe right here) welds + // the loaded signal by its hardcoded `"bias"` output port (the R-evaluator + // contract). A signal exposing any other output shape (a measurement-only + // blueprint) would otherwise only surface as `blueprint_axis_probe`'s + // `wrap_r` call panicking on `BuildError::UnknownOutPort` a few lines + // down — refuse here instead, before that probe runs. + if !signal.output().iter().any(|o| o.name == "bias") { + let exposed: Vec<&str> = signal.output().iter().map(|o| o.name.as_str()).collect(); + eprintln!( + "aura: `aura run` expects a strategy with a bias output; this blueprint \ + exposes {}", + if exposed.is_empty() { + "no outputs".to_string() + } else { + exposed.join(", ") + } + ); + std::process::exit(1); + } // Refuse an open (free-knob) blueprint at the dispatch boundary, mirroring // `blueprint_mc_family`'s closed-guard: `run` bootstraps over the EMPTY point, so a // free knob would panic in `compile_with_params` — reject it clean instead (#176).