Files
khadhroony-solana-project/deltas/0.2.6/pre.017.md
2026-08-22 09:39:13 +02:00

264 lines
7.7 KiB
Markdown

<!-- file: deltas/0.2.6/pre.017.md -->
<!-- version: 1 -->
# Delta `0.2.6-pre.017` — migration explicite `.kspwallet` V1 -> V2 et canaris de persistence
## Base requise
```text
0.2.6-pre.016-fix.002 appliquée
workspace.package.version = 0.2.6-pre.16.fix.2
```
Le checkpoint opérateur de cette base est intégralement vert :
```text
cargo fmt --all OK
python3 scripts/audit_rust_workspace_rules.py clean
cargo check --workspace OK
cargo clippy --workspace --all-targets OK
cargo test --workspace OK
ksp-wallet-lib 78 passed / 1 ignored
Wallet Desk desktop_contract 16/16 OK
Wallet Desk release_compliance 4/4 OK
smokes réseau / benchmark ignored comme prévu
```
`pre.016-fix.002` est donc accepté comme base de `pre.017`.
## Signal technique
Cette tranche ajoute des APIs/runtime/persistence de migration Wallet :
```text
workspace.package.version = 0.2.6-pre.17
commit = v0.2.6-pre.017
```
Aucun tag prerelease. Aucun `package.json`/`tauri.conf.json` n'est modifié.
## Objectif de la tranche
`pre.017` ferme le chantier fonctionnel V1/V2 avant la candidate documentaire `pre.018` :
```text
migration V1 -> V2 explicite
OWNER authentifie toujours la source
migration mémoire
copie filesystem no-clobber
remplacement in-place atomique et stale-protected
identité Solana conservée
metadata protégées conservées exactement
note IDs stables conservés
VIEW enabled/disabled conservé
credential VIEW cible explicite si VIEW est activé
aucune migration implicite lors d'un open/inspect
canaris tampering / no-clobber / stale-state
régression Wallet Desk non-migrante
```
## APIs publiques de migration
### Snapshot mémoire
```rust
migrate_wallet_v1_to_v2(
source,
owner_password,
target_view_password,
)
```
Le `source` doit être un V1 strict. La fonction :
1. parse le V1 ;
2. authentifie OWNER ;
3. récupère en mémoire l'identité Solana autorisée et le payload metadata exact ;
4. construit un nouvel envelope V2 ;
5. restaure alias, notes et identifiants stables de notes dans le payload V2 ;
6. retourne un `WalletOwner` V2.
Aucun octet source n'est modifié.
### Migration vers une nouvelle destination
```rust
migrate_wallet_file_v1_to_v2(
source,
destination,
owner_password,
target_view_password,
)
```
La source V1 est conservée. La destination V2 utilise la persistence no-clobber existante : une destination existante retourne `wallet.destination_exists` et reste inchangée.
### Migration in-place
```rust
migrate_wallet_file_v1_to_v2_in_place(
source,
owner_password,
target_view_password,
)
```
L'enveloppe V1 observée avant OWNER unlock devient l'état authentifié attendu. Après construction complète du V2, la publication passe par le remplacement atomique V1 existant :
```text
relire source bornée
parse V1 strict
vérifier signature OWNER
comparer à expected V1
écrire/sync temp
revérifier expected V1 juste avant publication
rename atomique
sync fichier / parent best-effort
```
Un autre état V1 valide publié entre-temps provoque `wallet.state_conflict` et n'est jamais écrasé.
## Politique credential VIEW pendant migration
V1 et V2 ont des domaines/AAD distincts. Le VIEW key-wrap V1 ne peut donc pas être réutilisé directement dans V2.
Règle `pre.017` :
```text
source V1 VIEW disabled -> target_view_password = None
source V1 VIEW enabled -> target_view_password = Some(...)
```
Lorsque VIEW est activé, le credential cible peut être :
- le même mot de passe VIEW que V1 ;
- un nouveau mot de passe VIEW choisi pendant la migration.
Cela ne constitue pas une élévation supplémentaire : OWNER possède déjà en V1/V2 l'autorité de rotation du credential VIEW sans connaître l'ancien mot de passe VIEW.
La migration pure ne change toutefois pas la **forme de capability** : elle ne peut ni activer VIEW sur une source désactivée ni supprimer VIEW sur une source activée.
Une incohérence de cette politique retourne le nouveau code :
```text
wallet.migration_invalid
```
## Ce qui est préservé et ce qui est renouvelé
Préservé :
```text
Solana keypair / Pubkey
alias protégé
notes protégées
note IDs stables
état VIEW enabled/disabled
credential OWNER logique fourni par le caller
```
Renouvelé en V2 :
```text
owner_root
metadata_key
secret_key
OWNER auth Ed25519 key
OWNER/VIEW slot IDs
Argon2 salts
XChaCha nonces
ciphertexts
state signature
AAD/transcripts V2
```
La migration n'est donc jamais une transformation syntaxique JSON -> binaire des ciphertexts existants. Elle ouvre/authentifie V1 puis reconstruit cryptographiquement un document V2.
## Secret lifetime interne
`OwnerPassword` reste publiquement non-`Clone`. La migration doit toutefois dériver :
```text
V1 OWNER KDF pour authentifier la source
V2 OWNER KDF pour créer la cible
```
`ksp-wallet-lib` ajoute donc uniquement un duplicate interne court-vivant du wrapper OWNER, non exposé aux consumers et toujours zéroïsé au drop. Aucun contrat public `Clone` n'est ajouté à `OwnerPassword` ou `ViewPassword`.
## Canaris `ksp-wallet-lib`
Nouveau module `unit_tests/migration.rs` :
- migration V1 full-vector -> V2 ;
- identité Solana conservée ;
- alias/notes/note IDs conservés ;
- OWNER V2 rouvert avec le même credential OWNER ;
- VIEW V2 rouvert avec le credential cible ;
- ancien VIEW refusé lorsqu'un nouveau credential cible est choisi ;
- forme VIEW activée/désactivée obligatoirement conservée ;
- copie vers destination existante = no-clobber ;
- copie réussie laisse V1 source inchangé ;
- migration in-place produit un V2 ouvrable via APIs génériques ;
- tampering OWNER-authenticated V1 rejeté avant publication ;
- remplacement concurrent par un autre V1 valide = `wallet.state_conflict` ;
- état concurrent plus récent conservé.
Le public API canary expose les trois fonctions de migration et `ERROR_CODE_MIGRATION_INVALID` depuis la racine de crate.
## Régression Wallet Desk
Wallet Desk ne reçoit volontairement **aucun bouton/commande de migration automatique** dans cette tranche.
Il reste sur :
```text
create/import génériques -> V2
open OWNER/VIEW générique -> V1 ou V2
inventory inspect générique -> V1 ou V2
```
Un nouveau canari `pre_017_wallet_desk_open_paths_remain_non_migrating` interdit l'introduction d'un appel `migrate_wallet_*` dans les chemins normaux `app_state`/inventory.
Cette séparation garantit qu'ouvrir un ancien V1 dans Wallet Desk ne modifie jamais le fichier à l'insu de l'utilisateur.
## Documentation synchronisée
Cette tranche met à jour :
```text
docs/formats/KSPWALLET_V2.md
docs/plans/013-V0_2_6_WALLET_DESK_PLAN.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/validation/009-V0_2_6_WALLET_DESK_COMPLIANCE.md
crates/ksp-wallet-lib/README.md
crates/ksp-wallet-lib/USAGE.md
prompts/011-V0_2_6_START_PROMPT.md
docs/000-README.md
docs/plans/000-README.md
ROADMAP.md état global uniquement
```
`CHANGELOG.md` reste réservé à `pre.018`/clôture de release conformément à la politique KSP.
## Validation opérateur requise
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-wallet-lib
cargo test -p ksp-app-wallet-desk
cargo test --workspace
```
Aucun `cargo tauri build` dans cette tranche.
Si ce checkpoint est vert, la prochaine tranche est :
```text
0.2.6-pre.018
```
avec documentation/candidate finale, fermeture CWD/resources du bundle, parcours Tauri final, prompt `0.2.7`, puis `cargo tauri build` comme toute dernière opération de validation.