//! `cost_graph` composite-builder: it fans the 4 PM-geometry inputs to N cost //! nodes, surfaces each node's extra inputs as `cost[k].` roles, and exposes //! `CostSum`'s 3-field aggregate. These tests pin the exposed wiring contract (the //! input-role set + the output triple); value byte-identity is the CLI goldens' job. use aura_composites::cost_graph; use aura_core::Scalar; use aura_std::{ConstantCost, VolSlippageCost}; /// Two heterogeneous cost nodes: ConstantCost (no extras, index 0) and /// VolSlippageCost (one extra `volatility`, index 1). The composite exposes the 4 /// geometry roles + the namespaced `cost[1].volatility`, and the 3-field aggregate. #[test] fn cost_graph_exposes_geometry_and_namespaced_extras() { let cg = cost_graph(vec![ ConstantCost::builder().bind("cost_per_trade", Scalar::f64(2.0)), VolSlippageCost::builder().bind("slip_vol_mult", Scalar::f64(0.5)), ]); let roles: Vec<&str> = cg.input_roles().iter().map(|r| r.name.as_str()).collect(); assert_eq!( roles, vec!["closed", "open", "entry_price", "stop_price", "cost[1].volatility"] ); let outs: Vec<&str> = cg.output().iter().map(|o| o.name.as_str()).collect(); assert_eq!(outs, vec!["cost_in_r", "cum_cost_in_r", "open_cost_in_r"]); } /// A lone cost node with no extras exposes only the 4 geometry roles (the n=1 /// shape), plus the 3-field aggregate. #[test] fn cost_graph_single_node_has_no_extra_roles() { let cg = cost_graph(vec![ ConstantCost::builder().bind("cost_per_trade", Scalar::f64(2.0)), ]); let roles: Vec<&str> = cg.input_roles().iter().map(|r| r.name.as_str()).collect(); assert_eq!(roles, vec!["closed", "open", "entry_price", "stop_price"]); let outs: Vec<&str> = cg.output().iter().map(|o| o.name.as_str()).collect(); assert_eq!(outs, vec!["cost_in_r", "cum_cost_in_r", "open_cost_in_r"]); } /// Property: `cost_graph` composes an ARBITRARY number of cost nodes — the /// cycle-0084 headline that retired the hardcoded `MAX_RUN_COST_NODES = 2` cap — /// and namespaces each extra-bearing node's inputs by the node index, so two /// same-typed factors never collide. Three nodes (ConstantCost at idx 0 with no /// extras, then two VolSlippageCost at idx 1 and 2, each carrying a `volatility` /// extra) must expose the 4 SHARED geometry roles plus the two DISTINCT /// `cost[1].volatility` / `cost[2].volatility` roles, and still collapse to the /// single 3-field aggregate output regardless of arity. The shipped n=2 fixture /// has only one extra-bearing node, so it cannot witness a cross-node collision: /// a mis-indexed `k`, or extras flattened to a bare `volatility`, would alias the /// two nodes' inputs into one role and silently mis-feed the cost graph — this /// n=3 two-extra fixture fails loudly on that class while the n=1/n=2 fixtures /// stay green. #[test] fn cost_graph_arbitrary_arity_namespaces_each_extra_by_node_index() { let cg = cost_graph(vec![ ConstantCost::builder().bind("cost_per_trade", Scalar::f64(2.0)), VolSlippageCost::builder().bind("slip_vol_mult", Scalar::f64(0.5)), VolSlippageCost::builder().bind("slip_vol_mult", Scalar::f64(0.25)), ]); let roles: Vec<&str> = cg.input_roles().iter().map(|r| r.name.as_str()).collect(); assert_eq!( roles, vec![ "closed", "open", "entry_price", "stop_price", "cost[1].volatility", "cost[2].volatility", ] ); let outs: Vec<&str> = cg.output().iter().map(|o| o.name.as_str()).collect(); assert_eq!(outs, vec!["cost_in_r", "cum_cost_in_r", "open_cost_in_r"]); } /// Property: the 4 PM-geometry inputs are FANNED (shared once) across every cost /// node, never duplicated per node — so N extra-free cost nodes expose exactly the /// 4 geometry roles and ZERO `cost[k]` roles, independent of N. Two extra-free /// ConstantCost nodes must expose only `[closed, open, entry_price, stop_price]` /// (and the 3-field aggregate). The shipped single-node fixture has N=1, so it /// cannot catch a regression that per-node-duplicated the geometry into /// `cost[k].closed`-style roles, or that leaked a spurious `cost[k]` role for an /// extra-free node; this multi-node extra-free fixture pins the fan-once contract. #[test] fn cost_graph_fans_shared_geometry_across_multiple_extra_free_nodes() { let cg = cost_graph(vec![ ConstantCost::builder().bind("cost_per_trade", Scalar::f64(2.0)), ConstantCost::builder().bind("cost_per_trade", Scalar::f64(1.0)), ]); let roles: Vec<&str> = cg.input_roles().iter().map(|r| r.name.as_str()).collect(); assert_eq!(roles, vec!["closed", "open", "entry_price", "stop_price"]); let outs: Vec<&str> = cg.output().iter().map(|o| o.name.as_str()).collect(); assert_eq!(outs, vec!["cost_in_r", "cum_cost_in_r", "open_cost_in_r"]); }