use serde_json::{Value, json};
use std::collections::BTreeMap;
use time::OffsetDateTime;
use time::format_description::well_known::Rfc3339;
use crate::signing::{b64encode, make_key_id};
pub fn tier_order() -> BTreeMap<&'static str, u32> {
[
("UNTRUSTED", 0u32),
("ORG_VERIFIED", 1),
("VERIFIED", 2),
("ATTESTED", 3),
("TRUSTED", 4),
]
.into_iter()
.collect()
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Tier {
Untrusted,
OrgVerified,
Verified,
Attested,
Trusted,
}
impl Tier {
pub fn as_str(self) -> &'static str {
match self {
Tier::Untrusted => "UNTRUSTED",
Tier::OrgVerified => "ORG_VERIFIED",
Tier::Verified => "VERIFIED",
Tier::Attested => "ATTESTED",
Tier::Trusted => "TRUSTED",
}
}
}
pub type Trust = Value;
pub fn empty_trust() -> Trust {
json!({"version": 1, "agents": {}})
}
pub fn get_tier(trust: &Trust, peer_handle: &str) -> String {
trust
.get("agents")
.and_then(|a| a.get(peer_handle))
.and_then(|a| a.get("tier"))
.and_then(Value::as_str)
.unwrap_or("UNTRUSTED")
.to_string()
}
pub fn effective_tier(trust: &Value, relay_state: &Value, handle: &str) -> String {
let raw = get_tier(trust, handle);
if raw != "VERIFIED" {
return raw;
}
let peer_obj = relay_state.get("peers").and_then(|p| p.get(handle));
let bilateral_at = peer_obj
.and_then(|p| p.get("bilateral_completed_at"))
.and_then(Value::as_str);
if bilateral_at.is_some() {
return raw;
}
let has_slot = peer_obj
.and_then(|p| p.get("slot_token"))
.and_then(Value::as_str)
.map(|t| !t.is_empty())
.unwrap_or(false)
|| crate::endpoints::peer_endpoints_in_priority_order(relay_state, handle)
.iter()
.any(|e| !e.slot_token.is_empty());
if has_slot {
raw
} else {
"PENDING_ACK".to_string()
}
}
pub fn resolve_peer_did(trust: &Value, peer_handle: &str) -> String {
trust
.get("agents")
.and_then(|a| a.get(peer_handle))
.and_then(|p| p.get("did"))
.and_then(Value::as_str)
.map(str::to_string)
.unwrap_or_else(|| format!("did:wire:{peer_handle}"))
}
pub fn add_agent_card_pin(trust: &mut Trust, card: &Value, tier: Option<&str>) {
let did = card.get("did").and_then(Value::as_str).unwrap_or_default();
let handle = card
.get("handle")
.and_then(Value::as_str)
.map(str::to_string)
.unwrap_or_else(|| crate::agent_card::display_handle_from_did(did).to_string());
if handle.is_empty() {
panic!("card has no resolvable handle (did={did:?})");
}
let tier = tier.unwrap_or("UNTRUSTED");
let now = now_iso();
let mut public_keys = Vec::new();
if let Some(vks) = card.get("verify_keys").and_then(Value::as_object) {
for (key_id_full, key_record) in vks {
let key_id = key_id_full.strip_prefix("ed25519:").unwrap_or(key_id_full);
public_keys.push(json!({
"key_id": key_id,
"key": key_record.get("key").cloned().unwrap_or(Value::Null),
"added_at": now,
"active": true,
}));
}
}
let agents = trust
.as_object_mut()
.expect("trust must be an object")
.entry("agents")
.or_insert_with(|| json!({}));
agents[handle] = json!({
"tier": tier,
"did": did,
"public_keys": public_keys,
"card": card.clone(),
"pinned_at": now,
});
}
pub fn promote_to_verified(trust: &mut Trust, peer_handle: &str) -> Result<(), String> {
let agents = trust
.as_object_mut()
.ok_or("trust is not an object")?
.get_mut("agents")
.and_then(Value::as_object_mut)
.ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
let agent = agents
.get_mut(peer_handle)
.ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
let current = agent
.get("tier")
.and_then(Value::as_str)
.unwrap_or("UNTRUSTED")
.to_string();
if current != "UNTRUSTED" && current != "ORG_VERIFIED" {
return Err(format!(
"peer {peer_handle:?} already at tier {current:?} — promotion is one-way"
));
}
agent["tier"] = json!("VERIFIED");
agent["verified_at"] = json!(now_iso());
Ok(())
}
pub fn promote_to_org_verified(trust: &mut Trust, peer_handle: &str) -> Result<(), String> {
let agents = trust
.as_object_mut()
.ok_or("trust is not an object")?
.get_mut("agents")
.and_then(Value::as_object_mut)
.ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
let agent = agents
.get_mut(peer_handle)
.ok_or_else(|| format!("peer {peer_handle:?} not pinned"))?;
let current = agent
.get("tier")
.and_then(Value::as_str)
.unwrap_or("UNTRUSTED")
.to_string();
if current != "UNTRUSTED" {
return Err(format!(
"peer {peer_handle:?} already at tier {current:?} — \
org_verified promotion fires from UNTRUSTED only"
));
}
agent["tier"] = json!("ORG_VERIFIED");
agent["org_verified_at"] = json!(now_iso());
Ok(())
}
pub fn project_recipients(
trust: &Value,
relay_state: &Value,
self_handle: &str,
project: &str,
) -> Vec<String> {
let order = tier_order();
let floor = order.get("ORG_VERIFIED").copied().unwrap_or(1);
let mut out = Vec::new();
if let Some(agents) = trust.get("agents").and_then(Value::as_object) {
for (handle, agent) in agents {
if handle == self_handle {
continue;
}
let tier = effective_tier(trust, relay_state, handle);
let rank = order.get(tier.as_str()).copied().unwrap_or(0);
if rank < floor {
continue;
}
let proj = agent.get("card").and_then(crate::agent_card::card_project);
if proj == Some(project) {
out.push(handle.clone());
}
}
}
out.sort();
out
}
pub fn add_self_to_trust(trust: &mut Trust, handle: &str, public_key: &[u8]) {
let agents = trust
.as_object_mut()
.expect("trust must be an object")
.entry("agents")
.or_insert_with(|| json!({}));
let key_id = make_key_id(handle, public_key);
agents[handle] = json!({
"tier": "ATTESTED",
"did": crate::agent_card::did_for_with_key(handle, public_key),
"public_keys": [{
"key_id": key_id,
"key": b64encode(public_key),
"added_at": now_iso(),
"active": true,
}],
});
}
fn now_iso() -> String {
let now = OffsetDateTime::now_utc();
now.format(&Rfc3339)
.unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agent_card::{build_agent_card, sign_agent_card};
use crate::signing::generate_keypair;
#[test]
fn empty_trust_shape() {
let t = empty_trust();
assert_eq!(t["version"], 1);
assert!(t["agents"].is_object());
assert_eq!(t["agents"].as_object().unwrap().len(), 0);
}
#[test]
fn get_tier_unknown_returns_untrusted() {
assert_eq!(get_tier(&empty_trust(), "ghost"), "UNTRUSTED");
}
#[test]
fn resolve_peer_did_returns_pinned_did_with_full_suffix() {
let (sk, pk) = generate_keypair();
let card = sign_agent_card(
&build_agent_card("sunlit-aurora", &pk, None, None, None),
&sk,
);
let pinned_did = card.get("did").and_then(Value::as_str).unwrap();
assert!(
pinned_did.starts_with("did:wire:sunlit-aurora-"),
"test setup: card DID should carry long-hex suffix"
);
let mut t = empty_trust();
add_agent_card_pin(&mut t, &card, Some("VERIFIED"));
let resolved = resolve_peer_did(&t, "sunlit-aurora");
assert_eq!(
resolved, pinned_did,
"pinned peer must resolve to its full DID, not the bare handle"
);
}
#[test]
fn resolve_peer_did_falls_back_to_bare_for_unknown_peer() {
let t = empty_trust();
assert_eq!(
resolve_peer_did(&t, "ghost-peer"),
"did:wire:ghost-peer",
"unknown peer falls back to bare-handle DID"
);
}
#[test]
fn add_agent_card_pin_defaults_untrusted() {
let (sk, pk) = generate_keypair();
let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
let mut t = empty_trust();
add_agent_card_pin(&mut t, &card, None);
assert_eq!(get_tier(&t, "paul"), "UNTRUSTED");
let did = t["agents"]["paul"]["did"].as_str().unwrap();
assert!(did.starts_with("did:wire:paul-"), "got: {did}");
}
#[test]
fn add_pin_strips_ed25519_prefix_from_key_id() {
let (sk, pk) = generate_keypair();
let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
let mut t = empty_trust();
add_agent_card_pin(&mut t, &card, None);
let kid = t["agents"]["paul"]["public_keys"][0]["key_id"]
.as_str()
.unwrap();
assert!(kid.contains(':'));
assert!(!kid.starts_with("ed25519:"));
}
#[test]
fn promote_to_verified_one_way() {
let (sk, pk) = generate_keypair();
let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
let mut t = empty_trust();
add_agent_card_pin(&mut t, &card, None);
promote_to_verified(&mut t, "paul").unwrap();
assert_eq!(get_tier(&t, "paul"), "VERIFIED");
assert!(t["agents"]["paul"]["verified_at"].is_string());
}
#[test]
fn promote_to_verified_idempotent_block() {
let (sk, pk) = generate_keypair();
let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
let mut t = empty_trust();
add_agent_card_pin(&mut t, &card, None);
promote_to_verified(&mut t, "paul").unwrap();
let err = promote_to_verified(&mut t, "paul").unwrap_err();
assert!(err.contains("VERIFIED"), "got: {err}");
}
#[test]
fn promote_unknown_peer_fails() {
let mut t = empty_trust();
let err = promote_to_verified(&mut t, "ghost").unwrap_err();
assert!(err.contains("not pinned"), "got: {err}");
}
#[test]
fn add_self_to_trust_attests() {
let (_, pk) = generate_keypair();
let mut t = empty_trust();
add_self_to_trust(&mut t, "paul", &pk);
assert_eq!(get_tier(&t, "paul"), "ATTESTED");
let did = t["agents"]["paul"]["did"].as_str().unwrap();
assert!(did.starts_with("did:wire:paul-"), "got: {did}");
}
#[test]
fn tier_order_matches_promotion_semantics() {
let order = tier_order();
assert!(order["UNTRUSTED"] < order["ORG_VERIFIED"]);
assert!(order["ORG_VERIFIED"] < order["VERIFIED"]);
assert!(order["VERIFIED"] < order["ATTESTED"]);
assert!(order["ATTESTED"] < order["TRUSTED"]);
}
#[test]
fn tier_as_str_covers_org_verified() {
assert_eq!(Tier::OrgVerified.as_str(), "ORG_VERIFIED");
}
#[test]
fn promote_to_org_verified_one_way() {
let (sk, pk) = generate_keypair();
let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
let mut t = empty_trust();
add_agent_card_pin(&mut t, &card, None);
promote_to_org_verified(&mut t, "paul").unwrap();
assert_eq!(get_tier(&t, "paul"), "ORG_VERIFIED");
assert!(t["agents"]["paul"]["org_verified_at"].is_string());
}
#[test]
fn promote_to_org_verified_refuses_already_verified() {
let (sk, pk) = generate_keypair();
let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
let mut t = empty_trust();
add_agent_card_pin(&mut t, &card, None);
promote_to_verified(&mut t, "paul").unwrap();
let err = promote_to_org_verified(&mut t, "paul").unwrap_err();
assert!(err.contains("VERIFIED"), "got: {err}");
assert_eq!(get_tier(&t, "paul"), "VERIFIED");
}
#[test]
fn promote_to_org_verified_refuses_self_idempotent() {
let (sk, pk) = generate_keypair();
let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
let mut t = empty_trust();
add_agent_card_pin(&mut t, &card, None);
promote_to_org_verified(&mut t, "paul").unwrap();
let err = promote_to_org_verified(&mut t, "paul").unwrap_err();
assert!(err.contains("ORG_VERIFIED"), "got: {err}");
}
#[test]
fn promote_to_verified_accepts_org_verified_source() {
let (sk, pk) = generate_keypair();
let card = sign_agent_card(&build_agent_card("paul", &pk, None, None, None), &sk);
let mut t = empty_trust();
add_agent_card_pin(&mut t, &card, None);
promote_to_org_verified(&mut t, "paul").unwrap();
promote_to_verified(&mut t, "paul").unwrap();
assert_eq!(get_tier(&t, "paul"), "VERIFIED");
assert!(t["agents"]["paul"]["org_verified_at"].is_string());
assert!(t["agents"]["paul"]["verified_at"].is_string());
}
#[test]
fn promote_to_verified_refuses_attested_source() {
let (_, pk) = generate_keypair();
let mut t = empty_trust();
add_self_to_trust(&mut t, "self", &pk);
let err = promote_to_verified(&mut t, "self").unwrap_err();
assert!(err.contains("ATTESTED"), "got: {err}");
}
#[test]
fn effective_tier_matrix() {
use serde_json::json;
let trust = json!({"agents": {"a": {"tier": "VERIFIED"}}});
let relay = json!({"peers": {"a": {"bilateral_completed_at": "t"}}});
assert_eq!(effective_tier(&trust, &relay, "a"), "VERIFIED");
let relay = json!({"peers": {"a": {"slot_token": "tok"}}});
assert_eq!(effective_tier(&trust, &relay, "a"), "VERIFIED");
let relay = json!({"peers": {"a": {"slot_token": ""}}});
assert_eq!(effective_tier(&trust, &relay, "a"), "PENDING_ACK");
let relay = json!({"peers": {}});
assert_eq!(effective_tier(&trust, &relay, "a"), "PENDING_ACK");
let relay = json!({"peers": {"a": {"endpoints": [
{"relay_url": "https://r", "slot_id": "s", "slot_token": "tok", "scope": "federation"}
]}}});
assert_eq!(effective_tier(&trust, &relay, "a"), "VERIFIED");
let relay = json!({"peers": {"a": {"endpoints": [
{"relay_url": "https://r", "slot_id": "s", "slot_token": "", "scope": "federation"}
]}}});
assert_eq!(effective_tier(&trust, &relay, "a"), "PENDING_ACK");
let trust = json!({"agents": {"a": {"tier": "UNTRUSTED"}}});
assert_eq!(effective_tier(&trust, &relay, "a"), "UNTRUSTED");
let trust = json!({"agents": {"a": {"tier": "ORG_VERIFIED"}}});
assert_eq!(effective_tier(&trust, &relay, "a"), "ORG_VERIFIED");
}
#[test]
fn project_recipients_filters_by_tier_and_project() {
use serde_json::json;
let trust = json!({"agents": {
"alice": {"tier": "ORG_VERIFIED", "card": {"project": "print-shop"}},
"bob": {"tier": "ORG_VERIFIED", "card": {"project": "lora-training"}},
"carol": {"tier": "UNTRUSTED", "card": {"project": "print-shop"}},
"dave": {"tier": "VERIFIED", "card": {"project": "print-shop"}},
"selfie": {"tier": "ATTESTED", "card": {"project": "print-shop"}},
"noproj": {"tier": "ORG_VERIFIED", "card": {}},
}});
let relay = json!({"peers": {"dave": {"bilateral_completed_at": "t"}}});
let r = project_recipients(&trust, &relay, "selfie", "print-shop");
assert_eq!(r, vec!["alice".to_string(), "dave".to_string()]);
}
#[test]
fn project_recipients_excludes_unreachable_verified() {
use serde_json::json;
let trust = json!({"agents": {
"ghost": {"tier": "VERIFIED", "card": {"project": "x"}},
}});
let relay = json!({"peers": {}});
assert!(project_recipients(&trust, &relay, "selfie", "x").is_empty());
}
#[test]
fn org_verified_does_not_satisfy_verified_policy_check() {
let order = tier_order();
let verified_rank = order["VERIFIED"];
let org_rank = order["ORG_VERIFIED"];
assert!(
org_rank < verified_rank,
"ORG_VERIFIED ({org_rank}) must rank strictly below VERIFIED ({verified_rank})"
);
}
}