mirror of
https://github.com/ruvnet/RuView.git
synced 2026-10-18 21:00:22 -05:00
CI fixes (both failures on PR #2062 were in this branch): - harness/ruview: remove `2.11.0` literals from training remedies; the ADR-265 D3 version-literal gate rejects them. - ruview-kernel workflow: install wasm32 for the toolchain pinned by v2/rust-toolchain (the action only added it to `stable`); build.mjs now fails fast with the exact `rustup target add` remedy. Kernel optimization (ADR-368 amendment): - New `analyze_flat` / `session_push_flat` ops via `call_f64`: WASM `rvk_call_f64` (alignment-agnostic bytes) and napi `callF64(Float64Array)`. `analyze()`/`session.push()` pack uniform frames automatically; irregular input keeps the JSON path and its per-frame errors. `analyzeFlat()` is the zero-copy entry. Shared `Session::step` makes both paths byte-identical on identical f64 inputs (Rust test), and the binary path avoids serde_json's non-correctly-rounded float parsing. - MEASURED (`ruview-kernel bench --seconds 120`, one Linux x64 host): 2.82x (wasm) and 2.45x (napi) faster than the JSON transport. - Profiled remaining cost (examples/profile_stages.rs): heart-rate ACF in wifi-densepose-vitals dominates; left for a separate, output-reviewed change. Rejected WASM +simd128 and opt-level=s after three measured runs (bench/wasm-variants.mjs). Device e2e (ADR-373 amendment): - test/e2e/devices.e2e.mjs + `npm run test:e2e:devices` + new workflow ruview-device-e2e.yml: pty-emulated MR60BHA2 and RPLIDAR through real pyserial, a UDP ESP32 node, and the real iPhone relay, via the CLI. - mmWave auto-detect keeps probe frames and reads only the remainder; iPhone LiDAR gains max_frames and returns immediately when refused. Suite wall time 19.6 s -> 5.6 s (MEASURED, dev container). Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_015H55jmprfz4oHWXSFbX5rf
95 lines
3.5 KiB
Markdown
95 lines
3.5 KiB
Markdown
# @ruvnet/ruview-kernel
|
||
|
||
RuView's Rust signal pipeline for JavaScript. The ADR-021 vitals pipeline
|
||
(`wifi-densepose-vitals`) is compiled to **zero-import WebAssembly** (default)
|
||
and, optionally, a **napi-rs** native addon. Both expose the same one-function
|
||
JSON ABI, so results match across backends (ADR-368).
|
||
|
||
> Outputs are signal-processing estimates, not clinical or camera-grade
|
||
> measurements. `synthesize()` data and every self-test number are
|
||
> **SYNTHETIC**.
|
||
|
||
## Build from a RuView checkout
|
||
|
||
```bash
|
||
rustup target add wasm32-unknown-unknown
|
||
cd harness/ruview-kernel
|
||
npm run build # wasm + napi for this platform, with SHA256SUMS
|
||
npm test # unit, security, and end-to-end tests
|
||
node bin/cli.js parity # wasm vs napi on identical SYNTHETIC input
|
||
```
|
||
|
||
`npm run build:wasm` alone is enough for the portable backend.
|
||
|
||
## CLI
|
||
|
||
```bash
|
||
ruview-kernel doctor # both backends, ABI, integrity
|
||
ruview-kernel selftest [--backend wasm|napi|auto]
|
||
ruview-kernel synth --seconds 60 --out frames.json
|
||
ruview-kernel analyze --file frames.json --readings
|
||
ruview-kernel bench --backend napi # MEASURED on this host only
|
||
```
|
||
|
||
`analyze` accepts a JSON array of frames, `{ "config": {...}, "frames": [...] }`,
|
||
or JSONL (one `{"amplitudes":[...],"phases":[...]}` per line).
|
||
|
||
## API
|
||
|
||
```js
|
||
import { loadKernel, selfTest } from '@ruvnet/ruview-kernel';
|
||
|
||
const kernel = loadKernel(); // wasm; or { backend: 'napi' | 'auto' }
|
||
console.log(kernel.backend, kernel.fallbackReason, kernel.integrity);
|
||
|
||
const { frames } = kernel.synthesize({ seconds: 60, breathing_bpm: 15 });
|
||
const report = kernel.analyze(frames, { n_subcarriers: 56, sample_rate_hz: 20 });
|
||
console.log(report.summary.last.respiratory); // { bpm, confidence, status }
|
||
|
||
const session = kernel.openSession({ n_subcarriers: 56 });
|
||
const readings = session.push(frames.slice(0, 200));
|
||
session.close();
|
||
```
|
||
|
||
### Binary fast path
|
||
|
||
`analyze()` and `session.push()` automatically send uniform frames as one
|
||
`Float64Array` (all amplitudes, then all phases) instead of JSON numbers.
|
||
Irregular input still goes through JSON, so errors name the bad frame. To
|
||
skip packing entirely:
|
||
|
||
```js
|
||
const data = new Float64Array(frames * 56 * 2); // amplitudes…, then phases…
|
||
kernel.analyzeFlat(data, { n_subcarriers: 56 }, { phases: true });
|
||
```
|
||
|
||
MEASURED on one Linux x64 host (`ruview-kernel bench --seconds 120`): 2.8×
|
||
(wasm) and 2.5× (napi) faster than the JSON transport for 2 400 frames. The
|
||
binary path is also bit-exact, while JSON number parsing can move inputs by
|
||
ULPs.
|
||
|
||
In a browser, fetch the module yourself and use the filesystem-free entry:
|
||
|
||
```js
|
||
import { loadWasmKernelFromBytes } from '@ruvnet/ruview-kernel/wasm';
|
||
const kernel = loadWasmKernelFromBytes(new Uint8Array(await (await fetch(url)).arrayBuffer()));
|
||
```
|
||
|
||
## Backends and trust
|
||
|
||
| Backend | Authority | Selection |
|
||
|---|---|---|
|
||
| `wasm` | none: module imports nothing, loader rejects any import | default |
|
||
| `napi` | full process (native code) | `backend: 'napi'`; never falls back silently |
|
||
| `auto` | napi if present, otherwise wasm with `fallbackReason` | opt-in |
|
||
|
||
Artifacts are checked against `SHA256SUMS`; a mismatch is always fatal.
|
||
Requests are bounded (16 MiB, 512 subcarriers, 100 000 frames per call, 64
|
||
sessions) and validated before processing.
|
||
|
||
## Metaharness
|
||
|
||
`npx @ruvnet/ruview kernel` (MCP tool `ruview_kernel_selftest`) runs this
|
||
package's self-test from the RuView harness when it is installed, and reports
|
||
`kernel_not_installed` otherwise.
|