plan: 0047 cell hot-path carrier
Four tasks for the behaviour-preserving carrier swap: (1) add the branch-free AnyColumn::push_cell + round-trip test (additive); (2) the atomic lib-code flip (Node::eval -> Option<&[Cell]>, 8 aura-std nodes, recorder, harness forward loop) gated by a lib-only build; (3) the cfg(test) migration (fixtures + eval-output assertions per the spec's three-role rule) gated by the full four-gate run; (4) the C7/C8 ledger realization note. refs #74
This commit is contained in:
@@ -0,0 +1,532 @@
|
||||
# Cell as the hot-path value carrier — Implementation Plan
|
||||
|
||||
> **Parent spec:** `docs/specs/0047-cell-hot-path-carrier.md`
|
||||
>
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: use the `implement` skill to run
|
||||
> this plan. Steps use `- [ ]` checkboxes for tracking.
|
||||
|
||||
**Goal:** Narrow the hot-path value carrier from `Scalar` to the tag-free `Cell`
|
||||
(`Node::eval -> Option<&[Cell]>`, every node out-buffer, the inter-node forward),
|
||||
keeping `Scalar` on the self-describing dynamic boundaries (params, `get`, source
|
||||
ingestion). Behaviour-preserving (C1).
|
||||
|
||||
**Architecture:** The `Node::eval` trait return type is the single atomic
|
||||
compile cut: flipping it breaks all 21 `impl Node` bodies at once, so the
|
||||
lib-code flip (trait + 8 aura-std nodes + recorder + harness forward loop) lands
|
||||
as one task gated by a **lib-only** build; the `#[cfg(test)]` fixtures and the
|
||||
eval-output assertions (which only compile under `--all-targets`) migrate in a
|
||||
second task gated by the full four-gate run. A new branch-free `AnyColumn::push_cell`
|
||||
(added first, additively) is the infallible inter-node push; the fallible
|
||||
`push(Scalar)` and `get -> Option<Scalar>` stay for ingestion / type-erased reads.
|
||||
|
||||
**Tech Stack:** `aura-core` (`node.rs`, `any.rs`), `aura-std` (8 nodes),
|
||||
`aura-engine` (`harness.rs` forward loop + fixtures, `blueprint.rs`/`graph_model.rs`
|
||||
fixtures), the design ledger.
|
||||
|
||||
---
|
||||
|
||||
## Files this plan creates or modifies
|
||||
|
||||
- Modify: `crates/aura-core/src/node.rs:13,264,286,410` — `use` adds `Cell`;
|
||||
`Node::eval` trait sig → `Option<&[Cell]>`; `Bare`/`Probe` eval sigs.
|
||||
- Modify: `crates/aura-core/src/any.rs:7,62-92` — add `use crate::cell::Cell;`;
|
||||
add `push_cell(Cell)`; add a `push_cell` round-trip test in the `tests` mod.
|
||||
- Modify: `crates/aura-std/src/add.rs:5,20,26,58,64,92`
|
||||
- Modify: `crates/aura-std/src/sub.rs:6,11,17,49,55,83`
|
||||
- Modify: `crates/aura-std/src/sma.rs:6-9,14,21,45,54,86,97,100`
|
||||
- Modify: `crates/aura-std/src/ema.rs:20-23,37,52,79,99,131,144,147`
|
||||
- Modify: `crates/aura-std/src/exposure.rs:6-9,15,22,46,51,81`
|
||||
- Modify: `crates/aura-std/src/lincomb.rs:9-12,30,40,68,77,105,118,136,168,183`
|
||||
- Modify: `crates/aura-std/src/sim_broker.rs:7,41,54,82,94,124`
|
||||
- Modify: `crates/aura-std/src/recorder.rs:10,60` — `use` adds `Cell` (the return
|
||||
type names it); eval sig → `Option<&[Cell]>` (body still returns `None`).
|
||||
- Modify: `crates/aura-engine/src/harness.rs:10,24,373,416,426-428` (forward path)
|
||||
+ fixtures `AsOfSum`/`BarrierSum`/`MixedSum`/`Ohlcv`/`TwoField`/`TapForward`
|
||||
(out-buffers, eval sigs, assignments) + their ctor literals
|
||||
(`:898,944,993,1044,1156,1209,1255,1380,1307,1614,1475,1781`).
|
||||
- Modify: `crates/aura-engine/src/blueprint.rs:816` (test `use` adds `Cell`) +
|
||||
fixtures `Join2`/`Pass1`/`SinkF64`/`SinkI64` (`:1057,1063,1069,1076,1082,1087,1098,1110`)
|
||||
+ ctor literals (`:1119,1127,2283`).
|
||||
- Modify: `crates/aura-engine/src/graph_model.rs:274-276,286` (test `use` adds
|
||||
`Cell`; `Bare` eval sig).
|
||||
- Modify: `docs/design/INDEX.md` (C7 realization note `:195-210`; C8 prose `:221,242`).
|
||||
- KEEP (no change — param plane / type-erased read / ingestion / channel rows):
|
||||
`node.rs` param path, `any.rs::push`/`get`, `sweep.rs`/`mc.rs`/`walkforward.rs`
|
||||
param args + `scalar_as_f64`, `aura-cli/src/main.rs` `run_oos`/`scalar_as_param_f64`,
|
||||
`report.rs::f64_field`, `recorder.rs` channel row, `graph_model.rs::scalar_str`,
|
||||
`blueprint.rs::compile_with_params`, `test_fixtures.rs`, `builder.rs`,
|
||||
`aura-ingest`, `aura-registry`, `ctx.rs`, `column.rs`, `error.rs`, `scalar.rs`,
|
||||
`cell.rs`.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Add `AnyColumn::push_cell` + round-trip test (additive)
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aura-core/src/any.rs`
|
||||
|
||||
This task is additive: `push_cell` is new `pub` code, the test exercises it, and
|
||||
nothing existing changes — the workspace stays green throughout.
|
||||
|
||||
- [ ] **Step 1: Import `Cell` in any.rs**
|
||||
|
||||
In `crates/aura-core/src/any.rs`, after the existing import line
|
||||
`use crate::error::KindMismatch;` (line ~6), and the line
|
||||
`use crate::scalar::{Scalar, ScalarKind, Timestamp};` (line 7), add:
|
||||
|
||||
```rust
|
||||
use crate::cell::Cell;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add the `push_cell` method**
|
||||
|
||||
In `impl AnyColumn`, immediately after the existing `push` method (ends ~line 82),
|
||||
add:
|
||||
|
||||
```rust
|
||||
/// Branch-free, infallible hot-path push of a bare [`Cell`] into this column's
|
||||
/// concrete `Column<T>`, reinterpreting the cell by *this* column's kind. The
|
||||
/// inter-node forward uses this: the edge's `from_field`→slot kind match is
|
||||
/// verified once at bootstrap (C7), so no per-value kind check is needed here
|
||||
/// — unlike [`push`](Self::push), which keeps the kind guard for the
|
||||
/// ingestion boundary.
|
||||
pub fn push_cell(&mut self, c: Cell) {
|
||||
match self {
|
||||
AnyColumn::I64(col) => col.push(c.i64()),
|
||||
AnyColumn::F64(col) => col.push(c.f64()),
|
||||
AnyColumn::Bool(col) => col.push(c.bool()),
|
||||
AnyColumn::Ts(col) => col.push(c.ts()),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add the round-trip test**
|
||||
|
||||
In the `#[cfg(test)] mod tests` block (after `same_kind_push_round_trips`, ~line
|
||||
171), add:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn push_cell_round_trips_by_column_kind() {
|
||||
// push_cell reinterprets the bare cell by the column's own kind; reading
|
||||
// back through `get` reconstructs the self-describing Scalar.
|
||||
let mut f = AnyColumn::with_capacity(ScalarKind::F64, 2);
|
||||
f.push_cell(Cell::from_f64(1.5));
|
||||
assert_eq!(f.get(0), Some(Scalar::f64(1.5)));
|
||||
assert_eq!(f.run_count(), 1);
|
||||
|
||||
let mut i = AnyColumn::with_capacity(ScalarKind::I64, 2);
|
||||
i.push_cell(Cell::from_i64(42));
|
||||
assert_eq!(i.get(0), Some(Scalar::i64(42)));
|
||||
|
||||
let mut b = AnyColumn::with_capacity(ScalarKind::Bool, 2);
|
||||
b.push_cell(Cell::from_bool(true));
|
||||
assert_eq!(b.get(0), Some(Scalar::bool(true)));
|
||||
|
||||
let mut t = AnyColumn::with_capacity(ScalarKind::Timestamp, 2);
|
||||
t.push_cell(Cell::from_ts(Timestamp(7)));
|
||||
assert_eq!(t.get(0), Some(Scalar::ts(Timestamp(7))));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify it compiles and passes**
|
||||
|
||||
Run: `cargo test -p aura-core push_cell_round_trips_by_column_kind`
|
||||
Expected: PASS (1 passed). The rest of the workspace is untouched and still green.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Atomic lib-code carrier flip
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aura-core/src/node.rs`
|
||||
- Modify: `crates/aura-std/src/{add,sub,sma,ema,exposure,lincomb,sim_broker,recorder}.rs`
|
||||
- Modify: `crates/aura-engine/src/harness.rs`
|
||||
|
||||
The trait-sig flip breaks every `impl Node` simultaneously; all lib-code impls +
|
||||
the harness forward loop must change together. Fixtures (`#[cfg(test)]`) and
|
||||
eval-output assertions are deferred to Task 3, so the gate here is a **lib-only**
|
||||
build (`cargo build --workspace`, NOT `--all-targets`) — the cfg(test) code does
|
||||
not compile until Task 3.
|
||||
|
||||
- [ ] **Step 1: Flip the `Node::eval` trait signature (node.rs)**
|
||||
|
||||
In `crates/aura-core/src/node.rs`:
|
||||
|
||||
Line 13 — add `Cell` to the import:
|
||||
```rust
|
||||
use crate::{Cell, Ctx, Scalar, ScalarKind};
|
||||
```
|
||||
|
||||
Line 264 — the trait method:
|
||||
```rust
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]>;
|
||||
```
|
||||
|
||||
Update the trait/struct doc prose that names the old carrier: in the `Node`
|
||||
doc-comment (~lines 248-255) and the C8 reference, replace the phrase
|
||||
`returning a borrowed slice into a buffer the node owns` context — specifically
|
||||
any `Option<&[Scalar]>` mention in rustdoc becomes `Option<&[Cell]>`. (Search the
|
||||
file for `&[Scalar]` in doc comments; the param-path doc mentions of `Scalar`
|
||||
stay.)
|
||||
|
||||
- [ ] **Step 2: Migrate `add.rs`**
|
||||
|
||||
`crates/aura-std/src/add.rs`:
|
||||
|
||||
Line 5 — `use aura_core::{Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, Scalar, ScalarKind};`
|
||||
Line 20 — `out: [Cell; 1],`
|
||||
Line 26 — `Self { out: [Cell::from_f64(0.0)] }`
|
||||
Line 58 — `fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {`
|
||||
Line 64 — `self.out[0] = Cell::from_f64(a[0] + b[0]);`
|
||||
|
||||
(`Scalar` stays in the `use` — the test column writes at lines 87, 91 keep it.)
|
||||
|
||||
- [ ] **Step 3: Migrate `sub.rs`**
|
||||
|
||||
`crates/aura-std/src/sub.rs`:
|
||||
|
||||
Line 6 — add `Cell,` to the import (→ `use aura_core::{Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, Scalar, ScalarKind};`)
|
||||
Line 11 — `out: [Cell; 1],`
|
||||
Line 17 — `Self { out: [Cell::from_f64(0.0)] }`
|
||||
Line 49 — `fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {`
|
||||
Line 55 — `self.out[0] = Cell::from_f64(a[0] - b[0]);`
|
||||
|
||||
- [ ] **Step 4: Migrate `sma.rs`**
|
||||
|
||||
`crates/aura-std/src/sma.rs`:
|
||||
|
||||
Lines 6-9 — add `Cell,` to the multi-line import:
|
||||
```rust
|
||||
use aura_core::{
|
||||
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder, Scalar,
|
||||
ScalarKind,
|
||||
};
|
||||
```
|
||||
Line 14 — `out: [Cell; 1],`
|
||||
Line 21 — `Self { length, out: [Cell::from_f64(0.0)] }`
|
||||
Line 45 — `fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {`
|
||||
Line 54 — `self.out[0] = Cell::from_f64(sum / self.length as f64);`
|
||||
|
||||
(`Scalar` stays — column writes + `bind(..., Scalar::i64(2))` keep it.)
|
||||
|
||||
- [ ] **Step 5: Migrate `ema.rs`**
|
||||
|
||||
`crates/aura-std/src/ema.rs`:
|
||||
|
||||
Lines 20-23 — add `Cell,` to the multi-line import:
|
||||
```rust
|
||||
use aura_core::{
|
||||
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder, Scalar,
|
||||
ScalarKind,
|
||||
};
|
||||
```
|
||||
Line 37 — `out: [Cell; 1],`
|
||||
Line 52 — `out: [Cell::from_f64(0.0)],`
|
||||
Line 79 — `fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {`
|
||||
Line 99 — `self.out[0] = Cell::from_f64(self.ema);`
|
||||
|
||||
- [ ] **Step 6: Migrate `exposure.rs`**
|
||||
|
||||
`crates/aura-std/src/exposure.rs`:
|
||||
|
||||
Lines 6-9 — add `Cell,` to the multi-line import:
|
||||
```rust
|
||||
use aura_core::{
|
||||
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder, Scalar,
|
||||
ScalarKind,
|
||||
};
|
||||
```
|
||||
Line 15 — `out: [Cell; 1],`
|
||||
Line 22 — `Self { scale, out: [Cell::from_f64(0.0)] }`
|
||||
Line 46 — `fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {`
|
||||
Line 51 — `self.out[0] = Cell::from_f64((w[0] / self.scale).clamp(-1.0, 1.0));`
|
||||
|
||||
- [ ] **Step 7: Migrate `lincomb.rs`**
|
||||
|
||||
`crates/aura-std/src/lincomb.rs`:
|
||||
|
||||
Lines 9-12 — add `Cell,` to the multi-line import:
|
||||
```rust
|
||||
use aura_core::{
|
||||
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder, Scalar,
|
||||
ScalarKind,
|
||||
};
|
||||
```
|
||||
Line 30 — `out: [Cell; 1],`
|
||||
Line 40 — `Self { weights, out: [Cell::from_f64(0.0)] }`
|
||||
Line 68 — `fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {`
|
||||
Line 77 — `self.out[0] = Cell::from_f64(acc);`
|
||||
|
||||
- [ ] **Step 8: Migrate `sim_broker.rs` (eval body; test helper is Task 3)**
|
||||
|
||||
`crates/aura-std/src/sim_broker.rs`:
|
||||
|
||||
Line 7 — add `Cell,` to the import.
|
||||
Line 41 — `out: [Cell; 1],`
|
||||
Line 54 — `out: [Cell::from_f64(0.0)],`
|
||||
Line 82 — `fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {`
|
||||
Line 94 — `self.out[0] = Cell::from_f64(self.cum);`
|
||||
|
||||
(The test helper at line 124 migrates in Task 3.)
|
||||
|
||||
- [ ] **Step 9: Migrate `recorder.rs` (eval sig only)**
|
||||
|
||||
`crates/aura-std/src/recorder.rs`:
|
||||
|
||||
Line 10 — add `Cell` to the import (the return type `Option<&[Cell]>` names it,
|
||||
even though the body returns `None`):
|
||||
```rust
|
||||
use aura_core::{Cell, Ctx, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, Scalar, ScalarKind, Timestamp};
|
||||
```
|
||||
Line 60 — `fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {`
|
||||
|
||||
The body is unchanged: it builds the `Vec<Scalar>` channel row from `get(0)?`
|
||||
and returns `None` (pure sink). `Scalar` stays in the `use`.
|
||||
|
||||
- [ ] **Step 10: Migrate the harness forward path (harness.rs)**
|
||||
|
||||
`crates/aura-engine/src/harness.rs`:
|
||||
|
||||
Line 24 — add `Cell` to the import:
|
||||
```rust
|
||||
use aura_core::{AnyColumn, Cell, Ctx, Firing, Node, NodeSchema, Scalar, ScalarKind, Timestamp};
|
||||
```
|
||||
Line 10 — the module-doc prose `borrowed record (`Option<&[Scalar]>`)` →
|
||||
`borrowed record (`Option<&[Cell]>`)`.
|
||||
Line 373 — `let mut scratch: Vec<Cell> = Vec::new();`
|
||||
Line 416 — `let result: Option<&[Cell]> = {`
|
||||
Lines 426-428 — replace the fallible inter-node push with the infallible cell push:
|
||||
```rust
|
||||
nb.inputs[e.slot].push_cell(scratch[e.from_field]);
|
||||
nb.slots[e.slot] = SlotState { fresh_at: cycle_id, last_ts: ts };
|
||||
```
|
||||
(i.e. drop the `.push(...).expect("edge kind checked at wiring")` and call
|
||||
`push_cell`. The `nb.slots[...] = ...` line that follows is unchanged.)
|
||||
|
||||
**Do NOT change line 402** — the source-target forward
|
||||
`nb.inputs[t.slot].push(value).expect("source kind checked at wiring")` stays on
|
||||
the fallible `push(Scalar)` (ingestion, keep-set).
|
||||
|
||||
- [ ] **Step 11: Lib-only build gate**
|
||||
|
||||
Run: `cargo build --workspace`
|
||||
Expected: `Finished` with 0 errors. (This builds lib + bin targets only; the
|
||||
`#[cfg(test)]` fixtures and eval-output assertions do NOT compile yet — that is
|
||||
Task 3. Do **not** run `--all-targets` here; it will fail by design until Task 3.)
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Migrate `#[cfg(test)]` fixtures + eval-output assertions
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aura-core/src/node.rs` (Bare, Probe)
|
||||
- Modify: `crates/aura-engine/src/harness.rs` (6 fixtures + ctor literals)
|
||||
- Modify: `crates/aura-engine/src/blueprint.rs` (4 fixtures + ctor literals + use)
|
||||
- Modify: `crates/aura-engine/src/graph_model.rs` (Bare + use)
|
||||
- Modify: `crates/aura-std/src/{add,sub,sma,ema,exposure,lincomb,sim_broker}.rs` (assertions)
|
||||
|
||||
Per the spec's three-role test-migration rule: **eval-output** sites → `Cell`;
|
||||
**column writes** (`inputs[i].push(Scalar::f64(x))`) → stay `Scalar`;
|
||||
**out-of-graph channel sends** (`vec![Scalar::f64(v)]`) → stay `Scalar`.
|
||||
|
||||
- [ ] **Step 1: node.rs fixtures Bare + Probe**
|
||||
|
||||
`crates/aura-core/src/node.rs`:
|
||||
|
||||
Line 288 (`Bare::eval`) — `fn eval(&mut self, _ctx: Ctx<'_>) -> Option<&[Cell]> {`
|
||||
Line 410 (`Probe::eval`) — `fn eval(&mut self, _ctx: Ctx<'_>) -> Option<&[Cell]> {`
|
||||
|
||||
Both bodies return `None` — unchanged. `Probe`'s field `Probe(Vec<Scalar>)`
|
||||
(line 405) stays `Vec<Scalar>` (a stored param point). All `Scalar::i64/f64` in
|
||||
the `tests` / `zip_params_tests` modules are param/bind sites — stay `Scalar`.
|
||||
|
||||
- [ ] **Step 2: harness.rs fixtures (6) + ctor literals**
|
||||
|
||||
`crates/aura-engine/src/harness.rs` — for each fixture, change the `out` field
|
||||
type, the `eval` signature, and the `Scalar::*` assignment(s) to the `Cell`
|
||||
carrier:
|
||||
|
||||
- `AsOfSum`: line 621 `out: [Cell; 1],`; line 636 eval sig; line 642
|
||||
`self.out[0] = Cell::from_f64(a[0] + b[0]);`
|
||||
- `BarrierSum`: line 650 `out: [Cell; 1],`; line 665 eval sig; line 666
|
||||
`self.out[0] = Cell::from_f64(ctx.f64_in(0)[0] + ctx.f64_in(1)[0]);`
|
||||
- `MixedSum`: line 675 `out: [Cell; 1],`; line 694 eval sig; line 701
|
||||
`self.out[0] = Cell::from_f64(a[0] + b[0] + c[0]);`
|
||||
- `Ohlcv`: line 711 `out: [Cell; 5],`; line 732 eval sig; line 738
|
||||
`self.out[i] = Cell::from_f64(w[0]);`
|
||||
- `TwoField`: line 749 `out: [Cell; 2],`; line 767 eval sig; line 768
|
||||
`self.out[0] = Cell::from_f64(0.0);`; line 769 `self.out[1] = Cell::from_i64(0);`
|
||||
- `TapForward` (**dual-role**): line 778 `out: [Cell; 1],`; line 794 eval sig;
|
||||
line 801 `self.out[0] = Cell::from_f64(v);`. **Line 779 keeps**
|
||||
`tx: mpsc::Sender<(Timestamp, Vec<Scalar>)>` and **line 800 keeps**
|
||||
`let _ = self.tx.send((ctx.now(), vec![Scalar::f64(v)]));` (out-of-graph row).
|
||||
|
||||
Then the fixture **constructor literals** — change each `out:` init to the `Cell`
|
||||
carrier (the type must match the migrated field):
|
||||
- Lines 898, 944, 993, 1044, 1781 (AsOfSum/BarrierSum/MixedSum/BarrierSum):
|
||||
`out: [Cell::from_f64(0.0)]`
|
||||
- Lines 1156, 1209, 1255, 1380 (`Ohlcv { out: [Scalar::f64(0.0); 5] }`):
|
||||
`out: [Cell::from_f64(0.0); 5]`
|
||||
- Lines 1307, 1614 (`TwoField { out: [Scalar::f64(0.0), Scalar::i64(0)] }`):
|
||||
`out: [Cell::from_f64(0.0), Cell::from_i64(0)]`
|
||||
- Line 1475 (`TapForward { out: [Scalar::f64(0.0)], tx: ... }`):
|
||||
`out: [Cell::from_f64(0.0)]` (the `tx:` field unchanged)
|
||||
|
||||
**Keep `Scalar`** on every recorded-row / channel-row literal (recorder + Tap
|
||||
channel sends, `Vec<Scalar>` assertions) at lines 830-832, 877-879, 924-927,
|
||||
970-971, 1025-1027, 1070-1071, 1235-1236, 1278-1279, 1358-1361, 1367-1368,
|
||||
1490-1492, 1562-1563, 1598-1601, 1682-1684, 1746-1756, 1813-1821, 1873-1874,
|
||||
1935-1947, 2022-2036, 2108 — these are out-of-graph rows, not eval output.
|
||||
|
||||
- [ ] **Step 3: blueprint.rs fixtures (4) + ctor literals + use**
|
||||
|
||||
`crates/aura-engine/src/blueprint.rs`:
|
||||
|
||||
Line 816 — add `Cell` to the test-mod import:
|
||||
```rust
|
||||
use aura_core::{Cell, Ctx, FieldSpec, Firing, NodeSchema, Timestamp};
|
||||
```
|
||||
- `Join2`: line 1057 `out: [Cell; 1],`; line 1063 eval sig; line 1069
|
||||
`self.out[0] = Cell::from_f64(a[0] + b[0]);`
|
||||
- `Pass1`: line 1076 `out: [Cell; 1],`; line 1082 eval sig; line 1087
|
||||
`self.out[0] = Cell::from_f64(w[0]);`
|
||||
- `SinkF64`: line 1098 eval sig → `Option<&[Cell]>` (body returns `None`).
|
||||
- `SinkI64`: line 1110 eval sig → `Option<&[Cell]>` (body returns `None`).
|
||||
|
||||
Constructor literals:
|
||||
- Line 1119 (`Pass1 { out: [Scalar::f64(0.0)] }`) → `out: [Cell::from_f64(0.0)]`
|
||||
- Line 1127 (`Join2 { out: [Scalar::f64(0.0)] }`) → `out: [Cell::from_f64(0.0)]`
|
||||
- Line 2283 (`Pass1 { out: [Scalar::f64(0.0)] }`) → `out: [Cell::from_f64(0.0)]`
|
||||
|
||||
**Keep `Scalar`** on `compile_with_params(&[Scalar::...])` calls (param plane):
|
||||
lines 1239, 1899, 2058, 2193, 2212, 2265, 2315.
|
||||
|
||||
- [ ] **Step 4: graph_model.rs fixture + use**
|
||||
|
||||
`crates/aura-engine/src/graph_model.rs`:
|
||||
|
||||
Lines 274-276 — add `Cell` to the test-mod import:
|
||||
```rust
|
||||
use aura_core::{
|
||||
Cell, FieldSpec, Node, NodeSchema, PortSpec, PrimitiveBuilder, Scalar,
|
||||
};
|
||||
```
|
||||
Line 286 — `Bare::eval` sig → `fn eval(&mut self, _c: aura_core::Ctx<'_>) -> Option<&[Cell]> {`
|
||||
(body returns `None`). `scalar_str(s: aura_core::Scalar)` at line 27 is untouched
|
||||
(renders a `BoundParam.value`). If clippy later flags `Scalar` as unused in this
|
||||
test module, remove it from the line-274 import — the clippy gate (Step 8) is the
|
||||
backstop.
|
||||
|
||||
- [ ] **Step 5: aura-std eval-output assertions + sim_broker helper**
|
||||
|
||||
Migrate ONLY the eval-output assertion lines (`Some([Scalar::f64(x)].as_slice())`
|
||||
→ `Some([Cell::from_f64(x)].as_slice())`); leave every `inputs[i].push(Scalar::f64(x))`
|
||||
column-write untouched.
|
||||
|
||||
- `add.rs:92` — `Some([Cell::from_f64(14.0)].as_slice())`
|
||||
- `sub.rs:83` — `Some([Cell::from_f64(6.0)].as_slice())`
|
||||
- `sma.rs:86,97,100` — wrap each expected mean with `Cell::from_f64(...)`
|
||||
(line 86 is inside the `Some(m) => assert_eq!(got, Some([Cell::from_f64(m)].as_slice()))`
|
||||
match arm; lines 97, 100 are `Some([Cell::from_f64(7.0)]...)` / `Some([Cell::from_f64(9.0)]...)`).
|
||||
- `ema.rs:131,144,147` — same: line 131 match arm `Some([Cell::from_f64(m)]...)`;
|
||||
lines 144, 147 `Some([Cell::from_f64(7.0)]...)` / `Some([Cell::from_f64(9.0)]...)`.
|
||||
- `exposure.rs:81` — `Some([Cell::from_f64(want)].as_slice())`
|
||||
- `lincomb.rs:105,118,136,168,183` — each `Some([Cell::from_f64(x)].as_slice())`
|
||||
(the expected weighted sums: 11.0, 12.0, 6.0, 11.0, 11.0 respectively).
|
||||
- `sim_broker.rs:124` — the test helper match arm:
|
||||
`Some([c]) => c.f64(),` (was `Some([s]) => s.as_f64(),`). The `panic!` arm and
|
||||
the column-write seeds (lines 120, 122) are unchanged.
|
||||
|
||||
- [ ] **Step 6: Full-target build gate**
|
||||
|
||||
Run: `cargo build --workspace --all-targets`
|
||||
Expected: `Finished` with 0 errors. (Now the test code compiles against the
|
||||
`Cell` carrier.)
|
||||
|
||||
- [ ] **Step 7: Test gate**
|
||||
|
||||
Run: `cargo test --workspace`
|
||||
Expected: all tests pass, 0 failed. (Behaviour-preserving: the only changed
|
||||
test expectations are eval-output re-spellings; values are identical.)
|
||||
|
||||
- [ ] **Step 8: Clippy gate**
|
||||
|
||||
Run: `cargo clippy --workspace --all-targets -- -D warnings`
|
||||
Expected: 0 warnings. (Catches any now-unused `Scalar` import left in a fully
|
||||
migrated file.)
|
||||
|
||||
- [ ] **Step 9: Doc gate**
|
||||
|
||||
Run: `cargo doc --workspace --no-deps 2>&1`
|
||||
Expected: builds with no warnings (the migrated rustdoc comments + intra-doc
|
||||
links to `Cell` resolve).
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Record the carrier swap in the design ledger
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/design/INDEX.md`
|
||||
|
||||
The C7 realization note promised "its own note (and touch C8's eval contract)";
|
||||
this task delivers it so the ledger no longer asserts a now-false "not yet".
|
||||
|
||||
- [ ] **Step 1: Update the C7 realization note**
|
||||
|
||||
In `docs/design/INDEX.md`, the C7 `Realization (Cell carrier split, 2026-06)`
|
||||
note (lines ~195-210) currently ends:
|
||||
|
||||
```
|
||||
the `scalar_eq_is_value_not_bitwise` fixture. Behaviour-preserving. `Cell` is
|
||||
**not yet** the hot-path carrier — `eval -> Option<&[Scalar]>` and the edges
|
||||
still flow `Scalar`; replacing the carrier with `Cell` is a larger step that will
|
||||
get its own note (and touch C8's `eval` contract).
|
||||
```
|
||||
|
||||
Replace the "not yet" sentence with the realization (keep the value-PartialEq
|
||||
sentence before it):
|
||||
|
||||
```
|
||||
the `scalar_eq_is_value_not_bitwise` fixture. Behaviour-preserving.
|
||||
|
||||
**Realization (`Cell` becomes the hot-path carrier, 2026-06, #74).** The carrier
|
||||
swap deferred above has landed: `Node::eval` now returns `Option<&[Cell]>` and
|
||||
every node out-buffer is `[Cell; N]`, so the inter-node forward carries tag-free
|
||||
8-byte words. The kind lives only at the schema/column: the harness forwards each
|
||||
field via the new branch-free `AnyColumn::push_cell` (infallible — the edge
|
||||
kind match is verified once at bootstrap, the surviving guard
|
||||
`bootstrap_rejects_*_kind_mismatch`). `Scalar` remains on the self-describing
|
||||
dynamic boundaries: the param plane (`build`/`bind`/`compile_with_params`/sweep
|
||||
points/`RunManifest`), `AnyColumn::get` (the type-erased read for sinks/serde),
|
||||
and source ingestion (`Source::next`, the heterogeneous C3 merge). The removed
|
||||
per-value runtime kind check on node output is the same authoring-bug class C8
|
||||
already leaves to a `debug_assert` (output width); node-output-kind correctness
|
||||
is the node's declared `FieldSpec` contract, caught by each node's own test.
|
||||
Behaviour-preserving (C1).
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update the C8 prose mentions of the old carrier**
|
||||
|
||||
In `docs/design/INDEX.md`, the C8 guarantee (line ~221) and the C8 cycle-0005
|
||||
realization (line ~242) state `eval(ctx) -> Option<&[Scalar]>` /
|
||||
`eval` returns `Option<&[Scalar]>`. Update both `Option<&[Scalar]>` →
|
||||
`Option<&[Cell]>`, and in the 0005 realization append a brief pointer: append to
|
||||
that sentence ` (the carrier is now a tag-free `Cell` — see the C7 carrier note)`.
|
||||
|
||||
- [ ] **Step 3: Glossary check (no write expected)**
|
||||
|
||||
Run: `grep -n "not yet" docs/glossary.md; grep -n -A2 "^### cell" docs/glossary.md`
|
||||
Expected: the `cell` entry already says cell "is what the SoA hot path reads
|
||||
without a per-value branch" — forward-compatible, no "not yet" claim. If the grep
|
||||
shows a "not yet"-style claim about cell being the carrier, fix it; otherwise no
|
||||
glossary change.
|
||||
|
||||
- [ ] **Step 4: Verify no stale "not yet the hot-path carrier" remains**
|
||||
|
||||
Run: `grep -rn "not yet.*hot-path carrier\|not yet.*carrier" docs/`
|
||||
Expected: no matches (the C7 note's "not yet" line is gone).
|
||||
|
||||
Run: `cargo doc --workspace --no-deps 2>&1`
|
||||
Expected: no new warnings (sanity re-check after the ledger edit; the ledger is
|
||||
markdown, but this confirms nothing in code regressed).
|
||||
Reference in New Issue
Block a user