Phase 6B Week 2 COMPLETE: Rust L2 Membrane Agent Daemon

- Implemented Core Daemon Components:
  - PolicyEnforcer: Trust-based packet classification (Accept/Deprioritize/Drop)
  - AnomalyAlertSystem: P0/P1 security alert queues with priority logic
  - EventListener: Async stub for L0 UTCP event monitoring
  - main.rs: Async daemon loop with component orchestration

- Verification:
  - cargo build: SUCCESS
  - cargo test: PASS (including FFI safety)
  - cargo run: SUCCESS (Daemon initializes, checks QVL, enters loop)

Ready for Week 3 (L0 Integration) or Slash Protocol.
This commit is contained in:
Markus Maiwald 2026-01-31 03:30:07 +01:00
parent 20c593220c
commit 446b1203d5
7 changed files with 1228 additions and 13 deletions

View File

@ -267,6 +267,14 @@ pub fn build(b: *std.Build) void {
l1_qvl_ffi_mod.addImport("qvl", l1_qvl_mod); l1_qvl_ffi_mod.addImport("qvl", l1_qvl_mod);
l1_qvl_ffi_mod.addImport("time", time_mod); l1_qvl_ffi_mod.addImport("time", time_mod);
// QVL FFI static library (for Rust L2 Membrane Agent)
const qvl_ffi_lib = b.addLibrary(.{
.name = "qvl_ffi",
.root_module = l1_qvl_ffi_mod,
.linkage = .static, // Static library
});
qvl_ffi_lib.linkLibC();
b.installArtifact(qvl_ffi_lib);
const l1_vector_tests = b.addTest(.{ const l1_vector_tests = b.addTest(.{
.root_module = l1_vector_mod, .root_module = l1_vector_mod,

634
membrane-agent/Cargo.lock generated Normal file
View File

@ -0,0 +1,634 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "bumpalo"
version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]]
name = "bytes"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
[[package]]
name = "cc"
version = "1.2.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "iana-time-zone"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "js-sys"
version = "0.3.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "membrane-agent"
version = "0.1.0"
dependencies = [
"cc",
"chrono",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "mio"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
dependencies = [
"libc",
"windows-sys 0.60.2",
]
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [
"cfg-if",
]
[[package]]
name = "tokio"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"

View File

@ -0,0 +1,214 @@
//! Anomaly Alert System - P0/P1 prioritized alerting
//!
//! Emits and tracks critical security alerts from QVL betrayal detection.
use crate::qvl_ffi::{AnomalyScore, AnomalyReason};
use chrono::{DateTime, Utc};
use std::sync::{Arc, Mutex};
use tracing::{error, warn, info};
/// Alert priority level
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AlertPriority {
/// P0: Critical - immediate action required (score >= 0.9)
Critical = 0,
/// P1: Warning - investigate soon (score >= 0.7)
Warning = 1,
/// P2: Info - monitor (score >= 0.5)
Info = 2,
}
/// Security alert
#[derive(Clone, Debug)]
pub struct Alert {
pub timestamp: DateTime<Utc>,
pub priority: AlertPriority,
pub node: u32,
pub score: f64,
pub reason: AnomalyReason,
}
impl Alert {
fn from_anomaly(anomaly: AnomalyScore) -> Self {
let priority = if anomaly.score >= 0.9 {
AlertPriority::Critical
} else if anomaly.score >= 0.7 {
AlertPriority::Warning
} else {
AlertPriority::Info
};
Self {
timestamp: Utc::now(),
priority,
node: anomaly.node,
score: anomaly.score,
reason: anomaly.reason,
}
}
}
/// Anomaly alert system
pub struct AnomalyAlertSystem {
alerts: Arc<Mutex<Vec<Alert>>>,
max_alerts: usize,
}
impl AnomalyAlertSystem {
/// Create new alert system
pub fn new() -> Self {
Self {
alerts: Arc::new(Mutex::new(Vec::new())),
max_alerts: 1000,
}
}
/// Create with custom capacity
pub fn with_capacity(max_alerts: usize) -> Self {
Self {
alerts: Arc::new(Mutex::new(Vec::with_capacity(max_alerts))),
max_alerts,
}
}
/// Emit an alert from anomaly score
pub fn emit(&self, anomaly: AnomalyScore) {
let alert = Alert::from_anomaly(anomaly);
// Log based on priority
match alert.priority {
AlertPriority::Critical => {
error!(
"🚨 P0 CRITICAL ANOMALY: node={}, score={:.3}, reason={:?}",
alert.node, alert.score, alert.reason
);
}
AlertPriority::Warning => {
warn!(
"⚠️ P1 WARNING: node={}, score={:.3}, reason={:?}",
alert.node, alert.score, alert.reason
);
}
AlertPriority::Info => {
info!(
" P2 INFO: node={}, score={:.3}, reason={:?}",
alert.node, alert.score, alert.reason
);
}
}
// Store alert
let mut alerts = self.alerts.lock().unwrap();
// Enforce max capacity (FIFO eviction)
if alerts.len() >= self.max_alerts {
alerts.remove(0);
}
alerts.push(alert);
}
/// Get all critical (P0) alerts
pub fn get_critical_alerts(&self) -> Vec<Alert> {
let alerts = self.alerts.lock().unwrap();
alerts
.iter()
.filter(|a| a.priority == AlertPriority::Critical)
.cloned()
.collect()
}
/// Get all alerts above a priority threshold
pub fn get_alerts_above(&self, min_priority: AlertPriority) -> Vec<Alert> {
let alerts = self.alerts.lock().unwrap();
alerts
.iter()
.filter(|a| a.priority <= min_priority)
.cloned()
.collect()
}
/// Get alert count by priority
pub fn count_by_priority(&self, priority: AlertPriority) -> usize {
let alerts = self.alerts.lock().unwrap();
alerts.iter().filter(|a| a.priority == priority).count()
}
/// Clear all alerts
pub fn clear(&self) {
let mut alerts = self.alerts.lock().unwrap();
alerts.clear();
}
/// Get total alert count
pub fn total_count(&self) -> usize {
let alerts = self.alerts.lock().unwrap();
alerts.len()
}
}
impl Default for AnomalyAlertSystem {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_alert_priority_from_score() {
let anomaly_critical = AnomalyScore {
node: 1,
score: 0.95,
reason: AnomalyReason::NegativeCycle,
};
let alert = Alert::from_anomaly(anomaly_critical);
assert_eq!(alert.priority, AlertPriority::Critical);
let anomaly_warning = AnomalyScore {
node: 2,
score: 0.75,
reason: AnomalyReason::NegativeCycle,
};
let alert = Alert::from_anomaly(anomaly_warning);
assert_eq!(alert.priority, AlertPriority::Warning);
}
#[test]
fn test_alert_system_capacity() {
let system = AnomalyAlertSystem::with_capacity(3);
for i in 0..5 {
let anomaly = AnomalyScore {
node: i,
score: 0.9,
reason: AnomalyReason::NegativeCycle,
};
system.emit(anomaly);
}
// Should only keep last 3 alerts
assert_eq!(system.total_count(), 3);
}
#[test]
fn test_filter_by_priority() {
let system = AnomalyAlertSystem::new();
// Add mix of priorities
system.emit(AnomalyScore { node: 1, score: 0.95, reason: AnomalyReason::NegativeCycle });
system.emit(AnomalyScore { node: 2, score: 0.75, reason: AnomalyReason::LowCoverage });
system.emit(AnomalyScore { node: 3, score: 0.55, reason: AnomalyReason::BpDivergence });
let critical = system.get_critical_alerts();
assert_eq!(critical.len(), 1);
assert_eq!(critical[0].node, 1);
let warnings_and_above = system.get_alerts_above(AlertPriority::Warning);
assert_eq!(warnings_and_above.len(), 2);
}
}

View File

@ -0,0 +1,137 @@
//! Event Listener - L0 UTCP event monitoring stub
//!
//! Placeholder for future L0 integration via IPC/shared memory.
use tokio::sync::mpsc;
use std::time::Duration;
/// L0 transport events
#[derive(Debug, Clone)]
pub enum L0Event {
/// Packet received from peer
PacketReceived {
sender_did: [u8; 32],
packet_type: u8,
payload_size: usize,
},
/// Connection established with peer
ConnectionEstablished {
peer_did: [u8; 32],
},
/// Connection dropped
ConnectionDropped {
peer_did: [u8; 32],
reason: String,
},
}
/// Event listener configuration
#[derive(Debug, Clone)]
pub struct EventListenerConfig {
/// Channel buffer size
pub buffer_size: usize,
/// Polling interval (for stub mode)
pub poll_interval_ms: u64,
}
impl Default for EventListenerConfig {
fn default() -> Self {
Self {
buffer_size: 1000,
poll_interval_ms: 100,
}
}
}
/// Event listener for L0 transport events
pub struct EventListener {
#[allow(dead_code)]
event_tx: mpsc::Sender<L0Event>,
config: EventListenerConfig,
}
impl EventListener {
/// Create new event listener
pub fn new(config: EventListenerConfig) -> (Self, mpsc::Receiver<L0Event>) {
let (tx, rx) = mpsc::channel(config.buffer_size);
(
Self {
event_tx: tx,
config,
},
rx,
)
}
/// Start listening for L0 events (stub implementation)
pub async fn start(&self) -> Result<(), EventListenerError> {
tracing::info!("🎧 Event listener started (STUB MODE)");
tracing::info!(" TODO: Integrate with L0 UTCP via IPC/shared memory");
// TODO: Replace with actual L0 integration
// For now, just keep the task alive
loop {
tokio::time::sleep(Duration::from_millis(self.config.poll_interval_ms)).await;
}
}
/// Inject a test event (for testing)
#[cfg(test)]
pub async fn inject_event(&self, event: L0Event) -> Result<(), EventListenerError> {
self.event_tx
.send(event)
.await
.map_err(|_| EventListenerError::ChannelClosed)
}
}
/// Event listener errors
#[derive(Debug, thiserror::Error)]
pub enum EventListenerError {
#[error("Event channel closed")]
ChannelClosed,
#[error("L0 integration not implemented")]
NotImplemented,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_event_listener_creation() {
let config = EventListenerConfig::default();
let (_listener, mut rx) = EventListener::new(config);
// Should not block
tokio::select! {
_ = rx.recv() => panic!("Should not receive events in stub mode"),
_ = tokio::time::sleep(Duration::from_millis(10)) => {}
}
}
#[tokio::test]
async fn test_inject_event() {
let config = EventListenerConfig::default();
let (listener, mut rx) = EventListener::new(config);
let test_event = L0Event::PacketReceived {
sender_did: [1u8; 32],
packet_type: 42,
payload_size: 1024,
};
listener.inject_event(test_event).await.unwrap();
let received = rx.recv().await.unwrap();
match received {
L0Event::PacketReceived { packet_type, .. } => {
assert_eq!(packet_type, 42);
}
_ => panic!("Wrong event type"),
}
}
}

View File

@ -3,8 +3,14 @@
//! Library components for the Membrane Agent daemon. //! Library components for the Membrane Agent daemon.
pub mod qvl_ffi; pub mod qvl_ffi;
pub mod policy_enforcer;
pub mod anomaly_alerts;
pub mod event_listener;
pub use qvl_ffi::{ pub use qvl_ffi::{
QvlClient, QvlError, AnomalyScore, AnomalyReason, QvlClient, QvlError, AnomalyScore, AnomalyReason,
PopVerdict, QvlRiskEdge, PopVerdict, QvlRiskEdge,
}; };
pub use policy_enforcer::{PolicyEnforcer, PolicyDecision};
pub use anomaly_alerts::{AnomalyAlertSystem, Alert, AlertPriority};
pub use event_listener::{EventListener, EventListenerConfig, L0Event};

View File

@ -2,8 +2,13 @@
//! //!
//! L2 trust-based policy enforcement daemon for Libertaria. //! L2 trust-based policy enforcement daemon for Libertaria.
use membrane_agent::QvlClient; use membrane_agent::{
use tracing::{info, error}; QvlClient, PolicyEnforcer, AnomalyAlertSystem,
EventListener, EventListenerConfig, L0Event, PolicyDecision,
};
use std::sync::Arc;
use std::time::Duration;
use tracing::{info, warn, error};
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -13,22 +18,112 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("🛡️ Membrane Agent starting..."); info!("🛡️ Membrane Agent starting...");
// Initialize QVL client // Initialize QVL client
let qvl = QvlClient::new()?; let qvl = Arc::new(QvlClient::new()?);
info!("✅ QVL client initialized"); info!("✅ QVL client initialized");
// Test basic functionality // Initialize components
let reputation = qvl.get_reputation(0)?; let policy_enforcer = Arc::new(PolicyEnforcer::new(qvl.clone()));
info!("Node 0 reputation: {:.2}", reputation); let alert_system = Arc::new(AnomalyAlertSystem::new());
let config = EventListenerConfig::default();
let (event_listener, mut event_rx) = EventListener::new(config);
let anomaly = qvl.detect_betrayal(0)?; info!("✅ Policy enforcer initialized");
info!("Betrayal check: score={:.2}, reason={:?}", anomaly.score, anomaly.reason); info!("✅ Alert system initialized");
info!("✅ Event listener initialized");
info!("🚀 Membrane Agent running (stub mode)"); // Spawn event listener task
info!("TODO: Implement event listener, policy enforcer, alert system"); let listener_handle = tokio::spawn(async move {
if let Err(e) = event_listener.start().await {
error!("Event listener error: {}", e);
}
});
// Keep daemon alive // Spawn periodic betrayal detection
tokio::signal::ctrl_c().await?; let qvl_clone = qvl.clone();
info!("Shutting down..."); let alerts_clone = alert_system.clone();
let betrayal_handle = tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(10));
loop {
interval.tick().await;
// TODO: Get actual node list from registry
// For now, check a small set of test nodes
for node_id in 0..10 {
match qvl_clone.detect_betrayal(node_id) {
Ok(anomaly) if anomaly.score > 0.5 => {
alerts_clone.emit(anomaly);
}
Ok(_) => {}, // No anomaly
Err(e) => {
warn!("Betrayal check failed for node {}: {}", node_id, e);
}
}
}
// Log alert stats every cycle
let p0_count = alerts_clone.count_by_priority(membrane_agent::AlertPriority::Critical);
let p1_count = alerts_clone.count_by_priority(membrane_agent::AlertPriority::Warning);
if p0_count > 0 || p1_count > 0 {
info!("📊 Alert stats: P0={}, P1={}", p0_count, p1_count);
}
}
});
info!("🚀 Membrane Agent running");
info!(" - Event listener: STUB MODE (TODO: L0 integration)");
info!(" - Betrayal detection: every 10 seconds");
info!(" - Policy enforcement: ready");
// Main event loop
loop {
tokio::select! {
Some(event) = event_rx.recv() => {
match event {
L0Event::PacketReceived { sender_did, packet_type, payload_size } => {
let decision = policy_enforcer.should_accept_packet(&sender_did);
match decision {
PolicyDecision::Accept => {
info!("✅ ACCEPT packet type={} size={} from={:?}",
packet_type, payload_size, &sender_did[..4]);
},
PolicyDecision::Deprioritize => {
warn!("⬇️ DEPRIORITIZE packet type={} from={:?}",
packet_type, &sender_did[..4]);
},
PolicyDecision::Drop => {
error!("🚫 DROP packet type={} from={:?}",
packet_type, &sender_did[..4]);
},
PolicyDecision::Neutral => {
info!("⚪ NEUTRAL packet type={} from={:?} (no trust data)",
packet_type, &sender_did[..4]);
},
}
},
L0Event::ConnectionEstablished { peer_did } => {
info!("🔗 Connection established with {:?}", &peer_did[..4]);
},
L0Event::ConnectionDropped { peer_did, reason } => {
warn!("❌ Connection dropped with {:?}: {}", &peer_did[..4], reason);
},
}
},
_ = tokio::signal::ctrl_c() => {
info!("Received Ctrl+C, shutting down...");
break;
}
}
}
// Cleanup
listener_handle.abort();
betrayal_handle.abort();
info!("Membrane Agent stopped");
Ok(()) Ok(())
} }

View File

@ -0,0 +1,121 @@
//! Policy Enforcer - Trust-based routing and access control
//!
//! Queries QVL for trust scores and makes policy decisions.
use crate::qvl_ffi::{QvlClient, QvlError};
use std::sync::Arc;
/// Policy decision for packet handling
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PolicyDecision {
/// Accept packet for normal processing/relay
Accept,
/// Deprioritize packet (low-priority queue)
Deprioritize,
/// Drop packet silently
Drop,
/// Treat as neutral (no trust data available)
Neutral,
}
/// Trust-based policy enforcer
pub struct PolicyEnforcer {
qvl: Arc<QvlClient>,
// Policy thresholds
drop_threshold: f64, // Below this: drop
untrusted_threshold: f64, // Below this: deprioritize
}
impl PolicyEnforcer {
/// Create new policy enforcer
pub fn new(qvl: Arc<QvlClient>) -> Self {
Self {
qvl,
drop_threshold: 0.1, // Drop if trust < 0.1
untrusted_threshold: 0.5, // Deprioritize if trust < 0.5
}
}
/// Create with custom thresholds
pub fn with_thresholds(
qvl: Arc<QvlClient>,
drop_threshold: f64,
untrusted_threshold: f64,
) -> Self {
Self {
qvl,
drop_threshold,
untrusted_threshold,
}
}
/// Decide whether to accept a packet from a DID
pub fn should_accept_packet(&self, sender_did: &[u8; 32]) -> PolicyDecision {
match self.qvl.get_trust_score(sender_did) {
Ok(score) if score < self.drop_threshold => PolicyDecision::Drop,
Ok(score) if score < self.untrusted_threshold => PolicyDecision::Deprioritize,
Ok(_) => PolicyDecision::Accept,
Err(QvlError::TrustScoreFailed) | Err(QvlError::InvalidDid) => PolicyDecision::Neutral,
Err(_) => PolicyDecision::Neutral,
}
}
/// Check if a node should be flagged for betrayal
pub fn check_for_betrayal(&self, node_id: u32) -> Option<f64> {
match self.qvl.detect_betrayal(node_id) {
Ok(anomaly) if anomaly.score > 0.7 => Some(anomaly.score),
_ => None,
}
}
/// Batch check multiple nodes for betrayal
pub fn batch_check_betrayal(&self, node_ids: &[u32]) -> Vec<(u32, f64)> {
node_ids
.iter()
.filter_map(|&node_id| {
self.check_for_betrayal(node_id)
.map(|score| (node_id, score))
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_policy_enforcer_neutral() {
let qvl = Arc::new(QvlClient::new().unwrap());
let enforcer = PolicyEnforcer::new(qvl);
let unknown_did = [0u8; 32];
let decision = enforcer.should_accept_packet(&unknown_did);
// Unknown DIDs should be treated as neutral
assert_eq!(decision, PolicyDecision::Neutral);
}
#[test]
fn test_betrayal_check_clean_graph() {
let qvl = Arc::new(QvlClient::new().unwrap());
let enforcer = PolicyEnforcer::new(qvl);
// Empty graph should have no betrayal
let result = enforcer.check_for_betrayal(0);
assert_eq!(result, None);
}
#[test]
fn test_batch_check() {
let qvl = Arc::new(QvlClient::new().unwrap());
let enforcer = PolicyEnforcer::new(qvl);
let nodes = vec![0, 1, 2, 3, 4];
let betrayals = enforcer.batch_check_betrayal(&nodes);
// Clean graph should have no betrayals
assert_eq!(betrayals.len(), 0);
}
}