feat(0070): streaming sink reductions — finalize hook + folding sinks (#138)
M3 machinery for BLOCKER #138: sink reductions can now fold online instead of buffering every per-cycle row to an unbounded channel. - aura-core: additive `Node::finalize` end-of-stream hook (default no-op) + `SeriesFold` online accumulator (last / max_drawdown / sign_flips). - aura-engine: `Harness::run` calls `finalize` once per node in topo order after the source loop; `summarize` refactored to drive `SeriesFold` (byte-identical), the now-dead `sign0` removed. - aura-std: `GatedRecorder` (emits only gated rows + the final row on finalize) and `SeriesReducer` (folds one f64 series, emits one summary row on finalize), both emitting the `Recorder` channel type — no aura-engine dependency. - Integration tests: folded reductions are byte-for-byte equal to the raw-recorder path; the R-reduction's peak memory is O(trades), independent of cycle count. Ratified two off-plan fixes the implementer made in the tests (both plan defects, not code defects): the GatedRecorder correctly flushes the final non-gated row on finalize, so the retained count is K_TRADES + 1; and the heterogeneous PM record needs kind-correct zero scalars (a blanket f64 zero is rejected by the kind-typed AnyColumn::push). Wiring these reducers into the stage1-r sweep (the actual O(cycles)->O(trades) win) follows next. refs #138
This commit is contained in:
@@ -280,6 +280,12 @@ pub trait Node {
|
||||
fn label(&self) -> String {
|
||||
"node".to_string()
|
||||
}
|
||||
|
||||
/// End-of-stream hook: `Harness::run` calls it once per node, in topological
|
||||
/// order, after the source loop drains. A folding sink overrides it to flush
|
||||
/// its accumulated summary; the default is a no-op, so existing nodes are
|
||||
/// unaffected. Takes `&mut self` like `eval`, so `Node` stays object-safe.
|
||||
fn finalize(&mut self) {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -315,6 +321,14 @@ mod tests {
|
||||
assert_eq!(Bare.label(), "node");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_finalize_is_a_callable_noop() {
|
||||
// a node that does not override finalize uses the default no-op; calling
|
||||
// it compiles and does nothing (additive, non-breaking for existing nodes).
|
||||
let mut b = Bare;
|
||||
b.finalize();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn primitive_builder_label_is_the_bare_type() {
|
||||
// param-generic: the label is just the node type, no knob suffix — the
|
||||
|
||||
Reference in New Issue
Block a user