Skip to main content

zeph_tools/
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
6use std::fmt;
7use std::str::FromStr;
8
9use serde::{Deserialize, Serialize};
10
11/// Trust tier controlling what a skill is allowed to do.
12#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Deserialize, Serialize)]
13#[serde(rename_all = "lowercase")]
14pub enum TrustLevel {
15    /// Built-in or user-audited skill: full tool access.
16    Trusted,
17    /// Signature or hash verified: default tool access.
18    Verified,
19    /// Newly imported or hash-mismatch: restricted tool access.
20    #[default]
21    Quarantined,
22    /// Explicitly disabled by user or auto-blocked by anomaly detector.
23    Blocked,
24}
25
26impl TrustLevel {
27    /// Ordered severity: lower value = more trusted.
28    #[must_use]
29    pub fn severity(self) -> u8 {
30        match self {
31            Self::Trusted => 0,
32            Self::Verified => 1,
33            Self::Quarantined => 2,
34            Self::Blocked => 3,
35        }
36    }
37
38    /// Returns the least-trusted (highest severity) of two levels.
39    #[must_use]
40    pub fn min_trust(self, other: Self) -> Self {
41        if self.severity() >= other.severity() {
42            self
43        } else {
44            other
45        }
46    }
47
48    #[must_use]
49    pub fn is_active(self) -> bool {
50        !matches!(self, Self::Blocked)
51    }
52}
53
54impl FromStr for TrustLevel {
55    type Err = String;
56
57    fn from_str(s: &str) -> Result<Self, Self::Err> {
58        match s {
59            "trusted" => Ok(Self::Trusted),
60            "verified" => Ok(Self::Verified),
61            "quarantined" => Ok(Self::Quarantined),
62            "blocked" => Ok(Self::Blocked),
63            other => Err(format!(
64                "unknown trust level '{other}'; expected: trusted, verified, quarantined, blocked"
65            )),
66        }
67    }
68}
69
70impl fmt::Display for TrustLevel {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        match self {
73            Self::Trusted => f.write_str("trusted"),
74            Self::Verified => f.write_str("verified"),
75            Self::Quarantined => f.write_str("quarantined"),
76            Self::Blocked => f.write_str("blocked"),
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn severity_ordering() {
87        assert!(TrustLevel::Trusted.severity() < TrustLevel::Verified.severity());
88        assert!(TrustLevel::Verified.severity() < TrustLevel::Quarantined.severity());
89        assert!(TrustLevel::Quarantined.severity() < TrustLevel::Blocked.severity());
90    }
91
92    #[test]
93    fn min_trust_picks_least_trusted() {
94        assert_eq!(
95            TrustLevel::Trusted.min_trust(TrustLevel::Quarantined),
96            TrustLevel::Quarantined
97        );
98        assert_eq!(
99            TrustLevel::Blocked.min_trust(TrustLevel::Trusted),
100            TrustLevel::Blocked
101        );
102    }
103
104    #[test]
105    fn is_active() {
106        assert!(TrustLevel::Trusted.is_active());
107        assert!(TrustLevel::Verified.is_active());
108        assert!(TrustLevel::Quarantined.is_active());
109        assert!(!TrustLevel::Blocked.is_active());
110    }
111
112    #[test]
113    fn default_is_quarantined() {
114        assert_eq!(TrustLevel::default(), TrustLevel::Quarantined);
115    }
116
117    #[test]
118    fn display() {
119        assert_eq!(TrustLevel::Trusted.to_string(), "trusted");
120        assert_eq!(TrustLevel::Blocked.to_string(), "blocked");
121        assert_eq!(TrustLevel::Quarantined.to_string(), "quarantined");
122        assert_eq!(TrustLevel::Verified.to_string(), "verified");
123    }
124
125    #[test]
126    fn serde_roundtrip() {
127        let level = TrustLevel::Quarantined;
128        let json = serde_json::to_string(&level).unwrap();
129        assert_eq!(json, "\"quarantined\"");
130        let back: TrustLevel = serde_json::from_str(&json).unwrap();
131        assert_eq!(back, level);
132    }
133
134    #[test]
135    fn serde_all_variants() {
136        let cases = [
137            (TrustLevel::Trusted, "\"trusted\""),
138            (TrustLevel::Verified, "\"verified\""),
139            (TrustLevel::Quarantined, "\"quarantined\""),
140            (TrustLevel::Blocked, "\"blocked\""),
141        ];
142        for (level, expected_json) in cases {
143            let json = serde_json::to_string(&level).unwrap();
144            assert_eq!(json, expected_json);
145            let back: TrustLevel = serde_json::from_str(&json).unwrap();
146            assert_eq!(back, level);
147        }
148    }
149
150    #[test]
151    fn min_trust_same_level_returns_self() {
152        assert_eq!(
153            TrustLevel::Verified.min_trust(TrustLevel::Verified),
154            TrustLevel::Verified
155        );
156        assert_eq!(
157            TrustLevel::Blocked.min_trust(TrustLevel::Blocked),
158            TrustLevel::Blocked
159        );
160    }
161
162    #[test]
163    fn hash_consistent() {
164        use std::collections::HashSet;
165        let mut set = HashSet::new();
166        set.insert(TrustLevel::Trusted);
167        set.insert(TrustLevel::Verified);
168        set.insert(TrustLevel::Quarantined);
169        set.insert(TrustLevel::Blocked);
170        assert_eq!(set.len(), 4);
171        // Inserting same value again does not grow the set
172        set.insert(TrustLevel::Trusted);
173        assert_eq!(set.len(), 4);
174    }
175}