feat(aura-engine): name the composite boundary — input roles + param aliases
Brings the other two composite-boundary edge-kinds to the named-projection shape #40 gave outputs. input_roles changes from a bare Vec<Vec<Target>> to Vec<Role { name, targets }> (rendered [in:<name>]); a composite gains params: Vec<ParamAlias { name, node, slot }> that relabels an interior leaf param slot's surface name in param_space() (rendered [param:<name>]). Param aliasing is a PURE NAMING OVERLAY, not curation (the load-bearing decision, per spec 0019): every interior param slot stays in param_space() and sweepable; the alias only relabels in place, never reorders or hides. Proven empirically — the MACD run is byte-identical (total_pips 0.1637945563898923, 3 sign flips), only the param labels improved: param_space() now surfaces [macd.fast, macd.slow, macd.signal] instead of three indistinguishable macd.length, and the manifest reads ema_fast/ ema_slow/ema_signal. C23 honoured: role/param/output names are non-load-bearing debug symbols dropped at lowering; identity is positional (role index, param slot, output field). compiled_view_golden is byte-identical (verified: the golden region is untouched in the diff). An out-of-range alias (missing/ non-leaf node or slot past the leaf's param count) is rejected at compile_with_params as BadInteriorIndex, mirroring the output range-check (no new variant). Orthogonal to #36 — purely additive at the composite level. Aliasing is demonstrated on the CLI MACD site only (the spec's worked example + a new E2E test macd_param_space_surfaces_the_three_named_aliases); sma_cross, the engine test fixtures, and the construction-layer fieldtests get the forced role-name + empty params, so the param_space C23 anchor goldens (param_space_mirrors_compiled_flat_node_param_order + siblings) stay byte-identical. Verification (orchestrator-run, not trusted from the agent report): cargo build/test/clippy --workspace -D warnings all green (engine 66, cli 12); the separate-workspace construction-layer fieldtest crate builds (guards the #42 latent-drift recurrence); compiled_view_golden + MACD determinism unchanged. Two faithful repairs to the plan's literal test/code bodies, no semantic change: the out-of-range test uses .err()/Some(BadInteriorIndex) (the Ok arm Vec<Box<dyn Node>> is not Debug, so .unwrap_err() would not compile), and the inline_composite destructure binds params as `param_aliases` to avoid shadowing the injected `params: &[Scalar]` arg. closes #41
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
use std::sync::mpsc;
|
||||
|
||||
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, SourceSpec, Target};
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, Role, SourceSpec, Target};
|
||||
use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub};
|
||||
|
||||
fn main() {
|
||||
@@ -41,10 +41,15 @@ fn main() {
|
||||
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
||||
],
|
||||
// input role 0 (price) fans into Sma(2).slot0 AND Sma(4).slot0
|
||||
vec![vec![
|
||||
Target { node: 0, slot: 0 },
|
||||
Target { node: 1, slot: 0 },
|
||||
]],
|
||||
vec![Role {
|
||||
name: "price".into(),
|
||||
targets: vec![
|
||||
Target { node: 0, slot: 0 },
|
||||
Target { node: 1, slot: 0 },
|
||||
],
|
||||
}],
|
||||
// no param aliases
|
||||
vec![],
|
||||
// single exposed output: Sub's output field 0
|
||||
vec![OutField { node: 2, field: 0, name: "out".into() }],
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// Public interface only: Composite / Blueprint accessors + Node::label() via
|
||||
// the boxed node; ledger C8-refinement (label is the disambiguating symbol).
|
||||
|
||||
use aura_engine::{BlueprintNode, Composite, Edge, OutField, Target};
|
||||
use aura_engine::{BlueprintNode, Composite, Edge, OutField, Role, Target};
|
||||
// NB: `node.label()` on a `&Box<dyn Node>` leaf dispatches via the trait object
|
||||
// without an explicit `use aura_core::Node` (the method is in scope through the
|
||||
// boxed trait object). A consumer does not need to import the Node trait to read
|
||||
@@ -43,7 +43,11 @@ fn cross(swap: bool) -> Composite {
|
||||
Sub::factory().into(),
|
||||
],
|
||||
vec![e_fast, e_slow],
|
||||
vec![vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }]],
|
||||
vec![Role {
|
||||
name: "price".into(),
|
||||
targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
|
||||
}],
|
||||
vec![],
|
||||
vec![OutField { node: 2, field: 0, name: "out".into() }],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use std::sync::mpsc;
|
||||
|
||||
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, SourceSpec, Target};
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, Role, SourceSpec, Target};
|
||||
use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub};
|
||||
|
||||
/// Inner composite: the SMA(2)/SMA(4) cross (one price role -> spread output).
|
||||
@@ -31,7 +31,11 @@ fn inner_cross() -> Composite {
|
||||
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
|
||||
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
||||
],
|
||||
vec![vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }]],
|
||||
vec![Role {
|
||||
name: "price".into(),
|
||||
targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
|
||||
}],
|
||||
vec![],
|
||||
vec![OutField { node: 2, field: 0, name: "out".into() }],
|
||||
)
|
||||
}
|
||||
@@ -48,7 +52,9 @@ fn strategy() -> Composite {
|
||||
Exposure::factory().into(),
|
||||
],
|
||||
vec![Edge { from: 0, to: 1, slot: 0, from_field: 0 }], // cross -> Exposure
|
||||
vec![vec![Target { node: 0, slot: 0 }]], // price -> inner role 0
|
||||
// price -> inner role 0
|
||||
vec![Role { name: "price".into(), targets: vec![Target { node: 0, slot: 0 }] }],
|
||||
vec![],
|
||||
vec![OutField { node: 1, field: 0, name: "out".into() }],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
// Public interface only.
|
||||
|
||||
use aura_core::ScalarKind;
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, SourceSpec, Target};
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, Role, SourceSpec, Target};
|
||||
// NB (fieldtest finding): `aura_engine::ScalarKind` does NOT resolve — the
|
||||
// construction surface (aura-engine) does not re-export the core scalar
|
||||
// vocabulary (ScalarKind / Scalar / Firing) that a graph-builder needs. A
|
||||
@@ -30,7 +30,11 @@ fn sma_cross() -> Composite {
|
||||
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
|
||||
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
||||
],
|
||||
vec![vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }]],
|
||||
vec![Role {
|
||||
name: "price".into(),
|
||||
targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
|
||||
}],
|
||||
vec![],
|
||||
vec![OutField { node: 2, field: 0, name: "out".into() }],
|
||||
)
|
||||
}
|
||||
@@ -63,8 +67,8 @@ fn describe_node(prefix: &str, n: &BlueprintNode) {
|
||||
for inner in c.nodes() {
|
||||
describe_node(&format!("{prefix} "), inner);
|
||||
}
|
||||
for (r, targets) in c.input_roles().iter().enumerate() {
|
||||
println!("{prefix} role[{r}] fans into {targets:?}");
|
||||
for (r, role) in c.input_roles().iter().enumerate() {
|
||||
println!("{prefix} role[{r}] {:?} fans into {:?}", role.name, role.targets);
|
||||
}
|
||||
for e in c.edges() {
|
||||
println!("{prefix} interior edge {e:?}");
|
||||
@@ -107,7 +111,8 @@ fn main() {
|
||||
assert_eq!(c.name(), "sma_cross");
|
||||
assert_eq!(c.nodes().len(), 3, "composite interior should have 3 items");
|
||||
assert_eq!(c.input_roles().len(), 1, "one price role");
|
||||
assert_eq!(c.input_roles()[0].len(), 2, "price fans into both SMAs");
|
||||
assert_eq!(c.input_roles()[0].name, "price", "the role carries its boundary name");
|
||||
assert_eq!(c.input_roles()[0].targets.len(), 2, "price fans into both SMAs");
|
||||
let out = c.output();
|
||||
assert_eq!(out.len(), 1, "the cross re-exports one output field");
|
||||
assert_eq!((out[0].node, out[0].field), (2, 0), "Sub output is the port");
|
||||
|
||||
Reference in New Issue
Block a user