Skip to main content

zeph_common/
trust_level.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Trust tier enum for skill execution permissions.
5//!
6//! [`SkillTrustLevel`] is the single source of truth for trust-level semantics across all
7//! Zeph crates. It lives in `zeph-common` so both `zeph-skills` and `zeph-tools` can depend
8//! on it without introducing a circular dependency.
9
10use std::fmt;
11use std::str::FromStr;
12
13use serde::{Deserialize, Serialize};
14
15/// Trust tier controlling what a skill is allowed to do.
16///
17/// The ordering from most to least trusted is: `Trusted` → `Verified` → `Quarantined` →
18/// `Blocked`. Use [`SkillTrustLevel::severity`] to compare levels numerically, or
19/// [`SkillTrustLevel::min_trust`] to find the least-trusted of two levels.
20///
21/// # Examples
22///
23/// ```rust
24/// use zeph_common::SkillTrustLevel;
25///
26/// let level = SkillTrustLevel::Quarantined;
27/// assert!(level.is_active());
28/// assert_eq!(level.severity(), 2);
29/// ```
30#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Deserialize, Serialize)]
31#[serde(rename_all = "lowercase")]
32#[non_exhaustive]
33pub enum SkillTrustLevel {
34    /// Built-in or user-audited skill: full tool access.
35    Trusted,
36    /// Signature or hash verified: default tool access.
37    Verified,
38    /// Newly imported or hash-mismatch: restricted tool access.
39    #[default]
40    Quarantined,
41    /// Explicitly disabled by user or auto-blocked by anomaly detector.
42    Blocked,
43}
44
45impl SkillTrustLevel {
46    /// Ordered severity: lower value = more trusted.
47    ///
48    /// # Examples
49    ///
50    /// ```rust
51    /// use zeph_common::SkillTrustLevel;
52    ///
53    /// assert!(SkillTrustLevel::Trusted.severity() < SkillTrustLevel::Blocked.severity());
54    /// ```
55    #[must_use]
56    pub const fn severity(self) -> u8 {
57        match self {
58            Self::Trusted => 0,
59            Self::Verified => 1,
60            Self::Quarantined => 2,
61            Self::Blocked => 3,
62        }
63    }
64
65    /// Returns the least-trusted (highest severity) of two levels.
66    ///
67    /// # Examples
68    ///
69    /// ```rust
70    /// use zeph_common::SkillTrustLevel;
71    ///
72    /// let result = SkillTrustLevel::Trusted.min_trust(SkillTrustLevel::Quarantined);
73    /// assert_eq!(result, SkillTrustLevel::Quarantined);
74    /// ```
75    #[must_use]
76    pub const fn min_trust(self, other: Self) -> Self {
77        if self.severity() >= other.severity() {
78            self
79        } else {
80            other
81        }
82    }
83
84    /// Returns the string representation used for database storage.
85    #[must_use]
86    pub const fn as_str(self) -> &'static str {
87        match self {
88            Self::Trusted => "trusted",
89            Self::Verified => "verified",
90            Self::Quarantined => "quarantined",
91            Self::Blocked => "blocked",
92        }
93    }
94
95    /// Returns `true` if the level is not `Blocked`.
96    ///
97    /// # Examples
98    ///
99    /// ```rust
100    /// use zeph_common::SkillTrustLevel;
101    ///
102    /// assert!(SkillTrustLevel::Quarantined.is_active());
103    /// assert!(!SkillTrustLevel::Blocked.is_active());
104    /// ```
105    #[must_use]
106    pub const fn is_active(self) -> bool {
107        !matches!(self, Self::Blocked)
108    }
109}
110
111impl FromStr for SkillTrustLevel {
112    type Err = String;
113
114    fn from_str(s: &str) -> Result<Self, Self::Err> {
115        match s {
116            "trusted" => Ok(Self::Trusted),
117            "verified" => Ok(Self::Verified),
118            "quarantined" => Ok(Self::Quarantined),
119            "blocked" => Ok(Self::Blocked),
120            other => Err(format!(
121                "unknown trust level '{other}'; expected: trusted, verified, quarantined, blocked"
122            )),
123        }
124    }
125}
126
127impl fmt::Display for SkillTrustLevel {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        match self {
130            Self::Trusted => f.write_str("trusted"),
131            Self::Verified => f.write_str("verified"),
132            Self::Quarantined => f.write_str("quarantined"),
133            Self::Blocked => f.write_str("blocked"),
134        }
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn severity_ordering() {
144        assert!(SkillTrustLevel::Trusted.severity() < SkillTrustLevel::Verified.severity());
145        assert!(SkillTrustLevel::Verified.severity() < SkillTrustLevel::Quarantined.severity());
146        assert!(SkillTrustLevel::Quarantined.severity() < SkillTrustLevel::Blocked.severity());
147    }
148
149    #[test]
150    fn min_trust_picks_least_trusted() {
151        assert_eq!(
152            SkillTrustLevel::Trusted.min_trust(SkillTrustLevel::Quarantined),
153            SkillTrustLevel::Quarantined
154        );
155        assert_eq!(
156            SkillTrustLevel::Blocked.min_trust(SkillTrustLevel::Trusted),
157            SkillTrustLevel::Blocked
158        );
159    }
160
161    #[test]
162    fn is_active() {
163        assert!(SkillTrustLevel::Trusted.is_active());
164        assert!(SkillTrustLevel::Verified.is_active());
165        assert!(SkillTrustLevel::Quarantined.is_active());
166        assert!(!SkillTrustLevel::Blocked.is_active());
167    }
168
169    #[test]
170    fn default_is_quarantined() {
171        assert_eq!(SkillTrustLevel::default(), SkillTrustLevel::Quarantined);
172    }
173
174    #[test]
175    fn display() {
176        assert_eq!(SkillTrustLevel::Trusted.to_string(), "trusted");
177        assert_eq!(SkillTrustLevel::Blocked.to_string(), "blocked");
178        assert_eq!(SkillTrustLevel::Quarantined.to_string(), "quarantined");
179        assert_eq!(SkillTrustLevel::Verified.to_string(), "verified");
180    }
181
182    #[test]
183    fn serde_roundtrip() {
184        let level = SkillTrustLevel::Quarantined;
185        let json = serde_json::to_string(&level).unwrap();
186        assert_eq!(json, "\"quarantined\"");
187        let back: SkillTrustLevel = serde_json::from_str(&json).unwrap();
188        assert_eq!(back, level);
189    }
190
191    #[test]
192    fn min_trust_same_level_returns_self() {
193        assert_eq!(
194            SkillTrustLevel::Verified.min_trust(SkillTrustLevel::Verified),
195            SkillTrustLevel::Verified
196        );
197    }
198}