"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.
12 KiB
Render the root composite like any other composite (slot stubs + role-name entries) — Design Spec
Date: 2026-06-09 Status: Draft — awaiting user spec review Authors: orchestrator + Claude
Goal
Close issue #49, and in doing so remove the last render special-case the root
still carries. After the render-path unification (#48) the blueprint main graph
and a composite's where:-interior share one core (render_graph,
crates/aura-cli/src/graph.rs), but the root is still rendered specially in two
places, both vestiges of the pre-0024 struct Blueprint:
- A composite-interior multi-input leaf renders its wired slots as
#…source-derived stubs ([cross := Sub(#Sf,#Ss)]); a top-level leaf does not —SimBrokerrenders as bare[SimBroker]— becauserender_blueprintthreadsstub_ctx: None. - An interior role renders as a
[price]entry marker (the role name); a root role renders as[source:F64](the ingestion kind), becauserender_blueprintbuilds entries fromformat!("source:{kind}").
Cycle 0024 (1b39093) deleted struct Blueprint: the root is now an ordinary
Composite with input_roles: Vec<Role>. So both special-cases are obsolete —
the root has roles, and the stub machinery is already keyed on &Composite +
&[Role]. This cycle makes the root render identically to an interior: a
root role becomes [price] marker + #price stub, byte-symmetric with how the
same role renders inside a where: definition. The payoff is the C12 "render the
structure so any mis-wiring is visible before a run" guarantee, now complete and
uniform at every level — a swapped exposure/price wiring at a top-level
SimBroker becomes visible, and the root no longer reads as a different kind of
graph than its interiors.
Architecture
Two coordinated changes, both dissolving root-specialness; neither adds code that branches on "is this the root":
(A) Thread the stub context. render_blueprint passes the root composite as
stub_ctx instead of None. Since 0024 the root is a &Composite carrying
input_roles + edges, exactly what fan_in_identifiers → slot_source →
signature_of already consume. stub_ctx drops its now-vestigial Option (both
render_graph callers pass &Composite). No new stub function — acceptance #3
holds — the existing machinery serves a top-level leaf verbatim. This makes the
stubs mechanically determined:
- An interior-fed root slot (SimBroker slot 0, the
Exposureedge) takes the producer's sibling-uniquesignature_ofprefix →#E. - A source-fed root slot (SimBroker slot 1, the
pricerole) takes the role name verbatim viaslot_source's role branch →#price.
(B) Name root entries by role. render_blueprint builds its external-entry
markers from role.name (filtered to bound roles, source.is_some()), the same
construction render_definition already uses for interior roles. The
source:{kind} naming is removed. A bound root role therefore renders [price],
and its slot stub #price now refers to a marker that carries the same name —
the marker/stub mismatch the source:{kind} vestige caused is gone at the source,
not patched at the stub.
What stays special is only what is genuinely special. The root still filters
its entries to bound roles (source: Some) — an interior role is an open port,
a root role is a bound ingestion feed (C3). That filter is semantic, not
cosmetic, and is the sole remaining distinction; the rendering of a role is now
identical across levels.
The compiled view is deliberately left as [source:F64]. render_flat_graph
(graph.rs:495) builds source labels from the flat SourceSpec.kind — post-inline
the role name has dissolved (C23, names are non-load-bearing debug symbols that
do not reach the flat graph), so only the kind survives there. Thus [price]
pre-inline (name alive) and [source:F64] post-inline (name gone) is not an
inconsistency — it is C23 made visible, and it is the negative control proving the
change is confined to the structural blueprint view.
Scope: the CLI render layer only (crates/aura-cli/src/graph.rs). No engine
change, no run-loop change; read-only render (C9), behaviour-preserving for the
run path (C1). signature_of (crates/aura-engine/src/blueprint.rs) is read, not
modified.
Concrete code shapes
User-facing: aura graph --macd (the empirical evidence)
The MACD strategy root wires SimBroker (node 2) slot 0 (exposure) from the
interior Exposure edge and slot 1 (price) from the price source role. Two
lines of the main graph change — the source marker and the broker leaf:
# before — root reads as a different graph: kind-named source, bare fan-in leaf
[source:F64]
...
[Recorder] [SimBroker]
# after — root reads like an interior: role-named source, stubbed fan-in leaf
[price]
...
[Recorder] [SimBroker(#E,#price)]
#E is the exposure producer's sibling-unique signature prefix (Exposure's
signature_of is Em at the macd root / Es at the sample root; the leading E
already separates it from price). #price is the source role's name, verbatim —
and now matches the [price] marker feeding the slot. The same two changes appear
in the default aura graph (sample) view, whose root is wired identically.
Both marker and leaf change width, so the ascii-dag Sugiyama layout of the whole
main graph re-flows. The full golden is re-captured from the actual aura graph
output (not a line edit); the load-bearing facts are: the source marker reads
[price], the SimBroker leaf carries the two #… stubs, and the where:
section stays byte-identical.
Implementation before → after
// render_blueprint entry construction (crates/aura-cli/src/graph.rs ~line 70)
// before — kind-named source marker (pre-0024 vestige)
let entries: Vec<Entry> = bp.input_roles().iter()
.filter_map(|role| role.source.map(|kind| Entry {
name: format!("source:{kind:?}"),
targets: &role.targets,
}))
.collect();
// after — role-named, identical to render_definition modulo the bound-only filter
let entries: Vec<Entry> = bp.input_roles().iter()
.filter(|role| role.source.is_some())
.map(|role| Entry { name: role.name.clone(), targets: &role.targets })
.collect();
// render_graph signature (crates/aura-cli/src/graph.rs ~line 111)
// before: stub_ctx: Option<&Composite> after: stub_ctx: &Composite
// leaf_label stub gate (crates/aura-cli/src/graph.rs ~line 283)
// before — Some/None gate; the None arm was the root carve-out
let stubs: Vec<String> = match stub_ctx {
Some(c) if slots.len() > 1 => fan_in_identifiers(c, index, &slots),
_ => Vec::new(),
};
// after — context always present; the fan-in test alone decides
let stubs: Vec<String> = if slots.len() > 1 {
fan_in_identifiers(stub_ctx, index, &slots)
} else {
Vec::new()
};
// the two render_graph call sites (crates/aura-cli/src/graph.rs ~80, ~454)
// render_blueprint: None, -> bp,
// render_definition: Some(c), -> c,
The Entry doc-comment (graph.rs ~41, "the blueprint from SourceSpec
(name = source:{kind})") and leaf_label's item-3 doc-comment (graph.rs ~243,
"At the blueprint root no such context is threaded…") are rewritten: a root entry
is named by its role like any interior role, and a multi-input fan-in renders its
slot stubs in both views (the "root has no context" carve-out is removed).
Components
render_blueprint(graph.rs): entries built fromrole.name(bound-only filter retained); passes the root composite asstub_ctx.render_graph/leaf_label(graph.rs):stub_ctxbecomes&Composite; the fan-in gate keys onslots.len() > 1alone.render_definition(graph.rs): passescinstead ofSome(c)(mechanical).render_flat_graph(graph.rs): unchanged — keepssource:{kind}from the flatSourceSpec(C23).slot_source/fan_in_identifiers/signature_of: unchanged, reused verbatim.
Data flow
render_blueprint(bp) builds entries { name: role.name, targets } for each
bound root role, then calls render_graph(bp.nodes(), bp.edges(), entries, …, stub_ctx = bp). Per node, leaf_label(…, stub_ctx = bp); for a multi-input leaf,
fan_in_identifiers(bp, index, slots) → per slot slot_source(bp, index, slot):
an edge-fed slot resolves through signature_of(bp.nodes(), bp.edges(), bp.input_roles(), bp.params(), e.from); a role-fed slot returns the role name.
The where: definitions still flow through render_definition(c) →
render_graph(…, stub_ctx = c), untouched and byte-stable. The compiled view
flows through the independent render_flat_graph, untouched.
Error handling
No new failure modes. Read-only structural rendering (C9): the stub path indexes
bp.nodes()/bp.edges()/bp.input_roles() the caller already holds;
slot_source returns an empty signature for an unwired slot (never panics);
fan_in_identifiers falls back to a positional #A for genuinely
interchangeable inputs. The dropped Option and the dropped source:{kind}
format both remove branches rather than add them. No engine validity surface is
touched (the derive_signature raw-indexing gap is the separate, still-open #24).
Testing strategy
All affected tests live in crates/aura-cli/src/main.rs:
-
blueprint_view_main_graph_shows_composite_as_opaque_node(~line 410): the needle list asserts the sample render contains[SimBroker](bare) with the comment "paramless leaves (SimBroker, Recorder) stay bare". Update: theSimBrokerneedle becomes[SimBroker(#E,#price)], and the comment is corrected — a paramless single-input leaf (Recorder) stays bare, a paramless multi-input fan-in (SimBroker) now shows its slot stubs. -
blueprint_view_golden(~line 526): full byte golden of the sample blueprint render — re-captured from the actualaura graphoutput. Two changes: the source marker[source:F64]→[price], theSimBrokerleaf gains(#E,#price); the main graph re-flows around both. Thewhere:section stays byte-identical (acceptance #2). -
macd_blueprint_renders_a_nested_composite_definition(~line 616): add an assertion that the macd rootSimBrokershows its two slot stubs —out.contains("[SimBroker(#E,#price)]"). The macd root source marker also becomes[price]; no existing assertion in this test pins the source marker, so thewhere:-interior and opaque-composite assertions are unchanged (acceptance #1 for--macd+ acceptance #2). -
compiled_view_golden(~line 569): asserted unchanged — renders throughrender_flat_graph, a separate path that keeps[source:F64](C23: name dissolved post-inline) and[SimBroker(0.0001)]. The negative control. -
nested_composite_renders_without_panic(~437) andreused_composite_defined_once(~475): use asrc-named bound root role. Their[source:F64]marker becomes[src], but neither asserts on the source marker text (only on composite markers[outer]/[inner]/[dup]and definition counts), so both stay green unmodified. Confirms the entry-naming change carries the role name through generally, not just forprice.
Build/test/lint gates: cargo build --workspace, cargo test --workspace,
cargo clippy --workspace --all-targets -- -D warnings.
Acceptance criteria
Verbatim from #49:
aura graph --macdrendersSimBroker's two input slots as#…stubs ([SimBroker(#E,#price)]).- The
where:section stays byte-identical (the composite stub output is unchanged). - No second parallel stub function is introduced (the de-duplication from #48
holds) —
stub_ctxloses itsOption; both views call the samefan_in_identifiers/slot_sourcepath.
Plus the cycle's structural-cleanliness goals:
- A bound root role renders
[price](role name), byte-symmetric with how the same role renders as an interior entry marker; thesource:{kind}special-case is removed from the blueprint view. - The sample
aura graphview shows both[price]and[SimBroker(#E,#price)](identical root wiring);blueprint_view_goldenre-captured value-asserted. compiled_view_goldenis byte-unchanged: the compiled view keeps[source:F64](C23 — name dissolved post-inline). Negative control.- Run-path determinism untouched (C1): only
crates/aura-cli/src/graph.rschanges. - Closes #49 on land.