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:
@@ -76,6 +76,7 @@ pub struct ParamSpec {
|
||||
/// pre-build (#43).
|
||||
pub struct PrimitiveBuilder {
|
||||
name: &'static str,
|
||||
instance_name: Option<String>,
|
||||
schema: NodeSchema,
|
||||
// The build closure's type is exactly the recipe contract (a param slice in, a
|
||||
// boxed node out); a type alias would not clarify it.
|
||||
@@ -92,7 +93,20 @@ impl PrimitiveBuilder {
|
||||
schema: NodeSchema,
|
||||
build: impl Fn(&[Scalar]) -> Box<dyn Node> + 'static,
|
||||
) -> Self {
|
||||
Self { name, schema, build: Box::new(build) }
|
||||
Self { name, instance_name: None, schema, build: Box::new(build) }
|
||||
}
|
||||
/// Set this node instance's name. Must be non-empty.
|
||||
pub fn named(mut self, name: &str) -> Self {
|
||||
debug_assert!(!name.is_empty(), "node name must be non-empty");
|
||||
self.instance_name = Some(name.to_string());
|
||||
self
|
||||
}
|
||||
/// The resolved node name: the explicit instance name if set, else the
|
||||
/// type label ASCII-lowercased verbatim (e.g. "SimBroker" -> "simbroker").
|
||||
pub fn node_name(&self) -> String {
|
||||
self.instance_name
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.name.to_ascii_lowercase())
|
||||
}
|
||||
/// The full declared signature (read pre-build by `Composite::param_space`,
|
||||
/// `BlueprintNode::signature`, and the renderer).
|
||||
@@ -120,7 +134,7 @@ impl PrimitiveBuilder {
|
||||
/// case), and an **empty** `output` (`vec![]`) declares a **pure consumer**
|
||||
/// (sink role, C8) — a node with no output port. Built once at wiring, never on
|
||||
/// the hot path — the `Vec`s are fine here.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct NodeSchema {
|
||||
pub inputs: Vec<PortSpec>,
|
||||
pub output: Vec<FieldSpec>,
|
||||
@@ -215,6 +229,26 @@ mod tests {
|
||||
assert_eq!(none.label(), "Sub");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_name_defaults_to_lowercased_type_label() {
|
||||
// unnamed → the type label, ASCII-lowercased verbatim (no snake_case)
|
||||
let unnamed = PrimitiveBuilder::new(
|
||||
"SimBroker",
|
||||
NodeSchema::default(),
|
||||
|_| panic!("not built in this test"),
|
||||
);
|
||||
assert_eq!(unnamed.node_name(), "simbroker");
|
||||
|
||||
// explicit name overrides
|
||||
let named = PrimitiveBuilder::new(
|
||||
"SMA",
|
||||
NodeSchema::default(),
|
||||
|_| panic!("not built in this test"),
|
||||
)
|
||||
.named("fast");
|
||||
assert_eq!(named.node_name(), "fast");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primitive_builder_build_runs_the_closure() {
|
||||
let f = PrimitiveBuilder::new(
|
||||
|
||||
Reference in New Issue
Block a user