7e195f66b7
End-to-end milestone fieldtest (closing gate) for the "Construction layer" milestone (#12 composites + #13 aura graph render). Four real downstream tasks authored against the PUBLIC interface only (ledger + doc-comments + re-exports + the aura CLI), under fieldtests/milestone-construction-layer/: mc_1 — author a named sma_cross composite, build a Blueprint, compile + bootstrap + run (12 populated equity rows); mc_2 — correct vs fast/slow-swapped cross: labels + graph-as-data differ observably (the headline mis-wire-visible property); mc_3 — composite-nested-in-composite inlines and runs; mc_4 — walk the built graph via the read-only accessors, no engine internals. Roll-up: friction_found, NO bugs. The milestone delivers its core promise — fractal authoring -> compile -> bootstrap -> run, introspection as graph-as-data, and param-carrying labels that make a mis-wire readable. None of the findings block the gate. Findings filed to the forward queue (not fixed here): - #28 (feature) — the headline friction: `aura graph` renders only the built-in sample and the render adapter is CLI-private, so a consumer can't render their OWN graph. Subsumes the reachability of #26 (the nested-render panic is structurally unreachable until the render is parameterizable). Likely next (consumer-project / World) milestone. - #29 (idea) — aura-engine doesn't re-export the scalar vocabulary (ScalarKind etc.) a graph-builder needs; one-crate ergonomics tidy. - #16 (comment) — `aura graph` arg policy (help / unknown-arg) should unify with the still-open `aura run` strictness question. The fieldtester read no implementation source; all artefacts are the consumer crate + the two live render captures (render_clustered.txt / render_compiled.txt, byte-identical across runs).
91 lines
3.7 KiB
Rust
91 lines
3.7 KiB
Rust
// 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, OutPort, 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
|
|
// 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![
|
|
BlueprintNode::from(Sma::new(2)),
|
|
BlueprintNode::from(Sma::new(4)),
|
|
BlueprintNode::from(Sub::new()),
|
|
],
|
|
vec![e_fast, e_slow],
|
|
vec![vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }]],
|
|
OutPort { node: 2, field: 0 },
|
|
)
|
|
}
|
|
|
|
fn node_labels(c: &Composite) -> Vec<String> {
|
|
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 per-node labels carry identifying params (SMA(2) vs SMA(4)) so two
|
|
// SMAs are distinguishable in a render.
|
|
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<Edge> = correct.edges().to_vec();
|
|
let se: Vec<Edge> = 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"
|
|
);
|
|
|
|
// And the disambiguating labels are present (SMA(2)/SMA(4) distinguishable).
|
|
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:?}"
|
|
);
|
|
println!("OK: a fast/slow swap is observable in graph-as-data + labels.");
|
|
}
|