lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

metadata.rs (49050B)


      1 //! Immutable database identity metadata for one service instance.
      2 
      3 use core::{fmt, num::NonZeroU32};
      4 use std::error::Error;
      5 
      6 use radroots_runtime_paths::{InstanceId, ServiceId};
      7 use radroots_storage::event::SourceGeneration;
      8 
      9 use crate::ServiceSqlitePaths;
     10 
     11 #[cfg(any(target_os = "linux", target_os = "macos"))]
     12 use crate::{ServiceSqliteError, ServiceSqliteErrorKind};
     13 
     14 #[cfg(any(target_os = "linux", target_os = "macos"))]
     15 use sqlx::{Row, SqliteConnection};
     16 
     17 #[cfg(all(test, any(target_os = "linux", target_os = "macos")))]
     18 use sqlx::Connection;
     19 
     20 const MAX_APPLICATION_ID: u32 = i32::MAX as u32;
     21 const MAX_CREATED_AT_UNIX_MS: u64 = i64::MAX as u64;
     22 
     23 const fn valid_creation_time(value: u64) -> bool {
     24     value != 0 && value <= MAX_CREATED_AT_UNIX_MS
     25 }
     26 
     27 /// A validated nonzero SQLite application identifier.
     28 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     29 pub struct ServiceSqliteApplicationId(u32);
     30 
     31 impl ServiceSqliteApplicationId {
     32     /// Validates a caller-owned application identifier for SQLite's signed range.
     33     pub const fn new(value: u32) -> Result<Self, ServiceSqliteMetadataValueError> {
     34         if value == 0 || value > MAX_APPLICATION_ID {
     35             return Err(ServiceSqliteMetadataValueError::InvalidApplicationId);
     36         }
     37         Ok(Self(value))
     38     }
     39 
     40     /// Returns the validated application identifier.
     41     #[must_use]
     42     pub const fn get(self) -> u32 {
     43         self.0
     44     }
     45 }
     46 
     47 /// Invalid caller-supplied database metadata.
     48 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     49 pub enum ServiceSqliteMetadataValueError {
     50     InvalidApplicationId,
     51     InvalidCreationTime,
     52 }
     53 
     54 impl fmt::Display for ServiceSqliteMetadataValueError {
     55     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
     56         formatter.write_str(match self {
     57             Self::InvalidApplicationId => "SQLite application ID is out of range",
     58             Self::InvalidCreationTime => "SQLite creation time is out of range",
     59         })
     60     }
     61 }
     62 
     63 impl Error for ServiceSqliteMetadataValueError {}
     64 
     65 /// Exact immutable identity expected from one service database.
     66 #[derive(Clone, PartialEq, Eq)]
     67 pub struct ServiceDatabaseMetadata {
     68     service: ServiceId,
     69     instance: InstanceId,
     70     source_generation: SourceGeneration,
     71     state_schema_version: NonZeroU32,
     72     created_at_unix_ms: u64,
     73     application_id: ServiceSqliteApplicationId,
     74 }
     75 
     76 /// Exact mount identity and maximum schema version accepted by one service binary.
     77 #[derive(Clone, PartialEq, Eq)]
     78 pub struct ServiceDatabaseIdentity {
     79     service: ServiceId,
     80     instance: InstanceId,
     81     source_generation: SourceGeneration,
     82     supported_state_schema_version: NonZeroU32,
     83     application_id: ServiceSqliteApplicationId,
     84 }
     85 
     86 /// Sealed expectation for opening an existing database without guessing its generation.
     87 #[derive(Clone, PartialEq, Eq)]
     88 pub struct ExistingServiceDatabaseIntent {
     89     service: ServiceId,
     90     instance: InstanceId,
     91     supported_state_schema_version: NonZeroU32,
     92     application_id: ServiceSqliteApplicationId,
     93 }
     94 
     95 impl ServiceDatabaseMetadata {
     96     #[cfg(any(target_os = "linux", target_os = "macos"))]
     97     pub(crate) fn from_verified_backup(
     98         service: ServiceId,
     99         instance: InstanceId,
    100         source_generation: SourceGeneration,
    101         state_schema_version: NonZeroU32,
    102         created_at_unix_ms: u64,
    103         application_id: ServiceSqliteApplicationId,
    104     ) -> Result<Self, ServiceSqliteMetadataValueError> {
    105         if !valid_creation_time(created_at_unix_ms) {
    106             return Err(ServiceSqliteMetadataValueError::InvalidCreationTime);
    107         }
    108         Ok(Self {
    109             service,
    110             instance,
    111             source_generation,
    112             state_schema_version,
    113             created_at_unix_ms,
    114             application_id,
    115         })
    116     }
    117 
    118     /// Constructs metadata bound to the service and instance in canonical paths.
    119     pub fn new(
    120         paths: &ServiceSqlitePaths,
    121         source_generation: SourceGeneration,
    122         state_schema_version: NonZeroU32,
    123         created_at_unix_ms: u64,
    124         application_id: ServiceSqliteApplicationId,
    125     ) -> Result<Self, ServiceSqliteMetadataValueError> {
    126         if !valid_creation_time(created_at_unix_ms) {
    127             return Err(ServiceSqliteMetadataValueError::InvalidCreationTime);
    128         }
    129         Ok(Self {
    130             service: paths.service().clone(),
    131             instance: paths.instance().clone(),
    132             source_generation,
    133             state_schema_version,
    134             created_at_unix_ms,
    135             application_id,
    136         })
    137     }
    138 
    139     /// Returns the bound service identity.
    140     #[must_use]
    141     pub fn service(&self) -> &ServiceId {
    142         &self.service
    143     }
    144 
    145     /// Returns the bound instance identity.
    146     #[must_use]
    147     pub fn instance(&self) -> &InstanceId {
    148         &self.instance
    149     }
    150 
    151     /// Returns the opaque nonzero source generation.
    152     #[must_use]
    153     pub const fn source_generation(&self) -> SourceGeneration {
    154         self.source_generation
    155     }
    156 
    157     /// Returns the expected nonzero state schema version.
    158     #[must_use]
    159     pub const fn state_schema_version(&self) -> NonZeroU32 {
    160         self.state_schema_version
    161     }
    162 
    163     /// Returns the injected positive creation time in Unix milliseconds.
    164     #[must_use]
    165     pub const fn created_at_unix_ms(&self) -> u64 {
    166         self.created_at_unix_ms
    167     }
    168 
    169     /// Returns the caller-owned SQLite application identifier.
    170     #[must_use]
    171     pub const fn application_id(&self) -> ServiceSqliteApplicationId {
    172         self.application_id
    173     }
    174 
    175     /// Returns the reopen identity derived from this initialization record.
    176     #[must_use]
    177     pub fn identity(&self) -> ServiceDatabaseIdentity {
    178         ServiceDatabaseIdentity {
    179             service: self.service.clone(),
    180             instance: self.instance.clone(),
    181             source_generation: self.source_generation,
    182             supported_state_schema_version: self.state_schema_version,
    183             application_id: self.application_id,
    184         }
    185     }
    186 
    187     pub(crate) fn matches_paths(&self, paths: &ServiceSqlitePaths) -> bool {
    188         crate::all_constraints([
    189             self.service == *paths.service(),
    190             self.instance == *paths.instance(),
    191         ])
    192     }
    193 }
    194 
    195 impl ServiceDatabaseIdentity {
    196     /// Constructs a reopen expectation bound to canonical service-instance paths.
    197     #[must_use]
    198     pub fn new(
    199         paths: &ServiceSqlitePaths,
    200         source_generation: SourceGeneration,
    201         supported_state_schema_version: NonZeroU32,
    202         application_id: ServiceSqliteApplicationId,
    203     ) -> Self {
    204         Self {
    205             service: paths.service().clone(),
    206             instance: paths.instance().clone(),
    207             source_generation,
    208             supported_state_schema_version,
    209             application_id,
    210         }
    211     }
    212 
    213     /// Returns the bound service identity.
    214     #[must_use]
    215     pub fn service(&self) -> &ServiceId {
    216         &self.service
    217     }
    218 
    219     /// Returns the bound instance identity.
    220     #[must_use]
    221     pub fn instance(&self) -> &InstanceId {
    222         &self.instance
    223     }
    224 
    225     /// Returns the expected opaque source generation.
    226     #[must_use]
    227     pub const fn source_generation(&self) -> SourceGeneration {
    228         self.source_generation
    229     }
    230 
    231     /// Returns the newest state schema version this binary accepts.
    232     #[must_use]
    233     pub const fn supported_state_schema_version(&self) -> NonZeroU32 {
    234         self.supported_state_schema_version
    235     }
    236 
    237     /// Returns the expected SQLite application identifier.
    238     #[must_use]
    239     pub const fn application_id(&self) -> ServiceSqliteApplicationId {
    240         self.application_id
    241     }
    242 
    243     #[cfg(any(target_os = "linux", target_os = "macos"))]
    244     pub(crate) fn matches_paths(&self, paths: &ServiceSqlitePaths) -> bool {
    245         crate::all_constraints([
    246             self.service == *paths.service(),
    247             self.instance == *paths.instance(),
    248         ])
    249     }
    250 }
    251 
    252 impl ExistingServiceDatabaseIntent {
    253     /// Binds an existing-only open to canonical paths and the binary's fixed contract.
    254     #[must_use]
    255     pub fn new(
    256         paths: &ServiceSqlitePaths,
    257         supported_state_schema_version: NonZeroU32,
    258         application_id: ServiceSqliteApplicationId,
    259     ) -> Self {
    260         Self {
    261             service: paths.service().clone(),
    262             instance: paths.instance().clone(),
    263             supported_state_schema_version,
    264             application_id,
    265         }
    266     }
    267 
    268     /// Returns the bound service identity.
    269     #[must_use]
    270     pub fn service(&self) -> &ServiceId {
    271         &self.service
    272     }
    273 
    274     /// Returns the bound instance identity.
    275     #[must_use]
    276     pub fn instance(&self) -> &InstanceId {
    277         &self.instance
    278     }
    279 
    280     /// Returns the newest state schema version this binary accepts.
    281     #[must_use]
    282     pub const fn supported_state_schema_version(&self) -> NonZeroU32 {
    283         self.supported_state_schema_version
    284     }
    285 
    286     /// Returns the expected SQLite application identifier.
    287     #[must_use]
    288     pub const fn application_id(&self) -> ServiceSqliteApplicationId {
    289         self.application_id
    290     }
    291 
    292     pub(crate) fn matches_paths(&self, paths: &ServiceSqlitePaths) -> bool {
    293         crate::all_constraints([
    294             self.service == *paths.service(),
    295             self.instance == *paths.instance(),
    296         ])
    297     }
    298 
    299     pub(crate) fn identity_for(
    300         &self,
    301         metadata: &ServiceDatabaseMetadata,
    302     ) -> ServiceDatabaseIdentity {
    303         ServiceDatabaseIdentity {
    304             service: self.service.clone(),
    305             instance: self.instance.clone(),
    306             source_generation: metadata.source_generation,
    307             supported_state_schema_version: self.supported_state_schema_version,
    308             application_id: self.application_id,
    309         }
    310     }
    311 }
    312 
    313 impl fmt::Debug for ServiceDatabaseIdentity {
    314     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    315         formatter
    316             .debug_struct("ServiceDatabaseIdentity")
    317             .field("service", &"[redacted]")
    318             .field("instance", &"[redacted]")
    319             .field("source_generation", &"[redacted]")
    320             .field(
    321                 "supported_state_schema_version",
    322                 &self.supported_state_schema_version,
    323             )
    324             .field("application_id", &self.application_id)
    325             .finish()
    326     }
    327 }
    328 
    329 impl fmt::Debug for ExistingServiceDatabaseIntent {
    330     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    331         formatter
    332             .debug_struct("ExistingServiceDatabaseIntent")
    333             .field("service", &"[redacted]")
    334             .field("instance", &"[redacted]")
    335             .field(
    336                 "supported_state_schema_version",
    337                 &self.supported_state_schema_version,
    338             )
    339             .field("application_id", &self.application_id)
    340             .finish()
    341     }
    342 }
    343 
    344 impl fmt::Debug for ServiceDatabaseMetadata {
    345     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    346         formatter
    347             .debug_struct("ServiceDatabaseMetadata")
    348             .field("service", &"[redacted]")
    349             .field("instance", &"[redacted]")
    350             .field("source_generation", &"[redacted]")
    351             .field("state_schema_version", &self.state_schema_version)
    352             .field("created_at_unix_ms", &self.created_at_unix_ms)
    353             .field("application_id", &self.application_id)
    354             .finish()
    355     }
    356 }
    357 
    358 #[cfg(any(target_os = "linux", target_os = "macos"))]
    359 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    360 enum MetadataFailureKind {
    361     AlreadyPresent,
    362     Missing,
    363     Corrupt,
    364     Mismatch,
    365     Storage,
    366 }
    367 
    368 #[cfg(any(target_os = "linux", target_os = "macos"))]
    369 #[derive(Debug)]
    370 struct MetadataFailure(MetadataFailureKind);
    371 
    372 #[cfg(any(target_os = "linux", target_os = "macos"))]
    373 impl fmt::Display for MetadataFailure {
    374     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    375         formatter.write_str(match self.0 {
    376             MetadataFailureKind::AlreadyPresent => "SQLite metadata already exists",
    377             MetadataFailureKind::Missing => "SQLite metadata is missing",
    378             MetadataFailureKind::Corrupt => "SQLite metadata is corrupt",
    379             MetadataFailureKind::Mismatch => "SQLite metadata identity does not match",
    380             MetadataFailureKind::Storage => "SQLite metadata could not be accessed",
    381         })
    382     }
    383 }
    384 
    385 #[cfg(any(target_os = "linux", target_os = "macos"))]
    386 impl Error for MetadataFailure {}
    387 
    388 #[cfg(any(target_os = "linux", target_os = "macos"))]
    389 fn metadata_error(kind: MetadataFailureKind) -> ServiceSqliteError {
    390     ServiceSqliteError::with_source(ServiceSqliteErrorKind::Metadata, MetadataFailure(kind))
    391 }
    392 
    393 #[cfg(any(target_os = "linux", target_os = "macos"))]
    394 fn require_metadata_condition(
    395     condition: bool,
    396     kind: MetadataFailureKind,
    397 ) -> Result<(), ServiceSqliteError> {
    398     condition.then_some(()).ok_or_else(|| metadata_error(kind))
    399 }
    400 
    401 #[cfg(any(target_os = "linux", target_os = "macos"))]
    402 #[derive(Debug)]
    403 struct MigrationLedgerInitializationFailure;
    404 
    405 #[cfg(any(target_os = "linux", target_os = "macos"))]
    406 impl fmt::Display for MigrationLedgerInitializationFailure {
    407     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    408         formatter.write_str("SQLite migration ledger could not be initialized")
    409     }
    410 }
    411 
    412 #[cfg(any(target_os = "linux", target_os = "macos"))]
    413 impl Error for MigrationLedgerInitializationFailure {}
    414 
    415 #[cfg(all(test, any(target_os = "linux", target_os = "macos")))]
    416 pub(crate) async fn write_database_metadata(
    417     connection: &mut SqliteConnection,
    418     expected: &ServiceDatabaseMetadata,
    419     schema_catalog: &crate::SchemaCatalog,
    420 ) -> Result<(), ServiceSqliteError> {
    421     let mut transaction = connection
    422         .begin()
    423         .await
    424         .map_err(|_| metadata_error(MetadataFailureKind::Storage))?;
    425     write_database_metadata_in_transaction(&mut transaction, expected, schema_catalog).await?;
    426     transaction
    427         .commit()
    428         .await
    429         .map_err(|_| metadata_error(MetadataFailureKind::Storage))?;
    430 
    431     let actual = read_database_metadata(connection).await?;
    432     require_metadata_condition(actual == *expected, MetadataFailureKind::Mismatch)?;
    433     Ok(())
    434 }
    435 
    436 #[cfg(any(target_os = "linux", target_os = "macos"))]
    437 pub(crate) async fn write_database_metadata_in_transaction(
    438     connection: &mut SqliteConnection,
    439     expected: &ServiceDatabaseMetadata,
    440     schema_catalog: &crate::SchemaCatalog,
    441 ) -> Result<(), ServiceSqliteError> {
    442     if expected.state_schema_version().get() != 1 {
    443         return Err(metadata_error(MetadataFailureKind::Mismatch));
    444     }
    445     if read_application_id(connection).await? != 0 {
    446         return Err(metadata_error(MetadataFailureKind::AlreadyPresent));
    447     }
    448 
    449     for statement in crate::integrity::catalog::METADATA_SCHEMA_SQL {
    450         sqlx::query(statement)
    451             .execute(&mut *connection)
    452             .await
    453             .map_err(|_| metadata_error(MetadataFailureKind::AlreadyPresent))?;
    454     }
    455     for statement in crate::integrity::catalog::MIGRATION_LEDGER_SCHEMA_SQL {
    456         sqlx::query(statement)
    457             .execute(&mut *connection)
    458             .await
    459             .map_err(|_source| {
    460                 ServiceSqliteError::with_source(
    461                     ServiceSqliteErrorKind::Migration,
    462                     MigrationLedgerInitializationFailure,
    463                 )
    464             })?;
    465     }
    466     sqlx::query(
    467         "INSERT INTO radroots_service_metadata (
    468             singleton, service_id, instance_id, source_generation,
    469             state_schema_version, created_at_unix_ms
    470          ) VALUES (1, ?, ?, ?, ?, ?)",
    471     )
    472     .bind(expected.service().as_str())
    473     .bind(expected.instance().as_str())
    474     .bind(expected.source_generation().as_bytes().as_slice())
    475     .bind(i64::from(expected.state_schema_version().get()))
    476     .bind(
    477         i64::try_from(expected.created_at_unix_ms())
    478             .map_err(|_| metadata_error(MetadataFailureKind::Corrupt))?,
    479     )
    480     .execute(&mut *connection)
    481     .await
    482     .map_err(|_| metadata_error(MetadataFailureKind::Storage))?;
    483     let set_application_id = format!(
    484         "PRAGMA application_id = {}",
    485         expected.application_id().get()
    486     );
    487     // The only dynamic token is a validated decimal u31 value.
    488     sqlx::query(sqlx::AssertSqlSafe(set_application_id.as_str()))
    489         .execute(&mut *connection)
    490         .await
    491         .map_err(|_| metadata_error(MetadataFailureKind::Storage))?;
    492     crate::integrity::verify_schema_catalog(
    493         &mut *connection,
    494         schema_catalog,
    495         expected.state_schema_version().get(),
    496     )
    497     .await?;
    498     let actual = read_database_metadata(connection).await?;
    499     require_metadata_condition(actual == *expected, MetadataFailureKind::Mismatch)?;
    500     Ok(())
    501 }
    502 
    503 #[cfg(any(target_os = "linux", target_os = "macos"))]
    504 pub(crate) async fn verify_database_metadata(
    505     connection: &mut SqliteConnection,
    506     expected: &ServiceDatabaseIdentity,
    507 ) -> Result<ServiceDatabaseMetadata, ServiceSqliteError> {
    508     let actual = read_database_metadata(connection).await?;
    509     require_metadata_condition(
    510         crate::all_constraints([
    511             actual.service == expected.service,
    512             actual.instance == expected.instance,
    513             actual.source_generation == expected.source_generation,
    514             actual.application_id == expected.application_id,
    515             actual.state_schema_version <= expected.supported_state_schema_version,
    516         ]),
    517         MetadataFailureKind::Mismatch,
    518     )?;
    519     Ok(actual)
    520 }
    521 
    522 #[cfg(any(target_os = "linux", target_os = "macos"))]
    523 pub(crate) async fn verify_existing_database_intent(
    524     connection: &mut SqliteConnection,
    525     intent: &ExistingServiceDatabaseIntent,
    526 ) -> Result<ServiceDatabaseMetadata, ServiceSqliteError> {
    527     let actual = read_database_metadata(connection).await?;
    528     require_metadata_condition(
    529         crate::all_constraints([
    530             actual.service == intent.service,
    531             actual.instance == intent.instance,
    532             actual.application_id == intent.application_id,
    533             actual.state_schema_version <= intent.supported_state_schema_version,
    534         ]),
    535         MetadataFailureKind::Mismatch,
    536     )?;
    537     Ok(actual)
    538 }
    539 
    540 #[cfg(any(target_os = "linux", target_os = "macos"))]
    541 async fn read_database_metadata(
    542     connection: &mut SqliteConnection,
    543 ) -> Result<ServiceDatabaseMetadata, ServiceSqliteError> {
    544     let application_id = read_application_id(connection).await?;
    545     let application_id = ServiceSqliteApplicationId::new(
    546         u32::try_from(application_id).map_err(|_| metadata_error(MetadataFailureKind::Corrupt))?,
    547     )
    548     .map_err(|_| metadata_error(MetadataFailureKind::Corrupt))?;
    549     let rows = sqlx::query(
    550         "SELECT
    551             singleton,
    552             state_schema_version, created_at_unix_ms,
    553             typeof(singleton) = 'integer' AS singleton_type_ok,
    554             typeof(service_id) = 'text' AS service_id_type_ok,
    555             length(CAST(service_id AS BLOB)) AS service_id_length,
    556             substr(CAST(service_id AS BLOB), 1, 129) AS service_id_prefix,
    557             typeof(instance_id) = 'text' AS instance_id_type_ok,
    558             length(CAST(instance_id AS BLOB)) AS instance_id_length,
    559             substr(CAST(instance_id AS BLOB), 1, 129) AS instance_id_prefix,
    560             typeof(source_generation) = 'blob' AS source_generation_type_ok,
    561             length(source_generation) AS source_generation_length,
    562             substr(source_generation, 1, 33) AS source_generation_prefix,
    563             typeof(state_schema_version) = 'integer' AS state_schema_version_type_ok,
    564             typeof(created_at_unix_ms) = 'integer' AS created_at_unix_ms_type_ok
    565          FROM radroots_service_metadata
    566          LIMIT 2",
    567     )
    568     .fetch_all(&mut *connection)
    569     .await
    570     .map_err(|_| metadata_error(MetadataFailureKind::Missing))?;
    571     let [row] = rows.as_slice() else {
    572         return Err(metadata_error(if rows.is_empty() {
    573             MetadataFailureKind::Missing
    574         } else {
    575             MetadataFailureKind::Corrupt
    576         }));
    577     };
    578     for column in [
    579         "singleton_type_ok",
    580         "state_schema_version_type_ok",
    581         "created_at_unix_ms_type_ok",
    582     ] {
    583         require_metadata_condition(
    584             row.try_get::<i64, _>(column)
    585                 .map_err(|_| metadata_error(MetadataFailureKind::Corrupt))?
    586                 == 1,
    587             MetadataFailureKind::Corrupt,
    588         )?;
    589     }
    590     require_metadata_condition(
    591         row.try_get::<i64, _>("singleton")
    592             .map_err(|_| metadata_error(MetadataFailureKind::Corrupt))?
    593             == 1,
    594         MetadataFailureKind::Corrupt,
    595     )?;
    596     let service = crate::persisted_value::bounded_utf8(
    597         row,
    598         "service_id_type_ok",
    599         "service_id_length",
    600         "service_id_prefix",
    601         1,
    602         crate::persisted_value::MAX_IDENTIFIER_UTF8_BYTES,
    603     )
    604     .and_then(|value| ServiceId::new(value).ok())
    605     .ok_or_else(|| metadata_error(MetadataFailureKind::Corrupt))?;
    606     let instance = crate::persisted_value::bounded_utf8(
    607         row,
    608         "instance_id_type_ok",
    609         "instance_id_length",
    610         "instance_id_prefix",
    611         1,
    612         crate::persisted_value::MAX_IDENTIFIER_UTF8_BYTES,
    613     )
    614     .and_then(|value| InstanceId::new(value).ok())
    615     .ok_or_else(|| metadata_error(MetadataFailureKind::Corrupt))?;
    616     let source_generation = crate::persisted_value::bounded_bytes(
    617         row,
    618         "source_generation_type_ok",
    619         "source_generation_length",
    620         "source_generation_prefix",
    621         32,
    622         32,
    623     )
    624     .and_then(|value| <[u8; 32]>::try_from(value).ok())
    625     .and_then(|value| SourceGeneration::new(value).ok())
    626     .ok_or_else(|| metadata_error(MetadataFailureKind::Corrupt))?;
    627     let state_schema_version = NonZeroU32::new(
    628         u32::try_from(
    629             row.try_get::<i64, _>("state_schema_version")
    630                 .map_err(|_| metadata_error(MetadataFailureKind::Corrupt))?,
    631         )
    632         .map_err(|_| metadata_error(MetadataFailureKind::Corrupt))?,
    633     )
    634     .ok_or_else(|| metadata_error(MetadataFailureKind::Corrupt))?;
    635     let created_at_unix_ms = u64::try_from(
    636         row.try_get::<i64, _>("created_at_unix_ms")
    637             .map_err(|_| metadata_error(MetadataFailureKind::Corrupt))?,
    638     )
    639     .map_err(|_| metadata_error(MetadataFailureKind::Corrupt))?;
    640     if created_at_unix_ms == 0 {
    641         return Err(metadata_error(MetadataFailureKind::Corrupt));
    642     }
    643 
    644     Ok(ServiceDatabaseMetadata {
    645         service,
    646         instance,
    647         source_generation,
    648         state_schema_version,
    649         created_at_unix_ms,
    650         application_id,
    651     })
    652 }
    653 
    654 #[cfg(any(target_os = "linux", target_os = "macos"))]
    655 async fn read_application_id(connection: &mut SqliteConnection) -> Result<i64, ServiceSqliteError> {
    656     sqlx::query_scalar::<_, i64>("PRAGMA application_id")
    657         .fetch_one(connection)
    658         .await
    659         .map_err(|_| metadata_error(MetadataFailureKind::Storage))
    660 }
    661 
    662 #[cfg(test)]
    663 mod tests {
    664     use std::path::PathBuf;
    665 
    666     use radroots_runtime_paths::{
    667         RadrootsHostEnvironment, RadrootsPathProfile, RadrootsPathResolver, RadrootsPlatform,
    668         RuntimeContext, RuntimeContextBootstrap, RuntimeContextSource,
    669     };
    670 
    671     use super::*;
    672 
    673     fn sqlite_paths(service: &str, instance: &str) -> ServiceSqlitePaths {
    674         let context = RuntimeContext::resolve(
    675             &RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()),
    676             RuntimeContextBootstrap::new(
    677                 RadrootsPathProfile::RepoLocal,
    678                 Some(PathBuf::from("/isolated/service-metadata")),
    679                 RuntimeContextSource::BootstrapCli,
    680                 RuntimeContextSource::BootstrapCli,
    681             )
    682             .expect("bootstrap"),
    683             ServiceId::new(service).expect("service"),
    684             InstanceId::new(instance).expect("instance"),
    685         )
    686         .expect("runtime context");
    687         ServiceSqlitePaths::from_runtime_context(&context).expect("SQLite paths")
    688     }
    689 
    690     #[cfg(any(target_os = "linux", target_os = "macos"))]
    691     fn metadata(
    692         paths: &ServiceSqlitePaths,
    693         generation_byte: u8,
    694         schema_version: u32,
    695         creation_time: u64,
    696         application_id: u32,
    697     ) -> ServiceDatabaseMetadata {
    698         ServiceDatabaseMetadata::new(
    699             paths,
    700             SourceGeneration::new([generation_byte; 32]).expect("source generation"),
    701             NonZeroU32::new(schema_version).expect("schema version"),
    702             creation_time,
    703             ServiceSqliteApplicationId::new(application_id).expect("application ID"),
    704         )
    705         .expect("database metadata")
    706     }
    707 
    708     #[cfg(any(target_os = "linux", target_os = "macos"))]
    709     async fn memory_connection() -> SqliteConnection {
    710         SqliteConnection::connect("sqlite::memory:")
    711             .await
    712             .expect("memory SQLite")
    713     }
    714 
    715     #[cfg(any(target_os = "linux", target_os = "macos"))]
    716     fn base_schema_catalog() -> crate::SchemaCatalog {
    717         let migrations = crate::MigrationCatalog::new([]).expect("empty migration catalog");
    718         let digest = crate::SchemaVersionCatalog::computed_digest(1, [])
    719             .expect("base schema snapshot digest");
    720         let version =
    721             crate::SchemaVersionCatalog::new(1, [], digest).expect("base schema version catalog");
    722         crate::SchemaCatalog::new(&migrations, [version]).expect("base schema catalog")
    723     }
    724 
    725     #[cfg(any(target_os = "linux", target_os = "macos"))]
    726     #[test]
    727     fn metadata_failure_inventory_preserves_kind_and_trusted_source() {
    728         for kind in [
    729             MetadataFailureKind::AlreadyPresent,
    730             MetadataFailureKind::Missing,
    731             MetadataFailureKind::Corrupt,
    732             MetadataFailureKind::Mismatch,
    733             MetadataFailureKind::Storage,
    734         ] {
    735             assert!(require_metadata_condition(true, kind).is_ok());
    736             let error = require_metadata_condition(false, kind).expect_err("false condition");
    737             assert_eq!(error.kind(), ServiceSqliteErrorKind::Metadata);
    738             assert!(error.source().is_some());
    739             assert!(!error.to_string().contains('/'));
    740             assert!(format!("{error:?}").contains("Metadata"));
    741         }
    742     }
    743 
    744     #[test]
    745     fn application_id_and_creation_time_bounds_are_exact() {
    746         assert!(valid_creation_time(1));
    747         assert!(valid_creation_time(MAX_CREATED_AT_UNIX_MS));
    748         assert!(!valid_creation_time(0));
    749         assert!(!valid_creation_time(MAX_CREATED_AT_UNIX_MS + 1));
    750         assert_eq!(
    751             ServiceSqliteApplicationId::new(0),
    752             Err(ServiceSqliteMetadataValueError::InvalidApplicationId)
    753         );
    754         assert_eq!(
    755             ServiceSqliteApplicationId::new(MAX_APPLICATION_ID + 1),
    756             Err(ServiceSqliteMetadataValueError::InvalidApplicationId)
    757         );
    758         assert_eq!(
    759             ServiceSqliteApplicationId::new(MAX_APPLICATION_ID)
    760                 .expect("maximum application ID")
    761                 .get(),
    762             MAX_APPLICATION_ID
    763         );
    764 
    765         let paths = sqlite_paths("myc", "primary");
    766         let generation = SourceGeneration::new([7; 32]).expect("source generation");
    767         let schema = NonZeroU32::new(1).expect("schema version");
    768         let application = ServiceSqliteApplicationId::new(1).expect("application ID");
    769         assert_eq!(
    770             ServiceDatabaseMetadata::new(&paths, generation, schema, 0, application),
    771             Err(ServiceSqliteMetadataValueError::InvalidCreationTime)
    772         );
    773         assert_eq!(
    774             ServiceDatabaseMetadata::new(
    775                 &paths,
    776                 generation,
    777                 schema,
    778                 MAX_CREATED_AT_UNIX_MS + 1,
    779                 application,
    780             ),
    781             Err(ServiceSqliteMetadataValueError::InvalidCreationTime)
    782         );
    783         assert!(
    784             ServiceDatabaseMetadata::new(
    785                 &paths,
    786                 generation,
    787                 schema,
    788                 MAX_CREATED_AT_UNIX_MS,
    789                 application,
    790             )
    791             .is_ok()
    792         );
    793         assert!(SourceGeneration::new([0; 32]).is_err());
    794         assert!(NonZeroU32::new(0).is_none());
    795     }
    796 
    797     #[cfg(any(target_os = "linux", target_os = "macos"))]
    798     #[tokio::test(flavor = "current_thread")]
    799     async fn fresh_metadata_write_read_and_immutability_are_exact() {
    800         let paths = sqlite_paths("myc", "primary");
    801         let expected = metadata(&paths, 7, 1, 1_700_000_000_000, 0x5244_5351);
    802         let mut connection = memory_connection().await;
    803 
    804         write_database_metadata(&mut connection, &expected, &base_schema_catalog())
    805             .await
    806             .expect("write metadata");
    807         verify_database_metadata(&mut connection, &expected.identity())
    808             .await
    809             .expect("verify metadata");
    810         let actual = read_database_metadata(&mut connection)
    811             .await
    812             .expect("read metadata");
    813         assert_eq!(actual, expected);
    814         assert_eq!(actual.service().as_str(), "myc");
    815         assert_eq!(actual.instance().as_str(), "primary");
    816         assert_eq!(actual.source_generation().as_bytes(), &[7; 32]);
    817         assert_eq!(actual.state_schema_version().get(), 1);
    818         assert_eq!(actual.created_at_unix_ms(), 1_700_000_000_000);
    819         assert_eq!(actual.application_id().get(), 0x5244_5351);
    820         assert_eq!(
    821             read_application_id(&mut connection).await.unwrap(),
    822             0x5244_5351
    823         );
    824         assert_eq!(
    825             sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM schema_migrations")
    826                 .fetch_one(&mut connection)
    827                 .await
    828                 .unwrap(),
    829             0
    830         );
    831 
    832         sqlx::query(
    833             "UPDATE radroots_service_metadata SET state_schema_version = 2 WHERE singleton = 1",
    834         )
    835         .execute(&mut connection)
    836         .await
    837         .expect("monotonic schema advance");
    838         assert_eq!(
    839             read_database_metadata(&mut connection)
    840                 .await
    841                 .expect("advanced metadata")
    842                 .state_schema_version()
    843                 .get(),
    844             2
    845         );
    846         for statement in [
    847             "UPDATE radroots_service_metadata SET service_id = 'rhi' WHERE singleton = 1",
    848             "UPDATE radroots_service_metadata SET state_schema_version = 2 WHERE singleton = 1",
    849             "UPDATE radroots_service_metadata SET state_schema_version = 1 WHERE singleton = 1",
    850             "DELETE FROM radroots_service_metadata WHERE singleton = 1",
    851             "INSERT INTO radroots_service_metadata VALUES (2, 'rhi', 'default', zeroblob(32), 1, 1)",
    852         ] {
    853             assert!(
    854                 sqlx::query(statement)
    855                     .execute(&mut connection)
    856                     .await
    857                     .is_err(),
    858                 "immutable metadata accepted `{statement}`"
    859             );
    860         }
    861         assert_eq!(
    862             write_database_metadata(&mut connection, &expected, &base_schema_catalog())
    863                 .await
    864                 .expect_err("second write must fail")
    865                 .kind(),
    866             ServiceSqliteErrorKind::Metadata
    867         );
    868 
    869         let debug = format!("{actual:?}");
    870         for sensitive in ["myc", "primary", "07070707"] {
    871             assert!(!debug.contains(sensitive));
    872         }
    873         assert!(debug.contains("source_generation: \"[redacted]\""));
    874     }
    875 
    876     #[cfg(any(target_os = "linux", target_os = "macos"))]
    877     #[tokio::test(flavor = "current_thread")]
    878     async fn existing_intent_discovers_generation_and_binds_every_trusted_dimension() {
    879         let paths = sqlite_paths("myc", "primary");
    880         let expected = metadata(&paths, 7, 1, 1_700_000_000_000, 0x5244_5351);
    881         let mut connection = memory_connection().await;
    882         write_database_metadata(&mut connection, &expected, &base_schema_catalog())
    883             .await
    884             .expect("write metadata");
    885 
    886         let intent = ExistingServiceDatabaseIntent::new(
    887             &paths,
    888             NonZeroU32::new(2).expect("schema ceiling"),
    889             expected.application_id(),
    890         );
    891         let actual = verify_existing_database_intent(&mut connection, &intent)
    892             .await
    893             .expect("discover metadata");
    894         assert_eq!(actual, expected);
    895         assert_eq!(actual.source_generation().as_bytes(), &[7; 32]);
    896         assert_eq!(
    897             intent.identity_for(&actual).source_generation(),
    898             actual.source_generation()
    899         );
    900 
    901         let debug = format!("{intent:?}");
    902         assert!(debug.contains("ExistingServiceDatabaseIntent"));
    903         assert!(!debug.contains("myc"));
    904         assert!(!debug.contains("primary"));
    905         assert!(!debug.contains("07070707"));
    906 
    907         let other_paths = sqlite_paths("rhi", "primary");
    908         let wrong_service = ExistingServiceDatabaseIntent::new(
    909             &other_paths,
    910             intent.supported_state_schema_version(),
    911             intent.application_id(),
    912         );
    913         let other_instance_paths = sqlite_paths("myc", "secondary");
    914         let wrong_instance = ExistingServiceDatabaseIntent::new(
    915             &other_instance_paths,
    916             intent.supported_state_schema_version(),
    917             intent.application_id(),
    918         );
    919         let wrong_application = ExistingServiceDatabaseIntent::new(
    920             &paths,
    921             intent.supported_state_schema_version(),
    922             ServiceSqliteApplicationId::new(7).expect("other application"),
    923         );
    924         for rejected in [&wrong_service, &wrong_instance, &wrong_application] {
    925             assert_eq!(
    926                 verify_existing_database_intent(&mut connection, rejected)
    927                     .await
    928                     .expect_err("intent mismatch")
    929                     .kind(),
    930                 ServiceSqliteErrorKind::Metadata
    931             );
    932         }
    933 
    934         sqlx::query(
    935             "UPDATE radroots_service_metadata SET state_schema_version = 2 WHERE singleton = 1",
    936         )
    937         .execute(&mut connection)
    938         .await
    939         .expect("advance stored schema");
    940         let older_binary = ExistingServiceDatabaseIntent::new(
    941             &paths,
    942             NonZeroU32::new(1).expect("older ceiling"),
    943             expected.application_id(),
    944         );
    945         assert_eq!(
    946             verify_existing_database_intent(&mut connection, &older_binary)
    947                 .await
    948                 .expect_err("newer stored schema")
    949                 .kind(),
    950             ServiceSqliteErrorKind::Metadata
    951         );
    952     }
    953 
    954     #[cfg(any(target_os = "linux", target_os = "macos"))]
    955     #[tokio::test(flavor = "current_thread")]
    956     async fn schema_mismatch_rolls_back_shared_objects_metadata_and_application_id() {
    957         let paths = sqlite_paths("myc", "primary");
    958         let expected = metadata(&paths, 7, 1, 1_700_000_000_000, 0x5244_5351);
    959         let mut connection = memory_connection().await;
    960         sqlx::query("CREATE TABLE unlisted_service_object (id INTEGER PRIMARY KEY)")
    961             .execute(&mut connection)
    962             .await
    963             .expect("pre-existing service object");
    964 
    965         let error = write_database_metadata(&mut connection, &expected, &base_schema_catalog())
    966             .await
    967             .expect_err("schema mismatch must roll back initialization transaction");
    968         assert_eq!(error.kind(), ServiceSqliteErrorKind::Integrity);
    969         assert_eq!(read_application_id(&mut connection).await.unwrap(), 0);
    970         assert_eq!(
    971             sqlx::query_scalar::<_, i64>(
    972                 "SELECT COUNT(*) FROM sqlite_schema
    973                  WHERE name IN (
    974                     'radroots_service_metadata',
    975                     'radroots_service_metadata_guard_update',
    976                     'radroots_service_metadata_no_delete',
    977                     'schema_migrations',
    978                     'schema_migrations_no_update',
    979                     'schema_migrations_no_delete'
    980                  )",
    981             )
    982             .fetch_one(&mut connection)
    983             .await
    984             .expect("shared schema rollback evidence"),
    985             0
    986         );
    987         assert_eq!(
    988             sqlx::query_scalar::<_, i64>(
    989                 "SELECT COUNT(*) FROM sqlite_schema
    990                  WHERE type = 'table' AND name = 'unlisted_service_object'",
    991             )
    992             .fetch_one(&mut connection)
    993             .await
    994             .expect("pre-existing object evidence"),
    995             1
    996         );
    997     }
    998 
    999     #[cfg(any(target_os = "linux", target_os = "macos"))]
   1000     #[tokio::test(flavor = "current_thread")]
   1001     async fn every_identity_dimension_must_match() {
   1002         let paths = sqlite_paths("myc", "primary");
   1003         let expected = metadata(&paths, 7, 1, 1_700_000_000_000, 0x5244_5351);
   1004         let mut connection = memory_connection().await;
   1005         write_database_metadata(&mut connection, &expected, &base_schema_catalog())
   1006             .await
   1007             .expect("write metadata");
   1008 
   1009         let alternatives = [
   1010             ServiceDatabaseIdentity::new(
   1011                 &sqlite_paths("rhi", "primary"),
   1012                 SourceGeneration::new([7; 32]).unwrap(),
   1013                 NonZeroU32::new(1).unwrap(),
   1014                 ServiceSqliteApplicationId::new(0x5244_5351).unwrap(),
   1015             ),
   1016             ServiceDatabaseIdentity::new(
   1017                 &sqlite_paths("myc", "secondary"),
   1018                 SourceGeneration::new([7; 32]).unwrap(),
   1019                 NonZeroU32::new(1).unwrap(),
   1020                 ServiceSqliteApplicationId::new(0x5244_5351).unwrap(),
   1021             ),
   1022             ServiceDatabaseIdentity::new(
   1023                 &paths,
   1024                 SourceGeneration::new([8; 32]).unwrap(),
   1025                 NonZeroU32::new(1).unwrap(),
   1026                 ServiceSqliteApplicationId::new(0x5244_5351).unwrap(),
   1027             ),
   1028             ServiceDatabaseIdentity::new(
   1029                 &paths,
   1030                 SourceGeneration::new([7; 32]).unwrap(),
   1031                 NonZeroU32::new(1).unwrap(),
   1032                 ServiceSqliteApplicationId::new(0x5244_5352).unwrap(),
   1033             ),
   1034         ];
   1035         for alternative in alternatives {
   1036             assert_eq!(
   1037                 verify_database_metadata(&mut connection, &alternative)
   1038                     .await
   1039                     .expect_err("identity mismatch")
   1040                     .kind(),
   1041                 ServiceSqliteErrorKind::Metadata
   1042             );
   1043         }
   1044 
   1045         let newer_binary = ServiceDatabaseIdentity::new(
   1046             &paths,
   1047             expected.source_generation(),
   1048             NonZeroU32::new(2).unwrap(),
   1049             expected.application_id(),
   1050         );
   1051         let stored = verify_database_metadata(&mut connection, &newer_binary)
   1052             .await
   1053             .expect("older schema is migration eligible");
   1054         assert_eq!(stored.created_at_unix_ms(), 1_700_000_000_000);
   1055         assert_eq!(stored.state_schema_version().get(), 1);
   1056 
   1057         let mut newer_state = memory_connection().await;
   1058         let version_two = metadata(&paths, 7, 2, 1_700_000_000_001, 0x5244_5351);
   1059         assert_eq!(
   1060             write_database_metadata(&mut newer_state, &version_two, &base_schema_catalog())
   1061                 .await
   1062                 .expect_err("fresh metadata must start at v1")
   1063                 .kind(),
   1064             ServiceSqliteErrorKind::Metadata
   1065         );
   1066         let version_one = metadata(&paths, 7, 1, 1_700_000_000_001, 0x5244_5351);
   1067         write_database_metadata(&mut newer_state, &version_one, &base_schema_catalog())
   1068             .await
   1069             .expect("write v1 metadata");
   1070         sqlx::query(
   1071             "UPDATE radroots_service_metadata SET state_schema_version = 2 WHERE singleton = 1",
   1072         )
   1073         .execute(&mut newer_state)
   1074         .await
   1075         .expect("advance newer state");
   1076         assert_eq!(
   1077             verify_database_metadata(&mut newer_state, &expected.identity())
   1078                 .await
   1079                 .expect_err("newer state must fail closed")
   1080                 .kind(),
   1081             ServiceSqliteErrorKind::Metadata
   1082         );
   1083     }
   1084 
   1085     #[cfg(any(target_os = "linux", target_os = "macos"))]
   1086     #[tokio::test(flavor = "current_thread")]
   1087     async fn missing_duplicate_and_corrupt_metadata_fail_closed() {
   1088         const PERMISSIVE_TABLE: &str = "CREATE TABLE radroots_service_metadata (
   1089             singleton, service_id, instance_id, source_generation,
   1090             state_schema_version, created_at_unix_ms
   1091         )";
   1092         let paths = sqlite_paths("myc", "primary");
   1093         let expected = metadata(&paths, 7, 1, 1_700_000_000_000, 0x5244_5351);
   1094 
   1095         let mut missing_table = memory_connection().await;
   1096         assert_eq!(
   1097             verify_database_metadata(&mut missing_table, &expected.identity())
   1098                 .await
   1099                 .expect_err("missing table")
   1100                 .kind(),
   1101             ServiceSqliteErrorKind::Metadata
   1102         );
   1103 
   1104         let corrupt_rows = [
   1105             "",
   1106             "INSERT INTO radroots_service_metadata VALUES
   1107                 (1, 'myc', 'primary', randomblob(32), 1, 1700000000000),
   1108                 (2, 'myc', 'primary', randomblob(32), 1, 1700000000000)",
   1109             "INSERT INTO radroots_service_metadata VALUES
   1110                 (1, NULL, 'primary', randomblob(32), 1, 1700000000000)",
   1111             "INSERT INTO radroots_service_metadata VALUES
   1112                 (1, 'Myc', 'primary', randomblob(32), 1, 1700000000000)",
   1113             "INSERT INTO radroots_service_metadata VALUES
   1114                 (1, 'myc', 'Primary', randomblob(32), 1, 1700000000000)",
   1115             "INSERT INTO radroots_service_metadata VALUES
   1116                 (1, 'myc', 'primary', zeroblob(31), 1, 1700000000000)",
   1117             "INSERT INTO radroots_service_metadata VALUES
   1118                 (1, 'myc', 'primary', zeroblob(32), 1, 1700000000000)",
   1119             "INSERT INTO radroots_service_metadata VALUES
   1120                 (1, 'myc', 'primary', randomblob(33), 1, 1700000000000)",
   1121             "INSERT INTO radroots_service_metadata VALUES
   1122                 (1, 'myc', 'primary', randomblob(32), 0, 1700000000000)",
   1123             "INSERT INTO radroots_service_metadata VALUES
   1124                 (1, 'myc', 'primary', randomblob(32), 1, 0)",
   1125             "INSERT INTO radroots_service_metadata VALUES
   1126                 ('1', 'myc', 'primary', randomblob(32), 1, 1700000000000)",
   1127             "INSERT INTO radroots_service_metadata VALUES
   1128                 (1, 7, 'primary', randomblob(32), 1, 1700000000000)",
   1129             "INSERT INTO radroots_service_metadata VALUES
   1130                 (1, 'myc', 7, randomblob(32), 1, 1700000000000)",
   1131             "INSERT INTO radroots_service_metadata VALUES
   1132                 (1, 'myc', 'primary', 'not-a-generation', 1, 1700000000000)",
   1133             "INSERT INTO radroots_service_metadata VALUES
   1134                 (1, 'myc', 'primary', randomblob(32), '1', 1700000000000)",
   1135             "INSERT INTO radroots_service_metadata VALUES
   1136                 (1, 'myc', 'primary', randomblob(32), 1, '1700000000000')",
   1137             "INSERT INTO radroots_service_metadata VALUES
   1138                 (1, 'myc', 'primary', randomblob(32), -1, 1700000000000)",
   1139             "INSERT INTO radroots_service_metadata VALUES
   1140                 (1, 'myc', 'primary', randomblob(32), 4294967296, 1700000000000)",
   1141             "INSERT INTO radroots_service_metadata VALUES
   1142                 (1, 'myc', 'primary', randomblob(32), 1, -1)",
   1143         ];
   1144         for corrupt_row in corrupt_rows {
   1145             let mut connection = memory_connection().await;
   1146             sqlx::raw_sql(PERMISSIVE_TABLE)
   1147                 .execute(&mut connection)
   1148                 .await
   1149                 .expect("permissive metadata table");
   1150             sqlx::query("PRAGMA application_id = 1380209489")
   1151                 .execute(&mut connection)
   1152                 .await
   1153                 .expect("application ID");
   1154             if !corrupt_row.is_empty() {
   1155                 sqlx::raw_sql(corrupt_row)
   1156                     .execute(&mut connection)
   1157                     .await
   1158                     .expect("corrupt metadata row");
   1159             }
   1160             assert_eq!(
   1161                 verify_database_metadata(&mut connection, &expected.identity())
   1162                     .await
   1163                     .expect_err("corrupt metadata")
   1164                     .kind(),
   1165                 ServiceSqliteErrorKind::Metadata,
   1166                 "accepted corrupt fixture `{corrupt_row}`"
   1167             );
   1168         }
   1169 
   1170         let oversized_text = "a".repeat(4 * 1024 * 1024);
   1171         for column in ["service_id", "instance_id"] {
   1172             let mut connection = memory_connection().await;
   1173             sqlx::raw_sql(PERMISSIVE_TABLE)
   1174                 .execute(&mut connection)
   1175                 .await
   1176                 .expect("permissive metadata table");
   1177             sqlx::query("PRAGMA application_id = 1380209489")
   1178                 .execute(&mut connection)
   1179                 .await
   1180                 .expect("application ID");
   1181             sqlx::raw_sql(
   1182                 "INSERT INTO radroots_service_metadata VALUES
   1183                     (1, 'myc', 'primary', randomblob(32), 1, 1700000000000)",
   1184             )
   1185             .execute(&mut connection)
   1186             .await
   1187             .expect("metadata row");
   1188             let statement = match column {
   1189                 "service_id" => "UPDATE radroots_service_metadata SET service_id = ?",
   1190                 "instance_id" => "UPDATE radroots_service_metadata SET instance_id = ?",
   1191                 _ => unreachable!("fixed oversized identifier column inventory"),
   1192             };
   1193             sqlx::query(statement)
   1194                 .bind(&oversized_text)
   1195                 .execute(&mut connection)
   1196                 .await
   1197                 .expect("oversized persisted identifier");
   1198             assert_eq!(
   1199                 verify_database_metadata(&mut connection, &expected.identity())
   1200                     .await
   1201                     .expect_err("oversized identifier must fail before decode")
   1202                     .kind(),
   1203                 ServiceSqliteErrorKind::Metadata
   1204             );
   1205         }
   1206 
   1207         let mut oversized_generation = memory_connection().await;
   1208         sqlx::raw_sql(PERMISSIVE_TABLE)
   1209             .execute(&mut oversized_generation)
   1210             .await
   1211             .expect("permissive metadata table");
   1212         sqlx::query("PRAGMA application_id = 1380209489")
   1213             .execute(&mut oversized_generation)
   1214             .await
   1215             .expect("application ID");
   1216         sqlx::query(
   1217             "INSERT INTO radroots_service_metadata VALUES
   1218                 (1, 'myc', 'primary', zeroblob(4194304), 1, 1700000000000)",
   1219         )
   1220         .execute(&mut oversized_generation)
   1221         .await
   1222         .expect("oversized persisted generation");
   1223         assert_eq!(
   1224             verify_database_metadata(&mut oversized_generation, &expected.identity())
   1225                 .await
   1226                 .expect_err("oversized generation must fail before decode")
   1227                 .kind(),
   1228             ServiceSqliteErrorKind::Metadata
   1229         );
   1230 
   1231         for (application_id, statement) in [
   1232             (0_i64, "PRAGMA application_id = 0"),
   1233             (-1, "PRAGMA application_id = -1"),
   1234         ] {
   1235             let mut connection = memory_connection().await;
   1236             sqlx::raw_sql(PERMISSIVE_TABLE)
   1237                 .execute(&mut connection)
   1238                 .await
   1239                 .expect("permissive metadata table");
   1240             sqlx::query(statement)
   1241                 .execute(&mut connection)
   1242                 .await
   1243                 .expect("invalid application ID fixture");
   1244             sqlx::query(
   1245                 "INSERT INTO radroots_service_metadata VALUES
   1246                     (1, 'myc', 'primary', randomblob(32), 1, 1700000000000)",
   1247             )
   1248             .execute(&mut connection)
   1249             .await
   1250             .expect("otherwise valid metadata row");
   1251             assert_eq!(
   1252                 verify_database_metadata(&mut connection, &expected.identity())
   1253                     .await
   1254                     .expect_err("invalid application ID")
   1255                     .kind(),
   1256                 ServiceSqliteErrorKind::Metadata,
   1257                 "accepted application ID {application_id}"
   1258             );
   1259         }
   1260     }
   1261 
   1262     #[cfg(any(target_os = "linux", target_os = "macos"))]
   1263     #[test]
   1264     fn verified_backup_metadata_rejects_invalid_creation_time() {
   1265         let service = ServiceId::new("myc").expect("service");
   1266         let instance = InstanceId::new("primary").expect("instance");
   1267         let generation = SourceGeneration::new([7; 32]).expect("generation");
   1268         let schema = NonZeroU32::new(1).expect("schema");
   1269         let application = ServiceSqliteApplicationId::new(0x5244_5351).expect("application");
   1270         assert!(
   1271             ServiceDatabaseMetadata::from_verified_backup(
   1272                 service.clone(),
   1273                 instance.clone(),
   1274                 generation,
   1275                 schema,
   1276                 0,
   1277                 application,
   1278             )
   1279             .is_err()
   1280         );
   1281         assert!(
   1282             ServiceDatabaseMetadata::from_verified_backup(
   1283                 service,
   1284                 instance,
   1285                 generation,
   1286                 schema,
   1287                 i64::MAX as u64 + 1,
   1288                 application,
   1289             )
   1290             .is_err()
   1291         );
   1292     }
   1293 }