feat(aura-engine): name the composite boundary — input roles + param aliases

Brings the other two composite-boundary edge-kinds to the named-projection
shape #40 gave outputs. input_roles changes from a bare Vec<Vec<Target>>
to Vec<Role { name, targets }> (rendered [in:<name>]); a composite gains
params: Vec<ParamAlias { name, node, slot }> that relabels an interior
leaf param slot's surface name in param_space() (rendered [param:<name>]).

Param aliasing is a PURE NAMING OVERLAY, not curation (the load-bearing
decision, per spec 0019): every interior param slot stays in param_space()
and sweepable; the alias only relabels in place, never reorders or hides.
Proven empirically — the MACD run is byte-identical (total_pips
0.1637945563898923, 3 sign flips), only the param labels improved:
param_space() now surfaces [macd.fast, macd.slow, macd.signal] instead of
three indistinguishable macd.length, and the manifest reads ema_fast/
ema_slow/ema_signal.

C23 honoured: role/param/output names are non-load-bearing debug symbols
dropped at lowering; identity is positional (role index, param slot,
output field). compiled_view_golden is byte-identical (verified: the
golden region is untouched in the diff). An out-of-range alias (missing/
non-leaf node or slot past the leaf's param count) is rejected at
compile_with_params as BadInteriorIndex, mirroring the output range-check
(no new variant). Orthogonal to #36 — purely additive at the composite
level.

Aliasing is demonstrated on the CLI MACD site only (the spec's worked
example + a new E2E test macd_param_space_surfaces_the_three_named_aliases);
sma_cross, the engine test fixtures, and the construction-layer fieldtests
get the forced role-name + empty params, so the param_space C23 anchor
goldens (param_space_mirrors_compiled_flat_node_param_order + siblings)
stay byte-identical.

Verification (orchestrator-run, not trusted from the agent report):
cargo build/test/clippy --workspace -D warnings all green (engine 66,
cli 12); the separate-workspace construction-layer fieldtest crate builds
(guards the #42 latent-drift recurrence); compiled_view_golden + MACD
determinism unchanged.

Two faithful repairs to the plan's literal test/code bodies, no semantic
change: the out-of-range test uses .err()/Some(BadInteriorIndex) (the Ok
arm Vec<Box<dyn Node>> is not Debug, so .unwrap_err() would not compile),
and the inline_composite destructure binds params as `param_aliases` to
avoid shadowing the injected `params: &[Scalar]` arg.

closes #41
This commit is contained in:
2026-06-08 02:21:30 +02:00
parent c9115e3e0f
commit 41cbb5506f
8 changed files with 323 additions and 66 deletions
+11 -5
View File
@@ -99,8 +99,9 @@ fn collect_distinct_composites(bp: &Blueprint) -> Vec<&Composite> {
}
/// Render one composite's interior as a flat graph: interior leaves as `[type]`,
/// nested composites as opaque `[name]`, plus an `[in:k]` entry marker per input
/// role (wired to its interior targets) and an `[out:<name>]` marker per
/// nested composites as opaque `[name]`, plus an `[in:<name>]` entry marker per
/// input role (wired to its interior targets), a `[param:<name>]` marker per param
/// alias (wired to the leaf it relabels), and an `[out:<name>]` marker per
/// re-exported output field (wired from its producer). Prefixed `"<name>:\n"`.
fn render_definition(c: &Composite) -> String {
let mut labels: Vec<String> = Vec::with_capacity(c.nodes().len());
@@ -114,13 +115,18 @@ fn render_definition(c: &Composite) -> String {
for e in c.edges() {
edges.push((e.from, e.to));
}
for (role, targets) in c.input_roles().iter().enumerate() {
for role in c.input_roles() {
let in_id = labels.len();
labels.push(format!("in:{role}"));
for t in targets {
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));