// Milestone fieldtest — axis (b): deliberately mis-wire a graph (swap the // fast/slow SMA legs of the cross) and confirm the wiring reads back // differently — the milestone's headline promise that a render visibly // surfaces a mis-wiring. // // The headline render is the `aura graph` CLI (exercised separately, in the // accompanying .sh-free flow: see the spec). HERE we test the property the // render RELIES ON from the consumer side: that the labels + graph-as-data of a // correctly-wired composite differ observably from a swapped one. If a swap // produced an identical graph-as-data, no renderer could ever surface it. // // 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, Role, Target}; // NB: `node.label()` on a `&Box` 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 // labels off a built graph. use aura_std::{Sma, Sub}; /// Build an SMA-cross composite. `swap` flips which SMA feeds Sub's slot 0. /// Correct: fast(SMA2) -> minuend(slot0), slow(SMA4) -> subtrahend(slot1). /// Swapped: slow -> slot0, fast -> slot1 (the classic fast/slow mis-wire). fn cross(swap: bool) -> Composite { let (e_fast, e_slow) = if swap { // slow(node 1) into slot0, fast(node 0) into slot1 ( Edge { from: 1, to: 2, slot: 0, from_field: 0 }, Edge { from: 0, to: 2, slot: 1, from_field: 0 }, ) } else { ( Edge { from: 0, to: 2, slot: 0, from_field: 0 }, Edge { from: 1, to: 2, slot: 1, from_field: 0 }, ) }; Composite::new( "sma_cross", vec![ Sma::factory().into(), Sma::factory().into(), Sub::factory().into(), ], vec![e_fast, e_slow], 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() }], ) } fn node_labels(c: &Composite) -> Vec { c.nodes() .iter() .map(|n| match n { BlueprintNode::Leaf(node) => node.label(), BlueprintNode::Composite(inner) => format!("composite:{}", inner.name()), }) .collect() } fn main() { let correct = cross(false); let swapped = cross(true); // 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)); // The mis-wire lives in the EDGE table; the graph-as-data must reflect it. let ce: Vec = correct.edges().to_vec(); let se: Vec = swapped.edges().to_vec(); println!("correct interior edges: {ce:?}"); println!("swapped interior edges: {se:?}"); // Headline property: the two graphs-as-data differ. A renderer reading the // edge table + labels can therefore make the swap visible. assert_ne!( ce, se, "a fast/slow swap MUST change the graph-as-data, else no render could surface it" ); // 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().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 are value-empty types."); }