From 241bb626e63309d3b1b013e56f19e75816b539c4 Mon Sep 17 00:00:00 2001 From: Brummel Date: Mon, 8 Jun 2026 01:23:47 +0200 Subject: [PATCH] fix(fieldtests): port construction-layer fixtures to current authoring API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The four standalone construction-layer fieldtest bins (mc_1..mc_4) no longer compiled: they are their own workspace root, so the engine's `cargo build --workspace` never touched them and two waves of API drift went latent. Two breakage classes, both confined to the fixture files (no engine change): - Stale cycle-0016 authoring. `BlueprintNode::from(::new(..))` relied on the `From` impls retired in the value-empty migration; only `From` survives. Ported to `::factory().into()` — nodes are value-empty blueprint items, params bound at compile. Knock-on in mc_4 `describe_node`: a blueprint leaf is now a `&LeafFactory` with no pre-build schema, so leaf introspection reports the render label + declared param count (inputs / outputs exist only on the compiled flat node, post-build). - Cycle-0018 (#40) OutPort -> OutField slice drift. `Composite::output()` now returns `&[OutField]`; the mc_4 read sites were still treating it as a single port. Ported to index the record; the walk assertion now also pins the arity (one re-exported field), which is the #40 shape. Faithful ports, not assertion-gutting: each fixture still exercises its axis (composite build+run / miswire render / nested composite / graph introspection), verified by running all four bins to their `OK:` line. One genuine semantic drift flagged in mc_2: the blueprint-view label is now the bare value-empty type ("SMA"), so two same-type leaves share a label and are disambiguated by the slot/edge table; the valued SMA(2)/SMA(4) label lives on the compiled view. The fixture asserts what the blueprint view actually exposes and keeps the load-bearing edge-table observability check untouched. closes #42 --- .../mc_1_composite_build_run.rs | 18 ++++++---- .../mc_2_miswire_render.rs | 22 +++++++----- .../mc_3_nested_composite.rs | 16 +++++---- .../mc_4_introspect_graph.rs | 34 +++++++++++-------- 4 files changed, 53 insertions(+), 37 deletions(-) diff --git a/fieldtests/milestone-construction-layer/mc_1_composite_build_run.rs b/fieldtests/milestone-construction-layer/mc_1_composite_build_run.rs index c2a8478..9bb1036 100644 --- a/fieldtests/milestone-construction-layer/mc_1_composite_build_run.rs +++ b/fieldtests/milestone-construction-layer/mc_1_composite_build_run.rs @@ -26,12 +26,14 @@ fn main() { // 0: Sma(2) (fast) 1: Sma(4) (slow) 2: Sub (fast - slow) // One input role (the price) fans into BOTH SMAs' slot 0. // The exposed output port is Sub's single output field. + // Value-empty recipes (factories); the two SMA lengths are injected at + // bootstrap (param vector below), not baked here. let sma_cross = Composite::new( "sma_cross", vec![ - BlueprintNode::from(Sma::new(2)), - BlueprintNode::from(Sma::new(4)), - BlueprintNode::from(Sub::new()), + Sma::factory().into(), + Sma::factory().into(), + Sub::factory().into(), ], // interior edges (local indices): fast -> Sub.slot0, slow -> Sub.slot1 vec![ @@ -55,9 +57,9 @@ fn main() { let blueprint = Blueprint::new( vec![ BlueprintNode::Composite(sma_cross), - BlueprintNode::from(Exposure::new(0.5)), - BlueprintNode::from(SimBroker::new(1e-4)), - BlueprintNode::from(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)), + Exposure::factory().into(), + SimBroker::factory(1e-4).into(), + Recorder::factory(vec![ScalarKind::F64], Firing::Any, tx).into(), ], // one f64 price source: feeds the composite (item 0, role 0) AND the // broker's price leg (item 2, slot 1). @@ -78,8 +80,10 @@ fn main() { ); // --- compile() + bootstrap() + run() ------------------------------------- + // Param vector (depth-first item order, C12): sma_cross's two SMA lengths + // (I64), then Exposure's scale (F64); Sub/SimBroker/Recorder declare none. let mut harness = blueprint - .bootstrap() + .bootstrap_with_params(vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)]) .expect("composite-authored blueprint should bootstrap"); // a synthetic upward price ramp then a dip, enough to warm SMA(4) and flip diff --git a/fieldtests/milestone-construction-layer/mc_2_miswire_render.rs b/fieldtests/milestone-construction-layer/mc_2_miswire_render.rs index 99bf474..8a0a938 100644 --- a/fieldtests/milestone-construction-layer/mc_2_miswire_render.rs +++ b/fieldtests/milestone-construction-layer/mc_2_miswire_render.rs @@ -38,9 +38,9 @@ fn cross(swap: bool) -> Composite { Composite::new( "sma_cross", vec![ - BlueprintNode::from(Sma::new(2)), - BlueprintNode::from(Sma::new(4)), - BlueprintNode::from(Sub::new()), + Sma::factory().into(), + Sma::factory().into(), + Sub::factory().into(), ], vec![e_fast, e_slow], vec![vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }]], @@ -62,8 +62,10 @@ fn main() { let correct = cross(false); let swapped = cross(true); - // The per-node labels carry identifying params (SMA(2) vs SMA(4)) so two - // SMAs are distinguishable in a render. + // The blueprint-view label is the bare value-empty type (SMA), not SMA(2)/ + // SMA(4): post cycle-0016 the lengths are injected params, so two same-type + // leaves share a blueprint label and are disambiguated by slot/edge-table + // (the compiled view labels them valued via Node::label, separately). println!("correct labels: {:?}", node_labels(&correct)); println!("swapped labels: {:?}", node_labels(&swapped)); @@ -80,11 +82,13 @@ fn main() { "a fast/slow swap MUST change the graph-as-data, else no render could surface it" ); - // And the disambiguating labels are present (SMA(2)/SMA(4) distinguishable). + // The blueprint-view labels carry the value-empty node type (both SMAs read + // as "SMA"); the swap's observability rests on the edge table above, not on a + // per-leg label. The labels still type-distinguish SMA from Sub. let labels = node_labels(&correct); assert!( - labels.iter().any(|l| l.contains("SMA(2)")) && labels.iter().any(|l| l.contains("SMA(4)")), - "labels must carry SMA params so a swapped leg reads differently: {labels:?}" + labels.iter().filter(|l| l.contains("SMA")).count() == 2 && labels.iter().any(|l| l.contains("Sub")), + "labels must type-distinguish the two SMAs from the Sub: {labels:?}" ); - println!("OK: a fast/slow swap is observable in graph-as-data + labels."); + println!("OK: a fast/slow swap is observable in graph-as-data; labels are value-empty types."); } diff --git a/fieldtests/milestone-construction-layer/mc_3_nested_composite.rs b/fieldtests/milestone-construction-layer/mc_3_nested_composite.rs index 366ab81..4894c6d 100644 --- a/fieldtests/milestone-construction-layer/mc_3_nested_composite.rs +++ b/fieldtests/milestone-construction-layer/mc_3_nested_composite.rs @@ -23,9 +23,9 @@ fn inner_cross() -> Composite { Composite::new( "sma_cross", vec![ - BlueprintNode::from(Sma::new(2)), - BlueprintNode::from(Sma::new(4)), - BlueprintNode::from(Sub::new()), + Sma::factory().into(), + Sma::factory().into(), + Sub::factory().into(), ], vec![ Edge { from: 0, to: 2, slot: 0, from_field: 0 }, @@ -45,7 +45,7 @@ fn strategy() -> Composite { "strategy", vec![ BlueprintNode::Composite(inner_cross()), - BlueprintNode::from(Exposure::new(0.5)), + 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 @@ -59,8 +59,8 @@ fn main() { let blueprint = Blueprint::new( vec![ BlueprintNode::Composite(strategy()), - BlueprintNode::from(SimBroker::new(1e-4)), - BlueprintNode::from(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)), + SimBroker::factory(1e-4).into(), + Recorder::factory(vec![ScalarKind::F64], Firing::Any, tx).into(), ], vec![SourceSpec { kind: ScalarKind::F64, @@ -75,8 +75,10 @@ fn main() { ], ); + // Param vector (depth-first, C12): strategy { inner_cross's two SMA lengths + // (I64), Exposure scale (F64) }; Sub/SimBroker/Recorder declare none. let mut harness = blueprint - .bootstrap() + .bootstrap_with_params(vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)]) .expect("nested-composite blueprint should bootstrap"); let prices: Vec = vec![ diff --git a/fieldtests/milestone-construction-layer/mc_4_introspect_graph.rs b/fieldtests/milestone-construction-layer/mc_4_introspect_graph.rs index a454d72..4840e52 100644 --- a/fieldtests/milestone-construction-layer/mc_4_introspect_graph.rs +++ b/fieldtests/milestone-construction-layer/mc_4_introspect_graph.rs @@ -22,9 +22,9 @@ fn sma_cross() -> Composite { Composite::new( "sma_cross", vec![ - BlueprintNode::from(aura_std::Sma::new(2)), - BlueprintNode::from(aura_std::Sma::new(4)), - BlueprintNode::from(aura_std::Sub::new()), + aura_std::Sma::factory().into(), + aura_std::Sma::factory().into(), + aura_std::Sub::factory().into(), ], vec![ Edge { from: 0, to: 2, slot: 0, from_field: 0 }, @@ -37,23 +37,28 @@ fn sma_cross() -> Composite { fn describe_node(prefix: &str, n: &BlueprintNode) { match n { - BlueprintNode::Leaf(node) => { - let sch = node.schema(); + BlueprintNode::Leaf(factory) => { + // A blueprint leaf is a value-empty LeafFactory (post cycle-0016): no + // built node, so no input/output schema yet — introspect its declared + // render label + tunable param count (inputs/outputs exist only after + // build, on the compiled flat node). println!( - "{prefix}LEAF {:<14} inputs={} output_fields={}", - node.label(), - sch.inputs.len(), - sch.output.len() + "{prefix}LEAF {:<14} params={}", + factory.label(), + factory.params().len() ); } BlueprintNode::Composite(c) => { + // output() is now a record (&[OutField]); render the whole field list + // (one entry today, but the type is the multi-field shape of #40). + let out_fields: Vec<(usize, usize)> = + c.output().iter().map(|f| (f.node, f.field)).collect(); println!( - "{prefix}COMPOSITE name={:?} interior_items={} roles={} out=({},{})", + "{prefix}COMPOSITE name={:?} interior_items={} roles={} out={:?}", c.name(), c.nodes().len(), c.input_roles().len(), - c.output().node, - c.output().field, + out_fields, ); for inner in c.nodes() { describe_node(&format!("{prefix} "), inner); @@ -72,7 +77,7 @@ fn main() { let bp = Blueprint::new( vec![ BlueprintNode::Composite(sma_cross()), - BlueprintNode::from(aura_std::Exposure::new(0.5)), + aura_std::Exposure::factory().into(), ], vec![SourceSpec { kind: ScalarKind::F64, @@ -104,7 +109,8 @@ fn main() { assert_eq!(c.input_roles().len(), 1, "one price role"); assert_eq!(c.input_roles()[0].len(), 2, "price fans into both SMAs"); let out = c.output(); - assert_eq!((out.node, out.field), (2, 0), "Sub output is the port"); + 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"); println!("OK: walked the composite interior via public accessors only."); } else { panic!("expected composite as first item");