diff --git a/crates/aura-engine/src/construction.rs b/crates/aura-engine/src/construction.rs index f601e9a..ffa93e7 100644 --- a/crates/aura-engine/src/construction.rs +++ b/crates/aura-engine/src/construction.rs @@ -659,6 +659,51 @@ mod tests { assert_eq!(names, ["length"]); } + /// #261 — the Frankfurt `Session` preset is reachable from a blueprint. The + /// property this pins: a data-only op-script (a blueprint) that names the + /// preset's closed-roster type-id resolves it through `std_vocabulary`, + /// carries its one scalar `period_minutes` knob and the `bars_since_open: + /// i64` session-index output, wires its `trigger` input through the ordinary + /// edge convention, and builds to a finished `Composite`. Reachability is the + /// whole ask — the node's tz-aware/DST session SEMANTICS are already pinned + /// in `aura-std/src/session.rs` and are not re-pinned here. Today the preset + /// id is absent from the roster (the base `Session` builder takes structural + /// construction args, so the zero-arg macro cannot host it), so the lookup + /// returns `None` and the op-script's `Add` would fail `UnknownNodeType`. + #[test] + fn session_frankfurt_preset_is_reachable_from_a_blueprint() { + // (a) the preset resolves through the closed roster as the Session preset + // — its one scalar knob and its i64 session-index output, not some + // other node parked under the id. + let preset = std_vocabulary("SessionFrankfurt") + .expect("the Frankfurt Session preset is in the std vocabulary roster"); + assert_eq!(preset.label(), "SessionFrankfurt"); + let schema = preset.schema(); + assert_eq!(schema.params.len(), 1, "exactly one scalar knob"); + assert_eq!(schema.params[0].name, "period_minutes"); + assert_eq!(schema.params[0].kind, ScalarKind::I64); + assert_eq!(schema.inputs.len(), 1); + assert_eq!(schema.inputs[0].name, "trigger"); + assert_eq!(schema.output[0].name, "bars_since_open"); + assert_eq!(schema.output[0].kind, ScalarKind::I64); + + // (b) an op-script naming that id builds: a bound trigger source feeds the + // preset's `trigger`, its `period_minutes` knob binds, and the i64 bar + // index exposes — a data-only blueprint reaching the session stream. + let ops = vec![ + Op::Source { role: "trigger".into(), kind: ScalarKind::F64 }, + Op::Add { + type_id: "SessionFrankfurt".into(), + as_name: Some("session".into()), + bind: vec![("period_minutes".into(), Scalar::i64(15))], + }, + Op::Feed { role: "trigger".into(), into: vec!["session.trigger".into()] }, + Op::Expose { from: "session.bars_since_open".into(), as_name: "bars".into() }, + ]; + super::replay("session_blueprint", ops, &std_vocabulary) + .expect("the preset resolves, wires and builds through an op-script"); + } + /// Eager refusals: unknown member param (a member bound at `Add` has left the /// open schema), kind mismatch across members, a doubly-ganged member, a /// single-member gang, and an unknown node identifier.