system_doctor.rs (8337B)
1 //! Production active-doctor probes composed from existing sealed authorities. 2 3 use radroots_service_host::{EntropySource, SystemEntropy, SystemWallClock, WallClock}; 4 use radroots_service_sqlite::{ 5 IntegrityCheckOutcome, IntegrityCheckedAtUnixMs, MinimumFreeBytes, 6 PlatformStateFilesystemCapacitySource, inspect_state_filesystem_capacity, 7 }; 8 9 use crate::admin_v1::admin_transport_limits; 10 use crate::provider_executor::MycProviderExecutor; 11 use crate::transport_nostr_adapter::MycNostrDeliveryAdapter; 12 use crate::{ 13 MycConfigDocumentV1, MycDoctorCheckDefinition, MycDoctorCheckId, MycDoctorFuture, 14 MycDoctorObservation, MycDoctorProbe, MycRuntimeContext, MycTaskCancellation, 15 open_myc_state_inspection_from_config, 16 }; 17 18 pub(crate) struct MycSystemDoctorProbe<'a> { 19 runtime: &'a MycRuntimeContext, 20 configuration: &'a MycConfigDocumentV1, 21 } 22 23 impl<'a> MycSystemDoctorProbe<'a> { 24 pub(crate) const fn new( 25 runtime: &'a MycRuntimeContext, 26 configuration: &'a MycConfigDocumentV1, 27 ) -> Self { 28 Self { 29 runtime, 30 configuration, 31 } 32 } 33 34 async fn run(&self, definition: MycDoctorCheckDefinition) -> bool { 35 match definition.id() { 36 MycDoctorCheckId::PathsPermissions => self.probe_paths(), 37 MycDoctorCheckId::WriterLock 38 | MycDoctorCheckId::SqliteSchema 39 | MycDoctorCheckId::SqliteIntegrity 40 | MycDoctorCheckId::OutboxInvariants => self.probe_state(definition.id()).await, 41 MycDoctorCheckId::SqliteFreeSpace => self.probe_free_space(), 42 MycDoctorCheckId::IdentityBinding => self.probe_identity_binding().await, 43 MycDoctorCheckId::SignerProvider => self.probe_signer_provider().await, 44 MycDoctorCheckId::AdminBindPolicy => self.probe_admin_policy(), 45 MycDoctorCheckId::OperationsBindPolicy => self.probe_operations_policy(), 46 MycDoctorCheckId::NetworkPolicy => { 47 MycNostrDeliveryAdapter::from_configuration(self.configuration).is_ok() 48 } 49 MycDoctorCheckId::RequiredRelays => { 50 let Some(deadline) = absolute_deadline(definition.deadline_ms()) else { 51 return false; 52 }; 53 MycNostrDeliveryAdapter::probe_required_relays(self.configuration, deadline) 54 .await 55 .is_ok() 56 } 57 MycDoctorCheckId::ClockSkew => false, 58 } 59 } 60 61 fn probe_paths(&self) -> bool { 62 let Ok(paths) = crate::state_host::state_paths(self.runtime) else { 63 return false; 64 }; 65 let Ok(minimum) = MinimumFreeBytes::new(1) else { 66 return false; 67 }; 68 inspect_state_filesystem_capacity(&paths, minimum, &PlatformStateFilesystemCapacitySource) 69 .is_ok() 70 } 71 72 async fn probe_state(&self, check: MycDoctorCheckId) -> bool { 73 let Ok(state) = 74 open_myc_state_inspection_from_config(self.runtime, self.configuration).await 75 else { 76 return false; 77 }; 78 let outcome = match check { 79 MycDoctorCheckId::WriterLock => true, 80 MycDoctorCheckId::SqliteSchema => state.repository().verify_binding().await.is_ok(), 81 MycDoctorCheckId::SqliteIntegrity => match integrity_time() { 82 Some(checked_at) => state 83 .inspect_integrity(checked_at) 84 .await 85 .is_ok_and(|report| { 86 report.sqlite() == IntegrityCheckOutcome::Verified 87 && report.foreign_keys() == IntegrityCheckOutcome::Verified 88 }), 89 None => false, 90 }, 91 MycDoctorCheckId::OutboxInvariants => state 92 .repository() 93 .verify_delivery_invariants() 94 .await 95 .is_ok(), 96 _ => false, 97 }; 98 let closed = state.close().await.is_ok(); 99 outcome && closed 100 } 101 102 fn probe_free_space(&self) -> bool { 103 let Some(minimum) = self 104 .configuration 105 .normalized() 106 .pointer("/database/minimum_free_bytes") 107 .and_then(serde_json::Value::as_u64) 108 .and_then(|value| MinimumFreeBytes::new(value).ok()) 109 else { 110 return false; 111 }; 112 let Ok(paths) = crate::state_host::state_paths(self.runtime) else { 113 return false; 114 }; 115 inspect_state_filesystem_capacity(&paths, minimum, &PlatformStateFilesystemCapacitySource) 116 .is_ok_and(|capacity| capacity.allows_authoritative_admission()) 117 } 118 119 async fn probe_identity_binding(&self) -> bool { 120 let cancellation = MycTaskCancellation::uncancelled(); 121 let Ok(executor) = 122 MycProviderExecutor::open(self.runtime, self.configuration, &cancellation).await 123 else { 124 return false; 125 }; 126 self.configuration 127 .provider_contract() 128 .bindings() 129 .iter() 130 .all(|binding| executor.contains_role(binding.role())) 131 } 132 133 async fn probe_signer_provider(&self) -> bool { 134 let cancellation = MycTaskCancellation::uncancelled(); 135 let Ok(executor) = 136 MycProviderExecutor::open(self.runtime, self.configuration, &cancellation).await 137 else { 138 return false; 139 }; 140 let Some(observed_at) = wall_time_millis() else { 141 return false; 142 }; 143 let mut seed = [0_u8; 32]; 144 if SystemEntropy.fill_bytes(&mut seed).is_err() { 145 return false; 146 } 147 executor 148 .probe_all(observed_at, seed, &cancellation) 149 .await 150 .is_ok() 151 } 152 153 fn probe_admin_policy(&self) -> bool { 154 let path = self.runtime.artifacts().admin_socket(); 155 path.is_absolute() 156 && path.to_str().is_some_and(|value| value.len() <= 4_096) 157 && admin_transport_limits(self.configuration).is_ok() 158 } 159 160 fn probe_operations_policy(&self) -> bool { 161 let Some(enabled) = self 162 .configuration 163 .normalized() 164 .pointer("/operations/enabled") 165 .and_then(serde_json::Value::as_bool) 166 else { 167 return false; 168 }; 169 !enabled 170 || (self 171 .configuration 172 .normalized() 173 .pointer("/operations/listen") 174 .and_then(serde_json::Value::as_str) 175 .is_some() 176 && self 177 .configuration 178 .normalized() 179 .pointer("/operations/bind_policy") 180 .and_then(serde_json::Value::as_str) 181 .is_some()) 182 } 183 } 184 185 impl MycDoctorProbe for MycSystemDoctorProbe<'_> { 186 fn probe(&self, definition: MycDoctorCheckDefinition) -> MycDoctorFuture<'_> { 187 Box::pin(async move { 188 if definition.id() == MycDoctorCheckId::ClockSkew { 189 MycDoctorObservation::Skipped 190 } else if self.run(definition).await { 191 MycDoctorObservation::Pass 192 } else { 193 MycDoctorObservation::Fail 194 } 195 }) 196 } 197 } 198 199 fn wall_time_millis() -> Option<u64> { 200 SystemWallClock 201 .now_utc() 202 .ok() 203 .and_then(|time| time.get().checked_mul(1_000)) 204 .filter(|value| i64::try_from(*value).is_ok()) 205 } 206 207 fn absolute_deadline(duration_ms: u64) -> Option<u64> { 208 wall_time_millis()?.checked_add(duration_ms) 209 } 210 211 fn integrity_time() -> Option<IntegrityCheckedAtUnixMs> { 212 IntegrityCheckedAtUnixMs::new(wall_time_millis()?) 213 } 214 215 #[cfg(test)] 216 mod tests { 217 use super::*; 218 219 #[test] 220 fn deadline_math_is_checked_and_clock_skew_remains_unclaimed() { 221 assert!(absolute_deadline(15_000).is_some()); 222 assert!(integrity_time().is_some()); 223 } 224 225 #[test] 226 fn production_probe_source_retains_no_raw_error_projection() { 227 let source = include_str!("system_doctor.rs") 228 .split("#[cfg(test)]") 229 .next() 230 .expect("production source"); 231 for forbidden in ["format!(\"{error", "to_string()", "source()"] { 232 assert!(!source.contains(forbidden), "found `{forbidden}`"); 233 } 234 } 235 }