v0.3.7-pre.005
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<LoggingRuntimeState>,
|
||||
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<crate::TransportRuntime>,
|
||||
transport_startup_diagnostic: std::option::Option<crate::CommandErrorDto>,
|
||||
}
|
||||
@@ -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<crate::BackfillDeskOptionsDto> {
|
||||
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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<String>,
|
||||
/// 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<String>,
|
||||
/// 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<CommandErrorDto>,
|
||||
/// Logical network selected by the active Store profile, without backend connection details.
|
||||
pub(crate) store_network: std::option::Option<String>,
|
||||
/// 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<CommandErrorDto>,
|
||||
/// Whether Transport currently has one coherent network and at least one compatible available role.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
220
crates/ksp-app-backfill-desk/src/store_runtime.rs
Normal file
220
crates/ksp-app-backfill-desk/src/store_runtime.rs
Normal file
@@ -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<crate::CommandErrorDto>,
|
||||
network_coherent: bool,
|
||||
runtime: std::option::Option<StoreRuntime>,
|
||||
store_network: std::option::Option<String>,
|
||||
}
|
||||
|
||||
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<std::option::Option<ksp_store_lib::Store>>,
|
||||
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<std::option::Option<ksp_store_lib::Store>>) -> ksp_core_lib::Result<std::option::Option<ksp_store_lib::Store>> {
|
||||
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<String>, 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;
|
||||
@@ -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::Wry>) -> tauri::Builder<tauri:
|
||||
});
|
||||
}
|
||||
|
||||
fn configure_window_events(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||
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::<crate::AppState>();
|
||||
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::<crate::AppState>();
|
||||
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,
|
||||
|
||||
@@ -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<String> {
|
||||
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<crate::BackfillDeskOptionsDto> {
|
||||
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,
|
||||
});
|
||||
|
||||
@@ -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::<std::vec::Vec<_>>();
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
18
crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs
Normal file
18
crates/ksp-app-backfill-desk/unit_tests/store_runtime.rs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
132
deltas/0.3.7/pre.005.md
Normal file
132
deltas/0.3.7/pre.005.md
Normal file
@@ -0,0 +1,132 @@
|
||||
<!-- file: deltas/0.3.7/pre.005.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# 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.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/028-V0_3_7_BACKFILL_DESK_PLAN.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/validation/024-V0_3_7_BACKFILL_DESK.md -->
|
||||
<!-- version: 5 -->
|
||||
<!-- version: 6 -->
|
||||
|
||||
# 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.
|
||||
|
||||
Reference in New Issue
Block a user