"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.
14 KiB
Composite Signature Render — Design Spec
Date: 2026-06-08 Status: Draft — awaiting user spec review Authors: orchestrator + Claude
Goal
Render a composite's blueprint definition as a typed signature line plus a
de-cluttered body, replacing the [param:<name>] marker nodes #41 introduced
(which bloat the ASCII graph). The interface moves into the title; the params and
ordered inputs fold into the node labels that already exist.
This is render-only — no engine logic changes. The #41 ParamAlias data and
param_space() are untouched.
For the MACD composite, the definition title becomes:
macd(fast:i64, slow:i64, signal:i64) -> (macd, signal, histogram)
and the three [param:*] marker nodes vanish. Output names only (not kinds):
a value-empty LeafFactory exposes no output schema pre-build (the mc_4/#42
finding), so the typed output arrow (-> (macd:f64, …)) is deferred to #43.
This cycle references #41 (the surface it refines) and #43 (the deferred typing).
Architecture
The blueprint-definition view (render_definition in crates/aura-cli/src/graph.rs)
is the only renderer that changes. Five edits, all in render_definition except
one doc-comment fix:
- Title = signature, not bare name.
c.name()→name(params) -> (outputs). - Leaf labels fold their aliased param names.
[EMA]→[EMA(fast)]. - Unnamed interior input slots render as positional stubs.
[Sub]→[Sub(#A,#B)], derived from the interior wiring. - Output markers drop the
out:prefix.[out:macd]→[macd]. - The
[param:<name>]marker nodes are removed.
Plus a stale-prose fix in crates/aura-core/src/node.rs.
Why this is renderable now (the ascii-dag history). LeafFactory::label
historically stayed the bare type (SMA, not SMA(length)) because ascii-dag
0.9.1 garbles wide sibling labels inside a cluster subgraph. Cycle 0017
removed clusters — both aura graph views build flat graphs
(crates/aura-cli/src/graph.rs:13-14: "the subgraph layout mis-centres wide
sibling labels, the flat layout does not"). Wide labels are now safe, which is
what makes [EMA(fast)] / [Sub(#A,#B)] renderable. The doc-comment at
node.rs:93-101 still cites the cluster constraint as the reason for bare-type
labels — stale prose this cycle corrects.
Invariant-neutrality
- Render-only. No engine logic change;
ParamAlias,param_space(), the compile path, and the run are untouched. Files:graph.rs(render_definition),main.rs(render tests/goldens),node.rs(doc comment only). - C23.
compiled_view_goldenis byte-identical — only the blueprint-definition render changes; the compiled/flat view and the flat graph are unaffected. Boundary names still never reach aBox<dyn Node>. - C8/C7/C4 untouched — authoring-surface legibility only.
aura run --macdis deterministic and unchanged (render-only).
Concrete code shapes
Worked example — aura graph --macd, before → after
The MACD definition block today (real output — note the three [param:*] markers
and the loose [in:price] cluttering the top, and the bare [EMA]/[Sub]):
macd:
[param:fast] [in:price] [param:slow] [param:signal]
│ └┐───────┌────┘ ┌─────────────┘
└──────────────┐ │ │
↓ ↓ │
[EMA] [EMA] │
└───┌───┘ │
↓ ┌───┘
[Sub] │
┌───┘──────│────┐
↓ └────↓
[out:macd] │ [EMA]
┌───────────┌────┘
↓───────────↓
[Sub] [out:signal]
└───────┐
↓
[out:histogram]
After this cycle — title carries the signature, [param:*] markers gone, params
folded into the EMAs, ordered Sub inputs shown as stubs, outputs de-prefixed
(exact box-drawing layout is whatever the flat renderer produces; the load-bearing
changes are the labels):
macd(fast:i64, slow:i64, signal:i64) -> (macd, signal, histogram):
[in:price]
└┐──────┌┘
↓ ↓
[EMA(fast)] [EMA(slow)]
└──┌────┘
↓
[Sub(#A,#B)]
┌────┘────┐
↓ ↓
[macd] [EMA(signal)]
┌──────┘
↓
[Sub(#A,#B)]
┌────┘────┐
↓ ↓
[signal] [histogram]
The sma_cross sample (named role, no aliases) confirms the empty-param case
renders cleanly — no params in the signature, leaves stay bare:
sma_cross() -> (cross):
[in:price]
└┐──────┌┘
↓ ↓
[SMA] [SMA]
└──┌───┘
↓
[Sub(#A,#B)]
│
↓
[cross]
The label-annotation rule (pinned)
A leaf item's label in render_definition is factory.label() plus an optional
parenthesised annotation built in this order:
- Aliased param names for this leaf — every
a.nameinc.params()witha.node == this_index, inc.params()order. - Input-slot stubs — only if the leaf has >1 distinct wired input slot:
one positional stub per slot in slot order (
#A,#B,#C, …). A single-input leaf gets no stub (no ordering ambiguity).
If both are present, the annotation is factory.label() + ( + params + ; +
stubs + ) (params and stubs separated by ; ). If only one is present, no
separator. If neither, the bare label (today's behaviour). In the worked MACD/SMA
graphs every leaf hits exactly one branch — EMAs have one aliased param and one
input (→ [EMA(fast)]); Subs have no params and two inputs (→ [Sub(#A,#B)]) —
the combined ; form is defined for completeness, not exercised here.
A node's wired input slots are the distinct .slot values targeting it, collected
from both c.edges() (entries with to == index) and c.input_roles()
(each role's targets with node == index). Nested-composite interior items keep
rendering as their opaque name() (unchanged; their own definition renders
separately).
Before → after — render_definition (crates/aura-cli/src/graph.rs)
Today (the relevant parts): leaves labelled bare; an [in:k], [param:name], and
[out:name] marker per role/alias/output; title is c.name().
fn render_definition(c: &Composite) -> String {
let mut labels: Vec<String> = Vec::with_capacity(c.nodes().len());
for inner in c.nodes() {
labels.push(match inner {
BlueprintNode::Leaf(factory) => factory.label(),
BlueprintNode::Composite(inner_c) => inner_c.name().to_string(),
});
}
let mut edges: Vec<(usize, usize)> = Vec::new();
for e in c.edges() {
edges.push((e.from, e.to));
}
for role in c.input_roles() {
let in_id = labels.len();
labels.push(format!("in:{}", role.name));
for t in &role.targets {
edges.push((in_id, t.node));
}
}
for a in c.params() {
let p_id = labels.len();
labels.push(format!("param:{}", a.name));
edges.push((p_id, a.node));
}
for of in c.output() {
let out_id = labels.len();
labels.push(format!("out:{}", of.name));
edges.push((of.node, out_id));
}
format!("{}:\n{}", c.name(), render_flat(&labels, &edges))
}
After: leaf labels annotated (params folded + input stubs), the [param:*] loop
removed, outputs de-prefixed, the title built from a new signature(c) helper.
fn render_definition(c: &Composite) -> String {
let mut labels: Vec<String> = Vec::with_capacity(c.nodes().len());
for (i, inner) in c.nodes().iter().enumerate() {
labels.push(match inner {
BlueprintNode::Leaf(factory) => leaf_label(c, i, factory),
BlueprintNode::Composite(inner_c) => inner_c.name().to_string(),
});
}
let mut edges: Vec<(usize, usize)> = Vec::new();
for e in c.edges() {
edges.push((e.from, e.to));
}
for role in c.input_roles() {
let in_id = labels.len();
labels.push(format!("in:{}", role.name));
for t in &role.targets {
edges.push((in_id, t.node));
}
}
// params are no longer marker nodes — they fold into leaf labels (leaf_label)
// and the signature title (signature). The [param:*] loop is removed.
for of in c.output() {
let out_id = labels.len();
labels.push(of.name.clone()); // [out:macd] -> [macd]
edges.push((of.node, out_id));
}
format!("{}:\n{}", signature(c), render_flat(&labels, &edges))
}
with two new render-side helpers (shapes; exact bodies are the planner's):
// "macd(fast:i64, slow:i64, signal:i64) -> (macd, signal, histogram)"
// param kinds from the interior leaf's factory().params()[slot].kind;
// output names only (kinds deferred to #43).
fn signature(c: &Composite) -> String { /* ... */ }
// "EMA(fast)" / "Sub(#A,#B)" / bare "SMA" per the label-annotation rule above.
fn leaf_label(c: &Composite, index: usize, factory: &LeafFactory) -> String { /* ... */ }
node.rs doc-comment fix (crates/aura-core/src/node.rs:93-101)
The LeafFactory::label doc currently reads (paraphrased) "the label stays the bare
type because ascii-dag overlaps two wide sibling boxes inside a cluster subgraph".
Replace that rationale: the renderer is flat (no clusters) since cycle 0017, so wide
labels are safe; the factory label stays the bare param-generic type because
alias / handle names are composite-level concepts resolved at render
(render_definition folds them via c.params()), not because of any ascii-dag
constraint. Behaviour-neutral (a comment).
Components
| Component | File | Change |
|---|---|---|
render_definition |
crates/aura-cli/src/graph.rs |
title = signature(c); leaf labels via leaf_label; drop [param:*] loop; outputs de-prefixed |
signature (new) |
crates/aura-cli/src/graph.rs |
builds the name(params) -> (outputs) line; param kinds from factory().params()[slot].kind; output names only |
leaf_label (new) |
crates/aura-cli/src/graph.rs |
folds aliased param names + input-slot stubs per the pinned rule |
LeafFactory::label doc |
crates/aura-core/src/node.rs:93-101 |
replace stale cluster rationale (comment only) |
| render tests / goldens | crates/aura-cli/src/main.rs |
blueprint_view_golden re-capture; new assertions; compiled_view_golden untouched |
Data flow
Render is a pure read over the blueprint (C9 graph-as-data): render_definition
reads c.nodes(), c.edges(), c.input_roles(), c.params(), c.output() and
the interior leaves' factory().label() / factory().params(). No build, no
param vector, no engine call. signature resolves param kinds by indexing the
aliased leaf's factory().params(); output kinds are intentionally not resolved
(deferred to #43).
Error handling
Render is total over a well-formed blueprint (the same blueprints the existing
render_definition already renders). An alias is validated at compile
(BadInteriorIndex, #41); the renderer is reached only for renderable blueprints,
so signature/leaf_label index c.params()/factory().params() within the
bounds an already-constructed composite guarantees. No new error paths.
Testing strategy
blueprint_view_golden(crates/aura-cli/src/main.rs) re-captured — the definition render changed. Run the test, paste the actual rendered output wholesale into the golden literal.- New render assertions on the MACD definition: contains the signature
substring
macd(fast:i64, slow:i64, signal:i64) -> (macd, signal, histogram), contains[EMA(fast)],[Sub(#A,#B)],[macd],[in:price]; and asserts[param:is absent (markers removed) and[out:is absent (prefix dropped). compiled_view_goldenbyte-identical (C23 guard — must stay green, untouched; a drift here is a bug, not a re-capture).run_macd…deterministicgreen, metrics unchanged (render-only).- No-alias case: a
sma_crossrender assertion confirmssma_cross() -> (cross)(empty param list) and bare[SMA]leaves render cleanly.
Acceptance criteria
- A trader reading
aura graph --macdsees the composite's interface as a typed signature line (macd(fast:i64, slow:i64, signal:i64) -> (macd, signal, histogram)) and an uncluttered body — the[param:*]marker bloat is gone, params read on the EMAs ([EMA(fast)]), the ordered Sub inputs are visible ([Sub(#A,#B)]). The worked before→after is the evidence. - Render-only:
compiled_view_goldenbyte-identical;aura run --macddeterministic and unchanged; no engine/ParamAlias/param_space()change. - Output kinds are honestly omitted (names only), with the typed arrow deferred to
#43; the spec and commit reference #41 (surface) and #43 (deferred typing), and
this cycle
closesno issue. cargo build/test/clippy --workspace(-D warnings) green.