From 5a165296d985a498ddb04d57cffa2d8d748e22de Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Thu, 27 Aug 2026 09:33:29 +0200 Subject: [PATCH] v0.2.12-pre.003 --- Cargo.toml | 4 +- config/composite.ksp-app-solprices-desk.json | 51 ++++ crates/ksp-app-config-desk/tauri.conf.json | 1 + .../tests/desktop_contract.rs | 8 +- .../ksp-app-solprices-desk/frontend/main.html | 12 +- .../frontend/ts/main.ts | 5 +- crates/ksp-app-solprices-desk/package.json | 2 +- .../ksp-app-solprices-desk/src/app_state.rs | 24 +- .../ksp-app-solprices-desk/src/bootstrap.rs | 72 ++++- .../ksp-app-solprices-desk/src/constants.rs | 10 +- .../ksp-app-solprices-desk/src/dto_common.rs | 20 +- crates/ksp-app-solprices-desk/src/errors.rs | 6 +- crates/ksp-app-solprices-desk/src/lib.rs | 19 +- .../src/offchain_runtime.rs | 59 +++++ crates/ksp-app-solprices-desk/tauri.conf.json | 3 +- .../tests/config_composition.rs | 94 +++++++ .../tests/desktop_contract.rs | 17 +- .../tests/desktop_security.rs | 25 +- crates/ksp-app-wallet-desk/tauri.conf.json | 1 + .../tests/desktop_contract.rs | 8 +- .../tests/release_compliance.rs | 4 +- crates/ksp-config-lib/src/lib.rs | 6 +- crates/ksp-config-lib/src/registry.rs | 23 +- crates/ksp-config-lib/tests/ownership.rs | 3 +- crates/ksp-config-lib/tests/public_api.rs | 29 +- .../unit_tests/offchain_transport.rs | 41 ++- crates/ksp-config-lib/unit_tests/registry.rs | 65 +++-- deltas/0.2.12/pre.003.md | 250 ++++++++++++++++++ .../plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md | 8 +- .../validation/015-V0_2_12_SOL_PRICES_DESK.md | 47 +++- 30 files changed, 806 insertions(+), 111 deletions(-) create mode 100644 config/composite.ksp-app-solprices-desk.json create mode 100644 crates/ksp-app-solprices-desk/src/offchain_runtime.rs create mode 100644 crates/ksp-app-solprices-desk/tests/config_composition.rs create mode 100644 deltas/0.2.12/pre.003.md diff --git a/Cargo.toml b/Cargo.toml index 9ebe9f9..c4f1aa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 288 +# version: 289 [workspace] 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"] [workspace.package] -version = "0.2.12-pre.2.fix.1" +version = "0.2.12-pre.3" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/config/composite.ksp-app-solprices-desk.json b/config/composite.ksp-app-solprices-desk.json new file mode 100644 index 0000000..a31d24a --- /dev/null +++ b/config/composite.ksp-app-solprices-desk.json @@ -0,0 +1,51 @@ +{ + "format_version": 1, + "default_profile": "public_keyless", + "profiles": [ + { + "profile_id": "public_keyless", + "documents": [ + { + "component_id": "logging", + "file_id": "cfg.std.logging", + "profile_id": "console_info" + }, + { + "component_id": "offchain_transport", + "file_id": "cfg.std.offchain_transport", + "profile_id": "public_keyless" + } + ] + }, + { + "profile_id": "all_free", + "documents": [ + { + "component_id": "logging", + "file_id": "cfg.std.logging", + "profile_id": "console_info" + }, + { + "component_id": "offchain_transport", + "file_id": "cfg.std.offchain_transport", + "profile_id": "all_free" + } + ] + }, + { + "profile_id": "tests", + "documents": [ + { + "component_id": "logging", + "file_id": "cfg.std.logging", + "profile_id": "console_info" + }, + { + "component_id": "offchain_transport", + "file_id": "cfg.std.offchain_transport", + "profile_id": "public_keyless" + } + ] + } + ] +} diff --git a/crates/ksp-app-config-desk/tauri.conf.json b/crates/ksp-app-config-desk/tauri.conf.json index 0619a83..e38b723 100644 --- a/crates/ksp-app-config-desk/tauri.conf.json +++ b/crates/ksp-app-config-desk/tauri.conf.json @@ -53,6 +53,7 @@ "icons/favicon.ico" ], "resources": { + "../../config/composite.ksp-app-solprices-desk.json": "config/composite.ksp-app-solprices-desk.json", "../../config/composite.ksp-app-wallet-desk.json": "config/composite.ksp-app-wallet-desk.json", "../../config/std.logging.json": "config/std.logging.json", "../../config/std.offchain_transport.json": "config/std.offchain_transport.json", diff --git a/crates/ksp-app-config-desk/tests/desktop_contract.rs b/crates/ksp-app-config-desk/tests/desktop_contract.rs index 5768af7..b36b835 100644 --- a/crates/ksp-app-config-desk/tests/desktop_contract.rs +++ b/crates/ksp-app-config-desk/tests/desktop_contract.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-config-desk/tests/desktop_contract.rs -// version: 8 +// version: 9 //! Desktop build/shell contract audits for Config Desk. @@ -150,7 +150,11 @@ fn pre_018_packaged_runtime_bundles_config_resources_and_activates_shared_writab let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object); assert!(resources.is_some(), "packaged Config resources map must exist"); if let std::option::Option::Some(resources) = resources { - assert_eq!(resources.len(), 10); + assert_eq!(resources.len(), 11); + assert_eq!( + resources.get("../../config/composite.ksp-app-solprices-desk.json").and_then(serde_json::Value::as_str), + std::option::Option::Some("config/composite.ksp-app-solprices-desk.json"), + ); assert_eq!(resources.get("../../config/std.logging.json").and_then(serde_json::Value::as_str), std::option::Option::Some("config/std.logging.json")); assert_eq!( resources.get("../../config/std.offchain_transport.json").and_then(serde_json::Value::as_str), diff --git a/crates/ksp-app-solprices-desk/frontend/main.html b/crates/ksp-app-solprices-desk/frontend/main.html index 3fef123..f9dd7d6 100644 --- a/crates/ksp-app-solprices-desk/frontend/main.html +++ b/crates/ksp-app-solprices-desk/frontend/main.html @@ -1,5 +1,5 @@ - + @@ -47,14 +47,14 @@

Prices

-

Scaffold desktop actif. Le runtime Off-chain et la table SOL/USD arrivent dans les tranches suivantes.

+

Composition Config/Off-chain active. La projection provider-neutral du registry arrive dans la tranche suivante.

Surface prix volontairement inactive

-

`pre.002` ne crée ni `MarketPriceService`, ni table provider, ni refresh réseau.

+

`pre.003` initialise le service via Config, mais ne crée encore ni lignes provider, ni observation affichée, ni refresh réseau déclenché par l’UI.

@@ -62,7 +62,7 @@

Diagnostics

-

État sûr du scaffold uniquement.

+

État sûr du bootstrap Config, Logging et Off-chain Transport.

@@ -75,6 +75,10 @@
Documents Config
+
Profil composite
+
+
Profil Off-chain
+
Profil Logging
Fallback Logging
diff --git a/crates/ksp-app-solprices-desk/frontend/ts/main.ts b/crates/ksp-app-solprices-desk/frontend/ts/main.ts index 89f099d..2d58abf 100644 --- a/crates/ksp-app-solprices-desk/frontend/ts/main.ts +++ b/crates/ksp-app-solprices-desk/frontend/ts/main.ts @@ -1,5 +1,5 @@ // file: crates/ksp-app-solprices-desk/frontend/ts/main.ts -// version: 2 +// version: 3 import "bootstrap"; import ResizeObserver from "resize-observer-polyfill"; @@ -44,6 +44,7 @@ function bindNavigation(): void { document.querySelectorAll("[data-view]").forEach(button => { button.addEventListener("click", () => { const viewId = button.dataset.view; + frontendDebug("main", "SOL Prices Desk navigation control clicked", { viewId: viewId ?? "unknown" }); if (isViewId(viewId)) { activateView(viewId, "user"); } @@ -54,6 +55,8 @@ function bindNavigation(): void { function renderRuntimeStatus(status: RuntimeStatusDto): void { const values: Record = { + runtimeCompositeProfile: status.activeCompositeProfile, + runtimeOffchainProfile: status.activeOffchainProfile, runtimeVersion: status.applicationVersion, runtimeShellPhase: status.shellPhase, runtimeConfigDocuments: status.configDocumentCount.toString(), diff --git a/crates/ksp-app-solprices-desk/package.json b/crates/ksp-app-solprices-desk/package.json index 96fe2ba..09f75c9 100644 --- a/crates/ksp-app-solprices-desk/package.json +++ b/crates/ksp-app-solprices-desk/package.json @@ -1,7 +1,7 @@ { "name": "ksp-app-solprices-desk", "private": true, - "version": "0.2.12-pre.2.fix.1", + "version": "0.2.12-pre.3", "type": "module", "scripts": { "dev": "vite", diff --git a/crates/ksp-app-solprices-desk/src/app_state.rs b/crates/ksp-app-solprices-desk/src/app_state.rs index 18a8223..687b6d4 100644 --- a/crates/ksp-app-solprices-desk/src/app_state.rs +++ b/crates/ksp-app-solprices-desk/src/app_state.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-solprices-desk/src/app_state.rs -// version: 1 +// version: 2 //! Shared backend state owned by the SOL Prices Desk Tauri application. @@ -7,12 +7,13 @@ pub(crate) struct AppState { config_management: ksp_config_lib::ConfigManagement, logging_runtime: std::sync::Mutex, + offchain_transport_startup: crate::OffchainTransportStartup, splash_settings: crate::SplashSettings, splash_sequence_started: std::sync::atomic::AtomicBool, } -impl AppState { - /// Initializes Config ownership, Logging and the common desktop splash state without creating the Off-chain runtime reserved for `pre.003`. +impl crate::AppState { + /// Initializes Config ownership, composite-managed Logging and the Config-selected Off-chain Transport runtime. pub(crate) fn initialize(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result { let config_management = crate::config_management(arguments); let config_management = match config_management { @@ -29,6 +30,11 @@ impl AppState { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; + let offchain_transport_startup = crate::initialize_offchain_transport(&config_management); + let offchain_transport_startup = match offchain_transport_startup { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; let splash_settings = crate::SplashSettings::load(); let splash_settings = match splash_settings { std::result::Result::Ok(value) => value, @@ -46,12 +52,13 @@ impl AppState { fallback_active: logging_startup.fallback_active, startup_diagnostic: logging_startup.startup_diagnostic, }), + offchain_transport_startup, splash_settings, splash_sequence_started: std::sync::atomic::AtomicBool::new(false), }); } - /// Builds the safe scaffold runtime status exposed before market-price composition is introduced. + /// Builds the safe runtime status exposed before provider rows and refresh commands are introduced. pub(crate) fn runtime_status(&self) -> ksp_core_lib::Result { let document_count = self.config_management.engine().registry().descriptors().count(); let document_count = u32::try_from(document_count); @@ -61,7 +68,7 @@ impl AppState { return std::result::Result::Err( ksp_core_lib::Error::new( crate::ERROR_CODE_APP_STATE_INVALID, - "Config registry contains too many descriptors for the SOL Prices Desk scaffold DTO", + "Config registry contains too many descriptors for the SOL Prices Desk runtime DTO", ) .with_source(error), ); @@ -78,12 +85,15 @@ impl AppState { }, }; let _keep_guard_alive = &runtime.guard; + let offchain = self.offchain_transport_startup.resolved(); return std::result::Result::Ok(crate::RuntimeStatusDto { - application_version: env!("CARGO_PKG_VERSION").to_owned(), + active_composite_profile: self.offchain_transport_startup.composite_profile_id().to_owned(), active_logging_profile: runtime.active_profile_id.clone(), + active_offchain_profile: offchain.profile_id().to_owned(), + application_version: env!("CARGO_PKG_VERSION").to_owned(), config_document_count: document_count, fallback_logging_active: runtime.fallback_active, - shell_phase: "pre.002-scaffold".to_owned(), + shell_phase: "pre.003-offchain-bootstrap".to_owned(), startup_diagnostic: runtime.startup_diagnostic.clone(), }); } diff --git a/crates/ksp-app-solprices-desk/src/bootstrap.rs b/crates/ksp-app-solprices-desk/src/bootstrap.rs index 8231499..491852c 100644 --- a/crates/ksp-app-solprices-desk/src/bootstrap.rs +++ b/crates/ksp-app-solprices-desk/src/bootstrap.rs @@ -1,7 +1,7 @@ // file: crates/ksp-app-solprices-desk/src/bootstrap.rs -// version: 1 +// version: 2 -//! Config and Logging bootstrap for the SOL Prices Desk scaffold. +//! Config composite and Logging bootstrap for SOL Prices Desk. /// Crate-internal Logging startup state shared by the application state. pub(crate) struct LoggingStartup { @@ -43,7 +43,51 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine)); } -/// Initializes Logging from the standard Config document until the SOL Prices composite is introduced by `pre.003`. +/// Loads the concrete SOL Prices Desk Config composite using its registered logical `file_id` and autonomous default profile. +pub(crate) fn load_solprices_desk_composite(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result { + let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK); + let file_id = match file_id { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return management.engine().load_resolved_composite(&file_id, std::option::Option::None); +} + +/// Returns one required standard profile from the SOL Prices Desk composite after validating the referenced logical `file_id`. +pub(crate) fn required_composite_component_profile( + composite: &ksp_config_lib::ResolvedConfigComposite, + component_id: &str, + expected_file_id: &str, +) -> ksp_core_lib::Result { + let component = composite.component(component_id); + let component = match component { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err( + ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "SOL Prices Desk composite is missing a required component") + .with_context("composite_file_id", composite.file_id().as_str()) + .with_context("composite_profile_id", composite.profile_id()) + .with_context("component_id", component_id), + ); + }, + }; + if component.resolved().file_id().as_str() != expected_file_id { + return std::result::Result::Err( + ksp_core_lib::Error::new( + crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, + "SOL Prices Desk composite component references an unexpected Config document", + ) + .with_context("composite_file_id", composite.file_id().as_str()) + .with_context("composite_profile_id", composite.profile_id()) + .with_context("component_id", component_id) + .with_context("expected_file_id", expected_file_id) + .with_context("actual_file_id", component.resolved().file_id().as_str()), + ); + } + return std::result::Result::Ok(component.resolved().clone()); +} + +/// Initializes Logging from the SOL Prices Desk composite, with the same bounded fallback policy as the other KSP desks. pub(crate) fn initialize_logging( management: &ksp_config_lib::ConfigManagement, runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity, @@ -54,7 +98,7 @@ pub(crate) fn initialize_logging( let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity); match guard { std::result::Result::Ok(guard) => { - ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, active_profile = active_profile_id.as_str(), "initialized SOL Prices Desk scaffold logging from managed configuration"); + ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, active_profile = active_profile_id.as_str(), "initialized SOL Prices Desk logging from composite-managed configuration"); std::result::Result::Ok(LoggingStartup { guard, active_profile_id: std::option::Option::Some(active_profile_id), @@ -86,7 +130,25 @@ fn resolve_logging_startup(management: &ksp_config_lib::ConfigManagement) -> Log std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return fallback_startup_plan(error), }; - let resolved = management.engine().load_resolved_logging_config(std::option::Option::None, &environment); + let composite = crate::load_solprices_desk_composite(management); + let composite = match composite { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return fallback_startup_plan(error), + }; + let logging_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_LOGGING, ksp_config_lib::FILE_ID_STD_LOGGING); + let logging_profile = match logging_profile { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return fallback_startup_plan(error), + }; + let offchain_profile = crate::required_composite_component_profile( + &composite, + crate::COMPOSITE_COMPONENT_ID_OFFCHAIN_TRANSPORT, + ksp_config_lib::FILE_ID_STD_OFFCHAIN_TRANSPORT, + ); + if let std::result::Result::Err(error) = offchain_profile { + return fallback_startup_plan(error); + } + let resolved = management.engine().resolve_logging_config_profile(&logging_profile, &environment); let resolved = match resolved { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return fallback_startup_plan(error), diff --git a/crates/ksp-app-solprices-desk/src/constants.rs b/crates/ksp-app-solprices-desk/src/constants.rs index 2f9ee40..c74dffc 100644 --- a/crates/ksp-app-solprices-desk/src/constants.rs +++ b/crates/ksp-app-solprices-desk/src/constants.rs @@ -1,12 +1,18 @@ // file: crates/ksp-app-solprices-desk/src/constants.rs -// version: 1 +// version: 2 -//! Application-owned tracing targets and domains. +//! Application-owned composition identifiers, tracing targets and domains. +/// Composite-local identifier for the standard Logging component. +pub(crate) const COMPOSITE_COMPONENT_ID_LOGGING: &str = "logging"; +/// Composite-local identifier for the standard Off-chain Transport component. +pub(crate) const COMPOSITE_COMPONENT_ID_OFFCHAIN_TRANSPORT: &str = "offchain_transport"; /// Structured domain used while bootstrapping Config and Logging. pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "solprices.bootstrap"; /// Structured domain used by technical frontend events. pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend"; +/// Structured domain used by the Config-selected Off-chain Transport runtime. +pub(crate) const TRACING_DOMAIN_OFFCHAIN_TRANSPORT: &str = "solprices.offchain_transport"; /// Structured domain used by the SOL Prices Desk shell. pub(crate) const TRACING_DOMAIN_SHELL: &str = "solprices.shell"; /// Structured domain used by Tauri window lifecycle operations. diff --git a/crates/ksp-app-solprices-desk/src/dto_common.rs b/crates/ksp-app-solprices-desk/src/dto_common.rs index 310123d..0fde365 100644 --- a/crates/ksp-app-solprices-desk/src/dto_common.rs +++ b/crates/ksp-app-solprices-desk/src/dto_common.rs @@ -1,7 +1,7 @@ // file: crates/ksp-app-solprices-desk/src/dto_common.rs -// version: 1 +// version: 2 -//! Common Tauri DTOs shared by the SOL Prices Desk scaffold. +//! Common Tauri DTOs shared by the SOL Prices Desk bootstrap shell. use ts_rs::TS; // rust-rules: trait-import @@ -18,7 +18,7 @@ pub(crate) struct CommandErrorDto { pub(crate) message: String, } -impl CommandErrorDto { +impl crate::CommandErrorDto { /// Builds a bounded safe projection from a KSP error. #[must_use] pub(crate) fn from_error(error: &ksp_core_lib::Error) -> Self { @@ -30,20 +30,24 @@ impl CommandErrorDto { } } -/// Safe scaffold/runtime snapshot exposed to the SOL Prices Desk shell before Off-chain bootstrap exists. +/// Safe Config/bootstrap snapshot exposed before market-price provider rows exist. #[derive(Clone, Debug, serde::Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export, export_to = "../frontend/ts/bindings/ksp_app_solprices_desk/dto_common/RuntimeStatusDto.ts")] pub(crate) struct RuntimeStatusDto { - /// Cargo application version. - pub(crate) application_version: String, + /// SOL Prices Desk composite profile selected for this launch. + pub(crate) active_composite_profile: String, /// Active configured Logging profile, or `None` while transient fallback Logging is active. pub(crate) active_logging_profile: std::option::Option, - /// Number of logical Config resources registered by the current scaffold runtime. + /// Standard Off-chain Transport profile selected by the active composite. + pub(crate) active_offchain_profile: String, + /// Cargo application version. + pub(crate) application_version: String, + /// Number of logical Config resources registered by the current runtime. pub(crate) config_document_count: u32, /// Whether SOL Prices Desk had to install its transient in-memory Logging fallback. pub(crate) fallback_logging_active: bool, - /// Current implementation phase exposed by the scaffold shell. + /// Current implementation phase exposed by the bootstrap shell. pub(crate) shell_phase: String, /// Safe startup diagnostic that caused fallback Logging, when applicable. pub(crate) startup_diagnostic: std::option::Option, diff --git a/crates/ksp-app-solprices-desk/src/errors.rs b/crates/ksp-app-solprices-desk/src/errors.rs index 6617431..fb1a356 100644 --- a/crates/ksp-app-solprices-desk/src/errors.rs +++ b/crates/ksp-app-solprices-desk/src/errors.rs @@ -1,12 +1,14 @@ // file: crates/ksp-app-solprices-desk/src/errors.rs -// version: 1 +// version: 2 -//! Application-local error codes for SOL Prices Desk scaffold and desktop runtime surfaces. +//! Application-local error codes for SOL Prices Desk composition and desktop runtime surfaces. /// Shared SOL Prices Desk application state is internally inconsistent. pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("solprices_desk", "app_state_invalid"); /// Shared SOL Prices Desk runtime state cannot be locked safely. pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("solprices_desk", "app_state_lock_failed"); +/// The SOL Prices Desk Config composite is missing or misroutes one required standard component. +pub(crate) const ERROR_CODE_CONFIG_COMPOSITE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("solprices_desk", "config_composite_invalid"); /// Frontend logging requested an unsupported level. pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("solprices_desk", "frontend_log_level_invalid"); /// Frontend logging requested a target outside the application whitelist. diff --git a/crates/ksp-app-solprices-desk/src/lib.rs b/crates/ksp-app-solprices-desk/src/lib.rs index e9c357a..d4cc89e 100644 --- a/crates/ksp-app-solprices-desk/src/lib.rs +++ b/crates/ksp-app-solprices-desk/src/lib.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-solprices-desk/src/lib.rs -// version: 1 +// version: 2 //! Tauri desktop application scaffold for provider-neutral SOL/USD price visualization. @@ -14,6 +14,7 @@ mod dto_common; mod errors; mod frontend_logging; mod logging_runtime; +mod offchain_runtime; mod splash; mod tauri; mod tw_main; @@ -30,10 +31,20 @@ pub(crate) use self::bootstrap::LoggingStartup; pub(crate) use self::bootstrap::config_management; /// Initializes the scaffold Logging runtime from Config, with a bounded in-memory fallback. pub(crate) use self::bootstrap::initialize_logging; +/// Loads the SOL Prices Desk composite selected by its registered logical file identifier. +pub(crate) use self::bootstrap::load_solprices_desk_composite; +/// Resolves one required standard Config profile from the SOL Prices Desk composite. +pub(crate) use self::bootstrap::required_composite_component_profile; +/// Composite-local identifier for the standard Logging component. +pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_LOGGING; +/// Composite-local identifier for the standard Off-chain Transport component. +pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_OFFCHAIN_TRANSPORT; /// Structured domain used while bootstrapping Config and Logging. pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP; /// Structured domain used by technical frontend events. pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND; +/// Structured domain used by the Config-selected Off-chain Transport runtime. +pub(crate) use self::constants::TRACING_DOMAIN_OFFCHAIN_TRANSPORT; /// Structured domain used by the SOL Prices Desk shell. pub(crate) use self::constants::TRACING_DOMAIN_SHELL; /// Structured domain used by Tauri window lifecycle operations. @@ -54,6 +65,8 @@ pub(crate) use self::dto_common::RuntimeStatusDto; pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID; /// Shared SOL Prices Desk runtime state cannot be locked safely. pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED; +/// The SOL Prices Desk Config composite is missing or misroutes a required component. +pub(crate) use self::errors::ERROR_CODE_CONFIG_COMPOSITE_INVALID; /// Frontend logging requested an unsupported level. pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID; /// Frontend logging requested a target outside the application whitelist. @@ -76,6 +89,10 @@ pub(crate) use self::frontend_logging::FrontendLogPayloadDto; pub(crate) use self::frontend_logging::emit_frontend_log_event; /// Creates the stable runtime identity for this SOL Prices Desk process launch. pub(crate) use self::logging_runtime::launch_identity; +/// Resolved composite and Off-chain Transport configuration retained by SOL Prices Desk. +pub(crate) use self::offchain_runtime::OffchainTransportStartup; +/// Resolves the composite-selected Off-chain Transport profile and constructs the Config-owned service. +pub(crate) use self::offchain_runtime::initialize_offchain_transport; /// Command emitted by Rust to the splash frontend. pub(crate) use self::splash::SplashOrderDto; /// Runtime timings used by the common desk splash lifecycle. diff --git a/crates/ksp-app-solprices-desk/src/offchain_runtime.rs b/crates/ksp-app-solprices-desk/src/offchain_runtime.rs new file mode 100644 index 0000000..b1e83ba --- /dev/null +++ b/crates/ksp-app-solprices-desk/src/offchain_runtime.rs @@ -0,0 +1,59 @@ +// file: crates/ksp-app-solprices-desk/src/offchain_runtime.rs +// version: 1 + +//! Config-owned Off-chain Transport bootstrap for SOL Prices Desk. + +/// Resolved SOL Prices Desk composite and Off-chain Transport configuration retained by application state. +pub(crate) struct OffchainTransportStartup { + composite_profile_id: String, + resolved: ksp_config_lib::ResolvedOffchainTransportConfig, +} + +impl crate::OffchainTransportStartup { + /// Returns the composite profile selected for this application launch. + #[must_use] + pub(crate) fn composite_profile_id(&self) -> &str { + return self.composite_profile_id.as_str(); + } + + /// Returns the Config-resolved Off-chain Transport runtime configuration. + #[must_use] + pub(crate) const fn resolved(&self) -> &ksp_config_lib::ResolvedOffchainTransportConfig { + return &self.resolved; + } +} + +/// Resolves the composite-selected Off-chain Transport profile and constructs the provider-neutral service owned by Config. +pub(crate) fn initialize_offchain_transport(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result { + let environment = ksp_config_lib::ConfigEnvironment::load(); + let environment = match environment { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let composite = crate::load_solprices_desk_composite(management); + let composite = match composite { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let logging_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_LOGGING, ksp_config_lib::FILE_ID_STD_LOGGING); + if let std::result::Result::Err(error) = logging_profile { + return std::result::Result::Err(error); + } + let offchain_profile = crate::required_composite_component_profile( + &composite, + crate::COMPOSITE_COMPONENT_ID_OFFCHAIN_TRANSPORT, + ksp_config_lib::FILE_ID_STD_OFFCHAIN_TRANSPORT, + ); + let offchain_profile = match offchain_profile { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let resolved = management.engine().resolve_offchain_transport_config_profile(&offchain_profile, &environment); + let resolved = match resolved { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let provider_count = resolved.service().registry().len(); + ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_OFFCHAIN_TRANSPORT, composite_profile = composite.profile_id(), offchain_profile = resolved.profile_id(), provider_count, "initialized SOL Prices Desk Off-chain Transport from composite-managed configuration"); + return std::result::Result::Ok(crate::OffchainTransportStartup { composite_profile_id: composite.profile_id().to_owned(), resolved }); +} diff --git a/crates/ksp-app-solprices-desk/tauri.conf.json b/crates/ksp-app-solprices-desk/tauri.conf.json index c8257dc..f67cbda 100644 --- a/crates/ksp-app-solprices-desk/tauri.conf.json +++ b/crates/ksp-app-solprices-desk/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "KSP SOL Prices Desk", - "version": "0.2.12-pre.2.fix.1", + "version": "0.2.12-pre.3", "identifier": "com.sasedev.ksp-app-solprices-desk", "build": { "beforeDevCommand": { @@ -53,6 +53,7 @@ "icons/favicon.ico" ], "resources": { + "../../config/composite.ksp-app-solprices-desk.json": "config/composite.ksp-app-solprices-desk.json", "../../config/composite.ksp-app-wallet-desk.json": "config/composite.ksp-app-wallet-desk.json", "../../config/std.logging.json": "config/std.logging.json", "../../config/std.offchain_transport.json": "config/std.offchain_transport.json", diff --git a/crates/ksp-app-solprices-desk/tests/config_composition.rs b/crates/ksp-app-solprices-desk/tests/config_composition.rs new file mode 100644 index 0000000..ac56434 --- /dev/null +++ b/crates/ksp-app-solprices-desk/tests/config_composition.rs @@ -0,0 +1,94 @@ +// file: crates/ksp-app-solprices-desk/tests/config_composition.rs +// version: 1 + +//! Config composition contracts for SOL Prices Desk `0.2.12-pre.003`. + +fn workspace_engine() -> ksp_core_lib::Result { + 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), + }; +} + +fn assert_component(composite: &ksp_config_lib::ResolvedConfigComposite, component_id: &str, file_id: &str, profile_id: &str) { + let component = composite.component(component_id); + assert!(component.is_some(), "missing component {component_id}"); + if let std::option::Option::Some(component) = component { + assert_eq!(component.resolved().file_id().as_str(), file_id); + assert_eq!(component.resolved().profile_id(), profile_id); + assert_eq!(component.resolved().selection_source(), ksp_config_lib::ConfigProfileSelectionSource::Composite); + } +} + +#[test] +fn solprices_desk_composite_profiles_select_logging_and_offchain_transport_by_file_id() { + let engine = workspace_engine(); + assert!(engine.is_ok(), "workspace Config engine should be constructible: {engine:?}"); + 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: {file_id:?}"); + let file_id = match file_id { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + for (selection, expected_composite_profile, expected_offchain_profile) in [ + (std::option::Option::None, "public_keyless", "public_keyless"), + (std::option::Option::Some("all_free"), "all_free", "all_free"), + (std::option::Option::Some("tests"), "tests", "public_keyless"), + ] { + let composite = engine.load_resolved_composite(&file_id, selection); + assert!(composite.is_ok(), "committed SOL Prices Desk composite profile should resolve: {composite:?}"); + if let std::result::Result::Ok(composite) = composite { + assert_eq!(composite.profile_id(), expected_composite_profile); + assert_component(&composite, "logging", ksp_config_lib::FILE_ID_STD_LOGGING, "console_info"); + assert_component(&composite, "offchain_transport", ksp_config_lib::FILE_ID_STD_OFFCHAIN_TRANSPORT, expected_offchain_profile); + } + } +} + +#[test] +fn solprices_desk_default_composite_builds_provider_neutral_offchain_service() { + let engine = workspace_engine(); + 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); + 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); + let composite = match composite { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let component = composite.component("offchain_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 environment should load for the public_keyless profile: {environment:?}"); + 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(), "public_keyless composite-selected Off-chain Transport should map: {resolved:?}"); + if let std::result::Result::Ok(resolved) = resolved { + assert_eq!(resolved.profile_id(), "public_keyless"); + assert_eq!(resolved.selection_source(), ksp_config_lib::ConfigProfileSelectionSource::Composite); + assert_eq!(resolved.service().registry().len(), 8); + } +} diff --git a/crates/ksp-app-solprices-desk/tests/desktop_contract.rs b/crates/ksp-app-solprices-desk/tests/desktop_contract.rs index abd7c5f..a9fcaa6 100644 --- a/crates/ksp-app-solprices-desk/tests/desktop_contract.rs +++ b/crates/ksp-app-solprices-desk/tests/desktop_contract.rs @@ -1,7 +1,7 @@ // file: crates/ksp-app-solprices-desk/tests/desktop_contract.rs -// version: 2 +// version: 3 -//! Desktop scaffold and shared-template contract audits for SOL Prices Desk `0.2.12-pre.002` and its fixes. +//! Desktop scaffold, shared-template and Config packaging contract audits for SOL Prices Desk `0.2.12`. fn app_root() -> std::path::PathBuf { return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -37,9 +37,9 @@ fn pre_002_shell_uses_reserved_ports_windows_and_owned_backend_paths() { let tauri = read_json(root.join("tauri.conf.json").as_path()); assert_eq!(tauri.pointer("/productName").and_then(serde_json::Value::as_str), std::option::Option::Some("KSP SOL Prices Desk")); assert_eq!(tauri.pointer("/identifier").and_then(serde_json::Value::as_str), std::option::Option::Some("com.sasedev.ksp-app-solprices-desk")); - assert_eq!(tauri.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some("0.2.12-pre.2.fix.1")); + assert_eq!(tauri.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some("0.2.12-pre.3")); let package = read_json(root.join("package.json").as_path()); - assert_eq!(package.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some("0.2.12-pre.2.fix.1")); + assert_eq!(package.pointer("/version").and_then(serde_json::Value::as_str), std::option::Option::Some("0.2.12-pre.3")); 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/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some(".")); @@ -86,14 +86,17 @@ fn pre_002_package_is_mixed_lib_bin_and_frontend_is_scaffold_only() { } #[test] -fn pre_002_packaging_contains_current_ten_config_resources_only() { +fn pre_003_packaging_contains_current_eleven_config_resources() { let root = app_root(); let tauri = read_json(root.join("tauri.conf.json").as_path()); let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object); assert!(resources.is_some()); if let std::option::Option::Some(resources) = resources { - assert_eq!(resources.len(), 10); - assert!(!resources.contains_key("../../config/composite.ksp-app-solprices-desk.json")); + assert_eq!(resources.len(), 11); + assert_eq!( + resources.get("../../config/composite.ksp-app-solprices-desk.json").and_then(serde_json::Value::as_str), + std::option::Option::Some("config/composite.ksp-app-solprices-desk.json"), + ); assert!(resources.contains_key("../../config/std.offchain_transport.json")); assert!(resources.contains_key("../../config/schemas/std.offchain_transport.schema.json")); } diff --git a/crates/ksp-app-solprices-desk/tests/desktop_security.rs b/crates/ksp-app-solprices-desk/tests/desktop_security.rs index e032eba..af8ed7e 100644 --- a/crates/ksp-app-solprices-desk/tests/desktop_security.rs +++ b/crates/ksp-app-solprices-desk/tests/desktop_security.rs @@ -1,7 +1,7 @@ // file: crates/ksp-app-solprices-desk/tests/desktop_security.rs -// version: 1 +// version: 2 -//! Security and ownership canaries for the non-functional SOL Prices Desk scaffold. +//! Security, ownership and frontend instrumentation canaries for SOL Prices Desk. fn app_root() -> std::path::PathBuf { return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -69,7 +69,7 @@ fn pre_002_frontend_has_no_network_persistence_or_native_dialog_surface() { } #[test] -fn pre_002_tauri_commands_remain_centralized_and_price_runtime_is_absent() { +fn pre_003_tauri_commands_remain_centralized_and_refresh_runtime_is_absent() { let root = app_root(); let mut rust_files = std::vec::Vec::new(); collect_files(root.join("src").as_path(), "rs", &mut rust_files); @@ -82,8 +82,25 @@ fn pre_002_tauri_commands_remain_centralized_and_price_runtime_is_absent() { } else { assert_eq!(count, 0, "{} declares a Tauri command outside tauri.rs", path.display()); } - assert!(!source.contains("MarketPriceService"), "{} advances market-price runtime before pre.003", path.display()); + assert!(!source.contains("MarketPriceRuntime"), "{} advances application price-state runtime before pre.004", path.display()); assert!(!source.contains("refresh_market_price"), "{} advances refresh commands before their planned tranche", path.display()); } assert_eq!(command_count, 3); } + +#[test] +fn pre_003_frontend_control_interactions_are_logged_without_business_values() { + let root = app_root(); + let main = read_text(root.join("frontend/ts/main.ts").as_path()); + let invoke = read_text(root.join("frontend/ts/invoke.ts").as_path()); + assert!(main.contains(r#"button.addEventListener("click""#)); + assert!(main.contains("SOL Prices Desk navigation control clicked")); + assert!(main.contains("frontendDebug")); + assert!(main.contains("SOL Prices Desk view activated")); + assert!(invoke.contains("Frontend IPC command requested")); + assert!(invoke.contains("Frontend IPC command completed")); + assert!(invoke.contains("frontendTrace")); + for forbidden in ["JSON.stringify(status)", "JSON.stringify(payload)", "apiKey", "authorization"] { + assert!(!main.contains(forbidden), "main frontend logging must not serialize business/secret values: {forbidden}"); + } +} diff --git a/crates/ksp-app-wallet-desk/tauri.conf.json b/crates/ksp-app-wallet-desk/tauri.conf.json index 22c32eb..b63ff5f 100644 --- a/crates/ksp-app-wallet-desk/tauri.conf.json +++ b/crates/ksp-app-wallet-desk/tauri.conf.json @@ -53,6 +53,7 @@ "icons/favicon.ico" ], "resources": { + "../../config/composite.ksp-app-solprices-desk.json": "config/composite.ksp-app-solprices-desk.json", "../../config/composite.ksp-app-wallet-desk.json": "config/composite.ksp-app-wallet-desk.json", "../../config/std.logging.json": "config/std.logging.json", "../../config/std.offchain_transport.json": "config/std.offchain_transport.json", diff --git a/crates/ksp-app-wallet-desk/tests/desktop_contract.rs b/crates/ksp-app-wallet-desk/tests/desktop_contract.rs index 2692353..f4ed4c6 100644 --- a/crates/ksp-app-wallet-desk/tests/desktop_contract.rs +++ b/crates/ksp-app-wallet-desk/tests/desktop_contract.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs -// version: 23 +// version: 24 //! Desktop build, shell and Config-status contract audits for Wallet Desk. @@ -472,7 +472,11 @@ fn pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_versi 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"); if let std::option::Option::Some(resources) = resources { - assert_eq!(resources.len(), 10); + assert_eq!(resources.len(), 11); + assert_eq!( + resources.get("../../config/composite.ksp-app-solprices-desk.json").and_then(serde_json::Value::as_str), + std::option::Option::Some("config/composite.ksp-app-solprices-desk.json"), + ); assert_eq!( resources.get("../../config/composite.ksp-app-wallet-desk.json").and_then(serde_json::Value::as_str), std::option::Option::Some("config/composite.ksp-app-wallet-desk.json"), diff --git a/crates/ksp-app-wallet-desk/tests/release_compliance.rs b/crates/ksp-app-wallet-desk/tests/release_compliance.rs index 6091c4e..ee6968b 100644 --- a/crates/ksp-app-wallet-desk/tests/release_compliance.rs +++ b/crates/ksp-app-wallet-desk/tests/release_compliance.rs @@ -1,5 +1,5 @@ // file: crates/ksp-app-wallet-desk/tests/release_compliance.rs -// version: 6 +// version: 7 //! Release-wide deterministic compliance canaries for Wallet Desk. @@ -198,7 +198,7 @@ fn packaged_resources_include_only_registered_config_sources_and_schemas() { std::option::Option::Some(value) => value, std::option::Option::None => return, }; - assert_eq!(resources.len(), 10); + assert_eq!(resources.len(), 11); for (source, destination) in resources { let destination = destination.as_str(); assert!(destination.is_some(), "resource destination must be textual"); diff --git a/crates/ksp-config-lib/src/lib.rs b/crates/ksp-config-lib/src/lib.rs index c245bbc..2608417 100644 --- a/crates/ksp-config-lib/src/lib.rs +++ b/crates/ksp-config-lib/src/lib.rs @@ -1,5 +1,5 @@ // file: crates/ksp-config-lib/src/lib.rs -// version: 19 +// version: 20 #![warn(missing_docs)] #![deny(unreachable_pub)] @@ -150,6 +150,8 @@ pub use self::registry::ConfigFileId; pub use self::registry::ConfigFileKind; /// Registry of KSP-known logical Config files and their replaceable physical filenames. pub use self::registry::ConfigFileRegistry; +/// Default physical filename for the SOL Prices Desk composite configuration document. +pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_SOLPRICES_DESK_FILENAME; /// Default physical filename for the Wallet Desk composite configuration document. pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME; /// Default physical filename for the generic composite JSON Schema document. @@ -170,6 +172,8 @@ pub use self::registry::DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME; pub use self::registry::DEFAULT_STD_WALLET_FILENAME; /// Default physical filename for the standard Wallet JSON Schema document. pub use self::registry::DEFAULT_STD_WALLET_SCHEMA_FILENAME; +/// Logical file identifier for the SOL Prices Desk composite configuration document. +pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK; /// Logical file identifier for the Wallet Desk composite configuration document. pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK; /// Logical file identifier for the generic composite JSON Schema document. diff --git a/crates/ksp-config-lib/src/registry.rs b/crates/ksp-config-lib/src/registry.rs index d6efdc3..4f63ce8 100644 --- a/crates/ksp-config-lib/src/registry.rs +++ b/crates/ksp-config-lib/src/registry.rs @@ -1,8 +1,10 @@ // file: crates/ksp-config-lib/src/registry.rs -// version: 11 +// version: 12 /// Bootstrap argument used to replace a known Config filename mapping. pub const ARG_FILE_MAP: &str = "--filemap"; +/// Default physical filename for the SOL Prices Desk composite configuration document. +pub const DEFAULT_COMPOSITE_KSP_APP_SOLPRICES_DESK_FILENAME: &str = "composite.ksp-app-solprices-desk.json"; /// Default physical filename for the Wallet Desk composite configuration document. pub const DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME: &str = "composite.ksp-app-wallet-desk.json"; /// Default physical filename for the generic composite JSON Schema document. @@ -23,6 +25,8 @@ pub const DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME: &str = "std.transport.schema.js pub const DEFAULT_STD_WALLET_FILENAME: &str = "std.wallet.json"; /// Default physical filename for the standard Wallet JSON Schema document. pub const DEFAULT_STD_WALLET_SCHEMA_FILENAME: &str = "std.wallet.schema.json"; +/// Logical file identifier for the SOL Prices Desk composite configuration document. +pub const FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK: &str = "cfg.composite.ksp-app-solprices-desk"; /// Logical file identifier for the Wallet Desk composite configuration document. pub const FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK: &str = "cfg.composite.ksp-app-wallet-desk"; /// Logical file identifier for the generic composite JSON Schema document. @@ -148,13 +152,23 @@ pub struct ConfigFileRegistry { impl ConfigFileRegistry { /// Creates the registry containing the KSP default file mappings known in the current release. pub fn defaults() -> ksp_core_lib::Result { - let composite = ConfigFileDescriptor::new( + let solprices_composite = ConfigFileDescriptor::new( + FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK, + ConfigFileKind::Config, + DEFAULT_COMPOSITE_KSP_APP_SOLPRICES_DESK_FILENAME, + std::option::Option::Some(FILE_ID_SCHEMA_COMPOSITE), + ); + let solprices_composite = match solprices_composite { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let wallet_composite = ConfigFileDescriptor::new( FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK, ConfigFileKind::Config, DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME, std::option::Option::Some(FILE_ID_SCHEMA_COMPOSITE), ); - let composite = match composite { + let wallet_composite = match wallet_composite { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; @@ -233,7 +247,8 @@ impl ConfigFileRegistry { std::result::Result::Err(error) => return std::result::Result::Err(error), }; return crate::build_registry([ - composite, + solprices_composite, + wallet_composite, composite_schema, logging, logging_schema, diff --git a/crates/ksp-config-lib/tests/ownership.rs b/crates/ksp-config-lib/tests/ownership.rs index 932cdaa..56356c7 100644 --- a/crates/ksp-config-lib/tests/ownership.rs +++ b/crates/ksp-config-lib/tests/ownership.rs @@ -1,5 +1,5 @@ // file: crates/ksp-config-lib/tests/ownership.rs -// version: 7 +// version: 8 //! Workspace ownership audits for KSP application configuration boundaries. @@ -262,6 +262,7 @@ fn workspace_crates_do_not_hardcode_config_managed_physical_files() { }; for token in [ "\".env\"", + "\"composite.ksp-app-solprices-desk.json\"", "\"composite.ksp-app-wallet-desk.json\"", "\"composite.schema.json\"", "\"std.logging.json\"", diff --git a/crates/ksp-config-lib/tests/public_api.rs b/crates/ksp-config-lib/tests/public_api.rs index f9c0535..fded8d6 100644 --- a/crates/ksp-config-lib/tests/public_api.rs +++ b/crates/ksp-config-lib/tests/public_api.rs @@ -1,5 +1,5 @@ // file: crates/ksp-config-lib/tests/public_api.rs -// version: 24 +// version: 25 //! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity, //! Logging/Transport adapters and management contracts. @@ -80,18 +80,19 @@ fn registry_descriptor_inventory_is_available_from_crate_root() { assert!(registry.is_ok(), "public registry should remain constructible: {registry:?}"); if let std::result::Result::Ok(registry) = registry { let descriptors: std::vec::Vec<&ksp_config_lib::ConfigFileDescriptor> = registry.descriptors().collect(); - assert_eq!(descriptors.len(), 10); - assert_eq!(descriptors[0].file_id().as_str(), ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); - assert_eq!(descriptors[1].file_id().as_str(), ksp_config_lib::FILE_ID_STD_LOGGING); - assert_eq!(descriptors[2].file_id().as_str(), ksp_config_lib::FILE_ID_STD_OFFCHAIN_TRANSPORT); - assert_eq!(descriptors[3].file_id().as_str(), ksp_config_lib::FILE_ID_STD_TRANSPORT); - assert_eq!(descriptors[4].file_id().as_str(), ksp_config_lib::FILE_ID_STD_WALLET); - assert_eq!(descriptors[5].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_COMPOSITE); - assert_eq!(descriptors[6].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_LOGGING); - assert_eq!(descriptors[7].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT); - assert_eq!(descriptors[8].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT); - assert_eq!(descriptors[9].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_WALLET); - let schema_file_id = descriptors[4].schema_file_id(); + assert_eq!(descriptors.len(), 11); + assert_eq!(descriptors[0].file_id().as_str(), ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK); + assert_eq!(descriptors[1].file_id().as_str(), ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); + assert_eq!(descriptors[2].file_id().as_str(), ksp_config_lib::FILE_ID_STD_LOGGING); + assert_eq!(descriptors[3].file_id().as_str(), ksp_config_lib::FILE_ID_STD_OFFCHAIN_TRANSPORT); + assert_eq!(descriptors[4].file_id().as_str(), ksp_config_lib::FILE_ID_STD_TRANSPORT); + assert_eq!(descriptors[5].file_id().as_str(), ksp_config_lib::FILE_ID_STD_WALLET); + assert_eq!(descriptors[6].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_COMPOSITE); + assert_eq!(descriptors[7].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_LOGGING); + assert_eq!(descriptors[8].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT); + assert_eq!(descriptors[9].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT); + assert_eq!(descriptors[10].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_WALLET); + let schema_file_id = descriptors[5].schema_file_id(); assert!(schema_file_id.is_some(), "public Wallet descriptor should preserve schema association"); if let std::option::Option::Some(schema_file_id) = schema_file_id { assert_eq!(schema_file_id.as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_WALLET); @@ -160,6 +161,8 @@ fn composite_schema_and_provenance_contracts_are_available_from_crate_root() { assert_eq!(descriptor.kind(), ksp_config_lib::ConfigFileKind::Schema); } } + assert_eq!(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK, "cfg.composite.ksp-app-solprices-desk"); + assert_eq!(ksp_config_lib::DEFAULT_COMPOSITE_KSP_APP_SOLPRICES_DESK_FILENAME, "composite.ksp-app-solprices-desk.json"); assert_ne!(ksp_config_lib::ConfigProfileSelectionSource::Composite, ksp_config_lib::ConfigProfileSelectionSource::Explicit); } diff --git a/crates/ksp-config-lib/unit_tests/offchain_transport.rs b/crates/ksp-config-lib/unit_tests/offchain_transport.rs index eb54d4d..c1eb5d1 100644 --- a/crates/ksp-config-lib/unit_tests/offchain_transport.rs +++ b/crates/ksp-config-lib/unit_tests/offchain_transport.rs @@ -1,5 +1,5 @@ // file: crates/ksp-config-lib/unit_tests/offchain_transport.rs -// version: 2 +// version: 3 const TEST_PAIR: &str = "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE"; @@ -31,6 +31,45 @@ fn committed_public_keyless_profile_maps_all_eight_providers_without_secret_envi } } +#[test] +fn committed_solprices_composite_preserves_offchain_profile_and_composite_provenance() { + let engine = committed_engine(); + let engine = match engine { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let file_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK); + assert!(file_id.is_ok(), "SOL Prices Desk composite file_id should be valid: {file_id:?}"); + 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: {composite:?}"); + 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 expose offchain_transport"); + let component = match component { + std::option::Option::Some(value) => value, + std::option::Option::None => return, + }; + assert_eq!(component.resolved().file_id().as_str(), crate::FILE_ID_STD_OFFCHAIN_TRANSPORT); + assert_eq!(component.resolved().profile_id(), "public_keyless"); + assert_eq!(component.resolved().selection_source(), crate::ConfigProfileSelectionSource::Composite); + let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new()); + let resolved = engine.resolve_offchain_transport_config_profile(component.resolved(), &environment); + assert!(resolved.is_ok(), "composite-selected Off-chain Transport should map: {resolved:?}"); + if let std::result::Result::Ok(resolved) = resolved { + assert_eq!(resolved.profile_id(), "public_keyless"); + assert_eq!(resolved.selection_source(), crate::ConfigProfileSelectionSource::Composite); + assert_eq!(resolved.service().registry().len(), 8); + } +} + #[test] fn committed_all_free_profile_requires_config_owned_secrets_and_public_pair_provenance() { let engine = committed_engine(); diff --git a/crates/ksp-config-lib/unit_tests/registry.rs b/crates/ksp-config-lib/unit_tests/registry.rs index 287897c..77ef4f7 100644 --- a/crates/ksp-config-lib/unit_tests/registry.rs +++ b/crates/ksp-config-lib/unit_tests/registry.rs @@ -1,5 +1,5 @@ // file: crates/ksp-config-lib/unit_tests/registry.rs -// version: 9 +// version: 10 #[test] fn descriptors_expose_complete_registry_in_deterministic_file_id_order() { @@ -7,23 +7,25 @@ fn descriptors_expose_complete_registry_in_deterministic_file_id_order() { assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); if let std::result::Result::Ok(registry) = registry { let descriptors: std::vec::Vec<&crate::ConfigFileDescriptor> = registry.descriptors().collect(); - assert_eq!(descriptors.len(), 10); - assert_eq!(descriptors[0].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); - assert_eq!(descriptors[0].filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME)); + assert_eq!(descriptors.len(), 11); + assert_eq!(descriptors[0].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK); + assert_eq!(descriptors[0].filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_SOLPRICES_DESK_FILENAME)); assert_eq!(descriptors[0].schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_COMPOSITE)); - assert_eq!(descriptors[1].file_id().as_str(), crate::FILE_ID_STD_LOGGING); - assert_eq!(descriptors[2].file_id().as_str(), crate::FILE_ID_STD_OFFCHAIN_TRANSPORT); - assert_eq!(descriptors[3].file_id().as_str(), crate::FILE_ID_STD_TRANSPORT); - assert_eq!(descriptors[4].file_id().as_str(), crate::FILE_ID_STD_WALLET); - assert_eq!(descriptors[4].filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_FILENAME)); - assert_eq!(descriptors[4].schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_STD_WALLET)); - assert_eq!(descriptors[5].file_id().as_str(), crate::FILE_ID_SCHEMA_COMPOSITE); - assert_eq!(descriptors[6].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING); - assert_eq!(descriptors[7].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT); - assert_eq!(descriptors[8].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT); - assert_eq!(descriptors[9].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_WALLET); - assert!(descriptors[0..5].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Config)); - assert!(descriptors[5..10].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Schema)); + assert_eq!(descriptors[1].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); + assert_eq!(descriptors[1].filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME)); + assert_eq!(descriptors[2].file_id().as_str(), crate::FILE_ID_STD_LOGGING); + assert_eq!(descriptors[3].file_id().as_str(), crate::FILE_ID_STD_OFFCHAIN_TRANSPORT); + assert_eq!(descriptors[4].file_id().as_str(), crate::FILE_ID_STD_TRANSPORT); + assert_eq!(descriptors[5].file_id().as_str(), crate::FILE_ID_STD_WALLET); + assert_eq!(descriptors[5].filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_FILENAME)); + assert_eq!(descriptors[5].schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_STD_WALLET)); + assert_eq!(descriptors[6].file_id().as_str(), crate::FILE_ID_SCHEMA_COMPOSITE); + assert_eq!(descriptors[7].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING); + assert_eq!(descriptors[8].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT); + assert_eq!(descriptors[9].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT); + assert_eq!(descriptors[10].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_WALLET); + assert!(descriptors[0..6].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Config)); + assert!(descriptors[6..11].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Schema)); } } @@ -337,24 +339,33 @@ fn absolute_fixture_path() -> std::path::PathBuf { } #[test] -fn defaults_register_generic_composite_schema_and_wallet_desk_runtime_composite() { +fn defaults_register_generic_composite_schema_and_both_runtime_composites() { let registry = crate::ConfigFileRegistry::defaults(); let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_COMPOSITE); - let runtime_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); + let solprices_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK); + let wallet_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); assert!(registry.is_ok(), "default registry should be valid: {registry:?}"); assert!(schema_id.is_ok(), "composite schema file_id should be valid: {schema_id:?}"); - assert!(runtime_id.is_ok(), "Wallet Desk composite runtime file_id should be valid: {runtime_id:?}"); - if let (std::result::Result::Ok(registry), std::result::Result::Ok(schema_id), std::result::Result::Ok(runtime_id)) = (registry, schema_id, runtime_id) { + assert!(solprices_id.is_ok(), "SOL Prices Desk composite runtime file_id should be valid: {solprices_id:?}"); + assert!(wallet_id.is_ok(), "Wallet Desk composite runtime file_id should be valid: {wallet_id:?}"); + if let (std::result::Result::Ok(registry), std::result::Result::Ok(schema_id), std::result::Result::Ok(solprices_id), std::result::Result::Ok(wallet_id)) = + (registry, schema_id, solprices_id, wallet_id) + { let schema = registry.descriptor(&schema_id); - let runtime = registry.descriptor(&runtime_id); + let solprices = registry.descriptor(&solprices_id); + let wallet = registry.descriptor(&wallet_id); assert!(schema.is_ok(), "generic composite schema should be registered: {schema:?}"); - assert!(runtime.is_ok(), "Wallet Desk runtime composite should be registered: {runtime:?}"); - if let (std::result::Result::Ok(schema), std::result::Result::Ok(runtime)) = (schema, runtime) { + assert!(solprices.is_ok(), "SOL Prices Desk runtime composite should be registered: {solprices:?}"); + assert!(wallet.is_ok(), "Wallet Desk runtime composite should be registered: {wallet:?}"); + if let (std::result::Result::Ok(schema), std::result::Result::Ok(solprices), std::result::Result::Ok(wallet)) = (schema, solprices, wallet) { assert_eq!(schema.kind(), crate::ConfigFileKind::Schema); assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_SCHEMA_FILENAME)); - assert_eq!(runtime.kind(), crate::ConfigFileKind::Config); - assert_eq!(runtime.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME)); - assert_eq!(runtime.schema_file_id(), std::option::Option::Some(&schema_id)); + assert_eq!(solprices.kind(), crate::ConfigFileKind::Config); + assert_eq!(solprices.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_SOLPRICES_DESK_FILENAME)); + assert_eq!(solprices.schema_file_id(), std::option::Option::Some(&schema_id)); + assert_eq!(wallet.kind(), crate::ConfigFileKind::Config); + assert_eq!(wallet.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME)); + assert_eq!(wallet.schema_file_id(), std::option::Option::Some(&schema_id)); } } } diff --git a/deltas/0.2.12/pre.003.md b/deltas/0.2.12/pre.003.md new file mode 100644 index 0000000..5416174 --- /dev/null +++ b/deltas/0.2.12/pre.003.md @@ -0,0 +1,250 @@ + + + +# Delta `0.2.12-pre.003` — composite Config et bootstrap Off-chain + +## 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 +``` + +Le retour opérateur du `27 août 2026` clôt proprement `pre.002-fix.001` : `cargo fmt`, installation npm, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace, `cargo test -p ksp-app-solprices-desk` et `cargo tauri dev` passent sans warning Rust. `cargo test --workspace` n'a pas été rejoué dans ce retour précis et n'est pas revendiqué comme tel. + +La version workspace passe à : + +```text +0.2.12-pre.3 +``` + +## 2. Responsabilité de la tranche + +`pre.003` matérialise uniquement la composition Config du nouveau Desk et son bootstrap Off-chain provider-neutral : + +```text +nouveau composite SOL Prices Desk +nouveau descriptor Config +registry Config 10 -> 11 descriptors +resources Config packagées 10 -> 11 dans les trois Desks +Logging du SOL Prices Desk sélectionné par composite +ResolvedOffchainTransportConfig construit au bootstrap +MarketPriceService détenu indirectement par Config +provenance Composite conservée +Diagnostics bootstrap sûrs +instrumentation frontend durable pour les contrôles existants +``` + +La tranche n'introduit encore aucun `MarketPriceRuntime`, row provider, observation affichée ni commande de refresh. + +## 3. Composite SOL Prices Desk + +Le document ajouté est : + +```text +config/composite.ksp-app-solprices-desk.json +file_id = cfg.composite.ksp-app-solprices-desk +schema = schema.composite +``` + +Il contient exactement les profils prévus : + +```text +public_keyless (default) + logging -> cfg.std.logging / console_info + offchain_transport -> cfg.std.offchain_transport / public_keyless + +all_free + logging -> cfg.std.logging / console_info + offchain_transport -> cfg.std.offchain_transport / all_free + +tests + logging -> cfg.std.logging / console_info + offchain_transport -> cfg.std.offchain_transport / public_keyless +``` + +Les composants sont résolus par `file_id`; aucun filename physique n'est connu du code applicatif. + +## 4. Registry et packaging atomiques + +`ksp-config-lib` enregistre le nouveau descriptor avec le schema composite existant. L'inventaire passe à **11 descriptors** : 6 documents Config et 5 schemas. + +Comme `prepare_packaged_runtime` travaille sur le registry logique complet, la même tranche ajoute le nouveau resource mapping aux trois applications packagées : + +```text +ksp-app-config-desk 11 resources +ksp-app-wallet-desk 11 resources +ksp-app-solprices-desk 11 resources +``` + +Les canaris historiques de Config Desk et Wallet Desk sont mis à jour pour rendre cette atomicité durable. + +## 5. Bootstrap Config -> Off-chain + +SOL Prices Desk charge son composite via `FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK` puis vérifie explicitement les composants obligatoires : + +```text +logging -> FILE_ID_STD_LOGGING +offchain_transport -> FILE_ID_STD_OFFCHAIN_TRANSPORT +``` + +Le profil Logging du composite alimente le runtime Logging existant. Le profil Off-chain sélectionné est ensuite transmis à : + +```text +ConfigDocumentEngine::resolve_offchain_transport_config_profile +``` + +Le résultat `ResolvedOffchainTransportConfig` reste détenu par l'état backend. Pour le profil default `public_keyless`, le service construit par Config expose les huit entrées provider-neutral déjà définies par `0.2.11`. + +Aucune dépendance provider, URL, API key, rate limit ou logique HTTP n'est recréée dans la crate applicative. + +## 6. État runtime exposé + +`get_runtime_status` reste une commande de diagnostic et expose uniquement des projections sûres supplémentaires : + +```text +active_composite_profile +active_logging_profile +active_offchain_profile +config_document_count +application_version +fallback_logging_active +shell_phase +startup_diagnostic sûr +``` + +Aucun provider individuel, prix, credential, endpoint ou payload Off-chain n'est projeté dans cette tranche. + +## 7. Logging frontend durable + +Conformément à `KSP-APP-027`, les contrôles frontend existants sont instrumentés via le bridge commun : + +```text +clic / action utilisateur debug +transition de vue debug +demande IPC debug +fin technique IPC trace +échec contrôlé error/warn selon le chemin existant +``` + +Les logs ne sérialisent pas de valeurs métier ou secrètes. Le canari ajouté impose ce comportement avant l'arrivée des boutons prix : les futurs refresh row/many/all devront journaliser l'action, l'ID opaque/count/code sûr lorsque nécessaire, jamais prix brut, API key, header ou payload provider. + +## 8. Tests et canaris ajoutés/étendus + +`ksp-config-lib` couvre désormais : + +```text +inventaire exact des 11 descriptors +public constants du nouveau composite +provenance Composite du profil Off-chain +construction du service public_keyless depuis le composite +interdiction de hardcoder le nouveau filename hors Config +``` + +SOL Prices Desk couvre : + +```text +profils composite public_keyless / all_free / tests +mapping logging + offchain_transport exact +provenance Composite +service provider-neutral à huit entrées pour public_keyless +11 resources packagées +absence de MarketPriceRuntime avant pre.004 +absence de refresh avant pre.005 +logging debug des contrôles/navigation +logging debug/trace du wrapper IPC +``` + +## 9. Fichiers ajoutés + +```text +config/composite.ksp-app-solprices-desk.json +crates/ksp-app-solprices-desk/src/offchain_runtime.rs +crates/ksp-app-solprices-desk/tests/config_composition.rs +deltas/0.2.12/pre.003.md +``` + +## 10. Fichiers modifiés + +```text +Cargo.toml +crates/ksp-app-config-desk/tauri.conf.json +crates/ksp-app-config-desk/tests/desktop_contract.rs +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/bootstrap.rs +crates/ksp-app-solprices-desk/src/constants.rs +crates/ksp-app-solprices-desk/src/dto_common.rs +crates/ksp-app-solprices-desk/src/errors.rs +crates/ksp-app-solprices-desk/src/lib.rs +crates/ksp-app-solprices-desk/tests/desktop_contract.rs +crates/ksp-app-solprices-desk/tests/desktop_security.rs +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 +crates/ksp-config-lib/src/lib.rs +crates/ksp-config-lib/src/registry.rs +crates/ksp-config-lib/tests/ownership.rs +crates/ksp-config-lib/tests/public_api.rs +crates/ksp-config-lib/unit_tests/offchain_transport.rs +crates/ksp-config-lib/unit_tests/registry.rs +docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md +docs/validation/015-V0_2_12_SOL_PRICES_DESK.md +``` + +## 11. Fichiers volontairement inchangés + +```text +crates/ksp-offchain-transport-lib/** +crates/ksp-app-wallet-desk/src/** +crates/ksp-app-wallet-desk/frontend/** +crates/ksp-app-config-desk/src/** +crates/ksp-app-config-desk/frontend/** +README.md +ROADMAP.md +CHANGELOG.md +prompts/017-V0_2_12_START_PROMPT.md +``` + +L'intégration Off-chain de Wallet Desk reste propriétaire des tranches ultérieures. Le présent changement Wallet/Config Desk est strictement limité à la cohérence des resources packagées et de leurs canaris. + +## 12. Validations exécutées dans le sandbox + +Après matérialisation de la tranche et du delta : + +```text +python3 scripts/audit_rust_workspace_rules.py PASS +General Rust rule audit clean +Rust export completeness audit 0 candidate +KSP workspace Rust rule audit clean + +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.12 + PASS +JSON parse composite/package/tauri.conf modifiés PASS +``` + +Le sandbox ne possède pas `cargo`; les commandes Cargo ne sont donc pas déclarées PASS localement. + +## 13. 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-config-lib +cargo test -p ksp-app-solprices-desk +cargo test --workspace +(cd crates/ksp-app-solprices-desk && cargo tauri dev) +``` + +Le `cargo tauri dev` doit confirmer que le Desk démarre avec le composite default `public_keyless`, que Diagnostics affiche les profils composite/Off-chain et `11` documents Config, et qu'aucune table prix ni requête de refresh n'est encore présente. diff --git a/docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md b/docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md index 85f01da..9e9889c 100644 --- a/docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md +++ b/docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md @@ -1,5 +1,5 @@ - + # Plan `0.2.12` — SOL Prices Desk + intégration prix Wallet Desk @@ -472,19 +472,19 @@ Remplacement du forecast tabulaire par des sous-sections éditables avec statut ### `pre.002` — Scaffold `ksp-app-solprices-desk` -**Statut : implémenté ; gate opérateur à confirmer.** +**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 : implémenté ; gate opérateur à confirmer.** +**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 : planifié.** +**Statut : implémenté ; gate opérateur à confirmer.** 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. diff --git a/docs/validation/015-V0_2_12_SOL_PRICES_DESK.md b/docs/validation/015-V0_2_12_SOL_PRICES_DESK.md index 697b991..b57dd0f 100644 --- a/docs/validation/015-V0_2_12_SOL_PRICES_DESK.md +++ b/docs/validation/015-V0_2_12_SOL_PRICES_DESK.md @@ -1,5 +1,5 @@ - + # Validation `0.2.12` — SOL Prices Desk + intégration Wallet Desk @@ -79,16 +79,16 @@ NOT RUN commande non exécutée dans le contexte indiqué | Contrat | Valeur attendue | Statut | |---------------------------------------|--------------------------------------------------------------|---------| -| Price Desk composite file | `config/composite.ksp-app-solprices-desk.json` | PENDING | -| Price Desk file_id | `cfg.composite.ksp-app-solprices-desk` | PENDING | -| schema | `schema.composite` existant | PENDING | +| Price Desk composite file | `config/composite.ksp-app-solprices-desk.json` | PASS | +| Price Desk file_id | `cfg.composite.ksp-app-solprices-desk` | PASS | +| schema | `schema.composite` existant | PASS | | components | `logging`, `offchain_transport` | PENDING | | default profile | `public_keyless` | PENDING | | optional profile | `all_free` | PENDING | | test profile | `tests` -> offchain `public_keyless` | PENDING | | Wallet composite | ajoute offchain `public_keyless` à devnet/temporary/tests | PENDING | -| registry/resource count | 11 après nouveau composite | PENDING | -| registry + resources atomiques | Config + Config Desk + Wallet Desk + Price Desk même tranche | PENDING | +| registry/resource count | 11 après nouveau composite | PASS | +| registry + resources atomiques | Config + Config Desk + Wallet Desk + Price Desk même tranche | PASS | | nouveau schema Config | inutile | OUT | | modification `std.offchain_transport` | pas de lacune identifiée | OUT | @@ -149,7 +149,7 @@ NOT RUN commande non exécutée dans le contexte indiqué | frontendDist | hors source, path Price Desk dédié | PASS | | hooks | npm dev/build cwd crate | PASS | | backend cross-app | aucune référence Wallet par erreur | PASS | -| resources | registry complet packagé | PENDING | +| resources | registry complet packagé | PASS | | bundle | targets/icons cohérents | PASS | | Tauri production build Price Desk | crate-local PASS attendu | PENDING | | Tauri production build Wallet Desk | crate-local PASS attendu | PENDING | @@ -220,7 +220,7 @@ python3 scripts/audit_markdown_tables.py README.md ... deltas PASS cargo fmt/check/clippy/test NOT RUN — cargo indisponible dans le sandbox ``` -Le statut de `pre.002` reste donc « implémenté ; gate opérateur à confirmer » jusqu'au retour du gate Cargo de l'opérateur. +Le retour opérateur du `27 août 2026` clôt le gate de tranche : `cargo fmt --all`, installation npm, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace, `cargo test -p ksp-app-solprices-desk` et `cargo tauri dev` passent. Le test applicatif exécute 12 unit tests, 4 canaris `desktop_contract`, 3 canaris `desktop_security` et 1 canari `public_api`, tous PASS. ## 14. Gate `0.2.12-pre.002-fix.001` @@ -239,4 +239,33 @@ TRACING_DOMAIN_SHELL réellement consommé PAS MarketPriceService / composite / refresh / table prix absent par contrat ``` -Le gate Cargo du checkout opérateur précédent a confirmé que `pre.002` compile et que ses tests passent, mais il signalait deux warnings `TRACING_DOMAIN_SHELL` unused. Le fix consomme désormais ce domaine dans `configure_setup`; le gate complet doit être rejoué sur `0.2.12-pre.2.fix.1` avant passage à `pre.003`. +Le gate opérateur du `27 août 2026` sur `0.2.12-pre.2.fix.1` est propre : audits Rust/Markdown, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo test -p ksp-app-solprices-desk` passent sans les deux warnings précédents. `cargo tauri dev` démarre Vite sur `1434` puis le backend Rust sans warning. `cargo test --workspace` n'a pas été rejoué dans ce retour précis et n'est donc pas revendiqué ici. + +## 15. Gate `0.2.12-pre.003` + +La tranche matérialise uniquement la composition Config et le bootstrap Off-chain provider-neutral : + +```text +composite cfg.composite.ksp-app-solprices-desk PASS structurel +profils public_keyless / all_free / tests PASS structurel +composants logging + offchain_transport par file_id PASS structurel +registry Config 11 descriptors +resources Config Desk / Wallet Desk / SOL Prices Desk 11/11 chacune +Logging SOL Prices Desk résolu depuis le composite PASS structurel +ResolvedOffchainTransportConfig construit au bootstrap PASS structurel +service provider-neutral public_keyless 8 entrées attendues par canari +MarketPriceRuntime / rows / observations / refresh commands absent par contrat pre.003 +navigation et contrôles frontend existants journalisés en debug PASS structurel/canari +IPC frontend request/completion debug / trace +``` + +Le contrat de logging frontend est volontairement durable : action utilisateur et transition de vue en `debug`, étapes techniques fréquentes/fin d'IPC en `trace`, sans sérialiser prix, credentials, headers ni payloads métier. Les futurs boutons de refresh devront reprendre le même bridge et le même niveau de discipline. + +Validations sandbox après matérialisation : + +```text +python3 scripts/audit_rust_workspace_rules.py PASS +python3 scripts/audit_markdown_tables.py README.md ... deltas PASS +validation syntaxique des JSON modifiés PASS +cargo fmt/check/clippy/test NOT RUN — cargo indisponible dans le sandbox +```