services_hardening_state_catalog.rs (23785B)
1 #![forbid(unsafe_code)] 2 3 use std::error::Error; 4 5 use radroots_service_sqlite::{ 6 MigrationCatalog, MigrationChecksum, MigrationDescriptor, SchemaCatalog, SchemaObject, 7 SchemaObjectKind, SchemaVersionCatalog, 8 }; 9 use rhi::{ 10 RHI_MIGRATION_CATALOG_SHA256, RHI_STATE_BASE_SCHEMA_VERSION, RHI_STATE_SCHEMA_CATALOG_SHA256, 11 RHI_STATE_SCHEMA_VERSION, RHI_STATE_SCHEMA_VERSION_1_OBJECT_COUNT, 12 RHI_STATE_SCHEMA_VERSION_1_SHA256, RHI_STATE_SCHEMA_VERSION_2_MIGRATION_SHA256, 13 RHI_STATE_SCHEMA_VERSION_2_OBJECT_COUNT, RHI_STATE_SCHEMA_VERSION_2_SHA256, 14 RHI_STATE_SCHEMA_VERSION_3_MIGRATION_SHA256, RHI_STATE_SCHEMA_VERSION_3_OBJECT_COUNT, 15 RHI_STATE_SCHEMA_VERSION_3_SHA256, RHI_STATE_SCHEMA_VERSION_4_MIGRATION_SHA256, 16 RHI_STATE_SCHEMA_VERSION_4_OBJECT_COUNT, RHI_STATE_SCHEMA_VERSION_4_SHA256, 17 RHI_STATE_SCHEMA_VERSION_5_MIGRATION_SHA256, RHI_STATE_SCHEMA_VERSION_5_OBJECT_COUNT, 18 RHI_STATE_SCHEMA_VERSION_5_SHA256, RHI_STATE_SCHEMA_VERSION_6_MIGRATION_SHA256, 19 RHI_STATE_SCHEMA_VERSION_6_OBJECT_COUNT, RHI_STATE_SCHEMA_VERSION_6_SHA256, 20 RHI_STATE_SCHEMA_VERSION_7_MIGRATION_SHA256, RHI_STATE_SCHEMA_VERSION_7_OBJECT_COUNT, 21 RHI_STATE_SCHEMA_VERSION_7_SHA256, RHI_STATE_SCHEMA_VERSION_8_MIGRATION_SHA256, 22 RHI_STATE_SCHEMA_VERSION_8_OBJECT_COUNT, RHI_STATE_SCHEMA_VERSION_8_SHA256, 23 RHI_STATE_SCHEMA_VERSION_9_MIGRATION_SHA256, RHI_STATE_SCHEMA_VERSION_9_OBJECT_COUNT, 24 RHI_STATE_SCHEMA_VERSION_9_SHA256, RHI_STATE_SCHEMA_VERSION_10_MIGRATION_SHA256, 25 RHI_STATE_SCHEMA_VERSION_10_OBJECT_COUNT, RHI_STATE_SCHEMA_VERSION_10_SHA256, 26 RHI_STATE_SCHEMA_VERSION_11_MIGRATION_SHA256, RHI_STATE_SCHEMA_VERSION_11_OBJECT_COUNT, 27 RHI_STATE_SCHEMA_VERSION_11_SHA256, RhiStateCatalogErrorKind, rhi_migration_catalog, 28 rhi_schema_catalog, validate_rhi_state_catalogs, 29 }; 30 31 const CATALOG_SOURCE: &str = include_str!("../src/state_catalog.rs"); 32 const LIB_SOURCE: &str = include_str!("../src/lib.rs"); 33 const MANIFEST: &str = include_str!("../Cargo.toml"); 34 35 #[test] 36 fn schema_v1_through_v11_catalogs_have_exact_literal_identities() { 37 let migrations = rhi_migration_catalog().expect("RHI migration catalog"); 38 let schema = rhi_schema_catalog().expect("RHI schema catalog"); 39 40 assert_eq!(RHI_STATE_BASE_SCHEMA_VERSION, 1); 41 assert_eq!(RHI_STATE_SCHEMA_VERSION, 11); 42 assert_eq!(migrations.descriptors().len(), 10); 43 assert_eq!(migrations.current_version(), 11); 44 assert_eq!(migrations.descriptors()[0].target_version(), 2); 45 assert_eq!( 46 migrations.descriptors()[0].name().as_str(), 47 "create_configuration_binding_history" 48 ); 49 assert_eq!( 50 migrations.descriptors()[0].checksum().as_bytes(), 51 &RHI_STATE_SCHEMA_VERSION_2_MIGRATION_SHA256 52 ); 53 assert_eq!(migrations.descriptors()[1].target_version(), 3); 54 assert_eq!( 55 migrations.descriptors()[1].name().as_str(), 56 "create_immutable_trade_evidence" 57 ); 58 assert_eq!( 59 migrations.descriptors()[1].checksum().as_bytes(), 60 &RHI_STATE_SCHEMA_VERSION_3_MIGRATION_SHA256 61 ); 62 assert_eq!(migrations.descriptors()[2].target_version(), 4); 63 assert_eq!( 64 migrations.descriptors()[2].name().as_str(), 65 "create_source_checkpoints_and_dirty_generations" 66 ); 67 assert_eq!( 68 migrations.descriptors()[2].checksum().as_bytes(), 69 &RHI_STATE_SCHEMA_VERSION_4_MIGRATION_SHA256 70 ); 71 assert_eq!(migrations.descriptors()[3].target_version(), 5); 72 assert_eq!( 73 migrations.descriptors()[3].name().as_str(), 74 "create_reconciliation_jobs" 75 ); 76 assert_eq!( 77 migrations.descriptors()[3].checksum().as_bytes(), 78 &RHI_STATE_SCHEMA_VERSION_5_MIGRATION_SHA256 79 ); 80 assert_eq!(migrations.descriptors()[4].target_version(), 6); 81 assert_eq!( 82 migrations.descriptors()[4].name().as_str(), 83 "create_reconciliation_source_results" 84 ); 85 assert_eq!( 86 migrations.descriptors()[4].checksum().as_bytes(), 87 &RHI_STATE_SCHEMA_VERSION_6_MIGRATION_SHA256 88 ); 89 assert_eq!(migrations.descriptors()[5].target_version(), 7); 90 assert_eq!( 91 migrations.descriptors()[5].name().as_str(), 92 "create_reports_and_publication_outbox" 93 ); 94 assert_eq!( 95 migrations.descriptors()[5].checksum().as_bytes(), 96 &RHI_STATE_SCHEMA_VERSION_7_MIGRATION_SHA256 97 ); 98 assert_eq!(migrations.descriptors()[6].target_version(), 8); 99 assert_eq!( 100 migrations.descriptors()[6].name().as_str(), 101 "guard_reconciliation_job_state_shape" 102 ); 103 assert_eq!( 104 migrations.descriptors()[6].checksum().as_bytes(), 105 &RHI_STATE_SCHEMA_VERSION_8_MIGRATION_SHA256 106 ); 107 assert_eq!(migrations.descriptors()[7].target_version(), 9); 108 assert_eq!( 109 migrations.descriptors()[7].name().as_str(), 110 "create_presence_desired_state" 111 ); 112 assert_eq!( 113 migrations.descriptors()[7].checksum().as_bytes(), 114 &RHI_STATE_SCHEMA_VERSION_9_MIGRATION_SHA256 115 ); 116 assert_eq!(migrations.descriptors()[8].target_version(), 10); 117 assert_eq!( 118 migrations.descriptors()[8].name().as_str(), 119 "create_presence_publication_workflow" 120 ); 121 assert_eq!( 122 migrations.descriptors()[8].checksum().as_bytes(), 123 &RHI_STATE_SCHEMA_VERSION_10_MIGRATION_SHA256 124 ); 125 assert_eq!(migrations.descriptors()[9].target_version(), 11); 126 assert_eq!( 127 migrations.descriptors()[9].name().as_str(), 128 "create_admin_operation_journal" 129 ); 130 assert_eq!( 131 migrations.descriptors()[9].checksum().as_bytes(), 132 &RHI_STATE_SCHEMA_VERSION_11_MIGRATION_SHA256 133 ); 134 assert_eq!( 135 migrations.digest().as_bytes(), 136 &RHI_MIGRATION_CATALOG_SHA256 137 ); 138 139 assert_eq!(schema.versions().len(), 11); 140 let version = schema.versions()[0]; 141 assert_eq!(version.version(), 1); 142 assert_eq!( 143 version.object_count(), 144 RHI_STATE_SCHEMA_VERSION_1_OBJECT_COUNT 145 ); 146 assert_eq!(version.object_count(), 6); 147 assert_eq!( 148 version.digest().as_bytes(), 149 &RHI_STATE_SCHEMA_VERSION_1_SHA256 150 ); 151 let version = schema.versions()[1]; 152 assert_eq!(version.version(), 2); 153 assert_eq!( 154 version.object_count(), 155 RHI_STATE_SCHEMA_VERSION_2_OBJECT_COUNT 156 ); 157 assert_eq!(version.object_count(), 10); 158 assert_eq!( 159 version.digest().as_bytes(), 160 &RHI_STATE_SCHEMA_VERSION_2_SHA256 161 ); 162 let version = schema.versions()[2]; 163 assert_eq!(version.version(), 3); 164 assert_eq!( 165 version.object_count(), 166 RHI_STATE_SCHEMA_VERSION_3_OBJECT_COUNT 167 ); 168 assert_eq!(version.object_count(), 22); 169 assert_eq!( 170 version.digest().as_bytes(), 171 &RHI_STATE_SCHEMA_VERSION_3_SHA256 172 ); 173 let version = schema.versions()[3]; 174 assert_eq!(version.version(), 4); 175 assert_eq!( 176 version.object_count(), 177 RHI_STATE_SCHEMA_VERSION_4_OBJECT_COUNT 178 ); 179 assert_eq!(version.object_count(), 28); 180 assert_eq!( 181 version.digest().as_bytes(), 182 &RHI_STATE_SCHEMA_VERSION_4_SHA256 183 ); 184 let version = schema.versions()[4]; 185 assert_eq!(version.version(), 5); 186 assert_eq!( 187 version.object_count(), 188 RHI_STATE_SCHEMA_VERSION_5_OBJECT_COUNT 189 ); 190 assert_eq!(version.object_count(), 33); 191 assert_eq!( 192 version.digest().as_bytes(), 193 &RHI_STATE_SCHEMA_VERSION_5_SHA256 194 ); 195 let version = schema.versions()[5]; 196 assert_eq!(version.version(), 6); 197 assert_eq!( 198 version.object_count(), 199 RHI_STATE_SCHEMA_VERSION_6_OBJECT_COUNT 200 ); 201 assert_eq!(version.object_count(), 39); 202 assert_eq!( 203 version.digest().as_bytes(), 204 &RHI_STATE_SCHEMA_VERSION_6_SHA256 205 ); 206 let version = schema.versions()[6]; 207 assert_eq!(version.version(), 7); 208 assert_eq!( 209 version.object_count(), 210 RHI_STATE_SCHEMA_VERSION_7_OBJECT_COUNT 211 ); 212 assert_eq!(version.object_count(), 63); 213 assert_eq!( 214 version.digest().as_bytes(), 215 &RHI_STATE_SCHEMA_VERSION_7_SHA256 216 ); 217 let version = schema.versions()[7]; 218 assert_eq!(version.version(), 8); 219 assert_eq!( 220 version.object_count(), 221 RHI_STATE_SCHEMA_VERSION_8_OBJECT_COUNT 222 ); 223 assert_eq!(version.object_count(), 65); 224 assert_eq!( 225 version.digest().as_bytes(), 226 &RHI_STATE_SCHEMA_VERSION_8_SHA256 227 ); 228 let version = schema.versions()[8]; 229 assert_eq!(version.version(), 9); 230 assert_eq!( 231 version.object_count(), 232 RHI_STATE_SCHEMA_VERSION_9_OBJECT_COUNT 233 ); 234 assert_eq!(version.object_count(), 69); 235 assert_eq!( 236 version.digest().as_bytes(), 237 &RHI_STATE_SCHEMA_VERSION_9_SHA256 238 ); 239 let version = schema.versions()[9]; 240 assert_eq!(version.version(), 10); 241 assert_eq!( 242 version.object_count(), 243 RHI_STATE_SCHEMA_VERSION_10_OBJECT_COUNT 244 ); 245 assert_eq!(version.object_count(), 80); 246 assert_eq!( 247 version.digest().as_bytes(), 248 &RHI_STATE_SCHEMA_VERSION_10_SHA256 249 ); 250 let version = schema.versions()[10]; 251 assert_eq!(version.version(), 11); 252 assert_eq!( 253 version.object_count(), 254 RHI_STATE_SCHEMA_VERSION_11_OBJECT_COUNT 255 ); 256 assert_eq!(version.object_count(), 82); 257 assert_eq!( 258 version.digest().as_bytes(), 259 &RHI_STATE_SCHEMA_VERSION_11_SHA256 260 ); 261 assert_eq!(schema.digest().as_bytes(), &RHI_STATE_SCHEMA_CATALOG_SHA256); 262 assert_eq!(schema.migration_catalog_digest(), migrations.digest()); 263 validate_rhi_state_catalogs(&migrations, &schema).expect("exact catalogs"); 264 265 assert_eq!( 266 lower_hex(&RHI_MIGRATION_CATALOG_SHA256), 267 "e6cbacbd1eb636c1a560f85ef8e51e89c9ffe3b342e66bc5c0b47ab34e90c818" 268 ); 269 assert_eq!( 270 lower_hex(&RHI_STATE_SCHEMA_VERSION_1_SHA256), 271 "94dc66fbca601679615c055229dc0db6119f5bd92b04390c67f698a036fa78ae" 272 ); 273 assert_eq!( 274 lower_hex(&RHI_STATE_SCHEMA_VERSION_2_MIGRATION_SHA256), 275 "a2c1aa53f7feee0385e774de20e072520f4926c559c6421aab590e92ba144c55" 276 ); 277 assert_eq!( 278 lower_hex(&RHI_STATE_SCHEMA_VERSION_2_SHA256), 279 "bccbf1e62fe7644c9c377205b25a92298e088b8c26d15ba45133ca5e9b7315a9" 280 ); 281 assert_eq!( 282 lower_hex(&RHI_STATE_SCHEMA_VERSION_3_MIGRATION_SHA256), 283 "07b098c393140afec222fce76ec668698d50f1d23785856873e30445af1a013f" 284 ); 285 assert_eq!( 286 lower_hex(&RHI_STATE_SCHEMA_VERSION_3_SHA256), 287 "fd96226405ab68655aee00f683f6023c7aab2dbd23feadac165333490d6f0ad5" 288 ); 289 assert_eq!( 290 lower_hex(&RHI_STATE_SCHEMA_VERSION_4_MIGRATION_SHA256), 291 "2441f7c4c11594c6fde787dbb94e44ba00b72d2c979b0e7fd3c733eae9144d78" 292 ); 293 assert_eq!( 294 lower_hex(&RHI_STATE_SCHEMA_VERSION_4_SHA256), 295 "9ca78a54b0ea2013e7aa70d09beadcfb6cdde07c40429dffe91cb83c2a425cea" 296 ); 297 assert_eq!( 298 lower_hex(&RHI_STATE_SCHEMA_VERSION_5_MIGRATION_SHA256), 299 "8275ffb3d5c9fa0c7648f89e8bfb92877b0dcd5ef3bf0b5254b7ffd2580bd45d" 300 ); 301 assert_eq!( 302 lower_hex(&RHI_STATE_SCHEMA_VERSION_5_SHA256), 303 "eef67b48400dee234f6c36d422551c7e9983150b887f264250f9c87a1a313e60" 304 ); 305 assert_eq!( 306 lower_hex(&RHI_STATE_SCHEMA_VERSION_6_MIGRATION_SHA256), 307 "48a14b2744c41186496d597ec780ad998fa9308a9c9d7540d475db3f4dcfc4bb" 308 ); 309 assert_eq!( 310 lower_hex(&RHI_STATE_SCHEMA_VERSION_6_SHA256), 311 "5d1fa9508b5b8a0c8065fd37f11c6866d51f9294c75272041f349e95ced50fb4" 312 ); 313 assert_eq!( 314 lower_hex(&RHI_STATE_SCHEMA_VERSION_7_MIGRATION_SHA256), 315 "9debf4f3caad4d018311cb169bf9282264d6abfc49e718840b9cbfad42ed357c" 316 ); 317 assert_eq!( 318 lower_hex(&RHI_STATE_SCHEMA_VERSION_7_SHA256), 319 "840aa83c689f9df99d26c5b4ef117131c270ef7522674037def79628a2b03946" 320 ); 321 assert_eq!( 322 lower_hex(&RHI_STATE_SCHEMA_VERSION_8_MIGRATION_SHA256), 323 "6d3aa06e6908e4281b5066ed4a13e2287bdaec05b0e539215891892a5afb583b" 324 ); 325 assert_eq!( 326 lower_hex(&RHI_STATE_SCHEMA_VERSION_8_SHA256), 327 "7cef559ae1e6efe158c5d1de50114ebaccac90538e0cd49a4ee214cb0a39c618" 328 ); 329 assert_eq!( 330 lower_hex(&RHI_STATE_SCHEMA_VERSION_9_MIGRATION_SHA256), 331 "32dabe777289e0fb6e64a4c1f8257843fc08018321c3b7ec5ca584fa1862bdcc" 332 ); 333 assert_eq!( 334 lower_hex(&RHI_STATE_SCHEMA_VERSION_9_SHA256), 335 "5551e8790544a7c78c8376c5ccf83dd2a8486d2dc08b6a7808bb2007c94335ec" 336 ); 337 assert_eq!( 338 lower_hex(&RHI_STATE_SCHEMA_VERSION_10_MIGRATION_SHA256), 339 "541ad13b2cb08d59857205e6ffaec19b74de0853240fdf11fa1340e406c7114e" 340 ); 341 assert_eq!( 342 lower_hex(&RHI_STATE_SCHEMA_VERSION_10_SHA256), 343 "d2aed51d0a6a2c01eda1844608472b2dcd502abaa8a4ca30a4823535b8bd0e45" 344 ); 345 assert_eq!( 346 lower_hex(&RHI_STATE_SCHEMA_VERSION_11_MIGRATION_SHA256), 347 "e3fbde511e8424c97080be2c09ed810ae284631d75eb25c2e88a6076b700aaef" 348 ); 349 assert_eq!( 350 lower_hex(&RHI_STATE_SCHEMA_VERSION_11_SHA256), 351 "c25ec63b33b411618068ee06a04c99ee7166e0014d979039faeaea4dc1ac7e62" 352 ); 353 assert_eq!( 354 lower_hex(&RHI_STATE_SCHEMA_CATALOG_SHA256), 355 "aec482818bd9a6f33fd92b55d142c6b85aa6f086855701dfc4b78f2a7ef5cf6d" 356 ); 357 } 358 359 #[test] 360 fn independent_validator_rejects_migration_or_schema_drift() { 361 const SQL: &str = "CREATE TABLE unexpected (value INTEGER NOT NULL) STRICT"; 362 let migration = 363 MigrationDescriptor::sql(2, "unexpected_schema", SQL, MigrationChecksum::for_sql(SQL)) 364 .expect("valid drift fixture"); 365 let migrations = MigrationCatalog::new([migration]).expect("drift migration catalog"); 366 let expected_schema = rhi_schema_catalog().expect("expected schema"); 367 assert_eq!( 368 validate_rhi_state_catalogs(&migrations, &expected_schema) 369 .expect_err("migration drift") 370 .kind(), 371 RhiStateCatalogErrorKind::CatalogMismatch 372 ); 373 374 let exact_migrations = rhi_migration_catalog().expect("exact migrations"); 375 let object_digest = 376 SchemaObject::computed_digest(SchemaObjectKind::Table, "unexpected", "unexpected", SQL) 377 .expect("object digest"); 378 let object = SchemaObject::new( 379 SchemaObjectKind::Table, 380 "unexpected", 381 "unexpected", 382 SQL, 383 object_digest, 384 ) 385 .expect("schema object"); 386 let version_one = SchemaVersionCatalog::new( 387 1, 388 [], 389 radroots_service_sqlite::SchemaDigest::from_bytes(RHI_STATE_SCHEMA_VERSION_1_SHA256), 390 ) 391 .expect("version one"); 392 let snapshot_digest = 393 SchemaVersionCatalog::computed_digest(2, [object.clone()]).expect("snapshot digest"); 394 let version_two = SchemaVersionCatalog::new(2, [object], snapshot_digest).expect("version two"); 395 let snapshot_digest = 396 SchemaVersionCatalog::computed_digest(3, [version_two_object()]).expect("v3 digest"); 397 let version_three = SchemaVersionCatalog::new(3, [version_two_object()], snapshot_digest) 398 .expect("version three"); 399 let snapshot_digest = 400 SchemaVersionCatalog::computed_digest(4, [version_two_object()]).expect("v4 digest"); 401 let version_four = SchemaVersionCatalog::new(4, [version_two_object()], snapshot_digest) 402 .expect("version four"); 403 let snapshot_digest = 404 SchemaVersionCatalog::computed_digest(5, [version_two_object()]).expect("v5 digest"); 405 let version_five = SchemaVersionCatalog::new(5, [version_two_object()], snapshot_digest) 406 .expect("version five"); 407 let snapshot_digest = 408 SchemaVersionCatalog::computed_digest(6, [version_two_object()]).expect("v6 digest"); 409 let version_six = 410 SchemaVersionCatalog::new(6, [version_two_object()], snapshot_digest).expect("version six"); 411 let snapshot_digest = 412 SchemaVersionCatalog::computed_digest(7, [version_two_object()]).expect("v7 digest"); 413 let version_seven = SchemaVersionCatalog::new(7, [version_two_object()], snapshot_digest) 414 .expect("version seven"); 415 let snapshot_digest = 416 SchemaVersionCatalog::computed_digest(8, [version_two_object()]).expect("v8 digest"); 417 let version_eight = SchemaVersionCatalog::new(8, [version_two_object()], snapshot_digest) 418 .expect("version eight"); 419 let snapshot_digest = 420 SchemaVersionCatalog::computed_digest(9, [version_two_object()]).expect("v9 digest"); 421 let version_nine = SchemaVersionCatalog::new(9, [version_two_object()], snapshot_digest) 422 .expect("version nine"); 423 let snapshot_digest = 424 SchemaVersionCatalog::computed_digest(10, [version_two_object()]).expect("v10 digest"); 425 let version_ten = SchemaVersionCatalog::new(10, [version_two_object()], snapshot_digest) 426 .expect("version ten"); 427 let snapshot_digest = 428 SchemaVersionCatalog::computed_digest(11, [version_two_object()]).expect("v11 digest"); 429 let version_eleven = SchemaVersionCatalog::new(11, [version_two_object()], snapshot_digest) 430 .expect("version eleven"); 431 let schema = SchemaCatalog::new( 432 &exact_migrations, 433 [ 434 version_one, 435 version_two, 436 version_three, 437 version_four, 438 version_five, 439 version_six, 440 version_seven, 441 version_eight, 442 version_nine, 443 version_ten, 444 version_eleven, 445 ], 446 ) 447 .expect("drift schema catalog"); 448 assert_eq!( 449 validate_rhi_state_catalogs(&exact_migrations, &schema) 450 .expect_err("schema drift") 451 .kind(), 452 RhiStateCatalogErrorKind::CatalogMismatch 453 ); 454 } 455 456 #[test] 457 fn catalog_errors_are_stable_source_free_and_redacted() { 458 let migrations = rhi_migration_catalog().expect("migration catalog"); 459 let object_digest = SchemaObject::computed_digest( 460 SchemaObjectKind::Table, 461 "secret_table", 462 "secret_table", 463 "secret SQL text", 464 ) 465 .expect("object digest"); 466 let object = SchemaObject::new( 467 SchemaObjectKind::Table, 468 "secret_table", 469 "secret_table", 470 "secret SQL text", 471 object_digest, 472 ) 473 .expect("object"); 474 let version_one = SchemaVersionCatalog::new( 475 1, 476 [], 477 radroots_service_sqlite::SchemaDigest::from_bytes(RHI_STATE_SCHEMA_VERSION_1_SHA256), 478 ) 479 .expect("version one"); 480 let snapshot = 481 SchemaVersionCatalog::computed_digest(2, [object.clone()]).expect("snapshot digest"); 482 let version_two = SchemaVersionCatalog::new(2, [object], snapshot).expect("version two"); 483 let version_three_digest = 484 SchemaVersionCatalog::computed_digest(3, [secret_object()]).expect("v3 digest"); 485 let version_three = SchemaVersionCatalog::new(3, [secret_object()], version_three_digest) 486 .expect("version three"); 487 let version_four_digest = 488 SchemaVersionCatalog::computed_digest(4, [secret_object()]).expect("v4 digest"); 489 let version_four = 490 SchemaVersionCatalog::new(4, [secret_object()], version_four_digest).expect("version four"); 491 let version_five_digest = 492 SchemaVersionCatalog::computed_digest(5, [secret_object()]).expect("v5 digest"); 493 let version_five = 494 SchemaVersionCatalog::new(5, [secret_object()], version_five_digest).expect("version five"); 495 let version_six_digest = 496 SchemaVersionCatalog::computed_digest(6, [secret_object()]).expect("v6 digest"); 497 let version_six = 498 SchemaVersionCatalog::new(6, [secret_object()], version_six_digest).expect("version six"); 499 let version_seven_digest = 500 SchemaVersionCatalog::computed_digest(7, [secret_object()]).expect("v7 digest"); 501 let version_seven = SchemaVersionCatalog::new(7, [secret_object()], version_seven_digest) 502 .expect("version seven"); 503 let version_eight_digest = 504 SchemaVersionCatalog::computed_digest(8, [secret_object()]).expect("v8 digest"); 505 let version_eight = SchemaVersionCatalog::new(8, [secret_object()], version_eight_digest) 506 .expect("version eight"); 507 let version_nine_digest = 508 SchemaVersionCatalog::computed_digest(9, [secret_object()]).expect("v9 digest"); 509 let version_nine = 510 SchemaVersionCatalog::new(9, [secret_object()], version_nine_digest).expect("version nine"); 511 let version_ten_digest = 512 SchemaVersionCatalog::computed_digest(10, [secret_object()]).expect("v10 digest"); 513 let version_ten = 514 SchemaVersionCatalog::new(10, [secret_object()], version_ten_digest).expect("version ten"); 515 let version_eleven_digest = 516 SchemaVersionCatalog::computed_digest(11, [secret_object()]).expect("v11 digest"); 517 let version_eleven = SchemaVersionCatalog::new(11, [secret_object()], version_eleven_digest) 518 .expect("version eleven"); 519 let schema = SchemaCatalog::new( 520 &migrations, 521 [ 522 version_one, 523 version_two, 524 version_three, 525 version_four, 526 version_five, 527 version_six, 528 version_seven, 529 version_eight, 530 version_nine, 531 version_ten, 532 version_eleven, 533 ], 534 ) 535 .expect("schema catalog"); 536 let error = validate_rhi_state_catalogs(&migrations, &schema).expect_err("mismatch"); 537 538 assert_eq!(error.kind(), RhiStateCatalogErrorKind::CatalogMismatch); 539 assert_eq!(error.code(), "state_catalog_mismatch"); 540 assert!(Error::source(&error).is_none()); 541 let rendered = format!("{error} {error:?}"); 542 assert!(!rendered.contains("secret")); 543 assert!(!rendered.contains(&lower_hex(snapshot.as_bytes()))); 544 } 545 546 fn version_two_object() -> SchemaObject { 547 const SQL: &str = "CREATE TABLE unexpected (value INTEGER NOT NULL) STRICT"; 548 let digest = 549 SchemaObject::computed_digest(SchemaObjectKind::Table, "unexpected", "unexpected", SQL) 550 .expect("object digest"); 551 SchemaObject::new( 552 SchemaObjectKind::Table, 553 "unexpected", 554 "unexpected", 555 SQL, 556 digest, 557 ) 558 .expect("schema object") 559 } 560 561 fn secret_object() -> SchemaObject { 562 let digest = SchemaObject::computed_digest( 563 SchemaObjectKind::Table, 564 "secret_table", 565 "secret_table", 566 "secret SQL text", 567 ) 568 .expect("object digest"); 569 SchemaObject::new( 570 SchemaObjectKind::Table, 571 "secret_table", 572 "secret_table", 573 "secret SQL text", 574 digest, 575 ) 576 .expect("object") 577 } 578 579 #[test] 580 fn catalog_source_is_pure_pinned_and_uses_only_the_shared_authority() { 581 assert!(MANIFEST.contains( 582 "radroots_service_sqlite = { git = \"https://github.com/radrootslabs/lib\", rev = \"055096853fca95e15d0f813d33a14aca13be3881\", version = \"=0.1.0-alpha\" }" 583 )); 584 assert!(LIB_SOURCE.contains("mod state_catalog;")); 585 assert!(!LIB_SOURCE.contains("pub mod state_catalog;")); 586 assert!(CATALOG_SOURCE.contains("MigrationDescriptor::sql(")); 587 assert!(CATALOG_SOURCE.contains("CREATE TABLE rhi_config_bindings")); 588 assert!(CATALOG_SOURCE.contains("SchemaDigest::from_bytes(")); 589 assert!(!CATALOG_SOURCE.contains("computed_digest")); 590 for forbidden in [ 591 "sqlx::", 592 "rusqlite", 593 "libsqlite3_sys", 594 "raw_sql", 595 "std::fs", 596 "std::path", 597 "Connection", 598 "Transaction", 599 ] { 600 assert!( 601 !CATALOG_SOURCE.contains(forbidden), 602 "found forbidden catalog authority `{forbidden}`" 603 ); 604 } 605 } 606 607 fn lower_hex(bytes: &[u8]) -> String { 608 const DIGITS: &[u8; 16] = b"0123456789abcdef"; 609 let mut output = String::with_capacity(bytes.len() * 2); 610 for byte in bytes { 611 output.push(char::from(DIGITS[usize::from(byte >> 4)])); 612 output.push(char::from(DIGITS[usize::from(byte & 0x0f)])); 613 } 614 output 615 }