1use serde_json::{Value, json};
24use std::collections::BTreeMap;
25use time::OffsetDateTime;
26use time::format_description::well_known::Rfc3339;
27
28use crate::signing::{b64encode, make_key_id};
29
30pub fn tier_order() -> BTreeMap<&'static str, u32> {
36 [
37 ("UNTRUSTED", 0u32),
38 ("ORG_VERIFIED", 1),
39 ("VERIFIED", 2),
40 ("ATTESTED", 3),
41 ("TRUSTED", 4),
42 ]
43 .into_iter()
44 .collect()
45}
46
47#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
48pub enum Tier {
49 Untrusted,
50 OrgVerified,
51 Verified,
52 Attested,
53 Trusted,
54}
55
56impl Tier {
57 pub fn as_str(self) -> &'static str {
58 match self {
59 Tier::Untrusted => "UNTRUSTED",
60 Tier::OrgVerified => "ORG_VERIFIED",
61 Tier::Verified => "VERIFIED",
62 Tier::Attested => "ATTESTED",
63 Tier::Trusted => "TRUSTED",
64 }
65 }
66}
67
68pub type Trust = Value;
71
72pub fn empty_trust() -> Trust {
73 json!({"version": 1, "agents": {}})
74}
75
76pub fn get_tier(trust: &Trust, peer_handle: &str) -> String {
77 trust
78 .get("agents")
79 .and_then(|a| a.get(peer_handle))
80 .and_then(|a| a.get("tier"))
81 .and_then(Value::as_str)
82 .unwrap_or("UNTRUSTED")
83 .to_string()
84}
85
86pub fn add_agent_card_pin(trust: &mut Trust, card: &Value, tier: Option<&str>) {
91 let did = card.get("did").and_then(Value::as_str).unwrap_or_default();
92 let handle = card
97 .get("handle")
98 .and_then(Value::as_str)
99 .map(str::to_string)
100 .unwrap_or_else(|| crate::agent_card::display_handle_from_did(did).to_string());
101 if handle.is_empty() {
102 panic!("card has no resolvable handle (did={did:?})");
103 }
104 let tier = tier.unwrap_or("UNTRUSTED");
105 let now = now_iso();
106
107 let mut public_keys = Vec::new();
108 if let Some(vks) = card.get("verify_keys").and_then(Value::as_object) {
109 for (key_id_full, key_record) in vks {
110 let key_id = key_id_full.strip_prefix("ed25519:").unwrap_or(key_id_full);
112 public_keys.push(json!({
113 "key_id": key_id,
114 "key": key_record.get("key").cloned().unwrap_or(Value::Null),
115 "added_at": now,
116 "active": true,
117 }));
118 }
119 }
120
121 let agents = trust
122 .as_object_mut()
123 .expect("trust must be an object")
124 .entry("agents")
125 .or_insert_with(|| json!({}));
126
127 agents[handle] = json!({
128 "tier": tier,
129 "did": did,
130 "public_keys": public_keys,
131 "card": card.clone(),
132 "pinned_at": now,
133 });
134}
135
136pub fn promote_to_verified(trust: &mut Trust, peer_handle: &str) -> Result<(), String> {
144 let agents = trust
145 .as_object_mut()
146 .ok_or("trust is not an object")?
147 .get_mut("agents")
148 .and_then(Value::as_object_mut)
149 .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
150
151 let agent = agents
152 .get_mut(peer_handle)
153 .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
154
155 let current = agent
156 .get("tier")
157 .and_then(Value::as_str)
158 .unwrap_or("UNTRUSTED")
159 .to_string();
160 if current != "UNTRUSTED" && current != "ORG_VERIFIED" {
161 return Err(format!(
162 "peer {peer_handle:?} already at tier {current:?} — promotion is one-way"
163 ));
164 }
165 agent["tier"] = json!("VERIFIED");
166 agent["verified_at"] = json!(now_iso());
167 Ok(())
168}
169
170pub fn promote_to_org_verified(trust: &mut Trust, peer_handle: &str) -> Result<(), String> {
185 let agents = trust
186 .as_object_mut()
187 .ok_or("trust is not an object")?
188 .get_mut("agents")
189 .and_then(Value::as_object_mut)
190 .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
191
192 let agent = agents
193 .get_mut(peer_handle)
194 .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
195
196 let current = agent
197 .get("tier")
198 .and_then(Value::as_str)
199 .unwrap_or("UNTRUSTED")
200 .to_string();
201 if current != "UNTRUSTED" {
202 return Err(format!(
203 "peer {peer_handle:?} already at tier {current:?} — \
204 org_verified promotion fires from UNTRUSTED only"
205 ));
206 }
207 agent["tier"] = json!("ORG_VERIFIED");
208 agent["org_verified_at"] = json!(now_iso());
209 Ok(())
210}
211
212pub fn add_self_to_trust(trust: &mut Trust, handle: &str, public_key: &[u8]) {
214 let agents = trust
215 .as_object_mut()
216 .expect("trust must be an object")
217 .entry("agents")
218 .or_insert_with(|| json!({}));
219 let key_id = make_key_id(handle, public_key);
220 agents[handle] = json!({
221 "tier": "ATTESTED",
222 "did": crate::agent_card::did_for_with_key(handle, public_key),
223 "public_keys": [{
224 "key_id": key_id,
225 "key": b64encode(public_key),
226 "added_at": now_iso(),
227 "active": true,
228 }],
229 });
230}
231
232fn now_iso() -> String {
233 let now = OffsetDateTime::now_utc();
234 now.format(&Rfc3339)
235 .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string())
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use crate::agent_card::{build_agent_card, sign_agent_card};
242 use crate::signing::generate_keypair;
243
244 #[test]
245 fn empty_trust_shape() {
246 let t = empty_trust();
247 assert_eq!(t["version"], 1);
248 assert!(t["agents"].is_object());
249 assert_eq!(t["agents"].as_object().unwrap().len(), 0);
250 }
251
252 #[test]
253 fn get_tier_unknown_returns_untrusted() {
254 assert_eq!(get_tier(&empty_trust(), "ghost"), "UNTRUSTED");
255 }
256
257 #[test]
258 fn add_agent_card_pin_defaults_untrusted() {
259 let (sk, pk) = generate_keypair();
260 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
261 let mut t = empty_trust();
262 add_agent_card_pin(&mut t, &card, None);
263 assert_eq!(get_tier(&t, "paul"), "UNTRUSTED");
264 let did = t["agents"]["paul"]["did"].as_str().unwrap();
266 assert!(did.starts_with("did:wire:paul-"), "got: {did}");
267 }
268
269 #[test]
270 fn add_pin_strips_ed25519_prefix_from_key_id() {
271 let (sk, pk) = generate_keypair();
272 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
273 let mut t = empty_trust();
274 add_agent_card_pin(&mut t, &card, None);
275 let kid = t["agents"]["paul"]["public_keys"][0]["key_id"]
276 .as_str()
277 .unwrap();
278 assert!(kid.contains(':'));
279 assert!(!kid.starts_with("ed25519:"));
280 }
281
282 #[test]
283 fn promote_to_verified_one_way() {
284 let (sk, pk) = generate_keypair();
285 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
286 let mut t = empty_trust();
287 add_agent_card_pin(&mut t, &card, None);
288 promote_to_verified(&mut t, "paul").unwrap();
289 assert_eq!(get_tier(&t, "paul"), "VERIFIED");
290 assert!(t["agents"]["paul"]["verified_at"].is_string());
291 }
292
293 #[test]
294 fn promote_to_verified_idempotent_block() {
295 let (sk, pk) = generate_keypair();
296 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
297 let mut t = empty_trust();
298 add_agent_card_pin(&mut t, &card, None);
299 promote_to_verified(&mut t, "paul").unwrap();
300 let err = promote_to_verified(&mut t, "paul").unwrap_err();
301 assert!(err.contains("VERIFIED"), "got: {err}");
302 }
303
304 #[test]
305 fn promote_unknown_peer_fails() {
306 let mut t = empty_trust();
307 let err = promote_to_verified(&mut t, "ghost").unwrap_err();
308 assert!(err.contains("not pinned"), "got: {err}");
309 }
310
311 #[test]
312 fn add_self_to_trust_attests() {
313 let (_, pk) = generate_keypair();
314 let mut t = empty_trust();
315 add_self_to_trust(&mut t, "paul", &pk);
316 assert_eq!(get_tier(&t, "paul"), "ATTESTED");
317 let did = t["agents"]["paul"]["did"].as_str().unwrap();
318 assert!(did.starts_with("did:wire:paul-"), "got: {did}");
319 }
320
321 #[test]
322 fn tier_order_matches_promotion_semantics() {
323 let order = tier_order();
324 assert!(order["UNTRUSTED"] < order["ORG_VERIFIED"]);
325 assert!(order["ORG_VERIFIED"] < order["VERIFIED"]);
326 assert!(order["VERIFIED"] < order["ATTESTED"]);
327 assert!(order["ATTESTED"] < order["TRUSTED"]);
328 }
329
330 #[test]
333 fn tier_as_str_covers_org_verified() {
334 assert_eq!(Tier::OrgVerified.as_str(), "ORG_VERIFIED");
335 }
336
337 #[test]
338 fn promote_to_org_verified_one_way() {
339 let (sk, pk) = generate_keypair();
340 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
341 let mut t = empty_trust();
342 add_agent_card_pin(&mut t, &card, None);
343 promote_to_org_verified(&mut t, "paul").unwrap();
344 assert_eq!(get_tier(&t, "paul"), "ORG_VERIFIED");
345 assert!(t["agents"]["paul"]["org_verified_at"].is_string());
346 }
347
348 #[test]
349 fn promote_to_org_verified_refuses_already_verified() {
350 let (sk, pk) = generate_keypair();
353 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
354 let mut t = empty_trust();
355 add_agent_card_pin(&mut t, &card, None);
356 promote_to_verified(&mut t, "paul").unwrap();
357 let err = promote_to_org_verified(&mut t, "paul").unwrap_err();
358 assert!(err.contains("VERIFIED"), "got: {err}");
359 assert_eq!(get_tier(&t, "paul"), "VERIFIED");
360 }
361
362 #[test]
363 fn promote_to_org_verified_refuses_self_idempotent() {
364 let (sk, pk) = generate_keypair();
367 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
368 let mut t = empty_trust();
369 add_agent_card_pin(&mut t, &card, None);
370 promote_to_org_verified(&mut t, "paul").unwrap();
371 let err = promote_to_org_verified(&mut t, "paul").unwrap_err();
372 assert!(err.contains("ORG_VERIFIED"), "got: {err}");
373 }
374
375 #[test]
376 fn promote_to_verified_accepts_org_verified_source() {
377 let (sk, pk) = generate_keypair();
381 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
382 let mut t = empty_trust();
383 add_agent_card_pin(&mut t, &card, None);
384 promote_to_org_verified(&mut t, "paul").unwrap();
385 promote_to_verified(&mut t, "paul").unwrap();
386 assert_eq!(get_tier(&t, "paul"), "VERIFIED");
387 assert!(t["agents"]["paul"]["org_verified_at"].is_string());
388 assert!(t["agents"]["paul"]["verified_at"].is_string());
389 }
390
391 #[test]
392 fn promote_to_verified_refuses_attested_source() {
393 let (_, pk) = generate_keypair();
396 let mut t = empty_trust();
397 add_self_to_trust(&mut t, "self", &pk);
398 let err = promote_to_verified(&mut t, "self").unwrap_err();
399 assert!(err.contains("ATTESTED"), "got: {err}");
400 }
401
402 #[test]
403 fn org_verified_does_not_satisfy_verified_policy_check() {
404 let order = tier_order();
407 let verified_rank = order["VERIFIED"];
408 let org_rank = order["ORG_VERIFIED"];
409 assert!(
410 org_rank < verified_rank,
411 "ORG_VERIFIED ({org_rank}) must rank strictly below VERIFIED ({verified_rank})"
412 );
413 }
414}