diff --git a/Cargo.toml b/Cargo.toml index 82cf73f..033d238 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 182 +# version: 183 [workspace] resolver = "3" members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"] [workspace.package] -version = "0.2.6-pre.12" +version = "0.2.6-pre.13" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-app-wallet-desk/Cargo.toml b/crates/ksp-app-wallet-desk/Cargo.toml index cb3d4d2..74c0905 100644 --- a/crates/ksp-app-wallet-desk/Cargo.toml +++ b/crates/ksp-app-wallet-desk/Cargo.toml @@ -1,5 +1,5 @@ # file: crates/ksp-app-wallet-desk/Cargo.toml -# version: 4 +# version: 5 [package] name = "ksp-app-wallet-desk" @@ -33,7 +33,7 @@ serde = { workspace = true, features = ["derive"] } tauri.workspace = true tauri-plugin-dialog.workspace = true tauri-plugin-tracing.workspace = true -tokio = { workspace = true, features = ["fs", "io-util", "rt", "time"] } +tokio = { workspace = true, features = ["fs", "io-util", "macros", "rt", "time"] } ts-rs.workspace = true zeroize.workspace = true diff --git a/crates/ksp-app-wallet-desk/frontend/main.html b/crates/ksp-app-wallet-desk/frontend/main.html index 722330f..7313e97 100644 --- a/crates/ksp-app-wallet-desk/frontend/main.html +++ b/crates/ksp-app-wallet-desk/frontend/main.html @@ -42,7 +42,7 @@

Dashboard

Identité autorisée et balance SOL via le Transport HTTP composite sont actives.

- 0.2.6-pre.012 + 0.2.6-pre.013
@@ -480,7 +480,7 @@
diff --git a/crates/ksp-app-wallet-desk/frontend/ts/main.ts b/crates/ksp-app-wallet-desk/frontend/ts/main.ts index cc39bfa..3d5e03f 100644 --- a/crates/ksp-app-wallet-desk/frontend/ts/main.ts +++ b/crates/ksp-app-wallet-desk/frontend/ts/main.ts @@ -1,5 +1,5 @@ // file: crates/ksp-app-wallet-desk/frontend/ts/main.ts -// version: 13 +// version: 14 import { Modal } from "bootstrap"; import DataTable from "datatables.net-bs5"; @@ -190,7 +190,7 @@ function initializeWalletTable(): void { zeroRecords: "Aucun wallet correspondant.", }, }); - frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.012-owner-transfer-export" }); + frontendDebug("main", "Wallet inventory DataTable initialized", { phase: "pre.013-integration-compliance" }); } function renderWalletInventory(entries: WalletInventoryEntryDto[]): void { diff --git a/crates/ksp-app-wallet-desk/package.json b/crates/ksp-app-wallet-desk/package.json index 9da86e7..d41b347 100644 --- a/crates/ksp-app-wallet-desk/package.json +++ b/crates/ksp-app-wallet-desk/package.json @@ -1,7 +1,7 @@ { "name": "ksp-app-wallet-desk", "private": true, - "version": "0.2.6-pre.12", + "version": "0.2.6-pre.13", "type": "module", "scripts": { "dev": "vite", diff --git a/crates/ksp-app-wallet-desk/src/app_state.rs b/crates/ksp-app-wallet-desk/src/app_state.rs index af25992..6875d62 100644 --- a/crates/ksp-app-wallet-desk/src/app_state.rs +++ b/crates/ksp-app-wallet-desk/src/app_state.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-wallet-desk/src/app_state.rs -// version: 16 +// version: 17 //! Shared backend state owned by the Wallet Desk Tauri application. @@ -126,7 +126,7 @@ impl AppState { effective_wallets_directory_created_on_startup: self.wallet_config_startup.effective_directory_created_on_startup(), fallback_logging_active: runtime.fallback_active, root_wallets_directory_created_on_startup: self.wallet_config_startup.root_directory_created_on_startup(), - shell_phase: "pre.012-owner-transfer-export".to_owned(), + shell_phase: "pre.013-integration-compliance".to_owned(), startup_diagnostic: runtime.startup_diagnostic.clone(), transport_available_endpoint_count, transport_clusters: self.transport_runtime.clusters(), diff --git a/crates/ksp-app-wallet-desk/tauri.conf.json b/crates/ksp-app-wallet-desk/tauri.conf.json index 6513048..620b1f4 100644 --- a/crates/ksp-app-wallet-desk/tauri.conf.json +++ b/crates/ksp-app-wallet-desk/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "KSP Wallet Desk", - "version": "0.2.6-pre.12", + "version": "0.2.6-pre.13", "identifier": "com.sasedev.ksp-app-wallet-desk", "build": { "beforeDevCommand": "npm run dev", diff --git a/crates/ksp-app-wallet-desk/tests/desktop_contract.rs b/crates/ksp-app-wallet-desk/tests/desktop_contract.rs index 989ae4c..5c0796d 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: 13 +// version: 14 //! Desktop build, shell and Config-status contract audits for Wallet Desk. @@ -329,5 +329,21 @@ fn pre_012_owner_export_uses_rust_native_save_picker_and_wallet_file_adapter() { assert!(html.contains("id=\"exportWalletKeypairModal\"")); assert!(main.contains("bindWalletExportActions")); assert!(main.contains("export_wallet_transfer")); - assert!(main.contains("pre.012-owner-transfer-export")); +} + +#[test] +fn pre_013_integration_compliance_and_devnet_smoke_are_durable_release_gates() { + let root = app_root(); + let state = read_text(root.join("src/app_state.rs").as_path()); + let main = read_text(root.join("frontend/ts/main.ts").as_path()); + let compliance = read_text(root.join("tests/release_compliance.rs").as_path()); + let smoke = read_text(root.join("tests/wallet_desk_devnet_smoke.rs").as_path()); + let validation = read_text(root.join("../../docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md").as_path()); + assert!(state.contains("pre.013-integration-compliance")); + assert!(main.contains("pre.013-integration-compliance")); + assert!(compliance.contains("wallet_desk_dependency_firewall_and_tauri_capabilities_are_minimal")); + assert!(compliance.contains("frontend_has_no_direct_filesystem_network_or_secret_persistence_surface")); + assert!(smoke.contains("committed_wallet_desk_composite_creates_wallet_and_reaches_devnet_balance")); + assert!(smoke.contains("#[ignore")); + assert!(validation.contains("Config -> Wallet -> Transport -> Devnet getBalance")); } diff --git a/crates/ksp-app-wallet-desk/tests/release_compliance.rs b/crates/ksp-app-wallet-desk/tests/release_compliance.rs new file mode 100644 index 0000000..ad169c1 --- /dev/null +++ b/crates/ksp-app-wallet-desk/tests/release_compliance.rs @@ -0,0 +1,181 @@ +// file: crates/ksp-app-wallet-desk/tests/release_compliance.rs +// version: 1 + +//! Release-wide deterministic compliance canaries for Wallet Desk. + +fn app_root() -> std::path::PathBuf { + return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); +} + +fn read_text(path: &std::path::Path) -> String { + let source = std::fs::read_to_string(path); + assert!(source.is_ok(), "unable to read {}", path.display()); + return match source { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => String::new(), + }; +} + +fn read_rust_source_tree(root: &std::path::Path) -> String { + let entries = std::fs::read_dir(root); + assert!(entries.is_ok(), "unable to enumerate {}", root.display()); + let entries = match entries { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return String::new(), + }; + let mut paths = std::vec::Vec::new(); + for entry in entries { + let entry = match entry { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => continue, + }; + let path = entry.path(); + if path.extension().and_then(std::ffi::OsStr::to_str) == std::option::Option::Some("rs") { + paths.push(path); + } + } + paths.sort(); + let mut combined = String::new(); + for path in paths { + combined.push_str(read_text(path.as_path()).as_str()); + combined.push('\n'); + } + return combined; +} + +fn struct_field_block<'a>(source: &'a str, name: &str) -> &'a str { + let marker = std::format!("struct {name}"); + let start = source.find(marker.as_str()); + assert!(start.is_some(), "missing struct {name}"); + let start = match start { + std::option::Option::Some(value) => value, + std::option::Option::None => return "", + }; + let tail = &source[start..]; + return tail.split("}\n").next().unwrap_or_default(); +} + +#[test] +fn backend_keeps_config_wallet_transport_and_logging_ownership_boundaries() { + let root = app_root(); + let source = read_rust_source_tree(root.join("src").as_path()); + for forbidden in [ + "std::env::var(", + "std::env::var_os(", + "std::env::vars(", + "reqwest::", + "solana_keypair::", + "solana_pubkey::", + "solana_signer::", + "solana_signature::", + ] { + assert!(!source.contains(forbidden), "Wallet Desk backend bypasses an owned boundary through {forbidden}"); + } + for line in source.lines() { + let trimmed = line.trim_start(); + assert!(!trimmed.starts_with("use tracing::"), "Wallet Desk backend must not import tracing directly: {trimmed}"); + assert!(!trimmed.starts_with("tracing::"), "Wallet Desk backend must not emit through tracing directly: {trimmed}"); + } + assert!(source.contains("ksp_config_lib::ConfigEnvironment::load")); + assert!(source.contains("ksp_wallet_lib::")); + assert!(source.contains("ksp_onchain_transport_lib::")); + assert!(source.contains("ksp_logging_lib::")); +} + +#[test] +fn frontend_has_no_direct_filesystem_network_or_secret_persistence_surface() { + let root = app_root(); + let package = read_text(root.join("package.json").as_path()); + let main = read_text(root.join("frontend/ts/main.ts").as_path()); + let invoke = read_text(root.join("frontend/ts/invoke.ts").as_path()); + let frontend_log = read_text(root.join("frontend/ts/frontend_log.ts").as_path()); + let splash = read_text(root.join("frontend/ts/splash.ts").as_path()); + let html = read_text(root.join("frontend/main.html").as_path()); + let combined = std::format!("{main}\n{invoke}\n{frontend_log}\n{splash}\n{html}"); + for forbidden in [ + "@tauri-apps/plugin-dialog", + "@tauri-apps/plugin-fs", + "navigator.clipboard", + "localStorage", + "sessionStorage", + "window.alert", + "window.confirm", + "window.prompt", + "XMLHttpRequest", + "new WebSocket", + "EventSource", + "fetch(", + "KSP_SECRET_WALLET_PASS_", + "sourcePath", + "source_path", + "destinationPath", + "destination_path", + ] { + assert!(!combined.contains(forbidden), "frontend contains forbidden direct surface {forbidden}"); + } + assert!(!package.contains("@tauri-apps/plugin-dialog")); + assert!(!package.contains("@tauri-apps/plugin-fs")); +} + +#[test] +fn response_dtos_keep_secret_key_material_out_of_ipc() { + let root = app_root(); + let session = read_text(root.join("src/wallet_session.rs").as_path()); + let balance = read_text(root.join("src/wallet_balance.rs").as_path()); + let import = read_text(root.join("src/wallet_import.rs").as_path()); + let export = read_text(root.join("src/wallet_export.rs").as_path()); + let security = read_text(root.join("src/wallet_security.rs").as_path()); + let common = read_text(root.join("src/dto_common.rs").as_path()); + for (source, name) in [ + (session.as_str(), "WalletAuthorizedDto"), + (session.as_str(), "WalletSessionDto"), + (balance.as_str(), "WalletBalanceDto"), + (import.as_str(), "WalletTransferInspectionDto"), + (export.as_str(), "WalletExportResultDto"), + (security.as_str(), "WalletViewSecurityStatusDto"), + (common.as_str(), "RuntimeStatusDto"), + ] { + let fields = struct_field_block(source, name); + for forbidden in ["password", "secret_key", "private_key", "keypair_bytes", "ciphertext"] { + assert!(!fields.contains(forbidden), "response DTO {name} contains forbidden field material {forbidden}"); + } + } + let inspection = struct_field_block(import.as_str(), "WalletTransferInspectionDto"); + let export_result = struct_field_block(export.as_str(), "WalletExportResultDto"); + assert!(!inspection.contains("path")); + assert!(!export_result.contains("path")); + assert!(!export_result.contains("content")); + assert!(!export_result.contains("bytes")); +} + +#[test] +fn wallet_desk_dependency_firewall_and_tauri_capabilities_are_minimal() { + let root = app_root(); + let manifest = read_text(root.join("Cargo.toml").as_path()); + for line in manifest.lines() { + let trimmed = line.trim_start(); + assert!(!trimmed.starts_with("solana-"), "Wallet Desk must consume Solana behavior through KSP crates: {trimmed}"); + assert!(!trimmed.starts_with("reqwest "), "Wallet Desk must consume HTTP through Transport: {trimmed}"); + assert!(!trimmed.starts_with("tracing "), "Wallet Desk must consume logging through the KSP facade/Tauri adapter: {trimmed}"); + } + let capability_source = read_text(root.join("capabilities/default.json").as_path()); + let capability = serde_json::from_str::(capability_source.as_str()); + assert!(capability.is_ok(), "Wallet Desk capability JSON should parse"); + let capability = match capability { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let permissions = capability.get("permissions").and_then(serde_json::Value::as_array); + assert!(permissions.is_some()); + let permissions = match permissions { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + assert_eq!(permissions.len(), 2); + assert_eq!(permissions[0].as_str(), std::option::Option::Some("core:default")); + assert_eq!(permissions[1].as_str(), std::option::Option::Some("tracing:default")); + let tauri = read_text(root.join("tauri.conf.json").as_path()); + assert!(tauri.contains("\"csp\": null")); + assert!(!capability_source.contains("dialog:")); + assert!(!capability_source.contains("fs:")); +} diff --git a/crates/ksp-app-wallet-desk/tests/wallet_desk_devnet_smoke.rs b/crates/ksp-app-wallet-desk/tests/wallet_desk_devnet_smoke.rs new file mode 100644 index 0000000..d35b05a --- /dev/null +++ b/crates/ksp-app-wallet-desk/tests/wallet_desk_devnet_smoke.rs @@ -0,0 +1,143 @@ +// file: crates/ksp-app-wallet-desk/tests/wallet_desk_devnet_smoke.rs +// version: 1 + +//! Opt-in live composition smoke owned by the Wallet Desk orchestration surface. + +fn workspace_engine() -> ksp_core_lib::Result { + let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../.."); + let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas")); + let bootstrap = match bootstrap { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let registry = ksp_config_lib::ConfigFileRegistry::defaults(); + return match registry { + std::result::Result::Ok(value) => std::result::Result::Ok(ksp_config_lib::ConfigDocumentEngine::new(bootstrap, value)), + std::result::Result::Err(error) => std::result::Result::Err(error), + }; +} + +#[tokio::test(flavor = "current_thread")] +#[ignore = "opt-in live Wallet Desk Devnet smoke; creates one temporary .kspwallet under the Config-selected root and performs an external getBalance request"] +async fn committed_wallet_desk_composite_creates_wallet_and_reaches_devnet_balance() { + let engine = workspace_engine(); + assert!(engine.is_ok(), "workspace Config engine should be constructible"); + let engine = match engine { + 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_WALLET_DESK); + assert!(file_id.is_ok(), "Wallet Desk composite file_id should be valid"); + let file_id = match file_id { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let composite = engine.load_resolved_composite(&file_id, std::option::Option::None); + assert!(composite.is_ok(), "committed Wallet Desk composite should resolve"); + let composite = match composite { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + assert_eq!(composite.profile_id(), "devnet"); + let environment = ksp_config_lib::ConfigEnvironment::load(); + assert!(environment.is_ok(), "Config-owned environment snapshot should load"); + let environment = match environment { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let wallet_component = composite.component("wallet"); + assert!(wallet_component.is_some(), "Wallet Desk composite should contain Wallet"); + let wallet_component = match wallet_component { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + let wallet_config = engine.resolve_wallet_config_profile(wallet_component.resolved(), &environment); + assert!(wallet_config.is_ok(), "composite-selected Wallet profile should map"); + let wallet_config = match wallet_config { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + assert_eq!(wallet_config.selection_source(), ksp_config_lib::ConfigProfileSelectionSource::Composite); + let wallet_root = wallet_config.effective_wallets_directory().to_path_buf(); + assert!(!wallet_root.as_os_str().is_empty()); + let directory = tokio::fs::create_dir_all(wallet_root.as_path()).await; + assert!(directory.is_ok(), "Config-selected Wallet smoke directory should be creatable"); + if directory.is_err() { + return; + } + let stamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH); + assert!(stamp.is_ok(), "system clock should be after Unix epoch"); + let stamp = match stamp { + std::result::Result::Ok(value) => value.as_nanos(), + std::result::Result::Err(_) => return, + }; + let wallet_path = wallet_root.join(std::format!("wallet-desk-pre013-smoke-{}-{stamp}.kspwallet", std::process::id())); + let owner = ksp_wallet_lib::create_wallet_file_v1( + wallet_path.as_path(), + ksp_wallet_lib::OwnerPassword::new("ksp-pre013-devnet-owner".to_owned()), + std::option::Option::Some(ksp_wallet_lib::ViewPassword::new("ksp-pre013-devnet-view".to_owned())), + ksp_wallet_lib::WalletCreateMetadataV1::new(std::option::Option::Some("pre013-devnet-smoke".to_owned()), std::vec::Vec::new()), + ) + .await; + assert!(owner.is_ok(), "Wallet smoke file should be created through the public Wallet API"); + let owner = match owner { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let locked = ksp_wallet_lib::inspect_locked_wallet_file_v1(wallet_path.as_path()).await; + assert!(locked.is_ok(), "created smoke wallet should inspect while locked"); + if let std::result::Result::Ok(locked) = locked { + assert_eq!(locked.format_version(), ksp_wallet_lib::KSPWALLET_FORMAT_VERSION_V1); + assert!(locked.view_enabled()); + } + let view = ksp_wallet_lib::open_wallet_view_file_v1(wallet_path.as_path(), ksp_wallet_lib::ViewPassword::new("ksp-pre013-devnet-view".to_owned())).await; + assert!(view.is_ok(), "created smoke wallet should open with VIEW"); + let view = match view { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => { + let _ = tokio::fs::remove_file(wallet_path.as_path()).await; + return; + }, + }; + assert_eq!(owner.pubkey(), view.pubkey()); + let transport_component = composite.component("transport"); + assert!(transport_component.is_some(), "Wallet Desk composite should contain Transport"); + let transport_component = match transport_component { + std::option::Option::Some(value) => value, + std::option::Option::None => { + let _ = tokio::fs::remove_file(wallet_path.as_path()).await; + return; + }, + }; + let transport = engine.resolve_transport_config_profile(transport_component.resolved(), &environment); + assert!(transport.is_ok(), "composite-selected Transport profile should map"); + let transport = match transport { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => { + let _ = tokio::fs::remove_file(wallet_path.as_path()).await; + return; + }, + }; + assert_eq!(transport.selection_source(), ksp_config_lib::ConfigProfileSelectionSource::Composite); + let pool = ksp_onchain_transport_lib::HttpTransportPool::new(transport.into_settings()); + assert!(pool.is_ok(), "composite-selected Transport should construct the HTTP pool"); + let pool = match pool { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => { + let _ = tokio::fs::remove_file(wallet_path.as_path()).await; + return; + }, + }; + let role = ksp_onchain_transport_lib::HttpRoleName::new("default"); + let config = ksp_onchain_transport_lib::GetBalanceConfig::new( + std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed), + std::option::Option::None, + ); + let balance = pool.get_balance(&role, view.pubkey(), std::option::Option::Some(&config)).await; + let cleanup = tokio::fs::remove_file(wallet_path.as_path()).await; + assert!(cleanup.is_ok(), "Wallet Desk Devnet smoke wallet should be removed after the request"); + assert!(balance.is_ok(), "Wallet Desk Config -> Wallet -> Transport getBalance smoke should succeed"); + if let std::result::Result::Ok(balance) = balance { + assert!(balance.context().slot() > 0); + } +} diff --git a/crates/ksp-app-wallet-desk/unit_tests/wallet_inventory.rs b/crates/ksp-app-wallet-desk/unit_tests/wallet_inventory.rs index 8bda4bc..9c05d27 100644 --- a/crates/ksp-app-wallet-desk/unit_tests/wallet_inventory.rs +++ b/crates/ksp-app-wallet-desk/unit_tests/wallet_inventory.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-wallet-desk/unit_tests/wallet_inventory.rs -// version: 1 +// version: 2 const VALID_WALLET: &[u8] = include_bytes!("../../ksp-wallet-lib/tests/fixtures/kspwallet_v1_full_vector.json"); @@ -13,7 +13,11 @@ fn inventory_lists_regular_wallets_reports_invalid_candidates_and_ignores_other_ assert!(std::fs::write(root.join("beta.kspwallet"), VALID_WALLET).is_ok()); assert!(std::fs::write(root.join("alpha.kspwallet"), b"not-json").is_ok()); assert!(std::fs::write(root.join("ignored.txt"), VALID_WALLET).is_ok()); + assert!(std::fs::write(root.join("upper.KSPWALLET"), VALID_WALLET).is_ok()); + assert!(std::fs::write(root.join("backup.kspwallet.bak"), VALID_WALLET).is_ok()); assert!(std::fs::create_dir(root.join("directory.kspwallet")).is_ok()); + assert!(std::fs::create_dir(root.join("nested")).is_ok()); + assert!(std::fs::write(root.join("nested/hidden.kspwallet"), VALID_WALLET).is_ok()); let runtime = tokio::runtime::Builder::new_current_thread().build(); assert!(runtime.is_ok(), "test runtime should be constructible: {runtime:?}"); if let std::result::Result::Ok(runtime) = runtime { @@ -60,6 +64,14 @@ fn selection_rejects_traversal_and_reinspects_one_valid_locked_wallet() { if let std::result::Result::Err(error) = rejected_backslash { assert_eq!(error.code(), crate::ERROR_CODE_WALLET_SELECTION_INVALID); } + for wallet_id in ["", "/selected.kspwallet", "nested/selected.kspwallet", "selected.kspwallet.bak"] { + let rejected = + runtime.block_on(crate::select_locked_wallet(root.as_path(), crate::WalletSelectionRequestDto { wallet_id: wallet_id.to_owned() })); + assert!(rejected.is_err(), "invalid wallet selection should be rejected: {wallet_id}"); + if let std::result::Result::Err(error) = rejected { + assert_eq!(error.code(), crate::ERROR_CODE_WALLET_SELECTION_INVALID); + } + } let selected = runtime.block_on(crate::select_locked_wallet(root.as_path(), crate::WalletSelectionRequestDto { wallet_id: "selected.kspwallet".to_owned() })); assert!(selected.is_ok(), "valid locked Wallet should be selectable: {selected:?}"); diff --git a/deltas/0.2.6/pre.013.md b/deltas/0.2.6/pre.013.md new file mode 100644 index 0000000..8d71325 --- /dev/null +++ b/deltas/0.2.6/pre.013.md @@ -0,0 +1,65 @@ + + + +# Delta `0.2.6-pre.013` — intégration / compliance / sécurité / smoke + +## Objet + +Consolider les preuves transversales de Wallet Desk avant le polish visuel et la documentation finale : frontières Config/Wallet/Transport, inventory/path adversarial, DTO/IPC secrets, capabilities Tauri, dependency firewall et smoke Devnet de composition réel. + +## Changements + +- `workspace.package.version` passe à `0.2.6-pre.13` ; +- ajout de `tests/release_compliance.rs` : + - interdiction des bypass `std::env`, `reqwest`, Solana crates et `tracing` direct côté backend ; + - audit frontend sans filesystem/dialog guest, réseau direct, storage/clipboard secret ou noms `KSP_SECRET_WALLET_PASS_*` ; + - audit global des DTO de réponse contre password/key material/ciphertext ; + - capability Tauri limitée à `core:default` + `tracing:default` ; + - firewall du manifest Wallet Desk contre dépendances `solana-*`, `reqwest` et `tracing` directes ; +- renforcement des tests inventory/path : + - inventory non récursif ; + - suffixe `.kspwallet` exact ; + - casse/backup/nested/directory ignorés ; + - traversal, absolute, nested, suffixe invalide et identifiant vide rejetés à la sélection ; +- ajout du smoke `tests/wallet_desk_devnet_smoke.rs`, opt-in/ignored : + - charge le composite Wallet Desk et `ConfigEnvironment` ; + - résout Wallet et Transport en provenance `Composite` ; + - crée un `.kspwallet` réel VIEW+OWNER sous le répertoire Config effectif ; + - inspecte locked puis ouvre VIEW ; + - exécute `getBalance(confirmed)` via `HttpTransportPool` ; + - supprime le wallet canari après la requête ; +- ajout de `tokio/macros` aux features locales Wallet Desk pour le test async opt-in ; +- création de `docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md` et indexation dans `docs/validation/000-README.md` ; +- correction des index `docs/000-README.md` et `docs/plans/000-README.md` pour refléter la séquence désormais réelle `pre.014` polish puis `pre.015` clôture ; +- `shell_phase`, badge et footer réalignés sur `pre.013-integration-compliance` ; +- `ROADMAP.md`, `CHANGELOG.md` et `docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md` restent inchangés. + +## Sécurité / architecture + +Le smoke cross-crates vit dans Wallet Desk, surface d'orchestration légitime, et non dans Config ou Transport. Le test ne lit aucune variable KSP directement : `KSP_WALLETS_DIRECTORY` est consommée par `ConfigEnvironment` conformément à l'ownership Config. + +Aucun secret supplémentaire ne traverse IPC. Aucune permission Tauri Dialog/FS n'est accordée au frontend. Aucun accès RPC n'est ajouté au frontend. + +## Validation opérateur + +```bash +cargo fmt --all +python3 scripts/audit_rust_workspace_rules.py +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test -p ksp-app-wallet-desk +cargo test --workspace + +cargo tree -p ksp-app-wallet-desk --depth 1 +cargo tree -p ksp-app-wallet-desk -d +cargo tree -i ksp-wallet-lib +cargo tree -i ksp-onchain-transport-lib +cargo tree -i tauri-plugin-dialog + +KSP_WALLETS_DIRECTORY=var/wallet-desk-pre013-smoke \ + cargo test -p ksp-app-wallet-desk \ + --test wallet_desk_devnet_smoke \ + -- --ignored --nocapture +``` + +`cargo tauri dev` n'est pas obligatoire pour cette tranche si le runtime `pre.012` déjà communiqué reste la dernière preuve UI, car `pre.013` ne change aucune interaction métier ; il reste acceptable comme contrôle supplémentaire. Aucun `cargo tauri build` avant `pre.015`. diff --git a/docs/000-README.md b/docs/000-README.md index f24f5f1..9033647 100644 --- a/docs/000-README.md +++ b/docs/000-README.md @@ -1,5 +1,5 @@ - + # Documentation KSP @@ -74,7 +74,7 @@ D'autres sous-répertoires seront ajoutés uniquement lorsque leur rôle aura é ## Documents de planification -Le plan historique de la phase fondatrice clôturée est conservé dans [`plans/001-V0_0_3_PLAN.md`](plans/001-V0_0_3_PLAN.md). La séquence active des premières releases fonctionnelles est définie dans [`plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md`](plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md). Le plan détaillé de la release stable `0.1.1` est conservé comme historique clôturé dans [`plans/003-V0_1_1_CORE_FOUNDATION_PLAN.md`](plans/003-V0_1_1_CORE_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.2` est conservé comme historique clôturé dans [`plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md`](plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.3 — Configuration foundation` est conservé comme historique clôturé dans [`plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.4 — ksp-app-config-desk` est conservé comme historique clôturé dans [`plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md`](plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md), avec sa matrice finale [`validation/001-V0_1_4_CONFIG_DESKTOP.md`](validation/001-V0_1_4_CONFIG_DESKTOP.md). Son prompt d'ouverture historique reste [`../prompts/004-V0_1_4_START_PROMPT.md`](../prompts/004-V0_1_4_START_PROMPT.md). La release stable `0.2.0` clôt l'audit de bot3 et le découpage de la série. Son plan directeur est conservé comme historique clôturé dans [`plans/007-V0_2_0_SERIES_PLANNING.md`](plans/007-V0_2_0_SERIES_PLANNING.md), avec sa matrice finale [`validation/002-V0_2_0_SERIES_PLANNING.md`](validation/002-V0_2_0_SERIES_PLANNING.md). La release stable `0.2.1 — HTTP Solana foundation` a été ouverte par [`../prompts/006-V0_2_1_START_PROMPT.md`](../prompts/006-V0_2_1_START_PROMPT.md). Son gate de sizing et sa matrice exhaustive sont conservés dans [`plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md`](plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md), avec la validation finale [`validation/003-V0_2_1_ONCHAIN_HTTP.md`](validation/003-V0_2_1_ONCHAIN_HTTP.md), README/USAGE Transport et le smoke Devnet opt-in de composition Config -> Transport. Le prompt [`../prompts/007-V0_2_2_START_PROMPT.md`](../prompts/007-V0_2_2_START_PROMPT.md) a ouvert la release stable `0.2.2 — HTTP Accounts + Tokens + Cluster`. Son plan clôturé [`plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md`](plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md) conserve l'audit et l'implémentation des 22 wrappers typés, tandis que [`validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md`](validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md) enregistre les validations déterministes, les graphes Cargo et les deux smokes Devnet passés avant publication. Le prompt [`../prompts/008-V0_2_3_START_PROMPT.md`](../prompts/008-V0_2_3_START_PROMPT.md) a ouvert la release stable `0.2.3 — HTTP Transactions`. Son plan clôturé [`plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) conserve l'audit et l'implémentation des 11 wrappers ; le réaudit [`validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md`](validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md) confirme la complétude des 37 wrappers HTTP typés et [`validation/006-V0_2_3_HTTP_TRANSACTIONS.md`](validation/006-V0_2_3_HTTP_TRANSACTIONS.md) enregistre les validations finales, graphes Cargo et deux smokes Devnet passés avant publication. Le prompt [`../prompts/009-V0_2_4_START_PROMPT.md`](../prompts/009-V0_2_4_START_PROMPT.md) a ouvert la release stable `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale`. Son plan clôturé [`plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`](plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md) conserve l’implémentation des 15 wrappers et la compliance `52/52 + 14/14`; la matrice finale [`validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md`](validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md) enregistre le réaudit SIMD/inventaire, les canaries globales et les preuves opérateur avant publication. Le prompt [`../prompts/010-V0_2_5_START_PROMPT.md`](../prompts/010-V0_2_5_START_PROMPT.md), finalisé par `0.2.4-pre.009-fix.001`, ouvre `0.2.5 — Wallet foundation` sur la base stable `v0.2.4`. Son plan historique clôturé [`plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md`](plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md) part du gate `pre.001` (héritage, threat model offline, VIEW/OWNER indépendants et niveau B read-only), puis matérialise la crate en `pre.002`, le wire/transcript en `pre.003`, les primitives Argon2id/XChaCha20-Poly1305 en `pre.004`, les payloads/create/open en `pre.005`, la persistence en `pre.006`, l'administration/signature en `pre.007` et les adapters transfer en `pre.008`. `pre.009` ferme l'audit adversarial/interoperability/compliance dans [`validation/008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md`](validation/008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md) avant la documentation finale `pre.010` ; `pre.010` finalise [`../crates/ksp-wallet-lib/README.md`](../crates/ksp-wallet-lib/README.md), [`../crates/ksp-wallet-lib/USAGE.md`](../crates/ksp-wallet-lib/USAGE.md), la spec, les graphes et la matrice ; `pre.010-fix.001`–`fix.003` ferment ensuite la mise à niveau Dalek et la normalisation Rust/audit structurel. `0.2.5-rel.001` publie la release stable et [`../prompts/011-V0_2_6_START_PROMPT.md`](../prompts/011-V0_2_6_START_PROMPT.md) ouvre `0.2.6 — Wallet Desk`. Le gate `0.2.6-pre.001` est matérialisé dans [`plans/013-V0_2_6_WALLET_DESK_PLAN.md`](plans/013-V0_2_6_WALLET_DESK_PLAN.md) : il réaudite Config Desk et les APIs finales, retient le gabarit Bootstrap/Font Awesome/DataTables/Select/SimpleBar/resize-observer-polyfill, fixe `std.wallet` avec root global + sous-répertoire de profil, création automatique des répertoires, secrets `KSP_SECRET_WALLET_PASS_*` via Config, inventory/path safety, lifecycle VIEW/OWNER, screen/command/DTO maps et balance HTTP. Le forecast souple est détaillé jusqu’à `pre.014`, dernière tranche prévue pour README/USAGE/docs/validation, prompt `0.2.7` et build Tauri final. `0.2.6-pre.002` matérialise la crate `ksp-app-wallet-desk` et son shell Tauri splash/main avec Config + Logging bootstrap, ports `1432/1433`, Bootstrap, Font Awesome, DataTables/Select, SimpleBar, `resize-observer-polyfill`, TS-RS et bridge frontend Logging ; `0.2.6-pre.003` ajoute `cfg.std.wallet`/`schema.std.wallet`, le composite concret Wallet Desk, les adapters Config capables de préserver une sélection composite, les profils Wallet `default`/`temporary`/`tests` et la préparation automatique du répertoire global/effectif par l’application. L’inventory `.kspwallet` reste réservé à `pre.004`. +Le plan historique de la phase fondatrice clôturée est conservé dans [`plans/001-V0_0_3_PLAN.md`](plans/001-V0_0_3_PLAN.md). La séquence active des premières releases fonctionnelles est définie dans [`plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md`](plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md). Le plan détaillé de la release stable `0.1.1` est conservé comme historique clôturé dans [`plans/003-V0_1_1_CORE_FOUNDATION_PLAN.md`](plans/003-V0_1_1_CORE_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.2` est conservé comme historique clôturé dans [`plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md`](plans/004-V0_1_2_LOGGING_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.3 — Configuration foundation` est conservé comme historique clôturé dans [`plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md). Le plan détaillé de la release stable `0.1.4 — ksp-app-config-desk` est conservé comme historique clôturé dans [`plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md`](plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md), avec sa matrice finale [`validation/001-V0_1_4_CONFIG_DESKTOP.md`](validation/001-V0_1_4_CONFIG_DESKTOP.md). Son prompt d'ouverture historique reste [`../prompts/004-V0_1_4_START_PROMPT.md`](../prompts/004-V0_1_4_START_PROMPT.md). La release stable `0.2.0` clôt l'audit de bot3 et le découpage de la série. Son plan directeur est conservé comme historique clôturé dans [`plans/007-V0_2_0_SERIES_PLANNING.md`](plans/007-V0_2_0_SERIES_PLANNING.md), avec sa matrice finale [`validation/002-V0_2_0_SERIES_PLANNING.md`](validation/002-V0_2_0_SERIES_PLANNING.md). La release stable `0.2.1 — HTTP Solana foundation` a été ouverte par [`../prompts/006-V0_2_1_START_PROMPT.md`](../prompts/006-V0_2_1_START_PROMPT.md). Son gate de sizing et sa matrice exhaustive sont conservés dans [`plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md`](plans/008-V0_2_1_ONCHAIN_HTTP_PLAN.md), avec la validation finale [`validation/003-V0_2_1_ONCHAIN_HTTP.md`](validation/003-V0_2_1_ONCHAIN_HTTP.md), README/USAGE Transport et le smoke Devnet opt-in de composition Config -> Transport. Le prompt [`../prompts/007-V0_2_2_START_PROMPT.md`](../prompts/007-V0_2_2_START_PROMPT.md) a ouvert la release stable `0.2.2 — HTTP Accounts + Tokens + Cluster`. Son plan clôturé [`plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md`](plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md) conserve l'audit et l'implémentation des 22 wrappers typés, tandis que [`validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md`](validation/004-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER.md) enregistre les validations déterministes, les graphes Cargo et les deux smokes Devnet passés avant publication. Le prompt [`../prompts/008-V0_2_3_START_PROMPT.md`](../prompts/008-V0_2_3_START_PROMPT.md) a ouvert la release stable `0.2.3 — HTTP Transactions`. Son plan clôturé [`plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](plans/010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) conserve l'audit et l'implémentation des 11 wrappers ; le réaudit [`validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md`](validation/005-V0_2_3_KSP_TRANSPORT_007_RETRO_AUDIT.md) confirme la complétude des 37 wrappers HTTP typés et [`validation/006-V0_2_3_HTTP_TRANSACTIONS.md`](validation/006-V0_2_3_HTTP_TRANSACTIONS.md) enregistre les validations finales, graphes Cargo et deux smokes Devnet passés avant publication. Le prompt [`../prompts/009-V0_2_4_START_PROMPT.md`](../prompts/009-V0_2_4_START_PROMPT.md) a ouvert la release stable `0.2.4 — HTTP Blocks + Economics + compliance HTTP finale`. Son plan clôturé [`plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`](plans/011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md) conserve l’implémentation des 15 wrappers et la compliance `52/52 + 14/14`; la matrice finale [`validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md`](validation/007-V0_2_4_HTTP_FINAL_COMPLIANCE.md) enregistre le réaudit SIMD/inventaire, les canaries globales et les preuves opérateur avant publication. Le prompt [`../prompts/010-V0_2_5_START_PROMPT.md`](../prompts/010-V0_2_5_START_PROMPT.md), finalisé par `0.2.4-pre.009-fix.001`, ouvre `0.2.5 — Wallet foundation` sur la base stable `v0.2.4`. Son plan historique clôturé [`plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md`](plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md) part du gate `pre.001` (héritage, threat model offline, VIEW/OWNER indépendants et niveau B read-only), puis matérialise la crate en `pre.002`, le wire/transcript en `pre.003`, les primitives Argon2id/XChaCha20-Poly1305 en `pre.004`, les payloads/create/open en `pre.005`, la persistence en `pre.006`, l'administration/signature en `pre.007` et les adapters transfer en `pre.008`. `pre.009` ferme l'audit adversarial/interoperability/compliance dans [`validation/008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md`](validation/008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md) avant la documentation finale `pre.010` ; `pre.010` finalise [`../crates/ksp-wallet-lib/README.md`](../crates/ksp-wallet-lib/README.md), [`../crates/ksp-wallet-lib/USAGE.md`](../crates/ksp-wallet-lib/USAGE.md), la spec, les graphes et la matrice ; `pre.010-fix.001`–`fix.003` ferment ensuite la mise à niveau Dalek et la normalisation Rust/audit structurel. `0.2.5-rel.001` publie la release stable et [`../prompts/011-V0_2_6_START_PROMPT.md`](../prompts/011-V0_2_6_START_PROMPT.md) ouvre `0.2.6 — Wallet Desk`. Le gate `0.2.6-pre.001` est matérialisé dans [`plans/013-V0_2_6_WALLET_DESK_PLAN.md`](plans/013-V0_2_6_WALLET_DESK_PLAN.md) : il réaudite Config Desk et les APIs finales, retient le gabarit Bootstrap/Font Awesome/DataTables/Select/SimpleBar/resize-observer-polyfill, fixe `std.wallet` avec root global + sous-répertoire de profil, création automatique des répertoires, secrets `KSP_SECRET_WALLET_PASS_*` via Config, inventory/path safety, lifecycle VIEW/OWNER, screen/command/DTO maps et balance HTTP. Le forecast souple est détaillé jusqu’à `pre.015` : `pre.014` est réservé au polish Bootstrap/splashscreen, puis `pre.015` finalise README/USAGE/docs/validation, le prompt `0.2.7` et le build Tauri final. `0.2.6-pre.002` matérialise la crate `ksp-app-wallet-desk` et son shell Tauri splash/main avec Config + Logging bootstrap, ports `1432/1433`, Bootstrap, Font Awesome, DataTables/Select, SimpleBar, `resize-observer-polyfill`, TS-RS et bridge frontend Logging ; `0.2.6-pre.003` ajoute `cfg.std.wallet`/`schema.std.wallet`, le composite concret Wallet Desk, les adapters Config capables de préserver une sélection composite, les profils Wallet `default`/`temporary`/`tests` et la préparation automatique du répertoire global/effectif par l’application. L’inventory `.kspwallet` reste réservé à `pre.004`. ## Spécifications de formats diff --git a/docs/plans/000-README.md b/docs/plans/000-README.md index dc61447..bfc7c05 100644 --- a/docs/plans/000-README.md +++ b/docs/plans/000-README.md @@ -1,5 +1,5 @@ - + # Plans KSP @@ -21,7 +21,7 @@ Un plan décrit le périmètre, les décisions déjà acquises, les questions ou - [`010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md`](010-V0_2_3_HTTP_TRANSACTIONS_PLAN.md) — plan historique clôturé de la release stable `0.2.3 — HTTP Transactions`, ouvert par `pre.001`, exécuté jusqu'à `pre.009` puis publié par `rel.001`; il couvre les 11 méthodes, la classification `8 Read / 2 WriteSubmission / 1 Simulation`, `KSP-TRANSPORT-007`, le no-resend et la préparation de `0.2.4`. - [`011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md`](011-V0_2_4_HTTP_BLOCKS_ECONOMICS_PLAN.md) — plan historique clôturé de la release stable `0.2.4`, ouvert par `pre.001`, exécuté jusqu’à `pre.009`, complété par le fix documentaire Wallet `pre.009-fix.001` puis publié par `rel.001`; il couvre les 10 Blocks + 5 Economics et la compliance finale `52/52 + 14/14` sous `KSP-TRANSPORT-007`. - [`012-V0_2_5_WALLET_FOUNDATION_PLAN.md`](012-V0_2_5_WALLET_FOUNDATION_PLAN.md) — plan historique clôturé de la release stable `0.2.5 — Wallet foundation`, ouvert par `pre.001`, livré jusqu’à `pre.010`, renforcé par `pre.010-fix.001`–`fix.003` pour Dalek 3 et la normalisation Rust/audit structurel, puis publié par `rel.001`; il couvre `.kspwallet` V1, VIEW/OWNER, crypto, persistence, administration, transfer et compliance. -- [`013-V0_2_6_WALLET_DESK_PLAN.md`](013-V0_2_6_WALLET_DESK_PLAN.md) — plan actif de `0.2.6 — Wallet Desk`, ouvert par `pre.001`; `pre.002` matérialise le shell Tauri et `pre.003` la composition `std.wallet`/composite ainsi que la préparation des répertoires. Le plan fixe aussi secrets `KSP_SECRET_WALLET_PASS_*`, inventory DataTables/path safety, lifecycle VIEW/OWNER, screen/command/DTO maps, balance HTTP, administration retenue et forecast souple détaillé jusqu’à `pre.014`, incluant la documentation finale et le prompt `0.2.7`. +- [`013-V0_2_6_WALLET_DESK_PLAN.md`](013-V0_2_6_WALLET_DESK_PLAN.md) — plan actif de `0.2.6 — Wallet Desk`, ouvert par `pre.001`; `pre.002` matérialise le shell Tauri et `pre.003` la composition `std.wallet`/composite ainsi que la préparation des répertoires. Le plan fixe aussi secrets `KSP_SECRET_WALLET_PASS_*`, inventory DataTables/path safety, lifecycle VIEW/OWNER, screen/command/DTO maps, balance HTTP, administration retenue et forecast souple détaillé jusqu’à `pre.015`, avec `pre.014` réservé au polish Bootstrap/splashscreen puis `pre.015` à la documentation finale, au prompt `0.2.7` et au build Tauri final. Le `pre.001` de chaque release fonctionnelle peut introduire son propre plan détaillé lorsque la release s'ouvre. diff --git a/docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md b/docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md index 19270d4..bb8868b 100644 --- a/docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md +++ b/docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md @@ -1,5 +1,5 @@ - + # Plan `0.2.6` — Wallet Desk @@ -1651,21 +1651,24 @@ Le nom de destination et le contenu secret ne sont pas journalisés. Les logs pe ### `pre.013` — intégration, compliance, sécurité et smoke -Objectifs : +Cette tranche consolide les preuves transversales sans ajouter de nouvelle capacité Wallet Desk métier. Elle matérialise : ```text -tests déterministes de composition Config -> Wallet -> Transport -inventory/path adversarial -dto/debug/log secret audit -frontend state purge/security checks -capabilities audit -cargo tree / inverse tree pertinents -Devnet smoke opt-in -validation/009 ou numéro courant dédié à 0.2.6 +tests déterministes de composition et de frontières release-wide +inventory/path adversarial renforcé : non récursif, suffixe exact, traversal/absolute/nested rejetés +audit DTO/IPC : aucun secret, keypair, endpoint URL ou path arbitraire projeté vers le frontend +audit frontend : aucun filesystem/dialog plugin guest, aucun fetch/WebSocket, aucun storage/clipboard secret +audit capabilities : core:default + tracing:default uniquement +audit dependency firewall Wallet Desk : pas de dépendance Solana/protocole directe hors crates KSP +smoke Devnet opt-in Config -> Wallet -> Transport -> getBalance depuis la surface Wallet Desk +matrice docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md +cargo tree / inverse tree opérateur workspace checkpoint complet ``` -Cette tranche doit produire la preuve que les surfaces ajoutées fonctionnent ensemble avant la documentation de clôture. +Le smoke live est volontairement `ignored` dans `cargo test --workspace`. Il doit être déclenché explicitement avec un `KSP_WALLETS_DIRECTORY` dédié ; le test charge ce paramètre uniquement via `ConfigEnvironment`, crée un `.kspwallet` réel dans le répertoire effectif du profil composite, ouvre VIEW, exécute `getBalance` via le Transport composite puis supprime le wallet canari. Ainsi le smoke cross-crates vit dans la surface d'orchestration Wallet Desk et non dans Config ou Transport. + +Cette tranche doit produire la preuve que les surfaces ajoutées fonctionnent ensemble avant le polish visuel `pre.014` et la documentation finale `pre.015`. ### `pre.014` — polish gabarit Bootstrap et splashscreen diff --git a/docs/validation/000-README.md b/docs/validation/000-README.md index 5628240..0abd648 100644 --- a/docs/validation/000-README.md +++ b/docs/validation/000-README.md @@ -1,5 +1,5 @@ - + # Validations KSP @@ -18,3 +18,5 @@ Documents : - [`006-V0_2_3_HTTP_TRANSACTIONS.md`](006-V0_2_3_HTTP_TRANSACTIONS.md) — matrice finale validée de `0.2.3`, 11 wrappers Transactions, sécurité write/simulation, réaudit 52+14, `KSP-TRANSPORT-007` 37/37, graphes Cargo et deux smokes Devnet passés avant publication stable. - [`007-V0_2_4_HTTP_FINAL_COMPLIANCE.md`](007-V0_2_4_HTTP_FINAL_COMPLIANCE.md) — matrice finale validée de `0.2.4`, inventaire exact 52 current + 14 Deprecated, preuve typed 52/52, audit SIMD final, `KSP-TRANSPORT-007`, workspace complet et deux smokes Devnet passés avant publication stable. - [`008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md`](008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md) — matrice finale validée de la release stable `0.2.5`, threat model V1, canaris adversariaux, reproduction externe des vecteurs, audit de frontières, normalisation Rust/audit structurel, graphes Cargo et checkpoint final `pre.010-fix.003` vert. + +- [`009-V0_2_6_WALLET_DESK_COMPLIANCE.md`](009-V0_2_6_WALLET_DESK_COMPLIANCE.md) — matrice de compliance ouverte en `0.2.6-pre.013`, couvrant composition Config/Wallet/Transport, frontières DTO/IPC/frontend, path/inventory adversarial, capabilities Tauri, dependency firewall, graphes Cargo et smoke Devnet Wallet Desk opt-in. diff --git a/docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md b/docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md new file mode 100644 index 0000000..8df7210 --- /dev/null +++ b/docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md @@ -0,0 +1,234 @@ + + + +# Validation `0.2.6` — Wallet Desk integration / compliance / security + +## 1. Objet + +Cette matrice est ouverte par `0.2.6-pre.013` comme preuve durable de composition et de conformité de `ksp-app-wallet-desk`. Elle sera complétée en `pre.015` avec les résultats finaux, le polish visuel de `pre.014`, la documentation de clôture et le dernier build Tauri. + +Elle ne remplace ni le plan [`../plans/013-V0_2_6_WALLET_DESK_PLAN.md`](../plans/013-V0_2_6_WALLET_DESK_PLAN.md), ni la matrice Wallet V1 [`008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md`](008-V0_2_5_WALLET_SECURITY_COMPLIANCE.md), ni les deltas `0.2.6`. + +Le gate `pre.013` porte sur : + +```text +composition Config -> Wallet -> Transport +inventory/path adversarial +DTO / IPC / frontend secret boundaries +capabilities Tauri +KSP dependency ownership +smoke Devnet opt-in Wallet Desk +Cargo tree / inverse tree +workspace checkpoint complet +``` + +## 2. Surface intégrée auditée + +Au démarrage de `pre.013`, Wallet Desk a déjà validé en runtime : + +```text +Config composite logging + wallet + transport +répertoire Wallet effectif Config-owned +inventory locked .kspwallet +create +import Solana CLI JSON / Base58 +unlock manuel et Config-owned VIEW / OWNER +getBalance via Transport +alias / notes OWNER +rotation OWNER +rotation VIEW par OWNER +self-rotation VIEW +strong disable/recreate VIEW +export OWNER Solana CLI JSON / Base58 +no-clobber export +``` + +Le checkpoint opérateur `pre.012` du 21 août 2026 est vert : `cargo fmt`, audit Python, `cargo check`, Clippy sans warning, tests Wallet Desk, tests workspace complets et parcours `cargo tauri dev`. Le runtime confirme aussi l'export des deux formats, le refus `wallet.destination_exists` sans invalidation de la session OWNER et l'annulation du save picker sans effet. + +## 3. Matrice de frontières + +| Frontière | Contrat `0.2.6` | Preuve durable | +|---|---|---| +| Frontend -> Config | aucun accès direct env/.env | `release_compliance::backend_keeps_config_wallet_transport_and_logging_ownership_boundaries` + ownership Config | +| Frontend -> Wallet secret | passwords uniquement request-only ; jamais de keypair en IPC | `desktop_security` + `release_compliance::response_dtos_keep_secret_key_material_out_of_ipc` | +| Frontend -> filesystem | aucun path arbitraire ; picker natif Rust | tests import/export + capability audit | +| Frontend -> réseau Solana | aucun `fetch`/WebSocket/RPC direct | `release_compliance::frontend_has_no_direct_filesystem_network_or_secret_persistence_surface` | +| Wallet Desk -> Solana crates | aucune dépendance protocole directe | `release_compliance::wallet_desk_dependency_firewall_and_tauri_capabilities_are_minimal` | +| Wallet Desk -> HTTP | `ksp-onchain-transport-lib` uniquement | manifest + smoke Devnet | +| Wallet Desk -> Wallet crypto | `ksp-wallet-lib` uniquement | manifest + feature canaries | +| Wallet Desk -> logging | façade `ksp-logging-lib` + adapter `tauri-plugin-tracing` | source audit + workspace logging canary | +| locked -> frontend | format/version/VIEW enabled seulement | `desktop_security` | +| VIEW/OWNER handles | Rust-only `AppState` | session/security tests | +| export | OWNER-only, file direct, no-clobber | `desktop_security` + runtime `pre.012` | + +## 4. Inventory / path adversarial + +`pre.013` renforce le canari déterministe d'inventory : + +```text +seuls les fichiers réguliers directs *.kspwallet sont candidats +suffixe exact et sensible à la casse +backup.kspwallet.bak ignoré +upper.KSPWALLET ignoré +nested/hidden.kspwallet ignoré +répertoire nommé *.kspwallet ignoré +symlink *.kspwallet ignoré +sélection ../... rejetée +sélection ..\... rejetée +sélection absolue rejetée +sélection nested/... rejetée +sélection suffixe non exact rejetée +sélection vide rejetée +``` + +La sélection continue de réinspecter le fichier verrouillé au moment de l'ouverture de session ; l'inventory n'est jamais utilisé comme autorité de sécurité durable. + +## 5. DTO / IPC / frontend audit + +Les réponses IPC auditées ne doivent jamais contenir : + +```text +password +secret_key +private_key +keypair_bytes +ciphertext +``` + +Les projections import/export ne contiennent aucun path complet. `WalletAuthorizedDto` peut exposer Pubkey/alias/notes uniquement après VIEW/OWNER ; le locked inventory ne peut jamais les exposer. + +Le frontend doit rester sans : + +```text +@tauri-apps/plugin-dialog +@tauri-apps/plugin-fs +window.alert / confirm / prompt +localStorage / sessionStorage +navigator.clipboard pour secret +fetch / XMLHttpRequest / WebSocket / EventSource +KSP_SECRET_WALLET_PASS_* concret +sourcePath / destinationPath +``` + +Les opérations natives import/export utilisent le plugin Dialog côté Rust, sans permission Dialog/FS accordée au guest frontend. + +## 6. Capabilities Tauri + +La capability Wallet Desk doit rester exactement : + +```text +core:default +tracing:default +``` + +Aucune permission `dialog:*` ou `fs:*` n'est accordée au frontend. La CSP reste `null` tant qu'un besoin concret n'impose pas une politique spécifique ; `pre.013` n'ajoute aucune ressource réseau frontend. + +## 7. Dependency firewall + +Dépendances KSP directes attendues de Wallet Desk : + +```text +ksp-config-lib +ksp-core-lib +ksp-logging-lib +ksp-onchain-transport-lib +ksp-wallet-lib +``` + +Aucune dépendance directe `solana-*`, `reqwest` ou `tracing` n'est admise dans le manifest Wallet Desk. `tauri-plugin-tracing` reste l'adapter Tauri justifié ; l'émission applicative reste via `ksp-logging-lib`. + +Commandes opérateur `pre.013` : + +```bash +cargo tree -p ksp-app-wallet-desk --depth 1 +cargo tree -p ksp-app-wallet-desk -d +cargo tree -i ksp-wallet-lib +cargo tree -i ksp-onchain-transport-lib +cargo tree -i tauri-plugin-dialog +``` + +Le résultat attendu est qu'aucune nouvelle dépendance Solana/protocole directe ne soit introduite par `pre.013`; le seul changement de feature est `tokio/macros` requis pour le smoke opt-in de la crate application. + +## 8. Smoke Devnet Wallet Desk + +Le smoke live est : + +```text +Config composite devnet +-> resolve Wallet profile Composite +-> ConfigEnvironment +-> répertoire Wallet effectif +-> create .kspwallet réel VIEW+OWNER +-> inspect locked +-> open VIEW +-> resolve Transport profile Composite +-> HttpTransportPool +-> getBalance(confirmed) sur la Pubkey du wallet créé +-> suppression du .kspwallet canari +``` + +Il est volontairement `ignored` dans la suite déterministe afin d'éviter une dépendance réseau lors de `cargo test --workspace`. + +Commande recommandée : + +```bash +KSP_WALLETS_DIRECTORY=var/wallet-desk-pre013-smoke \ + cargo test -p ksp-app-wallet-desk \ + --test wallet_desk_devnet_smoke \ + -- --ignored --nocapture +``` + +Ce placement est intentionnel : le smoke cross-crates `Config -> Wallet -> Transport -> Devnet getBalance` appartient à la surface d'orchestration Wallet Desk. Il ne doit pas migrer dans `ksp-config-lib` ou `ksp-onchain-transport-lib`. + +Un solde `0` est un succès valide ; le gate vérifie l'exécution RPC et un contexte de slot non nul, pas un montant particulier. + +## 9. Gate déterministe `pre.013` + +```bash +cargo fmt --all +python3 scripts/audit_rust_workspace_rules.py +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test -p ksp-app-wallet-desk +cargo test --workspace +``` + +Attendus : + +```text +audit structurel clean +aucun warning check/Clippy +release_compliance vert +wallet_desk_devnet_smoke présent mais ignored dans workspace +inventory adversarial renforcé vert +aucune régression Config/Core/Logging/Transport/Wallet +``` + +## 10. Checkpoint runtime `pre.012` déjà acquis + +Le runtime communiqué avant ouverture de `pre.013` confirme notamment : + +```text +export Base58 OWNER : OK +export Solana CLI JSON OWNER : OK +destination existante : wallet.destination_exists, session OWNER conservée +annulation save picker : aucun export, session conservée +VIEW : export indisponible +aucun secret/path complet observé dans les logs frontend/backend fournis +``` + +Ce checkpoint n'est pas rejoué automatiquement par `pre.013`; la tranche ajoute les canaries transversaux qui rendent ces frontières durables. + +## 11. Résultats `pre.013` + +À compléter après exécution opérateur : + +```text +fmt / audit / check / clippy : PENDING +cargo test -p ksp-app-wallet-desk : PENDING +cargo test --workspace : PENDING +cargo tree / inverse tree : PENDING +smoke Devnet Wallet Desk opt-in : PENDING +``` + +La matrice reste ouverte jusqu'à `pre.015`, où seront ajoutés le résultat du polish `pre.014`, le parcours fonctionnel final, les docs README/USAGE et le build Tauri exécuté en toute dernière opération.