audit: cycle 0003 tidy — rename Sim -> Harness (glossary alignment)

Architect drift review over 71c8f6e..dd5c3fa (deterministic-sim-loop).
Invariants hold: C1 (single-threaded, bit-identical re-run, alloc-free per-cycle
ring path), C7 (AnyColumn type-erased edges, Box<dyn Node> is the node object not
a dyn-Any payload, kinds checked once at bootstrap), C8/C9 (DAG wired, Kahn
rejects cycles, None forwards nothing = sample-and-hold seed). Column::run_count
already exists, so the C5 freshness primitive lives at the substrate — the
deferral is honest, not a hole.

Resolved [high]: the runtime type was named `Sim`, colliding with the glossary's
load-bearing distinction — `sim` is the disjoint *executable unit / parallelism*
framing, whereas the thing built here (a closed root graph of source + nodes +
observe under a clock) is by C20 / the glossary a **harness**. Renamed
`Sim` -> `Harness`, `sim.rs` -> `harness.rs`, and the module/exports/doc to match,
before any other code takes a dependency on the wrong term. Gates re-run green
(26 tests, clippy -D warnings, purity grep).

Resolved [med] (glossary gap): added an `edge` entry anchoring the new wiring
vocabulary (Edge / source target) to the glossary — record-reality per the boss
glossary-write authority.

Carry-on with forward notes (next-cycle owners, not fixes now):
- [med] C4 monotonic cycle_id has no realization in the runtime type yet (the
  cycle clock is the record iteration; the explicit counter's first reader is
  freshness). Cycle 0004 owns materializing it together with C5.
- [low] Sub declares lookback 1; once depth>1 join nodes exist, add a test
  guarding C8's "a node never reads beyond its declared lookback".

Regression gate: profile regression list empty — architect is the sole gate,
now clean. Next direction: freshness-gated recompute + a second source (C5/C3).
This commit is contained in:
2026-06-03 13:07:43 +02:00
parent dd5c3fad96
commit 02fe89176f
3 changed files with 30 additions and 24 deletions
@@ -1,10 +1,11 @@
//! The deterministic single-source sim loop. A `Sim` is a bootstrapped, frozen
//! root graph — a flat node array plus an index edge table, topologically ordered
//! — driven cycle by cycle by one source. It is the flat, monomorphized sharpening
//! of RustAst's reference-counted, interior-mutable observer push graph: no
//! reference counting, no interior mutability, no per-cycle allocation (C1/C7).
//! Each node owns its input
//! columns (the cycle-0002 shape), so `Ctx` is unchanged; a producer's `eval`
//! The harness — the closed root graph that runs (glossary: `harness`, C20) —
//! and its deterministic single-source run loop. A `Harness` is a bootstrapped,
//! frozen root graph: a flat node array plus an index edge table, topologically
//! ordered, driven cycle by cycle by one source. It is the flat, monomorphized
//! sharpening of RustAst's reference-counted, interior-mutable observer push
//! graph: no reference counting, no interior mutability, no per-cycle allocation
//! (C1/C7). Each node owns its input columns (the cycle-0002 shape), so `Ctx` is
//! unchanged; a producer's `eval`
//! output is forwarded into its consumers' input columns, and a `None` forwards
//! nothing (the structural seed of sample-and-hold, realized with freshness in a
//! later cycle).
@@ -45,7 +46,7 @@ struct NodeBox {
}
/// A bootstrapped, frozen root graph instance plus its deterministic run loop.
pub struct Sim {
pub struct Harness {
nodes: Vec<NodeBox>,
topo: Vec<usize>,
out_edges: Vec<Vec<Edge>>,
@@ -57,9 +58,9 @@ pub struct Sim {
// cannot derive it. This summary form is enough for `Result::unwrap_err` (which
// formats the `Ok` arm on a failed bootstrap-rejection assertion) without
// printing node internals.
impl core::fmt::Debug for Sim {
impl core::fmt::Debug for Harness {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Sim")
f.debug_struct("Harness")
.field("nodes", &self.nodes.len())
.field("topo", &self.topo)
.field("out_edges", &self.out_edges)
@@ -69,7 +70,7 @@ impl core::fmt::Debug for Sim {
}
}
impl Sim {
impl Harness {
/// Bind nodes + wiring into a frozen, runnable graph. Sizes each node's input
/// columns from its `schema`, kind-checks every edge and source target, and
/// topologically orders the nodes (Kahn), rejecting any directed cycle.
@@ -79,7 +80,7 @@ impl Sim {
edges: Vec<Edge>,
source_kind: ScalarKind,
observe: usize,
) -> Result<Sim, BootstrapError> {
) -> Result<Harness, BootstrapError> {
let n = nodes.len();
if observe >= n {
return Err(BootstrapError::BadIndex);
@@ -148,7 +149,7 @@ impl Sim {
return Err(BootstrapError::Cycle);
}
Ok(Sim {
Ok(Harness {
nodes: boxes,
topo,
out_edges,
@@ -165,7 +166,7 @@ impl Sim {
) -> Vec<Option<Scalar>> {
// disjoint field borrows so the topo walk can read `topo`/`out_edges`
// while mutating `nodes`
let Sim { nodes, topo, out_edges, source_targets, observe } = self;
let Harness { nodes, topo, out_edges, source_targets, observe } = self;
let observe = *observe;
let mut out = Vec::new();
@@ -217,7 +218,7 @@ mod tests {
fn chain_source_sma_runs() {
// node 0 = SMA(3); source -> SMA(3).in0; observe node 0
let nodes: Vec<Box<dyn Node>> = vec![Box::new(Sma::new(3))];
let mut sim = Sim::bootstrap(
let mut sim = Harness::bootstrap(
nodes,
vec![Target { node: 0, slot: 0 }],
vec![],
@@ -241,10 +242,10 @@ mod tests {
#[test]
fn fan_out_join_dag_runs_deterministically() {
// 0 = SMA(2), 1 = SMA(4), 2 = Sub; source fans into both SMAs; SMAs join into Sub
let build = || -> Sim {
let build = || -> Harness {
let nodes: Vec<Box<dyn Node>> =
vec![Box::new(Sma::new(2)), Box::new(Sma::new(4)), Box::new(Sub::new())];
Sim::bootstrap(
Harness::bootstrap(
nodes,
vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
vec![Edge { from: 0, to: 2, slot: 0 }, Edge { from: 1, to: 2, slot: 1 }],
@@ -281,7 +282,7 @@ mod tests {
fn bootstrap_rejects_a_cycle() {
// two SMA(1) nodes wired a -> b -> a
let nodes: Vec<Box<dyn Node>> = vec![Box::new(Sma::new(1)), Box::new(Sma::new(1))];
let err = Sim::bootstrap(
let err = Harness::bootstrap(
nodes,
vec![],
vec![Edge { from: 0, to: 1, slot: 0 }, Edge { from: 1, to: 0, slot: 0 }],
@@ -296,7 +297,7 @@ mod tests {
fn bootstrap_rejects_a_kind_mismatch() {
// SMA(1) declares an f64 input; feeding an i64 source mismatches
let nodes: Vec<Box<dyn Node>> = vec![Box::new(Sma::new(1))];
let err = Sim::bootstrap(
let err = Harness::bootstrap(
nodes,
vec![Target { node: 0, slot: 0 }],
vec![],
@@ -314,7 +315,7 @@ mod tests {
fn bootstrap_rejects_a_bad_index() {
// observe node 5 does not exist
let nodes: Vec<Box<dyn Node>> = vec![Box::new(Sma::new(1))];
let err = Sim::bootstrap(
let err = Harness::bootstrap(
nodes,
vec![Target { node: 0, slot: 0 }],
vec![],
+5 -4
View File
@@ -1,8 +1,9 @@
//! `aura-engine` — the headless, UI-agnostic reactive SoA engine.
//!
//! Delivered in cycle 0003 — the deterministic single-source sim loop:
//! Delivered in cycle 0003 — the harness and its deterministic single-source
//! run loop:
//!
//! - [`Sim`] — a bootstrapped, frozen root graph (a flat node array + an index
//! - [`Harness`] — the closed root graph that runs (a flat node array + an index
//! edge table, topologically ordered) and its deterministic `run` loop: one
//! source driven through a wired DAG of nodes, cycle by cycle, each output
//! forwarded into its consumers' input columns (C1/C4/C7/C8/C9).
@@ -18,6 +19,6 @@
//!
//! Visualization is never here: it is a downstream consumer node on the streams.
mod sim;
mod harness;
pub use sim::{BootstrapError, Edge, Sim, Target};
pub use harness::{BootstrapError, Edge, Harness, Target};
+4
View File
@@ -51,6 +51,10 @@ A node that wires a sub-graph and exposes one output (a combined signal, or a st
**Avoid:** —
One data-driven clock step: one input record = one cycle, advanced in global timestamp order with a monotonic `cycle_id`. (In the pipeline-process sense "cycle" also names one milestone round; the engine sense is this clock step.)
### edge
**Avoid:** —
A directed wiring link in a harness that forwards one node's `eval` output into a consumer node's input slot (the engine's `Edge`); the source-side variant binding the source value into an input slot is a **source target** (`Target`). Edges define the DAG the bootstrap topologically orders; mismatched scalar kinds across an edge are rejected at bootstrap.
### equity stream
**Avoid:** —
A broker node's output: the marked value of open positions over time — synthetic pips for the sim-optimal broker, currency for realistic brokers. An equity *curve* is explicitly not a strategy's output (the position table is).