Skip to main content

deepstrike_sdk/runtime/
os_profile.rs

1use deepstrike_core::runtime::kernel::{
2    ConstraintSpec, KernelInputEvent, PolicyAction, PolicyRule, RateLimitSpec,
3};
4
5use crate::{Error, Result};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct AttentionPolicy {
9    pub max_queue_size: Option<u32>,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct SchedulerBudget {
14    pub max_wall_ms: Option<u64>,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct MemoryWriteRateLimit {
19    pub max_writes: u32,
20    pub window_ms: u64,
21}
22
23impl From<MemoryWriteRateLimit> for (u32, u64) {
24    fn from(limit: MemoryWriteRateLimit) -> Self {
25        (limit.max_writes, limit.window_ms)
26    }
27}
28
29#[derive(Debug, Clone)]
30pub struct GovernancePolicy {
31    pub default_action: Option<PolicyAction>,
32    pub rules: Vec<PolicyRule>,
33    pub vetoed_tools: Vec<String>,
34    pub rate_limits: Vec<RateLimitSpec>,
35    pub constraints: Vec<ConstraintSpec>,
36    /// I5: when true (default), the runner pre-filters denied tools from the schema. Mirrors Node.
37    pub surface_denied_in_system: bool,
38}
39
40impl GovernancePolicy {
41    pub fn allow_all() -> Self {
42        Self {
43            default_action: None,
44            rules: vec![PolicyRule {
45                tool_pattern: "*".to_string(),
46                action: PolicyAction::Allow,
47            }],
48            vetoed_tools: vec![],
49            rate_limits: vec![],
50            constraints: vec![],
51            surface_denied_in_system: true,
52        }
53    }
54
55    pub fn into_kernel_event(self) -> KernelInputEvent {
56        KernelInputEvent::LoadGovernancePolicy {
57            default_action: self.default_action,
58            rules: self.rules,
59            vetoed_tools: self.vetoed_tools,
60            rate_limits: self.rate_limits,
61            constraints: self.constraints,
62        }
63    }
64}
65
66#[derive(Debug, Clone)]
67pub struct NativeOsProfile {
68    pub id: &'static str,
69    pub attention_policy: AttentionPolicy,
70    pub governance_policy: GovernancePolicy,
71}
72
73#[derive(Debug, Clone)]
74pub enum OsProfile {
75    Native,
76    Concrete(NativeOsProfile),
77}
78
79pub const DEFAULT_NATIVE_ATTENTION_POLICY: AttentionPolicy = AttentionPolicy {
80    max_queue_size: Some(64),
81};
82
83pub fn default_native_governance_policy() -> GovernancePolicy {
84    GovernancePolicy::allow_all()
85}
86
87/// I5: bucket tool schemas into allowed/denied per policy. Pure. Mirrors Node `governanceFilterSchema`.
88pub fn governance_filter_schema(
89    tools: &[deepstrike_core::types::message::ToolSchema],
90    policy: &GovernancePolicy,
91) -> (Vec<deepstrike_core::types::message::ToolSchema>, Vec<String>) {
92    let mut allowed = Vec::with_capacity(tools.len());
93    let mut denied = Vec::new();
94    let matches = |pat: &str, name: &str| -> bool {
95        if pat == name {
96            return true;
97        }
98        if let Some(prefix) = pat.strip_suffix('*') {
99            return name.starts_with(prefix);
100        }
101        false
102    };
103    for tool in tools {
104        let name = tool.name.as_str();
105        if policy.vetoed_tools.iter().any(|v| v == name) {
106            denied.push(name.to_string());
107            continue;
108        }
109        let mut action = policy
110            .default_action
111            .clone()
112            .unwrap_or(PolicyAction::Allow);
113        for r in &policy.rules {
114            if matches(&r.tool_pattern, name) {
115                action = r.action.clone();
116            }
117        }
118        if matches!(action, PolicyAction::Deny) {
119            denied.push(name.to_string());
120        } else {
121            allowed.push(tool.clone());
122        }
123    }
124    (allowed, denied)
125}
126
127pub fn os_profile(profile: Option<OsProfile>) -> NativeOsProfile {
128    match profile.unwrap_or(OsProfile::Native) {
129        OsProfile::Native => NativeOsProfile {
130            id: "native",
131            attention_policy: DEFAULT_NATIVE_ATTENTION_POLICY,
132            governance_policy: default_native_governance_policy(),
133        },
134        OsProfile::Concrete(profile) => profile,
135    }
136}
137
138pub fn assert_native_profile(profile: Option<OsProfile>) -> Result<NativeOsProfile> {
139    let resolved = os_profile(profile);
140    if resolved.id != "native" {
141        return Err(Error::Other(format!("Unsupported OS profile: {}", resolved.id)));
142    }
143    if let Some(max_queue_size) = resolved.attention_policy.max_queue_size {
144        if max_queue_size == 0 {
145            return Err(Error::Other(
146                "Invalid native OS profile: AttentionPolicy max_queue_size must be positive"
147                    .to_string(),
148            ));
149        }
150    }
151    Ok(resolved)
152}