refactor(composites): extract Stage-1 composite-builders into their own crate

Dissolve the aura-engine -> aura-std dependency by moving the vol_stop /
risk_executor composite-builders out of aura-engine into a new aura-composites
crate. This restores the engine's domain-free layering, which the
composites-in-engine placement (cycle 0065) had inverted.

Why: the engine routes type-erased Scalar records and never names a concrete
node -- summarize_r reads the PositionManagement record positionally, by column
index, without linking aura-std (which is exactly why aura-std was a dev-only
dependency before 0065). Parking the node-naming composite-builders inside
aura-engine forced the engine's production code to reference the node library
for the first time (aura-engine -> aura-std), an upside-down layering: the
engine is the foundation the node library builds upon, not the reverse.

The clean home is a dedicated crate that depends on BOTH the engine's
GraphBuilder and the std nodes, leaving each lower crate untouched:

    aura-composites -> { aura-engine, aura-std } -> aura-core   (acyclic)
    aura-engine     -> aura-core only            (domain-free again)
    aura-std        -> aura-core only            (lean node library)

aura-std reverts to a [dev-dependency] of aura-engine (the engine's own E2E
tests -- stage1_r_e2e, random_sweep_e2e, ger40_breakout -- still drive real
std nodes through a bootstrapped Harness; that test-only edge creates no cycle).

Moved with history (git mv):
- crates/aura-engine/src/composites.rs           -> crates/aura-composites/src/lib.rs
- crates/aura-engine/tests/risk_executor.rs      -> crates/aura-composites/tests/
- crates/aura-engine/tests/vol_stop_composite.rs -> crates/aura-composites/tests/

Consumers rewired: aura-cli imports risk_executor / StopRule from aura_composites;
the moved tests import the builders from the crate under test. The intra-doc link
to RollMode and the module rationale doc are updated for the new home.

Behaviour-preserving: workspace build clean; clippy --all-targets -D warnings
clean; full suite 513 passed / 0 failed, identical to baseline (the two moved
test files run unchanged under aura-composites); cargo doc --no-deps clean (no
broken intra-doc links).
This commit is contained in:
2026-06-24 13:44:44 +02:00
parent c406b969f5
commit 1a6eafa056
10 changed files with 71 additions and 25 deletions
Generated
+11
View File
@@ -50,6 +50,7 @@ dependencies = [
name = "aura-cli"
version = "0.1.0"
dependencies = [
"aura-composites",
"aura-core",
"aura-engine",
"aura-ingest",
@@ -60,6 +61,16 @@ dependencies = [
"serde_json",
]
[[package]]
name = "aura-composites"
version = "0.1.0"
dependencies = [
"aura-core",
"aura-engine",
"aura-std",
"serde_json",
]
[[package]]
name = "aura-core"
version = "0.1.0"
+1
View File
@@ -12,6 +12,7 @@ members = [
"crates/aura-core",
"crates/aura-std",
"crates/aura-engine",
"crates/aura-composites",
"crates/aura-cli",
"crates/aura-ingest",
"crates/aura-registry",
+3
View File
@@ -12,6 +12,9 @@ path = "src/main.rs"
[dependencies]
aura-core = { path = "../aura-core" }
aura-engine = { path = "../aura-engine" }
# aura-composites: the Stage-1 RiskExecutor / vol_stop composite-builders the
# stage1-r harness wires (kept out of aura-engine so the engine stays domain-free).
aura-composites = { path = "../aura-composites" }
aura-registry = { path = "../aura-registry" }
aura-std = { path = "../aura-std" }
aura-ingest = { path = "../aura-ingest" }
+5 -5
View File
@@ -15,12 +15,12 @@ mod render;
use render::{ChartData, ChartMeta, ChartMode, ReduceKind, Series};
use aura_core::{zip_params, Cell, Firing, Scalar, ScalarKind, Timestamp};
use aura_composites::{risk_executor, StopRule};
use aura_engine::{
f64_field, join_on_ts, monte_carlo, param_stability, risk_executor, summarize, summarize_r,
walk_forward, window_of, ColumnarTrace, Composite, Edge, FlatGraph, GraphBuilder, Harness,
JoinedRow, McAggregate, McFamily, RollMode, RunManifest, RunReport, SourceSpec, StopRule,
SweepFamily, SyntheticSpec, Target, VecSource, WalkForwardResult, WindowBounds, WindowRoller,
WindowRun,
f64_field, join_on_ts, monte_carlo, param_stability, summarize, summarize_r, walk_forward,
window_of, ColumnarTrace, Composite, Edge, FlatGraph, GraphBuilder, Harness, JoinedRow,
McAggregate, McFamily, RollMode, RunManifest, RunReport, SourceSpec, SweepFamily,
SyntheticSpec, Target, VecSource, WalkForwardResult, WindowBounds, WindowRoller, WindowRun,
};
use aura_registry::{
group_families, mc_member_reports, optimize, rank_by, sweep_member_reports,
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "aura-composites"
edition.workspace = true
version.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
# aura-composites is the composition layer: `GraphBuilder`/`Composite` come from the
# engine, the wired nodes from aura-std. It lives in its own crate so the engine
# stays domain-free (-> aura-core only) — see src/lib.rs for the full rationale.
# This is the single layer that couples both, and the graph stays acyclic
# (aura-composites -> {aura-engine, aura-std} -> aura-core).
aura-core = { path = "../aura-core" }
aura-engine = { path = "../aura-engine" }
aura-std = { path = "../aura-std" }
[dev-dependencies]
# serde_json: the risk_executor fixture round-trips a folded RunMetrics through
# serde to prove the `r` block survives (RunMetrics lives in aura-engine).
serde_json = { workspace = true }
@@ -1,18 +1,26 @@
//! Reusable Stage-1 composite-builders: the volatility stop and the per-symbol
//! RiskExecutor, promoted from the iter-2 integration-test fixtures so the CLI
//! harness (and any consumer) can wire them. Both are `GraphBuilder` compositions
//! of `aura-std` primitives — the engine's convenience layer over the standard
//! nodes (the sibling of `report::summarize_r`, which likewise lives in this crate
//! and reads the `aura-std` PositionManagement record). The Veto is a documented
//! seam, not a node (a pass-through identity is exactly what C19/C23 DCE deletes),
//! so it appears nowhere here.
//! `aura-composites` — reusable Stage-1 composite-builders: the volatility stop and
//! the per-symbol RiskExecutor. Both are `GraphBuilder` compositions (from
//! `aura-engine`) of `aura-std` primitives, so this crate sits *above* both and is
//! the single place where the engine's builder and the standard nodes are wired
//! together.
//!
//! It lives in its own crate on purpose. The engine routes type-erased `Scalar`
//! records and never names a concrete node — `aura_engine::summarize_r` reads the
//! `PositionManagement` record positionally, by column index, without linking
//! `aura-std`. Keeping these node-naming builders out of `aura-engine` preserves
//! that: the engine stays `-> aura-core` only (domain-free), `aura-std` stays the
//! lean node library (`-> aura-core`), and the `aura-engine -> aura-std` edge these
//! builders would otherwise force is dissolved. `aura-composites -> {aura-engine,
//! aura-std}` is the one layer that couples both, and the graph stays acyclic.
//!
//! The Veto is a documented seam, not a node (a pass-through identity is exactly
//! what C19/C23 DCE deletes), so it appears nowhere here.
use aura_core::Scalar;
use aura_engine::{Composite, GraphBuilder};
use aura_std::{
Delay, Ema, FixedStop, LinComb, Mul, PositionManagement, Sizer, Sqrt, Sub, PM_FIELD_NAMES,
};
use crate::{Composite, GraphBuilder};
/// The volatility stop as a composition of primitives:
/// `stop_distance = k · Sqrt(Ema(Mul(Δ,Δ), length))`, `Δ = price Delay(price, 1)`
/// — a rolling EWMA standard deviation. Role: input `price` → output `stop_distance`.
@@ -40,7 +48,7 @@ pub fn vol_stop(length: i64, k: f64) -> Composite {
/// volatility-scaled `vol_stop`. Both expose `price → stop_distance`, so the
/// RiskExecutor embeds either by identical downstream wiring.
///
/// Mirrors the sibling axis enum [`RollMode`](crate::RollMode); `Eq` is omitted
/// Mirrors the sibling axis enum [`RollMode`](aura_engine::RollMode); `Eq` is omitted
/// because the `f64` payloads forbid it.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum StopRule {
@@ -8,7 +8,8 @@ use aura_core::{
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, Scalar,
ScalarKind, Timestamp,
};
use aura_engine::{risk_executor, summarize_r, GraphBuilder, RunMetrics, StopRule, VecSource};
use aura_composites::{risk_executor, StopRule};
use aura_engine::{summarize_r, GraphBuilder, RunMetrics, VecSource};
use aura_std::{Recorder, PM_FIELD_NAMES, PM_RECORD_KINDS};
use std::sync::mpsc::channel;
@@ -4,7 +4,8 @@
//! `GraphBuilder`, bootstraps, and computes the right number end-to-end — the principle
//! that a node expressible as a DAG of primitives is a composition, not a fused node.
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
use aura_engine::{vol_stop, GraphBuilder, VecSource};
use aura_composites::vol_stop;
use aura_engine::{GraphBuilder, VecSource};
use aura_std::Recorder;
use std::sync::mpsc::channel;
+7 -5
View File
@@ -14,13 +14,15 @@ serde = { workspace = true }
# serde_json renders RunReport JSON for both the registry (disk) and stdout
# (RunReport::to_json) — one shape, no hand-rolled writer (amended C16).
serde_json = { workspace = true }
# aura-std's standard nodes back the public composite-builders in src/composites.rs
# (vol_stop, risk_executor) — the engine's convenience layer over the standard nodes.
# aura-std depends only on aura-core, so this stays acyclic (aura-engine -> aura-std
# -> aura-core).
aura-std = { path = "../aura-std" }
[dev-dependencies]
# aura-std is a TEST-ONLY dependency: the engine's own integration tests
# (stage1_r_e2e, random_sweep_e2e, ger40_breakout) drive real standard nodes through
# a bootstrapped Harness. The engine's library code never names a concrete node — it
# routes type-erased Scalar records (summarize_r reads the PositionManagement record
# positionally, by index), so aura-std stays out of [dependencies] and the engine is
# `-> aura-core` only. The node-naming composite-builders live in `aura-composites`.
aura-std = { path = "../aura-std" }
# chrono / chrono-tz build the Berlin-local-wall-clock epoch-ns timestamps the
# GER40 session-breakout E2E fixture (tests/ger40_breakout.rs) feeds the engine,
# exactly as `Session`'s own unit test does. Pinned to aura-std's versions (the
-2
View File
@@ -44,7 +44,6 @@
mod blueprint;
mod builder;
mod composites;
mod graph_model;
mod harness;
mod mc;
@@ -57,7 +56,6 @@ pub use blueprint::{
Role, SweepBinder,
};
pub use builder::{BuildError, GraphBuilder, InPort, NodeHandle, OutPort, RoleHandle};
pub use composites::{risk_executor, vol_stop, StopRule};
pub use graph_model::model_to_json;
pub use harness::{
window_of, BootstrapError, Edge, FlatGraph, Harness, Source, SourceSpec, SyntheticSpec, Target,