v0.2.12-pre.009

This commit is contained in:
2026-08-27 17:18:29 +02:00
parent d3bb026345
commit 233aaf9bd8
15 changed files with 462 additions and 64 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 297 # version: 298
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"] members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
[workspace.package] [workspace.package]
version = "0.2.12-pre.8.fix.3" version = "0.2.12-pre.9"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,7 +1,7 @@
{ {
"name": "ksp-app-solprices-desk", "name": "ksp-app-solprices-desk",
"private": true, "private": true,
"version": "0.2.12-pre.7", "version": "0.2.12-pre.9",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -1,10 +1,12 @@
// file: crates/ksp-app-solprices-desk/src/market_price_runtime.rs // file: crates/ksp-app-solprices-desk/src/market_price_runtime.rs
// version: 3 // version: 4
//! Provider-neutral market-price presentation runtime owned by SOL Prices Desk. //! Provider-neutral market-price presentation runtime owned by SOL Prices Desk.
use ts_rs::TS; // rust-rules: trait-import use ts_rs::TS; // rust-rules: trait-import
const MARKET_PRICE_REFRESH_MAX_PROVIDER_IDS: usize = 64;
/// Safe provider-neutral row projected to the SOL Prices Desk frontend. /// Safe provider-neutral row projected to the SOL Prices Desk frontend.
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, TS)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@@ -534,6 +536,16 @@ fn lock_presentation(
} }
fn parse_provider_ids(provider_ids: std::vec::Vec<String>) -> ksp_core_lib::Result<std::vec::Vec<ksp_offchain_transport_lib::MarketPriceProviderId>> { fn parse_provider_ids(provider_ids: std::vec::Vec<String>) -> ksp_core_lib::Result<std::vec::Vec<ksp_offchain_transport_lib::MarketPriceProviderId>> {
if provider_ids.is_empty() || provider_ids.len() > MARKET_PRICE_REFRESH_MAX_PROVIDER_IDS {
return std::result::Result::Err(
ksp_core_lib::Error::new(
crate::ERROR_CODE_MARKET_PRICE_SELECTION_INVALID,
"SOL Prices Desk market-price refresh selection has an invalid provider count",
)
.with_context("provider_count", provider_ids.len().to_string())
.with_context("max_provider_count", MARKET_PRICE_REFRESH_MAX_PROVIDER_IDS.to_string()),
);
}
let mut parsed = std::vec::Vec::with_capacity(provider_ids.len()); let mut parsed = std::vec::Vec::with_capacity(provider_ids.len());
for provider_id in provider_ids { for provider_id in provider_ids {
let provider_id = ksp_offchain_transport_lib::MarketPriceProviderId::new(provider_id); let provider_id = ksp_offchain_transport_lib::MarketPriceProviderId::new(provider_id);

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "KSP SOL Prices Desk", "productName": "KSP SOL Prices Desk",
"version": "0.2.12-pre.7", "version": "0.2.12-pre.9",
"identifier": "com.sasedev.ksp-app-solprices-desk", "identifier": "com.sasedev.ksp-app-solprices-desk",
"build": { "build": {
"beforeDevCommand": { "beforeDevCommand": {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-solprices-desk/tests/desktop_contract.rs // file: crates/ksp-app-solprices-desk/tests/desktop_contract.rs
// version: 7 // version: 8
//! Desktop scaffold, shared-template and Config packaging contract audits for SOL Prices Desk `0.2.12`. //! Desktop scaffold, shared-template and Config packaging contract audits for SOL Prices Desk `0.2.12`.
@@ -80,6 +80,7 @@ fn pre_002_shell_uses_reserved_ports_windows_and_owned_backend_paths() {
let package_version = package.pointer("/version").and_then(serde_json::Value::as_str); let package_version = package.pointer("/version").and_then(serde_json::Value::as_str);
assert_packaged_version_floor(package_version, "package.json"); assert_packaged_version_floor(package_version, "package.json");
assert_eq!(tauri_version, package_version, "desktop package metadata versions must remain synchronized"); assert_eq!(tauri_version, package_version, "desktop package metadata versions must remain synchronized");
assert_eq!(tauri_version, std::option::Option::Some(env!("CARGO_PKG_VERSION")), "SOL Prices Desk package metadata must match the current Cargo release");
assert_eq!(tauri.pointer("/build/devUrl").and_then(serde_json::Value::as_str), std::option::Option::Some("http://localhost:1434")); assert_eq!(tauri.pointer("/build/devUrl").and_then(serde_json::Value::as_str), std::option::Option::Some("http://localhost:1434"));
assert_eq!(tauri.pointer("/build/beforeDevCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev")); assert_eq!(tauri.pointer("/build/beforeDevCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev"));
assert_eq!(tauri.pointer("/build/beforeDevCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some(".")); assert_eq!(tauri.pointer("/build/beforeDevCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-solprices-desk/tests/desktop_security.rs // file: crates/ksp-app-solprices-desk/tests/desktop_security.rs
// version: 7 // version: 8
//! Security, ownership and frontend instrumentation canaries for SOL Prices Desk. //! Security, ownership and frontend instrumentation canaries for SOL Prices Desk.
@@ -186,3 +186,16 @@ fn pre_007_remains_manual_without_browser_persistence_or_price_scheduler() {
assert!(!main.contains(forbidden), "pre.007 must remain manual and backend-owned: {forbidden}"); assert!(!main.contains(forbidden), "pre.007 must remain manual and backend-owned: {forbidden}");
} }
} }
#[test]
fn pre_009_hardening_bounds_hostile_batches_and_preserves_manual_backend_owned_refresh() {
let root = app_root();
let runtime = read_text(root.join("src/market_price_runtime.rs").as_path());
assert!(runtime.contains("const MARKET_PRICE_REFRESH_MAX_PROVIDER_IDS: usize = 64;"));
assert!(runtime.contains("provider_ids.is_empty() || provider_ids.len() > MARKET_PRICE_REFRESH_MAX_PROVIDER_IDS"));
assert!(runtime.contains("ERROR_CODE_MARKET_PRICE_SELECTION_INVALID"));
let main = read_text(root.join("frontend/ts/main.ts").as_path());
for forbidden in ["setInterval(", "setTimeout(", "requestAnimationFrame(", "fetch(", "XMLHttpRequest", "localStorage", "sessionStorage"] {
assert!(!main.contains(forbidden), "release hardening must preserve manual backend-owned refresh: {forbidden}");
}
}

View File

@@ -0,0 +1,80 @@
// file: crates/ksp-app-solprices-desk/tests/market_price_composition_live_smoke.rs
// version: 1
//! Opt-in live smoke proving SOL Prices Desk composite -> Config -> generic MarketPriceService composition.
fn workspace_engine() -> ksp_core_lib::Result<ksp_config_lib::ConfigDocumentEngine> {
let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
let bootstrap = match bootstrap {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let registry = ksp_config_lib::ConfigFileRegistry::defaults();
return match registry {
std::result::Result::Ok(value) => std::result::Result::Ok(ksp_config_lib::ConfigDocumentEngine::new(bootstrap, value)),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
#[tokio::test(flavor = "current_thread")]
#[ignore = "opt-in live SOL Prices Desk composition smoke; resolves the committed public_keyless composite and performs external SOL/USD HTTPS refreshes"]
async fn committed_solprices_composite_refreshes_at_least_one_keyless_provider() {
let engine = workspace_engine();
assert!(engine.is_ok(), "workspace Config engine should be constructible");
let engine = match engine {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK);
assert!(file_id.is_ok(), "SOL Prices Desk composite file_id should be valid");
let file_id = match file_id {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let composite = engine.load_resolved_composite(&file_id, std::option::Option::None);
assert!(composite.is_ok(), "committed SOL Prices Desk composite should resolve");
let composite = match composite {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(composite.profile_id(), "public_keyless");
let component = composite.component("offchain_transport");
assert!(component.is_some(), "SOL Prices Desk composite should contain Off-chain Transport");
let component = match component {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let environment = ksp_config_lib::ConfigEnvironment::load();
assert!(environment.is_ok(), "Config-owned environment snapshot should load");
let environment = match environment {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let resolved = engine.resolve_offchain_transport_config_profile(component.resolved(), &environment);
assert!(resolved.is_ok(), "composite-selected public_keyless Off-chain Transport should map");
let resolved = match resolved {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(resolved.profile_id(), "public_keyless");
assert_eq!(resolved.selection_source(), ksp_config_lib::ConfigProfileSelectionSource::Composite);
let registry = resolved.service().registry();
assert_eq!(registry.len(), 8);
let outcomes = resolved.service().refresh_all().await;
assert!(outcomes.is_ok(), "generic public_keyless refresh_all should complete without provider-specific application branching");
let outcomes = match outcomes {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert_eq!(outcomes.len(), registry.len());
let refreshed_count = outcomes.iter().filter(|outcome| return outcome.refreshed()).count();
assert!(refreshed_count > 0, "at least one public_keyless provider should produce one live SOL/USD observation");
for outcome in outcomes {
assert!(registry.entry(outcome.provider_id()).is_some(), "live outcome provider must belong to the generic registry");
if let std::option::Option::Some(observation) = outcome.observation() {
assert_eq!(observation.pair(), ksp_offchain_transport_lib::MarketPricePair::SolUsd);
assert_eq!(observation.provider_id(), outcome.provider_id());
}
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-solprices-desk/unit_tests/market_price_runtime.rs // file: crates/ksp-app-solprices-desk/unit_tests/market_price_runtime.rs
// version: 3 // version: 4
fn descriptor() -> std::option::Option<ksp_offchain_transport_lib::MarketPriceProviderDescriptor> { fn descriptor() -> std::option::Option<ksp_offchain_transport_lib::MarketPriceProviderDescriptor> {
let provider_id = ksp_offchain_transport_lib::MarketPriceProviderId::new("test-provider"); let provider_id = ksp_offchain_transport_lib::MarketPriceProviderId::new("test-provider");
@@ -278,3 +278,25 @@ fn batch_loading_validation_rejects_in_flight_provider_without_marking_other_row
} }
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
#[test]
fn hostile_batch_size_is_rejected_before_provider_id_parsing() {
let provider_ids = (0..=super::MARKET_PRICE_REFRESH_MAX_PROVIDER_IDS)
.map(|index| return std::format!("invalid provider payload {index}"))
.collect::<std::vec::Vec<_>>();
let result = super::parse_provider_ids(provider_ids);
assert!(result.is_err());
if let std::result::Result::Err(error) = result {
assert_eq!(error.code(), crate::ERROR_CODE_MARKET_PRICE_SELECTION_INVALID);
assert_eq!(error.context().len(), 2);
}
}
#[test]
fn empty_batch_is_rejected_at_the_application_boundary() {
let result = super::parse_provider_ids(std::vec::Vec::new());
assert!(result.is_err());
if let std::result::Result::Err(error) = result {
assert_eq!(error.code(), crate::ERROR_CODE_MARKET_PRICE_SELECTION_INVALID);
}
}

View File

@@ -1,7 +1,7 @@
{ {
"name": "ksp-app-wallet-desk", "name": "ksp-app-wallet-desk",
"private": true, "private": true,
"version": "0.2.12-pre.8.fix.3", "version": "0.2.12-pre.9",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "KSP Wallet Desk", "productName": "KSP Wallet Desk",
"version": "0.2.12-pre.8.fix.3", "version": "0.2.12-pre.9",
"identifier": "com.sasedev.ksp-app-wallet-desk", "identifier": "com.sasedev.ksp-app-wallet-desk",
"build": { "build": {
"beforeDevCommand": { "beforeDevCommand": {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs // file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs
// version: 28 // version: 29
//! Desktop build, shell and Config-status contract audits for Wallet Desk. //! Desktop build, shell and Config-status contract audits for Wallet Desk.
@@ -77,6 +77,10 @@ fn tauri_shell_uses_reserved_wallet_desk_ports_and_template_windows() {
assert_eq!(tauri.pointer("/build/beforeDevCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some(".")); assert_eq!(tauri.pointer("/build/beforeDevCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));
assert_eq!(tauri.pointer("/build/beforeBuildCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run build")); assert_eq!(tauri.pointer("/build/beforeBuildCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run build"));
assert_eq!(tauri.pointer("/build/beforeBuildCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some(".")); assert_eq!(tauri.pointer("/build/beforeBuildCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));
assert_eq!(
tauri.pointer("/build/frontendDist").and_then(serde_json::Value::as_str),
std::option::Option::Some("../../../builds/khadhroony-solana-project/ksp-app-wallet-desk/dist"),
);
assert_eq!(tauri.pointer("/app/security/csp"), std::option::Option::Some(&serde_json::Value::Null)); assert_eq!(tauri.pointer("/app/security/csp"), std::option::Option::Some(&serde_json::Value::Null));
let manifest = read_text(root.join("Cargo.toml").as_path()); let manifest = read_text(root.join("Cargo.toml").as_path());
assert!(manifest.contains("name = \"ksp-app-wallet-desk\"")); assert!(manifest.contains("name = \"ksp-app-wallet-desk\""));
@@ -465,6 +469,7 @@ fn pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_versi
let package_version = package.pointer("/version").and_then(serde_json::Value::as_str); let package_version = package.pointer("/version").and_then(serde_json::Value::as_str);
assert_packaged_version_floor(package_version, "package.json"); assert_packaged_version_floor(package_version, "package.json");
assert_eq!(tauri_version, package_version, "desktop package metadata versions must remain synchronized"); assert_eq!(tauri_version, package_version, "desktop package metadata versions must remain synchronized");
assert_eq!(tauri_version, std::option::Option::Some(env!("CARGO_PKG_VERSION")), "Wallet Desk package metadata must match the current Cargo release");
let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object); let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object);
assert!(resources.is_some(), "packaged Wallet Desk Config resources map must exist"); assert!(resources.is_some(), "packaged Wallet Desk Config resources map must exist");
if let std::option::Option::Some(resources) = resources { if let std::option::Option::Some(resources) = resources {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/tests/release_compliance.rs // file: crates/ksp-app-wallet-desk/tests/release_compliance.rs
// version: 8 // version: 9
//! Release-wide deterministic compliance canaries for Wallet Desk. //! Release-wide deterministic compliance canaries for Wallet Desk.
@@ -215,3 +215,29 @@ fn packaged_resources_include_only_registered_config_sources_and_schemas() {
} }
} }
} }
#[test]
fn v0_2_12_wallet_market_price_projection_remains_provider_neutral_and_exact() {
let root = app_root();
let source = read_rust_source_tree(root.join("src").as_path());
for forbidden in [
"MarketPriceBirdeye",
"MarketPriceCoinbaseExchange",
"MarketPriceCoinGecko",
"MarketPriceCoinMarketCap",
"MarketPriceCoinPaprika",
"MarketPriceDexScreener",
"MarketPriceJupiter",
"MarketPriceKraken",
] {
assert!(!source.contains(forbidden), "Wallet Desk must not branch on concrete market-price providers: {forbidden}");
}
let balance = read_text(root.join("src/wallet_balance.rs").as_path());
assert!(!balance.contains("f64"));
assert!(!balance.contains("f32"));
let main = read_text(root.join("frontend/ts/main.ts").as_path());
assert!(!main.contains("parseFloat("));
assert!(!main.contains("Number(balance.sol"));
assert!(!main.contains("providerId"));
assert!(!main.contains("refresh_market_price"));
}

168
deltas/0.2.12/pre.009.md Normal file
View File

@@ -0,0 +1,168 @@
<!-- file: deltas/0.2.12/pre.009.md -->
<!-- version: 1 -->
# Delta `0.2.12-pre.009` — hardening transversal et gate technique/live final
## Base requise
```text
0.2.12-pre.008-fix.003
```
Le gate opérateur de cette base est propre : `cargo fmt --all`, audits Rust/Markdown, `cargo check --workspace`, Clippy `--all-targets`, `cargo test -p ksp-app-wallet-desk` et `cargo test --workspace` passent. Le parcours fonctionnel Wallet avait déjà confirmé le refresh balance, la moyenne SOL/USD, l'équivalent USD et la version dynamique.
## Objectif
Fermer le couloir technique de `0.2.12` sans ouvrir de nouvelle fonctionnalité métier :
```text
borne batch hostile Price Desk avant parsing provider ID
canaris packages/CWD/resources/provider-neutralité cross-app
smoke live keyless de composition applicative générique
synchronisation npm/Tauri des deux apps modifiées sur pre.9
préparation du gate workspace + deux builds Tauri finaux
```
## Version technique
```text
delivery 0.2.12-pre.009
workspace Cargo 0.2.12-pre.9
SOL Prices package/Tauri 0.2.12-pre.9
Wallet package/Tauri 0.2.12-pre.9
```
## Hardening Price Desk
`MarketPriceRuntime::refresh_many` conserve le dispatch générique existant mais rejette désormais avant parsing des IDs opaques une sélection :
```text
vide
ou
> 64 provider IDs
```
La borne applicative est volontairement indépendante de tout provider concret. La valeur `64` correspond au plafond V1 déjà défendu par `MarketPriceService`; le Desk peut rester plus strict qu'une évolution future du service tant qu'une révision explicite de l'IPC n'a pas été faite.
Les doublons, IDs inconnus et conflits `in_flight` restent validés comme avant. Aucun scheduler, sleep, fallback, consensus, provider branch ou réseau frontend n'est ajouté.
## Smoke live de composition
Ajout du test opt-in :
```text
crates/ksp-app-solprices-desk/tests/market_price_composition_live_smoke.rs
```
Commande opérateur :
```bash
cargo test -p ksp-app-solprices-desk --test market_price_composition_live_smoke -- --ignored --nocapture
```
Le smoke ne réaudite pas les wire adapters. Il prouve seulement le chemin applicatif attendu :
```text
cfg.composite.ksp-app-solprices-desk / public_keyless
-> ConfigDocumentEngine
-> ResolvedOffchainTransportConfig
-> MarketPriceService registry = 8
-> refresh_all
-> au moins une observation SOL/USD live
```
Aucun secret n'est requis par ce profil.
## Canaris finaux renforcés
```text
SOL Prices package.json == tauri.conf.json == CARGO_PKG_VERSION
Wallet package.json == tauri.conf.json == CARGO_PKG_VERSION
Wallet frontendDist reste le dist Wallet dédié
Price Desk capabilities restent core + tracing uniquement
Price Desk frontend reste sans fetch/persistence/scheduler
Wallet src reste sans type provider concret
Wallet moyenne/équivalent restent sans f64/f32
Wallet frontend reste sans calcul flottant prix
resources Config restent bornées au registry packagé
```
## Fichiers ajoutés
```text
crates/ksp-app-solprices-desk/tests/market_price_composition_live_smoke.rs
deltas/0.2.12/pre.009.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-app-solprices-desk/package.json
crates/ksp-app-solprices-desk/tauri.conf.json
crates/ksp-app-solprices-desk/src/market_price_runtime.rs
crates/ksp-app-solprices-desk/unit_tests/market_price_runtime.rs
crates/ksp-app-solprices-desk/tests/desktop_contract.rs
crates/ksp-app-solprices-desk/tests/desktop_security.rs
crates/ksp-app-wallet-desk/package.json
crates/ksp-app-wallet-desk/tauri.conf.json
crates/ksp-app-wallet-desk/tests/desktop_contract.rs
crates/ksp-app-wallet-desk/tests/release_compliance.rs
docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md
docs/validation/015-V0_2_12_SOL_PRICES_DESK.md
```
## Fichiers supprimés
```text
aucun
```
## Hors scope préservé
```text
nouveau provider
modification wire/provider adapters
nouvelle commande prix Wallet
panneau provider détaillé Wallet
auto-refresh/polling/scheduler
fallback ou provider canonique
historique/OHLCV/chart
Store/worker prix
réconciliation README/USAGE finale
CHANGELOG/ROADMAP/prompt de release suivante
```
## Validations exécutées dans le sandbox de préparation
```text
python3 scripts/audit_rust_workspace_rules.py PASS
python3 scripts/audit_markdown_tables.py README.md ... deltas PASS
validation JSON package/tauri PASS
contrôles statiques versions/frontières PASS
cargo fmt/check/clippy/test/live/tauri build NOT RUN — cargo indisponible dans le sandbox
```
## Gate opérateur attendu
Exécuter dans cet ordre :
```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 -p ksp-app-wallet-desk
cargo test --workspace
cargo test -p ksp-app-solprices-desk --test market_price_composition_live_smoke -- --ignored --nocapture
(cd crates/ksp-app-solprices-desk && cargo tauri build)
(cd crates/ksp-app-wallet-desk && cargo tauri build)
```
Les deux `cargo tauri build` sont volontairement les dernières opérations lourdes conformément à `KSP-APP-034`.
## Critère de fermeture
`pre.009` est fermée lorsque le gate workspace, le smoke live de composition et les deux builds Tauri production sont verts. À ce stade, `pre.010` peut rester strictement documentaire/mécanique avant `rel.001`.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md --> <!-- file: docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md -->
<!-- version: 17 --> <!-- version: 18 -->
# Plan `0.2.12` — SOL Prices Desk + intégration prix Wallet Desk # Plan `0.2.12` — SOL Prices Desk + intégration prix Wallet Desk
@@ -250,9 +250,12 @@ Le périmètre Wallet Desk est volontairement réduit après validation complèt
Wallet Desk existing shell Wallet Desk existing shell
-> Dashboard / Wallet courant -> Dashboard / Wallet courant
-> Balance SOL existante -> Balance SOL existante
-> une seule case “SOL/USD moyen” -> “SOL/USD moyen”
-> valeur moyenne dérivée du refresh courant -> valeur moyenne dérivée du refresh courant
-> N.A. si aucune observation exploitable -> 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 -> bouton Refresh balance existant
-> getBalance On-chain -> getBalance On-chain
-> refresh_all Off-chain des providers éligibles -> refresh_all Off-chain des providers éligibles
@@ -262,7 +265,7 @@ Wallet Desk existing shell
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. 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.
**Valorisation USD du solde : toujours hors scope `0.2.12`.** La moyenne est seulement affichée comme information SOL/USD ; aucun calcul `balance SOL × prix` n'est introduit. **É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 ## 9. DTO map retenue
@@ -342,8 +345,8 @@ refresh_wallet_balance
-> en parallèle : MarketPriceService::refresh_all -> en parallèle : MarketPriceService::refresh_all
-> ne garder que les observations réussies du refresh courant -> ne garder que les observations réussies du refresh courant
-> moyenne arithmétique backend sans f64, arrondie à 8 décimales maximum -> moyenne arithmétique backend sans f64, arrondie à 8 décimales maximum
-> WalletBalanceDto { ..., sol_usd_average: Option<String> } -> WalletBalanceDto { ..., sol_usd_average: Option<String>, sol_usd_equivalent: Option<String> }
-> une seule case UI “SOL/USD moyen” ou “N.A.” -> 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`. 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`.
@@ -444,7 +447,7 @@ Canari obligatoire : `ksp-app-solprices-desk` référence son propre `package.js
| TS-RS | bindings exportés par tests; prix exact en String | | TS-RS | bindings exportés par tests; prix exact en String |
| workspace | fmt + audits + check + clippy + test | | workspace | fmt + audits + check + clippy + test |
| SOL Prices Desk dev | parcours splash -> table -> row/many/all sur composition réelle | | SOL Prices Desk dev | parcours splash -> table -> row/many/all sur composition réelle |
| Wallet Desk dev | refresh balance + une case moyenne SOL/USD/N.A. + non-régression Wallet | | 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 | | 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 | | production build | `cargo tauri build` crate-local pour SOL Prices Desk puis Wallet Desk |
@@ -459,7 +462,7 @@ OHLCV/historique/chart
SSE/WebSocket prix SSE/WebSocket prix
consensus/fallback automatique consensus/fallback automatique
provider canonique provider canonique
valorisation USD du solde Wallet panneau providers détaillé ou commandes prix dédiées dans Wallet Desk
auto-refresh/polling périodique auto-refresh/polling périodique
Store/worker/job prix Store/worker/job prix
éditeur Config dans Price Desk éditeur Config dans Price Desk
@@ -555,7 +558,7 @@ Finalisation de la table responsive sans changer le contrat de dispatch : résum
### `pre.008` — Intégration Wallet minimale + recalibrage de clôture ### `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 à confirmer.** **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. 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.
@@ -573,15 +576,15 @@ Correction du dernier écart fonctionnel de la tranche : le refresh produisait b
#### `pre.008-fix.003` — Correction du littéral de canari version dynamique #### `pre.008-fix.003` — Correction du littéral de canari version dynamique
**Statut : implémenté ; gate opérateur à confirmer.** **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. 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 ### `pre.009` — Hardening transversal + gate technique/live final
**Statut : planifié.** **Statut : implémenté ; gate opérateur live/build à confirmer.**
Fusion des anciens hardenings `pre.008`/`pre.012` et du gate `pre.013` : canaris batch hostile/capabilities/resources, non-régression des deux Desks, workspace complet, smoke live keyless de composition, et `cargo tauri build` final des deux applications modifiées. Aucun nouveau comportement métier n'est ouvert. 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/version/package 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. Les métadonnées npm/Tauri des deux Desks modifiés sont synchronisées avec `0.2.12-pre.9` pour les builds de production finaux. Aucun nouveau comportement métier n'est ouvert ; les seuls gates restant opérateur sont le workspace complet, le smoke live et les deux `cargo tauri build` crate-local.
### `pre.010` — Réconciliation documentaire + préparation de publication ### `pre.010` — Réconciliation documentaire + préparation de publication

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/015-V0_2_12_SOL_PRICES_DESK.md --> <!-- file: docs/validation/015-V0_2_12_SOL_PRICES_DESK.md -->
<!-- version: 16 --> <!-- version: 17 -->
# Validation `0.2.12` — SOL Prices Desk + intégration Wallet Desk # Validation `0.2.12` — SOL Prices Desk + intégration Wallet Desk
@@ -39,8 +39,8 @@ NOT RUN commande non exécutée dans le contexte indiqué
| threat model desktop | PASS | plan 019 section 16 | | threat model desktop | PASS | plan 019 section 16 |
| Wallet integration map | PASS | plan 019 section 12 | | Wallet integration map | PASS | plan 019 section 12 |
| auto-refresh | OUT | hors scope confirmé | | auto-refresh | OUT | hors scope confirmé |
| valorisation USD du solde | OUT | aucun prix canonique; hors scope confirmé | | équivalent USD du solde | PASS | demandé opérateur; calcul backend exact depuis moyenne du refresh |
| forecast souple recalibré | PASS | 15 prereleases + rel; plan 019 section 19 | | forecast souple recalibré | PASS | clôture condensée jusquà `pre.010` + `rel.001` |
| scope clôturable | PASS | pas de redécoupage requis avant `pre.002` | | scope clôturable | PASS | pas de redécoupage requis avant `pre.002` |
| code/scaffold fonctionnel ajouté en `pre.001` | OUT | interdit par le gate | | code/scaffold fonctionnel ajouté en `pre.001` | OUT | interdit par le gate |
@@ -58,9 +58,9 @@ NOT RUN commande non exécutée dans le contexte indiqué
| refresh selected/many | `MarketPriceService::refresh_many` | PASS | | refresh selected/many | `MarketPriceService::refresh_many` | PASS |
| refresh all | `MarketPriceService::refresh_all` | PASS | | refresh all | `MarketPriceService::refresh_all` | PASS |
| Diagnostics | profils/counts sûrs seulement | PASS | | Diagnostics | profils/counts sûrs seulement | PASS |
| Wallet prix moyen | une case `SOL/USD moyen` / `N.A.` | PASS | | Wallet prix moyen | `SOL/USD moyen` / `N.A.` | PASS |
| auto-refresh | aucun scheduler/polling | OUT | | auto-refresh | aucun scheduler/polling | OUT |
| Wallet USD valuation | aucune multiplication balance × prix | OUT | | Wallet équivalent USD | calcul Rust exact / `N.A.` | PASS |
## 4. DTO/command matrix ## 4. DTO/command matrix
@@ -69,7 +69,7 @@ NOT RUN commande non exécutée dans le contexte indiqué
| `MarketPriceRuntimeStatusDto` | profiles/counts sans secret | PASS | | `MarketPriceRuntimeStatusDto` | profiles/counts sans secret | PASS |
| `MarketPriceProviderRowDto` | ID/display/pair/semantics/auth/availability/price/timestamps/retry | PASS | | `MarketPriceProviderRowDto` | ID/display/pair/semantics/auth/availability/price/timestamps/retry | PASS |
| single refresh direct | provider_id opaque seulement | PASS | | single refresh direct | provider_id opaque seulement | PASS |
| `MarketPriceRefreshManyRequestDto` | liste dIDs opaques ; borne hostile à durcir en `pre.009` | PASS | | `MarketPriceRefreshManyRequestDto` | liste dIDs opaques ; garde app `1..=64` avant parsing | PASS |
| `MarketPriceRefreshResultDto` | rows/outcome classifiés | PASS | | `MarketPriceRefreshResultDto` | rows/outcome classifiés | PASS |
| runtime/list/refresh/many/all commands | aucun URL/header/API key/body | PASS | | runtime/list/refresh/many/all commands | aucun URL/header/API key/body | PASS |
| TS-RS exports | bindings générés non versionnés | PASS | | TS-RS exports | bindings générés non versionnés | PASS |
@@ -78,20 +78,20 @@ NOT RUN commande non exécutée dans le contexte indiqué
## 5. Config/composite matrix ## 5. Config/composite matrix
| Contrat | Valeur attendue | Statut | | Contrat | Valeur attendue | Statut |
|---------------------------------------|--------------------------------------------------------------|---------| |---------------------------------------|--------------------------------------------------------------|--------|
| Price Desk composite file | `config/composite.ksp-app-solprices-desk.json` | PASS | | Price Desk composite file | `config/composite.ksp-app-solprices-desk.json` | PASS |
| Price Desk file_id | `cfg.composite.ksp-app-solprices-desk` | PASS | | Price Desk file_id | `cfg.composite.ksp-app-solprices-desk` | PASS |
| schema | `schema.composite` existant | PASS | | schema | `schema.composite` existant | PASS |
| components | `logging`, `offchain_transport` | PASS | | components | `logging`, `offchain_transport` | PASS |
| default profile | `public_keyless` | PASS | | default profile | `public_keyless` | PASS |
| optional profile | `all_free` | PASS | | optional profile | `all_free` | PASS |
| test profile | `tests` -> offchain `public_keyless` | PASS | | test profile | `tests` -> offchain `public_keyless` | PASS |
| Wallet composite | ajoute offchain `public_keyless` à devnet/temporary/tests | PENDING | | Wallet composite | ajoute offchain `public_keyless` à devnet/temporary/tests | PASS |
| registry/resource count | 11 après nouveau composite | PASS | | registry/resource count | 11 après nouveau composite | PASS |
| registry + resources atomiques | Config + Config Desk + Wallet Desk + Price Desk même tranche | PASS | | registry + resources atomiques | Config + Config Desk + Wallet Desk + Price Desk même tranche | PASS |
| nouveau schema Config | inutile | OUT | | nouveau schema Config | inutile | OUT |
| modification `std.offchain_transport` | pas de lacune identifiée | OUT | | modification `std.offchain_transport` | pas de lacune identifiée | OUT |
## 6. Provider-agnostic boundary matrix ## 6. Provider-agnostic boundary matrix
@@ -109,22 +109,22 @@ NOT RUN commande non exécutée dans le contexte indiqué
## 7. Security matrix ## 7. Security matrix
| Risque/canari | Attendu | Statut | | Risque/canari | Attendu | Statut |
|--------------------------------|-------------------------------------------------|---------| |--------------------------------|-------------------------------------------------------|--------|
| URL arbitraire depuis frontend | impossible par contrat command | PASS | | URL arbitraire depuis frontend | impossible par contrat command | PASS |
| extraction API key/header | aucune projection/log | PASS | | extraction API key/header | aucune projection/log | PASS |
| provider_id inconnu | erreur KSP sûre | PASS | | provider_id inconnu | erreur KSP sûre | PASS |
| batch duplicate | rejet avant dispatch | PASS | | batch duplicate | rejet avant dispatch | PASS |
| batch oversized | borne hostile applicative à durcir en `pre.009` | PENDING | | batch oversized | garde app `1..=64` avant parsing/dispatch | PASS |
| refresh spam | limites/cooldown Off-chain toujours actifs | PASS | | refresh spam | limites/cooldown Off-chain toujours actifs | PASS |
| stale observation | timestamps/état distingués | PASS | | stale observation | timestamps/état distingués | PASS |
| double-click race | in-flight/order contrôlés | PASS | | double-click race | in-flight/order contrôlés | PASS |
| close pendant refresh | aucune persistance/sûreté frontend requise | PENDING | | close pendant refresh | état uniquement mémoire ; aucune persistance frontend | PASS |
| CWD/resource confusion | canaris propres à chaque app | PENDING | | CWD/resource confusion | frontendDist/cwd/ports/resources canarisés par app | PASS |
| Price Desk capabilities | `core:default`, `tracing:default` uniquement | PASS | | Price Desk capabilities | `core:default`, `tracing:default` uniquement | PASS |
| plugin dialog Price Desk | absent | PASS | | plugin dialog Price Desk | absent | PASS |
| frontend sensitive logging | action/ID/count/code seulement | PASS | | frontend sensitive logging | action/ID/count/code seulement | PASS |
| Wallet VIEW/OWNER/secrets | non-régression | PENDING | | Wallet VIEW/OWNER/secrets | suite Wallet + compliance inchangées | PASS |
## 8. Wallet integration matrix ## 8. Wallet integration matrix
@@ -134,16 +134,16 @@ NOT RUN commande non exécutée dans le contexte indiqué
| Config component | Wallet composite -> `cfg.std.offchain_transport/public_keyless` | PASS | | Config component | Wallet composite -> `cfg.std.offchain_transport/public_keyless` | PASS |
| runtime | Config -> `ResolvedOffchainTransportConfig` -> `MarketPriceService` | PASS | | runtime | Config -> `ResolvedOffchainTransportConfig` -> `MarketPriceService` | PASS |
| command | `refresh_wallet_balance` existante, aucun input prix/provider | PASS | | command | `refresh_wallet_balance` existante, aucun input prix/provider | PASS |
| DTO | `WalletBalanceDto.sol_usd_average: Option<String>` | PASS | | DTO | moyenne + équivalent USD en `Option<String>` exacts | PASS |
| moyenne | observations réussies du refresh courant, backend sans `f64`, 8 déc. max | PASS | | moyenne | observations réussies du refresh courant, backend sans `f64`, 8 déc. max | PASS |
| UI | une seule case `SOL/USD moyen` / `N.A.` | PASS | | UI | résumé compact `SOL/USD moyen` + `Équivalent USD` / `N.A.` | PASS |
| balance HTTP On-chain | `getBalance` inchangé et toujours autoritatif | PASS | | balance HTTP On-chain | `getBalance` inchangé et toujours autoritatif | PASS |
| dégradation prix | échec/absence prix -> `N.A.` sans faire échouer la balance | PASS | | dégradation prix | échec/absence prix -> `N.A.` sans faire échouer la balance | PASS |
| WalletSession VIEW/OWNER | inchangée | PASS | | WalletSession VIEW/OWNER | inchangée | PASS |
| Wallet secret projections | aucune nouvelle | PASS | | Wallet secret projections | aucune nouvelle | PASS |
| provider ID frontend | aucun | PASS | | provider ID frontend | aucun | PASS |
| command prix Wallet | aucune | PASS | | command prix Wallet | aucune | PASS |
| USD balance multiplication | hors scope | OUT | | USD balance multiplication | calcul Rust exact depuis lamports + moyenne du refresh courant | PASS |
## 9. Tauri/package matrix ## 9. Tauri/package matrix
@@ -158,6 +158,7 @@ NOT RUN commande non exécutée dans le contexte indiqué
| backend cross-app | aucune référence Wallet par erreur | PASS | | backend cross-app | aucune référence Wallet par erreur | PASS |
| resources | registry complet packagé | PASS | | resources | registry complet packagé | PASS |
| bundle | targets/icons cohérents | PASS | | bundle | targets/icons cohérents | PASS |
| versions apps modifiées | npm/Tauri = Cargo `0.2.12-pre.9` | PASS |
| Tauri production build Price Desk | crate-local PASS attendu | PENDING | | Tauri production build Price Desk | crate-local PASS attendu | PENDING |
| Tauri production build Wallet Desk | crate-local PASS attendu | PENDING | | Tauri production build Wallet Desk | crate-local PASS attendu | PENDING |
@@ -171,8 +172,8 @@ NOT RUN commande non exécutée dans le contexte indiqué
| `cargo check --workspace` | PENDING | | `cargo check --workspace` | PENDING |
| `cargo clippy --workspace --all-targets` | PENDING | | `cargo clippy --workspace --all-targets` | PENDING |
| `cargo test --workspace` | PENDING | | `cargo test --workspace` | PENDING |
| Price Desk functional dev path | PENDING | | Price Desk functional dev path | PASS |
| Wallet Desk functional price path | PENDING | | Wallet Desk functional price path | PASS |
| keyless app-composition live smoke | PENDING | | keyless app-composition live smoke | PENDING |
| Price Desk `cargo tauri build` | PENDING | | Price Desk `cargo tauri build` | PENDING |
| Wallet Desk `cargo tauri build` | PENDING | | Wallet Desk `cargo tauri build` | PENDING |
@@ -726,3 +727,70 @@ cargo test -p ksp-app-wallet-desk
cargo test --workspace cargo test --workspace
``` ```
## 28. Gate `0.2.12-pre.009` — hardening transversal et gate final
Le gate opérateur de `pre.008-fix.003` ferme définitivement la tranche Wallet condensée :
```text
cargo fmt --all PASS
python3 scripts/audit_rust_workspace_rules.py PASS
python3 scripts/audit_markdown_tables.py README.md ... deltas PASS
cargo check --workspace PASS
cargo clippy --workspace --all-targets PASS
cargo test -p ksp-app-wallet-desk PASS
cargo test --workspace PASS
```
Le parcours fonctionnel avait déjà confirmé sous `fix.002` puis `fix.003` la moyenne SOL/USD, l'équivalent USD, la version dynamique et le refresh balance réel.
`pre.009` ajoute les hardenings finaux suivants sans ouvrir de nouvelle fonctionnalité :
```text
Price Desk batch IPC garde 1..=64 avant parsing des provider IDs
batch vide rejet KSP sûr
batch hostile 65 IDs rejet KSP sûr avant parsing provider
Price Desk manual refresh aucun scheduler/fetch/browser persistence
Price Desk package metadata npm/Tauri == CARGO_PKG_VERSION
Wallet package metadata npm/Tauri == CARGO_PKG_VERSION
Wallet frontendDist chemin Wallet dédié canarisé
Wallet provider-neutrality aucun type provider concret dans src/
Wallet exactness aucun f64/f32 backend, aucun calcul flottant frontend
keyless live composition nouveau smoke opt-in SOL Prices Desk
```
Le smoke live dédié est :
```bash
cargo test -p ksp-app-solprices-desk --test market_price_composition_live_smoke -- --ignored --nocapture
```
Validations sandbox après matérialisation de la tranche :
```text
python3 scripts/audit_rust_workspace_rules.py PASS
python3 scripts/audit_markdown_tables.py README.md ... deltas PASS
JSON package/tauri des deux apps modifiées PASS
contrôles statiques versions/resources/frontières PASS
cargo fmt/check/clippy/test/live/tauri build NOT RUN — cargo indisponible
```
Il doit résoudre le composite committed `public_keyless`, mapper `ResolvedOffchainTransportConfig`, conserver exactement les huit entrées génériques du registry, exécuter `MarketPriceService::refresh_all` et obtenir au moins une observation SOL/USD. Il ne réaudite pas les huit wire adapters individuellement.
Gate opérateur final à exécuter, dans cet ordre :
```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 -p ksp-app-wallet-desk
cargo test --workspace
cargo test -p ksp-app-solprices-desk --test market_price_composition_live_smoke -- --ignored --nocapture
(cd crates/ksp-app-solprices-desk && cargo tauri build)
(cd crates/ksp-app-wallet-desk && cargo tauri build)
```
Conformément à `KSP-APP-034`, les deux `cargo tauri build` restent les dernières opérations lourdes.