b39fd63396
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
105 lines
4.5 KiB
Rust
105 lines
4.5 KiB
Rust
//! End-to-end coverage for #246 tasks 1-2: `PrimitiveBuilder::bind`/`unbind`
|
|
//! storing the bound value as data merged back at build time (aura-core), and
|
|
//! `Composite::reopen` re-opening a bound param at its `param_space()` path
|
|
//! (aura-engine). The in-crate unit tests (`node.rs`/`blueprint.rs` `mod
|
|
//! tests`) already pin the mechanism directly against `PrimitiveBuilder`; this
|
|
//! file instead drives the **full public compile pipeline**
|
|
//! (`Composite::reopen` -> `compile_with_params` -> `FlatGraph`) through only
|
|
//! `aura_engine`'s exported surface — the seam the future CLI axis-override
|
|
//! path (the rest of #246) will call. A regression that broke the merge only
|
|
//! when routed through `compile_with_params` (as opposed to calling
|
|
//! `PrimitiveBuilder::build` directly) would pass every existing unit test
|
|
//! while breaking this.
|
|
|
|
use aura_core::{Scalar, ScalarKind};
|
|
use aura_engine::{Composite, Role, Target};
|
|
use aura_std::Sma;
|
|
use aura_strategy::Bias;
|
|
|
|
/// A single bound `Sma`, wired from a root role so it compiles standalone.
|
|
fn one_bound_sma() -> Composite {
|
|
Composite::new(
|
|
"sig",
|
|
vec![Sma::builder().named("sma").bind("length", Scalar::i64(2)).into()],
|
|
vec![],
|
|
vec![Role {
|
|
name: "series".into(),
|
|
targets: vec![Target { node: 0, slot: 0 }],
|
|
source: Some(ScalarKind::F64),
|
|
}],
|
|
vec![],
|
|
)
|
|
}
|
|
|
|
/// Property: a bound param, once reopened, is genuinely re-bindable through
|
|
/// the full `compile_with_params` pipeline — the compiled node reflects the
|
|
/// value supplied AT COMPILE TIME, not the stale bound default. This is the
|
|
/// build-time merge (`PrimitiveBuilder::build`, aura-core #246 task 1) proven
|
|
/// under the compile boundary it must survive (aura-engine #246 task 2), not
|
|
/// just called directly.
|
|
#[test]
|
|
fn reopened_param_rebuilds_with_the_freshly_supplied_value_not_the_stale_default() {
|
|
// Before reopen: fully bound, nothing to supply, and the default (2) is
|
|
// what gets built. (A fresh `Composite` — `reopen`/`compile_with_params`
|
|
// both consume `self`, so the "before" and "after" instances are built
|
|
// separately from the same constructor rather than cloned.)
|
|
let flat = one_bound_sma().compile_with_params(&[]).expect("fully-bound composite compiles with no params");
|
|
assert_eq!(flat.nodes[0].label(), "SMA(2)", "the bound default builds unchanged");
|
|
|
|
let reopened = one_bound_sma().reopen("sma.length").expect("bound knob reopens");
|
|
let space = reopened.param_space();
|
|
assert_eq!(space.len(), 1, "reopen returns exactly the one knob to the open surface");
|
|
assert_eq!(space[0].name, "sma.length");
|
|
|
|
let flat2 = reopened
|
|
.compile_with_params(&[Scalar::i64(9)])
|
|
.expect("reopened composite compiles once the freed slot is supplied");
|
|
assert_eq!(
|
|
flat2.nodes[0].label(),
|
|
"SMA(9)",
|
|
"the compiled node must be built from the freshly supplied value (9), not the stale bound default (2)"
|
|
);
|
|
}
|
|
|
|
/// Property: reopening one node's bound param does not disturb a SIBLING
|
|
/// node's own still-bound default. `BoundParam`'s per-node bookkeeping
|
|
/// (`pos`, restored on `unbind`) must stay node-local through the compile
|
|
/// pipeline — a bug that let one node's reopen leak into another's merge
|
|
/// would build the sibling from the wrong cell.
|
|
#[test]
|
|
fn reopening_one_node_leaves_a_sibling_bound_default_intact_through_compile() {
|
|
let signal = Composite::new(
|
|
"sig",
|
|
vec![
|
|
Sma::builder().named("fast").bind("length", Scalar::i64(2)).into(),
|
|
Bias::builder().named("b").bind("scale", Scalar::f64(0.5)).into(),
|
|
],
|
|
vec![],
|
|
vec![
|
|
Role {
|
|
name: "series".into(),
|
|
targets: vec![Target { node: 0, slot: 0 }],
|
|
source: Some(ScalarKind::F64),
|
|
},
|
|
Role {
|
|
name: "signal".into(),
|
|
targets: vec![Target { node: 1, slot: 0 }],
|
|
source: Some(ScalarKind::F64),
|
|
},
|
|
],
|
|
vec![],
|
|
);
|
|
|
|
let reopened = signal.reopen("fast.length").expect("fast.length reopens");
|
|
let flat = reopened
|
|
.compile_with_params(&[Scalar::i64(7)])
|
|
.expect("compiles once the one freed slot is supplied");
|
|
|
|
assert_eq!(flat.nodes[0].label(), "SMA(7)", "the reopened node builds from the freshly supplied value");
|
|
assert_eq!(
|
|
flat.nodes[1].label(),
|
|
"Bias(0.5)",
|
|
"the sibling's still-bound default must build unchanged — reopen must not perturb it"
|
|
);
|
|
}
|