Files
Aura/fieldtests/milestone-construction-layer/mc_4_introspect_graph.rs
T
Brummel 7e195f66b7 fieldtest: milestone construction-layer — 4 examples, 7 findings
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).
2026-06-05 22:38:39 +02:00

113 lines
4.1 KiB
Rust

// Milestone fieldtest — axis (d): introspect a built Blueprint via the
// read-only graph-as-data accessors — can a consumer walk the graph as data
// WITHOUT reading engine internals?
//
// Accessors under test (all public, from rustdoc):
// Blueprint::nodes() / sources() / edges()
// Composite::name() / nodes() / edges() / input_roles() / output()
// BlueprintNode::{Leaf, Composite}; Node::label() on a boxed leaf
//
// Public interface only.
use aura_core::ScalarKind;
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutPort, SourceSpec, Target};
// NB (fieldtest finding): `aura_engine::ScalarKind` does NOT resolve — the
// construction surface (aura-engine) does not re-export the core scalar
// vocabulary (ScalarKind / Scalar / Firing) that a graph-builder needs. A
// downstream author building a Blueprint must reach into aura_core directly.
// Recorded as friction in the spec. `node.label()` below dispatches on the
// boxed leaf via the Node trait object without an explicit `use` of Node.
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()),
],
vec![
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
],
vec![vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }]],
OutPort { node: 2, field: 0 },
)
}
fn describe_node(prefix: &str, n: &BlueprintNode) {
match n {
BlueprintNode::Leaf(node) => {
let sch = node.schema();
println!(
"{prefix}LEAF {:<14} inputs={} output_fields={}",
node.label(),
sch.inputs.len(),
sch.output.len()
);
}
BlueprintNode::Composite(c) => {
println!(
"{prefix}COMPOSITE name={:?} interior_items={} roles={} out=({},{})",
c.name(),
c.nodes().len(),
c.input_roles().len(),
c.output().node,
c.output().field,
);
for inner in c.nodes() {
describe_node(&format!("{prefix} "), inner);
}
for (r, targets) in c.input_roles().iter().enumerate() {
println!("{prefix} role[{r}] fans into {targets:?}");
}
for e in c.edges() {
println!("{prefix} interior edge {e:?}");
}
}
}
}
fn main() {
let bp = Blueprint::new(
vec![
BlueprintNode::Composite(sma_cross()),
BlueprintNode::from(aura_std::Exposure::new(0.5)),
],
vec![SourceSpec {
kind: ScalarKind::F64,
targets: vec![Target { node: 0, slot: 0 }],
}],
vec![Edge { from: 0, to: 1, slot: 0, from_field: 0 }],
);
println!("== Blueprint graph-as-data walk ==");
println!("sources: {}", bp.sources().len());
for (i, s) in bp.sources().iter().enumerate() {
println!(" source[{i}] kind={:?} -> {:?}", s.kind, s.targets);
}
println!("top-level items: {}", bp.nodes().len());
for n in bp.nodes() {
describe_node(" ", n);
}
println!("top-level edges: {}", bp.edges().len());
for e in bp.edges() {
println!(" edge {e:?}");
}
// The walk must reach the interior of the composite purely through the
// public accessors — no engine internals.
let first = &bp.nodes()[0];
if let BlueprintNode::Composite(c) = first {
assert_eq!(c.name(), "sma_cross");
assert_eq!(c.nodes().len(), 3, "composite interior should have 3 items");
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");
println!("OK: walked the composite interior via public accessors only.");
} else {
panic!("expected composite as first item");
}
}