"Compilat" (German "Kompilat") was a coined noun for the product of the
bootstrap compilation — neither English nor a natural fit. The runtime
artifact already has a code identifier for exactly this thing: the
`FlatGraph` struct (harness.rs). Replace the coinage with that identifier:
- prose mentions -> "flat graph" (mirrors the type, reads plainly)
- definitional anchors -> `FlatGraph` (C11, C23, the running-graph line)
- `render_compilat` -> `render_flat_graph` (historical render symbol;
keeps the `render_blueprint` / `render_flat_graph`
source-vs-product pairing)
- `intra-compilat` -> `intra-graph`
- `from_compilat` (test) -> `from_flat`
"compilation" / "re-compilation" / "bootstrap-as-compilation" (the process,
ordinary English) are deliberately left untouched. Behaviour-preserving:
only comments, design ledger, specs/plans, one test-local variable and its
assert messages change. Full workspace test suite green; clippy clean.
13 KiB
Blueprint view as main graph + composite definitions — Design Spec
Date: 2026-06-07 Status: Draft — awaiting user spec review Authors: orchestrator + Claude
Goal
Redesign the aura graph blueprint view (render_blueprint in
crates/aura-cli/src/graph.rs) from the current "composites as ascii-dag subgraph
cluster boxes, interiors expanded inline" model to a program-with-subroutines
model: a small main graph that wires the harness with each composite shown as a
single opaque node [name], plus a definitions section that renders each
distinct composite type's interior exactly once.
This is the faithful representation of the blueprint as authored structure
(C9/C12: a composite is an authoring-level node), distinct from the compiled view
which is the inlined machine form (C23). It scales to real strategies, renders a
reused composite's body once, removes the current nested-composite unimplemented!,
and renders on ascii-dag's flat layout only — sidestepping the cluster-layout
bug at the root.
The compiled view (render_flat_graph, flat fully-inlined, C23) is unchanged,
byte-for-byte.
Architecture
Two ontological tiers map onto two render shapes:
| View | What it shows | Mechanism |
|---|---|---|
Blueprint (render_blueprint) |
authored source: harness wiring + composites as named subroutines, each body rendered once | main flat graph (composite = opaque node) + a where: block of per-type definitions |
Compiled (render_flat_graph) |
inlined machine form, boundaries dissolved (C23) | one flat graph (unchanged) |
The redesign is CLI-render-only. No change to the engine, the Node/Blueprint
data model, or render_flat_graph. The blueprint data model already carries
everything needed (Composite::name/nodes/edges/input_roles/output,
Blueprint::nodes/sources/edges).
Why the model (substance, not effort):
- Correctness under width. ascii-dag 0.9.1's subgraph level-centering rounds
sibling x-positions with an integer
/2; wide sibling labels inside a cluster overlap (e.g.[SimpleMovingAverage(length)[SimpleMovingAverage(length)]). The garble is width/parity-sensitive and has no config or padding dodge that survives unequal-width siblings (Positioningexposes onlyCompact;Barycentric/BrandesKopfare unimplemented stubs;render()takes no config). The flat layout reserveswidth + 3per node and is collision-free at all widths. The new model renders only flat graphs, so the bug cannot arise. - Scale. The blueprint is authored structure, not the flat graph. A composite with 50 interior nodes is one node in the main graph; blueprint size tracks top-level wiring, not inlined node count. Real strategies stay displayable.
- Render-once. A composite reused N times appears N times as an opaque node in the main graph, but its body is rendered exactly once in the definitions section (deduplicated by composite type).
These satisfy aura's feature-acceptance criterion: the intended audience (a human
or LLM inspecting an authored strategy) naturally reaches for a structural view
that does not explode with strategy size; it removes a redundancy (a reused body
drawn N times) and a latent failure (the cluster garble); and it reintroduces no
look-ahead / determinism / scalar-typing violation (it is a read-only structural
render, no eval).
Concrete code shapes
Worked example — what aura graph emits (the acceptance evidence)
For the built-in sample (build_sample() in crates/aura-cli/src/main.rs):
the sma_cross composite (two SMA leaves → Sub) wired into
Exposure → SimBroker → Recorder, with the price source feeding the composite and
the broker, and exposure also tapped to a second Recorder.
The new blueprint view (real ascii-dag flat renders; final byte-exact goldens captured at implementation — the assembly format below is the shape):
[source:F64]
┌───└─────┐
↓ │
[sma_cross] │
└┐ │
↓ │
[Exposure] │
┌───┘────────│
↓ ↓
[Recorder] [SimBroker]
┌─────┘
↓
[Recorder]
where:
sma_cross:
[in:0]
┌───└───┐
↓ ↓
[SMA] [SMA]
└───┌───┘
↓
[Sub]
│
↓
[out]
The main graph shows sma_cross as a single opaque node; its body appears
once under where:. A 50-node composite, or one reused three times, leaves the
main graph the same size. Both SMA leaves render identically as [SMA] (bare
type; values appear only in the compiled view, C22/#31).
Before → after: render_blueprint
The load-bearing change is that a composite maps to one display node, not a cluster of interior ids, and a second pass emits the definitions.
ItemDisplay — a composite is now one node, not a set of interior ids:
// before: a composite carried its interior display ids + boundary-resolution data
enum ItemDisplay {
Leaf(usize),
Composite { interior_ids: Vec<usize>, output_interior: usize, input_roles: Vec<Vec<Target>> },
}
// after: a composite is a single opaque main-graph node
enum ItemDisplay {
Leaf(usize),
Composite(usize), // the one opaque display id
}
Boundary resolution collapses — every edge touching a composite attaches to its single node, regardless of slot:
fn producer_id(d: &ItemDisplay) -> usize {
match d { ItemDisplay::Leaf(id) | ItemDisplay::Composite(id) => *id }
}
// a composite's input slots all land on the one opaque node
fn consumer_ids(d: &ItemDisplay, _slot: usize) -> Vec<usize> {
match d { ItemDisplay::Leaf(id) | ItemDisplay::Composite(id) => vec![*id] }
}
render_blueprint becomes "render main graph (flat, composites opaque) + render
each distinct composite definition once":
pub fn render_blueprint(bp: &Blueprint) -> String {
// pass 1: one display node per top-level item (composite label = its name),
// plus source nodes; collect distinct composites by name in first-seen
// order for the definitions section.
// pass 2: top-level + source edges, attaching to opaque composite nodes.
let main = render_flat(/* main-graph nodes + edges, NO subgraphs */);
// definitions: each distinct composite type once, recursively. A composite
// nested inside another's interior is an opaque node there and queues its own
// definition (dedup by name across the whole blueprint).
let mut defs = collect_distinct_composites(bp); // ordered, by name()
let body = defs.iter().map(render_definition).collect::<Vec<_>>().join("\n");
if defs.is_empty() { main } else { format!("{main}\nwhere:\n\n{body}") }
}
render_definition renders one composite's interior as a flat graph with port
markers — input roles as entry nodes [in:k], the output port as [out]:
fn render_definition(c: &Composite) -> String {
// interior leaves -> [type] nodes; nested composites -> opaque [name] nodes
// (queued for their own definition). interior edges as-is.
// role r -> add an [in:r] node with edges to each interior target in input_roles()[r].
// output -> add an [out] node with an edge from the output interior node.
// flat ascii-dag render, prefixed with "<name>:\n".
}
Components
render_blueprint(bp) -> String(rewritten). Producesmain+ optionalwhere:block. Uses only flat ascii-dag graphs.render_flat(labels, edges) -> String(new internal helper, or inline). A thin wrapper over the existing flat-graph build (Graph::with_mode(Vertical),add_node,add_edge,render) — exactly whatrender_flat_graphalready does, with noadd_subgraph/put_nodes.collect_distinct_composites(bp) -> Vec<&Composite>(new). Walks top-level items and, recursively, composite interiors; returns distinct composites in first-seen order, keyed byname(). Recursion is what removes theunimplemented!.render_definition(c) -> String(new). One composite interior → flat graph with[in:k]/[out]port markers, prefixed"<name>:\n".ItemDisplay,producer_id,consumer_ids(simplified as above).render_flat_graph— untouched.
Data flow
render_blueprint(bp):
- Main graph. For each
bp.nodes()item: a leaf → one node labelledfactory.label(); a composite → one node labelledc.name()(opaque). Eachbp.sources()→ onesource:{kind}node. Edges:bp.edges()and source targets, resolved throughproducer_id/consumer_ids(composites attach at their single node). Render flat →main. - Distinct composites. Traverse
bp.nodes()and every composite'sc.nodes()recursively; record each composite the first time itsname()is seen (later same-name occurrences are skipped — dedup). Order = first-seen. - Definitions. For each distinct composite,
render_definition: interior leaves →[type], interior nested composites → opaque[name](already queued in step 2), interior edges fromc.edges(); add[in:k]nodes wired toc.input_roles()[k]targets, an[out]node wired fromc.output().node. Render flat, prefix"<name>:\n". - Assemble.
main, then (if any composites)\nwhere:\n\n+ definitions joined by a blank line.
Error handling
- Same name, different structure. Dedup keys on
name(). The authoring convention is that a composite name is its type, so same name ⇒ same structure; the first-seen body is rendered and later same-name composites are treated as the same type. This is a stated assumption, consistent with the ledger's "name is a non-load-bearing render symbol" (C23). A structural-identity key is a possible future refinement, out of scope here. No error is raised — this is a best-effort debug view, not a validator. - No composites. A blueprint of only leaves emits just the main graph, no
where:block (thedefs.is_empty()branch). - Recursion. Nested composites terminate because the blueprint is a finite DAG of items; dedup-by-name additionally guarantees each type is rendered once even under repeated nesting. (A blueprint cannot contain itself; composites are values built bottom-up.)
- The render reads structure and
label()only — nevereval, no look-ahead, no determinism surface touched.
Testing strategy
Unit tests in crates/aura-cli/src/main.rs (replacing the two blueprint tests
that assert the old cluster-box form):
blueprint_view_main_graph_shows_composite_as_opaque_node— render the sample; assert the main graph contains[sma_cross](the opaque node) and the top-level leaves[Exposure]/[SimBroker]/[Recorder], and that awhere:section is present.blueprint_view_defines_each_composite_once— assert thesma_crossdefinition appears, containing[SMA],[Sub], and the port markers[in:0]/[out]; assertsma_cross:(the definition header) occurs exactly once.blueprint_view_golden(re-captured) — the exact bytes of the new render for the sample (ascii-dag's Sugiyama layout is deterministic; capture at implementation).nested_composite_renders_without_panic— construct a small blueprint with a composite whose interior contains another composite (one level of nesting); assert it renders (nounimplemented!panic), the outer definition shows the inner as an opaque[name]node, and the inner composite gets its own definition. This pins the removal of thegraph.rs:75unimplemented!.- Reuse-renders-once — a blueprint using the same composite type twice; assert the main graph shows two opaque nodes but the definition header appears once.
Unchanged and must stay green: compiled_view_dissolves_the_composite_boundary,
compiled_view_golden, swapped_param_vector_changes_the_compiled_render,
run_sample_is_deterministic_and_non_trivial.
Acceptance criteria
aura graph(no flag) prints the sample as a small main graph with[sma_cross]opaque, followed by awhere:block definingsma_crossonce, on a flat layout (no╔═╗subgraph boxes).aura graph --compiledoutput is byte-identical to before (render_flat_graphuntouched; its golden unchanged).- The
unimplemented!("nested-composite cluster rendering")atgraph.rs:75is gone; a one-level-nested composite renders. - A composite reused N times renders its body once.
cargo test --workspacegreen;cargo clippy --workspace --all-targets -D warningsclean.- Closes #38. (#37 — interactive enter/focus navigation — is the playground counterpart and explicitly out of scope.)