Files
Aura/fieldtests/cycle-0006-substrate/c0006_5_reject_mismatch.rs
T
Brummel 0bf15cb069 fieldtest: cycle-0006 sink recording — 5 examples, 6 findings
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>
2026-06-04 15:36:50 +02:00

93 lines
2.8 KiB
Rust

//! Fieldtest c0006 #5 — a mis-wired recording edge is rejected at bootstrap.
//!
//! Supports the "record interior streams" axis from the failure side: a recording
//! node declares typed input slots like any consumer, so an edge whose producer
//! field kind mismatches the slot kind is rejected at bootstrap (the existing
//! per-field 0005 check; recording adds no new hole). Correct behaviour here is
//! REJECTION — this fixture asserts the rejection happens with the documented
//! error, before any data flows. Mirrors the 0006 spec's must-fail fixture.
use std::sync::mpsc;
use aura_core::{Ctx, FieldSpec, Firing, InputSpec, Node, NodeSchema, Scalar, ScalarKind, Timestamp};
use aura_engine::{BootstrapError, Edge, Harness, SourceSpec, Target};
/// A producer that emits one i64 field.
struct I64Source {
out: [Scalar; 1],
}
impl Node for I64Source {
fn schema(&self) -> NodeSchema {
NodeSchema {
inputs: vec![InputSpec {
kind: ScalarKind::I64,
lookback: 1,
firing: Firing::Any,
}],
output: vec![FieldSpec { name: "v", kind: ScalarKind::I64 }],
}
}
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Scalar]> {
let w = ctx.i64_in(0);
if w.is_empty() {
return None;
}
self.out[0] = Scalar::I64(w[0]);
Some(&self.out)
}
}
/// A recorder declaring an f64 input slot.
struct Recorder {
tx: mpsc::Sender<(Timestamp, f64)>,
}
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 main() {
let (tx, _rx) = mpsc::channel();
// Bind the i64 producer field into the f64 recorder slot -> must be rejected.
let err = Harness::bootstrap(
vec![
Box::new(I64Source { out: [Scalar::I64(0)] }),
Box::new(Recorder { tx }),
],
vec![SourceSpec {
kind: ScalarKind::I64,
targets: vec![Target { node: 0, slot: 0 }],
}],
vec![Edge { from: 0, to: 1, slot: 0, from_field: 0 }],
)
.unwrap_err();
println!("err = {err:?}");
assert_eq!(
err,
BootstrapError::KindMismatch {
producer: ScalarKind::I64,
consumer: ScalarKind::F64,
},
"kind-mismatched recorder edge must be rejected at bootstrap"
);
println!("c0006_5 OK: mis-wired recording edge rejected with KindMismatch");
}