Files
Aura/crates/aura-engine/tests/reopen_rebuild_e2e.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

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"
);
}