diff --git a/crates/aura-engine/src/harness.rs b/crates/aura-engine/src/harness.rs index eede29f..5e0b93b 100644 --- a/crates/aura-engine/src/harness.rs +++ b/crates/aura-engine/src/harness.rs @@ -1424,6 +1424,67 @@ mod tests { ); } + #[test] + fn a_producer_with_no_out_edge_runs_inertly_identical_to_its_removal() { + // CURRENT-BEHAVIOUR PIN (#282 substrate): a producer node with a + // NON-EMPTY output and ZERO out-edges compiles, bootstraps, and runs — + // its per-cycle Some(row) is simply discarded, disturbing nothing. This + // is the exact shape of an *unbound declared tap*: the tap mechanism + // will let such a producer be declared and left un-recorded in a sweep, + // and this pins that the engine already tolerates it (no DCE needed for + // correctness — check_ports_connected gates only input slots, and the + // Kahn sort emits a no-out-edge node as a valid sink). The proof is + // observable identity: the run's drained output is byte-for-byte the same + // with the dangling producer present as with it removed. + let prices = f64_stream(&[(1, 10.0), (2, 20.0), (3, 30.0)]); + + // WITH a dangling producer: source fans out to Sma(1) [node 0, a real + // producer that returns Some every warm cycle, wired to NOTHING] and to + // a Recorder [node 1] fed directly by the source. + let (tx, rx) = mpsc::channel(); + let mut h_with = boot( + vec![ + Box::new(Sma::new(1)), + Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)), + ], + vec![Sma::builder().schema().clone(), recorder_sig(&[ScalarKind::F64], Firing::Any)], + vec![SourceSpec::raw( + ScalarKind::F64, + vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }], + )], + vec![], // NO edge out of node 0 — the producer dangles + ) + .expect("a no-out-edge producer is a valid graph"); + h_with.run(vec![Box::new(VecSource::new(prices.clone()))]); + let with_dangling: Vec<(Timestamp, Vec)> = rx.try_iter().collect(); + + // WITHOUT it: just the Recorder, fed by the same source. + let (tx2, rx2) = mpsc::channel(); + let mut h_without = boot( + vec![Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx2))], + vec![recorder_sig(&[ScalarKind::F64], Firing::Any)], + vec![SourceSpec::raw(ScalarKind::F64, vec![Target { node: 0, slot: 0 }])], + vec![], + ) + .expect("valid"); + h_without.run(vec![Box::new(VecSource::new(prices))]); + let without_dangling: Vec<(Timestamp, Vec)> = rx2.try_iter().collect(); + + assert_eq!( + with_dangling, + vec![ + (Timestamp(1), vec![Scalar::f64(10.0)]), + (Timestamp(2), vec![Scalar::f64(20.0)]), + (Timestamp(3), vec![Scalar::f64(30.0)]), + ], + "the drained output is the source series; the dangling producer changes nothing" + ); + assert_eq!( + with_dangling, without_dangling, + "a producer wired to nothing is observably inert — identical to its removal" + ); + } + #[test] fn mixed_a_and_b_or_combine_on_one_node() { // MixedSum @0 (in0,in1 barrier group 0; in2 as-of); node 1 = Recorder taps it.