docs(C8): state output:vec![] as the sink declaration + eval return-width contract
Closes the spec_gap from the cycle-0006 fieldtest
(docs/specs/fieldtest-0006-substrate.md): the public surface never stated that
an empty `output` is THE pure-consumer (sink) declaration, nor defined what a
`Some` return paired with an empty output means.
The behaviour was already principled and defended in the engine — this only
documents it (no code change):
- `output: vec![]` is the pure-consumer declaration; there is no separate
Sink type/trait/marker.
- `eval` must return a row whose width equals `schema.output.len()`, so a pure
consumer returns `None` or a zero-width `Some(&[])`; the run loop
debug-asserts the width (harness.rs).
- field-wise wiring resolves `Edge::from_field` against the producer's output
at bootstrap, so no edge can bind a field of a zero-output node (it fails
with `BadIndex`) — a sink is structurally unwireable as an in-graph
producer; its only output is the out-of-graph side effect.
Ledger C8 gains an "Encoding & return contract" clause; aura-core node.rs
rustdoc (module, NodeSchema, Node trait) updated from the pre-0006 "1..K,
producer or transformer" wording to the three-role (producer/consumer/both)
contract.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
//! The node contract (C8): the interface every node implements. A node declares
|
||||
//! its inputs (each with its scalar kind, lookback depth, and firing policy, C6)
|
||||
//! and its output record (1..K base columns, C7) via `schema`, and computes one
|
||||
//! cycle's row via `eval`.
|
||||
//! and its output record (0..K base columns, C7; an empty output declares a
|
||||
//! pure consumer / sink role, C8) via `schema`, and computes one cycle's row
|
||||
//! via `eval`.
|
||||
//! Tunable params (C12/C19) are deliberately not part of the schema yet — see
|
||||
//! spec 0002's "Out of scope".
|
||||
|
||||
@@ -42,18 +43,25 @@ pub struct FieldSpec {
|
||||
|
||||
/// A node's declared interface: its inputs (in order) and its output record — an
|
||||
/// ordered list of named base columns; length 1 is a scalar (the degenerate
|
||||
/// case). Built once at wiring, never on the hot path — the `Vec`s are fine here.
|
||||
/// case), and an **empty** `output` (`vec![]`) declares a **pure consumer**
|
||||
/// (sink role, C8) — a node with no output port. Built once at wiring, never on
|
||||
/// the hot path — the `Vec`s are fine here.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct NodeSchema {
|
||||
pub inputs: Vec<InputSpec>,
|
||||
pub output: Vec<FieldSpec>,
|
||||
}
|
||||
|
||||
/// The universal composable dataflow unit (C8): one output port carrying a record
|
||||
/// of 1..K base columns, a producer or transformer. `schema` declares the
|
||||
/// The universal composable dataflow unit (C8): a **producer, consumer, or both**.
|
||||
/// A producer/transformer exposes one output port carrying a record of 1..K base
|
||||
/// columns; a **pure consumer (sink)** declares `output: vec![]` and records via
|
||||
/// an out-of-graph side effect in `eval` (cycle 0006). `schema` declares the
|
||||
/// interface; `eval` computes one cycle's row, returning a borrowed slice into a
|
||||
/// buffer the node owns (`None` = filter / not-yet-warmed-up). `&mut self` because
|
||||
/// a node may keep its own derived state (and its output buffer).
|
||||
/// buffer the node owns. `None` = filter / not-yet-warmed-up / pure sink; a
|
||||
/// returned `Some(row)` must satisfy `row.len() == schema().output.len()` (so a
|
||||
/// pure consumer returns `None` or an empty `Some(&[])`) — the engine debug-asserts
|
||||
/// the width and forwards the row field-wise along the node's out-edges. `&mut self`
|
||||
/// because a node may keep its own derived state (and its output buffer).
|
||||
pub trait Node {
|
||||
fn schema(&self) -> NodeSchema;
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Scalar]>;
|
||||
|
||||
Reference in New Issue
Block a user