audit(0070): cycle close — lift finalize lifecycle to ledger C8, drop ephemera

Cycle 0070 (streaming sink reductions, #138) close. Durable rationale lifted to
the ledger: C8 now records the end-of-stream `finalize` lifecycle (the second node
phase beside `eval`), its `Harness::run` topo-order epilogue, and its C1/C7/C8
reconciliation — with the open `Recorder`->`Probe` / per-cycle-allocation question
(#77) cross-referenced, since `finalize` is the "non-channel exit" that issue's
accumulate-then-read option named as missing. The per-cycle spec and plan are
git-rm'd (ephemeral, valid only for their cycle).

Audit (architect) found no invariant breakage; noted debt, left as-is: the
two-mode `stage1_r_graph` (reduce vs raw sink wiring) rests on the CLI-seam
equivalence test rather than structural unification, and `SeriesReducer.last_ts`
carries the last *warm* cycle's ts (latent only if `--trace` ever folds).

refs #138
This commit is contained in:
2026-06-25 11:29:51 +02:00
parent eb8653488f
commit ffd18b98ea
3 changed files with 20 additions and 1366 deletions
+20
View File
@@ -275,6 +275,26 @@ out-of-graph side effect. In-graph routing stays engine-owned data (the edge tab
of the graph is the node's own side effect — and that boundary is the
determinism / graph-as-data boundary (C1/C7).
**Refinement (cycle 0070 — end-of-stream `finalize` lifecycle, 2026-06-25).** The
node contract gains a second lifecycle phase beside `eval`: `finalize(&mut self)`,
a **default-no-op** trait method the engine calls **once per node, in topological
order, after the source loop drains** (the `Harness::run` epilogue). It lets a sink
**fold online** — accumulate per `eval` into O(trades)/O(1) owned state and flush
one compact summary at stream end — instead of pushing a record every fired cycle
into an unbounded channel that buffers O(cycles) rows until the run ends (the
retention BLOCKER #138 hit: a full-history Stage-1 sweep held ~2 GiB/member over
~5.5M one-minute bars). C1/C7/C8 are preserved: `finalize` runs once *after* the
deterministic event loop, adding no within-sim concurrency (C1); a folding sink
holds only owned accumulator state, no interior mutability (C7); it still declares
`output: vec![]` and its flush is the same out-of-graph side effect (C8) — one
summary row, not a per-cycle stream. Realized by `GatedRecorder` (emits only gated
rows + the genuine final row) and `SeriesReducer` (folds one column, emits one
summary row), aura-std siblings of the per-cycle `Recorder`, which survives for the
live / `--trace` / test-tap path. The recording sink's *own* per-cycle allocation
(the `Recorder``Probe` rename + its accumulate-vs-stream choice, #77) stays open;
`finalize` is the "non-channel exit from the graph" that issue's
accumulate-then-read option named as missing.
**Refinement (Construction-layer milestone — render labels, 2026-06-05).** A node
additionally exposes `label() -> String`, a **single-line, non-load-bearing**
render symbol: a default trait method the run loop never calls (wiring is by