v0.2.7-pre.002-fix.001

This commit is contained in:
2026-08-22 16:49:43 +02:00
parent b64a799c85
commit b6908cb573
5 changed files with 209 additions and 31 deletions

View File

@@ -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::<u64>().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::<u64>().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::<u64>().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 {

View File

@@ -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::<u64>().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::<u64>().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::<u64>().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"));

View File

@@ -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,

View File

@@ -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(),

View File

@@ -0,0 +1,63 @@
<!-- file: deltas/0.2.7/pre.002-fix.001.md -->
<!-- version: 1 -->
# 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.