v0.3.8-pre.006

This commit is contained in:
2026-09-03 16:26:18 +02:00
parent 62d6cf9dac
commit 3a38914be7
36 changed files with 1300 additions and 167 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 455 # version: 456
[workspace] [workspace]
resolver = "3" 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"] 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] [workspace.package]
version = "0.3.8-pre.5.fix.3" version = "0.3.8-pre.6"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -0,0 +1,51 @@
{
"format_version": 1,
"default_profile": "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"
}
]
}
]
}

View File

@@ -54,18 +54,19 @@
"resources": { "resources": {
"../../config/composite.ksp-app-backfill-desk.json": "config/composite.ksp-app-backfill-desk.json", "../../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-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/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/composite.schema.json": "config/schemas/composite.schema.json",
"../../config/schemas/std.logging.schema.json": "config/schemas/std.logging.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.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.store.schema.json": "config/schemas/std.store.schema.json",
"../../config/schemas/std.transport.schema.json": "config/schemas/std.transport.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"
} }
} }
} }

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-backfill-desk/tests/desktop_contract.rs // file: crates/ksp-app-backfill-desk/tests/desktop_contract.rs
// version: 16 // version: 17
//! Structural desktop contract checks for the Backfill Desk scaffold. //! 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::Some(value) => value,
std::option::Option::None => return, std::option::Option::None => return,
}; };
assert_eq!(resources.len(), 14); assert_eq!(resources.len(), 15);
for required in [ for required in [
"../../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-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",

View File

@@ -54,18 +54,19 @@
"resources": { "resources": {
"../../config/composite.ksp-app-backfill-desk.json": "config/composite.ksp-app-backfill-desk.json", "../../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-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/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/composite.schema.json": "config/schemas/composite.schema.json",
"../../config/schemas/std.logging.schema.json": "config/schemas/std.logging.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.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.store.schema.json": "config/schemas/std.store.schema.json",
"../../config/schemas/std.transport.schema.json": "config/schemas/std.transport.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"
} }
} }
} }

View File

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

View File

@@ -54,18 +54,19 @@
"resources": { "resources": {
"../../config/composite.ksp-app-backfill-desk.json": "config/composite.ksp-app-backfill-desk.json", "../../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-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/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/composite.schema.json": "config/schemas/composite.schema.json",
"../../config/schemas/std.logging.schema.json": "config/schemas/std.logging.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.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.store.schema.json": "config/schemas/std.store.schema.json",
"../../config/schemas/std.transport.schema.json": "config/schemas/std.transport.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"
} }
} }
} }

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-solprices-desk/tests/desktop_contract.rs // file: crates/ksp-app-solprices-desk/tests/desktop_contract.rs
// version: 11 // version: 12
//! Desktop scaffold, shared-template and Config packaging contract audits for SOL Prices Desk `0.2.12`. //! Desktop scaffold, shared-template and Config packaging contract audits for SOL Prices Desk `0.2.12`.
@@ -85,13 +85,13 @@ fn pre_002_package_is_mixed_lib_bin_and_frontend_is_scaffold_only() {
} }
#[test] #[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 root = app_root();
let tauri = read_json(root.join("tauri.conf.json").as_path()); let tauri = read_json(root.join("tauri.conf.json").as_path());
let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object); let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object);
assert!(resources.is_some()); assert!(resources.is_some());
if let std::option::Option::Some(resources) = resources { if let std::option::Option::Some(resources) = resources {
assert_eq!(resources.len(), 14); assert_eq!(resources.len(), 15);
assert_eq!( assert_eq!(
resources.get("../../config/composite.ksp-app-backfill-desk.json").and_then(serde_json::Value::as_str), 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"), 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), 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"), 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/std.offchain_transport.json"));
assert!(resources.contains_key("../../config/schemas/std.offchain_transport.schema.json")); assert!(resources.contains_key("../../config/schemas/std.offchain_transport.schema.json"));
assert!(resources.contains_key("../../config/std.store.json")); assert!(resources.contains_key("../../config/std.store.json"));

View File

@@ -1,5 +1,5 @@
# file: crates/ksp-app-store-desk/Cargo.toml # file: crates/ksp-app-store-desk/Cargo.toml
# version: 1 # version: 2
[package] [package]
name = "ksp-app-store-desk" name = "ksp-app-store-desk"
@@ -27,10 +27,11 @@ fs2.workspace = true
ksp-config-lib = { path = "../ksp-config-lib" } ksp-config-lib = { path = "../ksp-config-lib" }
ksp-core-lib = { path = "../ksp-core-lib" } ksp-core-lib = { path = "../ksp-core-lib" }
ksp-logging-lib = { path = "../ksp-logging-lib" } ksp-logging-lib = { path = "../ksp-logging-lib" }
ksp-store-lib = { path = "../ksp-store-lib" }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
tauri.workspace = true tauri.workspace = true
tauri-plugin-tracing.workspace = true tauri-plugin-tracing.workspace = true
tokio = { workspace = true, features = ["time"] } tokio = { workspace = true, features = ["sync", "time"] }
ts-rs.workspace = true ts-rs.workspace = true
[dev-dependencies] [dev-dependencies]

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-store-desk/frontend/main.html --> <!-- file: crates/ksp-app-store-desk/frontend/main.html -->
<!-- version: 1 --> <!-- version: 2 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
@@ -54,22 +54,29 @@
<div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-4"> <div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-4">
<div> <div>
<h1 class="h3 mb-1">Overview</h1> <h1 class="h3 mb-1">Overview</h1>
<p class="text-body-secondary mb-0">Scaffold read-only. Le lifecycle Store et les diagnostics backend-neutral seront branchés dans une tranche ultérieure.</p> <p class="text-body-secondary mb-0">État backend-neutral du Store sélectionné par le composite Config. Aucune donnée RAW complète n'est chargée ici.</p>
</div>
<div class="d-flex align-items-center gap-2">
<span id="appVersionBadge" class="badge text-bg-secondary"></span>
<button id="refreshOverview" class="btn btn-outline-primary" type="button">
<i class="fa-solid fa-arrows-rotate me-1" aria-hidden="true"></i>Refresh
</button>
</div> </div>
<span id="appVersionBadge" class="badge text-bg-secondary"></span>
</div> </div>
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6 col-xl-3"><div class="card h-100 shadow-sm"><div class="card-body"><span class="text-body-secondary small">Store target</span><div class="fw-semibold mt-2">Non ouvert</div></div></div></div> <div class="col-md-6 col-xl-3"><div class="card h-100 shadow-sm"><div class="card-body"><span class="text-body-secondary small">Store profile</span><div id="overviewStoreProfile" class="fw-semibold mt-2"></div></div></div></div>
<div class="col-md-6 col-xl-3"><div class="card h-100 shadow-sm"><div class="card-body"><span class="text-body-secondary small">Health</span><div class="fw-semibold mt-2">À venir</div></div></div></div> <div class="col-md-6 col-xl-3"><div class="card h-100 shadow-sm"><div class="card-body"><span class="text-body-secondary small">Target</span><div id="overviewStoreTarget" class="fw-semibold mt-2"></div></div></div></div>
<div class="col-md-6 col-xl-3"><div class="card h-100 shadow-sm"><div class="card-body"><span class="text-body-secondary small">Transactions</span><div class="fw-semibold mt-2">Table skeleton</div></div></div></div> <div class="col-md-6 col-xl-3"><div class="card h-100 shadow-sm"><div class="card-body"><span class="text-body-secondary small">Health</span><div id="overviewStoreHealth" class="fw-semibold mt-2"></div></div></div></div>
<div class="col-md-6 col-xl-3"><div class="card h-100 shadow-sm"><div class="card-body"><span class="text-body-secondary small">Accounts</span><div class="fw-semibold mt-2">Table skeleton</div></div></div></div> <div class="col-md-6 col-xl-3"><div class="card h-100 shadow-sm"><div class="card-body"><span class="text-body-secondary small">Pool</span><div id="overviewStorePool" class="fw-semibold mt-2"></div></div></div></div>
<div class="col-md-6 col-xl-3"><div class="card h-100 shadow-sm"><div class="card-body"><span class="text-body-secondary small">Migration</span><div id="overviewStoreMigration" class="fw-semibold mt-2"></div></div></div></div>
</div> </div>
<div id="overviewStoreDiagnostic" class="alert alert-warning mt-3 mb-0" role="status" aria-live="polite" hidden></div>
</section> </section>
<section data-view-panel="transactions" hidden> <section data-view-panel="transactions" hidden>
<div class="mb-4"> <div class="mb-4">
<h1 class="h3 mb-1">RAW Transactions</h1> <h1 class="h3 mb-1">RAW Transactions</h1>
<p class="text-body-secondary mb-0">Structure DataTables uniquement. Aucune capability Store n'est encore invoquée.</p> <p class="text-body-secondary mb-0">Structure DataTables conservée hors connexion métier jusqu'à pre.007. Le runtime Store est ouvert par le composite.</p>
</div> </div>
<div class="table-responsive app-store-table-container"> <div class="table-responsive app-store-table-container">
<table id="rawTransactionsTable" class="table table-hover align-middle mb-0 app-store-table"> <table id="rawTransactionsTable" class="table table-hover align-middle mb-0 app-store-table">
@@ -83,7 +90,7 @@
<section data-view-panel="accounts" hidden> <section data-view-panel="accounts" hidden>
<div class="mb-4"> <div class="mb-4">
<h1 class="h3 mb-1">RAW Accounts</h1> <h1 class="h3 mb-1">RAW Accounts</h1>
<p class="text-body-secondary mb-0">Structure DataTables uniquement. Aucune donnée Account ni bytes ne traversent IPC dans ce scaffold.</p> <p class="text-body-secondary mb-0">Structure DataTables conservée hors connexion métier jusqu'à pre.008. Aucun byte Account ne traverse IPC dans cette tranche.</p>
</div> </div>
<div class="table-responsive app-store-table-container"> <div class="table-responsive app-store-table-container">
<table id="rawAccountsTable" class="table table-hover align-middle mb-0 app-store-table"> <table id="rawAccountsTable" class="table table-hover align-middle mb-0 app-store-table">
@@ -98,7 +105,7 @@
<div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-4"> <div class="d-flex align-items-center justify-content-between gap-3 flex-wrap mb-4">
<div> <div>
<h1 class="h3 mb-1">Diagnostics</h1> <h1 class="h3 mb-1">Diagnostics</h1>
<p class="text-body-secondary mb-0">État sûr du scaffold Config / Logging. Aucun diagnostic physique Store n'est disponible à cette tranche.</p> <p class="text-body-secondary mb-0">État sûr Config / Logging / Store. Les URI et détails physiques sensibles restent hors IPC.</p>
</div> </div>
<button id="refreshDiagnostics" class="btn btn-outline-primary" type="button"> <button id="refreshDiagnostics" class="btn btn-outline-primary" type="button">
<i class="fa-solid fa-arrows-rotate me-1" aria-hidden="true"></i>Refresh <i class="fa-solid fa-arrows-rotate me-1" aria-hidden="true"></i>Refresh
@@ -113,9 +120,14 @@
<dt class="col-sm-5">Documents Config</dt><dd id="runtimeConfigDocuments" class="col-sm-7"></dd> <dt class="col-sm-5">Documents Config</dt><dd id="runtimeConfigDocuments" class="col-sm-7"></dd>
<dt class="col-sm-5">Profil Logging</dt><dd id="runtimeLoggingProfile" class="col-sm-7"></dd> <dt class="col-sm-5">Profil Logging</dt><dd id="runtimeLoggingProfile" class="col-sm-7"></dd>
<dt class="col-sm-5">Fallback Logging</dt><dd id="runtimeLoggingFallback" class="col-sm-7"></dd> <dt class="col-sm-5">Fallback Logging</dt><dd id="runtimeLoggingFallback" class="col-sm-7"></dd>
<dt class="col-sm-5">Store runtime</dt><dd class="col-sm-7">Non ouvert en pre.002</dd> <dt class="col-sm-5">Store profile</dt><dd id="runtimeStoreProfile" class="col-sm-7"></dd>
<dt class="col-sm-5">Store target</dt><dd id="runtimeStoreTarget" class="col-sm-7"></dd>
<dt class="col-sm-5">Store health</dt><dd id="runtimeStoreHealth" class="col-sm-7"></dd>
<dt class="col-sm-5">Migration</dt><dd id="runtimeStoreMigration" class="col-sm-7"></dd>
<dt class="col-sm-5">Pool</dt><dd id="runtimeStorePool" class="col-sm-7"></dd>
</dl> </dl>
<div id="runtimeDiagnostic" class="alert alert-warning mt-3 mb-0" role="status" aria-live="polite" hidden></div> <div id="runtimeDiagnostic" class="alert alert-warning mt-3 mb-0" role="status" aria-live="polite" hidden></div>
<div id="runtimeStoreDiagnostic" class="alert alert-warning mt-3 mb-0" role="status" aria-live="polite" hidden></div>
</div> </div>
</div> </div>
</section> </section>

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/frontend/ts/main.ts // file: crates/ksp-app-store-desk/frontend/ts/main.ts
// version: 2 // version: 3
import DataTable from "datatables.net-bs5"; import DataTable from "datatables.net-bs5";
import "datatables.net-bs5/css/dataTables.bootstrap5.css"; import "datatables.net-bs5/css/dataTables.bootstrap5.css";
@@ -7,6 +7,7 @@ import "bootstrap";
import ResizeObserver from "resize-observer-polyfill"; import ResizeObserver from "resize-observer-polyfill";
import "simplebar"; import "simplebar";
import type { ShellStatusDto } from "./bindings/ksp_app_store_desk/dto_common/ShellStatusDto.ts"; 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 { frontendDebug, frontendError, frontendInfo, frontendTrace, installFrontendConsoleBridge } from "./frontend_log";
import { invokeKsp } from "./invoke"; 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"; return value === "overview" || value === "transactions" || value === "accounts" || value === "diagnostics";
} }
function setText(elementId: string, value: string): void {
const element = document.querySelector<HTMLElement>(`#${elementId}`);
if (element) {
element.textContent = value;
}
}
function renderDiagnostic(elementId: string, diagnostic: { domain: string; code: string; message: string } | null): void {
const element = document.querySelector<HTMLElement>(`#${elementId}`);
if (!element) {
return;
}
element.hidden = diagnostic === null;
element.textContent = diagnostic ? `${diagnostic.domain}/${diagnostic.code}: ${diagnostic.message}` : "";
}
function activateView(viewId: ViewId): void { function activateView(viewId: ViewId): void {
document.querySelectorAll<HTMLElement>("[data-view-panel]").forEach(panel => { document.querySelectorAll<HTMLElement>("[data-view-panel]").forEach(panel => {
panel.hidden = panel.dataset.viewPanel !== viewId; panel.hidden = panel.dataset.viewPanel !== viewId;
@@ -67,7 +84,7 @@ function initializeEmptyTables(): void {
scrollX: true, scrollX: true,
autoWidth: false, autoWidth: false,
language: { 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; transactionTable = new DataTable("#rawTransactionsTable", commonOptions) as unknown as EmptyDataTable;
@@ -76,39 +93,65 @@ function initializeEmptyTables(): void {
} }
function renderShellStatus(status: ShellStatusDto): void { function renderShellStatus(status: ShellStatusDto): void {
const versionBadge = document.querySelector<HTMLElement>("#appVersionBadge"); setText("appVersionBadge", status.applicationVersion);
if (versionBadge) { setText("runtimeVersion", status.applicationVersion);
versionBadge.textContent = status.applicationVersion; setText("runtimeShellPhase", status.shellPhase);
} setText("runtimeConfigDocuments", status.configDocumentCount.toString());
const values: Record<string, string> = { setText("runtimeLoggingProfile", status.activeLoggingProfile ?? "fallback");
runtimeVersion: status.applicationVersion, setText("runtimeLoggingFallback", status.fallbackLoggingActive ? "oui" : "non");
runtimeShellPhase: status.shellPhase, renderDiagnostic("runtimeDiagnostic", status.startupDiagnostic);
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<HTMLElement>(`#${elementId}`);
if (element) {
element.textContent = value;
}
}
const diagnostic = document.querySelector<HTMLElement>("#runtimeDiagnostic");
if (diagnostic) {
diagnostic.hidden = status.startupDiagnostic === null;
diagnostic.textContent = status.startupDiagnostic ? `${status.startupDiagnostic.domain}/${status.startupDiagnostic.code}: ${status.startupDiagnostic.message}` : "";
}
frontendTrace("main", "Store Desk shell status rendered", { frontendTrace("main", "Store Desk shell status rendered", {
fallbackLoggingActive: status.fallbackLoggingActive, fallbackLoggingActive: status.fallbackLoggingActive,
shellPhase: status.shellPhase, 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<void> {
frontendDebug("main", "Store Desk Store runtime refresh started");
try {
const status = await invokeKsp<StoreRuntimeStatusDto>("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<void> { async function refreshDiagnostics(): Promise<void> {
frontendDebug("main", "Store Desk diagnostics refresh started"); frontendDebug("main", "Store Desk diagnostics refresh started");
try { try {
const status = await invokeKsp<ShellStatusDto>("main", "get_shell_status"); const status = await invokeKsp<ShellStatusDto>("main", "get_shell_status");
renderShellStatus(status); renderShellStatus(status);
await refreshStoreRuntime();
frontendDebug("main", "Store Desk diagnostics refresh completed"); frontendDebug("main", "Store Desk diagnostics refresh completed");
} catch { } catch {
frontendError("main", "Store Desk diagnostics refresh failed"); frontendError("main", "Store Desk diagnostics refresh failed");
@@ -125,6 +168,11 @@ function installInteractions(): void {
} }
}); });
}); });
const refreshOverview = document.querySelector<HTMLButtonElement>("#refreshOverview");
refreshOverview?.addEventListener("click", () => {
frontendDebug("main", "Store Desk Overview refresh button clicked");
void refreshStoreRuntime();
});
const refreshButton = document.querySelector<HTMLButtonElement>("#refreshDiagnostics"); const refreshButton = document.querySelector<HTMLButtonElement>("#refreshDiagnostics");
refreshButton?.addEventListener("click", () => { refreshButton?.addEventListener("click", () => {
frontendDebug("main", "Store Desk diagnostics refresh button clicked"); frontendDebug("main", "Store Desk diagnostics refresh button clicked");
@@ -139,7 +187,7 @@ function installInteractions(): void {
if (!control) { if (!control) {
return; 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; return;
} }
const controlId = control.id || control.getAttribute("data-bs-target") || control.getAttribute("aria-controls") || "anonymous"; const controlId = control.id || control.getAttribute("data-bs-target") || control.getAttribute("aria-controls") || "anonymous";

View File

@@ -1,18 +1,20 @@
// file: crates/ksp-app-store-desk/src/app_state.rs // 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. /// Shared Store Desk application state managed by Tauri.
pub(crate) struct AppState { pub(crate) struct AppState {
config_management: ksp_config_lib::ConfigManagement, config_management: ksp_config_lib::ConfigManagement,
logging_runtime: std::sync::Mutex<LoggingRuntimeState>, logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
shutdown_started: std::sync::atomic::AtomicBool,
splash_settings: crate::SplashSettings, splash_settings: crate::SplashSettings,
splash_sequence_started: std::sync::atomic::AtomicBool, splash_sequence_started: std::sync::atomic::AtomicBool,
store_startup: crate::StoreStartup,
} }
impl crate::AppState { 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<Self> { pub(crate) fn initialize(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<Self> {
let config_management = crate::config_management(arguments); let config_management = crate::config_management(arguments);
let config_management = match config_management { let config_management = match config_management {
@@ -29,6 +31,7 @@ impl crate::AppState {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let store_startup = tauri::async_runtime::block_on(crate::initialize_store(&config_management));
let splash_settings = crate::SplashSettings::load(); let splash_settings = crate::SplashSettings::load();
let splash_settings = match splash_settings { let splash_settings = match splash_settings {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
@@ -63,12 +66,14 @@ impl crate::AppState {
fallback_active: logging_startup.fallback_active, fallback_active: logging_startup.fallback_active,
startup_diagnostic: logging_startup.startup_diagnostic, startup_diagnostic: logging_startup.startup_diagnostic,
}), }),
shutdown_started: std::sync::atomic::AtomicBool::new(false),
splash_settings, splash_settings,
splash_sequence_started: std::sync::atomic::AtomicBool::new(false), 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<crate::ShellStatusDto> { pub(crate) fn shell_status(&self) -> ksp_core_lib::Result<crate::ShellStatusDto> {
let document_count = self.config_management.engine().registry().descriptors().count(); let document_count = self.config_management.engine().registry().descriptors().count();
let document_count = u32::try_from(document_count); let document_count = u32::try_from(document_count);
@@ -100,11 +105,16 @@ impl crate::AppState {
application_version: env!("CARGO_PKG_VERSION").to_owned(), application_version: env!("CARGO_PKG_VERSION").to_owned(),
config_document_count: document_count, config_document_count: document_count,
fallback_logging_active: runtime.fallback_active, fallback_logging_active: runtime.fallback_active,
shell_phase: "pre.002-scaffold".to_owned(), shell_phase: "pre.006-store-overview".to_owned(),
startup_diagnostic: runtime.startup_diagnostic.clone(), 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. /// Returns the resolved splash timings captured during application bootstrap.
#[must_use] #[must_use]
pub(crate) const fn splash_settings(&self) -> crate::SplashSettings { 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) .compare_exchange(false, true, std::sync::atomic::Ordering::AcqRel, std::sync::atomic::Ordering::Acquire)
.is_ok(); .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 { struct LoggingRuntimeState {

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-store-desk/src/bootstrap.rs // 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. /// Crate-internal Logging startup state shared by the application state.
pub(crate) struct LoggingStartup { 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)); 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<ksp_config_lib::ResolvedConfigComposite> {
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<ksp_config_lib::ResolvedConfigProfile> {
let component = composite.component(component_id);
let component = match component {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "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( pub(crate) fn initialize_logging(
management: &ksp_config_lib::ConfigManagement, management: &ksp_config_lib::ConfigManagement,
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity, runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
@@ -41,7 +82,29 @@ pub(crate) fn initialize_logging(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return initialize_fallback_logging(error, runtime_identity), 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 { let resolved = match resolved {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return initialize_fallback_logging(error, runtime_identity), 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, target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_BOOTSTRAP, domain = crate::TRACING_DOMAIN_BOOTSTRAP,
active_profile = active_profile_id.as_str(), 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 { std::result::Result::Ok(crate::LoggingStartup {
guard, guard,
@@ -96,7 +160,7 @@ fn initialize_fallback_logging(
domain = crate::TRACING_DOMAIN_BOOTSTRAP, domain = crate::TRACING_DOMAIN_BOOTSTRAP,
error_domain = diagnostic.domain.as_str(), error_domain = diagnostic.domain.as_str(),
error_code = diagnostic.code.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 { return std::result::Result::Ok(crate::LoggingStartup {
guard, guard,

View File

@@ -1,8 +1,12 @@
// file: crates/ksp-app-store-desk/src/constants.rs // 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. /// Logging profile used while Store Desk is under active development.
pub(crate) const DEVELOPMENT_LOGGING_PROFILE: &str = "supertrace"; pub(crate) const DEVELOPMENT_LOGGING_PROFILE: &str = "supertrace";
/// Structured domain used while bootstrapping Config and Logging. /// 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"; pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
/// Structured domain used by the Store Desk shell. /// Structured domain used by the Store Desk shell.
pub(crate) const TRACING_DOMAIN_SHELL: &str = "store.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. /// Structured domain used by Tauri window lifecycle operations.
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window"; pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
/// Owning target for backend events emitted by Store Desk. /// Owning target for backend events emitted by Store Desk.

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-app-store-desk/src/dto_common.rs // 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 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)] #[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_store_desk/dto_common/ShellStatusDto.ts")] #[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<CommandErrorDto>, pub(crate) startup_diagnostic: std::option::Option<CommandErrorDto>,
} }
/// 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<String>,
/// Safe Store startup/health diagnostic, when present.
pub(crate) diagnostic: std::option::Option<CommandErrorDto>,
/// 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<String>,
/// Logical Store network, when configuration resolution reached that phase.
pub(crate) network: std::option::Option<String>,
/// 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<String>,
/// Whether the application currently retains an opened Store facade.
pub(crate) store_open: bool,
}
#[cfg(test)] #[cfg(test)]
#[path = "../unit_tests/dto_common.rs"] #[path = "../unit_tests/dto_common.rs"]
mod tests; mod tests;

View File

@@ -1,12 +1,14 @@
// file: crates/ksp-app-store-desk/src/errors.rs // 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. /// 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"); 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. /// 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"); 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. /// 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"); 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. /// 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"); 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. /// 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"); 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. /// 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"); 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. /// A required Tauri window is missing from the configured application runtime.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/src/lib.rs // file: crates/ksp-app-store-desk/src/lib.rs
// version: 2 // version: 3
//! Tauri desktop scaffold for backend-neutral KSP Store inspection. //! Tauri desktop scaffold for backend-neutral KSP Store inspection.
@@ -15,6 +15,7 @@ mod errors;
mod frontend_logging; mod frontend_logging;
mod logging_runtime; mod logging_runtime;
mod splash; mod splash;
mod store_runtime;
mod tauri; mod tauri;
mod tw_main; mod tw_main;
mod tw_splash; mod tw_splash;
@@ -28,8 +29,16 @@ pub(crate) use self::app_state::AppState;
pub(crate) use self::bootstrap::LoggingStartup; pub(crate) use self::bootstrap::LoggingStartup;
/// Builds the Config management facade from the common KSP CLI bootstrap contract. /// Builds the Config management facade from the common KSP CLI bootstrap contract.
pub(crate) use self::bootstrap::config_management; 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; 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. /// Logging profile used while Store Desk is under active development.
pub(crate) use self::constants::DEVELOPMENT_LOGGING_PROFILE; pub(crate) use self::constants::DEVELOPMENT_LOGGING_PROFILE;
/// Structured domain used while bootstrapping Config and Logging. /// 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; pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
/// Structured domain used by the Store Desk shell. /// Structured domain used by the Store Desk shell.
pub(crate) use self::constants::TRACING_DOMAIN_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. /// Structured domain used by Tauri window lifecycle operations.
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS; pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
/// Owning target for backend events emitted by Store Desk. /// 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; pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
/// Safe command error projection exposed to Tauri commands. /// Safe command error projection exposed to Tauri commands.
pub(crate) use self::dto_common::CommandErrorDto; 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; 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. /// Shared Store Desk runtime state is internally inconsistent.
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID; pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
/// Shared Store Desk runtime state cannot be locked safely. /// Shared Store Desk runtime state cannot be locked safely.
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED; pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
/// 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. /// Frontend logging requested an unsupported level.
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID; pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
/// Frontend logging requested a target outside the application whitelist. /// Frontend logging requested a target outside the application whitelist.
@@ -66,6 +81,8 @@ pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID; pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
/// A KSP desk splash environment duration is malformed or exceeds its safety bound. /// A KSP desk splash environment duration is malformed or exceeds its safety bound.
pub(crate) use self::errors::ERROR_CODE_SPLASH_SETTING_INVALID; 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. /// Tauri runtime assembly or execution failed.
pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED; pub(crate) use self::errors::ERROR_CODE_TAURI_RUNTIME_FAILED;
/// A required Tauri window is missing from the configured application runtime. /// 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; pub(crate) use self::splash::SplashOrderDto;
/// Runtime timings used by the common desk splash lifecycle. /// Runtime timings used by the common desk splash lifecycle.
pub(crate) use self::splash::SplashSettings; 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. /// Resolves the required main window or returns a typed error.
pub(crate) use self::tw_main::require_main_window; pub(crate) use self::tw_main::require_main_window;
/// Shows and focuses the main Store Desk window. /// Shows and focuses the main Store Desk window.

View File

@@ -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<String>,
diagnostic: std::option::Option<crate::CommandErrorDto>,
network: std::option::Option<String>,
profile_id: std::option::Option<String>,
runtime: std::option::Option<StoreRuntime>,
}
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<std::option::Option<ksp_store_lib::Store>>,
}
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<crate::CommandErrorDto> {
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<String>,
backend_kind: std::option::Option<String>,
network: std::option::Option<String>,
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,
};
}

View File

@@ -1,12 +1,18 @@
// file: crates/ksp-app-store-desk/src/tauri.rs // 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. /// Runs the Store desktop application.
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> { pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
let context = tauri::generate_context!(); 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 = crate::AppState::initialize(arguments);
let app_state = match app_state { let app_state = match app_state {
std::result::Result::Ok(value) => value, 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_plugins(builder);
builder = configure_commands(builder); builder = configure_commands(builder);
builder = configure_setup(builder); builder = configure_setup(builder);
builder = configure_window_events(builder);
let run_result = builder.run(context); let run_result = builder.run(context);
return match run_result { return match run_result {
std::result::Result::Ok(()) => std::result::Result::Ok(()), 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<tauri::Wry>) -> 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<tauri::Wry>, app_state: crate::AppState) -> tauri::Builder<tauri::Wry> { fn configure_state(builder: tauri::Builder<tauri::Wry>, app_state: crate::AppState) -> tauri::Builder<tauri::Wry> {
return builder.manage(app_state); return builder.manage(app_state);
} }
@@ -38,7 +75,7 @@ fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<taur
#[allow(clippy::question_mark_used)] // Tauri generates the question-mark operator internally for async command dispatch. #[allow(clippy::question_mark_used)] // Tauri generates the question-mark operator internally for async command dispatch.
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> { fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
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::Wry>) -> tauri::Builder<tauri::Wry> { fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
@@ -51,11 +88,55 @@ fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri:
if let std::result::Result::Err(error) = main { if let std::result::Result::Err(error) = main {
return std::result::Result::Err(std::boxed::Box::new(error)); return std::result::Result::Err(std::boxed::Box::new(error));
} }
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_SHELL, "Store Desk scaffold Tauri shell setup completed"); ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_SHELL, "Store Desk Tauri shell setup completed with composite Store lifecycle");
return std::result::Result::Ok(()); return std::result::Result::Ok(());
}); });
} }
fn configure_window_events(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
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::<crate::AppState>();
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::<crate::AppState>();
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 { fn project_command_error(command: &'static str, domain: &'static str, error: &ksp_core_lib::Error) -> crate::CommandErrorDto {
ksp_logging_lib::warn!( ksp_logging_lib::warn!(
target: crate::TRACING_TARGET, 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<crate::StoreRuntimeStatusDto, crate::CommandErrorDto> {
return std::result::Result::Ok(state.store_runtime_status().await);
}
#[tauri::command] #[tauri::command]
async fn splash_frontend_ready( async fn splash_frontend_ready(
app: tauri::AppHandle, app: tauri::AppHandle,

View File

@@ -50,6 +50,23 @@
"icon": [ "icon": [
"icons/favicon.png", "icons/favicon.png",
"icons/favicon.ico" "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"
}
} }
} }

View File

@@ -1,13 +1,12 @@
// file: crates/ksp-app-store-desk/tests/dependency_boundary.rs // 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] #[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"); let manifest = include_str!("../Cargo.toml");
for forbidden in [ for forbidden in [
"ksp-store-lib",
"ksp-store-api", "ksp-store-api",
"ksp-store-postgres-lib", "ksp-store-postgres-lib",
"ksp-onchain-transport-lib", "ksp-onchain-transport-lib",
@@ -18,18 +17,26 @@ fn pre_002_manifest_keeps_store_and_transport_runtime_dependencies_out_of_scaffo
"tokio-postgres", "tokio-postgres",
"deadpool-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"] { for required in ["ksp-config-lib", "ksp-core-lib", "ksp-logging-lib", "ksp-store-lib", "tauri-plugin-tracing"] {
assert!(manifest.contains(required), "required scaffold dependency missing: {required}"); assert!(manifest.contains(required), "required Store Desk dependency missing: {required}");
} }
} }
#[test] #[test]
fn pre_002_sources_expose_no_sql_or_physical_store_escape_hatch() { fn pre_006_store_open_is_owned_by_store_runtime_and_never_escapes_to_physical_sql() {
let sources = let bootstrap = include_str!("../src/bootstrap.rs");
[include_str!("../src/app_state.rs"), include_str!("../src/bootstrap.rs"), include_str!("../src/tauri.rs"), include_str!("../src/lib.rs")].join("\n"); let store_runtime = include_str!("../src/store_runtime.rs");
for forbidden in ["Store::open", "tokio_postgres", "deadpool_postgres", "SELECT ", "INSERT ", "UPDATE ", "DELETE ", "ksp_store_api", "ksp_store_postgres"] { let app_state = include_str!("../src/app_state.rs");
assert!(!sources.contains(forbidden), "pre.002 scaffold leaked forbidden Store/runtime surface: {forbidden}"); 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}");
} }
} }

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/tests/desktop_contract.rs // 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. //! 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] #[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"); let html = include_str!("../frontend/main.html");
for view in ["overview", "transactions", "accounts", "diagnostics"] { for view in ["overview", "transactions", "accounts", "diagnostics"] {
assert!(html.contains(&format!("data-view=\"{view}\""))); 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=\"rawTransactionsTable\""));
assert!(html.contains("id=\"rawAccountsTable\"")); 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] #[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"); let source = include_str!("../frontend/ts/main.ts");
assert!(source.contains("datatables.net-bs5")); assert!(source.contains("datatables.net-bs5"));
assert!(source.contains("paging: true")); 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}"); 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"));
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/tests/desktop_security.rs // file: crates/ksp-app-store-desk/tests/desktop_security.rs
// version: 3 // version: 4
//! Security boundary tests for the Store Desk scaffold. //! 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 constants = include_str!("../src/constants.rs");
let bootstrap = include_str!("../src/bootstrap.rs"); let bootstrap = include_str!("../src/bootstrap.rs");
assert!(constants.contains("DEVELOPMENT_LOGGING_PROFILE: &str = \"supertrace\"")); 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")); assert!(bootstrap.contains("LogFilterLevel::Trace"));
return; 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}");
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-store-desk/unit_tests/dto_common.rs // file: crates/ksp-app-store-desk/unit_tests/dto_common.rs
// version: 1 // version: 2
#[test] #[test]
fn command_error_projection_keeps_only_stable_public_fields() { 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:?}"); let debug = format!("{projected:?}");
assert!(!debug.contains("must-not-cross-ipc")); 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}");
}
}
}

View File

@@ -54,18 +54,19 @@
"resources": { "resources": {
"../../config/composite.ksp-app-backfill-desk.json": "config/composite.ksp-app-backfill-desk.json", "../../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-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/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/composite.schema.json": "config/schemas/composite.schema.json",
"../../config/schemas/std.logging.schema.json": "config/schemas/std.logging.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.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.store.schema.json": "config/schemas/std.store.schema.json",
"../../config/schemas/std.transport.schema.json": "config/schemas/std.transport.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"
} }
} }
} }

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-config-lib/README.md --> <!-- file: crates/ksp-config-lib/README.md -->
<!-- version: 11 --> <!-- version: 12 -->
# ksp-config-lib # 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 : Le registre par défaut connaît :
```text ```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-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.composite.ksp-app-wallet-desk -> config/composite.ksp-app-wallet-desk.json
cfg.std.logging -> config/std.logging.json cfg.std.logging -> config/std.logging.json
cfg.std.offchain_transport -> config/std.offchain_transport.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é. `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 lexemple 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 lexemple 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 : 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.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.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/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-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 ; - [`../../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. - [`../../.env.example`](../../.env.example) — inventaire versionné des variables d'environnement runtime.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/lib.rs // file: crates/ksp-config-lib/src/lib.rs
// version: 22 // version: 23
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -155,6 +155,8 @@ pub use self::registry::ConfigFileRegistry;
pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_BACKFILL_DESK_FILENAME; pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_BACKFILL_DESK_FILENAME;
/// Default physical filename for the SOL Prices Desk composite configuration document. /// Default physical filename for the SOL Prices Desk composite configuration document.
pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_SOLPRICES_DESK_FILENAME; 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. /// Default physical filename for the Wallet Desk composite configuration document.
pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME; pub use self::registry::DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME;
/// Default physical filename for the generic composite JSON Schema document. /// Default physical filename for the generic composite JSON Schema document.
@@ -183,6 +185,8 @@ pub use self::registry::DEFAULT_STD_WALLET_SCHEMA_FILENAME;
pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_BACKFILL_DESK; pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_BACKFILL_DESK;
/// Logical file identifier for the SOL Prices Desk composite configuration document. /// Logical file identifier for the SOL Prices Desk composite configuration document.
pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK; 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. /// Logical file identifier for the Wallet Desk composite configuration document.
pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK; pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK;
/// Logical file identifier for the generic composite JSON Schema document. /// Logical file identifier for the generic composite JSON Schema document.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/src/registry.rs // file: crates/ksp-config-lib/src/registry.rs
// version: 14 // version: 15
/// Bootstrap argument used to replace a known Config filename mapping. /// Bootstrap argument used to replace a known Config filename mapping.
pub const ARG_FILE_MAP: &str = "--filemap"; pub const ARG_FILE_MAP: &str = "--filemap";
@@ -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"; 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. /// 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"; 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. /// Default physical filename for the Wallet Desk composite configuration document.
pub const DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME: &str = "composite.ksp-app-wallet-desk.json"; pub const DEFAULT_COMPOSITE_KSP_APP_WALLET_DESK_FILENAME: &str = "composite.ksp-app-wallet-desk.json";
/// Default physical filename for the generic composite JSON Schema document. /// Default physical filename for the generic composite JSON Schema document.
@@ -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"; 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. /// 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"; 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. /// Logical file identifier for the Wallet Desk composite configuration document.
pub const FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK: &str = "cfg.composite.ksp-app-wallet-desk"; pub const FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK: &str = "cfg.composite.ksp-app-wallet-desk";
/// Logical file identifier for the generic composite JSON Schema document. /// Logical file identifier for the generic composite JSON Schema document.
@@ -184,6 +188,16 @@ impl ConfigFileRegistry {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let 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( let wallet_composite = ConfigFileDescriptor::new(
FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK, FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK,
ConfigFileKind::Config, ConfigFileKind::Config,
@@ -287,6 +301,7 @@ impl ConfigFileRegistry {
return crate::build_registry([ return crate::build_registry([
backfill_composite, backfill_composite,
solprices_composite, solprices_composite,
store_desk_composite,
wallet_composite, wallet_composite,
composite_schema, composite_schema,
logging, logging,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-config-lib/tests/public_api.rs // file: crates/ksp-config-lib/tests/public_api.rs
// version: 27 // version: 28
//! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity, //! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity,
//! Logging/Transport/Store adapters and management contracts. //! 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:?}"); assert!(registry.is_ok(), "public registry should remain constructible: {registry:?}");
if let std::result::Result::Ok(registry) = registry { if let std::result::Result::Ok(registry) = registry {
let descriptors: std::vec::Vec<&ksp_config_lib::ConfigFileDescriptor> = registry.descriptors().collect(); let descriptors: std::vec::Vec<&ksp_config_lib::ConfigFileDescriptor> = registry.descriptors().collect();
assert_eq!(descriptors.len(), 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[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[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[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_STD_LOGGING); 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_OFFCHAIN_TRANSPORT); 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_STORE); 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_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_WALLET); 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_SCHEMA_COMPOSITE); 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_STD_LOGGING); 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_OFFCHAIN_TRANSPORT); 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_STORE); 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_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_WALLET); assert_eq!(descriptors[13].file_id().as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT);
let schema_file_id = descriptors[7].schema_file_id(); 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"); assert!(schema_file_id.is_some(), "public Wallet descriptor should preserve schema association");
if let std::option::Option::Some(schema_file_id) = schema_file_id { if let std::option::Option::Some(schema_file_id) = schema_file_id {
assert_eq!(schema_file_id.as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_WALLET); assert_eq!(schema_file_id.as_str(), ksp_config_lib::FILE_ID_SCHEMA_STD_WALLET);
@@ -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::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::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::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); assert_ne!(ksp_config_lib::ConfigProfileSelectionSource::Composite, ksp_config_lib::ConfigProfileSelectionSource::Explicit);
} }

View File

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

View File

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

251
deltas/0.3.8/pre.006.md Normal file
View File

@@ -0,0 +1,251 @@
<!-- file: deltas/0.3.8/pre.006.md -->
<!-- version: 1 -->
# 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)
```

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/029-V0_3_8_STORE_DESK_PLAN.md --> <!-- file: docs/plans/029-V0_3_8_STORE_DESK_PLAN.md -->
<!-- version: 5 --> <!-- version: 6 -->
# Plan v0.3.8 — Store Desk V1 RAW # 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 ### 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 ### pre.007 — RawTransaction DataTables serverSide + détail

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/025-V0_3_8_STORE_DESK.md --> <!-- file: docs/validation/025-V0_3_8_STORE_DESK.md -->
<!-- version: 11 --> <!-- version: 12 -->
# Validation v0.3.8 — Store Desk V1 RAW # 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.002` scaffold KSP strict + DataTables skeleton ;
- [X] `pre.003` contrats Store inspection offset/count/summaries ; - [X] `pre.003` contrats Store inspection offset/count/summaries ;
- [ ] `pre.004` PostgreSQL + façade inspection RawTransaction ; - [X] `pre.004` PostgreSQL + façade inspection RawTransaction ;
- [ ] `pre.005` PostgreSQL + façade inspection RawAccountState ; - [X] `pre.005` PostgreSQL + façade inspection RawAccountState ;
- [ ] `pre.006` composite Logging+Store + Overview/health ; - [X] `pre.006` composite Logging+Store + Overview/health ;
- [ ] `pre.007` RawTransaction DataTables serverSide + détail ; - [ ] `pre.007` RawTransaction DataTables serverSide + détail ;
- [ ] `pre.008` RawAccountState DataTables serverSide + détail ; - [ ] `pre.008` RawAccountState DataTables serverSide + détail ;
- [ ] `pre.009` observation listing/inspection Store ; - [ ] `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é. 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.