Files
Aura/docs/plans/0025-render-root-slot-stubs.md
T
Brummel 21c1621bd0 docs,engine: drop coined "compilat", use FlatGraph / "flat graph"
"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.
2026-06-14 17:02:15 +02:00

16 KiB

Render the root composite like any other composite — Implementation Plan

Parent spec: docs/specs/0025-render-root-slot-stubs.md

For agentic workers: REQUIRED SUB-SKILL: use the implement skill to run this plan. Steps use - [ ] checkboxes for tracking.

Goal: Close #49 by making a top-level blueprint leaf render its fan-in slot stubs ([SimBroker(#E,#price)]) and a bound root role render by name ([price]), byte-symmetric with how the same constructs render inside a where: definition — removing the last two pre-0024 root render special-cases.

Architecture: Two coordinated edits in crates/aura-cli/src/graph.rs, both in the CLI render layer: (A) thread the root composite as the fan-in stub context (stub_ctx drops its Option; both render_graph callers pass &Composite), and (B) build root entries from role.name instead of format!("source:{kind}"). The existing slot_source/fan_in_identifiers/signature_of are reused verbatim (no new stub path). render_flat_graph is untouched (the compiled view keeps [source:F64] — names dissolve post-inline, C23). Behaviour-preserving for the run path (C1); read-only render (C9); no engine change.

Tech Stack: crates/aura-cli/src/graph.rs (render core), golden + assertion updates in crates/aura-cli/src/main.rs. Goldens are value-asserted regressions captured from the live aura graph / aura graph --macd output.


Files this plan creates or modifies

  • Modify: crates/aura-cli/src/graph.rs:37-46Entry doc-comment (drop the source:{kind} vestige; both views name entries by role).
  • Modify: crates/aura-cli/src/graph.rs:62-89render_blueprint entry construction (role-named, bound-only filter) + the render_graph call (Nonebp) + its leading comment.
  • Modify: crates/aura-cli/src/graph.rs:106-117render_graph doc-comment + signature (stub_ctx: Option<&Composite>&Composite).
  • Modify: crates/aura-cli/src/graph.rs:243-258leaf_label doc-comment item 3
    • signature (stub_ctx: Option<&Composite>&Composite).
  • Modify: crates/aura-cli/src/graph.rs:283-286leaf_label stub gate (matchif slots.len() > 1).
  • Modify: crates/aura-cli/src/graph.rs:461render_definition render_graph call (Some(c)c).
  • Test: crates/aura-cli/src/main.rs:417 — needle [SimBroker] → captured [SimBroker(#E,#price)] + comment correction.
  • Test: crates/aura-cli/src/main.rs:526-562blueprint_view_golden full re-capture.
  • Test: crates/aura-cli/src/main.rs:615-639macd_blueprint_renders_a_nested_composite_definition add a root-SimBroker-stub assertion.
  • Test: crates/aura-cli/src/main.rs:474-497reused_composite_defined_once add a [src] role-name-passthrough assertion.

Explicitly OUT of scope (do not touch):

  • crates/aura-cli/src/graph.rs:486-505 (render_flat_graph) — the negative-control path; must stay byte-identical so compiled_view_golden does not move.
  • crates/aura-cli/src/main.rs:564-589 (compiled_view_golden) — its expected block ([source:F64], [SimBroker(0.0001)]) must NOT be edited.
  • fieldtests/milestone-construction-layer/render_clustered.txt — a stale manual fieldtest fixture (carries [source:F64] + a cluster box already stale since 0017); not referenced by any cargo test, not part of this cycle.
  • Any file under crates/aura-engine/signature_of is read, not modified.

Task 1: graph.rs — thread the stub context + name root entries by role

Files:

  • Modify: crates/aura-cli/src/graph.rs

This task is one compile unit: changing stub_ctx's type from Option<&Composite> to &Composite breaks compilation until BOTH render_graph call sites (render_blueprint and render_definition) are updated, so all edits land together and the gate is a build, not a test (the goldens stay red until Task 2 re-captures them).

  • Step 1: Rewrite the Entry doc-comment (drop the source:{kind} vestige)

Replace the doc-comment above struct Entry (graph.rs:37-42). Current:

/// A CLI-local, *borrowed* render notion unifying a composite **input role** and a
/// blueprint **source** for the shared graph core (#48): both are an external entry
/// drawn as a marker node wired into its interior `targets`. The composite builds
/// these from `Role` (name = role name), the blueprint from `SourceSpec` (name =
/// `source:{kind}`) — no engine change, the list is assembled CLI-side and borrows
/// the engine's `Target` slices.

New:

/// A CLI-local, *borrowed* render notion unifying a composite **input role** and a
/// blueprint **bound source role** for the shared graph core (#48): both are an
/// external entry drawn as a marker node wired into its interior `targets`. Both
/// views build these from `Role` (name = role name); the blueprint root filters to
/// bound roles (`source.is_some()`), the only remaining root-vs-interior
/// distinction (C3) — no engine change, the list is assembled CLI-side and borrows
/// the engine's `Target` slices.
  • Step 2: Rewrite render_blueprint's entry construction (role-named, bound-only filter)

Replace the entry construction (graph.rs:70-79). Current:

    let entries: Vec<Entry> = bp
        .input_roles()
        .iter()
        .filter_map(|role| {
            role.source.map(|kind| Entry {
                name: format!("source:{kind:?}"),
                targets: &role.targets,
            })
        })
        .collect();

New:

    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();
  • Step 3: Update render_blueprint's leading comment and pass bp as the stub context

Replace the comment block + render_graph call (graph.rs:62-89). Current (comment 62-69, call 80-89):

    // the main graph is the shared graph core (`render_graph`) over the root
    // composite's top-level (nodes, edges): leaves enriched exactly as `where:`
    // interior leaves are (param names folded in, a `<field> →` prefix for any input
    // fed by a multi-output producer — the headline: macd's `histogram` driving
    // Exposure), composites opaque. The deltas vs a composite definition: param names
    // come from the builder (no alias overlay at the root); the entries are the
    // root's source-bound roles, not interior roles; there is no output record
    // (terminals are sinks) and no title.
    let main = render_graph(
        bp.nodes(),
        bp.edges(),
        &entries,
        &[], // no output bindings: a blueprint's terminals are sinks
        ParamNames::Factory,
        None, // no fan-in stub context at the root (deterministic no-op, #48)
        None, // no title
        color,
    );

New:

    // the main graph is the shared graph core (`render_graph`) over the root
    // composite's top-level (nodes, edges), rendered exactly as a `where:` interior:
    // leaves enriched (param names, a `<field> →` prefix for a multi-output producer
    // — macd's `histogram` driving Exposure — and, now threaded at the root too,
    // fan-in slot stubs), composites opaque, entries named by their role. The only
    // deltas vs a composite definition: param names come from the builder (no alias
    // overlay at the root); entries are filtered to bound source roles (C3); there
    // is no output record (terminals are sinks) and no title.
    let main = render_graph(
        bp.nodes(),
        bp.edges(),
        &entries,
        &[], // no output bindings: a blueprint's terminals are sinks
        ParamNames::Factory,
        bp, // the root IS the fan-in stub context (it carries roles + edges)
        None, // no title
        color,
    );
  • Step 4: Update render_graph's doc-comment and signature

Replace the tail of render_graph's doc-comment + the signature (graph.rs:106-120). Current (doc tail 106-109, signature 110-120):

/// `Some`. The two render-borders that differ — param-name source and fan-in stub
/// availability — are passed as `param_names` / `stub_ctx`.
#[allow(clippy::too_many_arguments)]
fn render_graph(
    nodes: &[BlueprintNode],
    edges: &[Edge],
    entries: &[Entry],
    output: &[OutField],
    param_names: ParamNames,
    stub_ctx: Option<&Composite>,
    title: Option<&str>,
    color: Color,
) -> String {

New:

/// `Some`. The render-border that differs — the param-name source — is passed as
/// `param_names`; `stub_ctx` is the borrowed composite (root or interior) both
/// views thread for fan-in slot resolution.
#[allow(clippy::too_many_arguments)]
fn render_graph(
    nodes: &[BlueprintNode],
    edges: &[Edge],
    entries: &[Entry],
    output: &[OutField],
    param_names: ParamNames,
    stub_ctx: &Composite,
    title: Option<&str>,
    color: Color,
) -> String {
  • Step 5: Rewrite leaf_label's doc-comment item 3 and update its signature

Replace item 3 of leaf_label's doc-comment (graph.rs:243-246). Current:

/// 3. The leaf's input-slot stubs (`#Sf`, …) when it is a multi-input fan-in **and** a
///    composite context is available (`stub_ctx = Some`). At the blueprint root no
///    such context is threaded, so a top-level fan-in renders without stubs (a
///    deterministic no-op, #48) rather than duplicating the signature machinery.

New:

/// 3. The leaf's input-slot stubs (`#Sf`, …) when it is a multi-input fan-in. The
///    `stub_ctx` is the borrowed composite (root or interior) the wired slots
///    resolve against; both views thread it, so a top-level fan-in stubs exactly as
///    an interior one does — one shared path, no root carve-out (#49).

Then change the leaf_label signature parameter (graph.rs:258). Current:

    stub_ctx: Option<&Composite>,

New:

    stub_ctx: &Composite,
  • Step 6: Simplify the stub gate

Replace the stub gate in leaf_label (graph.rs:283-286). Current:

    let stubs: Vec<String> = match stub_ctx {
        Some(c) if slots.len() > 1 => fan_in_identifiers(c, index, &slots),
        _ => Vec::new(),
    };

New:

    let stubs: Vec<String> = if slots.len() > 1 {
        fan_in_identifiers(stub_ctx, index, &slots)
    } else {
        Vec::new()
    };
  • Step 7: Update render_definition's render_graph call

In render_definition, change the stub_ctx argument (graph.rs:461). Current:

        Some(c),

New:

        c,
  • Step 8: Build gate — graph.rs compiles

Run: cargo build -p aura-cli Expected: finishes with 0 errors (a warning:-free build; the #[allow(clippy::too_many_arguments)] stays, so no new clippy noise here).

  • Step 9: Observe the expected golden drift (RED confirmation, non-gating)

Run: cargo test -p aura-cli Expected: blueprint_view_golden FAILS ("blueprint render drifted") and blueprint_view_main_graph_shows_composite_as_opaque_node FAILS (the [SimBroker] needle no longer matches — the leaf now reads [SimBroker(#E,#price)]). compiled_view_golden, macd_blueprint_renders_a_nested_composite_definition, nested_composite_renders_without_panic, and reused_composite_defined_once still PASS. This drift is expected and is fixed in Task 2 — do NOT hand-edit the goldens here.


Task 2: main.rs — re-capture goldens and add the new assertions

Files:

  • Test: crates/aura-cli/src/main.rs

The render bytes (stub text, ascii-dag re-flow) are produced by the live binary, not hand-written. Capture them, paste them, and reuse the exact captured SimBroker substring in the two contains assertions so the pin and the rendered output are byte-consistent.

  • Step 1: Capture the exact sample aura graph output

Run: cargo run -p aura-cli -- graph Expected: the structural blueprint view prints to stdout. Two facts to read off it: (a) the full output bytes (for Step 2), and (b) the exact SimBroker bracket-substring on the [Recorder] [SimBroker(…)] line. The predicted value is [SimBroker(#E,#price)] (#E = the Exposure producer's sibling-unique signature_of prefix; #price = the price role name). If the capture differs, use the captured substring everywhere below — the golden is value-asserted, the prediction is not load-bearing.

  • Step 2: Replace the blueprint_view_golden expected block with the captured bytes

In blueprint_view_golden (main.rs:526), replace the entire expected raw-string (main.rs:531-560, between let expected = r#" and "#;) with the exact stdout from Step 1. Keep the // … Re-capture via aura graph if intended. comment (main.rs:528-530) and the assert (main.rs:561). Verification of intent (not a code edit): the where: portion of the new block (the sma_cross(fast:i64, slow:i64) -> (cross): definition through [cross := Sub(#Sf,#Ss)]) must be byte-identical to the old block — only the main-graph portion (the [source:F64][price] marker and the SimBroker leaf) and its layout re-flow may differ. If a where: line changed, STOP — that means the interior render moved, which this cycle must not do.

  • Step 3: Run the golden test green

Run: cargo test -p aura-cli blueprint_view_golden Expected: PASS (1 test). (Filter substring blueprint_view_golden matches exactly this named test in the current tree.)

  • Step 4: Update the needle and comment in the opaque-node test

In blueprint_view_main_graph_shows_composite_as_opaque_node, replace the needle array (main.rs:417) and its comment (main.rs:414-416). Current:

        // top-level leaves render enriched (param names folded in, #48), exactly as
        // the `where:` interior leaves are — `Exposure` folds its `scale` param;
        // paramless leaves (SimBroker, Recorder) stay bare.
        for needle in ["[Exposure(scale)]", "[SimBroker]", "[Recorder]"] {

New (use the exact SimBroker substring captured in Step 1 — shown here as the predicted [SimBroker(#E,#price)]):

        // top-level leaves render enriched exactly as the `where:` interior leaves:
        // `Exposure` folds its `scale` param; a paramless SINGLE-input leaf
        // (`Recorder`) stays bare, but a paramless MULTI-input fan-in (`SimBroker`)
        // now shows its slot stubs (#49).
        for needle in ["[Exposure(scale)]", "[SimBroker(#E,#price)]", "[Recorder]"] {
  • Step 5: Add the root-SimBroker-stub assertion to the macd test

First capture the macd view: cargo run -p aura-cli -- graph --macd and read its SimBroker bracket-substring (predicted [SimBroker(#E,#price)]; use the captured value if different). Then, in macd_blueprint_renders_a_nested_composite_definition, add one assertion after the [histogram := Sub(#S,#Es)] assertion (main.rs:630):

        // the root SimBroker is a top-level multi-input fan-in: its two slots now
        // render as #… stubs, mirroring the where: interior (#49).
        assert!(out.contains("[SimBroker(#E,#price)]"), "root SimBroker shows its two slot stubs: {out}");
  • Step 6: Add the [src] role-name-passthrough assertion

In reused_composite_defined_once, add an assertion after the dup( count assertion (main.rs:496):

        // the bound root role named "src" carries its name into the entry marker
        // (general non-"price" role-name passthrough, not just the sample's price).
        assert!(out.contains("[src]"), "root role name renders as the entry marker: {out}");
  • Step 7: Test gate — the whole CLI crate is green

Run: cargo test -p aura-cli Expected: all tests PASS (0 failed). In particular compiled_view_golden passes unchanged (its [source:F64] / [SimBroker(0.0001)] block was not edited — the negative control), and the four edited tests pass.

  • Step 8: Workspace gate — full suite + lint

Run: cargo test --workspace Expected: all tests PASS (0 failed) — no run-path test moved (C1).

Run: cargo clippy --workspace --all-targets -- -D warnings Expected: finishes clean (no warnings; exit 0).