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    /// Trust level to assume when a skill has no entry in the trust map.
47    ///
48    /// A missing entry means "never classified yet" (e.g. persistence not wired, or a
49    /// transient trust-map read failure), not "known untrusted" — callers must not fall
50    /// back to [`SkillTrustLevel::default`] ([`Quarantined`](Self::Quarantined)) for this
51    /// case, as that would misclassify legitimately trusted, already-vetted skills.
52    ///
53    /// # Examples
54    ///
55    /// ```rust
56    /// use zeph_common::SkillTrustLevel;
57    ///
58    /// let trust_levels: std::collections::HashMap<String, SkillTrustLevel> =
59    ///     std::collections::HashMap::new();
60    /// let trust = trust_levels
61    ///     .get("some-skill")
62    ///     .copied()
63    ///     .unwrap_or(SkillTrustLevel::MISSING_ENTRY_FALLBACK);
64    /// assert_eq!(trust, SkillTrustLevel::Trusted);
65    /// ```
66    pub const MISSING_ENTRY_FALLBACK: Self = Self::Trusted;
67
68    /// Ordered severity: lower value = more trusted.
69    ///
70    /// # Examples
71    ///
72    /// ```rust
73    /// use zeph_common::SkillTrustLevel;
74    ///
75    /// assert!(SkillTrustLevel::Trusted.severity() < SkillTrustLevel::Blocked.severity());
76    /// ```
77    #[must_use]
78    pub const fn severity(self) -> u8 {
79        match self {
80            Self::Trusted => 0,
81            Self::Verified => 1,
82            Self::Quarantined => 2,
83            Self::Blocked => 3,
84        }
85    }
86
87    /// Returns the least-trusted (highest severity) of two levels.
88    ///
89    /// # Examples
90    ///
91    /// ```rust
92    /// use zeph_common::SkillTrustLevel;
93    ///
94    /// let result = SkillTrustLevel::Trusted.min_trust(SkillTrustLevel::Quarantined);
95    /// assert_eq!(result, SkillTrustLevel::Quarantined);
96    /// ```
97    #[must_use]
98    pub const fn min_trust(self, other: Self) -> Self {
99        if self.severity() >= other.severity() {
100            self
101        } else {
102            other
103        }
104    }
105
106    /// Returns the string representation used for database storage.
107    #[must_use]
108    pub const fn as_str(self) -> &'static str {
109        match self {
110            Self::Trusted => "trusted",
111            Self::Verified => "verified",
112            Self::Quarantined => "quarantined",
113            Self::Blocked => "blocked",
114        }
115    }
116
117    /// Returns `true` if the level is not `Blocked`.
118    ///
119    /// # Examples
120    ///
121    /// ```rust
122    /// use zeph_common::SkillTrustLevel;
123    ///
124    /// assert!(SkillTrustLevel::Quarantined.is_active());
125    /// assert!(!SkillTrustLevel::Blocked.is_active());
126    /// ```
127    #[must_use]
128    pub const fn is_active(self) -> bool {
129        !matches!(self, Self::Blocked)
130    }
131}
132
133impl FromStr for SkillTrustLevel {
134    type Err = String;
135
136    fn from_str(s: &str) -> Result<Self, Self::Err> {
137        match s {
138            "trusted" => Ok(Self::Trusted),
139            "verified" => Ok(Self::Verified),
140            "quarantined" => Ok(Self::Quarantined),
141            "blocked" => Ok(Self::Blocked),
142            other => Err(format!(
143                "unknown trust level '{other}'; expected: trusted, verified, quarantined, blocked"
144            )),
145        }
146    }
147}
148
149impl fmt::Display for SkillTrustLevel {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match self {
152            Self::Trusted => f.write_str("trusted"),
153            Self::Verified => f.write_str("verified"),
154            Self::Quarantined => f.write_str("quarantined"),
155            Self::Blocked => f.write_str("blocked"),
156        }
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn severity_ordering() {
166        assert!(SkillTrustLevel::Trusted.severity() < SkillTrustLevel::Verified.severity());
167        assert!(SkillTrustLevel::Verified.severity() < SkillTrustLevel::Quarantined.severity());
168        assert!(SkillTrustLevel::Quarantined.severity() < SkillTrustLevel::Blocked.severity());
169    }
170
171    #[test]
172    fn min_trust_picks_least_trusted() {
173        assert_eq!(
174            SkillTrustLevel::Trusted.min_trust(SkillTrustLevel::Quarantined),
175            SkillTrustLevel::Quarantined
176        );
177        assert_eq!(
178            SkillTrustLevel::Blocked.min_trust(SkillTrustLevel::Trusted),
179            SkillTrustLevel::Blocked
180        );
181    }
182
183    #[test]
184    fn is_active() {
185        assert!(SkillTrustLevel::Trusted.is_active());
186        assert!(SkillTrustLevel::Verified.is_active());
187        assert!(SkillTrustLevel::Quarantined.is_active());
188        assert!(!SkillTrustLevel::Blocked.is_active());
189    }
190
191    #[test]
192    fn default_is_quarantined() {
193        assert_eq!(SkillTrustLevel::default(), SkillTrustLevel::Quarantined);
194    }
195
196    #[test]
197    fn display() {
198        assert_eq!(SkillTrustLevel::Trusted.to_string(), "trusted");
199        assert_eq!(SkillTrustLevel::Blocked.to_string(), "blocked");
200        assert_eq!(SkillTrustLevel::Quarantined.to_string(), "quarantined");
201        assert_eq!(SkillTrustLevel::Verified.to_string(), "verified");
202    }
203
204    #[test]
205    fn serde_roundtrip() {
206        let level = SkillTrustLevel::Quarantined;
207        let json = serde_json::to_string(&level).unwrap();
208        assert_eq!(json, "\"quarantined\"");
209        let back: SkillTrustLevel = serde_json::from_str(&json).unwrap();
210        assert_eq!(back, level);
211    }
212
213    #[test]
214    fn min_trust_same_level_returns_self() {
215        assert_eq!(
216            SkillTrustLevel::Verified.min_trust(SkillTrustLevel::Verified),
217            SkillTrustLevel::Verified
218        );
219    }
220}