pub mod ledger;
pub mod protocol;
pub mod runtime;
pub mod scheduler;
pub mod telemetry;
pub mod triggers;
pub use ledger::{LedgerStore, MemoryLedger, TaskLedger, TaskRecord, TaskState};
pub use protocol::{
CompletedTask, ComputeCallError, ComputeClient, ComputeProtocolHandler, ExecuteRequest,
ExecutorPolicy, ProbeReply, RejectReason, WasmFetcher,
};
pub use runtime::{
CompiledTask, ExecMetrics, Execution, HostGrants, HostStoreReader, TaskError, WasmRuntime,
};
pub use triggers::{DispatchError, TaskDispatcher, TriggerConfig, TriggerEngine, TriggerRule};
#[cfg(feature = "compute-nn")]
pub mod nn;
#[cfg(feature = "compute-nn")]
pub use nn::NnModelRegistry;
#[cfg(feature = "compute-nn")]
pub use runtime::{NnGrant, NnTarget};
pub use scheduler::{
CapabilityDirectory, ComputeScheduler, Delegated, RedundantOutcome, ReputationBook,
ScheduleError, SchedulerConfig, ScoreWeights,
};
pub use telemetry::{CAPABILITY_TOPIC, CapabilityGossip, TelemetryConfig, TelemetrySampler};
use iroh::EndpointId as NodeId;
use iroh_blobs::Hash;
use serde::{Deserialize, Serialize};
pub const COMPUTE_ALPN: &[u8] = b"/guardian-db/compute/1";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CpuArch {
X86_64,
Aarch64,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Accel {
Gpu,
Npu,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TaskClass {
General,
Media,
Analytics,
Inference,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourceLimits {
pub max_memory_bytes: u64,
pub fuel: u64,
pub timeout_ms: u64,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
max_memory_bytes: 64 * 1024 * 1024,
fuel: 1_000_000_000,
timeout_ms: 10_000,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Placement {
BestAvailable,
Node(NodeId),
Local,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TaskSpec {
pub wasm_hash: Hash,
pub entrypoint: String,
pub class: TaskClass,
pub limits: ResourceLimits,
pub placement: Placement,
pub required_model: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CapabilityVector {
pub node_id: NodeId,
pub cpu_cores: u16,
pub cpu_arch: CpuArch,
pub ram_total_mb: u32,
pub accelerators: Vec<Accel>,
pub cpu_load_pct: u8,
pub ram_free_mb: u32,
pub on_battery: bool,
pub battery_pct: Option<u8>,
pub tasks_running: u8,
pub max_concurrent: u8,
pub accepts: Vec<TaskClass>,
pub nn_models: Vec<String>,
pub issued_at: u64,
}
impl CapabilityVector {
pub fn is_candidate_for(&self, class: TaskClass) -> bool {
self.max_concurrent > 0
&& self.tasks_running < self.max_concurrent
&& self.accepts.contains(&class)
}
pub fn offers_model(&self, name: &str) -> bool {
self.nn_models.iter().any(|m| m == name)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_vector(node_id: NodeId) -> CapabilityVector {
CapabilityVector {
node_id,
cpu_cores: 8,
cpu_arch: CpuArch::X86_64,
ram_total_mb: 16_384,
accelerators: vec![],
cpu_load_pct: 12,
ram_free_mb: 9_000,
on_battery: false,
battery_pct: None,
tasks_running: 1,
max_concurrent: 4,
accepts: vec![TaskClass::General, TaskClass::Media],
nn_models: vec!["doubler".to_string()],
issued_at: 1_760_000_000,
}
}
fn test_node_id() -> NodeId {
iroh::SecretKey::generate().public()
}
#[test]
fn candidate_check_honors_class_and_slots() {
let mut v = sample_vector(test_node_id());
assert!(v.is_candidate_for(TaskClass::Media));
assert!(!v.is_candidate_for(TaskClass::Inference));
v.tasks_running = v.max_concurrent;
assert!(!v.is_candidate_for(TaskClass::Media));
v.tasks_running = 0;
v.max_concurrent = 0;
assert!(!v.is_candidate_for(TaskClass::General));
}
#[test]
fn capability_vector_roundtrips_through_postcard() {
let v = sample_vector(test_node_id());
let bytes = postcard::to_stdvec(&v).expect("serialize");
let back: CapabilityVector = postcard::from_bytes(&bytes).expect("deserialize");
assert_eq!(v, back);
}
#[test]
fn task_spec_roundtrips_through_postcard() {
let spec = TaskSpec {
wasm_hash: Hash::new(b"fake wasm module bytes"),
entrypoint: "generate_thumbnail".into(),
class: TaskClass::Media,
limits: ResourceLimits::default(),
placement: Placement::BestAvailable,
required_model: None,
};
let bytes = postcard::to_stdvec(&spec).expect("serialize");
let back: TaskSpec = postcard::from_bytes(&bytes).expect("deserialize");
assert_eq!(spec, back);
}
}