feat(aura-core,aura-engine,aura-cli): node-instance naming retires ParamAlias
Every blueprint node now carries a name. A primitive builder gains an optional
instance name (Sma::builder().named("fast")); when omitted it defaults to the
node's type label, ASCII-lowercased verbatim ("SMA"->"sma", "SimBroker"->
"simbroker"). param_space() is now uniformly <node>.<param> at every level
including the root (sma_cross.fast.length, exposure.scale). Fan-in
distinguishability (C9) and signature_of re-key onto the node name: two same-type
siblings both defaulting to "sma" collide and IndistinguishableFanIn fires, so
one act -- naming the legs "fast"/"slow" -- fixes both the naming collision and
the fan-in check. The index-addressed ParamAlias overlay, Composite.params, the
Composite::new 5th argument, aliases_on, and check_alias_indices are removed
(Role.name and OutField.name untouched). Ledger C9 and C23 amended.
This is why the change is correct, not merely convenient: blueprints are
value-empty (C11), so the thing that would otherwise distinguish two SMAs --
their length -- is not present at construction; identity must come from a name,
not a deferred value. Closes the #56 friction surfaced by the param-space &
sweep milestone fieldtest (a freshly-authored 2-SMA cross was unbindable and
would not bootstrap without two hand-counted ParamAlias overlays).
Two plan defects were corrected during implementation and verified:
- the three new fan-in/path tests authored sma_cross at root level, which would
qualify to "fast.length" (root prefix is empty) and is not the nested case the
fan-in check inspects; nesting sma_cross under a root (a shared
sma_cross_under_root helper) restores the asserted "sma_cross.fast.length" and
IndistinguishableFanIn{node:2};
- three cycle-0030 named-binder tests bound the real harness by the old names
(sma_cross.fast / scale) and were migrated to the new path strings, surfaced by
the workspace test gate.
Verified by the orchestrator: cargo build/test --workspace green (198 tests,
0 red), clippy --all-targets -D warnings clean. model_to_json emits the type
label + factory param names (node-name-independent), so sample-model.json and the
graph_model golden are byte-identical (untouched). NodeSchema gained a Default
derive (the builder default-name test constructs an empty schema). fieldtests/
are frozen non-workspace records, not migrated.
closes #56
This commit is contained in:
+34
-40
@@ -11,7 +11,7 @@ mod render;
|
||||
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
|
||||
use aura_engine::{
|
||||
f64_field, summarize, BlueprintNode, Composite, Edge, FlatGraph, Harness,
|
||||
OutField, ParamAlias, Role, RunManifest, RunReport, SourceSpec, SweepFamily, Target,
|
||||
OutField, Role, RunManifest, RunReport, SourceSpec, SweepFamily, Target,
|
||||
};
|
||||
use aura_registry::{rank_by, Registry};
|
||||
use aura_std::{Ema, Exposure, Recorder, SimBroker, Sma, Sub};
|
||||
@@ -136,7 +136,11 @@ fn run_sample() -> RunReport {
|
||||
fn sma_cross(name: &str) -> Composite {
|
||||
Composite::new(
|
||||
name,
|
||||
vec![Sma::builder().into(), Sma::builder().into(), Sub::builder().into()],
|
||||
vec![
|
||||
Sma::builder().named("fast").into(), // fast SMA leg
|
||||
Sma::builder().named("slow").into(), // slow SMA leg
|
||||
Sub::builder().into(),
|
||||
],
|
||||
vec![
|
||||
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
|
||||
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
||||
@@ -146,10 +150,6 @@ fn sma_cross(name: &str) -> Composite {
|
||||
targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
|
||||
source: None,
|
||||
}],
|
||||
vec![
|
||||
ParamAlias { name: "fast".into(), node: 0, slot: 0 }, // fast SMA length
|
||||
ParamAlias { name: "slow".into(), node: 1, slot: 0 }, // slow SMA length
|
||||
],
|
||||
vec![OutField { node: 2, field: 0, name: "cross".into() }],
|
||||
)
|
||||
}
|
||||
@@ -190,7 +190,6 @@ fn sample_blueprint_with_sinks() -> (
|
||||
],
|
||||
source: Some(ScalarKind::F64),
|
||||
}],
|
||||
vec![], // params: the interior sma_cross carries the aliases
|
||||
vec![], // output: the root ends in sinks, no re-export
|
||||
);
|
||||
(bp, rx_eq, rx_ex)
|
||||
@@ -226,9 +225,9 @@ fn scalar_as_param_f64(s: &Scalar) -> f64 {
|
||||
fn sweep_family() -> SweepFamily {
|
||||
let bp = sample_blueprint_with_sinks().0;
|
||||
let space = bp.param_space();
|
||||
bp.axis("sma_cross.fast", [2, 3])
|
||||
.axis("sma_cross.slow", [4, 5])
|
||||
.axis("scale", [0.5])
|
||||
bp.axis("sma_cross.fast.length", [2, 3])
|
||||
.axis("sma_cross.slow.length", [4, 5])
|
||||
.axis("exposure.scale", [0.5])
|
||||
.sweep(|point| {
|
||||
let (bp, rx_eq, rx_ex) = sample_blueprint_with_sinks();
|
||||
let mut h = bp
|
||||
@@ -338,11 +337,11 @@ fn macd(name: &str) -> Composite {
|
||||
Composite::new(
|
||||
name,
|
||||
vec![
|
||||
Ema::builder().into(), // 0 fast EMA
|
||||
Ema::builder().into(), // 1 slow EMA
|
||||
Sub::builder().into(), // 2 MACD line = fast − slow
|
||||
Ema::builder().into(), // 3 signal EMA of the MACD line
|
||||
Sub::builder().into(), // 4 histogram = MACD line − signal
|
||||
Ema::builder().named("fast").into(), // 0 fast EMA
|
||||
Ema::builder().named("slow").into(), // 1 slow EMA
|
||||
Sub::builder().into(), // 2 MACD line = fast − slow
|
||||
Ema::builder().named("signal").into(), // 3 signal EMA of the MACD line
|
||||
Sub::builder().into(), // 4 histogram = MACD line − signal
|
||||
],
|
||||
vec![
|
||||
Edge { from: 0, to: 2, slot: 0, from_field: 0 }, // fast → line[0]
|
||||
@@ -359,11 +358,6 @@ fn macd(name: &str) -> Composite {
|
||||
],
|
||||
source: None,
|
||||
}],
|
||||
vec![
|
||||
ParamAlias { name: "fast".into(), node: 0, slot: 0 }, // fast EMA length
|
||||
ParamAlias { name: "slow".into(), node: 1, slot: 0 }, // slow EMA length
|
||||
ParamAlias { name: "signal".into(), node: 3, slot: 0 }, // signal EMA length
|
||||
],
|
||||
vec![
|
||||
OutField { node: 2, field: 0, name: "macd".into() }, // the MACD line
|
||||
OutField { node: 3, field: 0, name: "signal".into() }, // the signal line
|
||||
@@ -402,7 +396,6 @@ fn macd_strategy_blueprint(
|
||||
],
|
||||
source: Some(ScalarKind::F64),
|
||||
}],
|
||||
vec![], // params: the interior macd carries the aliases
|
||||
vec![], // output: the root ends in sinks, no re-export
|
||||
)
|
||||
}
|
||||
@@ -513,9 +506,9 @@ mod tests {
|
||||
// so a caller can bootstrap one point, run it, and drain both sinks.
|
||||
let (bp, rx_eq, rx_ex) = sample_blueprint_with_sinks();
|
||||
let mut h = bp
|
||||
.with("sma_cross.fast", 2)
|
||||
.with("sma_cross.slow", 4)
|
||||
.with("scale", 0.5)
|
||||
.with("sma_cross.fast.length", 2)
|
||||
.with("sma_cross.slow.length", 4)
|
||||
.with("exposure.scale", 0.5)
|
||||
.bootstrap()
|
||||
.expect("sample blueprint compiles under a valid point");
|
||||
h.run(vec![synthetic_prices()]);
|
||||
@@ -534,10 +527,10 @@ mod tests {
|
||||
for line in &lines {
|
||||
assert!(line.starts_with(r#"{"manifest":{"commit":""#), "not a RunReport: {line}");
|
||||
}
|
||||
assert!(lines[0].contains(r#""params":{"sma_cross.fast":2,"sma_cross.slow":4,"scale":0.5}"#), "line0: {}", lines[0]);
|
||||
assert!(lines[1].contains(r#""params":{"sma_cross.fast":2,"sma_cross.slow":5,"scale":0.5}"#), "line1: {}", lines[1]);
|
||||
assert!(lines[2].contains(r#""params":{"sma_cross.fast":3,"sma_cross.slow":4,"scale":0.5}"#), "line2: {}", lines[2]);
|
||||
assert!(lines[3].contains(r#""params":{"sma_cross.fast":3,"sma_cross.slow":5,"scale":0.5}"#), "line3: {}", lines[3]);
|
||||
assert!(lines[0].contains(r#""params":{"sma_cross.fast.length":2,"sma_cross.slow.length":4,"exposure.scale":0.5}"#), "line0: {}", lines[0]);
|
||||
assert!(lines[1].contains(r#""params":{"sma_cross.fast.length":2,"sma_cross.slow.length":5,"exposure.scale":0.5}"#), "line1: {}", lines[1]);
|
||||
assert!(lines[2].contains(r#""params":{"sma_cross.fast.length":3,"sma_cross.slow.length":4,"exposure.scale":0.5}"#), "line2: {}", lines[2]);
|
||||
assert!(lines[3].contains(r#""params":{"sma_cross.fast.length":3,"sma_cross.slow.length":5,"exposure.scale":0.5}"#), "line3: {}", lines[3]);
|
||||
for line in &lines {
|
||||
assert!(line.contains(r#""total_pips":"#), "missing total_pips: {line}");
|
||||
assert!(line.contains(r#""max_drawdown":"#), "missing max_drawdown: {line}");
|
||||
@@ -577,24 +570,25 @@ mod tests {
|
||||
}
|
||||
|
||||
/// E2E acceptance (#41 / spec 0019, the worked example): the real MACD strategy
|
||||
/// blueprint's swept param surface relabels the three otherwise-indistinguishable
|
||||
/// EMA `length` slots to `macd.fast` / `macd.slow` / `macd.signal` — the named
|
||||
/// composite boundary visible end-to-end through `param_space()`, with the slot
|
||||
/// count and order unchanged (C23 — pure naming overlay, not curation: every
|
||||
/// interior slot stays sweepable, the `scale` knob is unaffected).
|
||||
/// blueprint's swept param surface qualifies the three otherwise-indistinguishable
|
||||
/// EMA `length` slots by node name to `macd.fast.length` / `macd.slow.length` /
|
||||
/// `macd.signal.length` — the named composite boundary visible end-to-end through
|
||||
/// `param_space()`, with the slot count and order unchanged (C23 — node names are
|
||||
/// non-load-bearing: every interior slot stays sweepable, the `exposure.scale`
|
||||
/// knob is unaffected).
|
||||
#[test]
|
||||
fn macd_param_space_surfaces_the_three_named_aliases() {
|
||||
fn macd_param_space_surfaces_the_three_named_legs() {
|
||||
let names: Vec<String> =
|
||||
macd_blueprint().param_space().into_iter().map(|p| p.name).collect();
|
||||
// three aliased composite slots, in declared (fast, slow, signal) order,
|
||||
// then the strategy-level Exposure `scale` (outside the composite, unaliased).
|
||||
// three named composite-interior slots, in declared (fast, slow, signal)
|
||||
// order, then the strategy-level Exposure `scale` (a root-level leaf).
|
||||
assert_eq!(
|
||||
names,
|
||||
vec![
|
||||
"macd.fast".to_string(),
|
||||
"macd.slow".to_string(),
|
||||
"macd.signal".to_string(),
|
||||
"scale".to_string(),
|
||||
"macd.fast.length".to_string(),
|
||||
"macd.slow.length".to_string(),
|
||||
"macd.signal.length".to_string(),
|
||||
"exposure.scale".to_string(),
|
||||
],
|
||||
"MACD param surface must expose the three named EMA lengths + scale",
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user