services_hardening_state_repository_topology.rs (9464B)
1 #![forbid(unsafe_code)] 2 3 use rhi::{ 4 RHI_STATE_REPOSITORY_CONTRACT_VERSION, RHI_STATE_REPOSITORY_COUNT, RhiStateRepositoryKind, 5 RhiStateRepositoryWriteClass, rhi_state_repository_descriptors, 6 }; 7 use serde_json::json; 8 9 const CONTRACT: &str = 10 include_str!("../contracts/services_hardening/state_repository_topology.v1.json"); 11 const LIB_SOURCE: &str = include_str!("../src/lib.rs"); 12 const HOST_SOURCE: &str = include_str!("../src/state_host.rs"); 13 const REPOSITORY_SOURCE: &str = include_str!("../src/state_repository.rs"); 14 15 #[test] 16 fn machine_contract_and_typed_descriptor_inventory_are_exact() { 17 let contract: serde_json::Value = serde_json::from_str(CONTRACT).expect("repository contract"); 18 assert_eq!(contract["schema"], "radroots.rhi.state-repository-topology"); 19 assert_eq!(contract["schema_version"], 1); 20 assert_eq!( 21 contract["contract_version"], 22 RHI_STATE_REPOSITORY_CONTRACT_VERSION 23 ); 24 assert_eq!(contract["repository_count"], RHI_STATE_REPOSITORY_COUNT); 25 assert_eq!(contract["construction"], "sealed_to_open_rhi_state_host"); 26 assert_eq!(contract["raw_sqlite_authority_exposed"], false); 27 assert_eq!( 28 contract["deferred_behavior"], 29 json!([ 30 "later_schema_migrations", 31 "remaining_repository_crud", 32 "backup_restore_and_recovery", 33 "network_io", 34 "task_supervision" 35 ]) 36 ); 37 assert_eq!( 38 contract 39 .as_object() 40 .expect("contract object") 41 .keys() 42 .map(String::as_str) 43 .collect::<std::collections::BTreeSet<_>>(), 44 [ 45 "schema", 46 "schema_version", 47 "contract_version", 48 "repository_count", 49 "construction", 50 "raw_sqlite_authority_exposed", 51 "repositories", 52 "deferred_behavior", 53 ] 54 .into_iter() 55 .collect() 56 ); 57 58 let descriptors = rhi_state_repository_descriptors(); 59 let actual = descriptors 60 .iter() 61 .map(|descriptor| { 62 json!({ 63 "kind": descriptor.code(), 64 "backing_table": descriptor.backing_table(), 65 "write_class": descriptor.write_class().code(), 66 }) 67 }) 68 .collect::<Vec<_>>(); 69 assert_eq!( 70 contract["repositories"] 71 .as_array() 72 .expect("repository array"), 73 &actual 74 ); 75 assert_eq!(descriptors.len(), RHI_STATE_REPOSITORY_COUNT); 76 for repository in contract["repositories"] 77 .as_array() 78 .expect("repository array") 79 { 80 assert_eq!( 81 repository 82 .as_object() 83 .expect("repository object") 84 .keys() 85 .map(String::as_str) 86 .collect::<std::collections::BTreeSet<_>>(), 87 ["kind", "backing_table", "write_class"] 88 .into_iter() 89 .collect() 90 ); 91 } 92 } 93 94 #[test] 95 fn repository_kinds_are_closed_ordered_and_cross_bound() { 96 use RhiStateRepositoryKind as Kind; 97 use RhiStateRepositoryWriteClass as Write; 98 99 let expected = [ 100 ( 101 Kind::Source, 102 "source", 103 "evidence_reconciliation_sources", 104 Write::AppendOnly, 105 ), 106 ( 107 Kind::SourceCursor, 108 "source_cursor", 109 "relay_checkpoints", 110 Write::CompareAndSwap, 111 ), 112 ( 113 Kind::SourceCompletion, 114 "source_completion", 115 "evidence_reconciliation_sources", 116 Write::AppendOnly, 117 ), 118 ( 119 Kind::SignedEvent, 120 "signed_event", 121 "nostr_events", 122 Write::AppendOnly, 123 ), 124 ( 125 Kind::Mutation, 126 "mutation", 127 "trade_mutations", 128 Write::AppendOnly, 129 ), 130 ( 131 Kind::Provenance, 132 "provenance", 133 "relay_observations", 134 Write::AppendOnly, 135 ), 136 ( 137 Kind::DirtyTrade, 138 "dirty_trade", 139 "trade_dirty_generations", 140 Write::CompareAndSwap, 141 ), 142 ( 143 Kind::ReconciliationJob, 144 "reconciliation_job", 145 "reconciliation_jobs", 146 Write::CompareAndSwap, 147 ), 148 ( 149 Kind::ReconciliationAttempt, 150 "reconciliation_attempt", 151 "evidence_reconciliations", 152 Write::AppendOnly, 153 ), 154 ( 155 Kind::EvidenceManifest, 156 "evidence_manifest", 157 "evidence_manifests", 158 Write::Immutable, 159 ), 160 ( 161 Kind::Projection, 162 "projection", 163 "trade_projections", 164 Write::Immutable, 165 ), 166 ( 167 Kind::Report, 168 "report", 169 "attestation_reports", 170 Write::Immutable, 171 ), 172 ( 173 Kind::Supersession, 174 "supersession", 175 "attestation_reports", 176 Write::AppendOnly, 177 ), 178 ( 179 Kind::SignedAttestationEvent, 180 "signed_attestation_event", 181 "signed_attestation_events", 182 Write::Immutable, 183 ), 184 ( 185 Kind::PublicationOutbox, 186 "publication_outbox", 187 "publication_outbox", 188 Write::CompareAndSwap, 189 ), 190 ( 191 Kind::PublicationTarget, 192 "publication_target", 193 "publication_targets", 194 Write::CompareAndSwap, 195 ), 196 ( 197 Kind::PublicationAttempt, 198 "publication_attempt", 199 "publication_attempts", 200 Write::AppendOnly, 201 ), 202 ( 203 Kind::DesiredPresence, 204 "desired_presence", 205 "presence_desired_state", 206 Write::CompareAndSwap, 207 ), 208 ( 209 Kind::PresenceOutbox, 210 "presence_outbox", 211 "presence_outbox", 212 Write::CompareAndSwap, 213 ), 214 ( 215 Kind::PresenceTarget, 216 "presence_target", 217 "presence_targets", 218 Write::CompareAndSwap, 219 ), 220 ( 221 Kind::PresenceAttempt, 222 "presence_attempt", 223 "presence_attempts", 224 Write::AppendOnly, 225 ), 226 ]; 227 for (descriptor, (kind, code, table, write_class)) in 228 rhi_state_repository_descriptors().iter().zip(expected) 229 { 230 assert_eq!(descriptor.kind(), kind); 231 assert_eq!(descriptor.code(), code); 232 assert_eq!(descriptor.backing_table(), table); 233 assert_eq!(descriptor.write_class(), write_class); 234 } 235 } 236 237 #[test] 238 fn capabilities_are_private_sealed_and_defer_unowned_behavior() { 239 assert!(LIB_SOURCE.contains("mod state_repository;")); 240 assert!(!LIB_SOURCE.contains("pub mod state_repository;")); 241 assert!(HOST_SOURCE.contains("pub const fn repositories(&self) -> RhiStateRepositories<'_>")); 242 for required in [ 243 "pub const fn sources(&self) -> RhiSourceRepository<'host>", 244 "pub const fn source_cursors(&self) -> RhiSourceCursorRepository<'host>", 245 "pub const fn source_completions(&self) -> RhiSourceCompletionRepository<'host>", 246 "pub const fn signed_events(&self) -> RhiSignedEventRepository<'host>", 247 "pub const fn mutations(&self) -> RhiMutationRepository<'host>", 248 "pub const fn provenance(&self) -> RhiProvenanceRepository<'host>", 249 "pub const fn dirty_trades(&self) -> RhiDirtyTradeRepository<'host>", 250 "pub const fn reconciliation_jobs(&self) -> RhiReconciliationJobRepository<'host>", 251 "pub const fn reconciliation_attempts(&self) -> RhiReconciliationAttemptRepository<'host>", 252 "pub const fn evidence_manifests(&self) -> RhiEvidenceManifestRepository<'host>", 253 "pub const fn projections(&self) -> RhiProjectionRepository<'host>", 254 "pub const fn reports(&self) -> RhiReportRepository<'host>", 255 "pub const fn supersessions(&self) -> RhiSupersessionRepository<'host>", 256 "pub const fn signed_attestation_events(&self) -> RhiSignedAttestationEventRepository<'host>", 257 "pub const fn publication_outbox(&self) -> RhiPublicationOutboxRepository<'host>", 258 "pub const fn publication_targets(&self) -> RhiPublicationTargetRepository<'host>", 259 "pub const fn publication_attempts(&self) -> RhiPublicationAttemptRepository<'host>", 260 "pub const fn desired_presence(&self) -> RhiDesiredPresenceRepository<'host>", 261 "pub const fn presence_outbox(&self) -> RhiPresenceOutboxRepository<'host>", 262 "pub const fn presence_targets(&self) -> RhiPresenceTargetRepository<'host>", 263 "pub const fn presence_attempts(&self) -> RhiPresenceAttemptRepository<'host>", 264 ] { 265 assert!(REPOSITORY_SOURCE.contains(required), "missing {required}"); 266 } 267 for forbidden in [ 268 "SqlitePool", 269 "SqliteConnection", 270 "ServiceSqliteTransaction", 271 "sqlx::", 272 "rusqlite::", 273 "CREATE TABLE", 274 "INSERT INTO", 275 "UPDATE ", 276 "DELETE FROM", 277 "std::fs", 278 "std::path", 279 "std::env", 280 "tokio::", 281 "nostr::", 282 "Deref", 283 "AsRef", 284 ] { 285 assert!( 286 !REPOSITORY_SOURCE.contains(forbidden), 287 "premature or escaping repository authority {forbidden}" 288 ); 289 } 290 }