From b6908cb573daa5011f74e2434d2b83c4c76e23c2 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Sat, 22 Aug 2026 16:49:43 +0200 Subject: [PATCH] v0.2.7-pre.002-fix.001 --- .../tests/desktop_contract.rs | 47 +++++++++++-- .../tests/desktop_contract.rs | 55 +++++++++++++-- .../src/ws_lifecycle.rs | 8 ++- .../src/ws_settings.rs | 67 ++++++++++++++----- deltas/0.2.7/pre.002-fix.001.md | 63 +++++++++++++++++ 5 files changed, 209 insertions(+), 31 deletions(-) create mode 100644 deltas/0.2.7/pre.002-fix.001.md diff --git a/crates/ksp-app-config-desk/tests/desktop_contract.rs b/crates/ksp-app-config-desk/tests/desktop_contract.rs index 1acec85..df3141a 100644 --- a/crates/ksp-app-config-desk/tests/desktop_contract.rs +++ b/crates/ksp-app-config-desk/tests/desktop_contract.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-config-desk/tests/desktop_contract.rs -// version: 6 +// version: 7 //! Desktop build/shell contract audits for Config Desk. @@ -26,6 +26,43 @@ fn read_json(path: &std::path::Path) -> serde_json::Value { }; } +fn parse_semver_core(value: &str) -> std::option::Option<(u64, u64, u64)> { + let core = match value.split_once('-') { + std::option::Option::Some((core, _)) => core, + std::option::Option::None => value, + }; + let mut parts = core.split('.'); + let major = match parts.next().and_then(|part| return part.parse::().ok()) { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::option::Option::None, + }; + let minor = match parts.next().and_then(|part| return part.parse::().ok()) { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::option::Option::None, + }; + let patch = match parts.next().and_then(|part| return part.parse::().ok()) { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::option::Option::None, + }; + if parts.next().is_some() { + return std::option::Option::None; + } + return std::option::Option::Some((major, minor, patch)); +} + +fn assert_packaged_version_floor(value: std::option::Option<&str>, field: &str) { + assert!(value.is_some(), "{field} packaged version must exist"); + let value = match value { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let parsed = parse_semver_core(value); + assert!(parsed.is_some(), "{field} packaged version must be SemVer-like"); + if let std::option::Option::Some(parsed) = parsed { + assert!(parsed >= (0, 2, 6), "{field} packaged version must be >= 0.2.6"); + } +} + #[test] fn tauri_and_frontend_build_contracts_remain_explicit() { let root = app_root(); @@ -103,11 +140,13 @@ fn pre_014_template_uses_sidebar_navigation_and_kbot_style_splash_contract() { #[test] fn pre_018_packaged_runtime_bundles_config_resources_and_activates_shared_writable_root() { let root = app_root(); - let expected_version = env!("CARGO_PKG_VERSION"); let tauri = read_json(root.join("tauri.conf.json").as_path()); - assert_eq!(tauri.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some(expected_version)); + let tauri_version = tauri.pointer("/version").and_then(serde_json::Value::as_str); + assert_packaged_version_floor(tauri_version, "tauri.conf.json"); let package = read_json(root.join("package.json").as_path()); - assert_eq!(package.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some(expected_version)); + let package_version = package.pointer("/version").and_then(serde_json::Value::as_str); + assert_packaged_version_floor(package_version, "package.json"); + assert_eq!(tauri_version, package_version, "desktop package metadata versions must remain synchronized"); let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object); assert!(resources.is_some(), "packaged Config resources map must exist"); if let std::option::Option::Some(resources) = resources { diff --git a/crates/ksp-app-wallet-desk/tests/desktop_contract.rs b/crates/ksp-app-wallet-desk/tests/desktop_contract.rs index 017fac5..c2b6a54 100644 --- a/crates/ksp-app-wallet-desk/tests/desktop_contract.rs +++ b/crates/ksp-app-wallet-desk/tests/desktop_contract.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs -// version: 21 +// version: 22 //! Desktop build, shell and Config-status contract audits for Wallet Desk. @@ -31,6 +31,43 @@ fn read_json(path: &std::path::Path) -> serde_json::Value { }; } +fn parse_semver_core(value: &str) -> std::option::Option<(u64, u64, u64)> { + let core = match value.split_once('-') { + std::option::Option::Some((core, _)) => core, + std::option::Option::None => value, + }; + let mut parts = core.split('.'); + let major = match parts.next().and_then(|part| return part.parse::().ok()) { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::option::Option::None, + }; + let minor = match parts.next().and_then(|part| return part.parse::().ok()) { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::option::Option::None, + }; + let patch = match parts.next().and_then(|part| return part.parse::().ok()) { + std::option::Option::Some(value) => value, + std::option::Option::None => return std::option::Option::None, + }; + if parts.next().is_some() { + return std::option::Option::None; + } + return std::option::Option::Some((major, minor, patch)); +} + +fn assert_packaged_version_floor(value: std::option::Option<&str>, field: &str) { + assert!(value.is_some(), "{field} packaged version must exist"); + let value = match value { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let parsed = parse_semver_core(value); + assert!(parsed.is_some(), "{field} packaged version must be SemVer-like"); + if let std::option::Option::Some(parsed) = parsed { + assert!(parsed >= (0, 2, 6), "{field} packaged version must be >= 0.2.6"); + } +} + #[test] fn tauri_shell_uses_reserved_wallet_desk_ports_and_template_windows() { let root = app_root(); @@ -419,13 +456,19 @@ fn pre_017_wallet_desk_open_paths_remain_non_migrating() { } #[test] -fn pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_version_current() { +fn pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_version_coherent() { let root = app_root(); - let expected_version = env!("CARGO_PKG_VERSION"); let tauri = read_json(root.join("tauri.conf.json").as_path()); - assert_eq!(tauri.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some(expected_version)); + let tauri_version = tauri.pointer("/version").and_then(serde_json::Value::as_str); + assert_packaged_version_floor(tauri_version, "tauri.conf.json"); let package = read_json(root.join("package.json").as_path()); - assert_eq!(package.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some(expected_version)); + let package_version = package.pointer("/version").and_then(serde_json::Value::as_str); + assert_packaged_version_floor(package_version, "package.json"); + assert_eq!(tauri_version, package_version, "desktop package metadata versions must remain synchronized"); + let packaged_version = match package_version { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object); assert!(resources.is_some(), "packaged Wallet Desk Config resources map must exist"); if let std::option::Option::Some(resources) = resources { @@ -440,7 +483,7 @@ fn pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_versi ); } let main = read_text(root.join("frontend/main.html").as_path()); - assert!(main.contains(expected_version)); + assert!(main.contains(packaged_version)); let tauri_source = read_text(root.join("src/tauri.rs").as_path()); assert!(tauri_source.contains("ksp_config_lib::prepare_packaged_runtime")); assert!(tauri_source.contains("tauri::utils::platform::resource_dir")); diff --git a/crates/ksp-onchain-transport-lib/src/ws_lifecycle.rs b/crates/ksp-onchain-transport-lib/src/ws_lifecycle.rs index 3970b57..0c3440f 100644 --- a/crates/ksp-onchain-transport-lib/src/ws_lifecycle.rs +++ b/crates/ksp-onchain-transport-lib/src/ws_lifecycle.rs @@ -1,5 +1,5 @@ // file: crates/ksp-onchain-transport-lib/src/ws_lifecycle.rs -// version: 1 +// version: 2 /// Stable local identity assigned to one physical WebSocket session. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] @@ -104,7 +104,7 @@ impl WsSubscriptionKind { /// Returns the stable KSP descriptor for this standard subscription family. #[must_use] pub const fn as_str(self) -> &'static str { - match self { + return match self { Self::Account => "account", Self::Block => "block", Self::Logs => "logs", @@ -114,7 +114,7 @@ impl WsSubscriptionKind { Self::Slot => "slot", Self::SlotsUpdates => "slots_updates", Self::Vote => "vote", - } + }; } } @@ -130,6 +130,7 @@ pub struct WsSubscriptionSnapshot { impl WsSubscriptionSnapshot { /// Creates one safe subscription lifecycle projection for Transport runtime internals. #[must_use] + #[cfg(test)] pub(crate) const fn new(id: crate::WsSubscriptionId, kind: crate::WsSubscriptionKind, state: crate::WsSubscriptionState, remote_bound: bool) -> Self { return Self { id, kind, state, remote_bound }; } @@ -182,6 +183,7 @@ pub struct WsSessionSnapshot { impl WsSessionSnapshot { /// Creates one safe session projection for Transport runtime internals. #[must_use] + #[cfg(test)] #[allow(clippy::too_many_arguments)] pub(crate) fn new( id: crate::WsSessionId, diff --git a/crates/ksp-onchain-transport-lib/src/ws_settings.rs b/crates/ksp-onchain-transport-lib/src/ws_settings.rs index 9f6428c..b743dc5 100644 --- a/crates/ksp-onchain-transport-lib/src/ws_settings.rs +++ b/crates/ksp-onchain-transport-lib/src/ws_settings.rs @@ -1,5 +1,5 @@ // file: crates/ksp-onchain-transport-lib/src/ws_settings.rs -// version: 2 +// version: 3 const DEFAULT_WS_CLOSE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5); const DEFAULT_WS_COMMAND_QUEUE_CAPACITY: usize = 128; @@ -132,9 +132,9 @@ impl WsProtocolKind { /// Returns the stable KSP descriptor for this protocol family. #[must_use] pub const fn as_str(self) -> &'static str { - match self { + return match self { Self::SolanaStandard => "solana_standard", - } + }; } } @@ -174,7 +174,11 @@ impl WsReconnectSettings { impl std::default::Default for WsReconnectSettings { fn default() -> Self { - return Self::new(DEFAULT_WS_RECONNECT_MAX_RETRIES, DEFAULT_WS_RECONNECT_INITIAL_BACKOFF, DEFAULT_WS_RECONNECT_MAX_BACKOFF); + return Self::new( + DEFAULT_WS_RECONNECT_MAX_RETRIES, + DEFAULT_WS_RECONNECT_INITIAL_BACKOFF, + DEFAULT_WS_RECONNECT_MAX_BACKOFF, + ); } } @@ -323,13 +327,27 @@ impl WsSessionSettings { "ws_session.reconnect.max_backoff", ); } - validate_non_zero_bound(self.command_queue_capacity, "ws_session.command_queue_capacity")?; - validate_non_zero_bound(self.notification_queue_capacity, "ws_session.notification_queue_capacity")?; - validate_non_zero_bound(self.max_active_subscriptions, "ws_session.max_active_subscriptions")?; - validate_non_zero_bound(self.max_pending_requests, "ws_session.max_pending_requests")?; - validate_non_zero_bound(self.max_message_size_bytes, "ws_session.max_message_size_bytes")?; - validate_non_zero_bound(self.max_frame_size_bytes, "ws_session.max_frame_size_bytes")?; - validate_non_zero_bound(self.max_write_buffer_size_bytes, "ws_session.max_write_buffer_size_bytes")?; + if let std::result::Result::Err(error) = validate_non_zero_bound(self.command_queue_capacity, "ws_session.command_queue_capacity") { + return std::result::Result::Err(error); + } + if let std::result::Result::Err(error) = validate_non_zero_bound(self.notification_queue_capacity, "ws_session.notification_queue_capacity") { + return std::result::Result::Err(error); + } + if let std::result::Result::Err(error) = validate_non_zero_bound(self.max_active_subscriptions, "ws_session.max_active_subscriptions") { + return std::result::Result::Err(error); + } + if let std::result::Result::Err(error) = validate_non_zero_bound(self.max_pending_requests, "ws_session.max_pending_requests") { + return std::result::Result::Err(error); + } + if let std::result::Result::Err(error) = validate_non_zero_bound(self.max_message_size_bytes, "ws_session.max_message_size_bytes") { + return std::result::Result::Err(error); + } + if let std::result::Result::Err(error) = validate_non_zero_bound(self.max_frame_size_bytes, "ws_session.max_frame_size_bytes") { + return std::result::Result::Err(error); + } + if let std::result::Result::Err(error) = validate_non_zero_bound(self.max_write_buffer_size_bytes, "ws_session.max_write_buffer_size_bytes") { + return std::result::Result::Err(error); + } ksp_logging_lib::debug!( target: crate::TRACING_TARGET, command_queue_capacity = self.command_queue_capacity, @@ -369,10 +387,10 @@ impl WsResubscribePolicy { /// Returns the stable KSP descriptor for this policy. #[must_use] pub const fn as_str(self) -> &'static str { - match self { + return match self { Self::Never => "never", Self::ActiveSubscriptions => "active_subscriptions", - } + }; } } @@ -473,7 +491,9 @@ impl WsTransportSettings { } let mut enabled_endpoint_count = 0_usize; for (endpoint_index, endpoint) in self.endpoints.iter().enumerate() { - validate_ws_endpoint(endpoint, endpoint_index)?; + if let std::result::Result::Err(error) = validate_ws_endpoint(endpoint, endpoint_index) { + return std::result::Result::Err(error); + } if endpoint.enabled() { enabled_endpoint_count += 1; } @@ -501,10 +521,21 @@ impl WsTransportSettings { } fn validate_ws_endpoint(endpoint: &crate::WsEndpointSettings, endpoint_index: usize) -> ksp_core_lib::Result<()> { - validate_ws_descriptor(endpoint.name(), format!("ws_endpoints[{endpoint_index}].name").as_str())?; - validate_ws_descriptor(endpoint.provider().as_str(), format!("ws_endpoints[{endpoint_index}].provider").as_str())?; - validate_ws_descriptor(endpoint.cluster().as_str(), format!("ws_endpoints[{endpoint_index}].cluster").as_str())?; - endpoint.session().validate()?; + let name_field = format!("ws_endpoints[{endpoint_index}].name"); + if let std::result::Result::Err(error) = validate_ws_descriptor(endpoint.name(), name_field.as_str()) { + return std::result::Result::Err(error); + } + let provider_field = format!("ws_endpoints[{endpoint_index}].provider"); + if let std::result::Result::Err(error) = validate_ws_descriptor(endpoint.provider().as_str(), provider_field.as_str()) { + return std::result::Result::Err(error); + } + let cluster_field = format!("ws_endpoints[{endpoint_index}].cluster"); + if let std::result::Result::Err(error) = validate_ws_descriptor(endpoint.cluster().as_str(), cluster_field.as_str()) { + return std::result::Result::Err(error); + } + if let std::result::Result::Err(error) = endpoint.session().validate() { + return std::result::Result::Err(error); + } ksp_logging_lib::trace!( target: crate::TRACING_TARGET, endpoint_name = endpoint.name(), diff --git a/deltas/0.2.7/pre.002-fix.001.md b/deltas/0.2.7/pre.002-fix.001.md new file mode 100644 index 0000000..39f0c2c --- /dev/null +++ b/deltas/0.2.7/pre.002-fix.001.md @@ -0,0 +1,63 @@ + + + +# Delta `0.2.7-pre.002-fix.001` — Clippy strict + canari package Config Desk + +## 1. Objet + +Ce correctif ferme les écarts détectés par la validation opérateur de `0.2.7-pre.002` sans modifier la surface fonctionnelle WebSocket et sans changer la version Cargo : + +```text +workspace.package.version = 0.2.7-pre.2 +livraison = 0.2.7-pre.002-fix.001 +commit = v0.2.7-pre.002-fix.001 +``` + +Aucun tag prerelease. + +## 2. Corrections Transport + +`ws_settings.rs` et `ws_lifecycle.rs` sont alignés avec les lints workspace stricts : + +- les helpers `as_str()` utilisent un `return match` explicite ; +- les propagations d'erreur n'utilisent plus l'opérateur `?`, interdit par `clippy::question_mark_used` ; +- les constructeurs crate-internal de snapshots, encore réservés aux tests dans `pre.002`, sont compilés uniquement sous `cfg(test)` afin de supprimer les warnings `dead_code` avant leur consommation runtime future. + +Aucun contrat public, default WebSocket, logging target ou règle de redaction n'est modifié. Le tracing reste exclusivement émis via `ksp-logging-lib` avec `TRACING_TARGET`. + +## 3. Canaris desktop packagés + +Le test Config Desk demandé : + +```text +pre_018_packaged_runtime_bundles_config_resources_and_activates_shared_writable_root +``` + +ne compare plus `tauri.conf.json` et `package.json` à chaque valeur de `CARGO_PKG_VERSION`. Cette égalité rendait le test faux dès l'ouverture d'une prerelease workspace alors que les ressources desktop packagées restaient volontairement sur la version stable `0.2.6`. + +Le canari Wallet Desk analogue est corrigé dans la même tranche afin que le workspace ne rencontre pas le même faux négatif immédiatement après Config Desk. Il vérifie en plus que le HTML embarque la version packagée déclarée, et non la version Cargo prerelease courante. + +Les deux canaris conservent désormais les garanties utiles : + +```text +version tauri >= 0.2.6 +version package >= 0.2.6 +version tauri == version package +``` + +Le cœur SemVer est comparé sur `major.minor.patch`; un suffixe prerelease éventuel ne change pas le floor. Les assertions de resources packagées et de writable runtime root restent inchangées. + +## 4. Validation attendue + +```bash +cargo fmt --all +python3 scripts/audit_rust_workspace_rules.py +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test -p ksp-onchain-transport-lib +cargo test -p ksp-app-config-desk --test desktop_contract +cargo test -p ksp-app-wallet-desk --test desktop_contract +cargo test --workspace +``` + +Le smoke Devnet reste opt-in et n'est pas requis pour ce correctif.