21c1621bd0
"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.
257 lines
12 KiB
Markdown
257 lines
12 KiB
Markdown
# 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`:
|
|
|
|
1. A composite-**interior** multi-input leaf renders its wired slots as `#…`
|
|
source-derived stubs (`[cross := Sub(#Sf,#Ss)]`); a **top-level** leaf does not
|
|
— `SimBroker` renders as bare `[SimBroker]` — because `render_blueprint`
|
|
threads `stub_ctx: None`.
|
|
2. An interior role renders as a `[price]` entry marker (the role **name**); a
|
|
root role renders as `[source:F64]` (the ingestion **kind**), because
|
|
`render_blueprint` builds entries from `format!("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 `Exposure` edge) takes the
|
|
producer's sibling-unique `signature_of` prefix → `#E`.
|
|
- A **source-fed** root slot (SimBroker slot 1, the `price` role) takes the role
|
|
name verbatim via `slot_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:
|
|
|
|
```text
|
|
# 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
|
|
|
|
```rust
|
|
// 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();
|
|
```
|
|
|
|
```rust
|
|
// render_graph signature (crates/aura-cli/src/graph.rs ~line 111)
|
|
// before: stub_ctx: Option<&Composite> after: stub_ctx: &Composite
|
|
```
|
|
|
|
```rust
|
|
// 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()
|
|
};
|
|
```
|
|
|
|
```rust
|
|
// 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 from `role.name` (bound-only
|
|
filter retained); passes the root composite as `stub_ctx`.
|
|
- **`render_graph` / `leaf_label`** (graph.rs): `stub_ctx` becomes `&Composite`;
|
|
the fan-in gate keys on `slots.len() > 1` alone.
|
|
- **`render_definition`** (graph.rs): passes `c` instead of `Some(c)` (mechanical).
|
|
- **`render_flat_graph`** (graph.rs): **unchanged** — keeps `source:{kind}` from the
|
|
flat `SourceSpec` (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`:
|
|
|
|
1. **`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: the
|
|
`SimBroker` needle 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.
|
|
|
|
2. **`blueprint_view_golden`** (~line 526): full byte golden of the sample
|
|
blueprint render — re-captured from the actual `aura graph` output. Two
|
|
changes: the source marker `[source:F64]` → `[price]`, the `SimBroker` leaf
|
|
gains `(#E,#price)`; the main graph re-flows around both. The `where:` section
|
|
stays byte-identical (acceptance #2).
|
|
|
|
3. **`macd_blueprint_renders_a_nested_composite_definition`** (~line 616): add an
|
|
assertion that the macd root `SimBroker` shows 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 the `where:`-interior and opaque-composite assertions are unchanged
|
|
(acceptance #1 for `--macd` + acceptance #2).
|
|
|
|
4. **`compiled_view_golden`** (~line 569): asserted **unchanged** — renders
|
|
through `render_flat_graph`, a separate path that keeps `[source:F64]` (C23:
|
|
name dissolved post-inline) and `[SimBroker(0.0001)]`. The negative control.
|
|
|
|
5. **`nested_composite_renders_without_panic`** (~437) and
|
|
**`reused_composite_defined_once`** (~475): use a `src`-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 for `price`.
|
|
|
|
Build/test/lint gates: `cargo build --workspace`, `cargo test --workspace`,
|
|
`cargo clippy --workspace --all-targets -- -D warnings`.
|
|
|
|
## Acceptance criteria
|
|
|
|
Verbatim from #49:
|
|
|
|
- [ ] `aura graph --macd` renders `SimBroker`'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_ctx` loses its `Option`; both views call the same
|
|
`fan_in_identifiers`/`slot_source` path.
|
|
|
|
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; the `source:{kind}` special-case
|
|
is removed from the blueprint view.
|
|
- [ ] The sample `aura graph` view shows both `[price]` and `[SimBroker(#E,#price)]`
|
|
(identical root wiring); `blueprint_view_golden` re-captured value-asserted.
|
|
- [ ] `compiled_view_golden` is 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.rs`
|
|
changes.
|
|
- [ ] Closes #49 on land.
|