commit 7ffa05d3f71ac35fec30302db26d2beac87982c2
parent 5704286ef326288a0ef8f4aaca53f8dcc3682c56
Author: triesap <tyson@radroots.org>
Date: Fri, 21 Aug 2026 13:30:28 +0000
state: freeze Myc SQLite catalogs
Diffstat:
7 files changed, 434 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -798,6 +798,16 @@ dependencies = [
]
[[package]]
+name = "fs2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
name = "futures"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -805,6 +815,7 @@ checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
+ "futures-executor",
"futures-io",
"futures-sink",
"futures-task",
@@ -1411,9 +1422,9 @@ dependencies = [
[[package]]
name = "libsqlite3-sys"
-version = "0.30.1"
+version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
+checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
dependencies = [
"cc",
"pkg-config",
@@ -1533,6 +1544,7 @@ dependencies = [
"radroots_nostr_connect",
"radroots_runtime_paths",
"radroots_secrets",
+ "radroots_service_sqlite",
"radroots_signing",
"rand 0.9.2",
"serde",
@@ -2073,6 +2085,24 @@ dependencies = [
]
[[package]]
+name = "radroots_service_sqlite"
+version = "0.1.0-alpha"
+source = "git+https://github.com/radrootslabs/lib?rev=b44119fbac5985be8127ad1bf56d2950e6399427#b44119fbac5985be8127ad1bf56d2950e6399427"
+dependencies = [
+ "fs2",
+ "futures",
+ "libsqlite3-sys",
+ "radroots_runtime_paths",
+ "radroots_storage",
+ "rustix",
+ "serde",
+ "serde_json",
+ "sha2",
+ "sqlx",
+ "tokio",
+]
+
+[[package]]
name = "radroots_signing"
version = "0.1.0-alpha"
source = "git+https://github.com/radrootslabs/lib?rev=b44119fbac5985be8127ad1bf56d2950e6399427#b44119fbac5985be8127ad1bf56d2950e6399427"
@@ -2087,6 +2117,40 @@ dependencies = [
]
[[package]]
+name = "radroots_storage"
+version = "0.1.0-alpha"
+source = "git+https://github.com/radrootslabs/lib?rev=b44119fbac5985be8127ad1bf56d2950e6399427#b44119fbac5985be8127ad1bf56d2950e6399427"
+dependencies = [
+ "radroots_event",
+ "radroots_event_codec",
+ "radroots_protocol",
+ "radroots_trade",
+ "radroots_transport",
+ "sha2",
+]
+
+[[package]]
+name = "radroots_trade"
+version = "0.1.0-alpha"
+source = "git+https://github.com/radrootslabs/lib?rev=b44119fbac5985be8127ad1bf56d2950e6399427#b44119fbac5985be8127ad1bf56d2950e6399427"
+dependencies = [
+ "radroots_core",
+ "radroots_event",
+ "radroots_identity",
+]
+
+[[package]]
+name = "radroots_transport"
+version = "0.1.0-alpha"
+source = "git+https://github.com/radrootslabs/lib?rev=b44119fbac5985be8127ad1bf56d2950e6399427#b44119fbac5985be8127ad1bf56d2950e6399427"
+dependencies = [
+ "radroots_event",
+ "radroots_identity",
+ "radroots_protocol",
+ "sha2",
+]
+
+[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2605,6 +2669,8 @@ dependencies = [
"sha2",
"smallvec",
"thiserror 2.0.18",
+ "tokio",
+ "tokio-stream",
"tracing",
"url",
]
@@ -2641,6 +2707,7 @@ dependencies = [
"sqlx-core",
"sqlx-sqlite",
"syn 2.0.117",
+ "tokio",
"url",
]
@@ -2914,6 +2981,17 @@ dependencies = [
]
[[package]]
+name = "tokio-stream"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
name = "tokio-tungstenite"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3377,6 +3455,28 @@ dependencies = [
]
[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -44,6 +44,7 @@ radroots_event = { git = "https://github.com/radrootslabs/lib", rev = "b44119fba
radroots_nostr = { git = "https://github.com/radrootslabs/lib", rev = "b44119fbac5985be8127ad1bf56d2950e6399427", version = "=0.1.0-alpha", features = ["events"] }
radroots_nostr_connect = { git = "https://github.com/radrootslabs/lib", rev = "b44119fbac5985be8127ad1bf56d2950e6399427", version = "=0.1.0-alpha" }
radroots_runtime_paths = { git = "https://github.com/radrootslabs/lib", rev = "b44119fbac5985be8127ad1bf56d2950e6399427", version = "=0.1.0-alpha" }
+radroots_service_sqlite = { git = "https://github.com/radrootslabs/lib", rev = "b44119fbac5985be8127ad1bf56d2950e6399427", version = "=0.1.0-alpha" }
radroots_secrets = { git = "https://github.com/radrootslabs/lib", rev = "b44119fbac5985be8127ad1bf56d2950e6399427", version = "=0.1.0-alpha", features = ["std", "keyring"] }
radroots_signing = { git = "https://github.com/radrootslabs/lib", rev = "b44119fbac5985be8127ad1bf56d2950e6399427", version = "=0.1.0-alpha", features = ["std"] }
serde = { version = "1.0", features = ["derive"] }
diff --git a/radroots.lib.source-lock.v1.toml b/radroots.lib.source-lock.v1.toml
@@ -6,4 +6,4 @@ workspace_catalog_sha256 = "deca0c080deae187ff8186c0708903e42f41ea57f77c5f91581e
version = "0.1.0-alpha"
source_archive_sha256 = "975474804e6358b9228981add0a23181dbdd1afddf5ae12579c82220876bc379"
lockfile = "Cargo.lock"
-lockfile_sha256 = "25d0049df23e822284b368707f08d08ddcfac85c30703d7154baf80b9baadf3e"
+lockfile_sha256 = "d546a6b89f6cf896d410da6b45b48454594cef208ac85f93892289d49eb04035"
diff --git a/src/lib.rs b/src/lib.rs
@@ -25,6 +25,7 @@ mod runtime_context;
pub mod signer;
mod signing_adapter;
pub mod sql;
+mod state_catalog;
pub mod transport;
pub use app::{
@@ -104,4 +105,10 @@ pub use runtime_context::{
MycRuntimeContext, MycRuntimeContextError, MycRuntimeContextErrorKind,
resolve_myc_runtime_context,
};
+pub use state_catalog::{
+ MYC_MIGRATION_CATALOG_SHA256, MYC_STATE_SCHEMA_CATALOG_SHA256, MYC_STATE_SCHEMA_VERSION,
+ MYC_STATE_SCHEMA_VERSION_1_OBJECT_COUNT, MYC_STATE_SCHEMA_VERSION_1_SHA256,
+ MycStateCatalogError, MycStateCatalogErrorKind, myc_migration_catalog, myc_schema_catalog,
+ validate_myc_state_catalogs,
+};
pub use transport::{MycNostrTransport, MycRelayPublishResult, MycTransportSnapshot};
diff --git a/src/state_catalog.rs b/src/state_catalog.rs
@@ -0,0 +1,155 @@
+//! Immutable Myc schema and migration catalog identity.
+
+use core::fmt;
+use std::error::Error;
+
+use radroots_service_sqlite::{
+ MigrationCatalog, SchemaCatalog, SchemaDigest, SchemaVersionCatalog,
+};
+
+/// The clean-slate Myc baseline schema version.
+pub const MYC_STATE_SCHEMA_VERSION: u32 = 1;
+
+/// The shared metadata and migration-ledger objects present at schema v1.
+pub const MYC_STATE_SCHEMA_VERSION_1_OBJECT_COUNT: u32 = 6;
+
+/// SHA-256 identity of the empty schema-v1 migration catalog.
+pub const MYC_MIGRATION_CATALOG_SHA256: [u8; 32] = [
+ 0xec, 0x89, 0xdc, 0x8f, 0x7b, 0x6c, 0x2a, 0x11, 0xb9, 0x67, 0xe3, 0x38, 0x08, 0xe4, 0x03, 0x1e,
+ 0x29, 0xb3, 0x97, 0x0f, 0xfe, 0xe4, 0x95, 0x9b, 0xff, 0x9b, 0xad, 0x35, 0x28, 0x77, 0xee, 0x9b,
+];
+
+/// SHA-256 identity of the exact schema-v1 object snapshot.
+pub const MYC_STATE_SCHEMA_VERSION_1_SHA256: [u8; 32] = [
+ 0x94, 0xdc, 0x66, 0xfb, 0xca, 0x60, 0x16, 0x79, 0x61, 0x5c, 0x05, 0x52, 0x29, 0xdc, 0x0d, 0xb6,
+ 0x11, 0x9f, 0x5b, 0xd9, 0x2b, 0x04, 0x39, 0x0c, 0x67, 0xf6, 0x98, 0xa0, 0x36, 0xfa, 0x78, 0xae,
+];
+
+/// SHA-256 identity of the schema catalog bound to the migration catalog.
+pub const MYC_STATE_SCHEMA_CATALOG_SHA256: [u8; 32] = [
+ 0x23, 0x09, 0x15, 0x3f, 0x3b, 0x49, 0x75, 0x48, 0x87, 0xc5, 0x48, 0xa7, 0x45, 0x9b, 0x3e, 0x09,
+ 0x09, 0x9c, 0x60, 0xf7, 0x14, 0x6b, 0x37, 0x3c, 0x8f, 0x96, 0x70, 0x6c, 0x67, 0x68, 0xd7, 0x91,
+];
+
+/// Stable classes for invalid embedded Myc catalog definitions.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum MycStateCatalogErrorKind {
+ MigrationCatalog,
+ SchemaCatalog,
+ CatalogMismatch,
+}
+
+impl MycStateCatalogErrorKind {
+ /// Returns the stable machine-readable classification.
+ #[must_use]
+ pub const fn code(self) -> &'static str {
+ match self {
+ Self::MigrationCatalog => "migration_catalog_invalid",
+ Self::SchemaCatalog => "schema_catalog_invalid",
+ Self::CatalogMismatch => "state_catalog_mismatch",
+ }
+ }
+}
+
+/// Source-free failure to construct or validate the embedded Myc catalogs.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct MycStateCatalogError {
+ kind: MycStateCatalogErrorKind,
+}
+
+impl MycStateCatalogError {
+ const fn new(kind: MycStateCatalogErrorKind) -> Self {
+ Self { kind }
+ }
+
+ /// Returns the stable failure class.
+ #[must_use]
+ pub const fn kind(self) -> MycStateCatalogErrorKind {
+ self.kind
+ }
+
+ /// Returns the stable machine-readable code.
+ #[must_use]
+ pub const fn code(self) -> &'static str {
+ self.kind.code()
+ }
+}
+
+impl fmt::Display for MycStateCatalogError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(match self.kind {
+ MycStateCatalogErrorKind::MigrationCatalog => {
+ "Myc migration catalog definition is invalid"
+ }
+ MycStateCatalogErrorKind::SchemaCatalog => "Myc schema catalog definition is invalid",
+ MycStateCatalogErrorKind::CatalogMismatch => {
+ "Myc state catalogs do not match the governed identity"
+ }
+ })
+ }
+}
+
+impl fmt::Debug for MycStateCatalogError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("MycStateCatalogError")
+ .field("kind", &self.kind)
+ .finish()
+ }
+}
+
+impl Error for MycStateCatalogError {}
+
+/// Constructs the exact schema-v1 migration catalog.
+pub fn myc_migration_catalog() -> Result<MigrationCatalog, MycStateCatalogError> {
+ let catalog = MigrationCatalog::new([])
+ .map_err(|_| MycStateCatalogError::new(MycStateCatalogErrorKind::MigrationCatalog))?;
+ if catalog.current_version() != MYC_STATE_SCHEMA_VERSION
+ || !catalog.descriptors().is_empty()
+ || catalog.digest().as_bytes() != &MYC_MIGRATION_CATALOG_SHA256
+ {
+ return Err(MycStateCatalogError::new(
+ MycStateCatalogErrorKind::CatalogMismatch,
+ ));
+ }
+ Ok(catalog)
+}
+
+/// Constructs the exact Myc schema catalog bound to the migration catalog.
+pub fn myc_schema_catalog() -> Result<SchemaCatalog, MycStateCatalogError> {
+ let migrations = myc_migration_catalog()?;
+ let version = SchemaVersionCatalog::new(
+ MYC_STATE_SCHEMA_VERSION,
+ [],
+ SchemaDigest::from_bytes(MYC_STATE_SCHEMA_VERSION_1_SHA256),
+ )
+ .map_err(|_| MycStateCatalogError::new(MycStateCatalogErrorKind::SchemaCatalog))?;
+ let catalog = SchemaCatalog::new(&migrations, [version])
+ .map_err(|_| MycStateCatalogError::new(MycStateCatalogErrorKind::SchemaCatalog))?;
+ validate_myc_state_catalogs(&migrations, &catalog)?;
+ Ok(catalog)
+}
+
+/// Independently validates exact catalog versions, counts, and digests.
+pub fn validate_myc_state_catalogs(
+ migrations: &MigrationCatalog,
+ schema: &SchemaCatalog,
+) -> Result<(), MycStateCatalogError> {
+ let versions = schema.versions();
+ let valid = migrations.current_version() == MYC_STATE_SCHEMA_VERSION
+ && migrations.descriptors().is_empty()
+ && migrations.digest().as_bytes() == &MYC_MIGRATION_CATALOG_SHA256
+ && schema.migration_catalog_digest() == migrations.digest()
+ && versions.len() == 1
+ && versions[0].version() == MYC_STATE_SCHEMA_VERSION
+ && versions[0].object_count() == MYC_STATE_SCHEMA_VERSION_1_OBJECT_COUNT
+ && versions[0].digest().as_bytes() == &MYC_STATE_SCHEMA_VERSION_1_SHA256
+ && schema.digest().as_bytes() == &MYC_STATE_SCHEMA_CATALOG_SHA256;
+ if valid {
+ Ok(())
+ } else {
+ Err(MycStateCatalogError::new(
+ MycStateCatalogErrorKind::CatalogMismatch,
+ ))
+ }
+}
diff --git a/tests/build_policy.rs b/tests/build_policy.rs
@@ -34,6 +34,13 @@ fn shared_runtime_paths_is_exactly_pinned_to_the_source_locked_lib() {
}
#[test]
+fn shared_service_sqlite_is_exactly_pinned_to_the_source_locked_lib() {
+ assert!(MANIFEST.contains(
+ "radroots_service_sqlite = { git = \"https://github.com/radrootslabs/lib\", rev = \"b44119fbac5985be8127ad1bf56d2950e6399427\", version = \"=0.1.0-alpha\" }"
+ ));
+}
+
+#[test]
fn release_acceptance_checks_both_feature_profiles() {
assert!(
RELEASE_ACCEPTANCE.contains("cargo check --locked --all-targets --no-default-features\n")
diff --git a/tests/services_hardening_state_catalog.rs b/tests/services_hardening_state_catalog.rs
@@ -0,0 +1,161 @@
+#![forbid(unsafe_code)]
+
+use std::error::Error;
+
+use myc::{
+ MYC_MIGRATION_CATALOG_SHA256, MYC_STATE_SCHEMA_CATALOG_SHA256, MYC_STATE_SCHEMA_VERSION,
+ MYC_STATE_SCHEMA_VERSION_1_OBJECT_COUNT, MYC_STATE_SCHEMA_VERSION_1_SHA256,
+ MycStateCatalogErrorKind, myc_migration_catalog, myc_schema_catalog,
+ validate_myc_state_catalogs,
+};
+use radroots_service_sqlite::{
+ MigrationCatalog, MigrationChecksum, MigrationDescriptor, SchemaCatalog, SchemaObject,
+ SchemaObjectKind, SchemaVersionCatalog,
+};
+
+const CATALOG_SOURCE: &str = include_str!("../src/state_catalog.rs");
+const LIB_SOURCE: &str = include_str!("../src/lib.rs");
+const MANIFEST: &str = include_str!("../Cargo.toml");
+
+#[test]
+fn schema_v1_and_empty_migration_catalog_have_exact_literal_identities() {
+ let migrations = myc_migration_catalog().expect("Myc migration catalog");
+ let schema = myc_schema_catalog().expect("Myc schema catalog");
+
+ assert_eq!(MYC_STATE_SCHEMA_VERSION, 1);
+ assert!(migrations.descriptors().is_empty());
+ assert_eq!(migrations.current_version(), 1);
+ assert_eq!(
+ migrations.digest().as_bytes(),
+ &MYC_MIGRATION_CATALOG_SHA256
+ );
+
+ assert_eq!(schema.versions().len(), 1);
+ let version = schema.versions()[0];
+ assert_eq!(version.version(), 1);
+ assert_eq!(
+ version.object_count(),
+ MYC_STATE_SCHEMA_VERSION_1_OBJECT_COUNT
+ );
+ assert_eq!(version.object_count(), 6);
+ assert_eq!(
+ version.digest().as_bytes(),
+ &MYC_STATE_SCHEMA_VERSION_1_SHA256
+ );
+ assert_eq!(schema.digest().as_bytes(), &MYC_STATE_SCHEMA_CATALOG_SHA256);
+ assert_eq!(schema.migration_catalog_digest(), migrations.digest());
+ validate_myc_state_catalogs(&migrations, &schema).expect("exact catalogs");
+
+ assert_eq!(
+ hex::encode(MYC_MIGRATION_CATALOG_SHA256),
+ "ec89dc8f7b6c2a11b967e33808e4031e29b3970ffee4959bff9bad352877ee9b"
+ );
+ assert_eq!(
+ hex::encode(MYC_STATE_SCHEMA_VERSION_1_SHA256),
+ "94dc66fbca601679615c055229dc0db6119f5bd92b04390c67f698a036fa78ae"
+ );
+ assert_eq!(
+ hex::encode(MYC_STATE_SCHEMA_CATALOG_SHA256),
+ "2309153f3b49754887c548a7459b3e09099c60f7146b373c8f96706c6768d791"
+ );
+}
+
+#[test]
+fn independent_validator_rejects_migration_or_schema_drift() {
+ const SQL: &str = "CREATE TABLE unexpected (value INTEGER NOT NULL) STRICT";
+ let migration =
+ MigrationDescriptor::sql(2, "unexpected_schema", SQL, MigrationChecksum::for_sql(SQL))
+ .expect("valid drift fixture");
+ let migrations = MigrationCatalog::new([migration]).expect("drift migration catalog");
+ let expected_schema = myc_schema_catalog().expect("expected schema");
+ assert_eq!(
+ validate_myc_state_catalogs(&migrations, &expected_schema)
+ .expect_err("migration drift")
+ .kind(),
+ MycStateCatalogErrorKind::CatalogMismatch
+ );
+
+ let empty_migrations = myc_migration_catalog().expect("empty migrations");
+ let object_digest =
+ SchemaObject::computed_digest(SchemaObjectKind::Table, "unexpected", "unexpected", SQL)
+ .expect("object digest");
+ let object = SchemaObject::new(
+ SchemaObjectKind::Table,
+ "unexpected",
+ "unexpected",
+ SQL,
+ object_digest,
+ )
+ .expect("schema object");
+ let snapshot_digest =
+ SchemaVersionCatalog::computed_digest(1, [object.clone()]).expect("snapshot digest");
+ let version = SchemaVersionCatalog::new(1, [object], snapshot_digest).expect("version");
+ let schema = SchemaCatalog::new(&empty_migrations, [version]).expect("drift schema catalog");
+ assert_eq!(
+ validate_myc_state_catalogs(&empty_migrations, &schema)
+ .expect_err("schema drift")
+ .kind(),
+ MycStateCatalogErrorKind::CatalogMismatch
+ );
+}
+
+#[test]
+fn catalog_errors_are_stable_source_free_and_redacted() {
+ let migrations = myc_migration_catalog().expect("migration catalog");
+ let object_digest = SchemaObject::computed_digest(
+ SchemaObjectKind::Table,
+ "secret_table",
+ "secret_table",
+ "secret SQL text",
+ )
+ .expect("object digest");
+ let object = SchemaObject::new(
+ SchemaObjectKind::Table,
+ "secret_table",
+ "secret_table",
+ "secret SQL text",
+ object_digest,
+ )
+ .expect("object");
+ let snapshot =
+ SchemaVersionCatalog::computed_digest(1, [object.clone()]).expect("snapshot digest");
+ let version = SchemaVersionCatalog::new(1, [object], snapshot).expect("version");
+ let schema = SchemaCatalog::new(&migrations, [version]).expect("schema catalog");
+ let error = validate_myc_state_catalogs(&migrations, &schema).expect_err("mismatch");
+
+ assert_eq!(error.kind(), MycStateCatalogErrorKind::CatalogMismatch);
+ assert_eq!(error.code(), "state_catalog_mismatch");
+ assert!(Error::source(&error).is_none());
+ let rendered = format!("{error} {error:?}");
+ assert!(!rendered.contains("secret"));
+ assert!(!rendered.contains(&hex::encode(snapshot.as_bytes())));
+}
+
+#[test]
+fn catalog_source_is_pure_pinned_and_uses_only_the_shared_authority() {
+ assert!(MANIFEST.contains(
+ "radroots_service_sqlite = { git = \"https://github.com/radrootslabs/lib\", rev = \"b44119fbac5985be8127ad1bf56d2950e6399427\", version = \"=0.1.0-alpha\" }"
+ ));
+ assert!(LIB_SOURCE.contains("mod state_catalog;"));
+ assert!(!LIB_SOURCE.contains("pub mod state_catalog;"));
+ assert!(CATALOG_SOURCE.contains("MigrationCatalog::new([])"));
+ assert!(CATALOG_SOURCE.contains("SchemaDigest::from_bytes("));
+ assert!(!CATALOG_SOURCE.contains("computed_digest"));
+ for forbidden in [
+ "sqlx::",
+ "rusqlite",
+ "libsqlite3_sys",
+ "CREATE TABLE",
+ "raw_sql",
+ "std::fs",
+ "std::path",
+ "Connection",
+ "Transaction",
+ "MigrationDescriptor",
+ ] {
+ assert!(
+ !CATALOG_SOURCE.contains(forbidden),
+ "found forbidden catalog authority `{forbidden}`"
+ );
+ }
+}