// Cycle-0031 fieldtest — scenario 1 (axis a): the named single-run authoring path. // // Promise probed (spec 0031 acceptance #1, #2, #4): a downstream author writes // `Sma::builder().named("fast")`, inspects `param_space()` (now uniformly // `.` at every level, including the root `exposure.scale`), binds // `sma_cross.fast.length` BY NAME with NO ParamAlias and NO index counting, and // runs. This is the exact pain the milestone-0030 fixture (mw_1) had to work // around with two hand-counted ParamAlias overlays. // // Public interface only: API discovered from the ledger (C9/C23), spec 0031, // and `cargo doc`. No crates/*/src was read. // // The harness: source -> sma_cross composite { Sma("fast"), Sma("slow"), Sub } // -> Exposure -> SimBroker -> Recorder. use std::sync::mpsc; use aura_core::{Firing, Scalar, ScalarKind, Timestamp}; use aura_engine::{BlueprintNode, Composite, Edge, OutField, Role, Target}; use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub}; /// The reusable inner 2/4-SMA-cross composite. The two SMA legs are now named /// inline via `.named()` — the single act that (a) distinguishes the fan-in and /// (b) qualifies the two param paths. No ParamAlias argument exists anymore /// (Composite::new is 4-arg). fn sma_cross() -> Composite { Composite::new( "sma_cross", vec![ Sma::builder().named("fast").into(), Sma::builder().named("slow").into(), Sub::builder().into(), ], vec![ Edge { from: 0, to: 2, slot: 0, from_field: 0 }, Edge { from: 1, to: 2, slot: 1, from_field: 0 }, ], vec![Role { name: "price".into(), targets: vec![ Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }, ], source: None, }], vec![OutField { node: 2, field: 0, name: "out".into() }], ) } /// The root harness over the named cross. The root-level Exposure node now also /// carries a name (default "exposure"), so its knob surfaces as `exposure.scale`. fn harness(tx: mpsc::Sender<(Timestamp, Vec)>) -> Composite { Composite::new( "harness", vec![ BlueprintNode::Composite(sma_cross()), Exposure::builder().into(), SimBroker::builder(1e-4).into(), Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx).into(), ], vec![ Edge { from: 0, to: 1, slot: 0, from_field: 0 }, Edge { from: 1, to: 2, slot: 0, from_field: 0 }, Edge { from: 2, to: 3, slot: 0, from_field: 0 }, ], vec![Role { name: "price".into(), targets: vec![ Target { node: 0, slot: 0 }, Target { node: 2, slot: 1 }, ], source: Some(ScalarKind::F64), }], vec![OutField { node: 0, field: 0, name: "exposure".into() }], ) } fn synthetic_prices() -> Vec<(Timestamp, Scalar)> { let prices = [ 1.00, 1.01, 1.02, 1.03, 1.05, 1.08, 1.06, 1.04, 1.02, 1.01, 1.03, 1.07, ]; prices .iter() .enumerate() .map(|(i, &p)| (Timestamp(60_000_000_000 * i as i64), Scalar::F64(p))) .collect() } fn main() { // --- 1. Inspect the param-space: are the names what the spec promises? ----- let (tx0, _rx0) = mpsc::channel(); let space = harness(tx0).param_space(); println!("param_space() knobs (name : kind):"); for p in &space { println!(" {} : {:?}", p.name, p.kind); } let names: Vec<&str> = space.iter().map(|p| p.name.as_str()).collect(); // spec 0031 acceptance #1/#4: uniform ., distinct named legs. assert!( names.contains(&"sma_cross.fast.length"), "expected sma_cross.fast.length, got {names:?}" ); assert!( names.contains(&"sma_cross.slow.length"), "expected sma_cross.slow.length, got {names:?}" ); assert!( names.contains(&"exposure.scale"), "expected root-level exposure.scale, got {names:?}" ); // --- 2. Bind BY NAME, with no ParamAlias and no index counting. ------------ let (tx, rx) = mpsc::channel::<(Timestamp, Vec)>(); let mut h = harness(tx) .with("sma_cross.fast.length", 2) .with("sma_cross.slow.length", 4) .with("exposure.scale", 0.5) .bootstrap() .expect("named harness binds by the uniform . names and bootstraps"); h.run(vec![synthetic_prices()]); drop(h); let rows: Vec<(Timestamp, Vec)> = rx.iter().collect(); println!("\nnamed-bound run — recorded rows: {}", rows.len()); for (ts, row) in &rows { if let Scalar::F64(x) = row[0] { println!(" ts={:>14} exposure={:+.4}", ts.0, x); } } assert!(!rows.is_empty(), "named-bound harness must record a populated trace"); // --- 3. Determinism (C1): a disjoint second run is bit-identical. ---------- let (tx2, rx2) = mpsc::channel::<(Timestamp, Vec)>(); let mut h2 = harness(tx2) .with("sma_cross.fast.length", 2) .with("sma_cross.slow.length", 4) .with("exposure.scale", 0.5) .bootstrap() .expect("second disjoint bootstrap"); h2.run(vec![synthetic_prices()]); drop(h2); let rows2: Vec<(Timestamp, Vec)> = rx2.iter().collect(); assert_eq!(rows, rows2, "two disjoint named runs must be bit-identical (C1)"); println!("\nOK: param-space uniform ., named bind ran, deterministic."); }