From 8b30d2316acf73f2b665d1e0e47bf49b749fca00 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 15 Jun 2026 10:28:48 +0200 Subject: [PATCH] =?UTF-8?q?feat(p3):=20falcon=20rate=20controller=20P3=20s?= =?UTF-8?q?tream=20variant=20=E2=80=94=20first=20control-cascade=20engine?= =?UTF-8?q?=20(#168,=20STREAM-P05)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends streamification from the cFS engines (LC/SCH/HK/SC) to the CONTROL CASCADE. The rate controller is the first flight-control engine as a P3 async stream, toward a composed flight-control stream pipeline (iekf->pos->att->rate ->mixer). De-risked: built the proven cFS-stream way (rust_wasm_component_bindgen, wasi p3, deps on the verified :relay-rate Bazel library) rather than the cascade's cargo-component path, with a SELF-CONTAINED WIT (falcon:rate-stream) that does not touch the shared, fragile cascade.wit. - wit/falcon-rate-stream/rate_stream.wit: monitor: func(stream) -> stream. - wasm/cm/rate/src/stream.rs: async fn monitor over a StreamReader, driving relay_rate::RatePid::tick (the verified body-rate PID; stateful PID + 1 kHz timestamp like the sync component) — use relay_rate, NOT an inline copy. - BUILD.bazel: falcon_rate_stream_wit + //:falcon-rate-stream (wasi=p3) deps on :relay-rate. - bazel.yml: builds //:falcon-rate-stream in CI (exercised, #168 lesson). - SWREQ-RELAY-STREAM-P05 -> FV-RELAY-STREAM-005. rivet validate PASS. New P3 direction (user-scoped) after the cFS streamification completed at v1.65. The cascade build path is new territory, so the P3 build is CI-validated. Co-Authored-By: Claude Fable 5 --- .github/workflows/bazel.yml | 11 +-- BUILD.bazel | 20 ++++++ artifacts/swreq/SWREQ-RELAY-STREAM-P05.yaml | 43 ++++++++++++ .../verification/FV-RELAY-STREAM-005.yaml | 44 ++++++++++++ wasm/cm/rate/src/stream.rs | 69 +++++++++++++++++++ wit/falcon-rate-stream/rate_stream.wit | 39 +++++++++++ 6 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 artifacts/swreq/SWREQ-RELAY-STREAM-P05.yaml create mode 100644 artifacts/verification/FV-RELAY-STREAM-005.yaml create mode 100644 wasm/cm/rate/src/stream.rs create mode 100644 wit/falcon-rate-stream/rate_stream.wit diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index 30e7bd6..813fa6b 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -76,11 +76,12 @@ jobs: working-directory: relay env: CARGO_BAZEL_REPIN: "1" - # Also builds the P3 (Component-Model Preview 3) async STREAM components - # for the full cFS set: //:relay-lc-stream + //:relay-sch-stream + - # //:relay-hk-stream + //:relay-sc-stream (#STREAM-P01..P04), so their P3 - # async build is CI-validated, not just asserted. - run: bazel build //:falcon-cascade-composed //:relay-lc-stream //:relay-sch-stream //:relay-hk-stream //:relay-sc-stream //:relay-lc //:relay-sch //:relay-hk //:relay-sc + # Also builds the P3 (Component-Model Preview 3) async STREAM components: + # the full cFS set //:relay-lc-stream + //:relay-sch-stream + + # //:relay-hk-stream + //:relay-sc-stream (#STREAM-P01..P04), plus the + # first control-cascade stream //:falcon-rate-stream (#STREAM-P05), so + # their P3 async build is CI-validated, not just asserted. + run: bazel build //:falcon-cascade-composed //:relay-lc-stream //:relay-sch-stream //:relay-hk-stream //:relay-sc-stream //:falcon-rate-stream //:relay-lc //:relay-sch //:relay-hk //:relay-sc # Exercise the witness MC/DC path end-to-end (#145): forces the # harness_crates crate_universe (wasmtime/tokio) to resolve, builds the diff --git a/BUILD.bazel b/BUILD.bazel index e5d8b02..7c0d327 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -704,6 +704,26 @@ rust_wasm_component_bindgen( profiles = ["release"], ) +# P3 STREAM variant — stream -> stream (#168). +# The first control-cascade engine as a P3 async stream, built the proven +# cFS-stream way (rust_wasm_component_bindgen, wasi p3) over the verified +# :relay-rate library; self-contained WIT (does not touch cascade.wit). +wit_library( + name = "falcon_rate_stream_wit", + srcs = ["wit/falcon-rate-stream/rate_stream.wit"], + package_name = "falcon:rate-stream", + world = "rate-stream-component", +) + +rust_wasm_component_bindgen( + name = "falcon-rate-stream", + srcs = ["wasm/cm/rate/src/stream.rs"], + wit = ":falcon_rate_stream_wit", + wasi_version = "p3", + deps = ["@crates//:wit-bindgen", ":relay-rate"], + profiles = ["release"], +) + rust_wasm_component_bindgen( name = "falcon-mixer", crate_features = ["bazel-bindings"], diff --git a/artifacts/swreq/SWREQ-RELAY-STREAM-P05.yaml b/artifacts/swreq/SWREQ-RELAY-STREAM-P05.yaml new file mode 100644 index 0000000..486449c --- /dev/null +++ b/artifacts/swreq/SWREQ-RELAY-STREAM-P05.yaml @@ -0,0 +1,43 @@ +artifacts: + - id: SWREQ-RELAY-STREAM-P05 + type: sw-req + title: "STREAM-P05 — falcon rate controller exposes a Component-Model Preview 3 async stream interface (first control-cascade engine)" + status: implemented + description: > + Extends the streamification template from the cFS engines (LC/SCH/HK/SC, + P01-P04) to the CONTROL CASCADE: the falcon rate controller shall expose a + WASM Component-Model Preview 3 (P3) async interface + monitor: func(inputs: stream) -> stream + implemented as a genuine async stream transformer (async fn over a + StreamReader, .next().await on the inputs, .write().await on the torque + outputs), built as a P3 component (wasi_version = "p3"). This is the first + flight-control engine as a P3 stream, toward a composed flight-control + STREAM pipeline (iekf -> pos -> att -> rate -> mixer). + + It RUNS THE VERIFIED CONTROLLER, not an inline copy: the component does + `use relay_rate::{RatePid, Timestamp};` and the //:falcon-rate-stream + bindgen target `deps` on the :relay-rate Bazel library (the verified + body-rate PID), so the streaming monitor() drives the ACTUAL relay-rate + RatePid::tick. The WIT is SELF-CONTAINED (its own falcon:rate-stream + package) and does not modify the shared cascade.wit. + + As with P01-P04, the async RUNTIME that executes these streams is kiln's + fuel-metered async scheduler (shipped but dormant); this requirement's + scope is AUTHORING + BUILDING a valid P3 async component, NOT end-to-end + execution on kiln. + tags: [relay, falcon-rate, cascade, stream, async, p3, component-model, streamification] + fields: + req-type: functional + priority: must + verification-criteria: > + wit/falcon-rate-stream/rate_stream.wit declares + `monitor: func(stream) -> stream`; the Rust + implementation (wasm/cm/rate/src/stream.rs) is a real async stream + transformer over StreamReader with .next().await / writer.write().await, + driving relay_rate::RatePid::tick; the //:falcon-rate-stream bazel target + builds it as a wasi_version="p3" component, exercised in CI + (.github/workflows/bazel.yml builds it alongside the cFS stream + components). + links: + - type: derives-from + target: SYSREQ-FALCON-008 diff --git a/artifacts/verification/FV-RELAY-STREAM-005.yaml b/artifacts/verification/FV-RELAY-STREAM-005.yaml new file mode 100644 index 0000000..57b5a87 --- /dev/null +++ b/artifacts/verification/FV-RELAY-STREAM-005.yaml @@ -0,0 +1,44 @@ +artifacts: + - id: FV-RELAY-STREAM-005 + type: sw-verification + title: "falcon rate controller P3 async stream component — runs the verified PID, builds as Component-Model P3 (v1.66)" + status: implemented + description: > + Verifies SWREQ-RELAY-STREAM-P05 for the rate controller — the first + control-cascade engine promoted to a real WASM Component-Model Preview 3 + (P3) async stream transformer, extending the streamification template + (FV-RELAY-STREAM-001..004) from the cFS engines to the flight cascade. + + Evidence: + 1. The WIT (wit/falcon-rate-stream/rate_stream.wit) declares genuine P3 + stream types: `monitor: func(inputs: stream) -> + stream`. Self-contained (falcon:rate-stream package), + not touching the shared cascade.wit. + 2. The implementation (wasm/cm/rate/src/stream.rs) is a real async stream + transformer: `async fn monitor(mut inputs: StreamReader) -> + StreamReader` with `wit_stream::new()`, `while let + Some(inp) = inputs.next().await`, and `writer.write(...).await`. + 3. RUNS THE VERIFIED CONTROLLER: `use relay_rate::{RatePid, Timestamp};` + and //:falcon-rate-stream `deps` on :relay-rate, so the streaming + monitor() drives the ACTUAL relay-rate RatePid::tick (the body-rate + PID) — no inline copy. The PID integrator/derivative state persists + across the stream (static RatePid), timestamp a synthesised 1 kHz + counter, exactly as the sync falcon-rate component. + 4. The mechanical ORACLE: the //:falcon-rate-stream bazel target + (wasi_version = "p3") builds it as a P3 component, EXERCISED in CI + (.github/workflows/bazel.yml builds it alongside the four cFS stream + components), so "valid P3 async component driving the verified PID" is + CI-validated, not asserted. + + Honest scope (same as STREAM-001..004): verifies AUTHORED + BUILDS as P3 + async driving the verified controller; does NOT verify end-to-end + execution on kiln's async scheduler (shipped but dormant). Composing the + cascade stream pipeline (iekf->pos->att->rate->mixer) is the follow-on. + tags: [verification, relay, falcon-rate, cascade, stream, async, p3, component-model, "v1.66"] + fields: + method: automated-test + steps: + - run: bazel build //:falcon-rate-stream + links: + - type: verifies + target: SWREQ-RELAY-STREAM-P05 diff --git a/wasm/cm/rate/src/stream.rs b/wasm/cm/rate/src/stream.rs new file mode 100644 index 0000000..56de9ec --- /dev/null +++ b/wasm/cm/rate/src/stream.rs @@ -0,0 +1,69 @@ +// Falcon Rate Controller — P3 Stream Transformer WASM component. +// +// Takes stream, emits stream. +// Wraps the verified relay-rate body-rate PID (RatePid) — the first +// control-cascade engine promoted to a P3 async stream (#168 cascade arc). +// +// Built as a rust_wasm_component_bindgen (wasi_version="p3"), the same proven +// pattern as the cFS stream components — deps on the verified :relay-rate +// Bazel library, NOT an inline copy of the controller. + +use relay_rate::{RatePid, Timestamp}; + +use falcon_rate_stream_bindings::exports::falcon::rate_stream::rate_stream::{ + Guest, RateInput as WitInput, TorqueSetpoint as WitTorque, +}; + +struct Component; + +// Stateful across the stream: the PID integrator/derivative state persists, +// and the timestamp is a synthesised 1 kHz counter (as in the sync component). +static mut PID: Option = None; +static mut TICK_MS: u64 = 0; + +fn pid() -> &'static mut RatePid { + unsafe { + if PID.is_none() { PID = Some(RatePid::new()); } + PID.as_mut().unwrap() + } +} + +fn next_timestamp() -> Timestamp { + unsafe { + let ms = TICK_MS; + TICK_MS += 1; + Timestamp { + seconds: ms / 1000, + fraction: ((ms % 1000) * (1u64 << 32) / 1000) as u32, + } + } +} + +impl Guest for Component { + /// STREAM TRANSFORMER: reads rate-loop inputs, steps the verified PID on + /// each, writes the torque setpoint to the output stream. + async fn monitor( + mut inputs: wit_bindgen::rt::async_support::StreamReader, + ) -> wit_bindgen::rt::async_support::StreamReader { + let (mut writer, reader) = falcon_rate_stream_bindings::wit_stream::new::(); + + while let Some(inp) = inputs.next().await { + let torque = pid().tick( + next_timestamp(), + [inp.wx, inp.wy, inp.wz], + [inp.rx, inp.ry, inp.rz], + ); + let out = WitTorque { + tx: torque[0], + ty: torque[1], + tz: torque[2], + thrust: inp.thrust, // thrust passes straight through the rate loop + }; + let _ = writer.write(vec![out]).await; + } + + reader + } +} + +falcon_rate_stream_bindings::export!(Component with_types_in falcon_rate_stream_bindings); diff --git a/wit/falcon-rate-stream/rate_stream.wit b/wit/falcon-rate-stream/rate_stream.wit new file mode 100644 index 0000000..5b93a06 --- /dev/null +++ b/wit/falcon-rate-stream/rate_stream.wit @@ -0,0 +1,39 @@ +package falcon:rate-stream@0.1.0; + +/// Rate controller — stream transformer edition. +/// +/// Takes a stream of rate-loop inputs (measured body rates + body-rate +/// setpoint), emits a stream of torque setpoints. Wraps the verified +/// relay-rate body-rate PID (RatePid). Self-contained types (does not depend +/// on the shared cascade.wit) — the first control-cascade engine as a P3 async +/// stream (#168 cascade arc). +interface rate-stream { + /// Rate-loop input: measured body rates (gyro, rad/s) + the body-rate + /// setpoint (rad/s) + collective thrust (passed straight through). + record rate-input { + wx: f32, + wy: f32, + wz: f32, + rx: f32, + ry: f32, + rz: f32, + thrust: f32, + } + + /// Body-frame torque + collective thrust. + record torque-setpoint { + tx: f32, + ty: f32, + tz: f32, + thrust: f32, + } + + /// STREAM TRANSFORMER: rate-loop inputs in → torque setpoints out. + /// Each input drives the stateful PID one step; the resulting torque is + /// emitted to the output stream. + monitor: func(inputs: stream) -> stream; +} + +world rate-stream-component { + export rate-stream; +}