diff --git a/Cargo.toml b/Cargo.toml
index 8fe9a9c..5110466 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,12 +1,12 @@
# file: Cargo.toml
-# version: 455
+# version: 456
[workspace]
resolver = "3"
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"]
[workspace.package]
-version = "0.3.8-pre.5.fix.3"
+version = "0.3.8-pre.6"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
diff --git a/config/composite.ksp-app-store-desk.json b/config/composite.ksp-app-store-desk.json
new file mode 100644
index 0000000..25192c8
--- /dev/null
+++ b/config/composite.ksp-app-store-desk.json
@@ -0,0 +1,51 @@
+{
+ "format_version": 1,
+ "default_profile": "mainnet",
+ "profiles": [
+ {
+ "profile_id": "devnet",
+ "documents": [
+ {
+ "component_id": "logging",
+ "file_id": "cfg.std.logging",
+ "profile_id": "supertrace"
+ },
+ {
+ "component_id": "store",
+ "file_id": "cfg.std.store",
+ "profile_id": "devnet"
+ }
+ ]
+ },
+ {
+ "profile_id": "mainnet",
+ "documents": [
+ {
+ "component_id": "logging",
+ "file_id": "cfg.std.logging",
+ "profile_id": "supertrace"
+ },
+ {
+ "component_id": "store",
+ "file_id": "cfg.std.store",
+ "profile_id": "mainnet"
+ }
+ ]
+ },
+ {
+ "profile_id": "testnet",
+ "documents": [
+ {
+ "component_id": "logging",
+ "file_id": "cfg.std.logging",
+ "profile_id": "supertrace"
+ },
+ {
+ "component_id": "store",
+ "file_id": "cfg.std.store",
+ "profile_id": "testnet"
+ }
+ ]
+ }
+ ]
+}
diff --git a/crates/ksp-app-backfill-desk/tauri.conf.json b/crates/ksp-app-backfill-desk/tauri.conf.json
index 7d6bde4..3a7b78f 100644
--- a/crates/ksp-app-backfill-desk/tauri.conf.json
+++ b/crates/ksp-app-backfill-desk/tauri.conf.json
@@ -54,18 +54,19 @@
"resources": {
"../../config/composite.ksp-app-backfill-desk.json": "config/composite.ksp-app-backfill-desk.json",
"../../config/composite.ksp-app-solprices-desk.json": "config/composite.ksp-app-solprices-desk.json",
+ "../../config/composite.ksp-app-store-desk.json": "config/composite.ksp-app-store-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",
- "../../config/std.store.json": "config/std.store.json",
- "../../config/std.transport.json": "config/std.transport.json",
- "../../config/std.wallet.json": "config/std.wallet.json",
"../../config/schemas/composite.schema.json": "config/schemas/composite.schema.json",
"../../config/schemas/std.logging.schema.json": "config/schemas/std.logging.schema.json",
"../../config/schemas/std.offchain_transport.schema.json": "config/schemas/std.offchain_transport.schema.json",
"../../config/schemas/std.store.schema.json": "config/schemas/std.store.schema.json",
"../../config/schemas/std.transport.schema.json": "config/schemas/std.transport.schema.json",
- "../../config/schemas/std.wallet.schema.json": "config/schemas/std.wallet.schema.json"
+ "../../config/schemas/std.wallet.schema.json": "config/schemas/std.wallet.schema.json",
+ "../../config/std.logging.json": "config/std.logging.json",
+ "../../config/std.offchain_transport.json": "config/std.offchain_transport.json",
+ "../../config/std.store.json": "config/std.store.json",
+ "../../config/std.transport.json": "config/std.transport.json",
+ "../../config/std.wallet.json": "config/std.wallet.json"
}
}
}
diff --git a/crates/ksp-app-backfill-desk/tests/desktop_contract.rs b/crates/ksp-app-backfill-desk/tests/desktop_contract.rs
index df6fd11..68cc661 100644
--- a/crates/ksp-app-backfill-desk/tests/desktop_contract.rs
+++ b/crates/ksp-app-backfill-desk/tests/desktop_contract.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/tests/desktop_contract.rs
-// version: 16
+// version: 17
//! Structural desktop contract checks for the Backfill Desk scaffold.
@@ -145,10 +145,11 @@ fn pre_003_composite_packaging_follows_atomic_config_registry_contract() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
- assert_eq!(resources.len(), 14);
+ assert_eq!(resources.len(), 15);
for required in [
"../../config/composite.ksp-app-backfill-desk.json",
"../../config/composite.ksp-app-solprices-desk.json",
+ "../../config/composite.ksp-app-store-desk.json",
"../../config/composite.ksp-app-wallet-desk.json",
"../../config/std.logging.json",
"../../config/std.offchain_transport.json",
diff --git a/crates/ksp-app-config-desk/tauri.conf.json b/crates/ksp-app-config-desk/tauri.conf.json
index d4be766..192ddc2 100644
--- a/crates/ksp-app-config-desk/tauri.conf.json
+++ b/crates/ksp-app-config-desk/tauri.conf.json
@@ -54,18 +54,19 @@
"resources": {
"../../config/composite.ksp-app-backfill-desk.json": "config/composite.ksp-app-backfill-desk.json",
"../../config/composite.ksp-app-solprices-desk.json": "config/composite.ksp-app-solprices-desk.json",
+ "../../config/composite.ksp-app-store-desk.json": "config/composite.ksp-app-store-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",
- "../../config/std.store.json": "config/std.store.json",
- "../../config/std.transport.json": "config/std.transport.json",
- "../../config/std.wallet.json": "config/std.wallet.json",
"../../config/schemas/composite.schema.json": "config/schemas/composite.schema.json",
"../../config/schemas/std.logging.schema.json": "config/schemas/std.logging.schema.json",
"../../config/schemas/std.offchain_transport.schema.json": "config/schemas/std.offchain_transport.schema.json",
"../../config/schemas/std.store.schema.json": "config/schemas/std.store.schema.json",
"../../config/schemas/std.transport.schema.json": "config/schemas/std.transport.schema.json",
- "../../config/schemas/std.wallet.schema.json": "config/schemas/std.wallet.schema.json"
+ "../../config/schemas/std.wallet.schema.json": "config/schemas/std.wallet.schema.json",
+ "../../config/std.logging.json": "config/std.logging.json",
+ "../../config/std.offchain_transport.json": "config/std.offchain_transport.json",
+ "../../config/std.store.json": "config/std.store.json",
+ "../../config/std.transport.json": "config/std.transport.json",
+ "../../config/std.wallet.json": "config/std.wallet.json"
}
}
}
diff --git a/crates/ksp-app-config-desk/tests/desktop_contract.rs b/crates/ksp-app-config-desk/tests/desktop_contract.rs
index 765f598..06e48e6 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: 12
+// version: 13
//! Desktop build/shell contract audits for Config Desk.
@@ -110,7 +110,7 @@ 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(), 14);
+ assert_eq!(resources.len(), 15);
assert_eq!(
resources.get("../../config/composite.ksp-app-backfill-desk.json").and_then(serde_json::Value::as_str),
std::option::Option::Some("config/composite.ksp-app-backfill-desk.json"),
@@ -119,6 +119,10 @@ fn pre_018_packaged_runtime_bundles_config_resources_and_activates_shared_writab
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-store-desk.json").and_then(serde_json::Value::as_str),
+ std::option::Option::Some("config/composite.ksp-app-store-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/tauri.conf.json b/crates/ksp-app-solprices-desk/tauri.conf.json
index aca4a3a..74ffd60 100644
--- a/crates/ksp-app-solprices-desk/tauri.conf.json
+++ b/crates/ksp-app-solprices-desk/tauri.conf.json
@@ -54,18 +54,19 @@
"resources": {
"../../config/composite.ksp-app-backfill-desk.json": "config/composite.ksp-app-backfill-desk.json",
"../../config/composite.ksp-app-solprices-desk.json": "config/composite.ksp-app-solprices-desk.json",
+ "../../config/composite.ksp-app-store-desk.json": "config/composite.ksp-app-store-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",
- "../../config/std.store.json": "config/std.store.json",
- "../../config/std.transport.json": "config/std.transport.json",
- "../../config/std.wallet.json": "config/std.wallet.json",
"../../config/schemas/composite.schema.json": "config/schemas/composite.schema.json",
"../../config/schemas/std.logging.schema.json": "config/schemas/std.logging.schema.json",
"../../config/schemas/std.offchain_transport.schema.json": "config/schemas/std.offchain_transport.schema.json",
"../../config/schemas/std.store.schema.json": "config/schemas/std.store.schema.json",
"../../config/schemas/std.transport.schema.json": "config/schemas/std.transport.schema.json",
- "../../config/schemas/std.wallet.schema.json": "config/schemas/std.wallet.schema.json"
+ "../../config/schemas/std.wallet.schema.json": "config/schemas/std.wallet.schema.json",
+ "../../config/std.logging.json": "config/std.logging.json",
+ "../../config/std.offchain_transport.json": "config/std.offchain_transport.json",
+ "../../config/std.store.json": "config/std.store.json",
+ "../../config/std.transport.json": "config/std.transport.json",
+ "../../config/std.wallet.json": "config/std.wallet.json"
}
}
}
diff --git a/crates/ksp-app-solprices-desk/tests/desktop_contract.rs b/crates/ksp-app-solprices-desk/tests/desktop_contract.rs
index d3d34a8..a949d87 100644
--- a/crates/ksp-app-solprices-desk/tests/desktop_contract.rs
+++ b/crates/ksp-app-solprices-desk/tests/desktop_contract.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-solprices-desk/tests/desktop_contract.rs
-// version: 11
+// version: 12
//! Desktop scaffold, shared-template and Config packaging contract audits for SOL Prices Desk `0.2.12`.
@@ -85,13 +85,13 @@ fn pre_002_package_is_mixed_lib_bin_and_frontend_is_scaffold_only() {
}
#[test]
-fn pre_004_packaging_contains_current_fourteen_config_resources() {
+fn v0_3_8_pre_006_packaging_contains_current_fifteen_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(), 14);
+ assert_eq!(resources.len(), 15);
assert_eq!(
resources.get("../../config/composite.ksp-app-backfill-desk.json").and_then(serde_json::Value::as_str),
std::option::Option::Some("config/composite.ksp-app-backfill-desk.json"),
@@ -100,6 +100,10 @@ fn pre_004_packaging_contains_current_fourteen_config_resources() {
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-store-desk.json").and_then(serde_json::Value::as_str),
+ std::option::Option::Some("config/composite.ksp-app-store-desk.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/std.store.json"));
diff --git a/crates/ksp-app-store-desk/Cargo.toml b/crates/ksp-app-store-desk/Cargo.toml
index fb84be2..4fab155 100644
--- a/crates/ksp-app-store-desk/Cargo.toml
+++ b/crates/ksp-app-store-desk/Cargo.toml
@@ -1,5 +1,5 @@
# file: crates/ksp-app-store-desk/Cargo.toml
-# version: 1
+# version: 2
[package]
name = "ksp-app-store-desk"
@@ -27,10 +27,11 @@ fs2.workspace = true
ksp-config-lib = { path = "../ksp-config-lib" }
ksp-core-lib = { path = "../ksp-core-lib" }
ksp-logging-lib = { path = "../ksp-logging-lib" }
+ksp-store-lib = { path = "../ksp-store-lib" }
serde = { workspace = true, features = ["derive"] }
tauri.workspace = true
tauri-plugin-tracing.workspace = true
-tokio = { workspace = true, features = ["time"] }
+tokio = { workspace = true, features = ["sync", "time"] }
ts-rs.workspace = true
[dev-dependencies]
diff --git a/crates/ksp-app-store-desk/frontend/main.html b/crates/ksp-app-store-desk/frontend/main.html
index b00ca2d..818db1f 100644
--- a/crates/ksp-app-store-desk/frontend/main.html
+++ b/crates/ksp-app-store-desk/frontend/main.html
@@ -1,5 +1,5 @@
-
+
@@ -54,22 +54,29 @@
Overview
-
Scaffold read-only. Le lifecycle Store et les diagnostics backend-neutral seront branchés dans une tranche ultérieure.
+
État backend-neutral du Store sélectionné par le composite Config. Aucune donnée RAW complète n'est chargée ici.
+
+
+ —
+
-
—
-
-
-
TransactionsTable skeleton
-
+
+
+
+
+
+
RAW Transactions
-
Structure DataTables uniquement. Aucune capability Store n'est encore invoquée.
+
Structure DataTables conservée hors connexion métier jusqu'à pre.007. Le runtime Store est ouvert par le composite.
@@ -83,7 +90,7 @@
RAW Accounts
-
Structure DataTables uniquement. Aucune donnée Account ni bytes ne traversent IPC dans ce scaffold.
+
Structure DataTables conservée hors connexion métier jusqu'à pre.008. Aucun byte Account ne traverse IPC dans cette tranche.
@@ -98,7 +105,7 @@
Diagnostics
-
État sûr du scaffold Config / Logging. Aucun diagnostic physique Store n'est disponible à cette tranche.
+
État sûr Config / Logging / Store. Les URI et détails physiques sensibles restent hors IPC.
diff --git a/crates/ksp-app-store-desk/frontend/ts/main.ts b/crates/ksp-app-store-desk/frontend/ts/main.ts
index 4092eb3..5f2c55e 100644
--- a/crates/ksp-app-store-desk/frontend/ts/main.ts
+++ b/crates/ksp-app-store-desk/frontend/ts/main.ts
@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/frontend/ts/main.ts
-// version: 2
+// version: 3
import DataTable from "datatables.net-bs5";
import "datatables.net-bs5/css/dataTables.bootstrap5.css";
@@ -7,6 +7,7 @@ import "bootstrap";
import ResizeObserver from "resize-observer-polyfill";
import "simplebar";
import type { ShellStatusDto } from "./bindings/ksp_app_store_desk/dto_common/ShellStatusDto.ts";
+import type { StoreRuntimeStatusDto } from "./bindings/ksp_app_store_desk/dto_common/StoreRuntimeStatusDto.ts";
import { frontendDebug, frontendError, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log";
import { invokeKsp } from "./invoke";
@@ -33,6 +34,22 @@ function isViewId(value: string | undefined): value is ViewId {
return value === "overview" || value === "transactions" || value === "accounts" || value === "diagnostics";
}
+function setText(elementId: string, value: string): void {
+ const element = document.querySelector(`#${elementId}`);
+ if (element) {
+ element.textContent = value;
+ }
+}
+
+function renderDiagnostic(elementId: string, diagnostic: { domain: string; code: string; message: string } | null): void {
+ const element = document.querySelector(`#${elementId}`);
+ if (!element) {
+ return;
+ }
+ element.hidden = diagnostic === null;
+ element.textContent = diagnostic ? `${diagnostic.domain}/${diagnostic.code}: ${diagnostic.message}` : "";
+}
+
function activateView(viewId: ViewId): void {
document.querySelectorAll("[data-view-panel]").forEach(panel => {
panel.hidden = panel.dataset.viewPanel !== viewId;
@@ -67,7 +84,7 @@ function initializeEmptyTables(): void {
scrollX: true,
autoWidth: false,
language: {
- emptyTable: "Aucune donnée Store n'est chargée dans le scaffold pre.002.",
+ emptyTable: "Aucune donnée RAW n'est chargée dans la tranche pre.006.",
},
};
transactionTable = new DataTable("#rawTransactionsTable", commonOptions) as unknown as EmptyDataTable;
@@ -76,39 +93,65 @@ function initializeEmptyTables(): void {
}
function renderShellStatus(status: ShellStatusDto): void {
- const versionBadge = document.querySelector("#appVersionBadge");
- if (versionBadge) {
- versionBadge.textContent = status.applicationVersion;
- }
- const values: Record = {
- runtimeVersion: status.applicationVersion,
- runtimeShellPhase: status.shellPhase,
- runtimeConfigDocuments: status.configDocumentCount.toString(),
- runtimeLoggingProfile: status.activeLoggingProfile ?? "fallback",
- runtimeLoggingFallback: status.fallbackLoggingActive ? "oui" : "non",
- };
- for (const [elementId, value] of Object.entries(values)) {
- const element = document.querySelector(`#${elementId}`);
- if (element) {
- element.textContent = value;
- }
- }
- const diagnostic = document.querySelector("#runtimeDiagnostic");
- if (diagnostic) {
- diagnostic.hidden = status.startupDiagnostic === null;
- diagnostic.textContent = status.startupDiagnostic ? `${status.startupDiagnostic.domain}/${status.startupDiagnostic.code}: ${status.startupDiagnostic.message}` : "";
- }
+ setText("appVersionBadge", status.applicationVersion);
+ setText("runtimeVersion", status.applicationVersion);
+ setText("runtimeShellPhase", status.shellPhase);
+ setText("runtimeConfigDocuments", status.configDocumentCount.toString());
+ setText("runtimeLoggingProfile", status.activeLoggingProfile ?? "fallback");
+ setText("runtimeLoggingFallback", status.fallbackLoggingActive ? "oui" : "non");
+ renderDiagnostic("runtimeDiagnostic", status.startupDiagnostic);
frontendTrace("main", "Store Desk shell status rendered", {
fallbackLoggingActive: status.fallbackLoggingActive,
shellPhase: status.shellPhase,
});
}
+function renderStoreRuntimeStatus(status: StoreRuntimeStatusDto): void {
+ const target = status.backendKind && status.network ? `${status.backendKind} / ${status.network}` : status.network ?? status.backendKind ?? "indisponible";
+ const migration = status.migrationVersionDecimal === null ? `inconnue · ${status.pendingMigrationCount} pending` : `v${status.migrationVersionDecimal} · ${status.pendingMigrationCount} pending`;
+ const pool = `${status.poolAvailable}/${status.poolSize}/${status.poolCapacity} · wait ${status.poolWaiting}`;
+ const profile = status.profileId ?? "indisponible";
+
+ setText("overviewStoreProfile", profile);
+ setText("overviewStoreTarget", target);
+ setText("overviewStoreHealth", status.healthState);
+ setText("overviewStorePool", pool);
+ setText("overviewStoreMigration", migration);
+ renderDiagnostic("overviewStoreDiagnostic", status.diagnostic);
+
+ setText("runtimeStoreProfile", profile);
+ setText("runtimeStoreTarget", target);
+ setText("runtimeStoreHealth", status.healthState);
+ setText("runtimeStoreMigration", migration);
+ setText("runtimeStorePool", pool);
+ renderDiagnostic("runtimeStoreDiagnostic", status.diagnostic);
+
+ frontendTrace("main", "Store Desk Store runtime status rendered", {
+ healthState: status.healthState,
+ pendingMigrationCount: status.pendingMigrationCount,
+ poolCapacity: status.poolCapacity,
+ poolSize: status.poolSize,
+ storeOpen: status.storeOpen,
+ });
+}
+
+async function refreshStoreRuntime(): Promise {
+ frontendDebug("main", "Store Desk Store runtime refresh started");
+ try {
+ const status = await invokeKsp("main", "store_runtime_status");
+ renderStoreRuntimeStatus(status);
+ frontendDebug("main", "Store Desk Store runtime refresh completed", { healthState: status.healthState, storeOpen: status.storeOpen });
+ } catch {
+ frontendError("main", "Store Desk Store runtime refresh failed");
+ }
+}
+
async function refreshDiagnostics(): Promise {
frontendDebug("main", "Store Desk diagnostics refresh started");
try {
const status = await invokeKsp("main", "get_shell_status");
renderShellStatus(status);
+ await refreshStoreRuntime();
frontendDebug("main", "Store Desk diagnostics refresh completed");
} catch {
frontendError("main", "Store Desk diagnostics refresh failed");
@@ -125,6 +168,11 @@ function installInteractions(): void {
}
});
});
+ const refreshOverview = document.querySelector("#refreshOverview");
+ refreshOverview?.addEventListener("click", () => {
+ frontendDebug("main", "Store Desk Overview refresh button clicked");
+ void refreshStoreRuntime();
+ });
const refreshButton = document.querySelector("#refreshDiagnostics");
refreshButton?.addEventListener("click", () => {
frontendDebug("main", "Store Desk diagnostics refresh button clicked");
@@ -139,7 +187,7 @@ function installInteractions(): void {
if (!control) {
return;
}
- if (control instanceof HTMLButtonElement && (control.dataset.view || control.id === "refreshDiagnostics")) {
+ if (control instanceof HTMLButtonElement && (control.dataset.view || control.id === "refreshDiagnostics" || control.id === "refreshOverview")) {
return;
}
const controlId = control.id || control.getAttribute("data-bs-target") || control.getAttribute("aria-controls") || "anonymous";
diff --git a/crates/ksp-app-store-desk/src/app_state.rs b/crates/ksp-app-store-desk/src/app_state.rs
index b032863..7e5f980 100644
--- a/crates/ksp-app-store-desk/src/app_state.rs
+++ b/crates/ksp-app-store-desk/src/app_state.rs
@@ -1,18 +1,20 @@
// file: crates/ksp-app-store-desk/src/app_state.rs
-// version: 1
+// version: 2
-//! Shared backend state owned by the Store Desk Tauri scaffold.
+//! Shared backend state owned by the Store Desk Tauri application.
/// Shared Store Desk application state managed by Tauri.
pub(crate) struct AppState {
config_management: ksp_config_lib::ConfigManagement,
logging_runtime: std::sync::Mutex,
+ shutdown_started: std::sync::atomic::AtomicBool,
splash_settings: crate::SplashSettings,
splash_sequence_started: std::sync::atomic::AtomicBool,
+ store_startup: crate::StoreStartup,
}
impl crate::AppState {
- /// Initializes Config ownership, transient standard Logging and common splash settings.
+ /// Initializes Config ownership, composite-managed Logging, Store readiness and common splash settings.
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 +31,7 @@ impl crate::AppState {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
+ let store_startup = tauri::async_runtime::block_on(crate::initialize_store(&config_management));
let splash_settings = crate::SplashSettings::load();
let splash_settings = match splash_settings {
std::result::Result::Ok(value) => value,
@@ -63,12 +66,14 @@ impl crate::AppState {
fallback_active: logging_startup.fallback_active,
startup_diagnostic: logging_startup.startup_diagnostic,
}),
+ shutdown_started: std::sync::atomic::AtomicBool::new(false),
splash_settings,
splash_sequence_started: std::sync::atomic::AtomicBool::new(false),
+ store_startup,
});
}
- /// Builds the safe runtime status exposed by the Store Desk scaffold shell.
+ /// Builds the safe shell/Config/Logging status exposed by Store Desk diagnostics.
pub(crate) fn shell_status(&self) -> ksp_core_lib::Result {
let document_count = self.config_management.engine().registry().descriptors().count();
let document_count = u32::try_from(document_count);
@@ -100,11 +105,16 @@ impl crate::AppState {
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.006-store-overview".to_owned(),
startup_diagnostic: runtime.startup_diagnostic.clone(),
});
}
+ /// Builds one fresh safe Store runtime/health projection for the Overview and Diagnostics screens.
+ pub(crate) async fn store_runtime_status(&self) -> crate::StoreRuntimeStatusDto {
+ return self.store_startup.status().await;
+ }
+
/// Returns the resolved splash timings captured during application bootstrap.
#[must_use]
pub(crate) const fn splash_settings(&self) -> crate::SplashSettings {
@@ -118,6 +128,16 @@ impl crate::AppState {
.compare_exchange(false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire)
.is_ok();
}
+
+ /// Marks graceful application shutdown as started and reports whether this caller won the one-shot transition.
+ pub(crate) fn begin_shutdown(&self) -> bool {
+ return self.shutdown_started.compare_exchange(false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire).is_ok();
+ }
+
+ /// Explicitly closes the retained Store runtime when application shutdown begins.
+ pub(crate) async fn close_store(&self) -> ksp_core_lib::Result<()> {
+ return self.store_startup.close().await;
+ }
}
struct LoggingRuntimeState {
diff --git a/crates/ksp-app-store-desk/src/bootstrap.rs b/crates/ksp-app-store-desk/src/bootstrap.rs
index 88159d2..646f7a1 100644
--- a/crates/ksp-app-store-desk/src/bootstrap.rs
+++ b/crates/ksp-app-store-desk/src/bootstrap.rs
@@ -1,7 +1,7 @@
// file: crates/ksp-app-store-desk/src/bootstrap.rs
-// version: 2
+// version: 3
-//! Standard Config and verbose development Logging bootstrap for Store Desk.
+//! Composite Config and verbose development Logging bootstrap for Store Desk.
/// Crate-internal Logging startup state shared by the application state.
pub(crate) struct LoggingStartup {
@@ -31,7 +31,48 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine));
}
-/// Initializes development Logging from the standard `supertrace` profile, with a trace-level in-memory fallback.
+/// Loads the concrete Store Desk Config composite using its registered logical `file_id` and autonomous default profile.
+pub(crate) fn load_store_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_STORE_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 Store 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, "Store 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, "Store 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 development Logging from the Store Desk composite, with a trace-level in-memory fallback.
pub(crate) fn initialize_logging(
management: &ksp_config_lib::ConfigManagement,
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
@@ -41,7 +82,29 @@ pub(crate) fn initialize_logging(
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return initialize_fallback_logging(error, runtime_identity),
};
- let resolved = management.engine().load_resolved_logging_config(std::option::Option::Some(crate::DEVELOPMENT_LOGGING_PROFILE), &environment);
+ let composite = crate::load_store_desk_composite(management);
+ let composite = match composite {
+ std::result::Result::Ok(value) => value,
+ std::result::Result::Err(error) => return initialize_fallback_logging(error, runtime_identity),
+ };
+ 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 initialize_fallback_logging(error, runtime_identity),
+ };
+ let store_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE);
+ if let std::result::Result::Err(error) = store_profile {
+ return initialize_fallback_logging(error, runtime_identity);
+ }
+ if logging_profile.profile_id() != crate::DEVELOPMENT_LOGGING_PROFILE {
+ let error =
+ ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "Store Desk development composite must select the supertrace Logging profile")
+ .with_context("component_id", crate::COMPOSITE_COMPONENT_ID_LOGGING)
+ .with_context("expected_profile_id", crate::DEVELOPMENT_LOGGING_PROFILE)
+ .with_context("actual_profile_id", logging_profile.profile_id());
+ return initialize_fallback_logging(error, runtime_identity);
+ }
+ 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 initialize_fallback_logging(error, runtime_identity),
@@ -55,7 +118,8 @@ pub(crate) fn initialize_logging(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_BOOTSTRAP,
active_profile = active_profile_id.as_str(),
- "initialized Store Desk development logging from explicit supertrace Config profile"
+ composite_profile = composite.profile_id(),
+ "initialized Store Desk development logging from composite-managed supertrace configuration"
);
std::result::Result::Ok(crate::LoggingStartup {
guard,
@@ -96,7 +160,7 @@ fn initialize_fallback_logging(
domain = crate::TRACING_DOMAIN_BOOTSTRAP,
error_domain = diagnostic.domain.as_str(),
error_code = diagnostic.code.as_str(),
- "Store Desk scaffold uses transient fallback Logging"
+ "Store Desk uses transient trace-level fallback Logging"
);
return std::result::Result::Ok(crate::LoggingStartup {
guard,
diff --git a/crates/ksp-app-store-desk/src/constants.rs b/crates/ksp-app-store-desk/src/constants.rs
index 3bed6a3..5073d6c 100644
--- a/crates/ksp-app-store-desk/src/constants.rs
+++ b/crates/ksp-app-store-desk/src/constants.rs
@@ -1,8 +1,12 @@
// file: crates/ksp-app-store-desk/src/constants.rs
-// version: 2
+// version: 3
-//! Application-owned tracing targets and domains.
+//! Application-owned tracing targets, domains and composite component identifiers.
+/// Composite component identifier for Logging.
+pub(crate) const COMPOSITE_COMPONENT_ID_LOGGING: &str = "logging";
+/// Composite component identifier for Store.
+pub(crate) const COMPOSITE_COMPONENT_ID_STORE: &str = "store";
/// Logging profile used while Store Desk is under active development.
pub(crate) const DEVELOPMENT_LOGGING_PROFILE: &str = "supertrace";
/// Structured domain used while bootstrapping Config and Logging.
@@ -11,6 +15,8 @@ pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "store.bootstrap";
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
/// Structured domain used by the Store Desk shell.
pub(crate) const TRACING_DOMAIN_SHELL: &str = "store.shell";
+/// Structured domain used by Store readiness, inspection admission and shutdown operations.
+pub(crate) const TRACING_DOMAIN_STORE: &str = "store.runtime";
/// Structured domain used by Tauri window lifecycle operations.
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
/// Owning target for backend events emitted by Store Desk.
diff --git a/crates/ksp-app-store-desk/src/dto_common.rs b/crates/ksp-app-store-desk/src/dto_common.rs
index 4b06b7e..5b720fc 100644
--- a/crates/ksp-app-store-desk/src/dto_common.rs
+++ b/crates/ksp-app-store-desk/src/dto_common.rs
@@ -1,7 +1,7 @@
// file: crates/ksp-app-store-desk/src/dto_common.rs
-// version: 1
+// version: 2
-//! Common Tauri DTOs shared by the Store Desk scaffold shell.
+//! Common Tauri DTOs shared by the Store Desk shell and Store runtime Overview.
use ts_rs::TS; // rust-rules: trait-import
@@ -30,7 +30,7 @@ impl crate::CommandErrorDto {
}
}
-/// Safe scaffold/runtime snapshot exposed to the Store Desk shell.
+/// Safe shell/Config/Logging snapshot exposed to Store Desk diagnostics.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_store_desk/dto_common/ShellStatusDto.ts")]
@@ -49,6 +49,37 @@ pub(crate) struct ShellStatusDto {
pub(crate) startup_diagnostic: std::option::Option,
}
+/// Backend-neutral Store runtime and health projection used by the Overview screen.
+#[derive(Clone, Debug, serde::Serialize, TS)]
+#[serde(rename_all = "camelCase")]
+#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_store_desk/dto_common/StoreRuntimeStatusDto.ts")]
+pub(crate) struct StoreRuntimeStatusDto {
+ /// Stable selected Store backend kind, when configuration resolution reached that phase.
+ pub(crate) backend_kind: std::option::Option,
+ /// Safe Store startup/health diagnostic, when present.
+ pub(crate) diagnostic: std::option::Option,
+ /// Portable Store health code (`ready`, `not_ready`, `unavailable`, `closed`, or `unknown`).
+ pub(crate) health_state: String,
+ /// Applied migration version rendered as an exact decimal string.
+ pub(crate) migration_version_decimal: std::option::Option,
+ /// Logical Store network, when configuration resolution reached that phase.
+ pub(crate) network: std::option::Option,
+ /// Number of embedded migrations newer than the applied migration version.
+ pub(crate) pending_migration_count: u32,
+ /// Number of currently available pooled backend objects.
+ pub(crate) pool_available: u32,
+ /// Configured pool capacity.
+ pub(crate) pool_capacity: u32,
+ /// Current pool size.
+ pub(crate) pool_size: u32,
+ /// Number of tasks currently waiting for pool capacity.
+ pub(crate) pool_waiting: u32,
+ /// Store profile selected by the Store Desk composite.
+ pub(crate) profile_id: std::option::Option,
+ /// Whether the application currently retains an opened Store facade.
+ pub(crate) store_open: bool,
+}
+
#[cfg(test)]
#[path = "../unit_tests/dto_common.rs"]
mod tests;
diff --git a/crates/ksp-app-store-desk/src/errors.rs b/crates/ksp-app-store-desk/src/errors.rs
index ae27a22..3bb6b1c 100644
--- a/crates/ksp-app-store-desk/src/errors.rs
+++ b/crates/ksp-app-store-desk/src/errors.rs
@@ -1,12 +1,14 @@
// file: crates/ksp-app-store-desk/src/errors.rs
-// version: 1
+// version: 2
-//! Application-local error codes for Store Desk scaffold and desktop runtime surfaces.
+//! Application-local error codes for Store Desk desktop runtime surfaces.
/// Shared Store Desk runtime state is internally inconsistent.
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "app_state_invalid");
/// Shared Store 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("store_desk", "app_state_lock_failed");
+/// Store Desk composite configuration is missing or references an unexpected document/profile.
+pub(crate) const ERROR_CODE_CONFIG_COMPOSITE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_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("store_desk", "frontend_log_level_invalid");
/// Frontend logging requested a target outside the application whitelist.
@@ -17,6 +19,8 @@ pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode =
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "splash_origin_invalid");
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
pub(crate) const ERROR_CODE_SPLASH_SETTING_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "splash_setting_invalid");
+/// Store Desk cannot complete the bounded Store shutdown lifecycle.
+pub(crate) const ERROR_CODE_STORE_SHUTDOWN_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "store_shutdown_failed");
/// Tauri runtime assembly or execution failed.
pub(crate) const ERROR_CODE_TAURI_RUNTIME_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("store_desk", "tauri_runtime_failed");
/// A required Tauri window is missing from the configured application runtime.
diff --git a/crates/ksp-app-store-desk/src/lib.rs b/crates/ksp-app-store-desk/src/lib.rs
index b6b7f26..08ed506 100644
--- a/crates/ksp-app-store-desk/src/lib.rs
+++ b/crates/ksp-app-store-desk/src/lib.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/src/lib.rs
-// version: 2
+// version: 3
//! Tauri desktop scaffold for backend-neutral KSP Store inspection.
@@ -15,6 +15,7 @@ mod errors;
mod frontend_logging;
mod logging_runtime;
mod splash;
+mod store_runtime;
mod tauri;
mod tw_main;
mod tw_splash;
@@ -28,8 +29,16 @@ pub(crate) use self::app_state::AppState;
pub(crate) use self::bootstrap::LoggingStartup;
/// Builds the Config management facade from the common KSP CLI bootstrap contract.
pub(crate) use self::bootstrap::config_management;
-/// Initializes verbose development Logging from the standard Config document.
+/// Initializes verbose development Logging from the Store Desk composite.
pub(crate) use self::bootstrap::initialize_logging;
+/// Loads the concrete Store Desk Config composite.
+pub(crate) use self::bootstrap::load_store_desk_composite;
+/// Returns one required standard component profile from the Store Desk composite.
+pub(crate) use self::bootstrap::required_composite_component_profile;
+/// Composite component identifier for Logging.
+pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_LOGGING;
+/// Composite component identifier for Store.
+pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_STORE;
/// Logging profile used while Store Desk is under active development.
pub(crate) use self::constants::DEVELOPMENT_LOGGING_PROFILE;
/// Structured domain used while bootstrapping Config and Logging.
@@ -38,6 +47,8 @@ pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
/// Structured domain used by the Store Desk shell.
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
+/// Structured domain used by Store readiness and shutdown operations.
+pub(crate) use self::constants::TRACING_DOMAIN_STORE;
/// Structured domain used by Tauri window lifecycle operations.
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
/// Owning target for backend events emitted by Store Desk.
@@ -50,12 +61,16 @@ pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
/// Safe command error projection exposed to Tauri commands.
pub(crate) use self::dto_common::CommandErrorDto;
-/// Safe scaffold/runtime snapshot exposed to the shell.
+/// Safe shell/Config/Logging snapshot exposed to the shell.
pub(crate) use self::dto_common::ShellStatusDto;
+/// Backend-neutral Store runtime and health projection exposed to Overview.
+pub(crate) use self::dto_common::StoreRuntimeStatusDto;
/// Shared Store Desk runtime state is internally inconsistent.
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
/// Shared Store Desk runtime state cannot be locked safely.
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
+/// Store Desk composite configuration is missing or references an unexpected document/profile.
+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.
@@ -66,6 +81,8 @@ pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
/// A KSP desk splash environment duration is malformed or exceeds its safety bound.
pub(crate) use self::errors::ERROR_CODE_SPLASH_SETTING_INVALID;
+/// Store Desk cannot complete the bounded Store shutdown lifecycle.
+pub(crate) use self::errors::ERROR_CODE_STORE_SHUTDOWN_FAILED;
/// Tauri runtime assembly or execution failed.
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
/// A required Tauri window is missing from the configured application runtime.
@@ -82,6 +99,12 @@ pub(crate) use self::logging_runtime::launch_identity;
pub(crate) use self::splash::SplashOrderDto;
/// Runtime timings used by the common desk splash lifecycle.
pub(crate) use self::splash::SplashSettings;
+/// Store runtime retained behind the Store Desk lifecycle boundary.
+pub(crate) use self::store_runtime::StoreRuntime;
+/// Store startup state retained by the application shell.
+pub(crate) use self::store_runtime::StoreStartup;
+/// Initializes Store from the Store Desk composite.
+pub(crate) use self::store_runtime::initialize_store;
/// Resolves the required main window or returns a typed error.
pub(crate) use self::tw_main::require_main_window;
/// Shows and focuses the main Store Desk window.
diff --git a/crates/ksp-app-store-desk/src/store_runtime.rs b/crates/ksp-app-store-desk/src/store_runtime.rs
new file mode 100644
index 0000000..f2d8792
--- /dev/null
+++ b/crates/ksp-app-store-desk/src/store_runtime.rs
@@ -0,0 +1,249 @@
+// file: crates/ksp-app-store-desk/src/store_runtime.rs
+// version: 1
+
+//! Composite-selected Store readiness, status and bounded shutdown lifecycle owned by Store Desk.
+
+/// Startup result for Store, including safe diagnostics when the desktop shell remains available without an opened runtime.
+pub(crate) struct StoreStartup {
+ backend_kind: std::option::Option,
+ diagnostic: std::option::Option,
+ network: std::option::Option,
+ profile_id: std::option::Option,
+ runtime: std::option::Option,
+}
+
+impl StoreStartup {
+ /// Builds a fresh backend-neutral runtime/health projection for the Overview screen.
+ pub(crate) async fn status(&self) -> crate::StoreRuntimeStatusDto {
+ let runtime = self.runtime.as_ref();
+ return match runtime {
+ std::option::Option::Some(value) => value.status().await,
+ std::option::Option::None => crate::StoreRuntimeStatusDto {
+ backend_kind: self.backend_kind.clone(),
+ diagnostic: self.diagnostic.clone(),
+ health_state: "unavailable".to_owned(),
+ migration_version_decimal: std::option::Option::None,
+ network: self.network.clone(),
+ pending_migration_count: 0,
+ pool_available: 0,
+ pool_capacity: 0,
+ pool_size: 0,
+ pool_waiting: 0,
+ profile_id: self.profile_id.clone(),
+ store_open: false,
+ },
+ };
+ }
+
+ /// Closes the retained Store runtime if startup reached the physical Store-open phase.
+ pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> {
+ let runtime = self.runtime.as_ref();
+ return match runtime {
+ std::option::Option::Some(value) => value.close().await,
+ std::option::Option::None => std::result::Result::Ok(()),
+ };
+ }
+}
+
+/// Executable Store runtime retained behind an async read/write lock so read-only Desk queries may coexist while shutdown remains exclusive.
+pub(crate) struct StoreRuntime {
+ profile_id: String,
+ store: tokio::sync::RwLock>,
+}
+
+impl StoreRuntime {
+ /// Builds one fresh safe Store status from the retained facade without exposing backend connection details.
+ pub(crate) async fn status(&self) -> crate::StoreRuntimeStatusDto {
+ let locked = self.store.read().await;
+ let store = locked.as_ref();
+ let store = match store {
+ std::option::Option::Some(value) => value,
+ std::option::Option::None => {
+ return crate::StoreRuntimeStatusDto {
+ backend_kind: std::option::Option::None,
+ diagnostic: std::option::Option::None,
+ health_state: "closed".to_owned(),
+ migration_version_decimal: std::option::Option::None,
+ network: std::option::Option::None,
+ pending_migration_count: 0,
+ pool_available: 0,
+ pool_capacity: 0,
+ pool_size: 0,
+ pool_waiting: 0,
+ profile_id: std::option::Option::Some(self.profile_id.clone()),
+ store_open: false,
+ };
+ },
+ };
+ let runtime = store.runtime_snapshot();
+ let health = store.health().await;
+ let health_state = store_health_state_code(health.state()).to_owned();
+ let diagnostic = non_ready_health_diagnostic(&health);
+ return crate::StoreRuntimeStatusDto {
+ backend_kind: std::option::Option::Some(runtime.backend_kind().code().to_owned()),
+ diagnostic,
+ health_state,
+ migration_version_decimal: health.migration_version().map(|value| return value.to_string()),
+ network: std::option::Option::Some(runtime.network().as_str().to_owned()),
+ pending_migration_count: health.pending_migration_count(),
+ pool_available: runtime.pool_available(),
+ pool_capacity: runtime.pool_capacity(),
+ pool_size: runtime.pool_size(),
+ pool_waiting: runtime.pool_waiting(),
+ profile_id: std::option::Option::Some(self.profile_id.clone()),
+ store_open: true,
+ };
+ }
+
+ /// Explicitly closes the Store exactly once through its backend-neutral facade after all read guards have drained.
+ pub(crate) async fn close(&self) -> ksp_core_lib::Result<()> {
+ let mut locked = self.store.write().await;
+ let store = locked.take();
+ drop(locked);
+ let store = match store {
+ std::option::Option::Some(value) => value,
+ std::option::Option::None => return std::result::Result::Ok(()),
+ };
+ let snapshot = store.runtime_snapshot();
+ let backend_kind = snapshot.backend_kind().code().to_owned();
+ let network = snapshot.network().as_str().to_owned();
+ let closed = store.close().await;
+ return match closed {
+ std::result::Result::Ok(()) => {
+ ksp_logging_lib::debug!(
+ target: crate::TRACING_TARGET,
+ domain = crate::TRACING_DOMAIN_STORE,
+ store_profile = self.profile_id.as_str(),
+ backend = backend_kind.as_str(),
+ network = network.as_str(),
+ "closed Store Desk Store runtime"
+ );
+ std::result::Result::Ok(())
+ },
+ std::result::Result::Err(error) => std::result::Result::Err(
+ ksp_core_lib::Error::new(crate::ERROR_CODE_STORE_SHUTDOWN_FAILED, "Store Desk Store shutdown failed").with_source(error),
+ ),
+ };
+ }
+}
+
+/// Resolves the composite Store target, opens Store through the facade and captures one initial readiness probe.
+pub(crate) async fn initialize_store(management: &ksp_config_lib::ConfigManagement) -> StoreStartup {
+ let environment = ksp_config_lib::ConfigEnvironment::load();
+ let environment = match environment {
+ std::result::Result::Ok(value) => value,
+ std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, std::option::Option::None, std::option::Option::None, error),
+ };
+ let composite = crate::load_store_desk_composite(management);
+ let composite = match composite {
+ std::result::Result::Ok(value) => value,
+ std::result::Result::Err(error) => return unavailable_startup(std::option::Option::None, std::option::Option::None, std::option::Option::None, error),
+ };
+ let profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE);
+ let profile = match profile {
+ std::result::Result::Ok(value) => value,
+ std::result::Result::Err(error) => {
+ return unavailable_startup(
+ std::option::Option::Some(composite.profile_id().to_owned()),
+ std::option::Option::None,
+ std::option::Option::None,
+ error,
+ );
+ },
+ };
+ let resolved = management.engine().resolve_store_config_profile(&profile, &environment);
+ let resolved = match resolved {
+ std::result::Result::Ok(value) => value,
+ std::result::Result::Err(error) => {
+ return unavailable_startup(
+ std::option::Option::Some(profile.profile_id().to_owned()),
+ std::option::Option::None,
+ std::option::Option::None,
+ error,
+ );
+ },
+ };
+ let profile_id = resolved.profile_id().to_owned();
+ let backend_kind = resolved.settings().backend_kind().code().to_owned();
+ let network = resolved.settings().network().as_str().to_owned();
+ let store = ksp_store_lib::Store::open(resolved.into_settings()).await;
+ let store = match store {
+ std::result::Result::Ok(value) => value,
+ std::result::Result::Err(error) => {
+ return unavailable_startup(
+ std::option::Option::Some(profile_id),
+ std::option::Option::Some(backend_kind),
+ std::option::Option::Some(network),
+ error,
+ );
+ },
+ };
+ let health = store.health().await;
+ ksp_logging_lib::debug!(
+ target: crate::TRACING_TARGET,
+ domain = crate::TRACING_DOMAIN_STORE,
+ store_profile = profile_id.as_str(),
+ backend = backend_kind.as_str(),
+ network = network.as_str(),
+ health_state = store_health_state_code(health.state()),
+ pending_migration_count = health.pending_migration_count(),
+ "initialized Store Desk Store from composite-managed configuration"
+ );
+ return StoreStartup {
+ backend_kind: std::option::Option::Some(backend_kind),
+ diagnostic: non_ready_health_diagnostic(&health),
+ network: std::option::Option::Some(network),
+ profile_id: std::option::Option::Some(profile_id.clone()),
+ runtime: std::option::Option::Some(StoreRuntime { profile_id, store: tokio::sync::RwLock::new(std::option::Option::Some(store)) }),
+ };
+}
+
+fn non_ready_health_diagnostic(health: &ksp_store_lib::StoreHealthSnapshot) -> std::option::Option {
+ if matches!(health.state(), ksp_store_lib::StoreHealthState::Ready) {
+ return std::option::Option::None;
+ }
+ let code = health.last_error_code();
+ return match code {
+ std::option::Option::Some(value) => std::option::Option::Some(crate::CommandErrorDto {
+ code: value.code().to_owned(),
+ domain: value.domain().to_owned(),
+ message: "Store health probe did not prove readiness".to_owned(),
+ }),
+ std::option::Option::None => std::option::Option::Some(crate::CommandErrorDto {
+ code: "store_not_ready".to_owned(),
+ domain: "store_desk".to_owned(),
+ message: "Store health probe did not prove readiness".to_owned(),
+ }),
+ };
+}
+
+fn store_health_state_code(state: ksp_store_lib::StoreHealthState) -> &'static str {
+ return match state {
+ ksp_store_lib::StoreHealthState::Ready => "ready",
+ ksp_store_lib::StoreHealthState::NotReady => "not_ready",
+ _ => "unknown",
+ };
+}
+
+fn unavailable_startup(
+ profile_id: std::option::Option,
+ backend_kind: std::option::Option,
+ network: std::option::Option,
+ error: ksp_core_lib::Error,
+) -> StoreStartup {
+ let diagnostic = crate::CommandErrorDto::from_error(&error);
+ ksp_logging_lib::warn!(
+ target: crate::TRACING_TARGET,
+ domain = crate::TRACING_DOMAIN_STORE,
+ error_domain = diagnostic.domain.as_str(),
+ error_code = diagnostic.code.as_str(),
+ "Store Desk Store readiness is unavailable; keeping desktop shell available"
+ );
+ return StoreStartup {
+ backend_kind,
+ diagnostic: std::option::Option::Some(diagnostic),
+ network,
+ profile_id,
+ runtime: std::option::Option::None,
+ };
+}
diff --git a/crates/ksp-app-store-desk/src/tauri.rs b/crates/ksp-app-store-desk/src/tauri.rs
index d4f845c..0864065 100644
--- a/crates/ksp-app-store-desk/src/tauri.rs
+++ b/crates/ksp-app-store-desk/src/tauri.rs
@@ -1,12 +1,18 @@
// file: crates/ksp-app-store-desk/src/tauri.rs
-// version: 1
+// version: 2
-//! Tauri runtime assembly for the KSP Store desktop scaffold.
+//! Tauri runtime assembly for the KSP Store desktop application.
+
+use tauri::Manager; // rust-rules: trait-import
/// Runs the Store desktop application.
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
let context = tauri::generate_context!();
+ let runtime_layout = configure_packaged_runtime(&context);
+ if let std::result::Result::Err(error) = runtime_layout {
+ return std::result::Result::Err(error);
+ }
let app_state = crate::AppState::initialize(arguments);
let app_state = match app_state {
std::result::Result::Ok(value) => value,
@@ -17,6 +23,7 @@ pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
builder = configure_plugins(builder);
builder = configure_commands(builder);
builder = configure_setup(builder);
+ builder = configure_window_events(builder);
let run_result = builder.run(context);
return match run_result {
std::result::Result::Ok(()) => std::result::Result::Ok(()),
@@ -27,6 +34,36 @@ pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
};
}
+fn configure_packaged_runtime(context: &tauri::Context) -> ksp_core_lib::Result<()> {
+ if cfg!(debug_assertions) {
+ return std::result::Result::Ok(());
+ }
+ let resource_root = tauri::utils::platform::resource_dir(context.package_info(), &tauri::Env::default());
+ let resource_root = match resource_root {
+ std::result::Result::Ok(value) => value,
+ std::result::Result::Err(error) => {
+ return std::result::Result::Err(
+ ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot resolve packaged Tauri resource directory")
+ .with_context("tauri_error", error.to_string()),
+ );
+ },
+ };
+ let layout = ksp_config_lib::prepare_packaged_runtime(resource_root.as_path());
+ let layout = match layout {
+ std::result::Result::Ok(value) => value,
+ std::result::Result::Err(error) => return std::result::Result::Err(error),
+ };
+ let working_directory = std::env::set_current_dir(layout.runtime_root());
+ return match working_directory {
+ std::result::Result::Ok(()) => std::result::Result::Ok(()),
+ std::result::Result::Err(error) => std::result::Result::Err(
+ ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot activate packaged KSP runtime directory")
+ .with_context("runtime_root", layout.runtime_root().to_string_lossy().into_owned())
+ .with_source(error),
+ ),
+ };
+}
+
fn configure_state(builder: tauri::Builder, app_state: crate::AppState) -> tauri::Builder {
return builder.manage(app_state);
}
@@ -38,7 +75,7 @@ fn configure_plugins(builder: tauri::Builder) -> tauri::Builder) -> tauri::Builder {
- return builder.invoke_handler(tauri::generate_handler![emit_frontend_log, get_shell_status, splash_frontend_ready]);
+ return builder.invoke_handler(tauri::generate_handler![emit_frontend_log, get_shell_status, splash_frontend_ready, store_runtime_status]);
}
fn configure_setup(builder: tauri::Builder) -> tauri::Builder {
@@ -51,11 +88,55 @@ fn configure_setup(builder: tauri::Builder) -> tauri::Builder) -> tauri::Builder {
+ return builder.on_window_event(|window, event| {
+ if window.label() != "main" {
+ return;
+ }
+ if let tauri::WindowEvent::CloseRequested { api, .. } = event {
+ api.prevent_close();
+ let app_handle = window.app_handle().clone();
+ let state = app_handle.state::();
+ if !state.begin_shutdown() {
+ return;
+ }
+ ksp_logging_lib::debug!(
+ target: crate::TRACING_TARGET,
+ domain = crate::TRACING_DOMAIN_WINDOWS,
+ "Store Desk main-window close requested; starting bounded Store shutdown"
+ );
+ tauri::async_runtime::spawn(async move {
+ let state = app_handle.state::();
+ let closed = state.close_store().await;
+ match closed {
+ std::result::Result::Ok(()) => {
+ ksp_logging_lib::debug!(
+ target: crate::TRACING_TARGET,
+ domain = crate::TRACING_DOMAIN_WINDOWS,
+ "Store Desk Store shutdown completed; exiting application"
+ );
+ },
+ std::result::Result::Err(error) => {
+ ksp_logging_lib::warn!(
+ target: crate::TRACING_TARGET,
+ domain = crate::TRACING_DOMAIN_WINDOWS,
+ error_domain = error.code().domain(),
+ error_code = error.code().code(),
+ "Store Desk Store shutdown failed; exiting application after bounded close attempt"
+ );
+ },
+ }
+ app_handle.exit(0);
+ });
+ }
+ });
+}
+
fn project_command_error(command: &'static str, domain: &'static str, error: &ksp_core_lib::Error) -> crate::CommandErrorDto {
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,
@@ -86,6 +167,11 @@ fn get_shell_status(state: tauri::State<'_, crate::AppState>) -> std::result::Re
};
}
+#[tauri::command]
+async fn store_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result {
+ return std::result::Result::Ok(state.store_runtime_status().await);
+}
+
#[tauri::command]
async fn splash_frontend_ready(
app: tauri::AppHandle,
diff --git a/crates/ksp-app-store-desk/tauri.conf.json b/crates/ksp-app-store-desk/tauri.conf.json
index f216ceb..960377b 100644
--- a/crates/ksp-app-store-desk/tauri.conf.json
+++ b/crates/ksp-app-store-desk/tauri.conf.json
@@ -50,6 +50,23 @@
"icon": [
"icons/favicon.png",
"icons/favicon.ico"
- ]
+ ],
+ "resources": {
+ "../../config/composite.ksp-app-backfill-desk.json": "config/composite.ksp-app-backfill-desk.json",
+ "../../config/composite.ksp-app-solprices-desk.json": "config/composite.ksp-app-solprices-desk.json",
+ "../../config/composite.ksp-app-store-desk.json": "config/composite.ksp-app-store-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",
+ "../../config/std.store.json": "config/std.store.json",
+ "../../config/std.transport.json": "config/std.transport.json",
+ "../../config/std.wallet.json": "config/std.wallet.json",
+ "../../config/schemas/composite.schema.json": "config/schemas/composite.schema.json",
+ "../../config/schemas/std.logging.schema.json": "config/schemas/std.logging.schema.json",
+ "../../config/schemas/std.offchain_transport.schema.json": "config/schemas/std.offchain_transport.schema.json",
+ "../../config/schemas/std.store.schema.json": "config/schemas/std.store.schema.json",
+ "../../config/schemas/std.transport.schema.json": "config/schemas/std.transport.schema.json",
+ "../../config/schemas/std.wallet.schema.json": "config/schemas/std.wallet.schema.json"
+ }
}
}
diff --git a/crates/ksp-app-store-desk/tests/dependency_boundary.rs b/crates/ksp-app-store-desk/tests/dependency_boundary.rs
index 0b9e3ab..f9456fd 100644
--- a/crates/ksp-app-store-desk/tests/dependency_boundary.rs
+++ b/crates/ksp-app-store-desk/tests/dependency_boundary.rs
@@ -1,13 +1,12 @@
// file: crates/ksp-app-store-desk/tests/dependency_boundary.rs
-// version: 2
+// version: 3
-//! Dependency-boundary tests for the Store Desk scaffold.
+//! Dependency-boundary tests for the Store Desk application.
#[test]
-fn pre_002_manifest_keeps_store_and_transport_runtime_dependencies_out_of_scaffold() {
+fn pre_006_manifest_uses_store_facade_without_physical_or_transport_dependencies() {
let manifest = include_str!("../Cargo.toml");
for forbidden in [
- "ksp-store-lib",
"ksp-store-api",
"ksp-store-postgres-lib",
"ksp-onchain-transport-lib",
@@ -18,18 +17,26 @@ fn pre_002_manifest_keeps_store_and_transport_runtime_dependencies_out_of_scaffo
"tokio-postgres",
"deadpool-postgres",
] {
- assert!(!manifest.contains(forbidden), "forbidden pre.002 dependency present: {forbidden}");
+ assert!(!manifest.contains(forbidden), "forbidden Store Desk dependency present: {forbidden}");
}
- for required in ["ksp-config-lib", "ksp-core-lib", "ksp-logging-lib", "tauri-plugin-tracing"] {
- assert!(manifest.contains(required), "required scaffold dependency missing: {required}");
+ for required in ["ksp-config-lib", "ksp-core-lib", "ksp-logging-lib", "ksp-store-lib", "tauri-plugin-tracing"] {
+ assert!(manifest.contains(required), "required Store Desk dependency missing: {required}");
}
}
#[test]
-fn pre_002_sources_expose_no_sql_or_physical_store_escape_hatch() {
- let sources =
- [include_str!("../src/app_state.rs"), include_str!("../src/bootstrap.rs"), include_str!("../src/tauri.rs"), include_str!("../src/lib.rs")].join("\n");
- for forbidden in ["Store::open", "tokio_postgres", "deadpool_postgres", "SELECT ", "INSERT ", "UPDATE ", "DELETE ", "ksp_store_api", "ksp_store_postgres"] {
- assert!(!sources.contains(forbidden), "pre.002 scaffold leaked forbidden Store/runtime surface: {forbidden}");
+fn pre_006_store_open_is_owned_by_store_runtime_and_never_escapes_to_physical_sql() {
+ let bootstrap = include_str!("../src/bootstrap.rs");
+ let store_runtime = include_str!("../src/store_runtime.rs");
+ let app_state = include_str!("../src/app_state.rs");
+ let tauri = include_str!("../src/tauri.rs");
+ assert!(!bootstrap.contains("Store::open"));
+ assert!(store_runtime.contains("ksp_store_lib::Store::open"));
+ assert!(store_runtime.contains("store.runtime_snapshot()"));
+ assert!(store_runtime.contains("store.health().await"));
+ assert!(store_runtime.contains("store.close().await"));
+ let sources = [bootstrap, store_runtime, app_state, tauri].join("\n");
+ for forbidden in ["tokio_postgres", "deadpool_postgres", "SELECT ", "INSERT ", "UPDATE ", "DELETE ", "ksp_store_api", "ksp_store_postgres"] {
+ assert!(!sources.contains(forbidden), "Store Desk leaked forbidden physical Store surface: {forbidden}");
}
}
diff --git a/crates/ksp-app-store-desk/tests/desktop_contract.rs b/crates/ksp-app-store-desk/tests/desktop_contract.rs
index 9864d31..ae2d28e 100644
--- a/crates/ksp-app-store-desk/tests/desktop_contract.rs
+++ b/crates/ksp-app-store-desk/tests/desktop_contract.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/tests/desktop_contract.rs
-// version: 2
+// version: 3
//! Desktop template and frontend contract tests for the Store Desk scaffold.
@@ -80,7 +80,7 @@ fn pre_002_binary_assets_reuse_the_ksp_desktop_template_byte_exact() {
}
#[test]
-fn pre_002_shell_exposes_only_planned_store_desk_views_and_empty_table_skeletons() {
+fn pre_006_shell_exposes_planned_views_store_overview_and_unconnected_raw_table_skeletons() {
let html = include_str!("../frontend/main.html");
for view in ["overview", "transactions", "accounts", "diagnostics"] {
assert!(html.contains(&format!("data-view=\"{view}\"")));
@@ -88,11 +88,16 @@ fn pre_002_shell_exposes_only_planned_store_desk_views_and_empty_table_skeletons
}
assert!(html.contains("id=\"rawTransactionsTable\""));
assert!(html.contains("id=\"rawAccountsTable\""));
- assert!(html.contains("Aucune capability Store n'est encore invoquée"));
+ for overview_id in ["refreshOverview", "overviewStoreProfile", "overviewStoreTarget", "overviewStoreHealth", "overviewStorePool", "overviewStoreMigration"]
+ {
+ assert!(html.contains(&format!("id=\"{overview_id}\"")), "missing Store Overview field: {overview_id}");
+ }
+ assert!(html.contains("jusqu'à pre.007"));
+ assert!(html.contains("jusqu'à pre.008"));
}
#[test]
-fn pre_002_datatables_are_scaffold_only_with_one_pager_owner_and_no_store_pager() {
+fn pre_006_datatables_remain_unconnected_with_one_pager_owner_and_no_store_pager() {
let source = include_str!("../frontend/ts/main.ts");
assert!(source.contains("datatables.net-bs5"));
assert!(source.contains("paging: true"));
@@ -104,3 +109,61 @@ fn pre_002_datatables_are_scaffold_only_with_one_pager_owner_and_no_store_pager(
assert!(!html.contains(forbidden), "second Store pager marker present: {forbidden}");
}
}
+
+#[test]
+fn pre_006_composite_and_packaged_resources_cover_store_desk_and_the_complete_config_registry() {
+ let composite = parse_json(include_str!("../../../config/composite.ksp-app-store-desk.json"));
+ assert!(composite.is_ok(), "Store Desk composite JSON must be valid");
+ if let std::result::Result::Ok(composite) = composite {
+ assert_eq!(composite["default_profile"], "mainnet");
+ let profiles = composite["profiles"].as_array();
+ assert!(profiles.is_some());
+ if let std::option::Option::Some(profiles) = profiles {
+ assert_eq!(profiles.len(), 3);
+ for profile in profiles {
+ let documents = profile["documents"].as_array();
+ assert!(documents.is_some());
+ if let std::option::Option::Some(documents) = documents {
+ assert_eq!(documents.len(), 2);
+ assert_eq!(documents[0]["component_id"], "logging");
+ assert_eq!(documents[0]["file_id"], "cfg.std.logging");
+ assert_eq!(documents[0]["profile_id"], "supertrace");
+ assert_eq!(documents[1]["component_id"], "store");
+ assert_eq!(documents[1]["file_id"], "cfg.std.store");
+ assert_eq!(documents[1]["profile_id"], profile["profile_id"]);
+ }
+ }
+ }
+ }
+ let tauri = parse_json(include_str!("../tauri.conf.json"));
+ assert!(tauri.is_ok(), "Store Desk Tauri JSON must be valid");
+ if let std::result::Result::Ok(tauri) = tauri {
+ let resources = tauri["bundle"]["resources"].as_object();
+ assert!(resources.is_some());
+ if let std::option::Option::Some(resources) = resources {
+ assert_eq!(resources.len(), 15);
+ assert!(resources.contains_key("../../config/composite.ksp-app-store-desk.json"));
+ assert!(resources.contains_key("../../config/std.store.json"));
+ assert!(resources.contains_key("../../config/schemas/std.store.schema.json"));
+ }
+ }
+}
+
+#[test]
+fn pre_006_store_lifecycle_is_composite_managed_health_visible_and_bounded_on_window_close() {
+ let bootstrap = include_str!("../src/bootstrap.rs");
+ let store = include_str!("../src/store_runtime.rs");
+ let tauri = include_str!("../src/tauri.rs");
+ assert!(bootstrap.contains("FILE_ID_COMPOSITE_KSP_APP_STORE_DESK"));
+ assert!(bootstrap.contains("COMPOSITE_COMPONENT_ID_STORE"));
+ assert!(store.contains("ksp_store_lib::Store::open"));
+ assert!(store.contains("store.runtime_snapshot()"));
+ assert!(store.contains("store.health().await"));
+ assert!(store.contains("store.close().await"));
+ assert!(tauri.contains("CloseRequested"));
+ assert!(tauri.contains("api.prevent_close()"));
+ assert!(tauri.contains("begin_shutdown"));
+ assert!(tauri.contains("close_store().await"));
+ assert!(tauri.contains("app_handle.exit(0)"));
+ assert!(tauri.contains("store_runtime_status"));
+}
diff --git a/crates/ksp-app-store-desk/tests/desktop_security.rs b/crates/ksp-app-store-desk/tests/desktop_security.rs
index 84e9a69..eebdee9 100644
--- a/crates/ksp-app-store-desk/tests/desktop_security.rs
+++ b/crates/ksp-app-store-desk/tests/desktop_security.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/tests/desktop_security.rs
-// version: 3
+// version: 4
//! Security boundary tests for the Store Desk scaffold.
@@ -61,7 +61,22 @@ fn pre_003_store_desk_development_logging_explicitly_selects_supertrace() {
let constants = include_str!("../src/constants.rs");
let bootstrap = include_str!("../src/bootstrap.rs");
assert!(constants.contains("DEVELOPMENT_LOGGING_PROFILE: &str = \"supertrace\""));
- assert!(bootstrap.contains("std::option::Option::Some(crate::DEVELOPMENT_LOGGING_PROFILE)"));
+ assert!(bootstrap.contains("FILE_ID_COMPOSITE_KSP_APP_STORE_DESK"));
+ assert!(bootstrap.contains("logging_profile.profile_id() != crate::DEVELOPMENT_LOGGING_PROFILE"));
assert!(bootstrap.contains("LogFilterLevel::Trace"));
return;
}
+
+#[test]
+fn pre_006_store_status_ipc_is_backend_neutral_and_secret_free() {
+ let dto = include_str!("../src/dto_common.rs");
+ let runtime = include_str!("../src/store_runtime.rs");
+ let tauri = include_str!("../src/tauri.rs");
+ assert!(dto.contains("StoreRuntimeStatusDto"));
+ assert!(runtime.contains("runtime_snapshot"));
+ assert!(runtime.contains("health().await"));
+ assert!(tauri.contains("store_runtime_status"));
+ for forbidden in ["connection_uri", "connectionUri", "postgresql://", "password", "RawPayload", "RawAccountState", "tokio_postgres"] {
+ assert!(!dto.contains(forbidden), "Store status DTO source contains forbidden field/material: {forbidden}");
+ }
+}
diff --git a/crates/ksp-app-store-desk/unit_tests/dto_common.rs b/crates/ksp-app-store-desk/unit_tests/dto_common.rs
index 94bb76a..93aa67e 100644
--- a/crates/ksp-app-store-desk/unit_tests/dto_common.rs
+++ b/crates/ksp-app-store-desk/unit_tests/dto_common.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/unit_tests/dto_common.rs
-// version: 1
+// version: 2
#[test]
fn command_error_projection_keeps_only_stable_public_fields() {
@@ -11,3 +11,30 @@ fn command_error_projection_keeps_only_stable_public_fields() {
let debug = format!("{projected:?}");
assert!(!debug.contains("must-not-cross-ipc"));
}
+
+#[test]
+fn store_runtime_status_dto_contains_only_safe_portable_fields() {
+ let value = crate::StoreRuntimeStatusDto {
+ backend_kind: std::option::Option::Some("postgres".to_owned()),
+ diagnostic: std::option::Option::None,
+ health_state: "ready".to_owned(),
+ migration_version_decimal: std::option::Option::Some("2".to_owned()),
+ network: std::option::Option::Some("mainnet-beta".to_owned()),
+ pending_migration_count: 0,
+ pool_available: 1,
+ pool_capacity: 8,
+ pool_size: 1,
+ pool_waiting: 0,
+ profile_id: std::option::Option::Some("mainnet".to_owned()),
+ store_open: true,
+ };
+ let serialized = serde_json::to_string(&value);
+ assert!(serialized.is_ok(), "safe Store runtime DTO must serialize: {serialized:?}");
+ if let std::result::Result::Ok(serialized) = serialized {
+ assert!(serialized.contains("\"backendKind\":\"postgres\""));
+ assert!(serialized.contains("\"migrationVersionDecimal\":\"2\""));
+ for forbidden in ["postgresql://", "connection_uri", "connectionUri", "password", "payload", "dataBytes"] {
+ assert!(!serialized.contains(forbidden), "Store runtime DTO leaked forbidden material: {forbidden}");
+ }
+ }
+}
diff --git a/crates/ksp-app-wallet-desk/tauri.conf.json b/crates/ksp-app-wallet-desk/tauri.conf.json
index 23b1907..7a47b7b 100644
--- a/crates/ksp-app-wallet-desk/tauri.conf.json
+++ b/crates/ksp-app-wallet-desk/tauri.conf.json
@@ -54,18 +54,19 @@
"resources": {
"../../config/composite.ksp-app-backfill-desk.json": "config/composite.ksp-app-backfill-desk.json",
"../../config/composite.ksp-app-solprices-desk.json": "config/composite.ksp-app-solprices-desk.json",
+ "../../config/composite.ksp-app-store-desk.json": "config/composite.ksp-app-store-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",
- "../../config/std.store.json": "config/std.store.json",
- "../../config/std.transport.json": "config/std.transport.json",
- "../../config/std.wallet.json": "config/std.wallet.json",
"../../config/schemas/composite.schema.json": "config/schemas/composite.schema.json",
"../../config/schemas/std.logging.schema.json": "config/schemas/std.logging.schema.json",
"../../config/schemas/std.offchain_transport.schema.json": "config/schemas/std.offchain_transport.schema.json",
"../../config/schemas/std.store.schema.json": "config/schemas/std.store.schema.json",
"../../config/schemas/std.transport.schema.json": "config/schemas/std.transport.schema.json",
- "../../config/schemas/std.wallet.schema.json": "config/schemas/std.wallet.schema.json"
+ "../../config/schemas/std.wallet.schema.json": "config/schemas/std.wallet.schema.json",
+ "../../config/std.logging.json": "config/std.logging.json",
+ "../../config/std.offchain_transport.json": "config/std.offchain_transport.json",
+ "../../config/std.store.json": "config/std.store.json",
+ "../../config/std.transport.json": "config/std.transport.json",
+ "../../config/std.wallet.json": "config/std.wallet.json"
}
}
}
diff --git a/crates/ksp-app-wallet-desk/tests/desktop_contract.rs b/crates/ksp-app-wallet-desk/tests/desktop_contract.rs
index 199ca20..bc84f67 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: 32
+// version: 33
//! Desktop build, shell and Config-status contract audits for Wallet Desk.
@@ -432,7 +432,7 @@ 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(), 14);
+ assert_eq!(resources.len(), 15);
assert_eq!(
resources.get("../../config/composite.ksp-app-backfill-desk.json").and_then(serde_json::Value::as_str),
std::option::Option::Some("config/composite.ksp-app-backfill-desk.json"),
@@ -441,6 +441,10 @@ fn pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_versi
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-store-desk.json").and_then(serde_json::Value::as_str),
+ std::option::Option::Some("config/composite.ksp-app-store-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 08184ce..05d914c 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: 11
+// version: 12
//! Release-wide deterministic compliance canaries for Wallet Desk.
@@ -199,7 +199,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(), 14);
+ assert_eq!(resources.len(), 15);
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/README.md b/crates/ksp-config-lib/README.md
index dc91c6e..7133819 100644
--- a/crates/ksp-config-lib/README.md
+++ b/crates/ksp-config-lib/README.md
@@ -1,5 +1,5 @@
-
+
# ksp-config-lib
@@ -35,7 +35,9 @@ La crate centralise les documents JSON, leurs schemas, les profils et compositio
Le registre par défaut connaît :
```text
+cfg.composite.ksp-app-backfill-desk -> config/composite.ksp-app-backfill-desk.json
cfg.composite.ksp-app-solprices-desk -> config/composite.ksp-app-solprices-desk.json
+cfg.composite.ksp-app-store-desk -> config/composite.ksp-app-store-desk.json
cfg.composite.ksp-app-wallet-desk -> config/composite.ksp-app-wallet-desk.json
cfg.std.logging -> config/std.logging.json
cfg.std.offchain_transport -> config/std.offchain_transport.json
@@ -54,7 +56,7 @@ schema.std.wallet -> config/schemas/std.wallet.schema.json
`ConfigManagement::read_source()` permet d'inspecter le texte brut d'un document Config enregistré même lorsque ce document est invalide. `save_source_candidate()` complète cette frontière : le candidat brut est parsé, validé contre son schema et les invariants sémantiques KSP, puis persisté atomiquement uniquement après validation complète. Le `file_id` doit appartenir au registre et désigner un document Config ; aucun path arbitraire n'est accepté.
-`config/examples/composite.example.json` conserve l’exemple générique. Les composites runtime committed restent possédés par Config : `config/composite.ksp-app-solprices-desk.json` sélectionne Logging + Off-chain Transport, tandis que `config/composite.ksp-app-wallet-desk.json` sélectionne Logging + Off-chain Transport + On-chain Transport + Wallet. Tous référencent leurs documents par `file_id`, sans dépendre de filenames physiques.
+`config/examples/composite.example.json` conserve l’exemple générique. Les composites runtime committed restent possédés par Config : Backfill Desk compose Logging + Transport + Store, SOL Prices Desk compose Logging + Off-chain Transport, Store Desk compose Logging + Store, et Wallet Desk compose Logging + Off-chain Transport + On-chain Transport + Wallet. Tous référencent leurs documents par `file_id`, sans dépendre de filenames physiques.
Le fichier local d'environnement est :
@@ -100,6 +102,8 @@ Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configur
- [`../../config/std.offchain_transport.json`](../../config/std.offchain_transport.json) — document standard Off-chain Transport V1, actuellement limité au domaine `market_price` SOL/USD ;
- [`../../config/std.store.json`](../../config/std.store.json) — targets Store PostgreSQL Devnet/Mainnet/Testnet et settings runtime bornés ;
- [`../../config/std.wallet.json`](../../config/std.wallet.json) — racine Wallet globale et sous-répertoire optionnel par profil ;
+- [`../../config/composite.ksp-app-backfill-desk.json`](../../config/composite.ksp-app-backfill-desk.json) — composition Logging/Transport/Store de Backfill Desk ;
- [`../../config/composite.ksp-app-solprices-desk.json`](../../config/composite.ksp-app-solprices-desk.json) — composition Logging/Off-chain Transport de SOL Prices Desk ;
+- [`../../config/composite.ksp-app-store-desk.json`](../../config/composite.ksp-app-store-desk.json) — composition Logging/Store de Store Desk ;
- [`../../config/composite.ksp-app-wallet-desk.json`](../../config/composite.ksp-app-wallet-desk.json) — composition Logging/Off-chain Transport/On-chain Transport/Wallet de Wallet Desk ;
- [`../../.env.example`](../../.env.example) — inventaire versionné des variables d'environnement runtime.
diff --git a/crates/ksp-config-lib/src/lib.rs b/crates/ksp-config-lib/src/lib.rs
index afe95b1..3bfebaf 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: 22
+// version: 23
#![warn(missing_docs)]
#![deny(unreachable_pub)]
@@ -155,6 +155,8 @@ pub use self::registry::ConfigFileRegistry;
pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_BACKFILL_DESK_FILENAME;
/// 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 Store Desk composite configuration document.
+pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_STORE_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.
@@ -183,6 +185,8 @@ pub use self::registry::DEFAULT_STD_WALLET_SCHEMA_FILENAME;
pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_BACKFILL_DESK;
/// 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 Store Desk composite configuration document.
+pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_STORE_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 206e950..b0494f8 100644
--- a/crates/ksp-config-lib/src/registry.rs
+++ b/crates/ksp-config-lib/src/registry.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/registry.rs
-// version: 14
+// version: 15
/// Bootstrap argument used to replace a known Config filename mapping.
pub const ARG_FILE_MAP: &str = "--filemap";
@@ -7,6 +7,8 @@ pub const ARG_FILE_MAP: &str = "--filemap";
pub const DEFAULT_COMPOSITE_KSP_APP_BACKFILL_DESK_FILENAME: &str = "composite.ksp-app-backfill-desk.json";
/// 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 Store Desk composite configuration document.
+pub const DEFAULT_COMPOSITE_KSP_APP_STORE_DESK_FILENAME: &str = "composite.ksp-app-store-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.
@@ -35,6 +37,8 @@ pub const DEFAULT_STD_WALLET_SCHEMA_FILENAME: &str = "std.wallet.schema.json";
pub const FILE_ID_COMPOSITE_KSP_APP_BACKFILL_DESK: &str = "cfg.composite.ksp-app-backfill-desk";
/// 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 Store Desk composite configuration document.
+pub const FILE_ID_COMPOSITE_KSP_APP_STORE_DESK: &str = "cfg.composite.ksp-app-store-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.
@@ -184,6 +188,16 @@ impl ConfigFileRegistry {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
+ let store_desk_composite = ConfigFileDescriptor::new(
+ FILE_ID_COMPOSITE_KSP_APP_STORE_DESK,
+ ConfigFileKind::Config,
+ DEFAULT_COMPOSITE_KSP_APP_STORE_DESK_FILENAME,
+ std::option::Option::Some(FILE_ID_SCHEMA_COMPOSITE),
+ );
+ let store_desk_composite = match store_desk_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,
@@ -287,6 +301,7 @@ impl ConfigFileRegistry {
return crate::build_registry([
backfill_composite,
solprices_composite,
+ store_desk_composite,
wallet_composite,
composite_schema,
logging,
diff --git a/crates/ksp-config-lib/tests/public_api.rs b/crates/ksp-config-lib/tests/public_api.rs
index 8f50ddd..b926a00 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: 27
+// version: 28
//! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity,
//! Logging/Transport/Store adapters and management contracts.
@@ -80,22 +80,23 @@ 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(), 14);
+ assert_eq!(descriptors.len(), 15);
assert_eq!(descriptors[0].file_id().as_str(), ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_BACKFILL_DESK);
assert_eq!(descriptors[1].file_id().as_str(), ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK);
- assert_eq!(descriptors[2].file_id().as_str(), ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
- assert_eq!(descriptors[3].file_id().as_str(), ksp_config_lib::FILE_ID_STD_LOGGING);
- assert_eq!(descriptors[4].file_id().as_str(), ksp_config_lib::FILE_ID_STD_OFFCHAIN_TRANSPORT);
- assert_eq!(descriptors[5].file_id().as_str(), ksp_config_lib::FILE_ID_STD_STORE);
- assert_eq!(descriptors[6].file_id().as_str(), ksp_config_lib::FILE_ID_STD_TRANSPORT);
- assert_eq!(descriptors[7].file_id().as_str(), ksp_config_lib::FILE_ID_STD_WALLET);
- assert_eq!(descriptors[8].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_COMPOSITE);
- assert_eq!(descriptors[9].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_LOGGING);
- assert_eq!(descriptors[10].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT);
- assert_eq!(descriptors[11].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_STORE);
- assert_eq!(descriptors[12].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT);
- assert_eq!(descriptors[13].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_WALLET);
- let schema_file_id = descriptors[7].schema_file_id();
+ assert_eq!(descriptors[2].file_id().as_str(), ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_STORE_DESK);
+ assert_eq!(descriptors[3].file_id().as_str(), ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
+ assert_eq!(descriptors[4].file_id().as_str(), ksp_config_lib::FILE_ID_STD_LOGGING);
+ assert_eq!(descriptors[5].file_id().as_str(), ksp_config_lib::FILE_ID_STD_OFFCHAIN_TRANSPORT);
+ assert_eq!(descriptors[6].file_id().as_str(), ksp_config_lib::FILE_ID_STD_STORE);
+ assert_eq!(descriptors[7].file_id().as_str(), ksp_config_lib::FILE_ID_STD_TRANSPORT);
+ assert_eq!(descriptors[8].file_id().as_str(), ksp_config_lib::FILE_ID_STD_WALLET);
+ assert_eq!(descriptors[9].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_COMPOSITE);
+ assert_eq!(descriptors[10].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_LOGGING);
+ assert_eq!(descriptors[11].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT);
+ assert_eq!(descriptors[12].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_STORE);
+ assert_eq!(descriptors[13].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT);
+ assert_eq!(descriptors[14].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_WALLET);
+ let schema_file_id = descriptors[8].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);
@@ -168,6 +169,8 @@ fn composite_schema_and_provenance_contracts_are_available_from_crate_root() {
assert_eq!(ksp_config_lib::DEFAULT_COMPOSITE_KSP_APP_BACKFILL_DESK_FILENAME, "composite.ksp-app-backfill-desk.json");
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_eq!(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_STORE_DESK, "cfg.composite.ksp-app-store-desk");
+ assert_eq!(ksp_config_lib::DEFAULT_COMPOSITE_KSP_APP_STORE_DESK_FILENAME, "composite.ksp-app-store-desk.json");
assert_ne!(ksp_config_lib::ConfigProfileSelectionSource::Composite, ksp_config_lib::ConfigProfileSelectionSource::Explicit);
}
diff --git a/crates/ksp-config-lib/unit_tests/fixtures/composite.ksp-app-store-desk.json b/crates/ksp-config-lib/unit_tests/fixtures/composite.ksp-app-store-desk.json
new file mode 100644
index 0000000..c6adb6c
--- /dev/null
+++ b/crates/ksp-config-lib/unit_tests/fixtures/composite.ksp-app-store-desk.json
@@ -0,0 +1,51 @@
+{
+ "format_version": 1,
+ "default_profile": "mainnet",
+ "profiles": [
+ {
+ "profile_id": "devnet",
+ "documents": [
+ {
+ "component_id": "logging",
+ "file_id": "cfg.std.logging",
+ "profile_id": "local_dev"
+ },
+ {
+ "component_id": "store",
+ "file_id": "cfg.std.store",
+ "profile_id": "devnet"
+ }
+ ]
+ },
+ {
+ "profile_id": "mainnet",
+ "documents": [
+ {
+ "component_id": "logging",
+ "file_id": "cfg.std.logging",
+ "profile_id": "local_dev"
+ },
+ {
+ "component_id": "store",
+ "file_id": "cfg.std.store",
+ "profile_id": "mainnet"
+ }
+ ]
+ },
+ {
+ "profile_id": "testnet",
+ "documents": [
+ {
+ "component_id": "logging",
+ "file_id": "cfg.std.logging",
+ "profile_id": "local_dev"
+ },
+ {
+ "component_id": "store",
+ "file_id": "cfg.std.store",
+ "profile_id": "testnet"
+ }
+ ]
+ }
+ ]
+}
diff --git a/crates/ksp-config-lib/unit_tests/registry.rs b/crates/ksp-config-lib/unit_tests/registry.rs
index bcbe38b..64453de 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: 12
+// version: 13
#[test]
fn descriptors_expose_complete_registry_in_deterministic_file_id_order() {
@@ -7,27 +7,30 @@ 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(), 14);
+ assert_eq!(descriptors.len(), 15);
assert_eq!(descriptors[0].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_BACKFILL_DESK);
assert_eq!(descriptors[0].filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_BACKFILL_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_COMPOSITE_KSP_APP_SOLPRICES_DESK);
- assert_eq!(descriptors[2].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
- assert_eq!(descriptors[3].file_id().as_str(), crate::FILE_ID_STD_LOGGING);
- assert_eq!(descriptors[4].file_id().as_str(), crate::FILE_ID_STD_OFFCHAIN_TRANSPORT);
- assert_eq!(descriptors[5].file_id().as_str(), crate::FILE_ID_STD_STORE);
- assert_eq!(descriptors[6].file_id().as_str(), crate::FILE_ID_STD_TRANSPORT);
- assert_eq!(descriptors[7].file_id().as_str(), crate::FILE_ID_STD_WALLET);
- assert_eq!(descriptors[7].filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_FILENAME));
- assert_eq!(descriptors[7].schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_STD_WALLET));
- assert_eq!(descriptors[8].file_id().as_str(), crate::FILE_ID_SCHEMA_COMPOSITE);
- assert_eq!(descriptors[9].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING);
- assert_eq!(descriptors[10].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT);
- assert_eq!(descriptors[11].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_STORE);
- assert_eq!(descriptors[12].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT);
- assert_eq!(descriptors[13].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_WALLET);
- assert!(descriptors[0..8].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Config));
- assert!(descriptors[8..14].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Schema));
+ assert_eq!(descriptors[2].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_STORE_DESK);
+ assert_eq!(descriptors[2].filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_STORE_DESK_FILENAME));
+ assert_eq!(descriptors[2].schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_COMPOSITE));
+ assert_eq!(descriptors[3].file_id().as_str(), crate::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK);
+ assert_eq!(descriptors[4].file_id().as_str(), crate::FILE_ID_STD_LOGGING);
+ assert_eq!(descriptors[5].file_id().as_str(), crate::FILE_ID_STD_OFFCHAIN_TRANSPORT);
+ assert_eq!(descriptors[6].file_id().as_str(), crate::FILE_ID_STD_STORE);
+ assert_eq!(descriptors[7].file_id().as_str(), crate::FILE_ID_STD_TRANSPORT);
+ assert_eq!(descriptors[8].file_id().as_str(), crate::FILE_ID_STD_WALLET);
+ assert_eq!(descriptors[8].filename(), std::path::Path::new(crate::DEFAULT_STD_WALLET_FILENAME));
+ assert_eq!(descriptors[8].schema_file_id().map(crate::ConfigFileId::as_str), std::option::Option::Some(crate::FILE_ID_SCHEMA_STD_WALLET));
+ assert_eq!(descriptors[9].file_id().as_str(), crate::FILE_ID_SCHEMA_COMPOSITE);
+ assert_eq!(descriptors[10].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_LOGGING);
+ assert_eq!(descriptors[11].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_OFFCHAIN_TRANSPORT);
+ assert_eq!(descriptors[12].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_STORE);
+ assert_eq!(descriptors[13].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_TRANSPORT);
+ assert_eq!(descriptors[14].file_id().as_str(), crate::FILE_ID_SCHEMA_STD_WALLET);
+ assert!(descriptors[0..9].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Config));
+ assert!(descriptors[9..15].iter().all(|descriptor| return descriptor.kind() == crate::ConfigFileKind::Schema));
}
}
@@ -362,35 +365,45 @@ fn absolute_fixture_path() -> std::path::PathBuf {
}
#[test]
-fn defaults_register_generic_composite_schema_and_three_runtime_composites() {
+fn defaults_register_generic_composite_schema_and_four_runtime_composites() {
let registry = crate::ConfigFileRegistry::defaults();
let schema_id = crate::ConfigFileId::new(crate::FILE_ID_SCHEMA_COMPOSITE);
let backfill_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_BACKFILL_DESK);
let solprices_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK);
+ let store_desk_id = crate::ConfigFileId::new(crate::FILE_ID_COMPOSITE_KSP_APP_STORE_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!(backfill_id.is_ok(), "Backfill Desk composite runtime file_id should be valid: {backfill_id:?}");
assert!(solprices_id.is_ok(), "SOL Prices Desk composite runtime file_id should be valid: {solprices_id:?}");
+ assert!(store_desk_id.is_ok(), "Store Desk composite runtime file_id should be valid: {store_desk_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(backfill_id),
std::result::Result::Ok(solprices_id),
+ std::result::Result::Ok(store_desk_id),
std::result::Result::Ok(wallet_id),
- ) = (registry, schema_id, backfill_id, solprices_id, wallet_id)
+ ) = (registry, schema_id, backfill_id, solprices_id, store_desk_id, wallet_id)
{
let schema = registry.descriptor(&schema_id);
let backfill = registry.descriptor(&backfill_id);
let solprices = registry.descriptor(&solprices_id);
+ let store_desk = registry.descriptor(&store_desk_id);
let wallet = registry.descriptor(&wallet_id);
assert!(schema.is_ok(), "generic composite schema should be registered: {schema:?}");
assert!(backfill.is_ok(), "Backfill Desk runtime composite should be registered: {backfill:?}");
assert!(solprices.is_ok(), "SOL Prices Desk runtime composite should be registered: {solprices:?}");
+ assert!(store_desk.is_ok(), "Store Desk runtime composite should be registered: {store_desk:?}");
assert!(wallet.is_ok(), "Wallet Desk runtime composite should be registered: {wallet:?}");
- if let (std::result::Result::Ok(schema), std::result::Result::Ok(backfill), std::result::Result::Ok(solprices), std::result::Result::Ok(wallet)) =
- (schema, backfill, solprices, wallet)
+ if let (
+ std::result::Result::Ok(schema),
+ std::result::Result::Ok(backfill),
+ std::result::Result::Ok(solprices),
+ std::result::Result::Ok(store_desk),
+ std::result::Result::Ok(wallet),
+ ) = (schema, backfill, solprices, store_desk, wallet)
{
assert_eq!(schema.kind(), crate::ConfigFileKind::Schema);
assert_eq!(schema.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_SCHEMA_FILENAME));
@@ -400,6 +413,9 @@ fn defaults_register_generic_composite_schema_and_three_runtime_composites() {
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!(store_desk.kind(), crate::ConfigFileKind::Config);
+ assert_eq!(store_desk.filename(), std::path::Path::new(crate::DEFAULT_COMPOSITE_KSP_APP_STORE_DESK_FILENAME));
+ assert_eq!(store_desk.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.3.8/pre.006.md b/deltas/0.3.8/pre.006.md
new file mode 100644
index 0000000..5020c0d
--- /dev/null
+++ b/deltas/0.3.8/pre.006.md
@@ -0,0 +1,251 @@
+
+
+
+# Delta `0.3.8-pre.006` — composite Config, lifecycle Store et Overview Store Desk
+
+## Base requise
+
+Base directe attendue :
+
+```text
+0.3.8-pre.005-fix.003
+workspace.package.version = 0.3.8-pre.5.fix.3
+```
+
+Le gate opérateur de cette base est entièrement vert. Le live proof `RawAccountState` passe sur PostgreSQL 17 et le smoke `cargo tauri dev` confirme le profil `supertrace` ainsi que le tracing frontend Store Desk.
+
+La livraison est :
+
+```text
+0.3.8-pre.006
+workspace.package.version = 0.3.8-pre.6
+commit = v0.3.8-pre.006
+tag = aucun
+```
+
+## Objectif
+
+Brancher pour la première fois `ksp-app-store-desk` sur le runtime Store réel, sans ouvrir encore les tables RAW en `serverSide` :
+
+```text
+Config composite
+ -> Logging supertrace
+ -> cfg.std.store
+ -> ksp-store-lib::Store
+ -> runtime_snapshot + health
+ -> Overview / Diagnostics
+```
+
+L'application reste backend-neutral : elle dépend de `ksp-store-lib`, jamais de `ksp-store-api`, `ksp-store-postgres-lib` ni d'un driver PostgreSQL.
+
+## Composite Config Store Desk
+
+Le nouveau document :
+
+```text
+config/composite.ksp-app-store-desk.json
+```
+
+contient trois profils :
+
+```text
+devnet
+mainnet
+testnet
+```
+
+Chaque profil sélectionne :
+
+```text
+logging -> cfg.std.logging / supertrace
+store -> cfg.std.store / profil homonyme
+```
+
+`ksp-config-lib` enregistre et réexporte :
+
+```text
+cfg.composite.ksp-app-store-desk
+composite.ksp-app-store-desk.json
+```
+
+Le registre partagé passe ainsi de 14 à 15 descriptors.
+
+## Packaging partagé
+
+`prepare_packaged_runtime()` traverse l'intégralité de `ConfigFileRegistry::defaults()`. L'ajout du quinzième descriptor impose donc que les cinq Desk apps utilisant ce mécanisme embarquent le nouveau composite :
+
+```text
+ksp-app-config-desk
+ksp-app-wallet-desk
+ksp-app-solprices-desk
+ksp-app-backfill-desk
+ksp-app-store-desk
+```
+
+Leurs bundles contiennent exactement les 15 resources Config/schema du registre. Cette propagation ne modifie aucune responsabilité runtime des quatre applications existantes.
+
+## Lifecycle Store Desk
+
+Store Desk :
+
+1. construit `ConfigManagement` ;
+2. charge le composite Store Desk par `file_id` ;
+3. initialise Logging depuis le composant `logging` et exige `supertrace` en développement ;
+4. résout le composant `store` via `resolve_store_config_profile()` ;
+5. ouvre `ksp_store_lib::Store::open()` ;
+6. conserve le Store derrière un slot async interne ;
+7. expose un snapshot Overview sûr ;
+8. intercepte la fermeture de la fenêtre principale ;
+9. exécute une fermeture Store one-shot et bornée avant `app_handle.exit(0)`.
+
+Si Config ou Store ne peut pas être ouvert, le shell reste disponible avec un diagnostic sûr sans secret.
+
+## Overview / Diagnostics
+
+Le DTO `StoreRuntimeStatusDto` expose uniquement :
+
+```text
+profile_id
+backend_kind
+network
+store_open
+health_state
+migration_version_decimal
+pending_migration_count
+pool_available
+pool_size
+pool_capacity
+pool_waiting
+diagnostic { domain, code, message }
+```
+
+Sont exclus :
+
+```text
+connection URI
+credential / password
+SQL
+PostgreSQL pool/client/handle
+RawPayload
+RawAccountState bytes
+transaction payload bytes
+```
+
+La commande Tauri `store_runtime_status` ne prend aucun paramètre et retourne cette projection backend-neutral.
+
+## Frontend
+
+Overview et Diagnostics consomment le nouveau statut Store et tracent :
+
+- refresh Overview ;
+- refresh Diagnostics ;
+- IPC demandé / terminé / échoué ;
+- navigation et activation de vues ;
+- boutons/tabs/contrôles génériques.
+
+Les valeurs sensibles et les valeurs de filtres ne sont pas logguées.
+
+Les tables `RAW Transactions` et `RAW Accounts` restent volontairement :
+
+```text
+serverSide: false
+pageLength: 25
+lengthMenu: [25, 50, 100]
+```
+
+Aucun invoke d'inspection, aucun détail RAW et aucun second pager Store ne sont introduits avant `pre.007`/`pre.008`.
+
+## Fichiers ajoutés
+
+```text
+config/composite.ksp-app-store-desk.json
+crates/ksp-app-store-desk/src/store_runtime.rs
+crates/ksp-config-lib/unit_tests/fixtures/composite.ksp-app-store-desk.json
+deltas/0.3.8/pre.006.md
+```
+
+## Fichiers modifiés
+
+```text
+Cargo.toml
+crates/ksp-app-backfill-desk/tauri.conf.json
+crates/ksp-app-backfill-desk/tests/desktop_contract.rs
+crates/ksp-app-config-desk/tauri.conf.json
+crates/ksp-app-config-desk/tests/desktop_contract.rs
+crates/ksp-app-solprices-desk/tauri.conf.json
+crates/ksp-app-solprices-desk/tests/desktop_contract.rs
+crates/ksp-app-store-desk/Cargo.toml
+crates/ksp-app-store-desk/frontend/main.html
+crates/ksp-app-store-desk/frontend/ts/main.ts
+crates/ksp-app-store-desk/src/app_state.rs
+crates/ksp-app-store-desk/src/bootstrap.rs
+crates/ksp-app-store-desk/src/constants.rs
+crates/ksp-app-store-desk/src/dto_common.rs
+crates/ksp-app-store-desk/src/errors.rs
+crates/ksp-app-store-desk/src/lib.rs
+crates/ksp-app-store-desk/src/tauri.rs
+crates/ksp-app-store-desk/tauri.conf.json
+crates/ksp-app-store-desk/tests/dependency_boundary.rs
+crates/ksp-app-store-desk/tests/desktop_contract.rs
+crates/ksp-app-store-desk/tests/desktop_security.rs
+crates/ksp-app-store-desk/unit_tests/dto_common.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/README.md
+crates/ksp-config-lib/src/lib.rs
+crates/ksp-config-lib/src/registry.rs
+crates/ksp-config-lib/tests/public_api.rs
+crates/ksp-config-lib/unit_tests/registry.rs
+docs/plans/029-V0_3_8_STORE_DESK_PLAN.md
+docs/validation/025-V0_3_8_STORE_DESK.md
+```
+
+## Fichiers supprimés
+
+```text
+aucun
+```
+
+## Validations d'assemblage
+
+Contrôles disponibles dans le sandbox d'assemblage :
+
+```text
+General Rust rule audit: clean
+Rust export completeness audit: 0 candidate(s)
+KSP workspace Rust rule audit: clean
+Markdown table audit: clean
+pre.006 structural contract audit: clean
+workspace.package.version: 0.3.8-pre.6
+Config registry expected descriptors: 15
+shared Tauri Config resources per Desk app: 15
+Store Desk physical Store dependencies: 0
+Store Desk RAW DataTables serverSide: false
+```
+
+Le sandbox ne dispose pas de `cargo`/`rustfmt`; aucun gate Cargo de `pre.006` n'est revendiqué localement.
+
+## Gate requis
+
+```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
+cargo check --workspace
+cargo clippy --workspace --all-targets
+cargo test -p ksp-config-lib
+cargo test -p ksp-store-lib
+cargo check -p ksp-store-lib --no-default-features
+cargo test -p ksp-app-store-desk
+cargo test -p ksp-app-config-desk
+cargo test -p ksp-app-wallet-desk
+cargo test -p ksp-app-solprices-desk
+cargo test -p ksp-app-backfill-desk
+```
+
+Smoke runtime recommandé après gate :
+
+```bash
+(cd crates/ksp-app-store-desk && cargo tauri dev)
+```
diff --git a/docs/plans/029-V0_3_8_STORE_DESK_PLAN.md b/docs/plans/029-V0_3_8_STORE_DESK_PLAN.md
index a73e4fd..9883922 100644
--- a/docs/plans/029-V0_3_8_STORE_DESK_PLAN.md
+++ b/docs/plans/029-V0_3_8_STORE_DESK_PLAN.md
@@ -1,5 +1,5 @@
-
+
# Plan v0.3.8 — Store Desk V1 RAW
@@ -576,7 +576,7 @@ Même vertical slice pour Account : filtre exact `pubkey`, range slot inclusif,
### pre.006 — composite Config + lifecycle Store + Overview
-Créer `config/composite.ksp-app-store-desk.json`, packaging resources, bootstrap Logging+Store, runtime/health safe et fermeture bornée.
+Créer `config/composite.ksp-app-store-desk.json`, enregistrer son `file_id` dans le registre partagé `ksp-config-lib`, propager la quinzième ressource Config aux cinq bundles Desk utilisant `prepare_packaged_runtime()`, puis brancher Store Desk sur `ksp-store-lib::Store` uniquement. Le composite sélectionne Logging `supertrace` et le profil Store homonyme `devnet`/`mainnet`/`testnet`. L'Overview et Diagnostics exposent uniquement backend logique, network, health, migration et compteurs de pool via un DTO IPC backend-neutral ; aucune URI, credential, SQL, handle physique ou byte RAW. La fermeture de la fenêtre principale déclenche une fermeture Store one-shot et bornée avant sortie Tauri. Les DataTables Transactions/Accounts restent locales et `serverSide:false` jusqu'à `pre.007`/`pre.008`.
### pre.007 — RawTransaction DataTables serverSide + détail
diff --git a/docs/validation/025-V0_3_8_STORE_DESK.md b/docs/validation/025-V0_3_8_STORE_DESK.md
index c3f5a48..fbb811f 100644
--- a/docs/validation/025-V0_3_8_STORE_DESK.md
+++ b/docs/validation/025-V0_3_8_STORE_DESK.md
@@ -1,5 +1,5 @@
-
+
# Validation v0.3.8 — Store Desk V1 RAW
@@ -91,9 +91,9 @@ Le journal opérateur fourni pour v0.3.7 rapporte un gate complet. Il reste une
- [X] `pre.002` scaffold KSP strict + DataTables skeleton ;
- [X] `pre.003` contrats Store inspection offset/count/summaries ;
-- [ ] `pre.004` PostgreSQL + façade inspection RawTransaction ;
-- [ ] `pre.005` PostgreSQL + façade inspection RawAccountState ;
-- [ ] `pre.006` composite Logging+Store + Overview/health ;
+- [X] `pre.004` PostgreSQL + façade inspection RawTransaction ;
+- [X] `pre.005` PostgreSQL + façade inspection RawAccountState ;
+- [X] `pre.006` composite Logging+Store + Overview/health ;
- [ ] `pre.007` RawTransaction DataTables serverSide + détail ;
- [ ] `pre.008` RawAccountState DataTables serverSide + détail ;
- [ ] `pre.009` observation listing/inspection Store ;
@@ -464,3 +464,51 @@ Les garanties pertinentes restent strictes et redondantes :
Aucun SQL, runtime PostgreSQL, contrat public, migration, capability, modèle ou comportement Store n'est modifié.
+## 25. Clôture opérateur `pre.005-fix.003`
+
+Le rejeu opérateur sur `0.3.8-pre.5.fix.3` ferme `pre.005` sans réserve :
+
+```text
+General Rust rule audit: clean
+Rust export completeness audit: 0 candidate(s)
+KSP workspace Rust rule audit: clean
+Markdown table audit: clean (314 table(s), 701 file(s))
+cargo check --workspace: PASS
+cargo clippy --workspace --all-targets: PASS
+cargo test -p ksp-store-postgres-lib: PASS (69 unitaires + integrations/doc-tests)
+cargo test -p ksp-store-lib: PASS
+cargo check -p ksp-store-lib --no-default-features: PASS
+cargo test -p ksp-store-api: PASS
+```
+
+Le live proof `postgres_raw_account_live` est également vert sur PostgreSQL 17 :
+
+```text
+KSP Store RawAccountState live proof: server major 17
+test pre_009_real_postgres_raw_account_vertical_slice_is_atomic_concurrent_and_recoverable ... ok
+```
+
+Le smoke `cargo tauri dev` de Store Desk démarre aussi correctement ; le journal confirme le profil Logging `supertrace`, le cycle splash/main et les événements frontend navigation/view/IPC. `pre.006` peut donc ouvrir.
+
+## 26. `pre.006` — composite Config + lifecycle Store + Overview
+
+- [X] `workspace.package.version = 0.3.8-pre.6` ;
+- [X] `config/composite.ksp-app-store-desk.json` ajouté avec profils `devnet`, `mainnet`, `testnet` ;
+- [X] chaque profil composite sélectionne Logging `supertrace` et le profil `cfg.std.store` homonyme ;
+- [X] `cfg.composite.ksp-app-store-desk` et son filename sont enregistrés et réexportés par `ksp-config-lib` ;
+- [X] le registre Config passe de 14 à 15 descriptors et conserve son ordre/inventaire exact ;
+- [X] une fixture Store Desk adaptée au corpus Config unitaire réduit est ajoutée ;
+- [X] les cinq Desk apps utilisant `prepare_packaged_runtime()` embarquent les 15 resources Config/schema ;
+- [X] leurs canaris de packaging sont réconciliés de 14 à 15 sans changement de runtime métier ;
+- [X] Store Desk dépend désormais de `ksp-store-lib` mais jamais directement de `ksp-store-api` ou `ksp-store-postgres-lib` ;
+- [X] le bootstrap charge le composite Store Desk par son `file_id`, valide les composants Logging/Store et garde `Trace` comme fallback Logging sûr ;
+- [X] l'ouverture Store passe exclusivement par `ksp_store_lib::Store::open` après résolution `cfg.std.store` ;
+- [X] l'Overview/Diagnostics utilise `runtime_snapshot()` et `health()` via `StoreRuntimeStatusDto` ;
+- [X] le DTO expose seulement profile/backend/network, health, migration et compteurs de pool ; aucune URI, credential, SQL, handle physique ou byte RAW ;
+- [X] la commande Tauri `store_runtime_status` est backend-neutral et ne prend aucun paramètre sensible ;
+- [X] la fermeture main-window est interceptée une fois, appelle `Store::close()` via la façade et sort ensuite de Tauri ;
+- [X] les tables RAW restent `serverSide:false`, sans invoke d'inspection ni second pager ;
+- [X] les clics Refresh, navigation/views, tabs/controls et IPC restent tracés sans valeurs de filtres ni secrets ;
+- [X] aucune migration, query métier d'inspection, détail RAW ou changement de capability Tauri n'est ajouté en `pre.006`.
+
+Le gate Cargo opérateur de `pre.006` reste à exécuter après application du delta.