commit abdefc7c92e5e62d0c8edf5cdc305bad6b157090 parent 5799dab326238d92d0f4e63856fee3fd3d423cab Author: triesap <tyson@radroots.org> Date: Thu, 27 Aug 2026 22:37:31 +0000 security: adopt sealed state initialization Diffstat:
27 files changed, 196 insertions(+), 126 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md @@ -56,6 +56,13 @@ - Keep changes narrowly scoped and independently reviewable. Do not mix unrelated cleanup, speculative abstractions, roadmap work, or compatibility scaffolding into a checkpoint. +- RCLD-RSHR-195 Step 246 advances the active native Lib source lock and freezes + RHI state creation behind the runtime-path directory plan plus the sealed + service-SQLite initializer. Explicit initialization may provision only the + exact governed service-instance suffix after identity and catalog validation; + every existing-only open remains non-creating. Do not restore raw paths, raw + SQLx connections, filesystem probes, or directory-creation fallbacks at the + state-host boundary. ## 3. Clean-slate service rule diff --git a/Cargo.lock b/Cargo.lock @@ -1690,7 +1690,7 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "radroots_blossom" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "mediatype", "serde", @@ -1702,7 +1702,7 @@ dependencies = [ [[package]] name = "radroots_core" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "rust_decimal", "serde", @@ -1711,7 +1711,7 @@ dependencies = [ [[package]] name = "radroots_event" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "hex", "jiff-tzdb", @@ -1729,7 +1729,7 @@ dependencies = [ [[package]] name = "radroots_event_codec" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "hex", "radroots_blossom", @@ -1746,7 +1746,7 @@ dependencies = [ [[package]] name = "radroots_identity" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "k256", "serde", @@ -1756,7 +1756,7 @@ dependencies = [ [[package]] name = "radroots_nostr" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "nostr", "radroots_event", @@ -1770,7 +1770,7 @@ dependencies = [ [[package]] name = "radroots_protocol" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "serde", ] @@ -1778,8 +1778,9 @@ dependencies = [ [[package]] name = "radroots_runtime_paths" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ + "rustix", "serde", "thiserror 1.0.69", ] @@ -1787,7 +1788,7 @@ dependencies = [ [[package]] name = "radroots_secrets" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "chacha20poly1305", "serde", @@ -1799,7 +1800,7 @@ dependencies = [ [[package]] name = "radroots_service_host" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "bytes", "fs2", @@ -1820,7 +1821,7 @@ dependencies = [ [[package]] name = "radroots_service_sqlite" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "fs2", "futures", @@ -1838,7 +1839,7 @@ dependencies = [ [[package]] name = "radroots_storage" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "radroots_event", "radroots_event_codec", @@ -1851,7 +1852,7 @@ dependencies = [ [[package]] name = "radroots_trade" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "hex", "radroots_core", @@ -1865,7 +1866,7 @@ dependencies = [ [[package]] name = "radroots_transport" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "radroots_event", "radroots_identity", @@ -1876,7 +1877,7 @@ dependencies = [ [[package]] name = "radroots_transport_nostr" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=21b11e7a5120ea949f7ad0838c746873fc73aac2#21b11e7a5120ea949f7ad0838c746873fc73aac2" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "async-wsocket", "futures", diff --git a/Cargo.toml b/Cargo.toml @@ -52,18 +52,18 @@ workspace = true [dependencies] base64 = "0.22" -radroots_event = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha", features = ["serde"] } -radroots_event_codec = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha", features = ["json"] } -radroots_nostr = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha", features = ["events"] } -radroots_protocol = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha" } -radroots_runtime_paths = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha" } -radroots_service_host = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha" } -radroots_service_sqlite = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha" } -radroots_storage = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha", default-features = false } -radroots_transport = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha", default-features = false, features = ["std"] } -radroots_transport_nostr = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha" } -radroots_secrets = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha" } -radroots_trade = { git = "https://github.com/radrootslabs/lib", rev = "21b11e7a5120ea949f7ad0838c746873fc73aac2", version = "=0.1.0-alpha" } +radroots_event = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha", features = ["serde"] } +radroots_event_codec = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha", features = ["json"] } +radroots_nostr = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha", features = ["events"] } +radroots_protocol = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha" } +radroots_runtime_paths = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha" } +radroots_service_host = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha" } +radroots_service_sqlite = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha" } +radroots_storage = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha", default-features = false } +radroots_transport = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha", default-features = false, features = ["std"] } +radroots_transport_nostr = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha" } +radroots_secrets = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha" } +radroots_trade = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha" } chacha20poly1305 = { version = "0.10" } clap = { version = "4", features = ["derive"] } diff --git a/README b/README @@ -743,6 +743,14 @@ credential remain excluded from the state-backup contract. That earlier wave-two boundary proof did not itself execute a backup; the governed resilience boundary below now owns the actual backup and recovery mechanics. +Explicit state initialization validates runtime identity, build evidence, and +the complete migration/schema catalogs before invoking the runtime-path +directory plan. That plan alone may provision the exact interactive +`services/rhi/<instance>` suffix; service-host deployment roots and suffixes +must already exist. The shared service-SQLite initializer owns the one +transaction and exposes only its sealed typed SQLx executor. Existing writable +and inspection opens never provision directories or create missing state. + RHI's state surface is partitioned into twenty-one distinct non-forgeable typed repository capabilities bound to one already-opened `RhiStateHost`. Their closed topology covers source results, cursors, completions, admitted signed diff --git a/contracts/services_hardening/admin_wave_qualification.v1.json b/contracts/services_hardening/admin_wave_qualification.v1.json @@ -62,7 +62,7 @@ }, "source_locked_transport_evidence": { "repository": "https://github.com/radrootslabs/lib", - "revision": "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "revision": "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "package": "radroots_service_host", "corpus": [ "serves_valid_json_with_exact_caller_correlation_and_no_web_headers", diff --git a/contracts/services_hardening/failure_qualification.v1.json b/contracts/services_hardening/failure_qualification.v1.json @@ -6,7 +6,7 @@ "service": "rhi", "source_lock": { "schema": "radroots.service.source-lock.v2", - "lib_revision": "21b11e7a5120ea949f7ad0838c746873fc73aac2" + "lib_revision": "053d0c750bf9cd683c6ea37cefe7e79617ba629f" }, "resource_bounds": { "configuration_document_utf8_bytes": 1048576, diff --git a/contracts/services_hardening/process_qualification.v1.json b/contracts/services_hardening/process_qualification.v1.json @@ -6,12 +6,12 @@ "binary": "rhi", "source_lock": { "schema": "radroots.service.source-lock.v2", - "lib_revision": "21b11e7a5120ea949f7ad0838c746873fc73aac2" + "lib_revision": "053d0c750bf9cd683c6ea37cefe7e79617ba629f" }, "component_qualification": { "schema": "radroots.rhi.failure-qualification.v1", "step": 214, - "sha256": "e9c782185a4a2b7512193a3cd237008026193ba3f8bb49fab1c70039a2f35bca" + "sha256": "f05da8e559f463f99c67c3c7e22eafa57935be91fb0094f2aa2f98a58544b8b9" }, "bounds": { "process_deadline_ms": 30000, diff --git a/radroots.service.source-lock.v2.toml b/radroots.service.source-lock.v2.toml @@ -2,12 +2,12 @@ schema = "radroots.service.source-lock.v2" contract_version = 2 service = "rhi" repository = "https://github.com/radrootslabs/lib" -revision = "21b11e7a5120ea949f7ad0838c746873fc73aac2" +revision = "053d0c750bf9cd683c6ea37cefe7e79617ba629f" architecture = "radroots.crates.release.v2" workspace_catalog_sha256 = "deca0c080deae187ff8186c0708903e42f41ea57f77c5f91581e23aa561164a4" version = "0.1.0-alpha" -source_archive_sha256 = "7e584a4b679264620d7bb6cf0a7028cc7651b33977b263c213f4e7b29c0e5a19" -cargo_lock_sha256 = "7a79bb19dc9275f65b86decff5136c5433b0858c5da5135625745194a074c021" +source_archive_sha256 = "4c0769a6105cf7547b85544a249178fc7384161e759e9e712994419eaf168e9c" +cargo_lock_sha256 = "644ad1c9024a0774df438df26b2152cf458f68a3c4b59d16b3335fbf296bb560" rust_version = "1.97.1" host_feature_profile = "service-host" diff --git a/src/runtime_graph.rs b/src/runtime_graph.rs @@ -1689,7 +1689,7 @@ mod tests { let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/src/state_host.rs b/src/state_host.rs @@ -1,18 +1,15 @@ //! Sealed lifecycle boundary for the canonical RHI SQLite state catalog. use core::fmt; -use std::{ - error::Error, - path::{Path, PathBuf}, -}; +use std::{error::Error, path::Path}; use radroots_service_sqlite::{ BackupCreatedAtUnixMs, ExistingServiceDatabaseIntent, IntegrityCheckedAtUnixMs, MigrationAppliedAtUnixSeconds, MigrationBuildIdentity, OpenMode, ServiceBackupManifest, ServiceSqliteApplicationId, ServiceSqliteConnectionOptions, ServiceSqliteHost, - ServiceSqliteIntegrityReport, ServiceSqlitePaths, initialize_database, + ServiceSqliteInitializer, ServiceSqliteInitializerFuture, ServiceSqliteIntegrityReport, + ServiceSqlitePaths, initialize_database, }; -use sqlx::{ConnectOptions, Connection, SqliteConnection, sqlite::SqliteConnectOptions}; use crate::{ RHI_STATE_APPLICATION_ID, RHI_STATE_BASE_SCHEMA_VERSION, RHI_STATE_SCHEMA_VERSION, @@ -222,6 +219,7 @@ pub async fn initialize_rhi_state( require_metadata(runtime, metadata)?; require_migration_build(metadata, build)?; let (migrations, schema) = catalogs()?; + provision_state_directory(runtime)?; let authority = initialize_database( &paths, OpenMode::Initialize, @@ -494,6 +492,14 @@ pub(crate) fn state_paths( .map_err(|_| RhiStateHostError::new(RhiStateHostErrorKind::InvalidPaths)) } +fn provision_state_directory(runtime: &RhiRuntimeContext) -> Result<(), RhiStateHostError> { + runtime + .context() + .state_directory_plan() + .and_then(|plan| plan.provision()) + .map_err(|_| RhiStateHostError::new(RhiStateHostErrorKind::Initialize)) +} + pub(crate) fn require_metadata( runtime: &RhiRuntimeContext, metadata: &RhiStateMetadata, @@ -575,27 +581,8 @@ pub(crate) fn catalogs() -> Result< Ok((migrations, schema)) } -#[derive(Debug)] -struct EmptyCatalogInitializationError; - -impl fmt::Display for EmptyCatalogInitializationError { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("RHI baseline database reservation could not be opened") - } -} - -impl Error for EmptyCatalogInitializationError {} - -async fn initialize_empty_catalog(path: PathBuf) -> Result<(), EmptyCatalogInitializationError> { - let options = SqliteConnectOptions::new() - .filename(path) - .create_if_missing(false) - .disable_statement_logging(); - let connection = SqliteConnection::connect_with(&options) - .await - .map_err(|_| EmptyCatalogInitializationError)?; - connection - .close() - .await - .map_err(|_| EmptyCatalogInitializationError) +fn initialize_empty_catalog<'a>( + _initializer: &'a mut ServiceSqliteInitializer<'_>, +) -> ServiceSqliteInitializerFuture<'a, core::convert::Infallible> { + Box::pin(async { Ok(()) }) } diff --git a/tests/build_policy.rs b/tests/build_policy.rs @@ -43,7 +43,7 @@ fn source_lock_metadata_is_exact_and_nix_is_absent() { fn shared_host_packages_are_exactly_source_locked() { for dependency in ["radroots_service_host", "radroots_service_sqlite"] { assert!(MANIFEST.contains(&format!( - "{dependency} = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"21b11e7a5120ea949f7ad0838c746873fc73aac2\", version = \"=0.1.0-alpha\" }}" + "{dependency} = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\" }}" ))); } } @@ -51,7 +51,7 @@ fn shared_host_packages_are_exactly_source_locked() { #[test] fn shared_service_sqlite_is_the_only_catalog_authority() { assert!(MANIFEST.contains( - "radroots_service_sqlite = { git = \"https://github.com/radrootslabs/lib\", rev = \"21b11e7a5120ea949f7ad0838c746873fc73aac2\", version = \"=0.1.0-alpha\" }" + "radroots_service_sqlite = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\" }" )); for forbidden in ["rusqlite", "libsqlite3-sys"] { assert!( @@ -64,17 +64,17 @@ fn shared_service_sqlite_is_the_only_catalog_authority() { #[test] fn shared_storage_generation_type_is_exactly_source_locked() { assert!(MANIFEST.contains( - "radroots_storage = { git = \"https://github.com/radrootslabs/lib\", rev = \"21b11e7a5120ea949f7ad0838c746873fc73aac2\", version = \"=0.1.0-alpha\", default-features = false }" + "radroots_storage = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\", default-features = false }" )); } #[test] fn shared_transport_spi_is_exactly_source_locked_without_serde() { assert!(MANIFEST.contains( - "radroots_transport = { git = \"https://github.com/radrootslabs/lib\", rev = \"21b11e7a5120ea949f7ad0838c746873fc73aac2\", version = \"=0.1.0-alpha\", default-features = false, features = [\"std\"] }" + "radroots_transport = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\", default-features = false, features = [\"std\"] }" )); assert!(MANIFEST.contains( - "radroots_transport_nostr = { git = \"https://github.com/radrootslabs/lib\", rev = \"21b11e7a5120ea949f7ad0838c746873fc73aac2\", version = \"=0.1.0-alpha\" }" + "radroots_transport_nostr = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\" }" )); } @@ -85,12 +85,12 @@ fn source_lock_binds_the_current_cargo_lock() { "schema = \"radroots.service.source-lock.v2\"\ncontract_version = 2\nservice = \"rhi\"\n" )); assert!(SOURCE_LOCK.contains(&format!("cargo_lock_sha256 = \"{digest}\""))); - assert!(SOURCE_LOCK.contains("revision = \"21b11e7a5120ea949f7ad0838c746873fc73aac2\"")); + assert!(SOURCE_LOCK.contains("revision = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\"")); assert!(SOURCE_LOCK.contains( "workspace_catalog_sha256 = \"deca0c080deae187ff8186c0708903e42f41ea57f77c5f91581e23aa561164a4\"" )); assert!(SOURCE_LOCK.contains( - "source_archive_sha256 = \"7e584a4b679264620d7bb6cf0a7028cc7651b33977b263c213f4e7b29c0e5a19\"" + "source_archive_sha256 = \"4c0769a6105cf7547b85544a249178fc7384161e759e9e712994419eaf168e9c\"" )); assert!(SOURCE_LOCK.contains("\n[nix]\nmaterial = \"absent\"\n")); assert!(!SOURCE_LOCK.contains("flake_lock_sha256")); diff --git a/tests/package_boundary.rs b/tests/package_boundary.rs @@ -88,6 +88,7 @@ const TRADE_EVIDENCE_PERSISTENCE_CONTRACT: &str = include_str!("../contracts/services_hardening/trade_evidence_persistence.v1.json"); const TRADE_SOURCE_INGEST_CONTRACT: &str = include_str!("../contracts/services_hardening/trade_source_ingest.v1.json"); +const STATE_HOST: &str = include_str!("../src/state_host.rs"); const PUBLIC_API: &str = include_str!("../contracts/api_baselines/rhi.txt"); const SOURCES: &[&str] = &[ include_str!("../src/adapters/nostr/event.rs"), @@ -131,6 +132,35 @@ const SOURCES: &[&str] = &[ ]; #[test] +fn state_initialization_uses_only_governed_directory_and_sqlite_authority() { + for required in [ + "ServiceSqliteInitializer", + "ServiceSqliteInitializerFuture", + ".state_directory_plan()", + ".and_then(|plan| plan.provision())", + "initialize_database(", + ] { + assert!( + STATE_HOST.contains(required), + "state initialization is missing `{required}`" + ); + } + for forbidden in [ + "PathBuf", + "use sqlx::", + "SqliteConnectOptions", + "ConnectOptions", + "create_dir_all", + "try_exists", + ] { + assert!( + !STATE_HOST.contains(forbidden), + "state initialization regained `{forbidden}`" + ); + } +} + +#[test] fn package_identity_is_standalone_and_non_publishable() { assert!(MANIFEST.contains("name = \"rhi\"")); assert!(MANIFEST.contains("repository = \"https://github.com/radrootslabs/rhi\"")); @@ -1251,7 +1281,7 @@ fn failure_qualification_is_source_locked_bounded_and_nonexpansive() { assert_eq!(contract["service"], "rhi"); assert_eq!( contract["source_lock"]["lib_revision"], - "21b11e7a5120ea949f7ad0838c746873fc73aac2" + "053d0c750bf9cd683c6ea37cefe7e79617ba629f" ); assert_eq!(contract["resource_bounds"]["source_result_events"], 4_096); assert_eq!( @@ -1283,7 +1313,7 @@ fn process_qualification_is_actual_bounded_and_wave_closed() { assert_eq!(contract["bounds"]["soak_iterations"], 32); assert_eq!( contract["component_qualification"]["sha256"], - "e9c782185a4a2b7512193a3cd237008026193ba3f8bb49fab1c70039a2f35bca" + "f05da8e559f463f99c67c3c7e22eafa57935be91fb0094f2aa2f98a58544b8b9" ); assert_eq!(contract["invariants"]["actual_executable_required"], true); assert_eq!( diff --git a/tests/services_hardening_config_lifecycle.rs b/tests/services_hardening_config_lifecycle.rs @@ -1,16 +1,13 @@ #![forbid(unsafe_code)] #![cfg(any(target_os = "linux", target_os = "macos"))] -use std::{ - fs, - os::unix::fs::PermissionsExt, - path::{Path, PathBuf}, -}; +use std::{fs, os::unix::fs::PermissionsExt, path::Path}; use nostr::{Keys, SecretKey}; use radroots_service_sqlite::{ MigrationAppliedAtUnixSeconds, MigrationBuildIdentity, OpenMode, - ServiceSqliteConnectionOptions, ServiceSqliteHost, ServiceSqlitePaths, initialize_database, + ServiceSqliteConnectionOptions, ServiceSqliteHost, ServiceSqliteInitializer, + ServiceSqliteInitializerFuture, ServiceSqlitePaths, initialize_database, }; use radroots_storage::event::SourceGeneration; use rhi::{ @@ -53,7 +50,7 @@ fn evidence(at: u64) -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentity) let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", @@ -77,18 +74,10 @@ async fn offline_connection(runtime: &rhi::RhiRuntimeContext) -> SqliteConnectio .expect("offline connection") } -async fn initialize_empty_catalog(path: PathBuf) -> Result<(), std::io::Error> { - let options = SqliteConnectOptions::new() - .filename(path) - .create_if_missing(false) - .disable_statement_logging(); - let connection = SqliteConnection::connect_with(&options) - .await - .map_err(|_| std::io::Error::other("database open failed"))?; - connection - .close() - .await - .map_err(|_| std::io::Error::other("database close failed")) +fn initialize_empty_catalog<'a>( + _initializer: &'a mut ServiceSqliteInitializer<'_>, +) -> ServiceSqliteInitializerFuture<'a, core::convert::Infallible> { + Box::pin(async { Ok(()) }) } #[tokio::test] diff --git a/tests/services_hardening_failure_qualification.rs b/tests/services_hardening_failure_qualification.rs @@ -56,7 +56,7 @@ fn contract_freezes_the_exact_failure_qualification_corpus() { contract["source_lock"], serde_json::json!({ "schema": "radroots.service.source-lock.v2", - "lib_revision": "21b11e7a5120ea949f7ad0838c746873fc73aac2" + "lib_revision": "053d0c750bf9cd683c6ea37cefe7e79617ba629f" }) ); assert_eq!( diff --git a/tests/services_hardening_native_release.rs b/tests/services_hardening_native_release.rs @@ -13,7 +13,7 @@ const SYSTEMD_UNIT: &str = include_str!("../packaging/systemd/rhi@.service"); const RELEASE_ACCEPTANCE: &str = include_str!("../scripts/release-acceptance.sh"); const XTASK_MANIFEST: &str = include_str!("../tools/xtask/Cargo.toml"); -const LIB_REVISION: &str = "21b11e7a5120ea949f7ad0838c746873fc73aac2"; +const LIB_REVISION: &str = "053d0c750bf9cd683c6ea37cefe7e79617ba629f"; const LIB_REPOSITORY: &str = "https://github.com/radrootslabs/lib"; #[test] diff --git a/tests/services_hardening_presence_desired_state.rs b/tests/services_hardening_presence_desired_state.rs @@ -47,7 +47,7 @@ fn evidence(at: u64) -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentity) let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_presence_publication.rs b/tests/services_hardening_presence_publication.rs @@ -117,7 +117,7 @@ fn evidence(at: u64) -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentity) MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_process.rs b/tests/services_hardening_process.rs @@ -534,7 +534,7 @@ fn process_qualification_contract_freezes_the_exact_wave_closure() { contract["source_lock"], serde_json::json!({ "schema": "radroots.service.source-lock.v2", - "lib_revision": "21b11e7a5120ea949f7ad0838c746873fc73aac2" + "lib_revision": "053d0c750bf9cd683c6ea37cefe7e79617ba629f" }) ); assert_eq!( @@ -542,7 +542,7 @@ fn process_qualification_contract_freezes_the_exact_wave_closure() { serde_json::json!({ "schema": "radroots.rhi.failure-qualification.v1", "step": 214, - "sha256": "e9c782185a4a2b7512193a3cd237008026193ba3f8bb49fab1c70039a2f35bca" + "sha256": "f05da8e559f463f99c67c3c7e22eafa57935be91fb0094f2aa2f98a58544b8b9" }) ); assert_eq!( @@ -652,7 +652,7 @@ fn process_qualification_contract_freezes_the_exact_wave_closure() { lower_hex(&Sha256::digest(FAILURE_QUALIFICATION_CONTRACT)), contract["component_qualification"]["sha256"] ); - assert!(SOURCE_LOCK.contains("revision = \"21b11e7a5120ea949f7ad0838c746873fc73aac2\"")); + assert!(SOURCE_LOCK.contains("revision = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\"")); } #[test] diff --git a/tests/services_hardening_reconciliation_jobs.rs b/tests/services_hardening_reconciliation_jobs.rs @@ -97,7 +97,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_runtime_foundation.rs b/tests/services_hardening_runtime_foundation.rs @@ -81,7 +81,7 @@ fn evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentity) { let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_source_ingest.rs b/tests/services_hardening_source_ingest.rs @@ -190,7 +190,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_state_catalog.rs b/tests/services_hardening_state_catalog.rs @@ -579,7 +579,7 @@ fn secret_object() -> SchemaObject { #[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 = \"21b11e7a5120ea949f7ad0838c746873fc73aac2\", version = \"=0.1.0-alpha\" }" + "radroots_service_sqlite = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\" }" )); assert!(LIB_SOURCE.contains("mod state_catalog;")); assert!(!LIB_SOURCE.contains("pub mod state_catalog;")); diff --git a/tests/services_hardening_state_host.rs b/tests/services_hardening_state_host.rs @@ -44,6 +44,18 @@ fn prepare_state_directory(runtime: &rhi::RhiRuntimeContext) { fs::set_permissions(directory, fs::Permissions::from_mode(0o700)).expect("state mode"); } +fn prepare_state_root(runtime: &rhi::RhiRuntimeContext) { + let root = runtime + .context() + .paths() + .state() + .ancestors() + .nth(3) + .expect("state root"); + fs::create_dir_all(root).expect("state root"); + fs::set_permissions(root, fs::Permissions::from_mode(0o700)).expect("state root mode"); +} + fn metadata(runtime: &rhi::RhiRuntimeContext) -> RhiStateMetadata { let configuration = parse_rhi_config_v1(EXAMPLE.as_bytes(), RhiConfigProfile::RepoLocal) .expect("configuration"); @@ -61,7 +73,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", @@ -182,11 +194,12 @@ async fn insert_historical_reconciliation_job( async fn initialize_is_create_new_and_both_existing_open_modes_close_explicitly() { let directory = tempfile::tempdir().expect("temporary root"); let runtime = runtime(directory.path(), "primary"); - prepare_state_directory(&runtime); + prepare_state_root(&runtime); let metadata = metadata(&runtime); let state = runtime.artifacts().state_database(); let lock = runtime.artifacts().state_lock(); + assert!(!runtime.context().paths().state().exists()); assert!(!state.exists()); let (applied_at, build) = migration_evidence(); initialize_rhi_state(&runtime, &metadata, applied_at, &build) @@ -528,23 +541,14 @@ async fn schema_v8_scans_historical_nullable_job_state_and_installs_permanent_gu #[tokio::test] async fn missing_state_and_mismatched_evidence_fail_before_database_creation() { - let directory = tempfile::tempdir().expect("temporary root"); - let primary = runtime(directory.path(), "primary"); - let secondary = runtime(directory.path(), "secondary"); - prepare_state_directory(&primary); - let primary_metadata = metadata(&primary); + let invalid_directory = tempfile::tempdir().expect("invalid temporary root"); + let invalid_runtime = runtime(invalid_directory.path(), "invalid"); + let invalid_metadata = metadata(&invalid_runtime); let (applied_at, build) = migration_evidence(); - - let missing = open_rhi_state_read_write(&primary, &primary_metadata, applied_at, &build) - .await - .expect_err("missing state is never created by open"); - assert_eq!(missing.kind(), RhiStateHostErrorKind::ReadWriteOpen); - assert!(!primary.artifacts().state_database().exists()); - let invalid_build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", @@ -555,10 +559,37 @@ async fn missing_state_and_mismatched_evidence_fail_before_database_creation() { 1, ) .expect("structurally valid mismatched build"); - let invalid = initialize_rhi_state(&primary, &primary_metadata, applied_at, &invalid_build) - .await - .expect_err("migration build must match RHI policy before I/O"); + let invalid = initialize_rhi_state( + &invalid_runtime, + &invalid_metadata, + applied_at, + &invalid_build, + ) + .await + .expect_err("migration build must match RHI policy before I/O"); assert_eq!(invalid.kind(), RhiStateHostErrorKind::InvalidEvidence); + assert!(!invalid_runtime.context().paths().state().exists()); + + let missing_directory = tempfile::tempdir().expect("missing temporary root"); + let missing_runtime = runtime(missing_directory.path(), "missing"); + prepare_state_root(&missing_runtime); + let missing_metadata = metadata(&missing_runtime); + let missing = + open_rhi_state_read_write(&missing_runtime, &missing_metadata, applied_at, &build) + .await + .expect_err("existing-only open never provisions the service suffix"); + assert_eq!(missing.kind(), RhiStateHostErrorKind::ReadWriteOpen); + assert!(!missing_runtime.context().paths().state().exists()); + + let directory = tempfile::tempdir().expect("temporary root"); + let primary = runtime(directory.path(), "primary"); + let secondary = runtime(directory.path(), "secondary"); + prepare_state_directory(&primary); + let primary_metadata = metadata(&primary); + let missing = open_rhi_state_read_write(&primary, &primary_metadata, applied_at, &build) + .await + .expect_err("missing state is never created by open"); + assert_eq!(missing.kind(), RhiStateHostErrorKind::ReadWriteOpen); assert!(!primary.artifacts().state_database().exists()); let mismatch = initialize_rhi_state(&secondary, &primary_metadata, applied_at, &build) @@ -579,6 +610,17 @@ fn public_lifecycle_source_is_sealed() { assert!(!LIB_SOURCE.contains("pub mod state_host;")); assert!(HOST_SOURCE.contains("host: ServiceSqliteHost")); assert!(!HOST_SOURCE.contains("pub host:")); + for required in [ + "ServiceSqliteInitializer", + "ServiceSqliteInitializerFuture", + ".state_directory_plan()", + ".and_then(|plan| plan.provision())", + ] { + assert!( + HOST_SOURCE.contains(required), + "missing sealed initialization boundary `{required}`" + ); + } for forbidden in [ "pub fn transaction", "pub async fn transaction", @@ -590,6 +632,12 @@ fn public_lifecycle_source_is_sealed() { "raw_sql", "CREATE TABLE", "PRAGMA application_id", + "PathBuf", + "use sqlx::", + "SqliteConnectOptions", + "ConnectOptions", + "create_dir_all", + "try_exists", ] { assert!( !HOST_SOURCE.contains(forbidden), diff --git a/tests/services_hardening_state_resilience.rs b/tests/services_hardening_state_resilience.rs @@ -73,7 +73,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", @@ -352,7 +352,7 @@ async fn exact_open_rejects_unexpected_migration_history_without_repair() { ) .bind([0x44_u8; 32].as_slice()) .bind("1111111111111111111111111111111111111111") - .bind("21b11e7a5120ea949f7ad0838c746873fc73aac2") + .bind("053d0c750bf9cd683c6ea37cefe7e79617ba629f") .execute(&mut connection) .await .expect("insert unexpected ledger row"); diff --git a/tests/services_hardening_trade_persistence.rs b/tests/services_hardening_trade_persistence.rs @@ -66,7 +66,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_wave_100_b.rs b/tests/services_hardening_wave_100_b.rs @@ -92,7 +92,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "21b11e7a5120ea949f7ad0838c746873fc73aac2", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/source_guards.rs b/tests/source_guards.rs @@ -43,7 +43,7 @@ fn rhi_manifest_exact_pins_radroots_contract() { ); assert_eq!( dependency.get("rev").and_then(toml::Value::as_str), - Some("21b11e7a5120ea949f7ad0838c746873fc73aac2"), + Some("053d0c750bf9cd683c6ea37cefe7e79617ba629f"), "RHI must source-lock {name} to the exact promoted Lib revision" ); assert_eq!(