Files
Claudeandclaude-flow 1938f0251f feat(npm): @ruvnet/ruview-kernel — WASM + napi-rs compute kernel with metaharness e2e (ADR-368)
Adds a portable RuView compute kernel for JavaScript, built from the Rust
source of truth and wired into the @ruvnet/ruview metaharness.

Rust
- v2/crates/ruview-kernel: bounded single-function JSON ABI
  (call(op, json) -> envelope) over the ADR-021 wifi-densepose-vitals
  pipeline: streaming sessions, batch analyze, deterministic SplitMix64
  synthetic CSI (SYNTHETIC), bounded session registry, strict input
  validation (deny_unknown_fields, width/finite/magnitude checks, size and
  count limits). wasm32-only zero-import C ABI (rvk_alloc/free/call).
- v2/crates/ruview-kernel-napi: napi-rs transport exposing only
  abiVersion()/call(); standalone workspace (excluded from v2) so napi never
  enters `cargo test --workspace`; panic=unwind + catch_unwind.

npm (harness/ruview-kernel, zero runtime deps)
- loadKernel({backend: wasm|napi|auto}); WASM default (no host authority),
  napi never silently falls back, auto reports fallbackReason.
- SHA256SUMS integrity verification; mismatch is always fatal; loader
  rejects any WASM module that imports host functions.
- Browser-safe ./wasm entry; CLI doctor/info/selftest/parity/synth/
  analyze/bench; build.mjs builds artifacts from source (no shell).
- Unit, security, and e2e tests (CLI round trip, cross-backend parity).

Metaharness
- New read-only tool ruview_kernel_selftest / `ruview kernel`: imports the
  fixed optional package, fails closed with kernel_not_installed; policy,
  claims, MCP policy, guidance capability, and manifest updated.

CI: .github/workflows/ruview-kernel-npm.yml builds both transports on
Linux/macOS/Windows, requires napi in tests, runs parity, verifies
artifacts, packs, and runs the cross-package e2e. No publish step.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_015H55jmprfz4oHWXSFbX5rf
2026-09-30 16:23:29 +00:00

85 lines
4.1 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { copyFileSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { DEFAULT_WASM_PATH, loadKernel } from '../src/index.js';
import { compileKernelModule, loadWasmKernelFromBytes } from '../src/wasm.js';
import { HAS_WASM, MODULE_WITH_IMPORT, requireWasm } from './helpers.mjs';
test('a module that imports host functions is refused', () => {
assert.throws(() => compileKernelModule(MODULE_WITH_IMPORT), (e) => e.code === 'untrusted_module');
assert.throws(() => loadWasmKernelFromBytes(MODULE_WITH_IMPORT), (e) => e.code === 'untrusted_module');
});
test('a module without the kernel exports is refused', () => {
const empty = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);
assert.throws(() => compileKernelModule(empty), (e) => e.code === 'untrusted_module' && /missing exports/.test(e.message));
});
test('invalid backend names are rejected', () => {
assert.throws(() => loadKernel({ backend: 'python' }), (e) => e.code === 'invalid_backend');
});
test('explicit napi never silently falls back to wasm', () => {
assert.throws(() => loadKernel({ backend: 'napi', nativePath: '/nonexistent/ruview.node' }), (e) => e.code === 'backend_unavailable');
});
test('checksum mismatch fails closed and is never an auto fallback', { skip: !HAS_WASM && 'wasm not built' }, () => {
const dir = mkdtempSync(join(tmpdir(), 'ruview-kernel-'));
try {
const wasmPath = join(dir, 'ruview_kernel.wasm');
copyFileSync(DEFAULT_WASM_PATH, wasmPath);
writeFileSync(join(dir, 'SHA256SUMS'), `${'0'.repeat(64)} ruview_kernel.wasm\n`);
assert.throws(() => loadKernel({ backend: 'wasm', wasmPath }), (e) => e.code === 'integrity_mismatch');
const fakeNative = join(dir, 'fake.node');
writeFileSync(fakeNative, 'not an addon');
writeFileSync(join(dir, 'SHA256SUMS'), `${'1'.repeat(64)} fake.node\n`);
assert.throws(() => loadKernel({ backend: 'auto', nativePath: fakeNative }), (e) => e.code === 'integrity_mismatch');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('requireIntegrity refuses an unchecksummed artifact', { skip: !HAS_WASM && 'wasm not built' }, () => {
const dir = mkdtempSync(join(tmpdir(), 'ruview-kernel-'));
try {
const wasmPath = join(dir, 'ruview_kernel.wasm');
copyFileSync(DEFAULT_WASM_PATH, wasmPath);
assert.equal(loadKernel({ backend: 'wasm', wasmPath }).integrity, 'unverified');
assert.throws(() => loadKernel({ backend: 'wasm', wasmPath, requireIntegrity: true }), (e) => e.code === 'integrity_unverified');
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test('kernel rejects hostile frames without crashing the instance', () => {
requireWasm();
const k = loadKernel({ backend: 'wasm' });
const cases = [
[() => k.analyze([{ amplitudes: [1, 2] }], { n_subcarriers: 3 }), 'invalid_request'],
[() => k.analyze([{ amplitudes: [1e300] }], { n_subcarriers: 1 }), 'invalid_request'],
[() => k.analyze([{ amplitudes: [1], evil: true }], { n_subcarriers: 1 }), 'invalid_request'],
[() => k.analyze([], { n_subcarriers: 100000 }), 'invalid_request'],
[() => k.analyze([], { sample_rate_hz: -1 }), 'invalid_request'],
[() => k.synthesize({ seconds: 3600, sample_rate_hz: 1000 }), 'limit_exceeded'],
[() => k.call('session_push', { session: 424242, frames: [] }), 'unknown_session'],
];
for (const [fn, code] of cases) assert.throws(fn, (e) => e.code === code, code);
assert.equal(k.info().abi, 1, 'instance still healthy after rejected input');
});
test('session registry is bounded per instance', () => {
requireWasm();
const k = loadKernel({ backend: 'wasm' });
const sessions = [];
let limited = false;
for (let i = 0; i < 70; i++) {
try { sessions.push(k.openSession({})); } catch (e) { assert.equal(e.code, 'limit_exceeded'); limited = true; break; }
}
assert.ok(limited, 'more than 64 concurrent sessions must be refused');
for (const s of sessions) s.close();
assert.ok(k.openSession({}).close());
});