# Delta `0.2.5-pre.006` — persistence native async/no-clobber ## Base ```text 0.2.5-pre.5.fix.1 ``` Le checkpoint opérateur de cette base a validé `cargo fmt --all`, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, `cargo test -p ksp-wallet-lib`, `cargo test --workspace` et l'audit Cargo tree. Une seule génération `ed25519-dalek 2.2.0` est utilisée et `solana-address 2.7.0` est unifié entre Core et `solana-keypair`. ## Version technique ```text workspace.package.version = 0.2.5-pre.6 ``` ## Objet Cette tranche matérialise la persistence native `.kspwallet` sans Config, environnement, Transport ou Tauri : ```text create_wallet_file_v1(path, ...) open_wallet_view_file_v1(path, ...) open_wallet_owner_file_v1(path, ...) inspect_locked_wallet_file_v1(path) ``` Le chemin est toujours fourni explicitement par le caller. Wallet ne possède et ne découvre aucun répertoire par défaut. ## Persistence retenue `tempfile ^3.27` est ajouté sous `[workspace.dependencies]` puis consommé par `ksp-wallet-lib` avec `tempfile.workspace = true`. La création native suit : ```text create crypto in-memory -> serialize locked JSON -> NamedTempFile dans le même répertoire que la destination -> write_all -> sync_all(temp) -> persist_noclobber(destination) -> sync_all(fichier publié) -> parent-directory sync best-effort sur Unix ``` `create` ne propose aucun overwrite. Une destination existante retourne `wallet.destination_exists` même sous concurrence. La documentation upstream de `tempfile 3.27.0` précise que `persist_noclobber` ne peut pas être présenté comme atomique sur absolument toutes les plateformes/filesystems et peut laisser un hard-link temporaire dans certains scénarios de crash. KSP conserve donc une documentation explicite de cette limite : no-clobber est le contrat, tandis que la durabilité power-loss universelle n'est pas revendiquée. ## Async / lecture bornée Les opérations filesystem bloquantes sont regroupées dans `tokio::task::spawn_blocking`; aucune feature Tokio filesystem supplémentaire n'est requise. La lecture : - ouvre le fichier dans la frontière blocking ; - vérifie d'abord la taille metadata ; - utilise ensuite une lecture bornée à `KSPWALLET_MAX_FILE_BYTES + 1` afin de couvrir aussi un fichier qui grossirait pendant la lecture ; - rejette un document surdimensionné avant parsing/KDF. Les wrappers fichier délèguent ensuite aux flows `pre.005` déjà validés et n'introduisent aucune seconde implémentation crypto. ## Fault / concurrence Nouveaux canaries déterministes : - première publication no-clobber conservée face à une seconde tentative ; - fault injecté après `sync_all(temp)` mais avant publication : aucune destination partielle ; - cleanup du temp lors d'un retour d'erreur ordinaire ; - huit créateurs concurrents : exactement un succès et sept `destination_exists` ; - rejet pré-parser d'un fichier supérieur à 1 MiB ; - création publique vers fichier puis projection locked ; - le vecteur complet `pre.005` peut être publié puis ouvert par les nouvelles API VIEW et OWNER depuis fichier. Un kill/power-loss brutal peut laisser un artifact temporaire selon l'OS/filesystem ; la tranche ne prétend pas simuler ni garantir le cleanup après arrêt non déroulé. ## Frontières conservées ```text Wallet -> ksp-core-lib Wallet -> ksp-logging-lib Wallet -> primitives crypto/Solana retenues Wallet -> tempfile Wallet -X-> Config Wallet -X-> Transport Wallet -X-> ExecutionPolicy Wallet -X-> Store Wallet -X-> Tauri Wallet -X-> tracing direct Wallet -X-> solana-pubkey direct ``` `Pubkey` reste consommée exclusivement via `ksp_core_lib::Pubkey`. `solana-keypair` reste propriété de Wallet parce qu'il encapsule du matériau secret et une capacité de signature ; aucune keypair brute n'est remontée dans Core. ## Replace administratif `pre.006` n'expose volontairement pas une primitive publique générale d'overwrite/replace. Le remplacement atomique d'un wallet existant sera introduit en `pre.007` à travers les seules opérations autorisées : administration OWNER et self-rotation du password VIEW. Cela évite de donner à un consumer de niveau inférieur une primitive arbitraire `replace(path, bytes)`. ## Fichiers ajoutés ```text crates/ksp-wallet-lib/src/persistence.rs crates/ksp-wallet-lib/unit_tests/persistence.rs deltas/0.2.5/pre.006.md ``` ## Fichiers modifiés ```text Cargo.toml ROADMAP.md crates/ksp-wallet-lib/Cargo.toml crates/ksp-wallet-lib/src/lib.rs crates/ksp-wallet-lib/tests/dependency_boundary.rs crates/ksp-wallet-lib/tests/public_api.rs docs/formats/KSPWALLET_V1.md docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md docs/plans/012-V0_2_5_WALLET_FOUNDATION_PLAN.md ``` ## Validation opérateur requise ```bash cargo fmt --all cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-wallet-lib cargo test --workspace cargo tree -p ksp-wallet-lib cargo tree -p ksp-wallet-lib -d cargo tree -i tempfile@3.27.0 ``` Points à contrôler dans le tree : pas de nouvelle dépendance Config/Transport/Tauri, `tempfile` uniquement par Wallet dans cette tranche et aucune duplication crypto nouvelle injustifiée. ## Suite Après validation, `0.2.5-pre.007` porte : ```text signature Solana publique sans getter secret alias/notes OWNER rotation password VIEW par VIEW rotation password VIEW par OWNER sans ancien password VIEW rotation password OWNER suppression/recréation VIEW révocation forte VIEW avec rekey metadata replace atomique lié aux mutations autorisées ```