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 effective_tier(trust: &Value, relay_state: &Value, handle: &str) -> String {
108 let raw = get_tier(trust, handle);
109 if raw != "VERIFIED" {
110 return raw;
111 }
112 let peer_obj = relay_state.get("peers").and_then(|p| p.get(handle));
113 let bilateral_at = peer_obj
114 .and_then(|p| p.get("bilateral_completed_at"))
115 .and_then(Value::as_str);
116 if bilateral_at.is_some() {
117 return raw;
118 }
119 let token = peer_obj
120 .and_then(|p| p.get("slot_token"))
121 .and_then(Value::as_str)
122 .unwrap_or("");
123 if token.is_empty() {
124 "PENDING_ACK".to_string()
125 } else {
126 raw
127 }
128}
129
130pub fn resolve_peer_did(trust: &Value, peer_handle: &str) -> String {
143 trust
144 .get("agents")
145 .and_then(|a| a.get(peer_handle))
146 .and_then(|p| p.get("did"))
147 .and_then(Value::as_str)
148 .map(str::to_string)
149 .unwrap_or_else(|| format!("did:wire:{peer_handle}"))
150}
151
152pub fn add_agent_card_pin(trust: &mut Trust, card: &Value, tier: Option<&str>) {
157 let did = card.get("did").and_then(Value::as_str).unwrap_or_default();
158 let handle = card
163 .get("handle")
164 .and_then(Value::as_str)
165 .map(str::to_string)
166 .unwrap_or_else(|| crate::agent_card::display_handle_from_did(did).to_string());
167 if handle.is_empty() {
168 panic!("card has no resolvable handle (did={did:?})");
169 }
170 let tier = tier.unwrap_or("UNTRUSTED");
171 let now = now_iso();
172
173 let mut public_keys = Vec::new();
174 if let Some(vks) = card.get("verify_keys").and_then(Value::as_object) {
175 for (key_id_full, key_record) in vks {
176 let key_id = key_id_full.strip_prefix("ed25519:").unwrap_or(key_id_full);
178 public_keys.push(json!({
179 "key_id": key_id,
180 "key": key_record.get("key").cloned().unwrap_or(Value::Null),
181 "added_at": now,
182 "active": true,
183 }));
184 }
185 }
186
187 let agents = trust
188 .as_object_mut()
189 .expect("trust must be an object")
190 .entry("agents")
191 .or_insert_with(|| json!({}));
192
193 agents[handle] = json!({
194 "tier": tier,
195 "did": did,
196 "public_keys": public_keys,
197 "card": card.clone(),
198 "pinned_at": now,
199 });
200}
201
202pub fn promote_to_verified(trust: &mut Trust, peer_handle: &str) -> Result<(), String> {
210 let agents = trust
211 .as_object_mut()
212 .ok_or("trust is not an object")?
213 .get_mut("agents")
214 .and_then(Value::as_object_mut)
215 .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
216
217 let agent = agents
218 .get_mut(peer_handle)
219 .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
220
221 let current = agent
222 .get("tier")
223 .and_then(Value::as_str)
224 .unwrap_or("UNTRUSTED")
225 .to_string();
226 if current != "UNTRUSTED" && current != "ORG_VERIFIED" {
227 return Err(format!(
228 "peer {peer_handle:?} already at tier {current:?} — promotion is one-way"
229 ));
230 }
231 agent["tier"] = json!("VERIFIED");
232 agent["verified_at"] = json!(now_iso());
233 Ok(())
234}
235
236pub fn promote_to_org_verified(trust: &mut Trust, peer_handle: &str) -> Result<(), String> {
251 let agents = trust
252 .as_object_mut()
253 .ok_or("trust is not an object")?
254 .get_mut("agents")
255 .and_then(Value::as_object_mut)
256 .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
257
258 let agent = agents
259 .get_mut(peer_handle)
260 .ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
261
262 let current = agent
263 .get("tier")
264 .and_then(Value::as_str)
265 .unwrap_or("UNTRUSTED")
266 .to_string();
267 if current != "UNTRUSTED" {
268 return Err(format!(
269 "peer {peer_handle:?} already at tier {current:?} — \
270 org_verified promotion fires from UNTRUSTED only"
271 ));
272 }
273 agent["tier"] = json!("ORG_VERIFIED");
274 agent["org_verified_at"] = json!(now_iso());
275 Ok(())
276}
277
278pub fn add_self_to_trust(trust: &mut Trust, handle: &str, public_key: &[u8]) {
280 let agents = trust
281 .as_object_mut()
282 .expect("trust must be an object")
283 .entry("agents")
284 .or_insert_with(|| json!({}));
285 let key_id = make_key_id(handle, public_key);
286 agents[handle] = json!({
287 "tier": "ATTESTED",
288 "did": crate::agent_card::did_for_with_key(handle, public_key),
289 "public_keys": [{
290 "key_id": key_id,
291 "key": b64encode(public_key),
292 "added_at": now_iso(),
293 "active": true,
294 }],
295 });
296}
297
298fn now_iso() -> String {
299 let now = OffsetDateTime::now_utc();
300 now.format(&Rfc3339)
301 .unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string())
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use crate::agent_card::{build_agent_card, sign_agent_card};
308 use crate::signing::generate_keypair;
309
310 #[test]
311 fn empty_trust_shape() {
312 let t = empty_trust();
313 assert_eq!(t["version"], 1);
314 assert!(t["agents"].is_object());
315 assert_eq!(t["agents"].as_object().unwrap().len(), 0);
316 }
317
318 #[test]
319 fn get_tier_unknown_returns_untrusted() {
320 assert_eq!(get_tier(&empty_trust(), "ghost"), "UNTRUSTED");
321 }
322
323 #[test]
324 fn resolve_peer_did_returns_pinned_did_with_full_suffix() {
325 let (sk, pk) = generate_keypair();
330 let card = sign_agent_card(
331 &build_agent_card("sunlit-aurora", &pk, None, None, None),
332 &sk,
333 );
334 let pinned_did = card.get("did").and_then(Value::as_str).unwrap();
335 assert!(
336 pinned_did.starts_with("did:wire:sunlit-aurora-"),
337 "test setup: card DID should carry long-hex suffix"
338 );
339 let mut t = empty_trust();
340 add_agent_card_pin(&mut t, &card, Some("VERIFIED"));
341
342 let resolved = resolve_peer_did(&t, "sunlit-aurora");
343 assert_eq!(
344 resolved, pinned_did,
345 "pinned peer must resolve to its full DID, not the bare handle"
346 );
347 }
348
349 #[test]
350 fn resolve_peer_did_falls_back_to_bare_for_unknown_peer() {
351 let t = empty_trust();
355 assert_eq!(
356 resolve_peer_did(&t, "ghost-peer"),
357 "did:wire:ghost-peer",
358 "unknown peer falls back to bare-handle DID"
359 );
360 }
361
362 #[test]
363 fn add_agent_card_pin_defaults_untrusted() {
364 let (sk, pk) = generate_keypair();
365 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
366 let mut t = empty_trust();
367 add_agent_card_pin(&mut t, &card, None);
368 assert_eq!(get_tier(&t, "paul"), "UNTRUSTED");
369 let did = t["agents"]["paul"]["did"].as_str().unwrap();
371 assert!(did.starts_with("did:wire:paul-"), "got: {did}");
372 }
373
374 #[test]
375 fn add_pin_strips_ed25519_prefix_from_key_id() {
376 let (sk, pk) = generate_keypair();
377 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
378 let mut t = empty_trust();
379 add_agent_card_pin(&mut t, &card, None);
380 let kid = t["agents"]["paul"]["public_keys"][0]["key_id"]
381 .as_str()
382 .unwrap();
383 assert!(kid.contains(':'));
384 assert!(!kid.starts_with("ed25519:"));
385 }
386
387 #[test]
388 fn promote_to_verified_one_way() {
389 let (sk, pk) = generate_keypair();
390 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
391 let mut t = empty_trust();
392 add_agent_card_pin(&mut t, &card, None);
393 promote_to_verified(&mut t, "paul").unwrap();
394 assert_eq!(get_tier(&t, "paul"), "VERIFIED");
395 assert!(t["agents"]["paul"]["verified_at"].is_string());
396 }
397
398 #[test]
399 fn promote_to_verified_idempotent_block() {
400 let (sk, pk) = generate_keypair();
401 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
402 let mut t = empty_trust();
403 add_agent_card_pin(&mut t, &card, None);
404 promote_to_verified(&mut t, "paul").unwrap();
405 let err = promote_to_verified(&mut t, "paul").unwrap_err();
406 assert!(err.contains("VERIFIED"), "got: {err}");
407 }
408
409 #[test]
410 fn promote_unknown_peer_fails() {
411 let mut t = empty_trust();
412 let err = promote_to_verified(&mut t, "ghost").unwrap_err();
413 assert!(err.contains("not pinned"), "got: {err}");
414 }
415
416 #[test]
417 fn add_self_to_trust_attests() {
418 let (_, pk) = generate_keypair();
419 let mut t = empty_trust();
420 add_self_to_trust(&mut t, "paul", &pk);
421 assert_eq!(get_tier(&t, "paul"), "ATTESTED");
422 let did = t["agents"]["paul"]["did"].as_str().unwrap();
423 assert!(did.starts_with("did:wire:paul-"), "got: {did}");
424 }
425
426 #[test]
427 fn tier_order_matches_promotion_semantics() {
428 let order = tier_order();
429 assert!(order["UNTRUSTED"] < order["ORG_VERIFIED"]);
430 assert!(order["ORG_VERIFIED"] < order["VERIFIED"]);
431 assert!(order["VERIFIED"] < order["ATTESTED"]);
432 assert!(order["ATTESTED"] < order["TRUSTED"]);
433 }
434
435 #[test]
438 fn tier_as_str_covers_org_verified() {
439 assert_eq!(Tier::OrgVerified.as_str(), "ORG_VERIFIED");
440 }
441
442 #[test]
443 fn promote_to_org_verified_one_way() {
444 let (sk, pk) = generate_keypair();
445 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
446 let mut t = empty_trust();
447 add_agent_card_pin(&mut t, &card, None);
448 promote_to_org_verified(&mut t, "paul").unwrap();
449 assert_eq!(get_tier(&t, "paul"), "ORG_VERIFIED");
450 assert!(t["agents"]["paul"]["org_verified_at"].is_string());
451 }
452
453 #[test]
454 fn promote_to_org_verified_refuses_already_verified() {
455 let (sk, pk) = generate_keypair();
458 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
459 let mut t = empty_trust();
460 add_agent_card_pin(&mut t, &card, None);
461 promote_to_verified(&mut t, "paul").unwrap();
462 let err = promote_to_org_verified(&mut t, "paul").unwrap_err();
463 assert!(err.contains("VERIFIED"), "got: {err}");
464 assert_eq!(get_tier(&t, "paul"), "VERIFIED");
465 }
466
467 #[test]
468 fn promote_to_org_verified_refuses_self_idempotent() {
469 let (sk, pk) = generate_keypair();
472 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
473 let mut t = empty_trust();
474 add_agent_card_pin(&mut t, &card, None);
475 promote_to_org_verified(&mut t, "paul").unwrap();
476 let err = promote_to_org_verified(&mut t, "paul").unwrap_err();
477 assert!(err.contains("ORG_VERIFIED"), "got: {err}");
478 }
479
480 #[test]
481 fn promote_to_verified_accepts_org_verified_source() {
482 let (sk, pk) = generate_keypair();
486 let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
487 let mut t = empty_trust();
488 add_agent_card_pin(&mut t, &card, None);
489 promote_to_org_verified(&mut t, "paul").unwrap();
490 promote_to_verified(&mut t, "paul").unwrap();
491 assert_eq!(get_tier(&t, "paul"), "VERIFIED");
492 assert!(t["agents"]["paul"]["org_verified_at"].is_string());
493 assert!(t["agents"]["paul"]["verified_at"].is_string());
494 }
495
496 #[test]
497 fn promote_to_verified_refuses_attested_source() {
498 let (_, pk) = generate_keypair();
501 let mut t = empty_trust();
502 add_self_to_trust(&mut t, "self", &pk);
503 let err = promote_to_verified(&mut t, "self").unwrap_err();
504 assert!(err.contains("ATTESTED"), "got: {err}");
505 }
506
507 #[test]
508 fn effective_tier_matrix() {
509 use serde_json::json;
510 let trust = json!({"agents": {"a": {"tier": "VERIFIED"}}});
512 let relay = json!({"peers": {"a": {"bilateral_completed_at": "t"}}});
513 assert_eq!(effective_tier(&trust, &relay, "a"), "VERIFIED");
514 let relay = json!({"peers": {"a": {"slot_token": "tok"}}});
516 assert_eq!(effective_tier(&trust, &relay, "a"), "VERIFIED");
517 let relay = json!({"peers": {"a": {"slot_token": ""}}});
519 assert_eq!(effective_tier(&trust, &relay, "a"), "PENDING_ACK");
520 let relay = json!({"peers": {}});
522 assert_eq!(effective_tier(&trust, &relay, "a"), "PENDING_ACK");
523 let trust = json!({"agents": {"a": {"tier": "UNTRUSTED"}}});
525 assert_eq!(effective_tier(&trust, &relay, "a"), "UNTRUSTED");
526 let trust = json!({"agents": {"a": {"tier": "ORG_VERIFIED"}}});
527 assert_eq!(effective_tier(&trust, &relay, "a"), "ORG_VERIFIED");
528 }
529
530 #[test]
531 fn org_verified_does_not_satisfy_verified_policy_check() {
532 let order = tier_order();
535 let verified_rank = order["VERIFIED"];
536 let org_rank = order["ORG_VERIFIED"];
537 assert!(
538 org_rank < verified_rank,
539 "ORG_VERIFIED ({org_rank}) must rank strictly below VERIFIED ({verified_rank})"
540 );
541 }
542}