Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d63d4d9

Browse files
committed
feat: implement 24 vendor-integrated WASM edge modules (ADR-041)
Complete implementation of all 24 vendor-integrated sensing modules across 7 categories, compiled to wasm32-unknown-unknown for ESP32-S3 WASM3 runtime deployment. All 243 unit tests pass. Signal Intelligence (6): flash attention, coherence gate, temporal compress, sparse recovery, min-cut person match, optimal transport. Adaptive Learning (4): DTW gesture learn, anomaly attractor, meta adapt, EWC++ lifelong learning. Spatial Reasoning (3): PageRank influence, micro-HNSW, spiking tracker. Temporal Analysis (3): pattern sequence, temporal logic guard, GOAP. AI Security (2): prompt shield, behavioral profiler. Quantum-Inspired (2): quantum coherence, interference search. Autonomous Systems (2): psycho-symbolic engine, self-healing mesh. Exotic (2): time crystal detector, hyperbolic space embedding. Includes vendor_common.rs shared library, security audit with 5 fixes, and security audit report. Co-Authored-By: claude-flow <[email protected]>
1 parent 0c9b73a commit d63d4d9

29 files changed

Lines changed: 10517 additions & 7 deletions

docs/security-audit-wasm-edge-vendor.md

Lines changed: 266 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
//! Behavioral profiling with Mahalanobis-inspired anomaly scoring.
2+
//!
3+
//! ADR-041 AI Security module. Maintains a 6D behavior profile and detects
4+
//! anomalous deviations using online Welford statistics and combined Z-scores.
5+
//!
6+
//! Dimensions: presence_rate, avg_motion, avg_n_persons, activity_variance,
7+
//! transition_rate, dwell_time.
8+
//!
9+
//! Events: BEHAVIOR_ANOMALY(825), PROFILE_DEVIATION(826), NOVEL_PATTERN(827),
10+
//! PROFILE_MATURITY(828). Budget: S (< 5 ms).
11+
12+
#[cfg(not(feature = "std"))]
13+
use libm::sqrtf;
14+
#[cfg(feature = "std")]
15+
fn sqrtf(x: f32) -> f32 { x.sqrt() }
16+
17+
const N_DIM: usize = 6;
18+
const LEARNING_FRAMES: u32 = 1000;
19+
const ANOMALY_Z: f32 = 3.0;
20+
const NOVEL_Z: f32 = 2.0;
21+
const NOVEL_MIN: u32 = 3;
22+
const OBS_WIN: usize = 200;
23+
const COOLDOWN: u16 = 100;
24+
const MATURITY_INTERVAL: u32 = 72000;
25+
const VAR_FLOOR: f32 = 1e-6;
26+
27+
pub const EVENT_BEHAVIOR_ANOMALY: i32 = 825;
28+
pub const EVENT_PROFILE_DEVIATION: i32 = 826;
29+
pub const EVENT_NOVEL_PATTERN: i32 = 827;
30+
pub const EVENT_PROFILE_MATURITY: i32 = 828;
31+
32+
/// Welford's online mean/variance accumulator (single dimension).
33+
#[derive(Clone, Copy)]
34+
struct Welford { count: u32, mean: f32, m2: f32 }
35+
impl Welford {
36+
const fn new() -> Self { Self { count: 0, mean: 0.0, m2: 0.0 } }
37+
fn update(&mut self, x: f32) {
38+
self.count += 1;
39+
let d = x - self.mean;
40+
self.mean += d / (self.count as f32);
41+
self.m2 += d * (x - self.mean);
42+
}
43+
fn variance(&self) -> f32 {
44+
if self.count < 2 { 0.0 } else { self.m2 / (self.count as f32) }
45+
}
46+
fn z_score(&self, x: f32) -> f32 {
47+
let v = self.variance();
48+
if v < VAR_FLOOR { return 0.0; }
49+
let z = (x - self.mean) / sqrtf(v);
50+
if z < 0.0 { -z } else { z }
51+
}
52+
}
53+
54+
/// Ring buffer for observation window.
55+
struct ObsWindow {
56+
pres: [u8; OBS_WIN],
57+
motion: [f32; OBS_WIN],
58+
persons: [u8; OBS_WIN],
59+
idx: usize,
60+
len: usize,
61+
}
62+
impl ObsWindow {
63+
const fn new() -> Self {
64+
Self { pres: [0; OBS_WIN], motion: [0.0; OBS_WIN], persons: [0; OBS_WIN], idx: 0, len: 0 }
65+
}
66+
fn push(&mut self, present: bool, mot: f32, np: u8) {
67+
self.pres[self.idx] = present as u8;
68+
self.motion[self.idx] = mot;
69+
self.persons[self.idx] = np;
70+
self.idx = (self.idx + 1) % OBS_WIN;
71+
if self.len < OBS_WIN { self.len += 1; }
72+
}
73+
/// Compute 6D feature vector from current window.
74+
fn features(&self) -> [f32; N_DIM] {
75+
if self.len == 0 { return [0.0; N_DIM]; }
76+
let n = self.len as f32;
77+
let start = if self.len < OBS_WIN { 0 } else { self.idx };
78+
// Sums
79+
let (mut ps, mut ms, mut ns) = (0u32, 0.0f32, 0u32);
80+
for i in 0..self.len { ps += self.pres[i] as u32; ms += self.motion[i]; ns += self.persons[i] as u32; }
81+
let avg_m = ms / n;
82+
// Variance of motion
83+
let mut mv = 0.0f32;
84+
for i in 0..self.len { let d = self.motion[i] - avg_m; mv += d * d; }
85+
// Transitions
86+
let mut tr = 0u32;
87+
let mut prev_p = self.pres[start];
88+
for s in 1..self.len {
89+
let cur = self.pres[(start + s) % OBS_WIN];
90+
if cur != prev_p { tr += 1; }
91+
prev_p = cur;
92+
}
93+
// Dwell time (avg consecutive presence run length)
94+
let (mut dsum, mut druns, mut rlen) = (0u32, 0u32, 0u32);
95+
for s in 0..self.len {
96+
if self.pres[(start + s) % OBS_WIN] == 1 { rlen += 1; }
97+
else if rlen > 0 { dsum += rlen; druns += 1; rlen = 0; }
98+
}
99+
if rlen > 0 { dsum += rlen; druns += 1; }
100+
let dwell = if druns > 0 { dsum as f32 / druns as f32 } else { 0.0 };
101+
[ps as f32 / n, avg_m, ns as f32 / n, mv / n, tr as f32 / n, dwell]
102+
}
103+
}
104+
105+
/// Behavioral profiler with Mahalanobis-inspired anomaly scoring.
106+
pub struct BehavioralProfiler {
107+
stats: [Welford; N_DIM],
108+
obs: ObsWindow,
109+
mature: bool,
110+
frame_count: u32,
111+
obs_cycles: u32,
112+
cooldown: u16,
113+
anomaly_count: u32,
114+
}
115+
116+
impl BehavioralProfiler {
117+
pub const fn new() -> Self {
118+
Self {
119+
stats: [Welford::new(); N_DIM], obs: ObsWindow::new(),
120+
mature: false, frame_count: 0, obs_cycles: 0, cooldown: 0, anomaly_count: 0,
121+
}
122+
}
123+
124+
/// Process one frame. Returns `(event_id, value)` pairs.
125+
pub fn process_frame(&mut self, present: bool, motion: f32, n_persons: u8) -> &[(i32, f32)] {
126+
self.frame_count += 1;
127+
self.cooldown = self.cooldown.saturating_sub(1);
128+
self.obs.push(present, motion, n_persons);
129+
130+
static mut EV: [(i32, f32); 4] = [(0, 0.0); 4];
131+
let mut ne = 0usize;
132+
133+
if self.frame_count % (OBS_WIN as u32) == 0 && self.obs.len == OBS_WIN {
134+
let feat = self.obs.features();
135+
self.obs_cycles += 1;
136+
137+
if !self.mature {
138+
for d in 0..N_DIM { self.stats[d].update(feat[d]); }
139+
if self.obs_cycles >= LEARNING_FRAMES / (OBS_WIN as u32) {
140+
self.mature = true;
141+
let days = self.frame_count as f32 / (20.0 * 86400.0);
142+
unsafe { EV[ne] = (EVENT_PROFILE_MATURITY, days); }
143+
ne += 1;
144+
}
145+
} else {
146+
// Score before updating.
147+
let mut zsq = 0.0f32;
148+
let mut hi_z = 0u32;
149+
let (mut max_z, mut max_d) = (0.0f32, 0usize);
150+
for d in 0..N_DIM {
151+
let z = self.stats[d].z_score(feat[d]);
152+
zsq += z * z;
153+
if z > NOVEL_Z { hi_z += 1; }
154+
if z > max_z { max_z = z; max_d = d; }
155+
}
156+
let cz = sqrtf(zsq / N_DIM as f32);
157+
for d in 0..N_DIM { self.stats[d].update(feat[d]); }
158+
159+
if self.cooldown == 0 {
160+
if cz > ANOMALY_Z {
161+
self.anomaly_count += 1;
162+
unsafe { EV[ne] = (EVENT_BEHAVIOR_ANOMALY, cz); } ne += 1;
163+
if ne < 4 { unsafe { EV[ne] = (EVENT_PROFILE_DEVIATION, max_d as f32); } ne += 1; }
164+
self.cooldown = COOLDOWN;
165+
}
166+
if hi_z >= NOVEL_MIN && ne < 4 {
167+
unsafe { EV[ne] = (EVENT_NOVEL_PATTERN, hi_z as f32); } ne += 1;
168+
if self.cooldown == 0 { self.cooldown = COOLDOWN; }
169+
}
170+
}
171+
}
172+
}
173+
174+
// Periodic maturity report.
175+
if self.mature && self.frame_count % MATURITY_INTERVAL == 0 && ne < 4 {
176+
unsafe { EV[ne] = (EVENT_PROFILE_MATURITY, self.frame_count as f32 / (20.0 * 86400.0)); }
177+
ne += 1;
178+
}
179+
unsafe { &EV[..ne] }
180+
}
181+
182+
pub fn is_mature(&self) -> bool { self.mature }
183+
pub fn frame_count(&self) -> u32 { self.frame_count }
184+
pub fn total_anomalies(&self) -> u32 { self.anomaly_count }
185+
pub fn dim_mean(&self, d: usize) -> f32 { if d < N_DIM { self.stats[d].mean } else { 0.0 } }
186+
pub fn dim_variance(&self, d: usize) -> f32 { if d < N_DIM { self.stats[d].variance() } else { 0.0 } }
187+
}
188+
189+
#[cfg(test)]
190+
mod tests {
191+
use super::*;
192+
193+
#[test]
194+
fn test_init() {
195+
let bp = BehavioralProfiler::new();
196+
assert_eq!(bp.frame_count(), 0);
197+
assert!(!bp.is_mature());
198+
assert_eq!(bp.total_anomalies(), 0);
199+
}
200+
201+
#[test]
202+
fn test_welford() {
203+
let mut w = Welford::new();
204+
for _ in 0..100 { w.update(5.0); }
205+
assert!((w.mean - 5.0).abs() < 0.001);
206+
assert!(w.variance() < 0.001);
207+
// Z-score at mean ~ 0, far from mean > 3.
208+
assert!(w.z_score(5.0) < 0.1);
209+
}
210+
211+
#[test]
212+
fn test_welford_z_far() {
213+
let mut w = Welford::new();
214+
for i in 1..=100 { w.update(i as f32); }
215+
assert!(w.z_score(200.0) > 3.0);
216+
}
217+
218+
#[test]
219+
fn test_learning_phase() {
220+
let mut bp = BehavioralProfiler::new();
221+
for _ in 0..LEARNING_FRAMES { bp.process_frame(true, 0.5, 1); }
222+
assert!(bp.is_mature());
223+
}
224+
225+
#[test]
226+
fn test_normal_no_anomaly() {
227+
let mut bp = BehavioralProfiler::new();
228+
for _ in 0..LEARNING_FRAMES { bp.process_frame(true, 0.5, 1); }
229+
for _ in 0..2000 {
230+
let ev = bp.process_frame(true, 0.5, 1);
231+
for &(t, _) in ev { assert_ne!(t, EVENT_BEHAVIOR_ANOMALY); }
232+
}
233+
assert_eq!(bp.total_anomalies(), 0);
234+
}
235+
236+
#[test]
237+
fn test_anomaly_detection() {
238+
let mut bp = BehavioralProfiler::new();
239+
// Learning phase: vary motion energy across observation windows so that
240+
// Welford stats accumulate non-zero variance. Each observation window
241+
// is OBS_WIN=200 frames; we need LEARNING_FRAMES/OBS_WIN = 5 cycles.
242+
// By giving each window a different motion level, inter-window variance
243+
// builds up, enabling z_score to detect anomalies after maturity.
244+
for i in 0..LEARNING_FRAMES {
245+
// Vary presence AND motion across observation windows so all
246+
// dimensions build non-zero variance.
247+
let window_id = i / (OBS_WIN as u32);
248+
let pres = window_id % 2 != 0;
249+
let mot = 0.1 + (window_id as f32) * 0.05;
250+
let per = (window_id % 3) as u8;
251+
bp.process_frame(pres, mot, per);
252+
}
253+
assert!(bp.is_mature());
254+
let mut found = false;
255+
// Now inject a dramatically different behaviour.
256+
for _ in 0..4000 {
257+
let ev = bp.process_frame(true, 10.0, 5);
258+
if ev.iter().any(|&(t,_)| t == EVENT_BEHAVIOR_ANOMALY) { found = true; }
259+
}
260+
assert!(found, "dramatic change should trigger anomaly");
261+
}
262+
263+
#[test]
264+
fn test_obs_features() {
265+
let mut obs = ObsWindow::new();
266+
for _ in 0..OBS_WIN { obs.push(true, 1.0, 2); }
267+
let f = obs.features();
268+
assert!((f[0] - 1.0).abs() < 0.01); // presence_rate
269+
assert!((f[1] - 1.0).abs() < 0.01); // avg_motion
270+
assert!((f[2] - 2.0).abs() < 0.01); // avg_n_persons
271+
assert!(f[3] < 0.01); // activity_variance
272+
assert!(f[4] < 0.01); // transition_rate
273+
}
274+
275+
#[test]
276+
fn test_maturity_event() {
277+
let mut bp = BehavioralProfiler::new();
278+
let mut found = false;
279+
for _ in 0..LEARNING_FRAMES {
280+
let ev = bp.process_frame(true, 0.5, 1);
281+
if ev.iter().any(|&(t,_)| t == EVENT_PROFILE_MATURITY) { found = true; }
282+
}
283+
assert!(found, "maturity event should be emitted");
284+
}
285+
}

0 commit comments

Comments
 (0)