Files
Aura/crates/aura-composites/tests/cost_graph.rs
T
claude b39fd63396 refactor: split the aura-std roster into C28 layer crates
Phase 4 of the Stratification milestone. aura-std held four C28 ladder
layers in one roster; this cuts them into layer-aligned, aura-core-only
node crates so the import direction is enforced by the crate graph:

- aura-std        — engine nodes only (arithmetic/logic/rolling + sinks)
- aura-market     — session, resample
- aura-strategy   — bias, stops, sizer, cost-model machinery
- aura-backtest   — sim_broker, position_management
- aura-vocabulary — the relocated closed std_vocabulary roster

Node modules move verbatim (byte-identical renames); consumers are
rewired by import path only. A new structural test
(aura-vocabulary/tests/c28_layering.rs) asserts each node crate's
[dependencies] stay within its C28-permitted inner set, catching the
acyclic-but-outward violation the compiler misses.

Behaviour byte-identical: full workspace suite green (1448 tests), no
golden edited, clippy -D warnings clean. C28 Status block updated.

closes #288
2026-07-19 20:28:20 +02:00

96 lines
4.8 KiB
Rust

//! `cost_graph` composite-builder: it fans the 4 PM-geometry inputs to N cost
//! nodes, surfaces each node's extra inputs as `cost[k].<port>` 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_strategy::{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"]);
}