plan: 0015 node tunable-parameter declaration (#30)
Four bite-sized tasks, sequenced by crate-compile boundary (the non-Default params field breaks every NodeSchema literal until repaired): aura-core type + field, aura-std declarations, aura-engine fixture compile-repair, then the param_space() aggregation + tests. refs #30
This commit is contained in:
@@ -0,0 +1,525 @@
|
||||
# Node Tunable-Parameter Declaration — Implementation Plan
|
||||
|
||||
> **Parent spec:** `docs/specs/0015-node-param-declaration.md`
|
||||
>
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: use the `implement` skill to run
|
||||
> this plan. Steps use `- [ ]` checkboxes for tracking.
|
||||
|
||||
**Goal:** A node declares its tunable parameters in its C8 schema (`ParamSpec`, a
|
||||
third `NodeSchema` field), and `Blueprint::param_space()` aggregates every node's
|
||||
params into one flat, path-qualified, inspectable param-space.
|
||||
|
||||
**Architecture:** Two layers on existing machinery. `aura-core` gains the
|
||||
`ParamSpec` type and the `NodeSchema.params` field; the shipped `aura-std` nodes
|
||||
declare their tunable knobs. `aura-engine` gains a read-only `Blueprint::param_space()`
|
||||
that walks the graph-as-data (using the already-public `Composite::name()` as path
|
||||
prefix) in the same deterministic depth-first order `lower_items` uses — a parallel
|
||||
projection that leaves `compile`/`inline_composite` (and the compilat) untouched.
|
||||
|
||||
**Tech Stack:** Rust workspace — `aura-core` (node contract), `aura-std` (7 nodes),
|
||||
`aura-engine` (blueprint/compile). Param identity is positional (slot), name a
|
||||
non-load-bearing path-qualified debug symbol (C23).
|
||||
|
||||
**Sequencing note (compile-gate ordering):** adding the non-`Default` `params`
|
||||
field makes every `NodeSchema { .. }` literal a compile error until updated. The 19
|
||||
workspace literals span three crates; each crate's literals are repaired *inside*
|
||||
the crate's own task, and the per-crate build gate is `-p <crate>` (a partial build)
|
||||
until Task 4 finishes the workspace-wide build. `crates/aura-cli` and
|
||||
`crates/aura-ingest` construct **zero** `NodeSchema` literals (recon-verified), so
|
||||
they need no change and compile clean once the others do.
|
||||
|
||||
---
|
||||
|
||||
## Files this plan creates or modifies
|
||||
|
||||
- Modify: `crates/aura-core/src/node.rs:6-7` — stale module doc-comment update
|
||||
- Modify: `crates/aura-core/src/node.rs:38-53` — add `ParamSpec`, add `NodeSchema.params`
|
||||
- Modify: `crates/aura-core/src/node.rs:99-100` — `Bare` test fixture literal gains `params`
|
||||
- Modify: `crates/aura-core/src/lib.rs:42` — re-export `ParamSpec`
|
||||
- Modify: `crates/aura-std/src/{sma,exposure,lincomb}.rs` — declare params (+ `ParamSpec` import)
|
||||
- Modify: `crates/aura-std/src/{sub,add,sim_broker,recorder}.rs` — `params: vec![]`
|
||||
- Test: `crates/aura-std/src/sma.rs` — per-node declaration test (Sma/Exposure/LinComb + empty nodes)
|
||||
- Modify: `crates/aura-engine/src/blueprint.rs:113` — `Composite::schema()` adds `params: vec![]`
|
||||
- Modify: `crates/aura-engine/src/blueprint.rs:{373,419,438,453}` — 4 test fixtures gain `params: vec![]`
|
||||
- Modify: `crates/aura-engine/src/harness.rs:{358,384,406,436,474,498}` — 6 test fixtures gain `params: vec![]`
|
||||
- Modify: `crates/aura-engine/src/blueprint.rs:140-158` — add `Blueprint::param_space()` + `collect_params` helper
|
||||
- Modify: `crates/aura-engine/src/lib.rs:44` — re-export `ParamSpec`
|
||||
- Test: `crates/aura-engine/src/blueprint.rs` — nested-aggregation, top-level-unqualified, determinism, empty
|
||||
|
||||
---
|
||||
|
||||
## Task 1: `aura-core` — `ParamSpec` type + `NodeSchema.params` field
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aura-core/src/node.rs`
|
||||
- Modify: `crates/aura-core/src/lib.rs:42`
|
||||
|
||||
- [ ] **Step 1: Add the `ParamSpec` type**
|
||||
|
||||
In `crates/aura-core/src/node.rs`, immediately after the `FieldSpec` struct (ends
|
||||
at line 42), insert:
|
||||
|
||||
```rust
|
||||
/// One declared tunable parameter of a node (C8/C12): its render name and scalar
|
||||
/// kind. The name is a **non-load-bearing** debug symbol (path-qualified at
|
||||
/// aggregation, as `FieldSpec.name` already is); a param's identity is its
|
||||
/// positional slot in the blueprint's aggregated param-space (C23 — by index, not
|
||||
/// by name). Unlike `FieldSpec.name` (`&'static str`), the name is a `String`: a
|
||||
/// vector knob carries a runtime index (`weights[0]`) and aggregation prefixes the
|
||||
/// composite path (`strategy.weights[0]`). Permitted kinds: `I64`/`F64`/`Bool`;
|
||||
/// `Timestamp` is a structural axis (C20), never a numeric knob.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParamSpec {
|
||||
pub name: String,
|
||||
pub kind: ScalarKind,
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add the `params` field to `NodeSchema`**
|
||||
|
||||
In the same file, the `NodeSchema` struct (lines 49-53) becomes:
|
||||
|
||||
```rust
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct NodeSchema {
|
||||
pub inputs: Vec<InputSpec>,
|
||||
pub output: Vec<FieldSpec>,
|
||||
pub params: Vec<ParamSpec>,
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Update the stale module doc-comment**
|
||||
|
||||
In the same file, replace lines 6-7:
|
||||
|
||||
```rust
|
||||
//! Tunable params (C12/C19) are deliberately not part of the schema yet — see
|
||||
//! spec 0002's "Out of scope".
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```rust
|
||||
//! Tunable params (C12/C19) are declared via `params` (cycle 0015): each node's
|
||||
//! typed knobs, which `Blueprint::param_space` aggregates into the sweep's flat,
|
||||
//! path-qualified param-space (C8/C23). Identity is positional (slot); the name is
|
||||
//! a non-load-bearing debug symbol.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Repair the `Bare` test fixture literal**
|
||||
|
||||
In the same file, the `Bare` fixture in `mod tests` (line ~99-101) constructs a
|
||||
`NodeSchema`. Add the field so it reads:
|
||||
|
||||
```rust
|
||||
fn schema(&self) -> NodeSchema {
|
||||
NodeSchema { inputs: vec![], output: vec![], params: vec![] }
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Re-export `ParamSpec`**
|
||||
|
||||
In `crates/aura-core/src/lib.rs:42`, the re-export line becomes:
|
||||
|
||||
```rust
|
||||
pub use node::{FieldSpec, Firing, InputSpec, Node, NodeSchema, ParamSpec};
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Add a declaration test pinning the new field**
|
||||
|
||||
In `crates/aura-core/src/node.rs` `mod tests`, add:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn schema_carries_declared_params() {
|
||||
let s = NodeSchema {
|
||||
inputs: vec![],
|
||||
output: vec![],
|
||||
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
||||
};
|
||||
assert_eq!(s.params.len(), 1);
|
||||
assert_eq!(s.params[0].name, "length");
|
||||
assert_eq!(s.params[0].kind, ScalarKind::I64);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Build + test `aura-core` (partial build gate)**
|
||||
|
||||
Run: `cargo test -p aura-core`
|
||||
Expected: PASS — `aura-core` compiles (its one fixture repaired) and all its tests
|
||||
incl. `schema_carries_declared_params` pass. `aura-std`/`aura-engine` do **not**
|
||||
compile yet (their literals are repaired in Tasks 2-3); that is expected — this
|
||||
gate is scoped to `-p aura-core`.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: `aura-std` — declare params in the 7 node schemas
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aura-std/src/sma.rs:6,24`
|
||||
- Modify: `crates/aura-std/src/exposure.rs:25`
|
||||
- Modify: `crates/aura-std/src/lincomb.rs:44`
|
||||
- Modify: `crates/aura-std/src/{sub.rs:29,add.rs:38,sim_broker.rs:61,recorder.rs:33}`
|
||||
- Test: `crates/aura-std/src/sma.rs`
|
||||
|
||||
- [ ] **Step 1: `Sma` declares `length`**
|
||||
|
||||
In `crates/aura-std/src/sma.rs`, add `ParamSpec` to the import (line 6):
|
||||
|
||||
```rust
|
||||
use aura_core::{Ctx, FieldSpec, Firing, InputSpec, Node, NodeSchema, ParamSpec, Scalar, ScalarKind};
|
||||
```
|
||||
|
||||
and the `schema()` literal (line 23-32) gains the field:
|
||||
|
||||
```rust
|
||||
fn schema(&self) -> NodeSchema {
|
||||
NodeSchema {
|
||||
inputs: vec![InputSpec {
|
||||
kind: ScalarKind::F64,
|
||||
lookback: self.length,
|
||||
firing: Firing::Any,
|
||||
}],
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: `Exposure` declares `scale`**
|
||||
|
||||
In `crates/aura-std/src/exposure.rs`, add `ParamSpec` to the `aura_core` import,
|
||||
and the `schema()` literal (lines 24-27) gains:
|
||||
|
||||
```rust
|
||||
NodeSchema {
|
||||
inputs: vec![InputSpec { kind: ScalarKind::F64, lookback: 1, firing: Firing::Any }],
|
||||
output: vec![FieldSpec { name: "exposure", kind: ScalarKind::F64 }],
|
||||
params: vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }],
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: `LinComb` declares `weights[0..N]` (flat expansion)**
|
||||
|
||||
In `crates/aura-std/src/lincomb.rs`, add `ParamSpec` to the `aura_core` import, and
|
||||
the `schema()` literal (lines 43-49) gains the mapped params:
|
||||
|
||||
```rust
|
||||
NodeSchema {
|
||||
inputs: self
|
||||
.weights
|
||||
.iter()
|
||||
.map(|_| InputSpec { kind: ScalarKind::F64, lookback: 1, firing: Firing::Any })
|
||||
.collect(),
|
||||
output: vec![FieldSpec { name: "value", kind: ScalarKind::F64 }],
|
||||
params: (0..self.weights.len())
|
||||
.map(|i| ParamSpec { name: format!("weights[{i}]"), kind: ScalarKind::F64 })
|
||||
.collect(),
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: The four param-less nodes declare `params: vec![]`**
|
||||
|
||||
Add `params: vec![]` as the final field of the `NodeSchema` literal in each of:
|
||||
- `crates/aura-std/src/sub.rs` (`Sub::schema`, ~line 29)
|
||||
- `crates/aura-std/src/add.rs` (`Add::schema`, ~line 38)
|
||||
- `crates/aura-std/src/sim_broker.rs` (`SimBroker::schema`, ~line 61 — `pip_size` is
|
||||
metadata, C10/C15, not a knob)
|
||||
- `crates/aura-std/src/recorder.rs` (`Recorder::schema`, ~line 33 — wiring, not a knob)
|
||||
|
||||
No `ParamSpec` import is needed in these four (they use only `vec![]`).
|
||||
|
||||
- [ ] **Step 5: Add the per-node declaration test**
|
||||
|
||||
In `crates/aura-std/src/sma.rs` `mod tests` (it already imports the sibling nodes in
|
||||
`labels_carry_identifying_params`), add:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn nodes_declare_expected_params() {
|
||||
use crate::{Add, Exposure, LinComb, Recorder, SimBroker, Sub};
|
||||
use aura_core::{Firing, ParamSpec, ScalarKind};
|
||||
// single scalar knobs
|
||||
assert_eq!(
|
||||
Sma::new(3).schema().params,
|
||||
vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
||||
);
|
||||
assert_eq!(
|
||||
Exposure::new(0.5).schema().params,
|
||||
vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }],
|
||||
);
|
||||
// vector knob expands flat to N indexed F64 entries
|
||||
let lc = LinComb::new(vec![1.0, -1.0]).schema().params;
|
||||
assert_eq!(lc.len(), 2);
|
||||
assert_eq!(lc[0].name, "weights[0]");
|
||||
assert_eq!(lc[1].name, "weights[1]");
|
||||
assert!(lc.iter().all(|p| p.kind == ScalarKind::F64));
|
||||
// param-less nodes declare empty
|
||||
assert!(Sub::new().schema().params.is_empty());
|
||||
assert!(Add::new().schema().params.is_empty());
|
||||
assert!(SimBroker::new(0.0001).schema().params.is_empty());
|
||||
let (tx, _rx) = std::sync::mpsc::channel();
|
||||
assert!(Recorder::new(&[ScalarKind::F64], Firing::Any, tx).schema().params.is_empty());
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Build + test `aura-std` (partial build gate)**
|
||||
|
||||
Run: `cargo test -p aura-std`
|
||||
Expected: PASS — `aura-std` compiles against the new `aura-core` and all tests incl.
|
||||
`nodes_declare_expected_params` pass. `aura-engine` still does not compile (Task 3).
|
||||
|
||||
---
|
||||
|
||||
## Task 3: `aura-engine` — repair `Composite::schema` + 10 test fixtures
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aura-engine/src/blueprint.rs:113`
|
||||
- Modify: `crates/aura-engine/src/blueprint.rs:{373,419,438,453}`
|
||||
- Modify: `crates/aura-engine/src/harness.rs:{358,384,406,436,474,498}`
|
||||
|
||||
This task is pure compile-repair (mechanical) — every `NodeSchema` literal in
|
||||
`aura-engine` gains `params: vec![]` so the crate compiles again against the new
|
||||
field. No new behaviour, no new test. None of these are tunable-knob nodes.
|
||||
|
||||
- [ ] **Step 1: `Composite::schema()` declares empty params**
|
||||
|
||||
In `crates/aura-engine/src/blueprint.rs`, the `Composite::schema()` literal (line
|
||||
112-113) becomes:
|
||||
|
||||
```rust
|
||||
let out_field = self.nodes[self.output.node].schema().output[self.output.field];
|
||||
NodeSchema { inputs, output: vec![out_field], params: vec![] }
|
||||
```
|
||||
|
||||
A composite is an authoring boundary, not a node; its interior params surface
|
||||
through `param_space()` (Task 4), not its derived schema.
|
||||
|
||||
- [ ] **Step 2: Repair the 4 `blueprint.rs` test fixtures**
|
||||
|
||||
In `crates/aura-engine/src/blueprint.rs` `mod tests`, append `params: vec![]` as the
|
||||
final field of the `NodeSchema { .. }` literal in each fixture: `Join2` (~line 373),
|
||||
`Pass1` (~419), `SinkF64` (~438), `SinkI64` (~453).
|
||||
|
||||
- [ ] **Step 3: Repair the 6 `harness.rs` test fixtures**
|
||||
|
||||
In `crates/aura-engine/src/harness.rs` `mod tests`, append `params: vec![]` as the
|
||||
final field of the `NodeSchema { .. }` literal in each fixture: `AsOfSum` (~line 358),
|
||||
`BarrierSum` (~384), `MixedSum` (~406), `Ohlcv` (~436), `TwoField` (~474),
|
||||
`TapForward` (~498).
|
||||
|
||||
- [ ] **Step 4: Build `aura-engine` incl. tests (compile-repair gate)**
|
||||
|
||||
Run: `cargo build -p aura-engine --all-targets`
|
||||
Expected: PASS — `aura-engine` lib + test targets compile (every literal repaired). If
|
||||
the build names any remaining `NodeSchema` literal missing `params`, add
|
||||
`params: vec![]` to it and re-run; the build error enumerates each one verbatim.
|
||||
|
||||
- [ ] **Step 5: Existing `aura-engine` tests stay green**
|
||||
|
||||
Run: `cargo test -p aura-engine`
|
||||
Expected: PASS — all pre-existing tests (incl. the bit-identical
|
||||
`composite_sma_cross_runs_bit_identical_to_hand_wired` and the golden render tests)
|
||||
stay green: the compilat is unchanged, only schema literals gained an empty field.
|
||||
|
||||
---
|
||||
|
||||
## Task 4: `aura-engine` — `Blueprint::param_space()` aggregation + tests
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/aura-engine/src/blueprint.rs:140-158` (Blueprint impl) + module scope (helper)
|
||||
- Modify: `crates/aura-engine/src/lib.rs:44`
|
||||
- Test: `crates/aura-engine/src/blueprint.rs`
|
||||
|
||||
- [ ] **Step 1: Add the `param_space()` accessor**
|
||||
|
||||
In `crates/aura-engine/src/blueprint.rs`, inside `impl Blueprint` (after `edges()`,
|
||||
which ends at line 158), add:
|
||||
|
||||
```rust
|
||||
/// The aggregated, flat, path-qualified param-space (C12): every node's declared
|
||||
/// params, concatenated in the deterministic depth-first item order `lower_items`
|
||||
/// uses, so a param's slot here matches the later flat-node order (#31 binds
|
||||
/// slot-by-slot). Read-only graph-as-data (C9); does not compile. Names are
|
||||
/// non-load-bearing: a composite's `name()` is prefixed at each level, but
|
||||
/// same-type siblings in one composite share a name — uniqueness is at the slot.
|
||||
pub fn param_space(&self) -> Vec<ParamSpec> {
|
||||
let mut out = Vec::new();
|
||||
collect_params(&self.nodes, "", &mut out);
|
||||
out
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add the `collect_params` helper at module scope**
|
||||
|
||||
In the same file, at module scope (next to `lower_items`, e.g. after the `Blueprint`
|
||||
impl block), add:
|
||||
|
||||
```rust
|
||||
/// Recursive read-only walk for `Blueprint::param_space`: a leaf contributes its
|
||||
/// declared params under the running path prefix; a composite pushes its `name()`
|
||||
/// onto the path and recurses. Order mirrors `lower_items` (items in declared order,
|
||||
/// composites depth-first) so a param's slot matches the later flat-node order.
|
||||
fn collect_params(items: &[BlueprintNode], prefix: &str, out: &mut Vec<ParamSpec>) {
|
||||
for item in items {
|
||||
match item {
|
||||
BlueprintNode::Leaf(node) => {
|
||||
for p in node.schema().params {
|
||||
let name = if prefix.is_empty() {
|
||||
p.name
|
||||
} else {
|
||||
format!("{prefix}.{}", p.name)
|
||||
};
|
||||
out.push(ParamSpec { name, kind: p.kind });
|
||||
}
|
||||
}
|
||||
BlueprintNode::Composite(c) => {
|
||||
let child = if prefix.is_empty() {
|
||||
c.name().to_string()
|
||||
} else {
|
||||
format!("{prefix}.{}", c.name())
|
||||
};
|
||||
collect_params(c.nodes(), &child, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Import `ParamSpec` into `blueprint.rs`**
|
||||
|
||||
In `crates/aura-engine/src/blueprint.rs`, the `aura_core` import (line 14) gains
|
||||
`ParamSpec`:
|
||||
|
||||
```rust
|
||||
use aura_core::{Node, NodeSchema, ParamSpec, ScalarKind};
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Re-export `ParamSpec` from `aura-engine`**
|
||||
|
||||
In `crates/aura-engine/src/lib.rs:44`, add `ParamSpec` to the `aura_core`
|
||||
re-export (consumer of `param_space()` needs one import surface, per the #29 tidy
|
||||
precedent). The line is currently:
|
||||
|
||||
```rust
|
||||
pub use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
|
||||
```
|
||||
|
||||
and becomes:
|
||||
|
||||
```rust
|
||||
pub use aura_core::{Firing, ParamSpec, Scalar, ScalarKind, Timestamp};
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Add the headline nested-aggregation test**
|
||||
|
||||
In `crates/aura-engine/src/blueprint.rs` `mod tests`, add (the test imports the
|
||||
sibling `aura_std` nodes, already a dev-dependency):
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn param_space_is_flat_path_qualified_and_slot_disambiguated() {
|
||||
use aura_std::{LinComb, Sma, Sub};
|
||||
// inner composite "fast_slow": two SMAs (same type → same param name) + a Sub
|
||||
let fast_slow = Composite::new(
|
||||
"fast_slow",
|
||||
vec![Sma::new(2).into(), Sma::new(4).into(), Sub::new().into()],
|
||||
vec![
|
||||
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
|
||||
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
||||
],
|
||||
vec![vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }]],
|
||||
OutPort { node: 2, field: 0 },
|
||||
);
|
||||
// outer composite "strategy": the inner composite + a LinComb([1,-1])
|
||||
let strategy = Composite::new(
|
||||
"strategy",
|
||||
vec![BlueprintNode::Composite(fast_slow), LinComb::new(vec![1.0, -1.0]).into()],
|
||||
vec![],
|
||||
vec![vec![Target { node: 0, slot: 0 }]],
|
||||
OutPort { node: 0, field: 0 },
|
||||
);
|
||||
let bp = Blueprint::new(vec![BlueprintNode::Composite(strategy)], vec![], vec![]);
|
||||
|
||||
let space = bp.param_space();
|
||||
let names: Vec<&str> = space.iter().map(|p| p.name.as_str()).collect();
|
||||
assert_eq!(
|
||||
names,
|
||||
[
|
||||
"strategy.fast_slow.length", // slot 0 — Sma(2)
|
||||
"strategy.fast_slow.length", // slot 1 — Sma(4): same name, distinct slot
|
||||
"strategy.weights[0]", // slot 2 — LinComb weight 0
|
||||
"strategy.weights[1]", // slot 3 — LinComb weight 1
|
||||
]
|
||||
);
|
||||
assert_eq!(space[0].kind, ScalarKind::I64);
|
||||
assert_eq!(space[2].kind, ScalarKind::F64);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Add top-level-unqualified, determinism, and empty tests**
|
||||
|
||||
In the same `mod tests`, add:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn top_level_leaf_params_are_unqualified() {
|
||||
use aura_std::Sma;
|
||||
let bp = Blueprint::new(vec![Sma::new(3).into()], vec![], vec![]);
|
||||
let space = bp.param_space();
|
||||
assert_eq!(space.len(), 1);
|
||||
assert_eq!(space[0].name, "length"); // no path prefix at the top level
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn param_space_is_deterministic() {
|
||||
use aura_std::{LinComb, Sma};
|
||||
let bp = Blueprint::new(
|
||||
vec![Sma::new(2).into(), LinComb::new(vec![1.0, -1.0]).into()],
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
assert_eq!(bp.param_space(), bp.param_space()); // pure structural function (C1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn param_space_empty_for_paramless_and_empty_blueprints() {
|
||||
use aura_std::{Add, Sub};
|
||||
let only_paramless =
|
||||
Blueprint::new(vec![Sub::new().into(), Add::new().into()], vec![], vec![]);
|
||||
assert!(only_paramless.param_space().is_empty());
|
||||
let empty = Blueprint::new(vec![], vec![], vec![]);
|
||||
assert!(empty.param_space().is_empty());
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Test `aura-engine` + full workspace build**
|
||||
|
||||
Run: `cargo test -p aura-engine`
|
||||
Expected: PASS — the four new tests pass; all existing tests stay green.
|
||||
|
||||
Run: `cargo build --workspace && cargo test --workspace`
|
||||
Expected: PASS — every crate compiles (aura-cli/aura-ingest unchanged, zero
|
||||
`NodeSchema` literals) and the whole suite is green.
|
||||
|
||||
- [ ] **Step 8: Clippy gate**
|
||||
|
||||
Run: `cargo clippy --workspace --all-targets -- -D warnings`
|
||||
Expected: PASS — no warnings (the `format!`/`collect` idioms in `collect_params`
|
||||
and `LinComb::schema` are clippy-clean).
|
||||
|
||||
---
|
||||
|
||||
## Notes for the implementer
|
||||
|
||||
- **Do not touch** `compile`, `inline_composite`, `lower_items`, or any edge/wiring
|
||||
logic. `param_space()` is a *parallel* read-only projection that mirrors the
|
||||
inliner's traversal order; it must not share or alter it. The spec's correctness
|
||||
rests on the compilat staying bit-identical (every existing golden / bit-identical
|
||||
test stays green by construction).
|
||||
- **fieldtests/ are out of scope** — they are excluded crates (own `Cargo.toml`),
|
||||
not in `--workspace`, and are frozen cycle-archive snapshots. They construct the
|
||||
old 2-field `NodeSchema` but never compile in this build, so they do not break the
|
||||
gate and are deliberately left unchanged.
|
||||
- Param identity is **positional** — name collisions (two same-type siblings sharing
|
||||
a path-qualified name) are the expected, correct case, never an error.
|
||||
Reference in New Issue
Block a user