feat(aura-engine): composite output is a named multi-field record
Replace a composite's single output port (OutPort { node, field }) with
an ordered, named record (Vec<OutField { node, field, name }>). A
composite can now re-export K interior fields as one output; a consumer
selects one via the existing Edge::from_field — the same field-wise
selector that already works for multi-output leaf producers (OHLCV).
Why: multi-line indicators (MACD, Bollinger, Stochastic, Ichimoku) could
not be authored as a composition and re-exported as a unit — only one
line escaped the boundary. The MACD PoC was forced to expose only the
histogram. This lifts the three `field == 0` / `from_field == 0` caps at
the composite boundary (inline_composite nested arm, rewrite_edge
composite arm), range-checking the index instead.
Boundary completion, not an invariant change:
- C8/C7/C4 untouched — one port, one row per eval, K base columns (a
3-line MACD is identical in kind to OHLCV).
- C23 honoured — re-export names live at the blueprint boundary only and
are dropped in the compilat (raw index wiring). compiled_view_golden
stayed byte-identical (the regression guard): no name leaked into the
flat graph.
The MACD PoC now re-exports macd / signal / histogram as a record; the
strategy still trades the histogram, selected via from_field: 2. The
render shows K [out:<name>] markers per multi-output composite (input
roles stay [in:<index>] — naming them is the dependent #41).
Output naming ships with the capability (OutField is born name-ready) so
#41 — composite param aliasing + input-role naming — does not reopen the
type.
Verification: cargo build/test/clippy --workspace -- -D warnings all
green; aura-engine 62 tests (+3 capability, +1 through-run E2E), aura-cli
unchanged-count green; compiled_view_golden byte-identical.
Known debt (out of scope, pre-existing): the non-workspace construction-
layer fieldtests (fieldtests/milestone-construction-layer/mc_1..mc_4)
carry their OutField sweep but still do not compile standalone — they use
the Sma::new / BlueprintNode::from(Sma) API retired in the cycle-0016
value-empty migration, never propagated to these fixtures. Tracked as a
follow-up.
closes #40
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
// and confirm the recorded trace is non-empty.
|
||||
//
|
||||
// Public interface only: Blueprint / Composite / BlueprintNode / Edge / Target /
|
||||
// OutPort / SourceSpec from aura-engine; Sma / Sub / Exposure / SimBroker /
|
||||
// OutField / SourceSpec from aura-engine; Sma / Sub / Exposure / SimBroker /
|
||||
// Recorder from aura-std; ScalarKind / Scalar / Firing / Timestamp from
|
||||
// aura-core. No crates/*/src was read.
|
||||
//
|
||||
@@ -17,7 +17,7 @@
|
||||
use std::sync::mpsc;
|
||||
|
||||
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutPort, SourceSpec, Target};
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, SourceSpec, Target};
|
||||
use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub};
|
||||
|
||||
fn main() {
|
||||
@@ -44,7 +44,7 @@ fn main() {
|
||||
Target { node: 1, slot: 0 },
|
||||
]],
|
||||
// single exposed output: Sub's output field 0
|
||||
OutPort { node: 2, field: 0 },
|
||||
vec![OutField { node: 2, field: 0, name: "out".into() }],
|
||||
);
|
||||
|
||||
// --- The harness blueprint that USES the composite. ----------------------
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// Public interface only: Composite / Blueprint accessors + Node::label() via
|
||||
// the boxed node; ledger C8-refinement (label is the disambiguating symbol).
|
||||
|
||||
use aura_engine::{BlueprintNode, Composite, Edge, OutPort, Target};
|
||||
use aura_engine::{BlueprintNode, Composite, Edge, OutField, Target};
|
||||
// NB: `node.label()` on a `&Box<dyn Node>` leaf dispatches via the trait object
|
||||
// without an explicit `use aura_core::Node` (the method is in scope through the
|
||||
// boxed trait object). A consumer does not need to import the Node trait to read
|
||||
@@ -44,7 +44,7 @@ fn cross(swap: bool) -> Composite {
|
||||
],
|
||||
vec![e_fast, e_slow],
|
||||
vec![vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }]],
|
||||
OutPort { node: 2, field: 0 },
|
||||
vec![OutField { node: 2, field: 0, name: "out".into() }],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use std::sync::mpsc;
|
||||
|
||||
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutPort, SourceSpec, Target};
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, SourceSpec, Target};
|
||||
use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub};
|
||||
|
||||
/// Inner composite: the SMA(2)/SMA(4) cross (one price role -> spread output).
|
||||
@@ -32,7 +32,7 @@ fn inner_cross() -> Composite {
|
||||
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
||||
],
|
||||
vec![vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }]],
|
||||
OutPort { node: 2, field: 0 },
|
||||
vec![OutField { node: 2, field: 0, name: "out".into() }],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ fn strategy() -> Composite {
|
||||
],
|
||||
vec![Edge { from: 0, to: 1, slot: 0, from_field: 0 }], // cross -> Exposure
|
||||
vec![vec![Target { node: 0, slot: 0 }]], // price -> inner role 0
|
||||
OutPort { node: 1, field: 0 },
|
||||
vec![OutField { node: 1, field: 0, name: "out".into() }],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
// Public interface only.
|
||||
|
||||
use aura_core::ScalarKind;
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutPort, SourceSpec, Target};
|
||||
use aura_engine::{Blueprint, BlueprintNode, Composite, Edge, OutField, SourceSpec, Target};
|
||||
// NB (fieldtest finding): `aura_engine::ScalarKind` does NOT resolve — the
|
||||
// construction surface (aura-engine) does not re-export the core scalar
|
||||
// vocabulary (ScalarKind / Scalar / Firing) that a graph-builder needs. A
|
||||
@@ -31,7 +31,7 @@ fn sma_cross() -> Composite {
|
||||
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
||||
],
|
||||
vec![vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }]],
|
||||
OutPort { node: 2, field: 0 },
|
||||
vec![OutField { node: 2, field: 0, name: "out".into() }],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user