From 500b87ef57e2131a39f6ef032e655f05c1d62423 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Wed, 2 Sep 2026 12:01:35 +0200 Subject: [PATCH] v0.3.7-pre.005 --- Cargo.toml | 4 +- crates/ksp-app-backfill-desk/Cargo.toml | 3 +- crates/ksp-app-backfill-desk/src/app_state.rs | 34 ++- crates/ksp-app-backfill-desk/src/constants.rs | 4 +- .../ksp-app-backfill-desk/src/dto_common.rs | 12 +- crates/ksp-app-backfill-desk/src/errors.rs | 8 +- crates/ksp-app-backfill-desk/src/lib.rs | 15 +- .../src/store_runtime.rs | 220 ++++++++++++++++++ crates/ksp-app-backfill-desk/src/tauri.rs | 49 +++- .../src/transport_runtime.rs | 17 +- .../tests/config_composition.rs | 66 +++++- .../tests/desktop_contract.rs | 30 ++- .../tests/desktop_security.rs | 25 +- .../unit_tests/dto_common.rs | 10 +- .../unit_tests/store_runtime.rs | 18 ++ .../unit_tests/transport_runtime.rs | 29 ++- deltas/0.3.7/pre.005.md | 132 +++++++++++ docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md | 8 +- docs/validation/024-V0_3_7_BACKFILL_DESK.md | 31 ++- 19 files changed, 686 insertions(+), 29 deletions(-) create mode 100644 crates/ksp-app-backfill-desk/src/store_runtime.rs create mode 100644 crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs create mode 100644 deltas/0.3.7/pre.005.md diff --git a/Cargo.toml b/Cargo.toml index 7db3a9f..418392a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 420 +# version: 421 [workspace] resolver = "3" members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"] [workspace.package] -version = "0.3.7-pre.4" +version = "0.3.7-pre.5" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-app-backfill-desk/Cargo.toml b/crates/ksp-app-backfill-desk/Cargo.toml index 91c6b13..a13c2c9 100644 --- a/crates/ksp-app-backfill-desk/Cargo.toml +++ b/crates/ksp-app-backfill-desk/Cargo.toml @@ -1,5 +1,5 @@ # file: crates/ksp-app-backfill-desk/Cargo.toml -# version: 2 +# version: 3 [package] name = "ksp-app-backfill-desk" @@ -28,6 +28,7 @@ ksp-config-lib = { path = "../ksp-config-lib" } ksp-core-lib = { path = "../ksp-core-lib" } ksp-logging-lib = { path = "../ksp-logging-lib" } ksp-onchain-transport-lib = { path = "../ksp-onchain-transport-lib" } +ksp-store-lib = { path = "../ksp-store-lib" } serde = { workspace = true, features = ["derive"] } tauri.workspace = true tauri-plugin-tracing.workspace = true diff --git a/crates/ksp-app-backfill-desk/src/app_state.rs b/crates/ksp-app-backfill-desk/src/app_state.rs index e66ecca..1fe0d93 100644 --- a/crates/ksp-app-backfill-desk/src/app_state.rs +++ b/crates/ksp-app-backfill-desk/src/app_state.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-backfill-desk/src/app_state.rs -// version: 2 +// version: 3 //! Shared backend state owned by the Backfill Desk Tauri application. @@ -7,8 +7,10 @@ pub(crate) struct AppState { config_management: ksp_config_lib::ConfigManagement, logging_runtime: std::sync::Mutex, + shutdown_started: std::sync::atomic::AtomicBool, splash_settings: crate::SplashSettings, splash_sequence_started: std::sync::atomic::AtomicBool, + store_startup: crate::StoreStartup, transport_runtime: std::option::Option, transport_startup_diagnostic: std::option::Option, } @@ -46,6 +48,7 @@ impl crate::AppState { (std::option::Option::None, std::option::Option::Some(diagnostic)) }, }; + let store_startup = tauri::async_runtime::block_on(crate::initialize_store(&config_management, transport_runtime.as_ref())); let splash_settings = crate::SplashSettings::load(); let splash_settings = match splash_settings { std::result::Result::Ok(value) => value, @@ -80,8 +83,10 @@ impl crate::AppState { fallback_active: logging_startup.fallback_active, startup_diagnostic: logging_startup.startup_diagnostic, }), + shutdown_started: std::sync::atomic::AtomicBool::new(false), splash_settings, splash_sequence_started: std::sync::atomic::AtomicBool::new(false), + store_startup, transport_runtime, transport_startup_diagnostic, }); @@ -116,23 +121,44 @@ impl crate::AppState { application_version: env!("CARGO_PKG_VERSION").to_owned(), config_document_count: document_count, fallback_logging_active: runtime.fallback_active, - shell_phase: "pre.004-transport-readiness".to_owned(), + shell_phase: "pre.005-store-readiness".to_owned(), startup_diagnostic: runtime.startup_diagnostic.clone(), }); } - /// Builds the safe Transport-readiness subset of Backfill Desk options. + /// Builds the safe Transport-and-Store readiness subset of Backfill Desk options. pub(crate) fn backfill_options(&self) -> ksp_core_lib::Result { let runtime = self.transport_runtime.as_ref(); - return match runtime { + let options = match runtime { std::option::Option::Some(value) => value.options(), std::option::Option::None => std::result::Result::Ok(crate::BackfillDeskOptionsDto { compatible_roles: std::vec::Vec::new(), + composition_ready: false, configured_networks: std::vec::Vec::new(), + network_coherent: false, + store_diagnostic: std::option::Option::None, + store_network: std::option::Option::None, + store_ready: false, transport_diagnostic: self.transport_startup_diagnostic.clone(), transport_ready: false, }), }; + let mut options = match options { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + self.store_startup.apply_to(&mut options); + return std::result::Result::Ok(options); + } + + /// Marks graceful application shutdown as started and reports whether this caller won the one-shot transition. + pub(crate) fn begin_shutdown(&self) -> bool { + return self.shutdown_started.compare_exchange(false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire).is_ok(); + } + + /// Explicitly closes the retained Store runtime when application shutdown begins. + pub(crate) async fn close_store(&self) -> ksp_core_lib::Result<()> { + return self.store_startup.close().await; } /// Returns the resolved splash timings captured during application bootstrap. diff --git a/crates/ksp-app-backfill-desk/src/constants.rs b/crates/ksp-app-backfill-desk/src/constants.rs index 4dc2753..8f9f9a5 100644 --- a/crates/ksp-app-backfill-desk/src/constants.rs +++ b/crates/ksp-app-backfill-desk/src/constants.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-backfill-desk/src/constants.rs -// version: 3 +// version: 4 //! Application-owned tracing targets and domains. @@ -15,6 +15,8 @@ pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "backfill.bootstrap"; pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend"; /// Structured domain used by the Backfill Desk shell. pub(crate) const TRACING_DOMAIN_SHELL: &str = "backfill.shell"; +/// Structured domain used by Store readiness and shutdown operations. +pub(crate) const TRACING_DOMAIN_STORE: &str = "backfill.store"; /// Structured domain used by Transport readiness and role inventory operations. pub(crate) const TRACING_DOMAIN_TRANSPORT: &str = "backfill.transport"; /// Structured domain used by Tauri window lifecycle operations. diff --git a/crates/ksp-app-backfill-desk/src/dto_common.rs b/crates/ksp-app-backfill-desk/src/dto_common.rs index 04fe69c..db8b5f9 100644 --- a/crates/ksp-app-backfill-desk/src/dto_common.rs +++ b/crates/ksp-app-backfill-desk/src/dto_common.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-backfill-desk/src/dto_common.rs -// version: 2 +// version: 3 //! Common Tauri DTOs shared by the Backfill Desk shell. @@ -15,8 +15,18 @@ use ts_rs::TS; // rust-rules: trait-import pub(crate) struct BackfillDeskOptionsDto { /// Logical roles that can route both RPC methods required by the Backfill runtime. pub(crate) compatible_roles: std::vec::Vec, + /// Whether the Transport and Store readiness gates jointly permit later Backfill composition. + pub(crate) composition_ready: bool, /// Distinct enabled HTTP cluster labels selected by the active Transport profile. pub(crate) configured_networks: std::vec::Vec, + /// Whether the selected Store network exactly matches the one coherent Transport network. + pub(crate) network_coherent: bool, + /// Safe startup diagnostic when Store configuration, opening or readiness could not be proven. + pub(crate) store_diagnostic: std::option::Option, + /// Logical network selected by the active Store profile, without backend connection details. + pub(crate) store_network: std::option::Option, + /// Whether Store opened successfully and its bounded health probe proved readiness. + pub(crate) store_ready: bool, /// Safe startup diagnostic when Transport configuration could not be resolved or constructed. pub(crate) transport_diagnostic: std::option::Option, /// Whether Transport currently has one coherent network and at least one compatible available role. diff --git a/crates/ksp-app-backfill-desk/src/errors.rs b/crates/ksp-app-backfill-desk/src/errors.rs index 19ec194..d386eff 100644 --- a/crates/ksp-app-backfill-desk/src/errors.rs +++ b/crates/ksp-app-backfill-desk/src/errors.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-backfill-desk/src/errors.rs -// version: 4 +// version: 5 //! Application-local error codes for Backfill Desk composition and desktop runtime surfaces. @@ -19,6 +19,12 @@ pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode = pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "splash_origin_invalid"); /// A KSP desk splash environment duration is malformed or exceeds its safety bound. pub(crate) const ERROR_CODE_SPLASH_SETTING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "splash_setting_invalid"); +/// Backfill Desk Store and Transport configuration target different logical networks. +pub(crate) const ERROR_CODE_STORE_NETWORK_MISMATCH: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "store_network_mismatch"); +/// Backfill Desk cannot prove a single Transport network before Store opening. +pub(crate) const ERROR_CODE_STORE_NETWORK_UNAVAILABLE: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "store_network_unavailable"); +/// Backfill Desk cannot complete the bounded Store shutdown lifecycle. +pub(crate) const ERROR_CODE_STORE_SHUTDOWN_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "store_shutdown_failed"); /// Tauri runtime assembly or execution failed. pub(crate) const ERROR_CODE_TAURI_RUNTIME_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("backfill_desk", "tauri_runtime_failed"); /// A required Tauri window is missing from the configured application runtime. diff --git a/crates/ksp-app-backfill-desk/src/lib.rs b/crates/ksp-app-backfill-desk/src/lib.rs index 51a1a77..c2521f1 100644 --- a/crates/ksp-app-backfill-desk/src/lib.rs +++ b/crates/ksp-app-backfill-desk/src/lib.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-backfill-desk/src/lib.rs -// version: 3 +// version: 4 //! Tauri desktop application scaffold for controlling and inspecting KSP RAW backfill jobs. @@ -15,6 +15,7 @@ mod errors; mod frontend_logging; mod logging_runtime; mod splash; +mod store_runtime; mod tauri; mod transport_runtime; mod tw_main; @@ -47,6 +48,8 @@ pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP; pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND; /// Structured domain used by the Backfill Desk shell. pub(crate) use self::constants::TRACING_DOMAIN_SHELL; +/// Structured domain used by Store readiness and shutdown operations. +pub(crate) use self::constants::TRACING_DOMAIN_STORE; /// Structured domain used by Transport readiness and role inventory operations. pub(crate) use self::constants::TRACING_DOMAIN_TRANSPORT; /// Structured domain used by Tauri window lifecycle operations. @@ -81,6 +84,12 @@ pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED; pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID; /// A KSP desk splash environment duration is malformed or exceeds its safety bound. pub(crate) use self::errors::ERROR_CODE_SPLASH_SETTING_INVALID; +/// Backfill Desk Store and Transport configuration target different logical networks. +pub(crate) use self::errors::ERROR_CODE_STORE_NETWORK_MISMATCH; +/// Backfill Desk cannot prove a single Transport network before Store opening. +pub(crate) use self::errors::ERROR_CODE_STORE_NETWORK_UNAVAILABLE; +/// Backfill Desk cannot complete the bounded Store shutdown lifecycle. +pub(crate) use self::errors::ERROR_CODE_STORE_SHUTDOWN_FAILED; /// Tauri runtime assembly or execution failed. pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED; /// A required Tauri window is missing from the configured application runtime. @@ -99,6 +108,10 @@ pub(crate) use self::logging_runtime::launch_identity; pub(crate) use self::splash::SplashOrderDto; /// Runtime timings used by the common desk splash lifecycle. pub(crate) use self::splash::SplashSettings; +/// Store startup state retained by the application shell. +pub(crate) use self::store_runtime::StoreStartup; +/// Initializes Store only after composite-selected Transport/Store network coherence is proven. +pub(crate) use self::store_runtime::initialize_store; /// Safe and executable Transport runtime retained by application state. pub(crate) use self::transport_runtime::TransportRuntime; /// Initializes the composite-selected HTTP Transport runtime. diff --git a/crates/ksp-app-backfill-desk/src/store_runtime.rs b/crates/ksp-app-backfill-desk/src/store_runtime.rs new file mode 100644 index 0000000..47faf33 --- /dev/null +++ b/crates/ksp-app-backfill-desk/src/store_runtime.rs @@ -0,0 +1,220 @@ +// file: crates/ksp-app-backfill-desk/src/store_runtime.rs +// version: 1 + +//! Composite-selected Store readiness and shutdown lifecycle owned by Backfill Desk. + +/// Startup result for the Store layer, including safe diagnostics when the desktop shell remains available without a ready Store. +pub(crate) struct StoreStartup { + diagnostic: std::option::Option, + network_coherent: bool, + runtime: std::option::Option, + store_network: std::option::Option, +} + +impl StoreStartup { + /// Applies Store readiness to the application-owned options DTO without exposing backend connection details. + pub(crate) fn apply_to(&self, options: &mut crate::BackfillDeskOptionsDto) { + options.network_coherent = self.network_coherent; + options.store_diagnostic = self.diagnostic.clone(); + options.store_network = self.store_network.clone(); + options.store_ready = self.runtime.as_ref().is_some_and(StoreRuntime::health_ready); + options.composition_ready = options.transport_ready && options.network_coherent && options.store_ready; + } + + /// Closes the retained Store runtime if startup reached the physical Store-open phase. + pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> { + let runtime = self.runtime.as_ref(); + return match runtime { + std::option::Option::Some(value) => value.close().await, + std::option::Option::None => std::result::Result::Ok(()), + }; + } +} + +/// Executable Store runtime retained after network coherence and bounded Store health checks. +pub(crate) struct StoreRuntime { + health_ready: bool, + profile_id: String, + store: std::sync::Mutex>, + store_network: String, +} + +impl StoreRuntime { + /// Returns whether the startup health probe proved the Store ready. + #[must_use] + pub(crate) const fn health_ready(&self) -> bool { + return self.health_ready; + } + + /// Explicitly closes the Store exactly once through its backend-neutral facade. + pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> { + let store = take_store(&self.store); + let store = match store { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let store = match store { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::result::Result::Ok(()), + }; + let closed = store.close().await; + return match closed { + std::result::Result::Ok(()) => { + ksp_logging_lib::debug!( + target: crate::TRACING_TARGET, + domain = crate::TRACING_DOMAIN_STORE, + store_profile = self.profile_id.as_str(), + store_network = self.store_network.as_str(), + "closed Backfill Desk Store runtime" + ); + std::result::Result::Ok(()) + }, + std::result::Result::Err(error) => std::result::Result::Err( + ksp_core_lib::Error::new(crate::ERROR_CODE_STORE_SHUTDOWN_FAILED, "Backfill Desk Store shutdown failed").with_source(error), + ), + }; + } +} + +/// Resolves the composite Store target, proves Transport/Store network coherence before Store I/O, opens Store and captures one health probe. +pub(crate) async fn initialize_store( + management: &ksp_config_lib::ConfigManagement, + transport_runtime: std::option::Option<&crate::TransportRuntime>, +) -> StoreStartup { + let environment = ksp_config_lib::ConfigEnvironment::load(); + let environment = match environment { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, false, error), + }; + let composite = crate::load_backfill_desk_composite(management); + let composite = match composite { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, false, error), + }; + let profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE); + let profile = match profile { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, false, error), + }; + let resolved = management.engine().resolve_store_config_profile(&profile, &environment); + let resolved = match resolved { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, false, error), + }; + let profile_id = resolved.profile_id().to_owned(); + let store_network = resolved.settings().network().as_str().to_owned(); + let transport_network = transport_runtime.and_then(crate::TransportRuntime::coherent_network); + let transport_network = match transport_network { + std::option::Option::Some(value) => value, + std::option::Option::None => { + let error = ksp_core_lib::Error::new( + crate::ERROR_CODE_STORE_NETWORK_UNAVAILABLE, + "Backfill Desk cannot open Store before one coherent Transport network is available", + ); + return unavailable_startup(std::option::Option::Some(store_network), false, error); + }, + }; + let coherence = validate_network_coherence(store_network.as_str(), transport_network.as_str()); + if let std::result::Result::Err(error) = coherence { + return unavailable_startup(std::option::Option::Some(store_network), false, error); + } + let store = ksp_store_lib::Store::open(resolved.into_settings()).await; + let store = match store { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return unavailable_startup(std::option::Option::Some(store_network), true, error), + }; + let health = store.health().await; + let health_ready = store_health_ready(health.state()); + let diagnostic = if health_ready { std::option::Option::None } else { std::option::Option::Some(non_ready_health_diagnostic(&health)) }; + ksp_logging_lib::debug!( + target: crate::TRACING_TARGET, + domain = crate::TRACING_DOMAIN_STORE, + store_profile = profile_id.as_str(), + store_network = store_network.as_str(), + store_ready = health_ready, + pending_migration_count = health.pending_migration_count(), + "initialized Backfill Desk Store readiness from composite-managed configuration" + ); + return StoreStartup { + diagnostic, + network_coherent: true, + runtime: std::option::Option::Some(StoreRuntime { + health_ready, + profile_id, + store: std::sync::Mutex::new(std::option::Option::Some(store)), + store_network: store_network.clone(), + }), + store_network: std::option::Option::Some(store_network), + }; +} + +fn non_ready_health_diagnostic(health: &ksp_store_lib::StoreHealthSnapshot) -> crate::CommandErrorDto { + let code = health.last_error_code(); + return match code { + std::option::Option::Some(value) => crate::CommandErrorDto { + code: value.code().to_owned(), + domain: value.domain().to_owned(), + message: "Store health probe did not prove readiness".to_owned(), + }, + std::option::Option::None => crate::CommandErrorDto { + code: "store_not_ready".to_owned(), + domain: "backfill_desk".to_owned(), + message: "Store health probe did not prove readiness".to_owned(), + }, + }; +} + +fn store_health_ready(state: ksp_store_lib::StoreHealthState) -> bool { + return match state { + ksp_store_lib::StoreHealthState::Ready => true, + ksp_store_lib::StoreHealthState::NotReady => false, + _ => false, + }; +} + +fn take_store(store: &std::sync::Mutex>) -> ksp_core_lib::Result> { + let locked = store.lock(); + let mut locked = match locked { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => { + return std::result::Result::Err(ksp_core_lib::Error::new( + crate::ERROR_CODE_APP_STATE_LOCK_FAILED, + "Backfill Desk Store runtime state lock is poisoned", + )); + }, + }; + return std::result::Result::Ok(locked.take()); +} + +fn unavailable_startup(store_network: std::option::Option, network_coherent: bool, error: ksp_core_lib::Error) -> StoreStartup { + let diagnostic = crate::CommandErrorDto::from_error(&error); + ksp_logging_lib::warn!( + target: crate::TRACING_TARGET, + domain = crate::TRACING_DOMAIN_STORE, + error_domain = diagnostic.domain.as_str(), + error_code = diagnostic.code.as_str(), + network_coherent = network_coherent, + "Backfill Desk Store readiness is unavailable; keeping desktop shell available" + ); + return StoreStartup { + diagnostic: std::option::Option::Some(diagnostic), + network_coherent, + runtime: std::option::Option::None, + store_network, + }; +} + +fn validate_network_coherence(store_network: &str, transport_network: &str) -> ksp_core_lib::Result<()> { + if store_network == transport_network { + return std::result::Result::Ok(()); + } + return std::result::Result::Err( + ksp_core_lib::Error::new(crate::ERROR_CODE_STORE_NETWORK_MISMATCH, "Backfill Desk Store and Transport networks do not match") + .with_context("store_network", store_network) + .with_context("transport_network", transport_network), + ); +} + +#[cfg(test)] +#[path = "../unit_tests/store_runtime.rs"] +mod tests; diff --git a/crates/ksp-app-backfill-desk/src/tauri.rs b/crates/ksp-app-backfill-desk/src/tauri.rs index e419933..eae1b32 100644 --- a/crates/ksp-app-backfill-desk/src/tauri.rs +++ b/crates/ksp-app-backfill-desk/src/tauri.rs @@ -1,8 +1,10 @@ // file: crates/ksp-app-backfill-desk/src/tauri.rs -// version: 3 +// version: 4 //! Tauri runtime assembly for the KSP Backfill desktop application. +use tauri::Manager; // rust-rules: trait-import + /// Runs the Backfill desktop application. #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> { @@ -21,6 +23,7 @@ pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> { builder = configure_plugins(builder); builder = configure_commands(builder); builder = configure_setup(builder); + builder = configure_window_events(builder); let run_result = builder.run(context); return match run_result { std::result::Result::Ok(()) => std::result::Result::Ok(()), @@ -90,6 +93,50 @@ fn configure_setup(builder: tauri::Builder) -> tauri::Builder) -> tauri::Builder { + return builder.on_window_event(|window, event| { + if window.label() != "main" { + return; + } + if let tauri::WindowEvent::CloseRequested { api, .. } = event { + api.prevent_close(); + let app_handle = window.app_handle().clone(); + let state = app_handle.state::(); + if !state.begin_shutdown() { + return; + } + ksp_logging_lib::debug!( + target: crate::TRACING_TARGET, + domain = crate::TRACING_DOMAIN_WINDOWS, + "Backfill Desk main-window close requested; starting bounded Store shutdown" + ); + tauri::async_runtime::spawn(async move { + let state = app_handle.state::(); + let closed = state.close_store().await; + match closed { + std::result::Result::Ok(()) => { + ksp_logging_lib::debug!( + target: crate::TRACING_TARGET, + domain = crate::TRACING_DOMAIN_WINDOWS, + "Backfill Desk Store shutdown completed; exiting application" + ); + }, + std::result::Result::Err(error) => { + ksp_logging_lib::warn!( + target: crate::TRACING_TARGET, + domain = crate::TRACING_DOMAIN_WINDOWS, + error_domain = error.code().domain(), + error_code = error.code().code(), + "Backfill Desk Store shutdown failed; exiting application after bounded close attempt" + ); + }, + } + app_handle.exit(0); + }); + } + }); +} + fn project_command_error(command: &'static str, domain: &'static str, error: &ksp_core_lib::Error) -> crate::CommandErrorDto { ksp_logging_lib::warn!( target: crate::TRACING_TARGET, diff --git a/crates/ksp-app-backfill-desk/src/transport_runtime.rs b/crates/ksp-app-backfill-desk/src/transport_runtime.rs index 56bca95..945ad29 100644 --- a/crates/ksp-app-backfill-desk/src/transport_runtime.rs +++ b/crates/ksp-app-backfill-desk/src/transport_runtime.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-backfill-desk/src/transport_runtime.rs -// version: 1 +// version: 2 //! Composite-selected HTTP Transport readiness owned by Backfill Desk. @@ -16,6 +16,16 @@ impl TransportRuntime { return self.profile_id.as_str(); } + /// Returns the one configured Transport network only when the active HTTP pool is logically coherent. + #[must_use] + pub(crate) fn coherent_network(&self) -> std::option::Option { + let networks = configured_networks(&self.pool.snapshot()); + if networks.len() != 1 { + return std::option::Option::None; + } + return networks.into_iter().next(); + } + /// Builds the current safe Transport-only projection for the Backfill options surface. pub(crate) fn options(&self) -> ksp_core_lib::Result { let snapshot = self.pool.snapshot(); @@ -28,7 +38,12 @@ impl TransportRuntime { let transport_ready = configured_networks.len() == 1 && !compatible_roles.is_empty() && snapshot.available_endpoint_count() > 0; return std::result::Result::Ok(crate::BackfillDeskOptionsDto { compatible_roles, + composition_ready: false, configured_networks, + network_coherent: false, + store_diagnostic: std::option::Option::None, + store_network: std::option::Option::None, + store_ready: false, transport_diagnostic: std::option::Option::None, transport_ready, }); diff --git a/crates/ksp-app-backfill-desk/tests/config_composition.rs b/crates/ksp-app-backfill-desk/tests/config_composition.rs index 9daac42..032f9df 100644 --- a/crates/ksp-app-backfill-desk/tests/config_composition.rs +++ b/crates/ksp-app-backfill-desk/tests/config_composition.rs @@ -1,7 +1,7 @@ // file: crates/ksp-app-backfill-desk/tests/config_composition.rs -// version: 2 +// version: 3 -//! Config composite and Transport-readiness contracts for Backfill Desk through pre.004. +//! Config composite, Transport readiness and Store-network composition contracts for Backfill Desk through pre.005. #![forbid(unsafe_code)] #![deny(unreachable_pub)] @@ -156,3 +156,65 @@ fn pre_004_default_composite_builds_http_pool_for_both_backfill_rpc_methods() { } } } + +#[test] +fn pre_005_all_composite_profiles_keep_resolved_transport_and_store_networks_identical() { + let engine = workspace_engine(); + assert!(engine.is_ok(), "workspace Config engine should be constructible: {engine:?}"); + let engine = match engine { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let environment = ksp_config_lib::ConfigEnvironment::load(); + assert!(environment.is_ok(), "Config environment should load for committed Backfill Desk profiles"); + let environment = match environment { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_BACKFILL_DESK); + let file_id = match file_id { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + for profile_id in ["devnet", "mainnet", "testnet"] { + let composite = engine.load_resolved_composite(&file_id, std::option::Option::Some(profile_id)); + assert!(composite.is_ok(), "Backfill Desk composite profile {profile_id} should resolve: {composite:?}"); + let composite = match composite { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => continue, + }; + let transport = composite.component("transport"); + let store = composite.component("store"); + assert!(transport.is_some(), "profile {profile_id} should expose Transport"); + assert!(store.is_some(), "profile {profile_id} should expose Store"); + let (transport, store) = match (transport, store) { + (std::option::Option::Some(transport), std::option::Option::Some(store)) => (transport, store), + _ => continue, + }; + let transport = engine.resolve_transport_config_profile(transport.resolved(), &environment); + let store = engine.resolve_store_config_profile(store.resolved(), &environment); + assert!(transport.is_ok(), "profile {profile_id} Transport should map: {transport:?}"); + assert!(store.is_ok(), "profile {profile_id} Store should map: {store:?}"); + let (transport, store) = match (transport, store) { + (std::result::Result::Ok(transport), std::result::Result::Ok(store)) => (transport, store), + _ => continue, + }; + let mut networks = transport + .settings() + .endpoints() + .iter() + .filter_map(|endpoint| { + if endpoint.enabled() { + return std::option::Option::Some(endpoint.cluster().as_str().to_owned()); + } + return std::option::Option::None; + }) + .collect::>(); + networks.sort(); + networks.dedup(); + assert_eq!(networks.len(), 1, "profile {profile_id} must resolve exactly one enabled HTTP network"); + if let std::option::Option::Some(transport_network) = networks.first() { + assert_eq!(transport_network.as_str(), store.settings().network().as_str(), "profile {profile_id} Store/Transport network mismatch"); + } + } +} diff --git a/crates/ksp-app-backfill-desk/tests/desktop_contract.rs b/crates/ksp-app-backfill-desk/tests/desktop_contract.rs index 62091d8..8d71293 100644 --- a/crates/ksp-app-backfill-desk/tests/desktop_contract.rs +++ b/crates/ksp-app-backfill-desk/tests/desktop_contract.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-backfill-desk/tests/desktop_contract.rs -// version: 4 +// version: 5 //! Structural desktop contract checks for the Backfill Desk scaffold. @@ -200,3 +200,31 @@ fn pre_004_transport_runtime_builds_pool_and_validates_both_required_rpc_methods let tauri = read_text(root.join("src/tauri.rs").as_path()); assert!(tauri.contains("backfill_options")); } + +#[test] +fn pre_005_store_readiness_proves_network_before_open_and_closes_on_main_window_exit() { + let root = app_root(); + let store = read_text(root.join("src/store_runtime.rs").as_path()); + let coherence = store.find("validate_network_coherence(store_network.as_str(), transport_network.as_str())"); + let open = store.find("ksp_store_lib::Store::open"); + assert!(coherence.is_some()); + assert!(open.is_some()); + if let (std::option::Option::Some(coherence), std::option::Option::Some(open)) = (coherence, open) { + assert!(coherence < open, "Store network coherence must be proven before physical Store open"); + } + assert!(store.contains("store.health().await")); + assert!(store.contains("store.close().await")); + assert!(store.contains("StoreHealthState::Ready")); + assert!(store.contains("StoreHealthState::NotReady")); + assert!(!store.contains("BackfillJobRuntime")); + assert!(!store.contains("BackfillRequest")); + let app_state = read_text(root.join("src/app_state.rs").as_path()); + assert!(app_state.contains("initialize_store")); + assert!(app_state.contains("pre.005-store-readiness")); + assert!(app_state.contains("composition_ready")); + assert!(app_state.contains("close_store")); + let tauri = read_text(root.join("src/tauri.rs").as_path()); + for required in ["on_window_event", "WindowEvent::CloseRequested", "prevent_close", "async_runtime::spawn", "close_store", "app_handle.exit(0)"] { + assert!(tauri.contains(required), "missing bounded Store shutdown lifecycle marker {required}"); + } +} diff --git a/crates/ksp-app-backfill-desk/tests/desktop_security.rs b/crates/ksp-app-backfill-desk/tests/desktop_security.rs index 1db8fc3..64eb615 100644 --- a/crates/ksp-app-backfill-desk/tests/desktop_security.rs +++ b/crates/ksp-app-backfill-desk/tests/desktop_security.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-backfill-desk/tests/desktop_security.rs -// version: 4 +// version: 5 //! Security and dependency-boundary checks for the Backfill Desk scaffold. @@ -43,7 +43,7 @@ fn read_text(path: &std::path::Path) -> String { } #[test] -fn capability_surface_remains_core_plus_tracing_while_transport_is_backend_only() { +fn capability_surface_remains_core_plus_tracing_while_transport_and_store_are_backend_only() { let root = app_root(); let capability = read_text(root.join("capabilities/default.json").as_path()); assert!(capability.contains("\"core:default\"")); @@ -54,7 +54,8 @@ fn capability_surface_remains_core_plus_tracing_while_transport_is_backend_only( let manifest = read_text(root.join("Cargo.toml").as_path()); assert!(manifest.contains("tauri-plugin-tracing.workspace = true")); assert!(manifest.contains("ksp-onchain-transport-lib = { path = \"../ksp-onchain-transport-lib\" }")); - for forbidden in ["ksp-job-backfill-lib", "ksp-store-lib", "ksp-store-api", "ksp-store-postgres-lib", "reqwest", "tokio-postgres"] { + assert!(manifest.contains("ksp-store-lib = { path = \"../ksp-store-lib\" }")); + for forbidden in ["ksp-job-backfill-lib", "ksp-store-api", "ksp-store-postgres-lib", "reqwest", "tokio-postgres"] { assert!(!manifest.contains(forbidden), "current Backfill Desk opens a forbidden dependency: {forbidden}"); } } @@ -114,21 +115,29 @@ fn pre_002_frontend_instrumentation_avoids_business_or_secret_payloads() { } #[test] -fn pre_004_opens_only_config_logging_and_onchain_transport_dependencies() { +fn pre_005_opens_store_facade_without_backend_or_job_dependencies() { let root = app_root(); let manifest = read_text(root.join("Cargo.toml").as_path()); assert!(manifest.contains("ksp-config-lib = { path = \"../ksp-config-lib\" }")); assert!(manifest.contains("ksp-onchain-transport-lib = { path = \"../ksp-onchain-transport-lib\" }")); - for forbidden in ["ksp-job-backfill-lib", "ksp-store-lib", "ksp-store-api", "ksp-store-postgres-lib"] { - assert!(!manifest.contains(forbidden), "pre.004 opens a later-layer dependency: {forbidden}"); + assert!(manifest.contains("ksp-store-lib = { path = \"../ksp-store-lib\" }")); + for forbidden in ["ksp-job-backfill-lib", "ksp-store-api", "ksp-store-postgres-lib", "reqwest", "tokio-postgres"] { + assert!(!manifest.contains(forbidden), "pre.005 opens a forbidden direct dependency: {forbidden}"); } let bootstrap = read_text(root.join("src/bootstrap.rs").as_path()); assert!(bootstrap.contains("LogFilterLevel::Trace")); assert!(!bootstrap.contains("HttpTransportPool")); assert!(!bootstrap.contains("Store::open")); + let store = read_text(root.join("src/store_runtime.rs").as_path()); + assert!(store.contains("ksp_store_lib::Store::open")); + assert!(store.contains("store.health().await")); + assert!(store.contains("store.close().await")); + for forbidden in ["connection_uri", "postgres", "database_url", "provider()", "endpoint_url"] { + assert!(!store.contains(forbidden), "Store runtime leaks or depends on forbidden physical metadata marker {forbidden}"); + } let dto = read_text(root.join("src/dto_common.rs").as_path()); assert!(dto.contains("BackfillDeskOptionsDto")); - for forbidden in ["pub(crate) provider", "endpoint_url", "pub(crate) url", "pub(crate) credential", "pub(crate) token"] { - assert!(!dto.contains(forbidden), "Transport options DTO source contains forbidden field marker {forbidden}"); + for forbidden in ["pub(crate) provider", "endpoint_url", "pub(crate) url", "pub(crate) credential", "pub(crate) token", "connection_uri"] { + assert!(!dto.contains(forbidden), "readiness options DTO source contains forbidden field marker {forbidden}"); } } diff --git a/crates/ksp-app-backfill-desk/unit_tests/dto_common.rs b/crates/ksp-app-backfill-desk/unit_tests/dto_common.rs index c3418d2..ba27cc4 100644 --- a/crates/ksp-app-backfill-desk/unit_tests/dto_common.rs +++ b/crates/ksp-app-backfill-desk/unit_tests/dto_common.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-backfill-desk/unit_tests/dto_common.rs -// version: 2 +// version: 3 #[test] fn command_error_projection_excludes_arbitrary_context_and_source_values() { @@ -17,7 +17,12 @@ fn command_error_projection_excludes_arbitrary_context_and_source_values() { fn transport_options_projection_contains_only_safe_transport_metadata() { let dto = crate::BackfillDeskOptionsDto { compatible_roles: vec!["default".to_owned()], + composition_ready: true, configured_networks: vec!["devnet".to_owned()], + network_coherent: true, + store_diagnostic: std::option::Option::None, + store_network: std::option::Option::Some("devnet".to_owned()), + store_ready: true, transport_diagnostic: std::option::Option::None, transport_ready: true, }; @@ -30,5 +35,8 @@ fn transport_options_projection_contains_only_safe_transport_metadata() { assert!(!serialized.contains("provider")); assert!(!serialized.contains("url")); assert!(!serialized.contains("endpoint")); + assert!(!serialized.contains("postgres")); + assert!(!serialized.contains("connection")); + assert!(!serialized.contains("database")); } } diff --git a/crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs b/crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs new file mode 100644 index 0000000..df0743f --- /dev/null +++ b/crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs @@ -0,0 +1,18 @@ +// file: crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs +// version: 1 + +#[test] +fn store_health_mapping_is_explicit_and_conservative() { + assert!(super::store_health_ready(ksp_store_lib::StoreHealthState::Ready)); + assert!(!super::store_health_ready(ksp_store_lib::StoreHealthState::NotReady)); +} + +#[test] +fn store_transport_network_coherence_requires_exact_logical_identity() { + assert!(super::validate_network_coherence("devnet", "devnet").is_ok()); + let mismatch = super::validate_network_coherence("mainnet-beta", "devnet"); + assert!(mismatch.is_err()); + if let std::result::Result::Err(error) = mismatch { + assert_eq!(error.code(), crate::ERROR_CODE_STORE_NETWORK_MISMATCH); + } +} diff --git a/crates/ksp-app-backfill-desk/unit_tests/transport_runtime.rs b/crates/ksp-app-backfill-desk/unit_tests/transport_runtime.rs index 9697c2f..5213361 100644 --- a/crates/ksp-app-backfill-desk/unit_tests/transport_runtime.rs +++ b/crates/ksp-app-backfill-desk/unit_tests/transport_runtime.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-backfill-desk/unit_tests/transport_runtime.rs -// version: 1 +// version: 2 fn endpoint( name: &str, @@ -99,3 +99,30 @@ fn configured_networks_are_safe_sorted_and_deduplicated() { assert_eq!(values, vec!["devnet".to_owned(), "testnet".to_owned()]); } } + +#[test] +fn coherent_network_requires_exactly_one_enabled_http_cluster() { + let one = endpoint("one", "devnet", "default", vec![ksp_onchain_transport_lib::HttpRequestKind::wildcard()]); + let one = match one { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let one_pool = pool(vec![one]); + assert!(one_pool.is_ok()); + if let std::result::Result::Ok(one_pool) = one_pool { + let runtime = crate::TransportRuntime { pool: one_pool, profile_id: "fixture".to_owned() }; + assert_eq!(runtime.coherent_network(), std::option::Option::Some("devnet".to_owned())); + } + let two = endpoint("two", "devnet", "default", vec![ksp_onchain_transport_lib::HttpRequestKind::wildcard()]); + let three = endpoint("three", "testnet", "default", vec![ksp_onchain_transport_lib::HttpRequestKind::wildcard()]); + let (two, three) = match (two, three) { + (std::result::Result::Ok(two), std::result::Result::Ok(three)) => (two, three), + _ => return, + }; + let multi_pool = pool(vec![two, three]); + assert!(multi_pool.is_ok()); + if let std::result::Result::Ok(multi_pool) = multi_pool { + let runtime = crate::TransportRuntime { pool: multi_pool, profile_id: "fixture".to_owned() }; + assert_eq!(runtime.coherent_network(), std::option::Option::None); + } +} diff --git a/deltas/0.3.7/pre.005.md b/deltas/0.3.7/pre.005.md new file mode 100644 index 0000000..b3aeeb8 --- /dev/null +++ b/deltas/0.3.7/pre.005.md @@ -0,0 +1,132 @@ + + + +# Delta `0.3.7-pre.005` — Store readiness et cohérence réseau de Backfill Desk + +## 1. Base requise + +```text +0.3.7-pre.004 +workspace.package.version = 0.3.7-pre.4 +``` + +Le replay opérateur de `pre.004` est propre : audits Rust/Markdown, `cargo check --workspace`, Clippy, suite complète `ksp-app-backfill-desk` et arbres de dépendances ne rapportent aucun échec. Le lancement réel `cargo tauri dev` confirme également le profil Logging de développement `supertrace`, le profil Transport `devnet_public`, un réseau unique, un rôle compatible, `transport_ready = true` et le tracing `DEBUG` des interactions frontend. + +## 2. Objectif + +Ouvrir uniquement la readiness Store de Backfill Desk : résoudre le Store sélectionné par le composite, prouver la cohérence exacte Store/Transport avant toute ouverture physique, ouvrir la façade backend-neutre `ksp-store-lib::Store`, exécuter un health probe borné, projeter une readiness sûre puis fermer explicitement le Store lors de la fermeture de l'application. Aucun runtime Job Backfill n'est ouvert dans cette tranche. + +## 3. Implémentation + +- ajouter uniquement la dépendance backend `ksp-store-lib` à Backfill Desk ; +- créer `store_runtime.rs`, propriétaire de la résolution Store, de la cohérence réseau, du health probe et du close explicite ; +- dériver le réseau Store depuis `ResolvedStoreConfig::settings()` et le réseau Transport depuis le pool déjà construit ; +- exiger exactement un réseau Transport puis une égalité stricte avec le réseau Store **avant** `Store::open` ; +- ne pas ouvrir Store lorsque Transport ne permet pas de déterminer un réseau unique ou lorsque les réseaux divergent ; +- exécuter `Store::health()` une fois après ouverture et conserver le Store côté Rust même lorsqu'il est `NotReady`, afin de permettre sa fermeture explicite ; +- étendre `BackfillDeskOptionsDto` avec `store_network`, `network_coherent`, `store_ready`, `composition_ready` et un diagnostic Store sûr ; +- conserver hors IPC toute URI, identité backend physique, détail de pool, provider, URL ou credential ; +- intercepter `CloseRequested` de la fenêtre principale, empêcher la fermeture immédiate, exécuter le close Store one-shot puis quitter l'application ; +- conserver les capabilities Tauri à `core:default + tracing:default` et le tracing de développement Backfill à `supertrace` / fallback `trace` ; +- ne pas ajouter `ksp-job-backfill-lib`, `BackfillRequest`, `BackfillJobRuntime`, Start, Cancel ou Resume. + +## 4. Version + +Cette tranche modifie le runtime et ouvre une nouvelle dépendance fonctionnelle. Conformément à `VER-ID-009` : + +```text +workspace.package.version = 0.3.7-pre.5 +label = 0.3.7-pre.005 +``` + +## 5. Fichiers ajoutés + +```text +crates/ksp-app-backfill-desk/src/store_runtime.rs +crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs +deltas/0.3.7/pre.005.md +``` + +## 6. Fichiers modifiés + +```text +Cargo.toml +crates/ksp-app-backfill-desk/Cargo.toml +crates/ksp-app-backfill-desk/src/app_state.rs +crates/ksp-app-backfill-desk/src/constants.rs +crates/ksp-app-backfill-desk/src/dto_common.rs +crates/ksp-app-backfill-desk/src/errors.rs +crates/ksp-app-backfill-desk/src/lib.rs +crates/ksp-app-backfill-desk/src/tauri.rs +crates/ksp-app-backfill-desk/src/transport_runtime.rs +crates/ksp-app-backfill-desk/tests/config_composition.rs +crates/ksp-app-backfill-desk/tests/desktop_contract.rs +crates/ksp-app-backfill-desk/tests/desktop_security.rs +crates/ksp-app-backfill-desk/unit_tests/dto_common.rs +crates/ksp-app-backfill-desk/unit_tests/transport_runtime.rs +docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md +docs/validation/024-V0_3_7_BACKFILL_DESK.md +``` + +## 7. Fichiers supprimés + +Aucun. + +## 8. Invariants conservés + +- Config reste propriétaire des profils, variables d'environnement et secrets ; +- Transport reste propriétaire du routage physique, retry, pacing et endpoint selection ; +- Store est consommé uniquement via `ksp-store-lib`, sans dépendance directe à `ksp-store-api` ou `ksp-store-postgres-lib` ; +- la comparaison réseau logique précède `Store::open` ; +- `BackfillDeskOptionsDto` ne projette aucun détail physique Transport/Store ; +- un Store non-ready ne produit jamais `composition_ready = true` ; +- le Store est fermé explicitement et au plus une fois lors de la fermeture de la fenêtre principale ; +- aucune capability Tauri réseau/fichier/dialog/shell n'est ajoutée ; +- le frontend, son gabarit commun et son instrumentation de clics/tabs restent inchangés ; +- `ksp-job-backfill-lib` reste fermé jusqu'à la tranche prévue ; +- le tracing de développement reste `supertrace` via Config devnet et `trace` en fallback Backfill Desk. + +## 9. Validations exécutées dans l'environnement d'assemblage + +```text +python3 scripts/audit_rust_workspace_rules.py +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.7 +``` + +Contrôles ciblés supplémentaires : + +- parse TOML des manifests racine et Backfill Desk ; +- parse JSON des capabilities et de la configuration Tauri ; +- vérification que la comparaison Store/Transport apparaît avant `Store::open` ; +- vérification de la frontière de dépendances : `ksp-store-lib` admis, backend/API Store et Job interdits ; +- vérification que les capabilities restent `core:default + tracing:default` ; +- vérification structurelle du lifecycle `CloseRequested -> prevent_close -> Store::close -> exit` ; +- vérification de la projection IPC sûre Store/Transport ; +- vérification statique de la cohérence réseau Config pour les profils `devnet`, `mainnet` et `testnet`, plus ajout du test Rust déterministe correspondant. + +## 10. Validations non exécutées dans l'environnement d'assemblage + +`cargo` et `rustfmt` ne sont pas disponibles dans cet environnement. Le gate opérateur `pre.005` reste à exécuter : + +```text +cargo fmt --all +python3 scripts/audit_rust_workspace_rules.py +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.7 +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test -p ksp-app-backfill-desk +cargo tree -p ksp-app-backfill-desk --edges normal +cargo tree -p ksp-app-backfill-desk -e features +``` + +Un lancement `cargo tauri dev` avec un Store PostgreSQL de développement disponible est recommandé pour confirmer le health probe et la fermeture explicite dans le runtime réel ; ce live smoke n'est pas assimilé aux tests déterministes. + +## 11. Décisions + +`pre.005` sépare explicitement la readiness Store de l'exécution Backfill. Le fait qu'un Store soit ouvert et healthy ne suffit pas à autoriser une campagne : `composition_ready` requiert également Transport ready et une cohérence réseau exacte. Les DTO/request de campagne restent réservés à `pre.006`. + +La fermeture de la fenêtre principale devient le point de lifecycle explicite du Store. Elle est one-shot afin d'éviter deux consommations concurrentes du handle `Store`, et le processus quitte après la tentative de close bornée même si celle-ci retourne une erreur, après émission d'un diagnostic backend sûr. + +## 12. Questions ouvertes + +Aucune. diff --git a/docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md b/docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md index 5294deb..a2d59d8 100644 --- a/docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md +++ b/docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md @@ -1,5 +1,5 @@ - + # Plan v0.3.7 — Backfill Desk @@ -245,9 +245,13 @@ Le replay opérateur de `pre.003-fix.001` est ensuite intégralement propre : au Construire `HttpTransportPool`, dériver l'inventaire sûr des réseaux/rôles, vérifier le support simultané de `getSignaturesForAddress` et `getTransaction`, puis exposer `backfill_options()` sans provider, endpoint physique ni URL. `BackfillDeskOptionsDto` apparaît ici comme sous-contrat Transport seulement (`configured_networks`, rôles compatibles, diagnostic sûr, readiness) ; `pre.006` l'étendra avec commitments, scopes et bornes plutôt que d'ouvrir prématurément `ksp-job-backfill-lib`. Une composition Transport avec plusieurs clusters HTTP actifs ou sans rôle compatible reste non-ready. Une erreur de résolution/construction Transport est conservée comme diagnostic sûr afin que le shell puisse rester ouvrable. +Le replay opérateur de `pre.004` est acquis : audits, `cargo check --workspace`, Clippy, toutes les suites ciblées Backfill Desk et les deux arbres de dépendances passent. Le lancement `cargo tauri dev` confirme également le profil Logging `supertrace`, le profil Transport `devnet_public`, un réseau HTTP unique, un rôle compatible et `transport_ready = true`; les clics de tabs frontend apparaissent bien au niveau `DEBUG`. + ### pre.005 — Store readiness -Ouvrir `ksp-store-lib::Store`, vérifier réseau Store/Transport avant run, lifecycle de fermeture et erreurs sûres. Scinder ici plutôt que mélanger Transport + Store dans une seule tranche large. +Ouvrir uniquement `ksp-store-lib::Store` depuis le profil Store sélectionné par le composite. La Desk résout d'abord les settings Store, dérive leur réseau logique et exige un unique réseau Transport identique **avant** `Store::open`; aucun URI, backend physique ni détail de pool n'est projeté vers l'IPC. Après ouverture, un unique `Store::health()` borné produit `store_ready`, tandis que `network_coherent` et `composition_ready` rendent la chaîne Transport + Store explicite. Une erreur Config, ouverture ou health conserve le shell utilisable avec un diagnostic sûr, sans inventer de readiness. + +Le `Store` ouvert reste détenu côté Rust et doit être fermé explicitement via `Store::close()` lors d'une demande de fermeture de la fenêtre principale. La Desk intercepte cette fermeture une seule fois, empêche la destruction immédiate, lance le close borné puis quitte l'application. Aucun `BackfillRequest`, `BackfillJobRuntime`, accès backend PostgreSQL direct ou capability Tauri supplémentaire n'est admis dans cette tranche. ### pre.006 — DTO/request mapping diff --git a/docs/validation/024-V0_3_7_BACKFILL_DESK.md b/docs/validation/024-V0_3_7_BACKFILL_DESK.md index 4dee638..7175bd1 100644 --- a/docs/validation/024-V0_3_7_BACKFILL_DESK.md +++ b/docs/validation/024-V0_3_7_BACKFILL_DESK.md @@ -1,5 +1,5 @@ - + # Validation v0.3.7 — Backfill Desk @@ -224,3 +224,32 @@ Une seule network distincte et au moins un rôle compatible sont requis pour `tr - [X] audit Rust workspace local propre, zéro candidat d'export ; - [ ] `cargo fmt/check/clippy/test` de `pre.004` à rejouer par l'opérateur ; `cargo`/`rustfmt` restent absents du sandbox. +## 13. `pre.005` — Store readiness et cohérence réseau + +### Gate opérateur `pre.004` acquis + +- [X] audits Rust/Markdown propres ; +- [X] `cargo check --workspace` propre ; +- [X] `cargo clippy --workspace --all-targets` propre ; +- [X] `cargo test -p ksp-app-backfill-desk` : 17 unitaires + 3 composition + 8 desktop contract + 5 desktop security + 1 public API, aucun échec ; +- [X] arbres `cargo tree` Backfill Desk exécutés sans anomalie rapportée ; +- [X] `cargo tauri dev` démarre le shell sur le profil Logging `supertrace` et le profil Transport `devnet_public` ; +- [X] le runtime réel rapporte un seul réseau, un rôle compatible et `transport_ready = true` ; +- [X] les changements de tabs sont visibles dans le tracing frontend `DEBUG`. + +### Gate statique local `pre.005` + +- [X] dépendance directe `ksp-store-lib` ajoutée à Backfill Desk ; +- [X] aucune dépendance directe `ksp-store-api`, `ksp-store-postgres-lib`, `ksp-job-backfill-lib`, `reqwest` ou `tokio-postgres` ajoutée ; +- [X] le réseau Store est dérivé des settings Config résolus et comparé au réseau Transport unique avant `Store::open` ; +- [X] une incohérence ou l'absence de réseau Transport cohérent empêche l'ouverture Store ; +- [X] `Store::health()` est exécuté une fois après ouverture et sa projection reste backend-neutre ; +- [X] `BackfillDeskOptionsDto` expose seulement réseau Store, `network_coherent`, `store_ready`, `composition_ready` et diagnostics sûrs en plus du sous-contrat Transport ; +- [X] aucune URI PostgreSQL, URL Transport, identité provider, credential ou objet Store n'est projeté vers l'IPC ; +- [X] le `Store` est détenu côté Rust derrière une fermeture one-shot et `Store::close()` ; +- [X] la fermeture de la fenêtre principale est interceptée pour exécuter le close Store borné avant la sortie applicative ; +- [X] capabilities Tauri inchangées : `core:default + tracing:default` ; +- [X] tracing de développement Backfill inchangé : Config `supertrace`, fallback `trace` ; +- [X] aucun `BackfillRequest`, `BackfillJobRuntime`, Start/Cancel/Resume n'est ouvert ; +- [X] audit Rust workspace local propre, zéro candidat d'export ; +- [ ] `cargo fmt/check/clippy/test` de `pre.005` à rejouer par l'opérateur ; `cargo`/`rustfmt` restent absents du sandbox.