# Plan `0.2.6` — Wallet Desk ## 1. Objet et statut de `0.2.6-pre.001` `0.2.6` crée `crates/ksp-app-wallet-desk`, une application Tauri spécialisée et mince qui valide la composition réelle : ```text ksp-config-lib + Config composite + ksp-wallet-lib + ksp-onchain-transport-lib HTTP + ksp-logging-lib ``` La base auditée est la release stable fournie `v0.2.5`, avec `workspace.package.version = 0.2.5` avant ouverture. La base ne contient encore ni crate `ksp-app-wallet-desk`, ni document `std.wallet`, ni composite Wallet Desk. `0.2.6-pre.001` reste une tranche d'audit, conception, inventaire et sizing. Elle ne crée pas encore la crate Tauri ni les fichiers Config runtime. Son rôle est de produire une trajectoire suffisamment détaillée pour que les prereleases suivantes implémentent des objectifs positifs et mesurables sans renégocier les frontières au fil des corrections. Les validations opérateur du `2026-08-20` ont ensuite confirmé la base `pre.001` : `cargo fmt --all`, `python3 scripts/audit_rust_workspace_rules.py`, `cargo check --workspace` et `cargo clippy --workspace --all-targets` sont verts, sans warning signalé. Le présent plan intègre les corrections documentaires de `pre.001-fix.001` puis `pre.001-fix.002` sans modifier la version Cargo, car aucun fichier code/build/runtime/configuration exécutable n'est touché. `pre.001-fix.002` aligne en particulier le workflow frontend sur la règle KSP : npm direct sert uniquement à installer ou mettre à jour les dépendances ; les cycles dev/build passent exclusivement par Cargo/Tauri. `0.2.6-pre.002` matérialise ensuite la première tranche technique : la crate `ksp-app-wallet-desk` rejoint le workspace avec le shell splash/main du gabarit Config Desk, le bootstrap Config + Logging commun, les ports `1432/1433`, Bootstrap, Font Awesome, DataTables/Select, SimpleBar, `resize-observer-polyfill`, TS-RS et le bridge frontend -> Rust -> `ksp-logging-lib`. Cette tranche ne branche encore ni `std.wallet`, ni inventory filesystem réel, ni `ksp-wallet-lib`, ni Transport : ces responsabilités restent dans les prereleases déjà dimensionnées. `0.2.6-pre.003` matérialise la composition Config prévue : `cfg.std.wallet`/`schema.std.wallet`, le composite `cfg.composite.ksp-app-wallet-desk`, les profils Wallet `default`/`temporary`/`tests`, `ResolvedWalletConfig` et les adapters qui peuvent consommer un profil déjà sélectionné par composite sans perdre la provenance `Composite`. Wallet Desk valide les composants `logging`/`transport`/`wallet`, crée la racine et le sous-répertoire effectif absents avec logs `debug`, refuse les objets filesystem invalides/symlinks de sous-répertoire avec logs `error`, et expose uniquement les chemins/profils non secrets dans le statut runtime. Aucun inventory `.kspwallet` n’est encore effectué. `pre.003-fix.001` corrige les deux régressions de tests révélées par la validation workspace et le warning Clippy, puis rend visibles les événements `trace` frontend. `pre.003-fix.002` corrige ensuite le canari d’inventaire `.env.example` qui confondait un fragment d’identifiant Rust avec une variable KSP, remplace le profil temporaire `wallet_desk_dev` par une famille Logging réutilisable (`console_*`, `file_info`, `superdev`, `supertrace`) et fait sélectionner temporairement `supertrace` par le composite Wallet Desk afin de conserver la visibilité des actions frontend sans profil spécifique à l’application. `pre.003-fix.003` corrige uniquement les canaris révélés par la validation de `fix.002` : conformité `clippy::implicit_return` et séparation explicite entre fragment lexical KSP incorporé dans un identifiant et nom d’environnement concret suffixé. `pre.003-fix.004` ferme le dernier défaut Clippy de cette série en ajoutant les `return` explicites aux cinq closures restantes du test de composition `supertrace`, sans changer les profils Logging ni le composite. `pre.004` matérialise ensuite l’inventory locked réel. Sa validation opérateur confirme le comportement fonctionnel et le workspace complet, mais révèle deux warnings `unused_imports` sur des réexports crate-root internes uniquement utiles aux unit tests ; `pre.004-fix.001` les limite à `#[cfg(test)]` : ils disparaissent du build runtime mais restent disponibles au crate-root pour les unit tests, conformément aux règles d’exports KSP, sans changement de comportement. `pre.005` matérialise ensuite la création native dans Wallet Desk et le premier lifecycle durable `WalletSession` : création no-clobber sous la racine Config via `create_wallet_file_v1`, session OWNER conservée uniquement en Rust après création, sélection locked durable, `Lock`/`Deselect`/Refresh/changement de wallet avec drop des handles et purge frontend des projections protégées/passwords. Les unlock VIEW/OWNER de wallets existants restent strictement `pre.006`. `pre.005-fix.001` corrige le gate de compilation/tests révélé par l'opérateur sans modifier le lifecycle : la variante `WalletSession::Owner` contient désormais `Box` afin de fermer `clippy::large_enum_variant`, et le canari de sécurité définit explicitement `request_start` avant de contrôler les champs password request-only. `pre.006` matérialise ensuite les unlock existants : VIEW/OWNER manuel, découverte Config des candidats `KSP_SECRET_WALLET_PASS_*`, ordre déterministe filename normalisé -> numérique -> nommé, actions configurées explicites et jamais automatiques, `PrivilegedOperation` pendant Argon2, puis sessions `View`/`Owner` Rust-only avec candidate count seul projeté vers l’UI. `pre.006-fix.001` corrige les deux canaris `desktop_security` qui inspectaient trop largement le texte de `WalletAuthorizedDto` : la documentation du compteur de candidats contient légitimement le mot `password`, mais aucun champ password n’est sérialisé ; le test filtre désormais uniquement les lignes de champs `pub(crate)` de la structure. `pre.007` ferme ensuite le MVP réseau : Config sait mapper un profil Transport déjà sélectionné par composite sans perdre la provenance `Composite`; Wallet Desk construit et conserve `HttpTransportPool`, publie uniquement profile/role/cluster/provider/compteurs sûrs, et `refresh_wallet_balance` accepte zéro argument frontend. Rust copie la Pubkey depuis `WalletView`/`WalletOwner`, libère le mutex avant l'I/O `getBalance`, utilise `confirmed`, puis projette lamports, SOL exact à neuf décimales, slot et `api_version`. Une réponse arrivée après lock/deselect/changement d'autorisation est refusée afin d'empêcher une balance stale de réapparaître. `pre.007-fix.001` corrige uniquement le canari d'intégration qui utilisait le constructeur test-only `ConfigEnvironment::from_maps`; le test passe désormais par `ConfigEnvironment::load`, API publique utilisée en production. `pre.007-fix.002` corrige le second défaut du même canari : son message d'assertion ne formate plus `environment` avec `{:?}`, ce qui aurait exigé `ConfigEnvironment: Debug`. ConfigEnvironment reste volontairement non-`Debug` et le runtime n'est pas modifié. `pre.008` ajoute ensuite l'import de keypairs Solana CLI JSON/Base58 via le plugin Dialog utilisé exclusivement côté Rust : le frontend choisit seulement le format, Rust ouvre le picker natif, convertit le résultat en chemin local, lit la source avec une borne stricte, valide la Pubkey via `ksp-wallet-lib`, conserve les octets secrets dans `Zeroizing>` et ne renvoie que basename/format/Pubkey. L'import consomme cette source staged en mémoire avec `import_wallet_transfer_v1`, publie no-clobber sous la racine Wallet Config et ouvre la session OWNER avec de nouveaux credentials KSP. Aucun chemin source, contenu secret ou permission guest Dialog/FS ne traverse l'IPC. ### Correctif `0.2.6-pre.013-fix.001` Le premier passage opérateur de `pre.013` a confirmé `fmt`, audit, `check` et Clippy, mais le nouveau canari `response_dtos_keep_secret_key_material_out_of_ipc` a produit un faux positif sur `WalletAuthorizedDto` : le scanner analysait aussi les commentaires du bloc de structure et rencontrait la documentation légitime `Wallet password candidates`. `pre.013-fix.001` aligne ce gate sur la règle déjà éprouvée en `pre.006-fix.001` : seuls les champs `pub(crate)` réellement sérialisables sont inspectés pour `password`, key material, path/content/bytes. Aucun DTO ni contrat IPC runtime n'est assoupli. ### Correctif `0.2.6-pre.013-fix.002` La validation de `pre.013-fix.001` confirme `fmt`, audit, `check`, Clippy, les tests Wallet Desk, le canari `release_compliance` et le smoke Devnet opt-in. Le workspace complet révèle toutefois un second faux positif : le test global `ksp-logging-lib::workspace_crates_do_not_bypass_ksp_logging_facade` détecte le littéral `tracing::` présent dans le code du canari Wallet Desk qui vérifie précisément l'absence de ce bypass. `pre.013-fix.002` conserve le scanner global inchangé et construit ce motif à l'exécution dans `release_compliance.rs`; le canari continue ainsi d'auditer les sources backend sans s'auto-déclarer comme bypass. Aucun comportement runtime, DTO, dependency firewall ou capability n'est modifié. ## 2. Sources relues et hiérarchie appliquée L'audit suit l'ordre d'autorité demandé : 1. archive KSP stable `v0.2.5` fournie ; 2. règles KSP versionnées ; 3. architecture, plans et validations actuels ; 4. contrats publics réellement exposés par les crates ; 5. sources officielles actuelles Tauri/frontend ; 6. archive bot3 fournie, uniquement comme référence historique. Les documents obligatoires du prompt d'ouverture ont été relus avant conception. Les surfaces ont ensuite été réauditées dans : ```text ksp-core-lib ksp-logging-lib ksp-config-lib ksp-onchain-transport-lib ksp-wallet-lib ksp-app-config-desk ``` Le plan historique `docs/plans/007-V0_2_0_SERIES_PLANNING.md` reste historique. La séquence active est celle de `ROADMAP.md` et `docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md`. ## 3. Frontières architecturales Direction autorisée : ```text ksp-app-wallet-desk -> ksp-config-lib -> ksp-wallet-lib -> ksp-onchain-transport-lib -> ksp-core-lib lorsque nécessaire -> ksp-logging-lib -> Tauri / frontend dependencies ``` Interdictions : ```text ksp-wallet-lib -X-> Config / Transport / Tauri / Store / execution policy ksp-app-wallet-desk -X-> cryptographie Wallet directe ksp-app-wallet-desk -X-> solana-keypair / solana-signer / solana-signature directs ksp-app-wallet-desk -X-> client RPC Solana alternatif frontend -X-> keypair ou secret key material frontend -X-> localStorage/sessionStorage pour les passwords frontend -X-> lecture directe de .env ``` La seule intégration directe `tracing` tolérée reste l'adapter Tauri imposé par `tauri-plugin-tracing`. La façade applicative continue de passer par `ksp-logging-lib`. ## 4. `ksp-app-config-desk` comme gabarit Tauri ### 4.1 Structure réutilisée Wallet Desk reprend le gabarit déjà validé : ```text frontend/ frontend/ts/ frontend/sass/ frontend/ts/bindings/ Vite + TypeScript Bootstrap Font Awesome DataTables Bootstrap 5 DataTables Select Bootstrap 5 SimpleBar resize-observer-polyfill splash + main window TS-RS à la frontière DTO applicative capabilities Tauri explicites bridge frontend -> Rust -> ksp-logging-lib npm direct uniquement pour installer/mettre à jour les dépendances de package.json beforeDevCommand = frontend dev déclenché automatiquement par cargo tauri dev beforeBuildCommand = build frontend de production déclenché automatiquement par cargo tauri build ``` `SimpleBar` et `resize-observer-polyfill` sont conservés comme éléments du gabarit desktop, même si Wallet Desk ne les consomme pas tous dès le premier écran métier. Ils évitent de recréer un shell divergent du template éprouvé. ### 4.2 DataTables et sélection des wallets Le tableau d'inventaire des wallets utilise `datatables.net-bs5`. `datatables.net-select-bs5` est également conservé afin que la sélection de ligne reste cohérente avec le template et qu'un wallet puisse être sélectionné sans implémenter une logique de table parallèle. Le tableau doit fournir au minimum : ```text état lock/unlock filename format_version VIEW enabled/disabled inspection status ``` La recherche, le tri et la pagination sont fournis par DataTables. Les données locked ne contiennent jamais Pubkey, alias ni notes. ### 4.3 Font Awesome Font Awesome est utilisé pour les états lisibles rapidement : ```text fa-lock wallet verrouillé fa-lock-open wallet actuellement ouvert fa-eye / eye-slash selon état VIEW si utile fa-triangle-exclamation pour une entrée invalide/diagnostic ``` Une icône n'est jamais l'unique information accessible : texte, `title`/ARIA ou colonne d'état accompagne l'icône. ### 4.4 Ports de développement Config Desk utilise `1430` et son HMR `1431`. Wallet Desk réserve : ```text Vite dev : 1432 HMR : 1433 ``` ### 4.5 Logging à reprendre/refondre Le pattern validé est : ```text frontend helper -> invoke emit_frontend_log -> DTO validé côté Rust -> ksp-logging-lib::{trace,debug,info,warn,error}! ``` Les helpers Wallet journalisent des événements sémantiques sans payload sensible, par exemple : ```text wallet_inventory_refresh_started wallet_selected wallet_unlock_view_succeeded wallet_unlock_owner_failed wallet_balance_refresh_succeeded wallet_directory_created wallet_export_cancelled ``` Ils ne sérialisent jamais aveuglément les request DTOs. La configuration standard Logging expose aussi des profils opératoires réutilisables : ```text console_error / console_warn / console_info / console_debug / console_trace console seule au niveau correspondant file_info console désactivée fichier global info pour tous les targets KSP superdev console debug un fichier debug par target KSP racine supertrace console debug un fichier trace par target KSP racine ``` `local_dev` est conservé pour compatibilité avec Config Desk et les scénarios historiques. Pendant le développement de Wallet Desk, le composite sélectionne explicitement `supertrace`; ce choix reste une donnée de composition et pourra être resserré ultérieurement sans modifier l’application. Les fichiers par target utilisent des filtres de préfixe, de sorte que les sous-targets frontend (`ksp-app-wallet-desk.frontend.*`) restent routés dans le fichier de leur target racine. ## 5. Audit de la surface publique `ksp-wallet-lib` La release stable `0.2.5` possède déjà la logique métier nécessaire. Wallet Desk compose et projette ; il ne réimplémente rien. ### 5.1 Locked projection `LockedWalletInfo` expose uniquement : ```text format_version view_enabled ``` Aucune Pubkey, aucun alias et aucune note ne sont disponibles avant autorisation. ### 5.2 Authorized projection `WalletInfo` expose après autorisation : ```text format_version capability pubkey alias notes ``` ### 5.3 Handles Rust `WalletView` et `WalletOwner` restent possédés par l'état Rust de l'application. `WalletOwner` fournit déjà : ```text sign update_alias add_note update_note delete_note rotate_owner_password rotate_view_password disable_view recreate_view export_transfer export_transfer_file ``` Le frontend ne reçoit aucun handle ni secret interne. ### 5.4 Persistence et transfer Les APIs publiques stables couvrent déjà : ```text create_wallet_file_v1 inspect_locked_wallet_file_v1 open_wallet_view_file_v1 open_wallet_owner_file_v1 inspect_wallet_transfer_file import_wallet_transfer_file_v1 export_wallet_transfer_file ``` Les formats transfer retenus sont : ```text Solana CLI JSON Solana keypair Base58 complet ``` ### 5.5 `wallet.state_conflict` Un conflit d'état provoque : ```text message UI explicite -> abandon du handle courant -> purge de la projection autorisée et de la balance -> nouvelle inspection locked -> unlock requis de nouveau ``` Aucun retry aveugle ni overwrite forcé. ## 6. Config Wallet : racine globale + sous-répertoire de profil ### 6.1 Besoin d'étendre le registre Config `ConfigFileRegistry::defaults()` connaît actuellement Logging, Transport et le schéma composite, mais pas Wallet. La tranche Config doit donc ajouter durablement : ```text cfg.std.wallet schema.std.wallet cfg.composite.ksp-app-wallet-desk ``` Fichiers physiques prévus : ```text config/std.wallet.json config/schemas/std.wallet.schema.json config/examples/std.wallet.example.json config/composite.ksp-app-wallet-desk.json config/examples/composite.ksp-app-wallet-desk.example.json ``` ### 6.2 Shape de `std.wallet` `wallets_directory` est global. Chaque profil peut ajouter un sous-répertoire relatif optionnel : ```json { "format_version": 1, "wallets_directory": "${KSP_WALLETS_DIRECTORY:-wallets}", "default_profile": "default", "profiles": [ { "profile_id": "default" }, { "profile_id": "tests", "wallets_subdirectory": "tests" }, { "profile_id": "temporary", "wallets_subdirectory": "temporary" } ] } ``` Le profil `default` utilise directement la racine globale. Les profils `tests`, `temporary` ou futurs profils de scénario peuvent isoler leurs wallets sans introduire une seconde variable globale ni dupliquer le root. Le champ `wallets_subdirectory` : - est optionnel ; - est relatif ; - peut contenir plusieurs composants relatifs si le schema final le permet ; - refuse chemin absolu, `..` et toute résolution hors de `wallets_directory` ; - reste une donnée non secrète de Config. Le nom exact est retenu comme `wallets_subdirectory` pour rendre explicite sa relation avec `wallets_directory`. ### 6.3 `ResolvedWalletConfig` `ksp-config-lib` doit fournir un adapter public analogue aux adapters Logging/Transport, par exemple : ```text ResolvedWalletConfig file_id source_path profile_id selection_source wallets_directory wallets_subdirectory effective_wallets_directory provenance sûre ``` `effective_wallets_directory` est : ```text wallets_directory ou wallets_directory / wallets_subdirectory ``` Config résout et valide les chemins mais n'énumère pas les wallets. ### 6.4 Création automatique des répertoires Après résolution Config, Wallet Desk garantit l'existence du répertoire global et du répertoire effectif de profil avec une opération Rust `create_dir_all` ou équivalente. Comportement : ```text absent -> tentative de création créé/prêt -> debug échec création -> error + erreur runtime explicite existe mais n'est pas un répertoire -> error + refus d'inventory ``` Le log de succès inclut au minimum le profil et un indicateur `created=true/false`. Le log d'échec est `error`. Aucun échec de création n'est traité comme un inventaire vide normal. La création est une responsabilité de composition de Wallet Desk après résolution Config, pas une raison d'ajouter du filesystem Wallet dans `ksp-wallet-lib`. ### 6.5 Composite Wallet Desk Shape de référence : ```json { "format_version": 1, "default_profile": "devnet", "profiles": [ { "profile_id": "devnet", "documents": [ {"component_id": "logging", "file_id": "cfg.std.logging", "profile_id": "supertrace"}, {"component_id": "transport", "file_id": "cfg.std.transport", "profile_id": "devnet_public"}, {"component_id": "wallet", "file_id": "cfg.std.wallet", "profile_id": "default"} ] }, { "profile_id": "tests", "documents": [ {"component_id": "logging", "file_id": "cfg.std.logging", "profile_id": "supertrace"}, {"component_id": "transport", "file_id": "cfg.std.transport", "profile_id": "devnet_public"}, {"component_id": "wallet", "file_id": "cfg.std.wallet", "profile_id": "tests"} ] } ] } ``` Le composite référence des `file_id`, jamais des filenames physiques. ## 7. Passwords provenant de Config / `.env` ### 7.1 Évolution du contrat d'ouverture Pour `0.2.6`, les passwords Wallet peuvent être fournis de deux façons : ```text 1. saisie éphémère dans l'UI 2. secret KSP géré par Config depuis process env ou .env ``` Les secrets `.env` sont donc explicitement autorisés pour cette application sous le namespace : ```text KSP_SECRET_WALLET_PASS_* ``` Cela ne place pas les passwords dans les documents JSON Config : Config reste seulement le propriétaire de l'environnement et de `.env`. ### 7.2 Noms supportés Exemples opérateur : ```text KSP_SECRET_WALLET_PASS_01 KSP_SECRET_WALLET_PASS_02 KSP_SECRET_WALLET_PASS_MY_WALLET KSP_SECRET_WALLET_PASS_TREASURY ``` Les suffixes peuvent représenter un numéro, le filename ou son stem normalisé, ou un **alias de filename/opérateur**. Ils ne représentent jamais l'alias interne protégé stocké dans le `.kspwallet`. Le filename locked peut produire un candidat déterministe. La normalisation exacte doit être testée et documentée dans la tranche Config/secret-provider ; direction retenue : ```text strip .kspwallet -> uppercase ASCII -> caractères hors [A-Z0-9] remplacés par _ -> runs de _ compactés -> _ de bord supprimés ``` Les collisions de labels sont possibles et ne deviennent pas une identité Wallet. ### 7.3 Alias de filename et alias interne Wallet L'alias interne du wallet (`WalletInfo.alias`) reste une metadata protégée et n'intervient **jamais** dans la résolution des `KSP_SECRET_WALLET_PASS_*`. Wallet Desk n'ouvre donc jamais un wallet pour découvrir cet alias afin de choisir un secret. Un suffixe nommé correspond uniquement au filename/stem normalisé ou à un **alias de filename/opérateur** défini pour identifier le fichier côté exploitation. Cet alias de filename n'est pas une identité interne du wallet et peut être connu avant unlock. Les noms de variables restent côté Rust/Config et ne sont pas renvoyés au frontend ni journalisés. ### 7.4 Découverte et ordre des candidats Le moteur Config possède déjà une vue sûre des noms d'environnement présents. Wallet Desk utilise uniquement les APIs Config publiques ; aucun accès `std::env` direct n'est autorisé. Ordre de tentative proposé : ```text 1. candidat correspondant au filename normalisé 2. candidats numériques (_01, _02, ...) dans l'ordre numérique 3. candidats correspondant aux alias de filename/opérateur dans un ordre déterministe ``` Les valeurs provenant de Config restent entièrement côté Rust. Chaque `String` obtenue de Config est déplacée immédiatement dans `ViewPassword` ou `OwnerPassword`, qui possède la zeroization à la destruction. Cette règle concerne les secrets Config/`.env` ; elle n'interdit pas les passwords saisis par l'utilisateur de transiter **du frontend vers Rust** dans la request Tauri qui exécute l'opération demandée. ### 7.5 Tentative explicite, pas d'auto-unlock silencieux Argon2id rend chaque tentative volontairement coûteuse. Wallet Desk ne doit donc pas essayer toutes les variables au simple clic de sélection d'une ligne. L'UX distingue : ```text Unlock VIEW avec saisie Unlock OWNER avec saisie Unlock VIEW avec secret configuré Unlock OWNER avec secret configuré ``` Une action « secret configuré » tente les candidats jusqu'au premier succès pour la capability demandée. Pour une saisie manuelle, le password est envoyé une seule fois par IPC **HTML/TypeScript -> Rust**, converti immédiatement en wrapper Wallet puis purgé côté frontend. Rust ne renvoie jamais ce password. Le frontend peut recevoir : ```text candidate_count success/failure capability obtenue ``` mais jamais la valeur d'un secret Config ni un password saisi. Les noms/suffixes de variables Config ne sont pas exposés au frontend. Les logs peuvent enregistrer le nombre de candidats et le résultat, pas leurs noms ni leurs valeurs. ### 7.6 `.env.example` La tranche Config ajoute une documentation de pattern dans `.env.example`, par exemple : ```text # Wallet Desk password candidates. Values are secrets and never committed. # Additional KSP_SECRET_WALLET_PASS_