docs(ledger, glossary): tap-subscribers cycle tidy — C8/C27/C28 lifts, tap subscription vocabulary

Cycle-close audit for the #283/#77 tap-subscribers cycle
(09994b8..6e3f394). Architect review: code contract-clean; what
holds — C1/C7/C8 (empty-output caller-built consumers, (Timestamp,
Cell) zero-heap payload, lifecycle hooks inside the sequential loop,
byte-identical twin-run and streamed-vs-legacy byte-equality pins),
C28 (assembly-crate placement of the writer-holding consumer,
c28_layering guard green), C25 (registry growth = new Rust entry,
data-expressible Named form, roster-enumerating refusals). All drift
items were doc lifts, resolved in this commit:

- C8: the lifecycle is now a symmetric pair — initialize() as
  finalize's start-of-stream mirror (infallible by signature;
  acquire-in-initialize, degrade-to-inert, surface-once-at-finalize);
  history sidecar records the refinement and the #77 resolution the
  old Deferred paragraph still carried as open.
- C27: Guarantee and Current state now describe the shipped model —
  tap plan (Named{label, params} | Live) over the layered fold
  registry, BOTH entry points (run_signal_r AND run_measurement) on
  the one shared wiring pair, record streaming at constant memory.
- C28: aura-runner now defines one graph node of its own (the
  in-graph record consumer — exactly the graph-meets-persistence
  assembly position); the #297 refusal-site count grows ~20 → ~24
  (the four tap-plan refusals, already typed before the exit).
- glossary §tap: the subscription/fold-registry sense added; Avoid
  line steers probe/monitor/scope away from the observation slot.

Bench (report-only, loadavg 5.8 > 2 on the box — read with care),
all fingerprints OK, no regression:
  engine_throughput    bars_per_s 14005262   wall_s 0.714s   Δ bars_per_s +3.0% wall_s -3.0%
  ingest_throughput    bars_per_s 12970670   wall_s 0.387s   Δ bars_per_s +0.5% wall_s -0.5%
  campaign_sweep       cpu_percent 2202.0   peak_rss_mb 92.2   wall_s 1.396s   Δ cpu_percent +1.5% peak_rss_mb -6.1% wall_s -1.5%
  campaign_heavy       cpu_percent 2155.0   peak_rss_mb 94.8   wall_s 5.585s   Δ cpu_percent +1.8% peak_rss_mb -3.5% wall_s -0.4%
  cli_fixed_cost       help_ms 1.5ms   run_ms 3.6ms   Δ help_ms -1.8% run_ms -2.6%
No baseline update (report-only surface; no ratify due).

Correction on 6e3f394's body: "no Probe symbol is introduced
anywhere" — introduced is the operative word; the pre-existing
aura-core test fixture `struct Probe` stays, as the cycle's design
record specified.

Follow-ups on the tracker: #308 (legacy Recorder surface migration),
#307 (binary trace format). Cycle spec/plan working files discarded.

refs #283 refs #77
This commit is contained in:
2026-07-21 23:56:14 +02:00
parent 6e3f394b48
commit bd0c557f16
5 changed files with 75 additions and 23 deletions
+17 -9
View File
@@ -32,8 +32,10 @@ a zero-width `Some(&[])`, and it pushes its record to a destination it holds as
field (a channel, a chart handle). A node may record **and** return a forwarded
output in the same `eval` (the "both" case). `None` = filter / not-yet-warmed-up /
pure sink. Sources are pure producers; sinks are pure consumers. Beside `eval`,
the contract has a second lifecycle phase, `finalize()` — a default-no-op hook the
engine calls once per node, in topological order, after the source loop drains —
the contract has a symmetric lifecycle pair — default-no-op hooks the engine calls
once per node, in topological order: `initialize()` before the first source value,
letting a consumer acquire its run resources (e.g. the file a recording sink
streams into) at stream start, and `finalize()` after the source loop drains,
letting a sink fold online and flush one compact summary at stream end instead of
streaming a row every fired cycle. A node also exposes `label()`, a single-line,
non-load-bearing render symbol (C23).
@@ -55,7 +57,12 @@ boundary is the determinism / graph-as-data boundary (C1/C7). `finalize` gives a
folding sink O(trades)/O(1) owned accumulator state and one summary row, instead of
an unbounded channel that buffers O(cycles) rows until the run ends; it runs once
*after* the deterministic event loop, adding no within-sim concurrency (C1), and
holds only owned state, no interior mutability (C7).
holds only owned state, no interior mutability (C7). `initialize` mirrors it at
stream start (#283): resource acquisition happens inside the same deterministic
sequence, and the hook is infallible by signature — a consumer that fails to
acquire stores the error, degrades to inert, and surfaces the failure once,
terminally, at `finalize` (the run completes; refusal-worthy faults are caught
pre-run, at construction/bind time).
## Current state
@@ -70,7 +77,8 @@ structural axis, C20, never a numeric sweep param).
`trait Node` declares `lookbacks() -> Vec<usize>`, `eval(&mut self, ctx) ->
Option<&[Cell]>`, `label() -> String` (default a placeholder every shipped node
overrides; overrides carry identifying params so `SMA(2)` vs `SMA(4)` disambiguate
in a graph render, #13), and `finalize(&mut self)` (default no-op). There is **no**
in a graph render, #13), and the lifecycle pair `initialize(&mut self)` /
`finalize(&mut self)` (both default no-op). There is **no**
`Node::schema()` — the signature is pre-build data, not a built-node method. The
signature is declared once on the value-empty recipe `PrimitiveBuilder`
(`aura-core/src/node.rs`), whose `schema()` / `params()` are read pre-build by the
@@ -83,9 +91,10 @@ against the producer's `output` at bootstrap. A sink's empty `output` therefore
makes it structurally unwireable as an in-graph producer — no edge can bind a field
of a zero-output node. The run loop (`aura-engine/src/harness.rs`) debug-asserts
`row.len()` equals the declared output width, and that `lookbacks()` arity equals
`signature().inputs.len()`. The end-of-stream `finalize` epilogue is realized in
`Harness::run` (`aura-engine/src/harness.rs`), which calls `finalize()` once per
node in topological order after the source loop drains; `GatedRecorder` and
`signature().inputs.len()`. The lifecycle pair is realized in `Harness::run`
(`aura-engine/src/harness.rs`): an `initialize()` prologue once per node in
topological order before the source loop, and the `finalize()` epilogue after it
drains — each pinned by its own once-per-run mirror test; `GatedRecorder` and
`SeriesReducer` (`aura-std`) are the folding siblings of the per-cycle `Recorder`
(`aura-std/src/recorder.rs`), which survives for the live / `--trace` / test-tap
path.
@@ -124,8 +133,7 @@ untouched), blanked in the identity projection by `strip_debug_symbols`, dissolv
at inline like the name, and surfaced read-only in the graph model and viewer
(C22).
Deferred: the recording sink's own per-cycle allocation — the `Recorder``Probe`
rename and its accumulate-vs-stream choice — stays open (#77). Naming input ports
Deferred: Naming input ports
(`PortSpec.name`) makes swap-prone same-kind slots self-documenting but does not add
a name-consuming wiring validation; a swapped same-kind wiring is still only
kind-checked (#21). The construction op-script vocabulary has no doc-carrying