tf_types/
trust_overlay.rs1use serde::{Deserialize, Serialize};
8
9use crate::generated::{ActorIdentity, ProofLevel, TrustLevel};
10
11#[derive(Clone, Debug, Default, Serialize, Deserialize)]
12pub struct PostureContext {
13 #[serde(default)]
14 pub hardware_backed: bool,
15 #[serde(default)]
16 pub attestation_verified: bool,
17 #[serde(default)]
18 pub proof_level_achieved: Option<ProofLevel>,
19 #[serde(default)]
20 pub recent_verification_seconds: Option<u64>,
21 #[serde(default)]
22 pub stale_after_seconds: Option<u64>,
23 #[serde(default)]
24 pub quorum_approvers_at_least: Option<u32>,
25 #[serde(default)]
26 pub untrusted_relay_path: bool,
27 #[serde(default)]
28 pub recently_revoked: bool,
29 #[serde(default)]
30 pub publicly_anchored: bool,
31 #[serde(default)]
32 pub compliance_attested: bool,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
36pub struct TrustOverlayResult {
37 pub level: TrustLevel,
38 pub reasons: Vec<String>,
39}
40
41const ORDER: [TrustLevel; 8] = [
42 TrustLevel::T0,
43 TrustLevel::T1,
44 TrustLevel::T2,
45 TrustLevel::T3,
46 TrustLevel::T4,
47 TrustLevel::T5,
48 TrustLevel::T6,
49 TrustLevel::T7,
50];
51
52fn rank(level: &TrustLevel) -> usize {
53 ORDER.iter().position(|t| t == level).unwrap_or(0)
54}
55
56fn level_from_rank(r: usize) -> TrustLevel {
57 let clamped = r.min(ORDER.len() - 1);
58 ORDER[clamped].clone()
59}
60
61pub fn compose_trust_level(
62 identity: &ActorIdentity,
63 posture: &PostureContext,
64) -> TrustOverlayResult {
65 let mut reasons = Vec::new();
66 let base = highest_level(&identity.trust_levels);
67 let mut r = rank(&base);
68 reasons.push(format!("base={}", trust_level_str(&base)));
69
70 if posture.recently_revoked {
71 reasons.push("revoked → T0".into());
72 return TrustOverlayResult {
73 level: TrustLevel::T0,
74 reasons,
75 };
76 }
77 let stale = posture.stale_after_seconds.unwrap_or(86_400);
78 if let Some(seen) = posture.recent_verification_seconds {
79 if seen > stale {
80 reasons.push(format!("stale ({}s > {}s) → T0", seen, stale));
81 return TrustOverlayResult {
82 level: TrustLevel::T0,
83 reasons,
84 };
85 }
86 }
87
88 if posture.hardware_backed && r < rank(&TrustLevel::T4) {
89 r = rank(&TrustLevel::T4);
90 reasons.push("hardware-backed → ≥T4".into());
91 }
92 if posture.attestation_verified && r < rank(&TrustLevel::T4) {
93 r = rank(&TrustLevel::T4);
94 reasons.push("attestation verified → ≥T4".into());
95 }
96 if posture.quorum_approvers_at_least.unwrap_or(0) >= 2 && r < rank(&TrustLevel::T5) {
97 r = rank(&TrustLevel::T5);
98 reasons.push("quorum ≥2 → ≥T5".into());
99 }
100 if posture.publicly_anchored && r < rank(&TrustLevel::T6) {
101 r = rank(&TrustLevel::T6);
102 reasons.push("publicly anchored → ≥T6".into());
103 }
104 if posture.compliance_attested && r < rank(&TrustLevel::T7) {
105 r = rank(&TrustLevel::T7);
106 reasons.push("compliance attestation → T7".into());
107 }
108 if let Some(level) = &posture.proof_level_achieved {
109 if let Some(target) = proof_level_minimum_trust(level) {
110 if rank(&target) > r {
111 r = rank(&target);
112 reasons.push(format!(
113 "proof level {} → ≥{}",
114 proof_level_str(level),
115 trust_level_str(&target)
116 ));
117 }
118 }
119 }
120
121 if posture.untrusted_relay_path && r > 0 {
122 r -= 1;
123 reasons.push("untrusted relay path → -1".into());
124 }
125
126 TrustOverlayResult {
127 level: level_from_rank(r),
128 reasons,
129 }
130}
131
132fn highest_level(levels: &[TrustLevel]) -> TrustLevel {
133 if levels.is_empty() {
134 return TrustLevel::T0;
135 }
136 let mut best = TrustLevel::T0;
137 for l in levels {
138 if rank(l) > rank(&best) {
139 best = l.clone();
140 }
141 }
142 best
143}
144
145fn proof_level_minimum_trust(level: &ProofLevel) -> Option<TrustLevel> {
146 match level {
147 ProofLevel::L0 => Some(TrustLevel::T0),
148 ProofLevel::L1 => Some(TrustLevel::T1),
149 ProofLevel::L2 => Some(TrustLevel::T2),
150 ProofLevel::L3 => Some(TrustLevel::T3),
151 ProofLevel::L4 => Some(TrustLevel::T6),
152 ProofLevel::L5 => Some(TrustLevel::T7),
153 }
154}
155
156fn trust_level_str(level: &TrustLevel) -> &'static str {
157 match level {
158 TrustLevel::T0 => "T0",
159 TrustLevel::T1 => "T1",
160 TrustLevel::T2 => "T2",
161 TrustLevel::T3 => "T3",
162 TrustLevel::T4 => "T4",
163 TrustLevel::T5 => "T5",
164 TrustLevel::T6 => "T6",
165 TrustLevel::T7 => "T7",
166 }
167}
168
169fn proof_level_str(level: &ProofLevel) -> &'static str {
170 match level {
171 ProofLevel::L0 => "L0",
172 ProofLevel::L1 => "L1",
173 ProofLevel::L2 => "L2",
174 ProofLevel::L3 => "L3",
175 ProofLevel::L4 => "L4",
176 ProofLevel::L5 => "L5",
177 }
178}