# Plan `0.2.12` — SOL Prices Desk + intégration prix Wallet Desk ## 1. Base et autorité La release est ouverte exclusivement depuis l'archive opérateur stable `v0.2.11` fournie au démarrage de session. L'archive ne contient pas de metadata `.git`; le tag ne peut donc pas être revérifié localement par `git describe`. Les marqueurs internes sont cohérents : `workspace.package.version = 0.2.11`, `deltas/0.2.11/rel.001.md` et `prompts/017-V0_2_12_START_PROMPT.md` sont présents. Le log opérateur fourni avec la base exécute sur le checkout stable `v0.2.11` : ```text cargo fmt --all PASS python3 scripts/audit_rust_workspace_rules.py PASS python3 scripts/audit_markdown_tables.py ... deltas/0.2.11 PASS cargo check --workspace PASS cargo clippy --workspace --all-targets PASS cargo test --workspace PASS ``` Dans le sandbox de préparation, `cargo` n'est pas disponible. Les deux audits Python ont néanmoins été rejoués sur l'archive extraite et sont propres. Aucune commande Cargo non exécutée dans ce sandbox n'est déclarée localement PASS; le gate Cargo de base repose sur la preuve opérateur fournie pour le même état stable. `0.2.12-pre.001` est une tranche documentaire d'audit/sizing. Elle ne crée ni crate Tauri, ni Config runtime nouveau, ni commande prix, ni code frontend fonctionnel. ## 2. Mission et frontières Mission concrète de la release : ```text 1. créer ksp-app-solprices-desk comme HID Tauri provider-agnostic 2. valider son contrat Config + MarketPriceService + DTO/commands + UI 3. intégrer dans ksp-app-wallet-desk une projection auxiliaire minimale : moyenne SOL/USD calculée lors du refresh de balance ``` Ownership durable : ```text ksp-offchain-transport-lib providers, wire REST, endpoints, normalisation, rate limits, cooldowns, dispatch ksp-config-lib documents, profils, composite, env, secrets, construction MarketPriceService apps Tauri composition, état de présentation courant, IPC, UI, instrumentation frontend projection/interaction uniquement; aucun réseau provider ni secret ``` La release ne crée ni `ksp-offchain-transport-api`, ni crate UI partagée spéculative, ni provider supplémentaire. ## 3. Audit de la surface stable Off-chain Transport La façade publique `0.2.11` suffit au besoin HID. Le nouveau desk n'a pas besoin d'accéder aux adapters concrets. | Contrat stable | Usage `0.2.12` | Décision | |-----------------------------------------|------------------------------------------------------------|-------------------------------------------| | `MarketPriceService::registry` | inventaire détaché provider-neutral | consommer directement | | `MarketPriceService::refresh` | refresh d’un `provider_id` opaque | consommer directement | | `MarketPriceService::refresh_many` | batch borné, validation avant dispatch, ordre déterministe | consommer directement | | `MarketPriceService::refresh_all` | refresh de l’inventaire dans l’ordre stable | consommer directement | | `MarketPriceProviderRegistry` / `Entry` | descriptor + état génériques | source unique des lignes | | `MarketPriceProviderDescriptor` | display name, semantics, auth, limites génériques | projection bornée; pas de wire | | `MarketPriceProviderAvailability` | Ready/Disabled/CoolingDown/etc. + `retry_at` | projection UI sans scheduler | | `MarketPriceObservation` | prix exact + timestamps + provenance + semantics | projection sûre | | `MarketPriceDecimal` | vérité décimale exacte | DTO frontend en chaîne, jamais `f64` | | `MarketPriceProviderId` | identifiant opaque borné | seul identifiant accepté par les commands | Constats structurants : - le service ne constitue pas un cache historique ; - `refresh_many` rejette les doublons et IDs inconnus avant dispatch ; - `refresh_many/all` V1 est volontairement séquentiel et ne dort pas pour attendre un provider ; - disponibilité, cooldown et retry provider sont déjà possédés par Off-chain Transport ; - les erreurs provider sont normalisées sans recopier de body distant dans les diagnostics sûrs ; - huit setups concrets existent côté composition, mais ils ne doivent jamais apparaître dans le code applicatif par branche provider. **Verdict :** aucune modification de `ksp-offchain-transport-lib` n'est nécessaire pour ouvrir la HID. ## 4. Audit Config et composition `ResolvedOffchainTransportConfig` transporte déjà la sélection Config résolue, la provenance sûre et un `MarketPriceService`. `ConfigDocumentEngine` expose les chemins `load_resolved_offchain_transport_config` et `resolve_offchain_transport_config_profile`; l'application n'a donc pas à reconstruire les setups providers. La base stable possède : ```text cfg.std.offchain_transport schema.std.offchain_transport format_version = 1 default profile = public_keyless explicit profile = all_free schema.composite générique cfg.composite.ksp-app-wallet-desk ``` ### 4.1 Composite SOL Prices Desk retenu Créer pendant `pre.003` : ```text filename config/composite.ksp-app-solprices-desk.json file_id cfg.composite.ksp-app-solprices-desk schema schema.composite existant component_id logging component_id offchain_transport ``` Profils retenus : | Composite profile | Logging | Off-chain Transport | Rôle | |----------------------------|----------------|---------------------|-------------------------------------------------------------| | `public_keyless` (default) | `console_info` | `public_keyless` | démarrage sans credential et profil de référence stable | | `all_free` | `console_info` | `all_free` | opt-in Config; credentials résolus exclusivement par Config | | `tests` | `console_info` | `public_keyless` | tests déterministes sans secrets | `console_info` est retenu pour les profils de référence afin de ne pas créer en `0.2.12` une dépendance artificielle à des filtres/fichiers de logging spécialisés. L'instrumentation `trace` reste présente dans le code frontend/Rust conformément aux règles KSP, même si ce profil stable ne l'affiche pas par défaut. Aucun nouveau schema composite n'est nécessaire. ### 4.2 Extension Wallet Desk retenue Chaque profil du composite Wallet Desk (`devnet`, `temporary`, `tests`) recevra : ```text component_id = offchain_transport file_id = cfg.std.offchain_transport profile_id = public_keyless ``` Le défaut Wallet reste donc keyless et ne gagne aucune exigence de secret. `all_free` n'est pas injecté arbitrairement dans Wallet Desk. ### 4.3 Contrainte atomique registry/packaging `prepare_packaged_runtime` parcourt **tous** les descriptors de `ConfigFileRegistry::defaults()`. L'ajout du composite SOL Prices Desk augmente donc l'inventaire de ressources packagées de 10 à 11. La tranche qui ajoute le descriptor doit synchroniser dans le même delta : ```text Config registry nouveau document composite Config unit/public tests resources Tauri ksp-app-config-desk resources Tauri ksp-app-wallet-desk resources Tauri ksp-app-solprices-desk tests desktop/package qui comptent ou valident les resources ``` Il ne faut pas publier un état intermédiaire dans lequel le registry exige le nouveau composite mais un Desk packagé ne l'embarque pas. ## 5. Audit des gabarits desktop stables La base confirme : | Surface | Config Desk | Wallet Desk | Décision SOL Prices Desk | |------------------|-------------------------------|-------------------------------------|----------------------------| | ports Vite/HMR | 1430 / 1431 | 1432 / 1433 | 1434 / 1435 | | Rust package | lib + bin | lib + bin | lib + bin | | windows | splash + main | splash + main | splash + main | | commands | centralisés `tauri.rs` | centralisés `tauri.rs` | même pattern | | window modules | `tw_splash`, `tw_main` | `tw_splash`, `tw_main` | même pattern | | frontendDist | hors source sous `builds/...` | hors source sous `builds/...` | même pattern dédié | | Vite | strictPort + HMR dédié | strictPort + HMR dédié | strictPort 1434 + HMR 1435 | | capabilities | core + tracing | core + tracing | core + tracing seulement | | dialog plugin | absent | Rust-side pour import/export Wallet | absent | | browser network | aucun fetch direct attendu | aucun fetch direct attendu | interdit | | Config resources | 10 resources registry | 10 resources registry | 11 après nouveau composite | Le nouveau desk doit avoir ses propres `productName`, `identifier`, chemins `frontendDist`, hooks NPM et backend Rust. Un canari doit empêcher une copie erronée qui lancerait Vite ou le backend Wallet Desk. La version de release desktop est désormais explicitement Cargo-owned : les `tauri.conf.json` des Desks omettent leur champ `version`, ce qui laisse Tauri dériver la version du package Rust résolu. Les `package.json` sont des manifests frontend privés de dépendances/scripts et ne sont pas des sources de vérité de release ; leur propriété `version`, lorsqu'elle est conservée, peut donc rester indépendante du bump global du workspace. Cette règle évite qu'une prerelease KSP sans travail sur une app force des modifications artificielles de ses manifests desktop. ## 6. Audit externe desktop du 2026-08-26 Sources officielles réauditées : ```text https://v2.tauri.app/reference/config/ https://v2.tauri.app/security/ https://v2.tauri.app/security/capabilities/ https://v2.tauri.app/reference/acl/core-permissions/ https://github.com/tauri-apps/tauri/releases https://github.com/Aleph-Alpha/ts-rs https://github.com/Aleph-Alpha/ts-rs/releases ``` Constats : - Tauri documente toujours `beforeDevCommand`, `beforeBuildCommand`, `devUrl` et `frontendDist` dans `tauri.conf.json` ; le pattern KSP actuel reste aligné ; - les capabilities restent la frontière d'autorisation du WebView vers les commandes/plugin permissions ; `core:default` est toujours un permission set officiel ; - la release Tauri courante observée est `2.11.5`, cohérente avec la contrainte workspace `tauri = ^2.11` ; - TS-RS publie `12.0.0` comme release courante et recommande `ts-rs = "12.0"`, cohérent avec `ts-rs = ^12.0` déjà présent ; - aucun besoin `pre.001` ne justifie une mise à niveau globale de Tauri, TS-RS ou des dépendances frontend. Le nouveau desk réutilisera le baseline frontend actuel uniquement pour les dépendances réellement utilisées. `tauri-plugin-dialog` n'est pas retenu pour SOL Prices Desk. ## 7. État de présentation courant Décision : **backend Tauri en mémoire**. Un runtime applicatif dédié possède : ```text Resolved Off-chain profile metadata sûre MarketPriceService snapshot courant des lignes de présentation état in-flight borné par provider pour éviter les races d'affichage ``` Le snapshot de présentation n'est ni un historique, ni une source métier, ni une nouvelle vérité de marché. Une observation vient toujours d'un `MarketPriceRefreshOutcome` Off-chain. Règles : - aucun prix n'est persisté dans Config, Store, filesystem navigateur, `localStorage` ou `sessionStorage` ; - l'inventaire initial provient du `registry()` et affiche `price = None` avant première observation ; - une erreur de refresh peut conserver l'ancienne observation uniquement si elle reste explicitement marquée comme antérieure et accompagnée du nouvel état provider ; - `received_at` et `provider_timestamp` sont projetés séparément ; l'UI ne fabrique jamais un timestamp provider ; - l'UI ne remplace un snapshot que par un résultat backend ordonné/accepté, évitant qu'une réponse plus ancienne écrase une plus récente. Cette décision reste dans la couche application et ne transforme pas Off-chain Transport en store UI. ## 8. Screen map SOL Prices Desk ```text splash -> bootstrap Config + Logging + Off-chain runtime -> main shell -> Prices (vue par défaut) -> runtime summary compact -> table provider-neutral SOL/USD provider display name semantics auth capability générique availability exact price ou “aucune observation” provider timestamp ou “non fourni” received_at retry_at si présent row refresh -> selection DataTable optionnelle -> Refresh selected -> Refresh all -> loading / empty / classified error states -> Diagnostics -> app/composite/offchain profile -> provider count / ready count / unavailable count -> aucun endpoint/credential/header ``` Le shell reste volontairement petit : pas de dashboard trading, graphe, historique, quote routing ou éditeur Config. ### Point d'insertion Wallet Desk — décision opérateur `pre.008` Le périmètre Wallet Desk est volontairement réduit après validation complète de SOL Prices Desk : ```text Wallet Desk existing shell -> Dashboard / Wallet courant -> Balance SOL existante -> “SOL/USD moyen” -> valeur moyenne dérivée du refresh courant -> N.A. si aucune observation exploitable -> “Équivalent USD” -> balance SOL × moyenne du refresh courant, calcul exact côté Rust -> N.A. si le prix auxiliaire est indisponible -> bouton Refresh balance existant -> getBalance On-chain -> refresh_all Off-chain des providers éligibles -> moyenne backend -> projection commune Balance + SOL/USD ``` Aucun inventaire provider, aucune availability détaillée et aucun bouton prix supplémentaire ne sont dupliqués dans Wallet Desk. Les fonctions avancées restent la responsabilité de `ksp-app-solprices-desk`. Le prix auxiliaire suit le lifecycle du refresh de balance existant et n'ajoute aucun nouveau droit VIEW/OWNER. **Équivalent USD retenu par décision opérateur.** Il reste une projection auxiliaire strictement dérivée du refresh courant : calcul côté Rust sans `f64`, aucun prix canonique/fallback et aucune dépendance supplémentaire du frontend aux providers. ## 9. DTO map retenue Les DTO sont applicatifs, sérialisables et exportés TS-RS. Les enums évolutifs de la crate Off-chain ne sont pas exposés directement si cela rigidifie l'IPC ; ils sont projetés en codes/labels génériques bornés. Le prix exact traverse IPC comme chaîne décimale, jamais comme `f64`. Les timestamps sont des millisecondes Unix ou chaînes de présentation selon le canari TS-RS final; la source de vérité reste `MarketPriceTimestamp`. | DTO | Champs principaux | Sensibilité | |------------------------------------|----------------------------------------------------------------------------------------------------------------|--------------------| | `MarketPriceRuntimeStatusDto` | app version, composite profile, offchain profile, provider counts, fallback logging | Public/safe | | `MarketPriceProviderRowDto` | provider_id, display_name, pair, semantics, auth_mode, availability, price?, timestamps?, retry_at?, refreshed | Public/safe | | single refresh direct | `provider_id` opaque conservé directement depuis `pre.005` | Public input borné | | `MarketPriceRefreshManyRequestDto` | provider_ids opaques bornés | Public input borné | | `MarketPriceRefreshResultDto` | rows affectées + refreshed/classification | Public/safe | | `CommandErrorDto` | domain/code/message borné sans context sensible | Public/safe | Explicitement exclus de tout DTO : ```text API key header name/value endpoint URL raw response provider provider-specific request/response DTO bucket/token internals Config effective secret values ``` ## 10. Command map SOL Prices Desk | Command | Input | Output | Owner | Sensibilité | |-----------------------------|--------------------------------------|----------------------------------|----------------------------------|-----------------------| | `get_runtime_status` | aucun | `MarketPriceRuntimeStatusDto` | AppState/Config runtime | safe | | `list_market_prices` | aucun | `Vec` | MarketPriceRuntime + registry | safe | | `refresh_market_price` | `provider_id` | `MarketPriceProviderRowDto` | MarketPriceService::refresh | safe ID | | `refresh_market_prices` | liste bornée `provider_id` | `MarketPriceRefreshResultDto` | MarketPriceService::refresh_many | safe IDs | | `refresh_all_market_prices` | aucun | `MarketPriceRefreshResultDto` | MarketPriceService::refresh_all | safe | | `splash_frontend_ready` | fenêtre invoquante | ordre splash existant | Tauri window lifecycle | safe | | `emit_frontend_log` | niveau/target/message déjà contrôlés | ack | logging bridge | safe-only by contract | Aucune command n'accepte URL, credential, header, rate-limit override ou payload provider. Wallet Desk n'expose finalement **aucune nouvelle command prix**. La command existante `refresh_wallet_balance` orchestre côté Rust le `getBalance` et le `MarketPriceService::refresh_all`; le frontend ne transmet ni `provider_id`, ni URL, ni sélection provider. `WalletBalanceDto` gagne uniquement `sol_usd_average: Option`. ## 11. Refresh UX Décisions : - ordre initial des lignes = ordre stable du registry ; un tri visuel DataTable ne change pas l'ordre de dispatch backend ; - bouton row désactivé pendant le refresh de cette ligne ; - batch : sélection bornée, doublons éliminés/rejetés côté UI pour UX mais validation authoritative conservée par Off-chain ; - `refresh_many/all` suit l'implémentation séquentielle V1, sans parallélisme ajouté par l'app ; - un provider non éligible est représenté par son availability, pas par un retry/sleep local ; - `CoolingDown` affiche `retry_at` si disponible ; aucun countdown actif n'est nécessaire ; - `AuthenticationUnavailable`, `Misconfigured`, `Disabled`, `QuotaUnavailable`, `TemporarilyUnavailable` restent des états génériques ; - `provider_timestamp = None` s'affiche comme « non fourni par le provider », pas comme heure de réception ; - une observation précédente peut rester visible après un échec seulement avec sa fraîcheur réelle et le nouvel état provider clairement distingués ; - fermeture de fenêtre : la commande/backend peut terminer/cancel selon lifecycle Tauri, mais aucun état frontend persistant ne doit être requis pour la sûreté. **Auto-refresh périodique : hors scope.** Aucun `setInterval`, scheduler ou worker prix n'est ajouté en `0.2.12`. ## 12. Intégration Wallet Desk — périmètre simplifié Wallet Desk ajoute une dépendance directe à `ksp-offchain-transport-lib` uniquement pour consommer les contrats publics `MarketPriceService`, `MarketPriceRefreshOutcome` et `MarketPriceDecimal` construits par Config. Aucune logique provider concrète n'est dupliquée. Flux retenu : ```text Config composite Wallet Desk -> component offchain_transport / cfg.std.offchain_transport / public_keyless -> ConfigDocumentEngine::resolve_offchain_transport_config_profile -> ResolvedOffchainTransportConfig -> MarketPriceService conservé côté Rust refresh_wallet_balance -> getBalance On-chain -> en parallèle : MarketPriceService::refresh_all -> ne garder que les observations réussies du refresh courant -> moyenne arithmétique backend sans f64, arrondie à 8 décimales maximum -> WalletBalanceDto { ..., sol_usd_average: Option, sol_usd_equivalent: Option } -> UI compacte “SOL/USD moyen” + “Équivalent USD”, ou “N.A.” ``` La branche prix est **auxiliaire/best-effort** : un runtime Off-chain indisponible, zéro observation ou un échec structurel prix ne rend pas `getBalance` invalide. La balance SOL reste autoritative et la projection prix devient `None`. Non-régressions obligatoires : ```text WalletSession inchangée VIEW/OWNER inchangés Wallet secrets inchangés commande refresh_wallet_balance inchangée côté input aucun provider_id dans l'IPC Wallet aucune command prix Wallet supplémentaire aucun fetch TS aucune multiplication balance x prix aucun fallback sur une observation stale aucun provider canonique ``` La moyenne n'est ni un consensus de marché, ni un nouveau contrat Off-chain partagé : c'est une présentation auxiliaire propre au Wallet Desk. ## 13. Mutualisation entre applications La mutualisation métier existe déjà dans `ksp-offchain-transport-lib` et la mutualisation de construction dans `ksp-config-lib`. `pre.001` ne retient **aucune nouvelle crate partagée**. Les deux apps peuvent posséder de petits adapters DTO/runtime propres parce qu'ils sont de la présentation/composition et que leurs `AppState`/runtime status diffèrent déjà. Le périmètre Wallet simplifié ne justifie aucune nouvelle crate/helper partagé : SOL Prices Desk conserve les projections riches, Wallet Desk ne consomme que le service générique et calcule une moyenne auxiliaire locale. ## 14. Capabilities et plugins SOL Prices Desk : ```text permissions core:default tracing:default plugins tauri-plugin-tracing uniquement ``` Aucun plugin filesystem, HTTP, shell, CLI ou dialog n'est nécessaire au WebView. Wallet Desk conserve sa capability actuelle `core:default + tracing:default`. Son `tauri-plugin-dialog` Rust-side reste limité aux workflows Wallet existants et n'est pas utilisé par les prix. Le threat model considère le WebView comme moins fiable que le core Rust : les commands Rust restent la frontière de validation et n'exposent que les capacités applicatives nécessaires. ## 15. Packaging et build SOL Prices Desk : ```text productName KSP SOL Prices Desk identifier com.sasedev.ksp-app-solprices-desk dev URL http://localhost:1434 Vite HMR 1435 strict frontendDist ../../../builds/khadhroony-solana-project/ksp-app-solprices-desk/dist windows splash + main bundle targets all, comme le gabarit stable icons même baseline KSP, copiée explicitement dans la nouvelle crate beforeDevCommand npm run dev, cwd . beforeBuildCommand npm run build, cwd . ``` Les commandes Tauri de validation finales sont exécutées depuis chaque crate ciblée. Aucun `cargo tauri ... -c` ne sert de sélecteur de crate. Canari obligatoire : `ksp-app-solprices-desk` référence son propre `package.json`, son propre Vite config, ses ports 1434/1435, son `frontendDist` et son backend; aucune chaîne `ksp-app-wallet-desk` ne doit apparaître dans les hooks/paths qui sélectionnent le runtime. ## 16. Threat model desktop `0.2.12` | Risque | Contrôle retenu | |------------------------------------------|----------------------------------------------------------------------------------| | frontend compromis choisit une URL | aucun champ URL dans IPC; origine possédée par adapter Off-chain | | frontend demande API key/header | aucun DTO/command ne les expose; secrets restent Config -> service | | provider_id arbitraire/inconnu | validation par registry/service; erreur KSP sûre | | batch oversized/duplicate | borne app + validation authoritative `refresh_many` avant dispatch | | spam refresh | admission/rate limits/cooldown Off-chain; UI disable seulement ergonomique | | cooldown non expiré | availability générique; aucun sleep/retry dans app | | provider unavailable | ligne reste visible avec état générique; autres providers indépendants | | raw error body hostile | projection `CommandErrorDto` classifiée; jamais de body brut | | stale observation affichée comme fraîche | timestamps réels conservés; état provider séparé; pas de timestamp inventé | | race double-click | état in-flight backend + UI disable; ordre de snapshot contrôlé | | fermeture fenêtre pendant refresh | aucune persistance requise; lifecycle/cancel borné testé | | CWD/resource confusion cross-app | CWD KSP normalisé + resources registry + canari paths/ports/backend dédiés | | capabilities trop larges | core + tracing seulement pour Price Desk; aucun network/fs/dialog guest | | logs frontend sensibles | log seulement action, provider_id, compte, code KSP; jamais DTO brut/secret/body | | faux prix canonique | Price Desk sans canonique/fallback; moyenne Wallet explicitement auxiliaire | | perte de précision JS | prix transmis comme chaîne décimale exacte | ## 17. Stratégie de tests et gates | Gate | Preuve attendue | |---------------------|--------------------------------------------------------------------------------------| | unit Rust app | mapping descriptor/state/outcome -> DTO; exact decimal string; stale/in-flight/error | | Config composition | composite Price Desk et extension Wallet résolus par file_id/profile exacts | | Config packaging | 11 descriptors/resources synchronisés, seed/schemas propres | | public/API boundary | aucun provider-specific type requis par l’app; dépendances exactes | | desktop contract | ports, windows, hooks, frontendDist, header, DataTable, backend propre | | desktop security | aucun fetch/network/fs/secret storage; capabilities minimales; DTO sans URL/key/body | | TS-RS | bindings exportés par tests; prix exact en String | | workspace | fmt + audits + check + clippy + test | | SOL Prices Desk dev | parcours splash -> table -> row/many/all sur composition réelle | | Wallet Desk dev | refresh balance + moyenne SOL/USD + équivalent USD/N.A. + non-régression Wallet | | live composition | un smoke keyless applicatif opt-in suffit; ne réaudite pas les 8 wires | | production build | `cargo tauri build` crate-local pour SOL Prices Desk puis Wallet Desk | Le live gate `0.2.12` vérifie la composition application -> Config -> service avec au moins un provider keyless disponible. Il ne répète pas le smoke exhaustif `7/7` de `0.2.11` sauf contradiction live réelle. ## 18. Hors périmètre confirmé ```text nouveau provider ou modification des 8 adapters V1 SOL/EUR ou token arbitraire OHLCV/historique/chart SSE/WebSocket prix consensus/fallback automatique provider canonique panneau providers détaillé ou commandes prix dédiées dans Wallet Desk auto-refresh/polling périodique Store/worker/job prix éditeur Config dans Price Desk filesystem/network/dialog plugin frontend nouvelle crate API/UI partagée spéculative ``` ## 19. Forecast souple recalibré Chaque tranche technique vise **environ 15 à 20 minutes de travail effectif** lorsque le sujet s'y prête. Cette durée est une cible de granularité et non une durée maximale : build Tauri, live test, diagnostic ou difficulté réelle peuvent légitimement prolonger une tranche. Le forecast reste volontairement souple. Une tranche peut être scindée, fusionnée ou réordonnée par delta si la réalité technique l'exige, à condition de préserver les responsabilités de clôture. Le nombre de prereleases prévu n'impose ni une session de conversation par tranche, ni une clôture artificielle sur le numéro initialement prévu. Chaque prerelease possède un statut directement modifiable. Lorsqu'un correctif d'une tranche est nécessaire, il est ajouté sous la prerelease concernée avec un titre `#### pre.NNN-fix.MMM`, ce qui conserve l'historique de progression sans transformer le forecast en tableau. ### `pre.001` — Audit, sizing et cadrage SOL Prices Desk **Statut : réalisé ; gate opérateur complet PASS.** Audit/sizing de la base `v0.2.11`, screen map, DTO/command map, composition Config, threat model, stratégie de tests/build/live, intégration Wallet Desk et recalibrage de la release. Aucun scaffold Tauri ni code prix fonctionnel n'est avancé dans cette tranche. #### `pre.001-fix.001` — Forecast souple éditable et statuts **Statut : réalisé.** Remplacement du forecast tabulaire par des sous-sections éditables avec statut explicite. Les futurs correctifs peuvent être insérés sous leur prerelease avec un titre `####`, sans modifier le découpage technique, le scope fonctionnel ni les responsabilités de clôture de `0.2.12`. ### `pre.002` — Scaffold `ksp-app-solprices-desk` **Statut : réalisé ; gate opérateur de tranche PASS.** Création du scaffold strict de `ksp-app-solprices-desk` : crate lib/bin, shell Tauri, frontend de base, splash, logging et paire de ports réservée `1434/1435`. La tranche n'implémente encore ni table prix, ni `MarketPriceService`, ni refresh réseau. #### `pre.002-fix.001` — Alignement strict du gabarit desktop commun **Statut : réalisé ; gate opérateur de tranche PASS.** Correction du scaffold pour reprendre réellement les conventions communes Config/Wallet Desk : dépendances frontend de gabarit, `ResizeObserver`, identité de fenêtre, footer, police/provenance splash, vocabulaire SCSS partagé, commande `get_runtime_status`, instrumentation shell et canaris anti-dérive. Aucun runtime Off-chain, composite ni table prix n'est avancé. ### `pre.003` — Composite Config et bootstrap Off-chain **Statut : réalisé ; gate opérateur de tranche PASS après `pre.003-fix.001`.** Ajout atomique du composite `cfg.composite.ksp-app-solprices-desk`, de son descriptor registry et des resources packagées nécessaires aux trois Desks concernés, puis bootstrap Config -> Off-chain Transport du nouveau Desk. Le gate exige que le packaging Config reste cohérent ; aucune UI prix n'est ajoutée ici. #### `pre.003-fix.001` — Correction du canari ConfigEnvironment sans élargissement de contrat **Statut : réalisé ; gate opérateur de tranche PASS.** Correction strictement locale du test `config_composition` : le message d'assertion ne tente plus de formater le `Result` en `Debug`. `ConfigEnvironment` reste volontairement sans contrat `Debug`; aucun comportement runtime, composite, provider ou frontend n'est modifié. ### `pre.004` — Runtime prix, registry rows et DTOs **Statut : réalisé ; corrigé par `pre.004-fix.001` puis `pre.004-fix.002`, gate opérateur intégralement PASS.** Création du `MarketPriceRuntime` applicatif, projection provider-neutral du registry, DTOs TS-RS et états initiaux/loading/empty. `list_market_prices` projette les lignes depuis le registry sans dispatch réseau ; l'état de présentation reste uniquement en mémoire backend. Les providers restent opaques à l'UI, le prix exact et les timestamps traversent l'IPC sous forme de chaînes exactes, et aucun refresh n'est encore disponible avant `pre.005`. #### `pre.004-fix.001` — Synchronisation de la fixture composite Config Desk **Statut : réalisé ; corrigé par `pre.004-fix.002`, gate opérateur final PASS.** Correction du gate workspace révélé après `pre.004` : le registry Config contient désormais le composite SOL Prices Desk, donc le jeu de fixtures utilisé par `ksp-app-config-desk` doit contenir le document correspondant. Le fix ajoute la fixture dédiée avec des profils valides dans l'environnement de test et renforce le canari d'inventory pour vérifier explicitement `cfg.composite.ksp-app-solprices-desk`. Aucun runtime prix, provider, DTO ou frontend n'est modifié. #### `pre.004-fix.002` — Canari de version desktop rendu version-neutral **Statut : réalisé ; gate opérateur intégralement PASS.** Le gate de `pre.004-fix.001` a révélé que `desktop_contract.rs` comparait encore `package.json` et `tauri.conf.json` à la chaîne figée `0.2.12-pre.4`. Le fix reprend la convention déjà utilisée par Config Desk et Wallet Desk : validation SemVer-like avec plancher compatible, puis égalité entre les deux métadonnées desktop. Les futures prereleases/fixes peuvent ainsi avancer la version sans rendre le canari historique `pre.002` artificiellement rouge. Aucun runtime prix, provider, DTO ou frontend n'est modifié. ### `pre.005` — Refresh individuel **Statut : réalisé ; corrigé par `pre.005-fix.001`, gate opérateur Cargo/workspace intégralement PASS.** Branchement du refresh d'une ligne via `MarketPriceService::refresh`, projection de l'observation exacte, de la fraîcheur et de l'availability, gestion d'erreur sûre et maintien du dernier état applicatif uniquement en mémoire backend. Le backend marque la ligne `loading` sans conserver le mutex de présentation pendant l'`await`, puis applique l'outcome provider-neutral ; une tentative sans nouvelle observation conserve la dernière observation réussie. L'UI ajoute un bouton `Refresh` par ligne, journalise le clic en `debug`, et n'expose encore aucun refresh multiple/global avant `pre.006`. #### `pre.005-fix.001` — Canari de centralisation Tauri rendu structurel **Statut : réalisé ; gate opérateur Cargo/workspace intégralement PASS.** Le gate de `pre.005` confirme que le refresh individuel fonctionne et que le runtime Tauri démarre, mais révèle un faux positif dans `desktop_security.rs` : le canari assimilait toute occurrence du nom métier `refresh_market_price` hors `tauri.rs` à du plumbing de commande. Le fix conserve l'interdiction structurelle de `#[tauri::command]` hors `tauri.rs`, ajoute l'interdiction de `tauri::generate_handler!` hors `tauri.rs`, et autorise les méthodes applicatives homonymes dans `AppState`. Aucun comportement réseau, DTO, provider, row, frontend ou commande n'est modifié. ### `pre.006` — Refresh multiple et global **Statut : réalisé ; gate opérateur Cargo/workspace et parcours manuel multi-refresh intégralement PASS.** Ajout de `refresh_market_prices` et `refresh_all_market_prices` sur les APIs génériques `MarketPriceService::refresh_many`/`refresh_all`. La sélection frontend est reconstruite dans l'ordre courant des rows, le backend valide atomiquement doublons/IDs inconnus/conflits `in_flight`, marque toutes les lignes concernées `loading`, relâche le mutex de présentation pendant l'`await`, puis applique les outcomes dans leur ordre déterministe. `MarketPriceRefreshManyRequestDto` transporte uniquement les IDs opaques et `MarketPriceRefreshResultDto` retourne les rows affectées avec `requested_count`/`refreshed_count`. Les cooldown/retry deadlines restent les projections Off-chain existantes ; aucun polling, sleep consommateur, fallback automatique ou consensus provider n'est introduit. ### `pre.007` — UX et instrumentation SOL Prices Desk **Statut : réalisé ; gate opérateur Cargo/workspace/Tauri et parcours visuel intégralement PASS.** Finalisation de la table responsive sans changer le contrat de dispatch : résumé providers/ready/observed/attention/refreshing, header sticky, colonnes secondaires adaptatives, badges génériques d'auth/semantics/availability, prix exact conservé en String et timestamps présentés à la fois sous forme humaine et en millisecondes exactes. `provider_timestamp = None` est rendu explicitement comme `Not supplied`, sans substitution par `received_at`. La sélection gagne un contrôle global et `Clear selection`, uniquement frontend. Chaque nouveau contrôle est journalisé en `debug`; les rendus et comptes sûrs restent en `trace`. Les wrappers Tauri journalisent les échecs avec seulement `command`, `error_domain` et `error_code`. Aucun prix, timestamp, payload provider, URL, header ou credential n'entre dans les logs. La tranche reste manuelle : aucune nouvelle commande métier, aucun auto-refresh, scheduler ou persistance navigateur n'est ajouté. ### `pre.008` — Intégration Wallet minimale + recalibrage de clôture **Statut : réalisé ; corrigé par `pre.008-fix.001`, `pre.008-fix.002` puis `pre.008-fix.003`, gate opérateur final PASS.** Absorption de l'ancien couloir Wallet `pre.009`–`pre.011` dans une seule tranche : ajout `offchain_transport/public_keyless` au composite Wallet, runtime Off-chain auxiliaire, `refresh_all` lors de `refresh_wallet_balance`, moyenne SOL/USD backend sans `f64` et projection compacte dans Wallet Desk. La correction `fix.002` complète le besoin opérateur réellement attendu : `WalletBalanceDto` expose également un équivalent USD exact calculé côté Rust à partir des lamports et de la moyenne du refresh courant ; Dashboard et Details affichent le prix moyen et l'équivalent ou `N.A.`. Aucun panneau provider, aucune command prix Wallet et aucun provider ID frontend ne sont ajoutés. La tranche réconcilie également le forecast avec cette réduction de scope. #### `pre.008-fix.001` — Correction du canari desktop et Clippy de la moyenne **Statut : réalisé ; gate opérateur technique partiellement PASS, interrompu par un canari historique de version.** Le premier gate de `pre.008` confirme `cargo check --workspace` mais révèle deux défauts purement techniques : des littéraux Rust non échappés dans le nouveau canari `desktop_contract.rs`, qui empêchent `cargo fmt` et les targets de test de parser le fichier, et un warning Clippy `manual_is_multiple_of` dans le formateur décimal Wallet. Le fix remplace les littéraux fragiles par des raw strings Rust et applique `u128::is_multiple_of(10)`. Son gate confirme ensuite `fmt`, audits, `check` et Clippy, puis s'arrête sur le canari `pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_version_coherent`, encore couplé à une version littérale dans le HTML. Le runtime Tauri et le refresh balance/prix fonctionnent. #### `pre.008-fix.002` — Équivalent USD, projection Details et version desktop dynamique **Statut : réalisé ; comportement runtime/UX confirmé manuellement, gate compilé interrompu par un littéral de canari invalide.** Correction du dernier écart fonctionnel de la tranche : le refresh produisait bien une moyenne SOL/USD mais elle n'était visible que dans le résumé Dashboard, tandis que Details n'affichait ni cette moyenne ni la valeur USD équivalente de la balance. `WalletBalanceDto.sol_usd_equivalent` est désormais calculé côté Rust sans flottant, à partir des lamports et de `sol_usd_average`, avec arrondi half-up à huit décimales maximum ; l'UI rend moyenne et équivalent dans Dashboard et Details, ou `N.A.` si le prix auxiliaire est indisponible. Le badge statique `0.2.6` est remplacé par `RuntimeStatusDto.application_version`, et le canari packaging vérifie ce binding version-neutral au lieu d'exiger la version courante en littéral dans `main.html`. #### `pre.008-fix.003` — Correction du littéral de canari version dynamique **Statut : réalisé ; gate opérateur Cargo/workspace intégralement PASS.** Le gate de `pre.008-fix.002` confirme le fonctionnement runtime et visuel attendu : Dashboard et Details affichent la moyenne SOL/USD, l'équivalent USD et la version `0.2.12-pre.8.fix.2`. `cargo fmt`, les audits KSP et `cargo check --workspace` passent. Clippy `--all-targets` s'arrête uniquement sur le canari `desktop_contract.rs` : la raw string `r#"..."#` se ferme prématurément sur le fragment `"#appVersionBadge"`. Le fix remplace cette assertion par une chaîne Rust standard correctement échappée, sans modifier le runtime, le frontend ou le calcul prix. ### `pre.009` — Hardening transversal + gate technique/live final **Statut : réalisé ; gate opérateur Rust/workspace/live PASS, build Wallet PASS, build Price Desk à corriger par `fix.001`.** Fusion des anciens hardenings `pre.008`/`pre.012` et du gate `pre.013` : la sélection batch Price Desk est bornée à `1..=64` avant parsing des IDs opaques, les canaris capabilities/resources/CWD et provider-neutralité Wallet sont renforcés, et un smoke live opt-in prouve le chemin composite SOL Prices Desk -> Config -> `MarketPriceService::refresh_all` avec au moins une observation keyless. Le gate opérateur confirme `fmt`, audits, `check`, Clippy, tests ciblés, workspace complet et smoke live. Le build Wallet produit ses trois bundles Linux ; le build Price Desk s'arrête uniquement au `beforeBuildCommand` TypeScript sur `TS6133` parce que `appendMarketPriceCell` est devenue inutilisée après le polish `pre.007`. #### `pre.009-fix.001` — Build Price Desk + source de vérité de version Tauri **Statut : réalisé ; gate opérateur complet PASS, y compris smoke live et builds Config/Wallet/SOL Prices Desk.** Le fix supprime la fonction TypeScript morte et formalise la politique de version desktop : `workspace.package.version` est l'identité KSP autoritative ; les trois `tauri.conf.json` omettent `version` afin que Tauri la dérive de Cargo ; les `package.json` privés restent des manifests frontend non couplés au bump workspace. Les canaris Config/Wallet/SOL Prices vérifient désormais ce fallback plutôt qu'une égalité artificielle `package.json == tauri.conf.json == CARGO_PKG_VERSION`. Aucun comportement prix, Wallet ou Config n'est modifié. ### `pre.010` — Réconciliation documentaire finale **Statut : réalisé ; gate documentaire à confirmer.** Réconciliation finale des documents durables avec le runtime effectivement validé : README/USAGE SOL Prices Desk, README/USAGE Wallet Desk, architecture Apps/Dependencies/Contracts/Inventory, séquence fonctionnelle, index documentation, présent plan et matrice de validation. Cette tranche ne modifie ni `CHANGELOG.md`, ni `ROADMAP.md`, ni le prompt suivant et ne rouvre aucun code fonctionnel. ### `pre.011` — Préparation de publication minimale **Statut : planifié.** Dernière prerelease conforme à `PROMPT_STRUCTURE.md` / `VER-LIFECYCLE-003` : hors `Cargo.toml` et delta mécaniques, elle modifie uniquement le prompt de démarrage `0.2.13`, `CHANGELOG.md` et `ROADMAP.md`. Aucun README/USAGE/plan/validation/architecture/code/test n'est rouvert. ### `rel.001` — Publication stable `v0.2.12` **Statut : planifié.** Mécanique de publication stable uniquement, après validation de `pre.011` et vérification de la cohérence de release. Aucun défaut fonctionnel ou documentaire ne doit être absorbé par `rel.001`. Le Price Desk est fermé fonctionnellement après `pre.007`, Wallet après `pre.008`, et le gate technique/live/builds après `pre.009-fix.001`. La fermeture minimale restante est donc `pre.010` documentaire, `pre.011` publication prep, puis `rel.001`. ## 20. Critères de sortie `pre.001` | Critère | Verdict | |-------------------------------------------------------|---------| | base stable `v0.2.11` cohérente | PASS | | règles/architecture relues | PASS | | baseline opérateur verte; limites sandbox documentées | PASS | | surface Off-chain/Config réelle inventoriée | PASS | | gabarits Config/Wallet Desk audités | PASS | | audit Tauri/TS-RS actuel | PASS | | screen map | PASS | | DTO/command map | PASS | | Config/composite map | PASS | | threat model | PASS | | Wallet integration map | PASS | | tests/build/live strategy | PASS | | forecast recalibré | PASS | | scope clôturable | PASS | `pre.001` n'autorise aucune implémentation fonctionnelle supplémentaire dans ce delta. La prochaine responsabilité est exclusivement `pre.002` : scaffold du nouveau Desk et canaris de gabarit.