v0.2.12-pre.003

This commit is contained in:
2026-08-27 09:33:29 +02:00
parent 0d01017c31
commit 5a165296d9
30 changed files with 806 additions and 111 deletions

View File

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

View File

@@ -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"
}
]
}
]
}

View File

@@ -53,6 +53,7 @@
"icons/favicon.ico" "icons/favicon.ico"
], ],
"resources": { "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/composite.ksp-app-wallet-desk.json": "config/composite.ksp-app-wallet-desk.json",
"../../config/std.logging.json": "config/std.logging.json", "../../config/std.logging.json": "config/std.logging.json",
"../../config/std.offchain_transport.json": "config/std.offchain_transport.json", "../../config/std.offchain_transport.json": "config/std.offchain_transport.json",

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-config-desk/tests/desktop_contract.rs // file: crates/ksp-app-config-desk/tests/desktop_contract.rs
// version: 8 // version: 9
//! Desktop build/shell contract audits for Config Desk. //! 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); let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object);
assert!(resources.is_some(), "packaged Config resources map must exist"); assert!(resources.is_some(), "packaged Config resources map must exist");
if let std::option::Option::Some(resources) = resources { 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.logging.json").and_then(serde_json::Value::as_str), std::option::Option::Some("config/std.logging.json"));
assert_eq!( assert_eq!(
resources.get("../../config/std.offchain_transport.json").and_then(serde_json::Value::as_str), resources.get("../../config/std.offchain_transport.json").and_then(serde_json::Value::as_str),

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-solprices-desk/frontend/main.html --> <!-- file: crates/ksp-app-solprices-desk/frontend/main.html -->
<!-- version: 3 --> <!-- version: 4 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -47,14 +47,14 @@
<div class="d-flex align-items-center justify-content-between mb-4"> <div class="d-flex align-items-center justify-content-between mb-4">
<div> <div>
<h1 class="h3 mb-1">Prices</h1> <h1 class="h3 mb-1">Prices</h1>
<p class="text-body-secondary mb-0">Scaffold desktop actif. Le runtime Off-chain et la table SOL/USD arrivent dans les tranches suivantes.</p> <p class="text-body-secondary mb-0">Composition Config/Off-chain active. La projection provider-neutral du registry arrive dans la tranche suivante.</p>
</div> </div>
</div> </div>
<div class="app-placeholder"> <div class="app-placeholder">
<div> <div>
<i class="fa-solid fa-chart-line fa-3x text-body-secondary mb-3" aria-hidden="true"></i> <i class="fa-solid fa-chart-line fa-3x text-body-secondary mb-3" aria-hidden="true"></i>
<h2 class="h5">Surface prix volontairement inactive</h2> <h2 class="h5">Surface prix volontairement inactive</h2>
<p class="text-body-secondary mb-0">`pre.002` ne crée ni `MarketPriceService`, ni table provider, ni refresh réseau.</p> <p class="text-body-secondary mb-0">`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 lUI.</p>
</div> </div>
</div> </div>
</section> </section>
@@ -62,7 +62,7 @@
<div class="d-flex align-items-center justify-content-between mb-4"> <div class="d-flex align-items-center justify-content-between mb-4">
<div> <div>
<h1 class="h3 mb-1">Diagnostics</h1> <h1 class="h3 mb-1">Diagnostics</h1>
<p class="text-body-secondary mb-0">État sûr du scaffold uniquement.</p> <p class="text-body-secondary mb-0">État sûr du bootstrap Config, Logging et Off-chain Transport.</p>
</div> </div>
</div> </div>
<div class="card shadow-sm"> <div class="card shadow-sm">
@@ -75,6 +75,10 @@
<dd id="runtimeShellPhase" class="col-sm-7"></dd> <dd id="runtimeShellPhase" class="col-sm-7"></dd>
<dt class="col-sm-5">Documents Config</dt> <dt class="col-sm-5">Documents Config</dt>
<dd id="runtimeConfigDocuments" class="col-sm-7"></dd> <dd id="runtimeConfigDocuments" class="col-sm-7"></dd>
<dt class="col-sm-5">Profil composite</dt>
<dd id="runtimeCompositeProfile" class="col-sm-7"></dd>
<dt class="col-sm-5">Profil Off-chain</dt>
<dd id="runtimeOffchainProfile" class="col-sm-7"></dd>
<dt class="col-sm-5">Profil Logging</dt> <dt class="col-sm-5">Profil Logging</dt>
<dd id="runtimeLoggingProfile" class="col-sm-7"></dd> <dd id="runtimeLoggingProfile" class="col-sm-7"></dd>
<dt class="col-sm-5">Fallback Logging</dt> <dt class="col-sm-5">Fallback Logging</dt>

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-solprices-desk/frontend/ts/main.ts // file: crates/ksp-app-solprices-desk/frontend/ts/main.ts
// version: 2 // version: 3
import "bootstrap"; import "bootstrap";
import ResizeObserver from "resize-observer-polyfill"; import ResizeObserver from "resize-observer-polyfill";
@@ -44,6 +44,7 @@ function bindNavigation(): void {
document.querySelectorAll<HTMLButtonElement>("[data-view]").forEach(button => { document.querySelectorAll<HTMLButtonElement>("[data-view]").forEach(button => {
button.addEventListener("click", () => { button.addEventListener("click", () => {
const viewId = button.dataset.view; const viewId = button.dataset.view;
frontendDebug("main", "SOL Prices Desk navigation control clicked", { viewId: viewId ?? "unknown" });
if (isViewId(viewId)) { if (isViewId(viewId)) {
activateView(viewId, "user"); activateView(viewId, "user");
} }
@@ -54,6 +55,8 @@ function bindNavigation(): void {
function renderRuntimeStatus(status: RuntimeStatusDto): void { function renderRuntimeStatus(status: RuntimeStatusDto): void {
const values: Record<string, string> = { const values: Record<string, string> = {
runtimeCompositeProfile: status.activeCompositeProfile,
runtimeOffchainProfile: status.activeOffchainProfile,
runtimeVersion: status.applicationVersion, runtimeVersion: status.applicationVersion,
runtimeShellPhase: status.shellPhase, runtimeShellPhase: status.shellPhase,
runtimeConfigDocuments: status.configDocumentCount.toString(), runtimeConfigDocuments: status.configDocumentCount.toString(),

View File

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

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-solprices-desk/src/app_state.rs // 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. //! Shared backend state owned by the SOL Prices Desk Tauri application.
@@ -7,12 +7,13 @@
pub(crate) struct AppState { pub(crate) struct AppState {
config_management: ksp_config_lib::ConfigManagement, config_management: ksp_config_lib::ConfigManagement,
logging_runtime: std::sync::Mutex<LoggingRuntimeState>, logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
offchain_transport_startup: crate::OffchainTransportStartup,
splash_settings: crate::SplashSettings, splash_settings: crate::SplashSettings,
splash_sequence_started: std::sync::atomic::AtomicBool, splash_sequence_started: std::sync::atomic::AtomicBool,
} }
impl AppState { impl crate::AppState {
/// Initializes Config ownership, Logging and the common desktop splash state without creating the Off-chain runtime reserved for `pre.003`. /// 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<Self> { pub(crate) fn initialize(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<Self> {
let config_management = crate::config_management(arguments); let config_management = crate::config_management(arguments);
let config_management = match config_management { let config_management = match config_management {
@@ -29,6 +30,11 @@ impl AppState {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), 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 = crate::SplashSettings::load();
let splash_settings = match splash_settings { let splash_settings = match splash_settings {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -46,12 +52,13 @@ impl AppState {
fallback_active: logging_startup.fallback_active, fallback_active: logging_startup.fallback_active,
startup_diagnostic: logging_startup.startup_diagnostic, startup_diagnostic: logging_startup.startup_diagnostic,
}), }),
offchain_transport_startup,
splash_settings, splash_settings,
splash_sequence_started: std::sync::atomic::AtomicBool::new(false), 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<crate::RuntimeStatusDto> { pub(crate) fn runtime_status(&self) -> ksp_core_lib::Result<crate::RuntimeStatusDto> {
let document_count = self.config_management.engine().registry().descriptors().count(); let document_count = self.config_management.engine().registry().descriptors().count();
let document_count = u32::try_from(document_count); let document_count = u32::try_from(document_count);
@@ -61,7 +68,7 @@ impl AppState {
return std::result::Result::Err( return std::result::Result::Err(
ksp_core_lib::Error::new( ksp_core_lib::Error::new(
crate::ERROR_CODE_APP_STATE_INVALID, 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), .with_source(error),
); );
@@ -78,12 +85,15 @@ impl AppState {
}, },
}; };
let _keep_guard_alive = &runtime.guard; let _keep_guard_alive = &runtime.guard;
let offchain = self.offchain_transport_startup.resolved();
return std::result::Result::Ok(crate::RuntimeStatusDto { 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_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, config_document_count: document_count,
fallback_logging_active: runtime.fallback_active, 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(), startup_diagnostic: runtime.startup_diagnostic.clone(),
}); });
} }

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-solprices-desk/src/bootstrap.rs // 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. /// Crate-internal Logging startup state shared by the application state.
pub(crate) struct LoggingStartup { 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)); 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<ksp_config_lib::ResolvedConfigComposite> {
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<ksp_config_lib::ResolvedConfigProfile> {
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( pub(crate) fn initialize_logging(
management: &ksp_config_lib::ConfigManagement, management: &ksp_config_lib::ConfigManagement,
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity, 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); let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity);
match guard { match guard {
std::result::Result::Ok(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 { std::result::Result::Ok(LoggingStartup {
guard, guard,
active_profile_id: std::option::Option::Some(active_profile_id), 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::Ok(value) => value,
std::result::Result::Err(error) => return fallback_startup_plan(error), 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 { let resolved = match resolved {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return fallback_startup_plan(error), std::result::Result::Err(error) => return fallback_startup_plan(error),

View File

@@ -1,12 +1,18 @@
// file: crates/ksp-app-solprices-desk/src/constants.rs // 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. /// Structured domain used while bootstrapping Config and Logging.
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "solprices.bootstrap"; pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "solprices.bootstrap";
/// Structured domain used by technical frontend events. /// Structured domain used by technical frontend events.
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend"; 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. /// Structured domain used by the SOL Prices Desk shell.
pub(crate) const TRACING_DOMAIN_SHELL: &str = "solprices.shell"; pub(crate) const TRACING_DOMAIN_SHELL: &str = "solprices.shell";
/// Structured domain used by Tauri window lifecycle operations. /// Structured domain used by Tauri window lifecycle operations.

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-solprices-desk/src/dto_common.rs // 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 use ts_rs::TS; // rust-rules: trait-import
@@ -18,7 +18,7 @@ pub(crate) struct CommandErrorDto {
pub(crate) message: String, pub(crate) message: String,
} }
impl CommandErrorDto { impl crate::CommandErrorDto {
/// Builds a bounded safe projection from a KSP error. /// Builds a bounded safe projection from a KSP error.
#[must_use] #[must_use]
pub(crate) fn from_error(error: &ksp_core_lib::Error) -> Self { 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)] #[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_solprices_desk/dto_common/RuntimeStatusDto.ts")] #[ts(export, export_to = "../frontend/ts/bindings/ksp_app_solprices_desk/dto_common/RuntimeStatusDto.ts")]
pub(crate) struct RuntimeStatusDto { pub(crate) struct RuntimeStatusDto {
/// Cargo application version. /// SOL Prices Desk composite profile selected for this launch.
pub(crate) application_version: String, pub(crate) active_composite_profile: String,
/// Active configured Logging profile, or `None` while transient fallback Logging is active. /// Active configured Logging profile, or `None` while transient fallback Logging is active.
pub(crate) active_logging_profile: std::option::Option<String>, pub(crate) active_logging_profile: std::option::Option<String>,
/// 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, pub(crate) config_document_count: u32,
/// Whether SOL Prices Desk had to install its transient in-memory Logging fallback. /// Whether SOL Prices Desk had to install its transient in-memory Logging fallback.
pub(crate) fallback_logging_active: bool, 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, pub(crate) shell_phase: String,
/// Safe startup diagnostic that caused fallback Logging, when applicable. /// Safe startup diagnostic that caused fallback Logging, when applicable.
pub(crate) startup_diagnostic: std::option::Option<CommandErrorDto>, pub(crate) startup_diagnostic: std::option::Option<CommandErrorDto>,

View File

@@ -1,12 +1,14 @@
// file: crates/ksp-app-solprices-desk/src/errors.rs // 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. /// 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"); 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. /// 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"); 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. /// 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"); 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. /// Frontend logging requested a target outside the application whitelist.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-solprices-desk/src/lib.rs // file: crates/ksp-app-solprices-desk/src/lib.rs
// version: 1 // version: 2
//! Tauri desktop application scaffold for provider-neutral SOL/USD price visualization. //! Tauri desktop application scaffold for provider-neutral SOL/USD price visualization.
@@ -14,6 +14,7 @@ mod dto_common;
mod errors; mod errors;
mod frontend_logging; mod frontend_logging;
mod logging_runtime; mod logging_runtime;
mod offchain_runtime;
mod splash; mod splash;
mod tauri; mod tauri;
mod tw_main; mod tw_main;
@@ -30,10 +31,20 @@ pub(crate) use self::bootstrap::LoggingStartup;
pub(crate) use self::bootstrap::config_management; pub(crate) use self::bootstrap::config_management;
/// Initializes the scaffold Logging runtime from Config, with a bounded in-memory fallback. /// Initializes the scaffold Logging runtime from Config, with a bounded in-memory fallback.
pub(crate) use self::bootstrap::initialize_logging; 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. /// Structured domain used while bootstrapping Config and Logging.
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP; pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
/// Structured domain used by technical frontend events. /// Structured domain used by technical frontend events.
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND; 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. /// Structured domain used by the SOL Prices Desk shell.
pub(crate) use self::constants::TRACING_DOMAIN_SHELL; pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
/// Structured domain used by Tauri window lifecycle operations. /// 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; pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
/// Shared SOL Prices Desk runtime state cannot be locked safely. /// Shared SOL Prices Desk runtime state cannot be locked safely.
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED; 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. /// Frontend logging requested an unsupported level.
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID; pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
/// Frontend logging requested a target outside the application whitelist. /// 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; pub(crate) use self::frontend_logging::emit_frontend_log_event;
/// Creates the stable runtime identity for this SOL Prices Desk process launch. /// Creates the stable runtime identity for this SOL Prices Desk process launch.
pub(crate) use self::logging_runtime::launch_identity; 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. /// Command emitted by Rust to the splash frontend.
pub(crate) use self::splash::SplashOrderDto; pub(crate) use self::splash::SplashOrderDto;
/// Runtime timings used by the common desk splash lifecycle. /// Runtime timings used by the common desk splash lifecycle.

View File

@@ -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<crate::OffchainTransportStartup> {
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 });
}

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "KSP SOL Prices Desk", "productName": "KSP SOL Prices Desk",
"version": "0.2.12-pre.2.fix.1", "version": "0.2.12-pre.3",
"identifier": "com.sasedev.ksp-app-solprices-desk", "identifier": "com.sasedev.ksp-app-solprices-desk",
"build": { "build": {
"beforeDevCommand": { "beforeDevCommand": {
@@ -53,6 +53,7 @@
"icons/favicon.ico" "icons/favicon.ico"
], ],
"resources": { "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/composite.ksp-app-wallet-desk.json": "config/composite.ksp-app-wallet-desk.json",
"../../config/std.logging.json": "config/std.logging.json", "../../config/std.logging.json": "config/std.logging.json",
"../../config/std.offchain_transport.json": "config/std.offchain_transport.json", "../../config/std.offchain_transport.json": "config/std.offchain_transport.json",

View File

@@ -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<ksp_config_lib::ConfigDocumentEngine> {
let workspace = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..");
let bootstrap = ksp_config_lib::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
let bootstrap = match bootstrap {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let registry = ksp_config_lib::ConfigFileRegistry::defaults();
return match registry {
std::result::Result::Ok(value) => std::result::Result::Ok(ksp_config_lib::ConfigDocumentEngine::new(bootstrap, value)),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
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);
}
}

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-solprices-desk/tests/desktop_contract.rs // 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 { fn app_root() -> std::path::PathBuf {
return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); 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()); 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("/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("/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()); 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/devUrl").and_then(serde_json::Value::as_str), std::option::Option::Some("http://localhost:1434"));
assert_eq!(tauri.pointer("/build/beforeDevCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev")); assert_eq!(tauri.pointer("/build/beforeDevCommand/script").and_then(serde_json::Value::as_str), std::option::Option::Some("npm run dev"));
assert_eq!(tauri.pointer("/build/beforeDevCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some(".")); assert_eq!(tauri.pointer("/build/beforeDevCommand/cwd").and_then(serde_json::Value::as_str), std::option::Option::Some("."));
@@ -86,14 +86,17 @@ fn pre_002_package_is_mixed_lib_bin_and_frontend_is_scaffold_only() {
} }
#[test] #[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 root = app_root();
let tauri = read_json(root.join("tauri.conf.json").as_path()); let tauri = read_json(root.join("tauri.conf.json").as_path());
let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object); let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object);
assert!(resources.is_some()); assert!(resources.is_some());
if let std::option::Option::Some(resources) = resources { if let std::option::Option::Some(resources) = resources {
assert_eq!(resources.len(), 10); assert_eq!(resources.len(), 11);
assert!(!resources.contains_key("../../config/composite.ksp-app-solprices-desk.json")); 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/std.offchain_transport.json"));
assert!(resources.contains_key("../../config/schemas/std.offchain_transport.schema.json")); assert!(resources.contains_key("../../config/schemas/std.offchain_transport.schema.json"));
} }

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-solprices-desk/tests/desktop_security.rs // 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 { fn app_root() -> std::path::PathBuf {
return std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); 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] #[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 root = app_root();
let mut rust_files = std::vec::Vec::new(); let mut rust_files = std::vec::Vec::new();
collect_files(root.join("src").as_path(), "rs", &mut rust_files); 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 { } else {
assert_eq!(count, 0, "{} declares a Tauri command outside tauri.rs", path.display()); 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!(!source.contains("refresh_market_price"), "{} advances refresh commands before their planned tranche", path.display());
} }
assert_eq!(command_count, 3); 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}");
}
}

View File

@@ -53,6 +53,7 @@
"icons/favicon.ico" "icons/favicon.ico"
], ],
"resources": { "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/composite.ksp-app-wallet-desk.json": "config/composite.ksp-app-wallet-desk.json",
"../../config/std.logging.json": "config/std.logging.json", "../../config/std.logging.json": "config/std.logging.json",
"../../config/std.offchain_transport.json": "config/std.offchain_transport.json", "../../config/std.offchain_transport.json": "config/std.offchain_transport.json",

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs // file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs
// version: 23 // version: 24
//! Desktop build, shell and Config-status contract audits for Wallet Desk. //! 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); let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object);
assert!(resources.is_some(), "packaged Wallet Desk Config resources map must exist"); assert!(resources.is_some(), "packaged Wallet Desk Config resources map must exist");
if let std::option::Option::Some(resources) = resources { if let std::option::Option::Some(resources) = resources {
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!( assert_eq!(
resources.get("../../config/composite.ksp-app-wallet-desk.json").and_then(serde_json::Value::as_str), 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"), std::option::Option::Some("config/composite.ksp-app-wallet-desk.json"),

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/tests/release_compliance.rs // file: crates/ksp-app-wallet-desk/tests/release_compliance.rs
// version: 6 // version: 7
//! Release-wide deterministic compliance canaries for Wallet Desk. //! 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::Some(value) => value,
std::option::Option::None => return, std::option::Option::None => return,
}; };
assert_eq!(resources.len(), 10); assert_eq!(resources.len(), 11);
for (source, destination) in resources { for (source, destination) in resources {
let destination = destination.as_str(); let destination = destination.as_str();
assert!(destination.is_some(), "resource destination must be textual"); assert!(destination.is_some(), "resource destination must be textual");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/lib.rs // file: crates/ksp-config-lib/src/lib.rs
// version: 19 // version: 20
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -150,6 +150,8 @@ pub use self::registry::ConfigFileId;
pub use self::registry::ConfigFileKind; pub use self::registry::ConfigFileKind;
/// Registry of KSP-known logical Config files and their replaceable physical filenames. /// Registry of KSP-known logical Config files and their replaceable physical filenames.
pub use self::registry::ConfigFileRegistry; 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. /// Default physical filename for the Wallet Desk composite configuration document.
pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME; pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME;
/// Default physical filename for the generic composite JSON Schema document. /// 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; pub use self::registry::DEFAULT_STD_WALLET_FILENAME;
/// Default physical filename for the standard Wallet JSON Schema document. /// Default physical filename for the standard Wallet JSON Schema document.
pub use self::registry::DEFAULT_STD_WALLET_SCHEMA_FILENAME; 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. /// Logical file identifier for the Wallet Desk composite configuration document.
pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK; pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK;
/// Logical file identifier for the generic composite JSON Schema document. /// Logical file identifier for the generic composite JSON Schema document.

View File

@@ -1,8 +1,10 @@
// file: crates/ksp-config-lib/src/registry.rs // file: crates/ksp-config-lib/src/registry.rs
// version: 11 // version: 12
/// Bootstrap argument used to replace a known Config filename mapping. /// Bootstrap argument used to replace a known Config filename mapping.
pub const ARG_FILE_MAP: &str = "--filemap"; 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. /// 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"; 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. /// 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"; pub const DEFAULT_STD_WALLET_FILENAME: &str = "std.wallet.json";
/// Default physical filename for the standard Wallet JSON Schema document. /// Default physical filename for the standard Wallet JSON Schema document.
pub const DEFAULT_STD_WALLET_SCHEMA_FILENAME: &str = "std.wallet.schema.json"; 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. /// 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"; 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. /// Logical file identifier for the generic composite JSON Schema document.
@@ -148,13 +152,23 @@ pub struct ConfigFileRegistry {
impl ConfigFileRegistry { impl ConfigFileRegistry {
/// Creates the registry containing the KSP default file mappings known in the current release. /// Creates the registry containing the KSP default file mappings known in the current release.
pub fn defaults() -> ksp_core_lib::Result<Self> { pub fn defaults() -> ksp_core_lib::Result<Self> {
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, FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK,
ConfigFileKind::Config, ConfigFileKind::Config,
DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME, DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME,
std::option::Option::Some(FILE_ID_SCHEMA_COMPOSITE), 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::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), 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), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
return crate::build_registry([ return crate::build_registry([
composite, solprices_composite,
wallet_composite,
composite_schema, composite_schema,
logging, logging,
logging_schema, logging_schema,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/tests/ownership.rs // file: crates/ksp-config-lib/tests/ownership.rs
// version: 7 // version: 8
//! Workspace ownership audits for KSP application configuration boundaries. //! 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 [ for token in [
"\".env\"", "\".env\"",
"\"composite.ksp-app-solprices-desk.json\"",
"\"composite.ksp-app-wallet-desk.json\"", "\"composite.ksp-app-wallet-desk.json\"",
"\"composite.schema.json\"", "\"composite.schema.json\"",
"\"std.logging.json\"", "\"std.logging.json\"",

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/tests/public_api.rs // 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, //! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity,
//! Logging/Transport adapters and management contracts. //! 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:?}"); assert!(registry.is_ok(), "public registry should remain constructible: {registry:?}");
if let std::result::Result::Ok(registry) = registry { if let std::result::Result::Ok(registry) = registry {
let descriptors: std::vec::Vec<&ksp_config_lib::ConfigFileDescriptor> = registry.descriptors().collect(); let descriptors: std::vec::Vec<&ksp_config_lib::ConfigFileDescriptor> = registry.descriptors().collect();
assert_eq!(descriptors.len(), 10); assert_eq!(descriptors.len(), 11);
assert_eq!(descriptors[0].file_id().as_str(), ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); 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_STD_LOGGING); 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_OFFCHAIN_TRANSPORT); 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_TRANSPORT); 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_WALLET); 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_SCHEMA_COMPOSITE); 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_STD_LOGGING); 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_OFFCHAIN_TRANSPORT); 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_TRANSPORT); 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_WALLET); assert_eq!(descriptors[9].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT);
let schema_file_id = descriptors[4].schema_file_id(); 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"); 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 { 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); 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!(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); assert_ne!(ksp_config_lib::ConfigProfileSelectionSource::Composite, ksp_config_lib::ConfigProfileSelectionSource::Explicit);
} }

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/unit_tests/offchain_transport.rs // file: crates/ksp-config-lib/unit_tests/offchain_transport.rs
// version: 2 // version: 3
const TEST_PAIR: &str = "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE"; 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] #[test]
fn committed_all_free_profile_requires_config_owned_secrets_and_public_pair_provenance() { fn committed_all_free_profile_requires_config_owned_secrets_and_public_pair_provenance() {
let engine = committed_engine(); let engine = committed_engine();

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/unit_tests/registry.rs // file: crates/ksp-config-lib/unit_tests/registry.rs
// version: 9 // version: 10
#[test] #[test]
fn descriptors_expose_complete_registry_in_deterministic_file_id_order() { 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:?}"); assert!(registry.is_ok(), "default registry should be valid: {registry:?}");
if let std::result::Result::Ok(registry) = registry { if let std::result::Result::Ok(registry) = registry {
let descriptors: std::vec::Vec<&crate::ConfigFileDescriptor> = registry.descriptors().collect(); let descriptors: std::vec::Vec<&crate::ConfigFileDescriptor> = registry.descriptors().collect();
assert_eq!(descriptors.len(), 10); assert_eq!(descriptors.len(), 11);
assert_eq!(descriptors[0].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK); 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_WALLET_DESK_FILENAME)); 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[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[1].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
assert_eq!(descriptors[2].file_id().as_str(), crate::FILE_ID_STD_OFFCHAIN_TRANSPORT); assert_eq!(descriptors[1].filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME));
assert_eq!(descriptors[3].file_id().as_str(), crate::FILE_ID_STD_TRANSPORT); assert_eq!(descriptors[2].file_id().as_str(), crate::FILE_ID_STD_LOGGING);
assert_eq!(descriptors[4].file_id().as_str(), crate::FILE_ID_STD_WALLET); assert_eq!(descriptors[3].file_id().as_str(), crate::FILE_ID_STD_OFFCHAIN_TRANSPORT);
assert_eq!(descriptors[4].filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_FILENAME)); assert_eq!(descriptors[4].file_id().as_str(), crate::FILE_ID_STD_TRANSPORT);
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_STD_WALLET);
assert_eq!(descriptors[5].file_id().as_str(), crate::FILE_ID_SCHEMA_COMPOSITE); assert_eq!(descriptors[5].filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_FILENAME));
assert_eq!(descriptors[6].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING); 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[7].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT); assert_eq!(descriptors[6].file_id().as_str(), crate::FILE_ID_SCHEMA_COMPOSITE);
assert_eq!(descriptors[8].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT); assert_eq!(descriptors[7].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING);
assert_eq!(descriptors[9].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_WALLET); assert_eq!(descriptors[8].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT);
assert!(descriptors[0..5].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Config)); assert_eq!(descriptors[9].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT);
assert!(descriptors[5..10].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Schema)); 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] #[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 registry = crate::ConfigFileRegistry::defaults();
let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_COMPOSITE); 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!(registry.is_ok(), "default registry should be valid: {registry:?}");
assert!(schema_id.is_ok(), "composite schema file_id should be valid: {schema_id:?}"); 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:?}"); assert!(solprices_id.is_ok(), "SOL Prices Desk composite runtime file_id should be valid: {solprices_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!(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 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!(schema.is_ok(), "generic composite schema should be registered: {schema:?}");
assert!(runtime.is_ok(), "Wallet Desk runtime composite should be registered: {runtime:?}"); assert!(solprices.is_ok(), "SOL Prices Desk runtime composite should be registered: {solprices:?}");
if let (std::result::Result::Ok(schema), std::result::Result::Ok(runtime)) = (schema, runtime) { 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.kind(), crate::ConfigFileKind::Schema);
assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_SCHEMA_FILENAME)); assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_SCHEMA_FILENAME));
assert_eq!(runtime.kind(), crate::ConfigFileKind::Config); assert_eq!(solprices.kind(), crate::ConfigFileKind::Config);
assert_eq!(runtime.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME)); assert_eq!(solprices.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_SOLPRICES_DESK_FILENAME));
assert_eq!(runtime.schema_file_id(), std::option::Option::Some(&schema_id)); 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));
} }
} }
} }

250
deltas/0.2.12/pre.003.md Normal file
View File

@@ -0,0 +1,250 @@
<!-- file: deltas/0.2.12/pre.003.md -->
<!-- version: 1 -->
# 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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md --> <!-- file: docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md -->
<!-- version: 4 --> <!-- version: 5 -->
# Plan `0.2.12` — SOL Prices Desk + intégration prix Wallet Desk # 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` ### `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. 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 #### `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é. 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 ### `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. 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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/015-V0_2_12_SOL_PRICES_DESK.md --> <!-- file: docs/validation/015-V0_2_12_SOL_PRICES_DESK.md -->
<!-- version: 3 --> <!-- version: 4 -->
# Validation `0.2.12` — SOL Prices Desk + intégration Wallet Desk # 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 | | Contrat | Valeur attendue | Statut |
|---------------------------------------|--------------------------------------------------------------|---------| |---------------------------------------|--------------------------------------------------------------|---------|
| Price Desk composite file | `config/composite.ksp-app-solprices-desk.json` | PENDING | | Price Desk composite file | `config/composite.ksp-app-solprices-desk.json` | PASS |
| Price Desk file_id | `cfg.composite.ksp-app-solprices-desk` | PENDING | | Price Desk file_id | `cfg.composite.ksp-app-solprices-desk` | PASS |
| schema | `schema.composite` existant | PENDING | | schema | `schema.composite` existant | PASS |
| components | `logging`, `offchain_transport` | PENDING | | components | `logging`, `offchain_transport` | PENDING |
| default profile | `public_keyless` | PENDING | | default profile | `public_keyless` | PENDING |
| optional profile | `all_free` | PENDING | | optional profile | `all_free` | PENDING |
| test profile | `tests` -> offchain `public_keyless` | PENDING | | test profile | `tests` -> offchain `public_keyless` | PENDING |
| Wallet composite | ajoute offchain `public_keyless` à devnet/temporary/tests | PENDING | | Wallet composite | ajoute offchain `public_keyless` à devnet/temporary/tests | PENDING |
| registry/resource count | 11 après nouveau composite | PENDING | | registry/resource count | 11 après nouveau composite | PASS |
| registry + resources atomiques | Config + Config Desk + Wallet Desk + Price Desk même tranche | PENDING | | registry + resources atomiques | Config + Config Desk + Wallet Desk + Price Desk même tranche | PASS |
| nouveau schema Config | inutile | OUT | | nouveau schema Config | inutile | OUT |
| modification `std.offchain_transport` | pas de lacune identifiée | OUT | | modification `std.offchain_transport` | pas de lacune identifiée | OUT |
@@ -149,7 +149,7 @@ NOT RUN commande non exécutée dans le contexte indiqué
| frontendDist | hors source, path Price Desk dédié | PASS | | frontendDist | hors source, path Price Desk dédié | PASS |
| hooks | npm dev/build cwd crate | PASS | | hooks | npm dev/build cwd crate | PASS |
| backend cross-app | aucune référence Wallet par erreur | PASS | | backend cross-app | aucune référence Wallet par erreur | PASS |
| resources | registry complet packagé | PENDING | | resources | registry complet packagé | PASS |
| bundle | targets/icons cohérents | PASS | | bundle | targets/icons cohérents | PASS |
| Tauri production build Price Desk | crate-local PASS attendu | PENDING | | Tauri production build Price Desk | crate-local PASS attendu | PENDING |
| Tauri production build Wallet Desk | crate-local PASS attendu | PENDING | | Tauri production build Wallet Desk | crate-local PASS attendu | PENDING |
@@ -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 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` ## 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 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
```