feat(engine): bind ingestion sources by role key — SourceSpec.role + bind_sources/run_bound
refs #275 The engine-side half of by-name source binding. `SourceSpec` carries a load-bearing `role: Option<String>` — `Some(name)` for a spec lowered from a bound `Role` (carried through the blueprint lowering, previously dropped at compile), `None` for a hand-built raw-index spec. A pure `bind_sources(specs, keyed_supply)` resolves a `Vec<(role, Box<dyn Source>)>` into the positional `Vec` `run` consumes, in `SourceSpec` declaration order, erroring named (`SourceBindError`: Missing/Extra/Duplicate/Unnamed feed) on a mis-bind. `Harness::run_bound` is the ergonomic method the by-name production path calls. The raw-index `run(Vec)` primitive and the k-way merge / per-cycle event loop are untouched: a positional run stays byte-identical (C1, pinned by `run_bound_out_of_order_matches_positional_run`), and every existing `run(Vec)` caller keeps working (it ignores `role`). A `BTreeMap` index makes the Duplicate/Extra diagnostics deterministic. C4's tie-break guarantee ("source declaration order") is unchanged — `bind_sources` emits in `SourceSpec` order, so the merge's source-index tie-break resolves on declaration order regardless of the caller's supply order. The narrow C23 amendment (a `SourceSpec.role` is load-bearing for source binding, the rest of the flat graph stays raw-index) is deferred to the ledger note. The four error-path RED tests assert via `.map(|_| ()).unwrap_err()`: the `Ok` arm `Vec<Box<dyn Source>>` is not `Debug` (`Source` has no `Debug` supertrait), so a bare `.unwrap_err()` would not compile — a plan-byte defect corrected here. Also sweeps the 45 raw-index `SourceSpec { .. }` literals to `SourceSpec::raw(..)` across the workspace (mechanical, compiler-enumerated) and pins that a bound root role's name survives lowering. Verification: `cargo test --workspace` green (0 failed; incl. 5 `bind_sources` cases, the `run_bound` byte-identity test, the role-survives-lowering pin, and the four `.sources`-equality twins under the now-stricter `role` equality); `cargo build --workspace --all-targets` clean; `cargo clippy -p aura-engine --all-targets` clean.
This commit is contained in:
@@ -184,10 +184,7 @@ pub fn build_harness() -> (Harness, Taps) {
|
||||
// declaration order open(0), high(1), low(2), close(3) is the merge tie-break
|
||||
// order for the Barrier(0) group — identical to the synthetic capstone.
|
||||
let sources = (0..4)
|
||||
.map(|slot| SourceSpec {
|
||||
kind: ScalarKind::F64,
|
||||
targets: vec![Target { node: RESAMPLE, slot }],
|
||||
})
|
||||
.map(|slot| SourceSpec::raw(ScalarKind::F64, vec![Target { node: RESAMPLE, slot }]))
|
||||
.collect();
|
||||
|
||||
let h = Harness::bootstrap(FlatGraph { nodes, signatures, sources, edges })
|
||||
|
||||
@@ -47,14 +47,11 @@ fn run_sample_over(prices: Vec<(Timestamp, Scalar)>) -> RunReport {
|
||||
f64_recorder_sig(),
|
||||
f64_recorder_sig(),
|
||||
],
|
||||
sources: vec![SourceSpec {
|
||||
kind: ScalarKind::F64,
|
||||
targets: vec![
|
||||
sources: vec![SourceSpec::raw(ScalarKind::F64, vec![
|
||||
Target { node: 0, slot: 0 },
|
||||
Target { node: 1, slot: 0 },
|
||||
Target { node: 4, slot: 1 }, // price into the broker
|
||||
],
|
||||
}],
|
||||
])],
|
||||
edges: vec![
|
||||
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
|
||||
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
||||
|
||||
@@ -53,14 +53,11 @@ fn run_sample(window: (Timestamp, Timestamp), source: Box<dyn Source>) -> RunRep
|
||||
f64_recorder_sig(),
|
||||
f64_recorder_sig(),
|
||||
],
|
||||
sources: vec![SourceSpec {
|
||||
kind: ScalarKind::F64,
|
||||
targets: vec![
|
||||
sources: vec![SourceSpec::raw(ScalarKind::F64, vec![
|
||||
Target { node: 0, slot: 0 },
|
||||
Target { node: 1, slot: 0 },
|
||||
Target { node: 4, slot: 1 },
|
||||
],
|
||||
}],
|
||||
])],
|
||||
edges: vec![
|
||||
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
|
||||
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
||||
|
||||
Reference in New Issue
Block a user