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
+```