rhi

Coordinated trade for connected markets
git clone https://radroots.dev/git/rhi.git
Log | Files | Refs | README | LICENSE

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 }