myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

commit 24810199435363d2ed0096615c18b0a344a257f8
parent 704bcad192dff3f81ac867ea25169cea5d699880
Author: triesap <tyson@radroots.org>
Date:   Thu, 27 Aug 2026 22:05:57 +0000

security: adopt sealed state initialization

Diffstat:
MAGENTS.md | 7+++++++
MCargo.lock | 33+++++++++++++++++----------------
MCargo.toml | 22+++++++++++-----------
MREADME | 8++++++++
Mcontracts/services_hardening/process_qualification.v1.json | 2+-
Mcontracts/services_hardening/provider_delivery.v1.json | 2+-
Mradroots.service.source-lock.v2.toml | 6+++---
Msrc/nip46_wave_080_a.rs | 2+-
Msrc/state_admin.rs | 2+-
Msrc/state_host.rs | 45++++++++++++++++-----------------------------
Mtests/build_policy.rs | 18+++++++++---------
Mtests/package_boundary.rs | 30++++++++++++++++++++++++++++++
Mtests/services_hardening_config_lifecycle.rs | 51+++++++++++++--------------------------------------
Mtests/services_hardening_connection_state.rs | 2+-
Mtests/services_hardening_delivery_state.rs | 2+-
Mtests/services_hardening_discovery_state.rs | 2+-
Mtests/services_hardening_local_signer_transport.rs | 2+-
Mtests/services_hardening_native_release.rs | 2+-
Mtests/services_hardening_process_qualification.rs | 2+-
Mtests/services_hardening_runtime_context.rs | 2+-
Mtests/services_hardening_runtime_foundation.rs | 2+-
Mtests/services_hardening_signer_request_state.rs | 2+-
Mtests/services_hardening_state_catalog.rs | 2+-
Mtests/services_hardening_state_host.rs | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mtests/services_hardening_state_repository.rs | 2+-
Mtests/services_hardening_state_resilience.rs | 2+-
26 files changed, 189 insertions(+), 137 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md @@ -97,6 +97,13 @@ it is not active native revision authority. Native target metadata does not qualify an artifact; Nix, OCI, signing, tags, publication, and deployment remain deferred. +- RCLD-RSHR-195 Step 245 advances the active native Lib source lock and freezes + Myc 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. - Step 148 closes the production NIP-46 response authority in `contracts/services_hardening/nip46_response_commit.v1.json`. Production code may commit a completion only through the atomic exact-response method; diff --git a/Cargo.lock b/Cargo.lock @@ -1752,7 +1752,7 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "radroots_blossom" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "mediatype", "serde", @@ -1764,7 +1764,7 @@ dependencies = [ [[package]] name = "radroots_core" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "rust_decimal", "serde", @@ -1773,7 +1773,7 @@ dependencies = [ [[package]] name = "radroots_event" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "hex", "jiff-tzdb", @@ -1791,7 +1791,7 @@ dependencies = [ [[package]] name = "radroots_event_codec" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "hex", "radroots_blossom", @@ -1808,7 +1808,7 @@ dependencies = [ [[package]] name = "radroots_identity" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "k256", "serde", @@ -1818,7 +1818,7 @@ dependencies = [ [[package]] name = "radroots_nostr" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "nostr", "radroots_event", @@ -1832,7 +1832,7 @@ dependencies = [ [[package]] name = "radroots_nostr_connect" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "nostr", "radroots_event", @@ -1848,7 +1848,7 @@ dependencies = [ [[package]] name = "radroots_protocol" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "serde", ] @@ -1856,8 +1856,9 @@ dependencies = [ [[package]] name = "radroots_runtime_paths" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ + "rustix", "serde", "thiserror 1.0.69", ] @@ -1865,7 +1866,7 @@ dependencies = [ [[package]] name = "radroots_secrets" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "chacha20poly1305", "serde", @@ -1877,7 +1878,7 @@ dependencies = [ [[package]] name = "radroots_service_host" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "bytes", "fs2", @@ -1898,7 +1899,7 @@ dependencies = [ [[package]] name = "radroots_service_sqlite" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "fs2", "futures", @@ -1916,7 +1917,7 @@ dependencies = [ [[package]] name = "radroots_storage" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "radroots_event", "radroots_event_codec", @@ -1929,7 +1930,7 @@ dependencies = [ [[package]] name = "radroots_trade" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "radroots_core", "radroots_event", @@ -1939,7 +1940,7 @@ dependencies = [ [[package]] name = "radroots_transport" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "radroots_event", "radroots_identity", @@ -1950,7 +1951,7 @@ dependencies = [ [[package]] name = "radroots_transport_nostr" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=d287d41c2cd97cd0e455445da90f22180029f089#d287d41c2cd97cd0e455445da90f22180029f089" +source = "git+https://github.com/radrootslabs/lib?rev=053d0c750bf9cd683c6ea37cefe7e79617ba629f#053d0c750bf9cd683c6ea37cefe7e79617ba629f" dependencies = [ "async-wsocket", "futures", diff --git a/Cargo.toml b/Cargo.toml @@ -58,17 +58,17 @@ futures-executor = "0.3" hex = "0.4" jsonschema = { version = "0.48.1", default-features = false } nostr = { version = "0.44.2", features = ["nip04", "nip44", "nip46", "nip49"] } -radroots_identity = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha" } -radroots_nostr = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha", features = ["events"] } -radroots_nostr_connect = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha" } -radroots_event_codec = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha", features = ["json"] } -radroots_runtime_paths = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha" } -radroots_service_host = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha" } -radroots_service_sqlite = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha" } -radroots_secrets = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha", features = ["std"] } -radroots_storage = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha", default-features = false } -radroots_transport = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha", default-features = false } -radroots_transport_nostr = { git = "https://github.com/radrootslabs/lib", rev = "d287d41c2cd97cd0e455445da90f22180029f089", version = "=0.1.0-alpha" } +radroots_identity = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha" } +radroots_nostr = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha", features = ["events"] } +radroots_nostr_connect = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha" } +radroots_event_codec = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha", features = ["json"] } +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_secrets = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha", features = ["std"] } +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 } +radroots_transport_nostr = { git = "https://github.com/radrootslabs/lib", rev = "053d0c750bf9cd683c6ea37cefe7e79617ba629f", version = "=0.1.0-alpha" } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["raw_value"] } sha2 = "0.10" diff --git a/README b/README @@ -494,6 +494,14 @@ reads return the same committed event bytes and projection inputs; publication and hosted NIP-05 responses remain later runtime concerns and never run inside the SQLite transaction. +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/myc/<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. + Writable hosts expose Myc-bound online-backup and active-integrity operations. Backup verification retains the exact admitted member inode, and offline staging derives the same runtime paths, database identity, migration diff --git a/contracts/services_hardening/process_qualification.v1.json b/contracts/services_hardening/process_qualification.v1.json @@ -6,7 +6,7 @@ "binary": "myc", "source_lock": { "schema": "radroots.service.source-lock.v2", - "lib_revision": "d287d41c2cd97cd0e455445da90f22180029f089" + "lib_revision": "053d0c750bf9cd683c6ea37cefe7e79617ba629f" }, "bounds": { "process_deadline_ms": 30000, diff --git a/contracts/services_hardening/provider_delivery.v1.json b/contracts/services_hardening/provider_delivery.v1.json @@ -16,7 +16,7 @@ }, "relay_adapter": { "implementation": "radroots_transport_nostr", - "source_locked_revision": "d287d41c2cd97cd0e455445da90f22180029f089", + "source_locked_revision": "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "preparation_performs_io": false, "target_cardinality_per_attempt": 1, "public_profile": "wss_only", 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 = "myc" repository = "https://github.com/radrootslabs/lib" -revision = "d287d41c2cd97cd0e455445da90f22180029f089" +revision = "053d0c750bf9cd683c6ea37cefe7e79617ba629f" architecture = "radroots.crates.release.v2" workspace_catalog_sha256 = "deca0c080deae187ff8186c0708903e42f41ea57f77c5f91581e23aa561164a4" version = "0.1.0-alpha" -source_archive_sha256 = "ddd9d91e346a33f75e4e6efb4001acae1d90a6846387ca4a57118dd6e056a1ed" -cargo_lock_sha256 = "9b1e1ae86510575dcbae6ebeb1d10c0d98f1f94cc9c23ba3d0646c425460a382" +source_archive_sha256 = "4c0769a6105cf7547b85544a249178fc7384161e759e9e712994419eaf168e9c" +cargo_lock_sha256 = "5e6dc7b87e10c9d122b1d79fcf2dd664d0490f2a0c1567fdb956b98d810eb60d" rust_version = "1.97.1" host_feature_profile = "service-host" diff --git a/src/nip46_wave_080_a.rs b/src/nip46_wave_080_a.rs @@ -110,7 +110,7 @@ pub(crate) fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationB let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/src/state_admin.rs b/src/state_admin.rs @@ -969,7 +969,7 @@ mod tests { MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "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 Myc 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, MigrationApplicationOutcome, MigrationAppliedAtUnixSeconds, MigrationBuildIdentity, OpenMode, ServiceBackupManifest, ServiceSqliteApplicationId, ServiceSqliteConnectionOptions, - ServiceSqliteHost, ServiceSqliteIntegrityReport, ServiceSqlitePaths, initialize_database, + ServiceSqliteHost, ServiceSqliteInitializer, ServiceSqliteInitializerFuture, + ServiceSqliteIntegrityReport, ServiceSqlitePaths, initialize_database, }; -use sqlx::{ConnectOptions, Connection, SqliteConnection, sqlite::SqliteConnectOptions}; use crate::{ MYC_STATE_APPLICATION_ID, MYC_STATE_BASE_SCHEMA_VERSION, MYC_STATE_SCHEMA_VERSION, @@ -224,6 +221,7 @@ pub async fn initialize_myc_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, @@ -453,6 +451,14 @@ pub(crate) fn state_paths( .map_err(|_| MycStateHostError::new(MycStateHostErrorKind::InvalidPaths)) } +fn provision_state_directory(runtime: &MycRuntimeContext) -> Result<(), MycStateHostError> { + runtime + .context() + .state_directory_plan() + .and_then(|plan| plan.provision()) + .map_err(|_| MycStateHostError::new(MycStateHostErrorKind::Initialize)) +} + pub(crate) fn require_metadata( runtime: &MycRuntimeContext, metadata: &MycStateMetadata, @@ -526,27 +532,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("Myc 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 @@ -30,35 +30,35 @@ fn service_host_is_the_exact_default_feature_profile() { #[test] fn shared_runtime_paths_is_exactly_pinned_to_the_source_locked_lib() { assert!(MANIFEST.contains( - "radroots_runtime_paths = { git = \"https://github.com/radrootslabs/lib\", rev = \"d287d41c2cd97cd0e455445da90f22180029f089\", version = \"=0.1.0-alpha\" }" + "radroots_runtime_paths = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\" }" )); } #[test] fn shared_identity_is_exactly_pinned_to_the_source_locked_lib() { assert!(MANIFEST.contains( - "radroots_identity = { git = \"https://github.com/radrootslabs/lib\", rev = \"d287d41c2cd97cd0e455445da90f22180029f089\", version = \"=0.1.0-alpha\" }" + "radroots_identity = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\" }" )); } #[test] fn shared_service_host_is_exactly_pinned_to_the_source_locked_lib() { assert!(MANIFEST.contains( - "radroots_service_host = { git = \"https://github.com/radrootslabs/lib\", rev = \"d287d41c2cd97cd0e455445da90f22180029f089\", version = \"=0.1.0-alpha\" }" + "radroots_service_host = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\" }" )); } #[test] fn shared_service_sqlite_is_exactly_pinned_to_the_source_locked_lib() { assert!(MANIFEST.contains( - "radroots_service_sqlite = { git = \"https://github.com/radrootslabs/lib\", rev = \"d287d41c2cd97cd0e455445da90f22180029f089\", version = \"=0.1.0-alpha\" }" + "radroots_service_sqlite = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\" }" )); } #[test] fn shared_storage_evidence_is_exactly_pinned_to_the_source_locked_lib() { assert!(MANIFEST.contains( - "radroots_storage = { git = \"https://github.com/radrootslabs/lib\", rev = \"d287d41c2cd97cd0e455445da90f22180029f089\", 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 }" )); } @@ -71,7 +71,7 @@ fn delivery_dependencies_are_exactly_source_locked() { ] { assert!( MANIFEST.contains(&format!( - "{dependency} = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"d287d41c2cd97cd0e455445da90f22180029f089\", version = \"=0.1.0-alpha\"" + "{dependency} = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\"" )), "{dependency} is not pinned to the exact source lock" ); @@ -99,18 +99,18 @@ fn source_lock_binds_the_current_cargo_lock() { )); assert!(SOURCE_LOCK.contains(&format!("cargo_lock_sha256 = \"{digest}\""))); assert!(SOURCE_LOCK.contains(&format!("flake_lock_sha256 = \"{flake_digest}\""))); - assert!(SOURCE_LOCK.contains("revision = \"d287d41c2cd97cd0e455445da90f22180029f089\"")); + assert!(SOURCE_LOCK.contains("revision = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\"")); assert!(SOURCE_LOCK.contains( "[nix]\nmaterial = \"deferred\"\nlib_revision = \"b44119fbac5985be8127ad1bf56d2950e6399427\"\n" )); assert!(!SOURCE_LOCK.contains( - "[nix]\nmaterial = \"deferred\"\nlib_revision = \"d287d41c2cd97cd0e455445da90f22180029f089\"\n" + "[nix]\nmaterial = \"deferred\"\nlib_revision = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\"\n" )); assert!(SOURCE_LOCK.contains( "workspace_catalog_sha256 = \"deca0c080deae187ff8186c0708903e42f41ea57f77c5f91581e23aa561164a4\"" )); assert!(SOURCE_LOCK.contains( - "source_archive_sha256 = \"ddd9d91e346a33f75e4e6efb4001acae1d90a6846387ca4a57118dd6e056a1ed\"" + "source_archive_sha256 = \"4c0769a6105cf7547b85544a249178fc7384161e759e9e712994419eaf168e9c\"" )); assert!(SOURCE_LOCK.ends_with( "[contract_versions]\nconfig = 1\nstate = 12\nadmin = 1\nstatus = 1\nprovider = 1\n" diff --git a/tests/package_boundary.rs b/tests/package_boundary.rs @@ -16,6 +16,7 @@ const NIP46_WAVE_080_A: &str = include_str!("../src/nip46_wave_080_a.rs"); const NIP46_COMPLETION: &str = include_str!("../src/state_completion.rs"); const NIP46_RESPONSE: &str = include_str!("../src/state_response.rs"); const STATE_CATALOG: &str = include_str!("../src/state_catalog.rs"); +const STATE_HOST: &str = include_str!("../src/state_host.rs"); const DELIVERY_RECOVERY: &str = include_str!("../src/state_recovery.rs"); const DELIVERY_WORKER: &str = include_str!("../src/delivery_worker.rs"); const PROVIDER_EXECUTOR: &str = include_str!("../src/provider_executor.rs"); @@ -118,6 +119,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 implementation_modules_are_private_and_rustdoc_uses_the_reviewed_readme() { assert_eq!( ROOT.lines() diff --git a/tests/services_hardening_config_lifecycle.rs b/tests/services_hardening_config_lifecycle.rs @@ -1,13 +1,7 @@ #![forbid(unsafe_code)] #![cfg(any(target_os = "linux", target_os = "macos"))] -use std::{ - error::Error, - fs, - num::NonZeroU32, - os::unix::fs::PermissionsExt, - path::{Path, PathBuf}, -}; +use std::{error::Error, fs, num::NonZeroU32, os::unix::fs::PermissionsExt, path::Path}; use myc::{ MycConfigApplyErrorKind, MycConfigProfile, MycStateHostErrorKind, MycStateMetadata, @@ -18,7 +12,8 @@ use myc::{ use radroots_service_sqlite::{ MigrationAppliedAtUnixSeconds, MigrationBuildIdentity, MigrationCatalog, OpenMode, SchemaCatalog, ServiceDatabaseIdentity, ServiceSqliteConnectionOptions, ServiceSqliteHost, - ServiceSqlitePaths, initialize_database, + ServiceSqliteInitializer, ServiceSqliteInitializerFuture, ServiceSqlitePaths, + initialize_database, }; use radroots_storage::event::SourceGeneration; use sqlx::{ConnectOptions, Connection, Row, sqlite::SqliteConnectOptions}; @@ -87,7 +82,7 @@ fn build_for_contracts( MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", @@ -111,6 +106,12 @@ impl std::fmt::Display for TestInitializationError { impl Error for TestInitializationError {} +fn initialize_empty_catalog<'a>( + _initializer: &'a mut ServiceSqliteInitializer<'_>, +) -> ServiceSqliteInitializerFuture<'a, TestInitializationError> { + Box::pin(async { Ok(()) }) +} + async fn initialize_v9(runtime: &myc::MycRuntimeContext, metadata: &MycStateMetadata) { let full_migrations = myc::myc_migration_catalog().expect("full migrations"); let migrations = MigrationCatalog::new(full_migrations.descriptors()[..8].iter().cloned()) @@ -131,20 +132,7 @@ async fn initialize_v9(runtime: &myc::MycRuntimeContext, metadata: &MycStateMeta OpenMode::Initialize, initial, &schema, - |path: PathBuf| async move { - let connection = sqlx::SqliteConnection::connect_with( - &SqliteConnectOptions::new() - .filename(path) - .create_if_missing(false) - .disable_statement_logging(), - ) - .await - .map_err(|_| TestInitializationError)?; - connection - .close() - .await - .map_err(|_| TestInitializationError) - }, + initialize_empty_catalog, ) .await .expect("v9 initialize"); @@ -215,20 +203,7 @@ async fn initialize_v10(runtime: &myc::MycRuntimeContext, metadata: &MycStateMet OpenMode::Initialize, initial, &schema, - |path: PathBuf| async move { - let connection = sqlx::SqliteConnection::connect_with( - &SqliteConnectOptions::new() - .filename(path) - .create_if_missing(false) - .disable_statement_logging(), - ) - .await - .map_err(|_| TestInitializationError)?; - connection - .close() - .await - .map_err(|_| TestInitializationError) - }, + initialize_empty_catalog, ) .await .expect("v10 initialize"); @@ -281,7 +256,7 @@ async fn initialize_v10(runtime: &myc::MycRuntimeContext, metadata: &MycStateMet discovery_public_key, config_contract_version, 10, operator_contract_version, \ status_contract_version, 1725000000, '0.1.0', \ '1111111111111111111111111111111111111111', \ - 'd287d41c2cd97cd0e455445da90f22180029f089', 'rustc-test', 'test-target', \ + '053d0c750bf9cd683c6ea37cefe7e79617ba629f', 'rustc-test', 'test-target', \ 'service-host', 1 FROM myc_state_metadata WHERE singleton = 1", ) .execute(&mut *transaction) diff --git a/tests/services_hardening_connection_state.rs b/tests/services_hardening_connection_state.rs @@ -85,7 +85,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_delivery_state.rs b/tests/services_hardening_delivery_state.rs @@ -126,7 +126,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_discovery_state.rs b/tests/services_hardening_discovery_state.rs @@ -109,7 +109,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_local_signer_transport.rs b/tests/services_hardening_local_signer_transport.rs @@ -84,7 +84,7 @@ fn machine_contract_freezes_the_complete_local_signer_transport() { #[test] fn implementation_uses_only_the_hardened_fixed_unix_admin_boundary() { for required in [ - "radroots_service_host = { git = \"https://github.com/radrootslabs/lib\", rev = \"d287d41c2cd97cd0e455445da90f22180029f089\"", + "radroots_service_host = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\"", "const MYC_LOCAL_SIGNER_ENDPOINT: &str = \"/v1/provider/operation\"", "radroots_service_host::AdminClient", ".mutate::<_, LocalSignerResponse>(", diff --git a/tests/services_hardening_native_release.rs b/tests/services_hardening_native_release.rs @@ -12,7 +12,7 @@ const FLAKE_LOCK: &str = include_str!("../flake.lock"); const CARGO_CONFIG: &str = include_str!("../.cargo/config.toml"); const SYSTEMD_UNIT: &str = include_str!("../packaging/systemd/myc@.service"); -const LIB_REVISION: &str = "d287d41c2cd97cd0e455445da90f22180029f089"; +const LIB_REVISION: &str = "053d0c750bf9cd683c6ea37cefe7e79617ba629f"; const DEFERRED_NIX_LIB_REVISION: &str = "b44119fbac5985be8127ad1bf56d2950e6399427"; const LIB_REPOSITORY: &str = "https://github.com/radrootslabs/lib"; diff --git a/tests/services_hardening_process_qualification.rs b/tests/services_hardening_process_qualification.rs @@ -220,7 +220,7 @@ fn qualification_contract_freezes_the_exact_process_and_component_corpus() { contract["source_lock"], serde_json::json!({ "schema": "radroots.service.source-lock.v2", - "lib_revision": "d287d41c2cd97cd0e455445da90f22180029f089" + "lib_revision": "053d0c750bf9cd683c6ea37cefe7e79617ba629f" }) ); assert_eq!( diff --git a/tests/services_hardening_runtime_context.rs b/tests/services_hardening_runtime_context.rs @@ -288,7 +288,7 @@ fn unsupported_profile_platform_and_diagnostics_fail_safely() { #[test] fn shared_runtime_paths_are_the_only_path_policy_and_identity_authority() { assert!(MANIFEST.contains( - "radroots_runtime_paths = { git = \"https://github.com/radrootslabs/lib\", rev = \"d287d41c2cd97cd0e455445da90f22180029f089\", version = \"=0.1.0-alpha\" }" + "radroots_runtime_paths = { git = \"https://github.com/radrootslabs/lib\", rev = \"053d0c750bf9cd683c6ea37cefe7e79617ba629f\", version = \"=0.1.0-alpha\" }" )); assert!(LIB_SOURCE.contains("mod runtime_context;")); assert!(!LIB_SOURCE.contains("pub mod runtime_context;")); diff --git a/tests/services_hardening_runtime_foundation.rs b/tests/services_hardening_runtime_foundation.rs @@ -129,7 +129,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_signer_request_state.rs b/tests/services_hardening_signer_request_state.rs @@ -66,7 +66,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_state_catalog.rs b/tests/services_hardening_state_catalog.rs @@ -469,7 +469,7 @@ fn catalog_errors_are_stable_source_free_and_redacted() { #[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 = \"d287d41c2cd97cd0e455445da90f22180029f089\", 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 @@ -43,6 +43,18 @@ fn prepare_state_directory(runtime: &myc::MycRuntimeContext) { fs::set_permissions(directory, fs::Permissions::from_mode(0o700)).expect("state mode"); } +fn prepare_state_root(runtime: &myc::MycRuntimeContext) { + 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: &myc::MycRuntimeContext) -> MycStateMetadata { let configuration = parse_myc_config_v1(CONFIG_EXAMPLE, MycConfigProfile::RepoLocal).expect("configuration"); @@ -60,7 +72,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", @@ -78,7 +90,7 @@ fn mismatched_migration_build() -> MigrationBuildIdentity { MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", @@ -95,11 +107,12 @@ fn mismatched_migration_build() -> MigrationBuildIdentity { 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_myc_state(&runtime, &metadata, applied_at, &build) @@ -151,12 +164,37 @@ async fn initialize_is_create_new_and_both_existing_open_modes_close_explicitly( #[tokio::test] async fn missing_state_and_mismatched_evidence_fail_before_database_creation() { + 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 invalid_build = initialize_myc_state( + &invalid_runtime, + &invalid_metadata, + applied_at, + &mismatched_migration_build(), + ) + .await + .expect_err("migration build contract mismatch"); + assert_eq!(invalid_build.kind(), MycStateHostErrorKind::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_myc_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(), MycStateHostErrorKind::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 (applied_at, build) = migration_evidence(); let missing = open_myc_state_read_write(&primary, &primary_metadata, applied_at, &build) .await @@ -164,17 +202,6 @@ async fn missing_state_and_mismatched_evidence_fail_before_database_creation() { assert_eq!(missing.kind(), MycStateHostErrorKind::ReadWriteOpen); assert!(!primary.artifacts().state_database().exists()); - let invalid_build = initialize_myc_state( - &primary, - &primary_metadata, - applied_at, - &mismatched_migration_build(), - ) - .await - .expect_err("migration build contract mismatch"); - assert_eq!(invalid_build.kind(), MycStateHostErrorKind::InvalidEvidence); - assert!(!primary.artifacts().state_database().exists()); - let mismatch = initialize_myc_state(&secondary, &primary_metadata, applied_at, &build) .await .expect_err("cross-instance metadata"); @@ -193,6 +220,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", @@ -204,6 +242,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_repository.rs b/tests/services_hardening_state_repository.rs @@ -62,7 +62,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host", diff --git a/tests/services_hardening_state_resilience.rs b/tests/services_hardening_state_resilience.rs @@ -72,7 +72,7 @@ fn migration_evidence() -> (MigrationAppliedAtUnixSeconds, MigrationBuildIdentit let build = MigrationBuildIdentity::new( env!("CARGO_PKG_VERSION"), "1111111111111111111111111111111111111111", - "d287d41c2cd97cd0e455445da90f22180029f089", + "053d0c750bf9cd683c6ea37cefe7e79617ba629f", "rustc-test", "test-target", "service-host",