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")]
32pub enum SkillTrustLevel {
33    /// Built-in or user-audited skill: full tool access.
34    Trusted,
35    /// Signature or hash verified: default tool access.
36    Verified,
37    /// Newly imported or hash-mismatch: restricted tool access.
38    #[default]
39    Quarantined,
40    /// Explicitly disabled by user or auto-blocked by anomaly detector.
41    Blocked,
42}
43
44impl SkillTrustLevel {
45    /// Ordered severity: lower value = more trusted.
46    ///
47    /// # Examples
48    ///
49    /// ```rust
50    /// use zeph_common::SkillTrustLevel;
51    ///
52    /// assert!(SkillTrustLevel::Trusted.severity() < SkillTrustLevel::Blocked.severity());
53    /// ```
54    #[must_use]
55    pub const fn severity(self) -> u8 {
56        match self {
57            Self::Trusted => 0,
58            Self::Verified => 1,
59            Self::Quarantined => 2,
60            Self::Blocked => 3,
61        }
62    }
63
64    /// Returns the least-trusted (highest severity) of two levels.
65    ///
66    /// # Examples
67    ///
68    /// ```rust
69    /// use zeph_common::SkillTrustLevel;
70    ///
71    /// let result = SkillTrustLevel::Trusted.min_trust(SkillTrustLevel::Quarantined);
72    /// assert_eq!(result, SkillTrustLevel::Quarantined);
73    /// ```
74    #[must_use]
75    pub const fn min_trust(self, other: Self) -> Self {
76        if self.severity() >= other.severity() {
77            self
78        } else {
79            other
80        }
81    }
82
83    /// Returns the string representation used for database storage.
84    #[must_use]
85    pub const fn as_str(self) -> &'static str {
86        match self {
87            Self::Trusted => "trusted",
88            Self::Verified => "verified",
89            Self::Quarantined => "quarantined",
90            Self::Blocked => "blocked",
91        }
92    }
93
94    /// Returns `true` if the level is not `Blocked`.
95    ///
96    /// # Examples
97    ///
98    /// ```rust
99    /// use zeph_common::SkillTrustLevel;
100    ///
101    /// assert!(SkillTrustLevel::Quarantined.is_active());
102    /// assert!(!SkillTrustLevel::Blocked.is_active());
103    /// ```
104    #[must_use]
105    pub const fn is_active(self) -> bool {
106        !matches!(self, Self::Blocked)
107    }
108}
109
110impl FromStr for SkillTrustLevel {
111    type Err = String;
112
113    fn from_str(s: &str) -> Result<Self, Self::Err> {
114        match s {
115            "trusted" => Ok(Self::Trusted),
116            "verified" => Ok(Self::Verified),
117            "quarantined" => Ok(Self::Quarantined),
118            "blocked" => Ok(Self::Blocked),
119            other => Err(format!(
120                "unknown trust level '{other}'; expected: trusted, verified, quarantined, blocked"
121            )),
122        }
123    }
124}
125
126impl fmt::Display for SkillTrustLevel {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        match self {
129            Self::Trusted => f.write_str("trusted"),
130            Self::Verified => f.write_str("verified"),
131            Self::Quarantined => f.write_str("quarantined"),
132            Self::Blocked => f.write_str("blocked"),
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn severity_ordering() {
143        assert!(SkillTrustLevel::Trusted.severity() < SkillTrustLevel::Verified.severity());
144        assert!(SkillTrustLevel::Verified.severity() < SkillTrustLevel::Quarantined.severity());
145        assert!(SkillTrustLevel::Quarantined.severity() < SkillTrustLevel::Blocked.severity());
146    }
147
148    #[test]
149    fn min_trust_picks_least_trusted() {
150        assert_eq!(
151            SkillTrustLevel::Trusted.min_trust(SkillTrustLevel::Quarantined),
152            SkillTrustLevel::Quarantined
153        );
154        assert_eq!(
155            SkillTrustLevel::Blocked.min_trust(SkillTrustLevel::Trusted),
156            SkillTrustLevel::Blocked
157        );
158    }
159
160    #[test]
161    fn is_active() {
162        assert!(SkillTrustLevel::Trusted.is_active());
163        assert!(SkillTrustLevel::Verified.is_active());
164        assert!(SkillTrustLevel::Quarantined.is_active());
165        assert!(!SkillTrustLevel::Blocked.is_active());
166    }
167
168    #[test]
169    fn default_is_quarantined() {
170        assert_eq!(SkillTrustLevel::default(), SkillTrustLevel::Quarantined);
171    }
172
173    #[test]
174    fn display() {
175        assert_eq!(SkillTrustLevel::Trusted.to_string(), "trusted");
176        assert_eq!(SkillTrustLevel::Blocked.to_string(), "blocked");
177        assert_eq!(SkillTrustLevel::Quarantined.to_string(), "quarantined");
178        assert_eq!(SkillTrustLevel::Verified.to_string(), "verified");
179    }
180
181    #[test]
182    fn serde_roundtrip() {
183        let level = SkillTrustLevel::Quarantined;
184        let json = serde_json::to_string(&level).unwrap();
185        assert_eq!(json, "\"quarantined\"");
186        let back: SkillTrustLevel = serde_json::from_str(&json).unwrap();
187        assert_eq!(back, level);
188    }
189
190    #[test]
191    fn min_trust_same_level_returns_self() {
192        assert_eq!(
193            SkillTrustLevel::Verified.min_trust(SkillTrustLevel::Verified),
194            SkillTrustLevel::Verified
195        );
196    }
197}