# 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é. ## 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. ## 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"}, {"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"}, {"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_