# Delta `0.2.12-pre.004` — runtime prix, registry rows et DTOs ## 1. Base requise Cette tranche s'applique exclusivement après : ```text v0.2.11 + 0.2.12-pre.001 + 0.2.12-pre.001-fix.001 + 0.2.12-pre.002 + 0.2.12-pre.002-fix.001 + 0.2.12-pre.003 + 0.2.12-pre.003-fix.001 ``` Le retour opérateur du `27 août 2026` clôt `pre.003-fix.001` pour la progression de release : `cargo fmt`, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace, `cargo test -p ksp-app-solprices-desk` et `cargo tauri dev` passent. Le runtime Tauri journalise le bootstrap Logging, le profil composite `public_keyless`, le profil Off-chain `public_keyless` et `provider_count=8`. `cargo test --workspace` n'est pas présent dans ce retour précis et n'est donc pas revendiqué comme exécuté. La version workspace passe à : ```text 0.2.12-pre.4 ``` Les versions `package.json` et `tauri.conf.json` de SOL Prices Desk sont synchronisées sur la même prerelease. ## 2. Responsabilité de la tranche `pre.004` matérialise uniquement la couche applicative provider-neutral entre le `MarketPriceService` déjà construit par Config et la future UX de refresh : ```text MarketPriceRuntime backend état de présentation courant en mémoire uniquement projection du registry en lignes stables MarketPriceRuntimeStatusDto MarketPriceProviderRowDto commande list_market_prices états frontend loading / empty / table projection initiale sans observation ni prix ``` La tranche n'appelle ni `MarketPriceService::refresh`, ni `refresh_many`, ni `refresh_all`. Aucun bouton de refresh ou scheduler n'est introduit avant les tranches prévues. ## 3. Ownership du runtime prix `AppState` possède désormais un `MarketPriceRuntime`. Celui-ci retient : ```text OffchainTransportStartup -> ResolvedOffchainTransportConfig -> MarketPriceService presentation: Mutex<...> -> une entrée par provider_id opaque -> loading bool -> dernière observation optionnelle ``` La présentation n'est persistée ni dans Config, ni dans Store, ni sur le filesystem frontend, ni dans `localStorage`/`sessionStorage`. L'inventaire initial est dérivé du `MarketPriceService::registry()` détaché. Chaque ligne commence avec : ```text loading = false observation = None price = None refreshed = false ``` L'availability initiale reste celle possédée par Off-chain Transport (`Ready`, `Disabled`, etc.). ## 4. DTOs provider-neutral `MarketPriceRuntimeStatusDto` remplace le nom bootstrap générique précédent et expose uniquement : ```text application/config/composite/logging/offchain metadata sûrs provider_count ready_provider_count unavailable_provider_count startup_diagnostic sûr ``` `MarketPriceProviderRowDto` expose uniquement la projection générique décidée par le plan : ```text provider_id opaque display_name pair = SOL/USD semantics générique auth_mode générique availability générique loading refreshed price optionnel provider_timestamp optionnel received_at optionnel retry_at optionnel ``` Le prix exact traverse l'IPC sous forme de `Option` issue de `MarketPriceDecimal::to_canonical_string()`. Les timestamps traversent eux aussi sous forme de chaînes de millisecondes Unix afin de ne pas introduire de perte d'entier JavaScript. Aucun `f64` n'est une vérité IPC. Les enums Off-chain `#[non_exhaustive]` sont projetés en codes génériques bornés avec fallback `unknown`; aucune enum provider-specific n'est exposée au frontend. ## 5. Commande d'inventaire La nouvelle commande Tauri centralisée est : ```text list_market_prices ``` Elle n'accepte aucun argument et retourne : ```text Vec ``` Elle ne déclenche aucun réseau. L'ordre des lignes suit l'ordre stable du registry Off-chain. La surface command reste donc à cette tranche : ```text emit_frontend_log get_runtime_status list_market_prices splash_frontend_ready ``` Les commandes `refresh_market_price`, `refresh_market_prices` et `refresh_all_market_prices` restent absentes par contrat. ## 6. Frontend initial La vue `Prices` remplace le placeholder de `pre.003` par trois états explicites : ```text loading empty table provider-neutral ``` La table initiale affiche les capacités et l'availability du registry. Comme aucun refresh n'a encore eu lieu, les prix et timestamps d'observation s'affichent comme absents. La tranche n'ajoute aucun bouton Refresh. La finalisation responsive/interactions/DataTable reste propriétaire de `pre.007`. Diagnostics ajoute les comptes total/ready/unavailable sans exposer endpoint, credential, header ou payload provider. ## 7. Logging frontend/backend `KSP-APP-027` reste appliqué : ```text navigation/clic utilisateur debug requête IPC debug via invokeKsp fin IPC trace chargement registry start/end trace rendu des lignes trace avec row_count uniquement erreur de projection/chargement warn/error sûr ``` Aucun log ne sérialise le prix, la row DTO complète, une API key, un header ou une réponse provider. ## 8. Dépendances et boundaries SOL Prices Desk ajoute une dépendance directe à `ksp-offchain-transport-lib` parce que la couche application consomme désormais ses types publics génériques de registry/observation. Cette dépendance ne donne lieu à aucune connaissance provider-specific : ```text aucun SDK provider aucun reqwest direct app aucun fetch frontend aucune URL/header/API key dans IPC aucune branche CoinGecko/Kraken/etc. aucun limiter/retry/sleep provider dans app ``` Config reste propriétaire de la construction du service et des secrets. ## 9. Tests et canaris Les tests ajoutés/étendus couvrent : ```text codes génériques auth/availability/semantics projection initiale sans observation projection exacte d'un decimal 123.4500 -> "123.45" provider_timestamp / received_at / retry_at distincts MarketPriceRuntime présent à partir de pre.004 commande list_market_prices centralisée dans tauri.rs absence des trois commandes de refresh états frontend loading/empty/table aucune conversion parseFloat/Number du prix aucun reqwest direct dans la crate app logging registry sans sérialisation métier ``` Les bindings TS-RS restent générés par les tests et ne sont pas versionnés. ## 10. Fichiers ajoutés ```text crates/ksp-app-solprices-desk/src/market_price_runtime.rs crates/ksp-app-solprices-desk/unit_tests/market_price_runtime.rs deltas/0.2.12/pre.004.md ``` ## 11. Fichiers modifiés ```text Cargo.toml crates/ksp-app-solprices-desk/Cargo.toml crates/ksp-app-solprices-desk/package.json crates/ksp-app-solprices-desk/tauri.conf.json crates/ksp-app-solprices-desk/frontend/main.html crates/ksp-app-solprices-desk/frontend/ts/main.ts crates/ksp-app-solprices-desk/src/app_state.rs crates/ksp-app-solprices-desk/src/dto_common.rs crates/ksp-app-solprices-desk/src/lib.rs crates/ksp-app-solprices-desk/src/tauri.rs crates/ksp-app-solprices-desk/tests/desktop_contract.rs crates/ksp-app-solprices-desk/tests/desktop_security.rs docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md docs/validation/015-V0_2_12_SOL_PRICES_DESK.md ``` ## 12. Fichiers volontairement inchangés ```text crates/ksp-offchain-transport-lib/** crates/ksp-config-lib/** crates/ksp-app-wallet-desk/** crates/ksp-app-config-desk/** README.md ROADMAP.md CHANGELOG.md prompts/017-V0_2_12_START_PROMPT.md ``` Aucun provider, wire REST, rate limit ou Config document n'est modifié. ## 13. Validations exécutées dans le sandbox Après matérialisation du code avant écriture du delta : ```text python3 scripts/audit_rust_workspace_rules.py General Rust rule audit: clean Rust export completeness audit: 0 candidate(s) KSP workspace Rust rule audit: clean ``` Après écriture du présent delta : ```text python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.12 Markdown table audit: clean (143 table(s), 113 file(s)) validation JSON package.json / tauri.conf.json JSON validation: clean ``` Le sandbox ne possède pas `cargo`; aucune commande Cargo n'est déclarée PASS localement. ## 14. Gate opérateur attendu ```bash cargo fmt --all python3 scripts/audit_rust_workspace_rules.py python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.12 cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-app-solprices-desk cargo test --workspace (cd crates/ksp-app-solprices-desk && cargo tauri dev) ``` Le parcours Tauri attendu doit afficher huit lignes provider-neutral pour le profil `public_keyless`, avec availability initiale mais sans prix ni timestamp d'observation, et sans effectuer de refresh réseau. Diagnostics doit afficher `provider_count=8` avec les comptes ready/unavailable cohérents. ## 15. Questions ouvertes Aucune. Si le gate devient propre, `pre.005` peut brancher exclusivement le refresh individuel sur ce runtime sans modifier le format Config ni les adapters providers.