0bf15cb069
First fieldtest of the project. A standalone downstream-consumer crate
(fieldtests/cycle-0006-substrate/) path-depends on the engine crates and
exercises the post-0006 substrate from the public interface only (rustdoc +
specs + ledger, never crates/ source):
1 custom recording node via the Node contract + Ctx::now()
2 fan-out/fan-in DAG, 3-arg bootstrap, run() -> ()
3 two interior streams recorded from one run (the cycle headline)
4 byte-identical recorded output across two runs (C1 determinism)
5 mis-wired recording edge rejected (KindMismatch) at bootstrap
Findings: 3 working (carry-on), 2 friction, 1 spec_gap.
- friction: a nested standalone consumer crate fights the workspace
resolver (needs an empty [workspace] table — Cargo's own hint).
- friction: recorder boilerplate is hand-rewritten per author (C9
deliberately ships no library sink).
- spec_gap: the public surface never states output: vec![] is THE sink
declaration, nor defines a Some return paired with empty output.
Spec at docs/specs/fieldtest-0006-substrate.md feeds the 0007 plan.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
106 lines
3.2 KiB
Rust
106 lines
3.2 KiB
Rust
//! Fieldtest c0006 #4 — recorded output is byte-identical across two runs (C1).
|
|
//!
|
|
//! Axis: "Then verify determinism: same input => byte-identical recorded output
|
|
//! across two runs."
|
|
//!
|
|
//! Two fresh harnesses (each its own channel), identical input. Drain both
|
|
//! recorded streams and assert bit-identical. A recorded side effect cannot feed
|
|
//! back into the graph, so determinism must hold (the cycle's own acceptance
|
|
//! claim). To make "byte-identical" concrete we compare the bit patterns of the
|
|
//! recorded f64s, not just `==` (which treats NaN specially), and the timestamps.
|
|
|
|
use std::sync::mpsc::{self, Sender};
|
|
|
|
use aura_core::{Ctx, Firing, InputSpec, Node, NodeSchema, Scalar, ScalarKind, Timestamp};
|
|
use aura_engine::{Edge, Harness, SourceSpec, Target};
|
|
use aura_std::{Sma, Sub};
|
|
|
|
struct Recorder {
|
|
tx: Sender<(Timestamp, f64)>,
|
|
}
|
|
impl Recorder {
|
|
fn new(tx: Sender<(Timestamp, f64)>) -> Self {
|
|
Self { tx }
|
|
}
|
|
}
|
|
impl Node for Recorder {
|
|
fn schema(&self) -> NodeSchema {
|
|
NodeSchema {
|
|
inputs: vec![InputSpec {
|
|
kind: ScalarKind::F64,
|
|
lookback: 1,
|
|
firing: Firing::Any,
|
|
}],
|
|
output: vec![],
|
|
}
|
|
}
|
|
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Scalar]> {
|
|
let w = ctx.f64_in(0);
|
|
if w.is_empty() {
|
|
return None;
|
|
}
|
|
let _ = self.tx.send((ctx.now(), w[0]));
|
|
None
|
|
}
|
|
}
|
|
|
|
fn f64_stream(pairs: &[(i64, f64)]) -> Vec<(Timestamp, Scalar)> {
|
|
pairs
|
|
.iter()
|
|
.map(|&(t, v)| (Timestamp(t), Scalar::F64(v)))
|
|
.collect()
|
|
}
|
|
|
|
fn build_and_run(tx: Sender<(Timestamp, f64)>) {
|
|
let mut h = Harness::bootstrap(
|
|
vec![
|
|
Box::new(Sma::new(2)),
|
|
Box::new(Sma::new(4)),
|
|
Box::new(Sub::new()),
|
|
Box::new(Recorder::new(tx)),
|
|
],
|
|
vec![SourceSpec {
|
|
kind: ScalarKind::F64,
|
|
targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }],
|
|
}],
|
|
vec![
|
|
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
|
|
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
|
Edge { from: 2, to: 3, slot: 0, from_field: 0 },
|
|
],
|
|
)
|
|
.expect("valid DAG");
|
|
|
|
h.run(vec![f64_stream(&[
|
|
(1, 10.5),
|
|
(2, 12.25),
|
|
(3, 9.75),
|
|
(4, 16.0),
|
|
(5, 18.125),
|
|
(6, 7.0),
|
|
(7, 21.5),
|
|
])]);
|
|
}
|
|
|
|
fn main() {
|
|
let (tx1, rx1) = mpsc::channel();
|
|
build_and_run(tx1);
|
|
let run1: Vec<(Timestamp, f64)> = rx1.try_iter().collect();
|
|
|
|
let (tx2, rx2) = mpsc::channel();
|
|
build_and_run(tx2);
|
|
let run2: Vec<(Timestamp, f64)> = rx2.try_iter().collect();
|
|
|
|
println!("run1 = {run1:?}");
|
|
println!("run2 = {run2:?}");
|
|
|
|
// bit-exact comparison (f64::to_bits) — true byte-identity, not float ==.
|
|
assert_eq!(run1.len(), run2.len(), "same number of recorded events");
|
|
for (a, b) in run1.iter().zip(run2.iter()) {
|
|
assert_eq!(a.0, b.0, "timestamps byte-identical");
|
|
assert_eq!(a.1.to_bits(), b.1.to_bits(), "values byte-identical");
|
|
}
|
|
assert!(!run1.is_empty(), "the run actually recorded something");
|
|
println!("c0006_4 OK: recorded output byte-identical across two runs (C1)");
|
|
}
|