feat(aura-core): name input ports
PortSpec gains a non-load-bearing `name: String`, so an input port is named just as FieldSpec.name (output) and ParamSpec.name (param) already are — input ports were the lone unnamed member of the node signature. Identity stays positional by slot (C23); the name is render/debug only, never read by bootstrap or the run loop. PortSpec drops Copy (String is not Copy), exactly as ParamSpec already does. - Every aura-std node names its input slots: SMA/EMA "series", Sub/Add "lhs"/"rhs", Exposure "signal", SimBroker "exposure"/"price" (the slots become self-documenting), LinComb "term[i]" and Recorder "col[i]" generated in their build loops (mirroring LinComb's existing weights[i] param loop). - derive_signature carries a composite's Role.name into the derived input port (it was dropped before — the output side already carried FieldSpec.name), so the graph model is homogeneously named at both levels. - model_to_json (port_json + the composite-header inputs in scope_json) emits the name as a third tuple element: ["f64","any","exposure"]. The byte golden was re-captured (machine bytes) and its substring twins updated; the model is now fully named across inputs/outputs/params. - All 16 PortSpec construction sites threaded in one compile-gate change; test fixtures carry fixture names. C8 realization note added to the design ledger. Why name-only, no validation: the name is a pure debug symbol. Wire-by-name was rejected (it would be a C23 contract change). Bootstrap slot-wiring validation (which would close #21's same-kind swap footgun) is deferred to its own cycle — a name alone does not catch the swap; it makes the slots self-documenting and gives a future validation something to check against. Verified: cargo test --workspace 168 green; clippy --all-targets -D warnings clean; cargo build --workspace clean. Read-only render path (C9), no serde (C14), scalar kinds unchanged (C4). closes #50 refs #21 refs #51
This commit is contained in:
@@ -33,8 +33,8 @@ impl Add {
|
||||
"Add",
|
||||
NodeSchema {
|
||||
inputs: vec![
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "lhs".into() },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "rhs".into() },
|
||||
],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
@@ -91,4 +91,11 @@ mod tests {
|
||||
inputs[1].push(Scalar::F64(4.0)).unwrap();
|
||||
assert_eq!(add.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(14.0)].as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slots_are_named_lhs_rhs() {
|
||||
let a = Add::builder();
|
||||
let names: Vec<&str> = a.schema().inputs.iter().map(|p| p.name.as_str()).collect();
|
||||
assert_eq!(names, ["lhs", "rhs"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ impl Ema {
|
||||
PrimitiveBuilder::new(
|
||||
"EMA",
|
||||
NodeSchema {
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any }],
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "series".into() }],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
||||
},
|
||||
@@ -161,4 +161,9 @@ mod tests {
|
||||
assert_eq!(b.schema().params[0].name, "length");
|
||||
assert_eq!(b.schema().params[0].kind, ScalarKind::I64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slot_is_named_series() {
|
||||
assert_eq!(Ema::builder().schema().inputs[0].name, "series");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ impl Exposure {
|
||||
PrimitiveBuilder::new(
|
||||
"Exposure",
|
||||
NodeSchema {
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any }],
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "signal".into() }],
|
||||
output: vec![FieldSpec { name: "exposure", kind: ScalarKind::F64 }],
|
||||
params: vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }],
|
||||
},
|
||||
@@ -89,4 +89,9 @@ mod tests {
|
||||
let inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 1)];
|
||||
assert_eq!(e.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slot_is_named_signal() {
|
||||
assert_eq!(Exposure::builder().schema().inputs[0].name, "signal");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ impl LinComb {
|
||||
/// are injected, slot by slot, through `LinComb::new` (the single sizing gate).
|
||||
pub fn builder(arity: usize) -> PrimitiveBuilder {
|
||||
let inputs = (0..arity)
|
||||
.map(|_| PortSpec { kind: ScalarKind::F64, firing: Firing::Any })
|
||||
.map(|i| PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: format!("term[{i}]") })
|
||||
.collect();
|
||||
let params = (0..arity)
|
||||
.map(|i| ParamSpec { name: format!("weights[{i}]"), kind: ScalarKind::F64 })
|
||||
@@ -141,4 +141,11 @@ mod tests {
|
||||
fn lincomb_empty_weights_panics() {
|
||||
let _ = LinComb::new(vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slots_are_named_term_index() {
|
||||
let lc = LinComb::builder(3);
|
||||
let names: Vec<String> = lc.schema().inputs.iter().map(|p| p.name.clone()).collect();
|
||||
assert_eq!(names, ["term[0]", "term[1]", "term[2]"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,11 @@ impl Recorder {
|
||||
firing: Firing,
|
||||
tx: Sender<(Timestamp, Vec<Scalar>)>,
|
||||
) -> PrimitiveBuilder {
|
||||
let inputs = kinds.iter().map(|&kind| PortSpec { kind, firing }).collect();
|
||||
let inputs = kinds
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &kind)| PortSpec { kind, firing, name: format!("col[{i}]") })
|
||||
.collect();
|
||||
let build_kinds = kinds.clone();
|
||||
PrimitiveBuilder::new(
|
||||
"Recorder",
|
||||
@@ -137,4 +141,12 @@ mod tests {
|
||||
let rows: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
|
||||
assert_eq!(rows, vec![(Timestamp(2), vec![Scalar::F64(1.0), Scalar::F64(2.0)])]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slots_are_named_col_index() {
|
||||
let (tx, _rx) = std::sync::mpsc::channel();
|
||||
let r = Recorder::builder(vec![ScalarKind::F64, ScalarKind::F64], Firing::Any, tx);
|
||||
let names: Vec<String> = r.schema().inputs.iter().map(|p| p.name.clone()).collect();
|
||||
assert_eq!(names, ["col[0]", "col[1]"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ impl SimBroker {
|
||||
"SimBroker",
|
||||
NodeSchema {
|
||||
inputs: vec![
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any }, // 0 exposure
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any }, // 1 price
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "exposure".into() },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "price".into() },
|
||||
],
|
||||
output: vec![FieldSpec { name: "equity", kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
@@ -162,4 +162,11 @@ mod tests {
|
||||
let mut inputs = two_f64_inputs();
|
||||
assert_eq!(step(&mut b, &mut inputs, Some(1.0), 100.0), 0.0); // no prev price to mark
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slots_are_named_exposure_price() {
|
||||
let b = SimBroker::builder(1.0);
|
||||
let names: Vec<&str> = b.schema().inputs.iter().map(|p| p.name.as_str()).collect();
|
||||
assert_eq!(names, ["exposure", "price"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ impl Sma {
|
||||
PrimitiveBuilder::new(
|
||||
"SMA",
|
||||
NodeSchema {
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any }],
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "series".into() }],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
||||
},
|
||||
@@ -150,4 +150,9 @@ mod tests {
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slot_is_named_series() {
|
||||
assert_eq!(Sma::builder().schema().inputs[0].name, "series");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ impl Sub {
|
||||
"Sub",
|
||||
NodeSchema {
|
||||
inputs: vec![
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "lhs".into() },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "rhs".into() },
|
||||
],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
@@ -82,4 +82,11 @@ mod tests {
|
||||
inputs[1].push(Scalar::F64(4.0)).unwrap();
|
||||
assert_eq!(sub.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(6.0)].as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slots_are_named_lhs_rhs() {
|
||||
let s = Sub::builder();
|
||||
let names: Vec<&str> = s.schema().inputs.iter().map(|p| p.name.as_str()).collect();
|
||||
assert_eq!(names, ["lhs", "rhs"]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user