v0.5.1-pre.008
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/app_state.rs
|
||||
// version: 15
|
||||
// version: 16
|
||||
|
||||
//! Shared Tauri application state and startup initialization.
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
pub(crate) struct AppState {
|
||||
config_path: std::string::String,
|
||||
logging_config_path: std::string::String,
|
||||
transport_config_path: std::string::String,
|
||||
listeners_config_path: std::string::String,
|
||||
store_config_path: std::string::String,
|
||||
wallet_config_path: std::string::String,
|
||||
execution_config_path: std::string::String,
|
||||
desktop_config: crate::DesktopApplicationConfig,
|
||||
app_config: ks_config::AppConfig,
|
||||
active_profile: ks_config::ProfileConfig,
|
||||
logging_guard: std::sync::Mutex<ks_logging::LoggingGuard>,
|
||||
@@ -44,8 +50,17 @@ impl crate::AppState {
|
||||
std::result::Result::Ok(profile) => profile.clone(),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let desktop_config = match crate::desktop_application_config(&composition_profile) {
|
||||
std::result::Result::Ok(config) => config,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let logging_config_path =
|
||||
resolve_logging_config_path(&workspace_root, &composed.logging_path);
|
||||
let transport_config_path = composed.transport_path.display().to_string();
|
||||
let listeners_config_path = composed.listeners_path.display().to_string();
|
||||
let store_config_path = composed.store_path.display().to_string();
|
||||
let wallet_config_path = composed.wallet_path.display().to_string();
|
||||
let execution_config_path = composed.execution_path.display().to_string();
|
||||
let app_config = composed.app_config;
|
||||
let active_profile = match ks_config::active_profile(&app_config) {
|
||||
std::result::Result::Ok(profile) => profile.clone(),
|
||||
@@ -79,6 +94,12 @@ impl crate::AppState {
|
||||
return std::result::Result::Ok(crate::AppState {
|
||||
config_path: config_path.display().to_string(),
|
||||
logging_config_path: logging_config_path.display().to_string(),
|
||||
transport_config_path,
|
||||
listeners_config_path,
|
||||
store_config_path,
|
||||
wallet_config_path,
|
||||
execution_config_path,
|
||||
desktop_config,
|
||||
app_config,
|
||||
active_profile,
|
||||
logging_guard: std::sync::Mutex::new(logging_guard),
|
||||
@@ -108,6 +129,36 @@ impl crate::AppState {
|
||||
return self.logging_config_path.as_str();
|
||||
}
|
||||
|
||||
/// Returns the path used to load the transport configuration file.
|
||||
pub(crate) fn transport_config_path(&self) -> &str {
|
||||
return self.transport_config_path.as_str();
|
||||
}
|
||||
|
||||
/// Returns the path used to load the listeners configuration file.
|
||||
pub(crate) fn listeners_config_path(&self) -> &str {
|
||||
return self.listeners_config_path.as_str();
|
||||
}
|
||||
|
||||
/// Returns the path used to load the store configuration file.
|
||||
pub(crate) fn store_config_path(&self) -> &str {
|
||||
return self.store_config_path.as_str();
|
||||
}
|
||||
|
||||
/// Returns the path used to load the wallet configuration file.
|
||||
pub(crate) fn wallet_config_path(&self) -> &str {
|
||||
return self.wallet_config_path.as_str();
|
||||
}
|
||||
|
||||
/// Returns the path used to load the execution configuration file.
|
||||
pub(crate) fn execution_config_path(&self) -> &str {
|
||||
return self.execution_config_path.as_str();
|
||||
}
|
||||
|
||||
/// Returns desktop-owned settings selected from the active composition profile.
|
||||
pub(crate) fn desktop_config(&self) -> &crate::DesktopApplicationConfig {
|
||||
return &self.desktop_config;
|
||||
}
|
||||
|
||||
/// Returns the complete parsed application configuration.
|
||||
pub(crate) fn app_config(&self) -> &ks_config::AppConfig {
|
||||
return &self.app_config;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: kb-app-demo-desktop/src/demo_config.rs
|
||||
// version: 3
|
||||
// version: 5
|
||||
|
||||
//! Configuration demo payload and state projection.
|
||||
//! Configuration demo payloads with explicit public and bounded diagnostic projections.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
@@ -12,29 +12,432 @@ use ts_rs::TS; // rust-rules: derive-import
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigPayload {
|
||||
/// Path used to load the configuration file.
|
||||
pub(crate) config_path: std::string::String,
|
||||
/// Active profile name.
|
||||
/// Active composition profile name.
|
||||
pub(crate) active_profile_name: std::string::String,
|
||||
/// Active profile environment.
|
||||
/// Desktop-owned environment label.
|
||||
pub(crate) environment: std::string::String,
|
||||
/// Entire parsed configuration.
|
||||
pub(crate) app_config: ks_config::AppConfig,
|
||||
/// Active profile configuration.
|
||||
pub(crate) active_profile: ks_config::ProfileConfig,
|
||||
/// Embedded JSON Schema text used during loading.
|
||||
pub(crate) schema_json: std::string::String,
|
||||
/// Explicit public configuration projection.
|
||||
pub(crate) public_config: crate::DemoConfigPublicPayload,
|
||||
/// Explicit bounded diagnostic projection.
|
||||
pub(crate) diagnostic_config: crate::DemoConfigDiagnosticPayload,
|
||||
/// Generic binary-composition JSON Schema text.
|
||||
pub(crate) composition_schema_json: std::string::String,
|
||||
}
|
||||
|
||||
/// Public desktop configuration projection containing no secret or internal paths.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigPublicPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigPublicPayload {
|
||||
/// Desktop application identity.
|
||||
pub(crate) application_name: std::string::String,
|
||||
/// Desktop environment label.
|
||||
pub(crate) environment: std::string::String,
|
||||
/// Active composed profile.
|
||||
pub(crate) profile_name: std::string::String,
|
||||
/// Public HTTP endpoint identities without URLs or credentials.
|
||||
pub(crate) http_endpoints: std::vec::Vec<crate::DemoConfigPublicEndpointPayload>,
|
||||
/// Public WebSocket endpoint identities without URLs or credentials.
|
||||
pub(crate) ws_endpoints: std::vec::Vec<crate::DemoConfigPublicEndpointPayload>,
|
||||
/// Listener summary without account-specific filters.
|
||||
pub(crate) listeners: crate::DemoConfigPublicListenersPayload,
|
||||
/// Wallet cluster only; wallet paths and aliases remain diagnostic/internal.
|
||||
pub(crate) wallet_cluster: std::string::String,
|
||||
/// Desktop-owned demo feature flags.
|
||||
pub(crate) demo: crate::DemoConfigPublicDemoPayload,
|
||||
}
|
||||
|
||||
/// Public endpoint identity with no URL or authentication material.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigPublicEndpointPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigPublicEndpointPayload {
|
||||
/// Endpoint code.
|
||||
pub(crate) name: std::string::String,
|
||||
/// Whether the endpoint is enabled.
|
||||
pub(crate) enabled: bool,
|
||||
/// Provider code.
|
||||
pub(crate) provider: std::string::String,
|
||||
/// Cluster code.
|
||||
pub(crate) cluster: std::string::String,
|
||||
}
|
||||
|
||||
/// Public listener summary.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigPublicListenersPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigPublicListenersPayload {
|
||||
/// Whether listener creation is enabled.
|
||||
pub(crate) enabled: bool,
|
||||
/// Default commitment used by subscriptions.
|
||||
pub(crate) default_commitment: std::string::String,
|
||||
/// Number of log listener declarations.
|
||||
pub(crate) log_listener_count: u32,
|
||||
/// Number of program listener declarations.
|
||||
pub(crate) program_listener_count: u32,
|
||||
/// Number of account listener declarations.
|
||||
pub(crate) account_listener_count: u32,
|
||||
}
|
||||
|
||||
/// Public desktop demo feature flags.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigPublicDemoPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigPublicDemoPayload {
|
||||
/// Enables live demo pages.
|
||||
pub(crate) live_demo_enabled: bool,
|
||||
/// Enables trading demo pages.
|
||||
pub(crate) trading_demo_enabled: bool,
|
||||
}
|
||||
|
||||
/// Bounded diagnostic projection containing internal configuration metadata but no secret values.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigDiagnosticPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigDiagnosticPayload {
|
||||
/// Filesystem sources selected by the active binary composition.
|
||||
pub(crate) sources: std::vec::Vec<crate::DemoConfigSourceDiagnosticPayload>,
|
||||
/// Endpoint runtime settings without endpoint URLs.
|
||||
pub(crate) endpoints: std::vec::Vec<crate::DemoConfigEndpointDiagnosticPayload>,
|
||||
/// Store diagnostics without database URLs or filesystem paths.
|
||||
pub(crate) store: crate::DemoConfigStoreDiagnosticPayload,
|
||||
/// Wallet diagnostics without wallet filesystem paths or key material.
|
||||
pub(crate) wallet: crate::DemoConfigWalletDiagnosticPayload,
|
||||
/// Execution-policy diagnostics.
|
||||
pub(crate) execution: crate::DemoConfigExecutionDiagnosticPayload,
|
||||
}
|
||||
|
||||
/// One selected configuration source exposed only through the explicit diagnostic surface.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigSourceDiagnosticPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigSourceDiagnosticPayload {
|
||||
/// Source category.
|
||||
pub(crate) category: std::string::String,
|
||||
/// Resolved filesystem path.
|
||||
pub(crate) path: std::string::String,
|
||||
}
|
||||
|
||||
/// Endpoint diagnostic without URL content.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigEndpointDiagnosticPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigEndpointDiagnosticPayload {
|
||||
/// Transport kind, either `http` or `ws`.
|
||||
pub(crate) kind: std::string::String,
|
||||
/// Endpoint code.
|
||||
pub(crate) name: std::string::String,
|
||||
/// Whether the endpoint is enabled.
|
||||
pub(crate) enabled: bool,
|
||||
/// Provider code.
|
||||
pub(crate) provider: std::string::String,
|
||||
/// Cluster code.
|
||||
pub(crate) cluster: std::string::String,
|
||||
/// URL state marker; the URL itself is never serialized.
|
||||
pub(crate) url_state: std::string::String,
|
||||
/// Connection timeout in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) connect_timeout_ms: u64,
|
||||
/// Request timeout in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) request_timeout_ms: u64,
|
||||
/// Optional unsubscribe timeout in milliseconds for WebSocket endpoints.
|
||||
#[ts(type = "number | null")]
|
||||
pub(crate) unsubscribe_timeout_ms: std::option::Option<u64>,
|
||||
/// Optional reconnect policy for WebSocket endpoints.
|
||||
pub(crate) auto_reconnect: std::option::Option<bool>,
|
||||
/// Number of configured endpoint roles.
|
||||
pub(crate) role_count: u32,
|
||||
}
|
||||
|
||||
/// Store diagnostic projection without DSN or SQLite path content.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigStoreDiagnosticPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigStoreDiagnosticPayload {
|
||||
/// Whether persistence is enabled.
|
||||
pub(crate) enabled: bool,
|
||||
/// Selected store backend.
|
||||
pub(crate) backend: std::string::String,
|
||||
/// Whether a PostgreSQL URL is configured.
|
||||
pub(crate) postgres_url_state: std::string::String,
|
||||
/// PostgreSQL connection pool ceiling.
|
||||
pub(crate) postgres_max_connections: u32,
|
||||
/// PostgreSQL connection timeout in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) postgres_connect_timeout_ms: u64,
|
||||
/// Whether PostgreSQL schema initialization is enabled.
|
||||
pub(crate) postgres_auto_initialize_schema: bool,
|
||||
/// Whether a SQLite path is configured.
|
||||
pub(crate) sqlite_path_state: std::string::String,
|
||||
/// SQLite connection pool ceiling.
|
||||
pub(crate) sqlite_max_connections: u32,
|
||||
/// Whether SQLite schema initialization is enabled.
|
||||
pub(crate) sqlite_auto_initialize_schema: bool,
|
||||
}
|
||||
|
||||
/// Wallet diagnostic projection without paths or secret key material.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigWalletDiagnosticPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigWalletDiagnosticPayload {
|
||||
/// Whether a wallet directory is configured.
|
||||
pub(crate) wallet_directory_state: std::string::String,
|
||||
/// Wallet cluster.
|
||||
pub(crate) cluster: std::string::String,
|
||||
/// Whether the managed temporary wallet is enabled.
|
||||
pub(crate) temporary_wallet_enabled: bool,
|
||||
/// Temporary wallet alias, which is internal but not secret.
|
||||
pub(crate) temporary_wallet_alias: std::string::String,
|
||||
/// Whether the managed temporary wallet persists between restarts.
|
||||
pub(crate) temporary_wallet_persist: bool,
|
||||
}
|
||||
|
||||
/// Execution-policy diagnostic projection.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_config/DemoConfigExecutionDiagnosticPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoConfigExecutionDiagnosticPayload {
|
||||
/// Enables local-validator sends.
|
||||
pub(crate) localnet_send_enabled: bool,
|
||||
/// Enables Devnet sends.
|
||||
pub(crate) devnet_send_enabled: bool,
|
||||
/// Enables Testnet sends.
|
||||
pub(crate) testnet_send_enabled: bool,
|
||||
/// Enables Mainnet sends.
|
||||
pub(crate) mainnet_send_enabled: bool,
|
||||
/// Default dry-run policy.
|
||||
pub(crate) dry_run_default: bool,
|
||||
/// Requires simulation before send.
|
||||
pub(crate) require_simulation: bool,
|
||||
/// Requires explicit operator confirmation.
|
||||
pub(crate) require_operator_confirmation: bool,
|
||||
/// Maximum local-validator spend in lamports.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) localnet_max_spend_lamports: u64,
|
||||
/// Maximum Devnet spend in lamports.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) devnet_max_spend_lamports: u64,
|
||||
/// Maximum Testnet spend in lamports.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) testnet_max_spend_lamports: u64,
|
||||
/// Maximum Mainnet spend in lamports.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) mainnet_max_spend_lamports: u64,
|
||||
/// Maximum fee in lamports.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) max_fee_lamports: u64,
|
||||
/// Maximum compute-unit price in micro-lamports.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) max_compute_unit_price_micro_lamports: u64,
|
||||
/// Maximum recent-blockhash age in slots.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) recent_blockhash_max_age_slots: u64,
|
||||
/// Maximum send retries.
|
||||
pub(crate) send_max_retries: u32,
|
||||
/// Confirmation poll interval in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) confirmation_poll_interval_ms: u64,
|
||||
/// Maximum confirmation poll attempts.
|
||||
pub(crate) confirmation_max_attempts: u32,
|
||||
/// Maximum Devnet faucet airdrop in lamports.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) devnet_airdrop_max_lamports: u64,
|
||||
}
|
||||
|
||||
/// Builds the configuration payload from shared application state.
|
||||
pub(crate) fn demo_config_payload(state: &crate::AppState) -> crate::DemoConfigPayload {
|
||||
let active_profile = state.active_profile();
|
||||
let desktop = state.desktop_config();
|
||||
return crate::DemoConfigPayload {
|
||||
config_path: state.config_path().to_owned(),
|
||||
active_profile_name: state.active_profile().name.clone(),
|
||||
environment: state.active_profile().app.environment.clone(),
|
||||
app_config: state.app_config().clone(),
|
||||
active_profile: state.active_profile().clone(),
|
||||
schema_json: ks_config::config_json_schema_text().to_owned(),
|
||||
active_profile_name: active_profile.name.clone(),
|
||||
environment: desktop.environment.clone(),
|
||||
public_config: public_config_payload(active_profile, desktop),
|
||||
diagnostic_config: diagnostic_config_payload(state),
|
||||
composition_schema_json: ks_config::composition_json_schema_text().to_owned(),
|
||||
};
|
||||
}
|
||||
|
||||
fn public_config_payload(
|
||||
profile: &ks_config::ProfileConfig,
|
||||
desktop: &crate::DesktopApplicationConfig,
|
||||
) -> DemoConfigPublicPayload {
|
||||
let http_endpoints = profile
|
||||
.solana
|
||||
.http_endpoints
|
||||
.iter()
|
||||
.map(|endpoint| {
|
||||
return DemoConfigPublicEndpointPayload {
|
||||
name: endpoint.name.clone(),
|
||||
enabled: endpoint.enabled,
|
||||
provider: endpoint.provider.clone(),
|
||||
cluster: endpoint.cluster.clone(),
|
||||
};
|
||||
})
|
||||
.collect::<std::vec::Vec<DemoConfigPublicEndpointPayload>>();
|
||||
let ws_endpoints = profile
|
||||
.solana
|
||||
.ws_endpoints
|
||||
.iter()
|
||||
.map(|endpoint| {
|
||||
return DemoConfigPublicEndpointPayload {
|
||||
name: endpoint.name.clone(),
|
||||
enabled: endpoint.enabled,
|
||||
provider: endpoint.provider.clone(),
|
||||
cluster: endpoint.cluster.clone(),
|
||||
};
|
||||
})
|
||||
.collect::<std::vec::Vec<DemoConfigPublicEndpointPayload>>();
|
||||
return DemoConfigPublicPayload {
|
||||
application_name: desktop.name.clone(),
|
||||
environment: desktop.environment.clone(),
|
||||
profile_name: profile.name.clone(),
|
||||
http_endpoints,
|
||||
ws_endpoints,
|
||||
listeners: DemoConfigPublicListenersPayload {
|
||||
enabled: profile.solana.listeners.enabled,
|
||||
default_commitment: profile.solana.listeners.default_commitment.clone(),
|
||||
log_listener_count: bounded_len(profile.solana.listeners.log_listeners.len()),
|
||||
program_listener_count: bounded_len(profile.solana.listeners.program_listeners.len()),
|
||||
account_listener_count: bounded_len(profile.solana.listeners.account_listeners.len()),
|
||||
},
|
||||
wallet_cluster: profile.wallet.cluster.clone(),
|
||||
demo: DemoConfigPublicDemoPayload {
|
||||
live_demo_enabled: desktop.demo.live_demo_enabled,
|
||||
trading_demo_enabled: desktop.demo.trading_demo_enabled,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn diagnostic_config_payload(state: &crate::AppState) -> DemoConfigDiagnosticPayload {
|
||||
let profile = state.active_profile();
|
||||
let mut endpoints = std::vec::Vec::<DemoConfigEndpointDiagnosticPayload>::new();
|
||||
for endpoint in &profile.solana.http_endpoints {
|
||||
endpoints.push(DemoConfigEndpointDiagnosticPayload {
|
||||
kind: "http".to_string(),
|
||||
name: endpoint.name.clone(),
|
||||
enabled: endpoint.enabled,
|
||||
provider: endpoint.provider.clone(),
|
||||
cluster: endpoint.cluster.clone(),
|
||||
url_state: configured_state(endpoint.url.as_str()),
|
||||
connect_timeout_ms: endpoint.connect_timeout_ms,
|
||||
request_timeout_ms: endpoint.request_timeout_ms,
|
||||
unsubscribe_timeout_ms: std::option::Option::None,
|
||||
auto_reconnect: std::option::Option::None,
|
||||
role_count: bounded_len(endpoint.roles.len()),
|
||||
});
|
||||
}
|
||||
for endpoint in &profile.solana.ws_endpoints {
|
||||
endpoints.push(DemoConfigEndpointDiagnosticPayload {
|
||||
kind: "ws".to_string(),
|
||||
name: endpoint.name.clone(),
|
||||
enabled: endpoint.enabled,
|
||||
provider: endpoint.provider.clone(),
|
||||
cluster: endpoint.cluster.clone(),
|
||||
url_state: configured_state(endpoint.url.as_str()),
|
||||
connect_timeout_ms: endpoint.connect_timeout_ms,
|
||||
request_timeout_ms: endpoint.request_timeout_ms,
|
||||
unsubscribe_timeout_ms: std::option::Option::Some(endpoint.unsubscribe_timeout_ms),
|
||||
auto_reconnect: std::option::Option::Some(endpoint.auto_reconnect),
|
||||
role_count: bounded_len(endpoint.roles.len()),
|
||||
});
|
||||
}
|
||||
return DemoConfigDiagnosticPayload {
|
||||
sources: std::vec![
|
||||
source_diagnostic("composition", state.config_path()),
|
||||
source_diagnostic("logging", state.logging_config_path()),
|
||||
source_diagnostic("transport", state.transport_config_path()),
|
||||
source_diagnostic("listeners", state.listeners_config_path()),
|
||||
source_diagnostic("store", state.store_config_path()),
|
||||
source_diagnostic("wallet", state.wallet_config_path()),
|
||||
source_diagnostic("execution", state.execution_config_path()),
|
||||
],
|
||||
endpoints,
|
||||
store: DemoConfigStoreDiagnosticPayload {
|
||||
enabled: profile.database.enabled,
|
||||
backend: profile.database.backend.clone(),
|
||||
postgres_url_state: configured_state(profile.database.postgres.url.as_str()),
|
||||
postgres_max_connections: profile.database.postgres.max_connections,
|
||||
postgres_connect_timeout_ms: profile.database.postgres.connect_timeout_ms,
|
||||
postgres_auto_initialize_schema: profile.database.postgres.auto_initialize_schema,
|
||||
sqlite_path_state: configured_state(profile.database.sqlite.path.as_str()),
|
||||
sqlite_max_connections: profile.database.sqlite.max_connections,
|
||||
sqlite_auto_initialize_schema: profile.database.sqlite.auto_initialize_schema,
|
||||
},
|
||||
wallet: DemoConfigWalletDiagnosticPayload {
|
||||
wallet_directory_state: configured_state(profile.wallet.wallet_dir.as_str()),
|
||||
cluster: profile.wallet.cluster.clone(),
|
||||
temporary_wallet_enabled: profile.wallet.temporary_wallet_enabled,
|
||||
temporary_wallet_alias: profile.wallet.temporary_wallet_alias.clone(),
|
||||
temporary_wallet_persist: profile.wallet.temporary_wallet_persist,
|
||||
},
|
||||
execution: execution_diagnostic(&profile.execution),
|
||||
};
|
||||
}
|
||||
|
||||
fn source_diagnostic(category: &str, path: &str) -> DemoConfigSourceDiagnosticPayload {
|
||||
return DemoConfigSourceDiagnosticPayload {
|
||||
category: category.to_string(),
|
||||
path: path.to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
fn configured_state(value: &str) -> std::string::String {
|
||||
if value.trim().is_empty() {
|
||||
return "missing".to_string();
|
||||
}
|
||||
return "configured".to_string();
|
||||
}
|
||||
|
||||
fn bounded_len(value: usize) -> u32 {
|
||||
return match u32::try_from(value) {
|
||||
std::result::Result::Ok(count) => count,
|
||||
std::result::Result::Err(_) => u32::MAX,
|
||||
};
|
||||
}
|
||||
|
||||
fn execution_diagnostic(
|
||||
execution: &ks_config::ExecutionConfig,
|
||||
) -> DemoConfigExecutionDiagnosticPayload {
|
||||
return DemoConfigExecutionDiagnosticPayload {
|
||||
localnet_send_enabled: execution.localnet_send_enabled,
|
||||
devnet_send_enabled: execution.devnet_send_enabled,
|
||||
testnet_send_enabled: execution.testnet_send_enabled,
|
||||
mainnet_send_enabled: execution.mainnet_send_enabled,
|
||||
dry_run_default: execution.dry_run_default,
|
||||
require_simulation: execution.require_simulation,
|
||||
require_operator_confirmation: execution.require_operator_confirmation,
|
||||
localnet_max_spend_lamports: execution.localnet_max_spend_lamports,
|
||||
devnet_max_spend_lamports: execution.devnet_max_spend_lamports,
|
||||
testnet_max_spend_lamports: execution.testnet_max_spend_lamports,
|
||||
mainnet_max_spend_lamports: execution.mainnet_max_spend_lamports,
|
||||
max_fee_lamports: execution.max_fee_lamports,
|
||||
max_compute_unit_price_micro_lamports: execution.max_compute_unit_price_micro_lamports,
|
||||
recent_blockhash_max_age_slots: execution.recent_blockhash_max_age_slots,
|
||||
send_max_retries: execution.send_max_retries,
|
||||
confirmation_poll_interval_ms: execution.confirmation_poll_interval_ms,
|
||||
confirmation_max_attempts: execution.confirmation_max_attempts,
|
||||
devnet_airdrop_max_lamports: execution.devnet_airdrop_max_lamports,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,10 +445,126 @@ pub(crate) fn demo_config_payload(state: &crate::AppState) -> crate::DemoConfigP
|
||||
mod tests {
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
fn active_fixture_profile() -> ks_config::ProfileConfig {
|
||||
let parsed = ks_config::parse_config_json(include_str!(
|
||||
"../../test-fixtures/config/resolved.app.config.json"
|
||||
));
|
||||
let config = match parsed {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("resolved fixture must parse: {error}"),
|
||||
};
|
||||
return match ks_config::active_profile(&config) {
|
||||
std::result::Result::Ok(value) => value.clone(),
|
||||
std::result::Result::Err(error) => {
|
||||
panic!("resolved fixture active profile must exist: {error}")
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn desktop_fixture() -> crate::DesktopApplicationConfig {
|
||||
return crate::DesktopApplicationConfig {
|
||||
name: "kb-app-demo-desktop".to_string(),
|
||||
environment: "test".to_string(),
|
||||
demo: crate::DesktopDemoConfig {
|
||||
live_demo_enabled: true,
|
||||
trading_demo_enabled: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn payload_binding_uses_desktop_path() {
|
||||
let config = ts_rs::Config::default();
|
||||
let declaration = <crate::DemoConfigPayload as TS>::decl(&config);
|
||||
assert!(declaration.contains("DemoConfigPayload"));
|
||||
assert!(declaration.contains("DemoConfigPublicPayload"));
|
||||
assert!(declaration.contains("DemoConfigDiagnosticPayload"));
|
||||
assert!(!declaration.contains("AppConfig"));
|
||||
assert!(!declaration.contains("ProfileConfig"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_projection_excludes_internal_execution_and_storage_fields() {
|
||||
let profile = active_fixture_profile();
|
||||
let payload = super::public_config_payload(&profile, &desktop_fixture());
|
||||
let serialized = match serde_json::to_string(&payload) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("public payload must serialize: {error}"),
|
||||
};
|
||||
assert!(!serialized.contains("mainnet_send_enabled"));
|
||||
assert!(!serialized.contains("postgres"));
|
||||
assert!(!serialized.contains("wallet_dir"));
|
||||
assert!(!serialized.contains("url"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_and_diagnostic_projections_never_serialize_secret_canaries() {
|
||||
let mut profile = active_fixture_profile();
|
||||
profile.database.postgres.url =
|
||||
"postgres://operator:POSTGRES-SECRET-CANARY@localhost/solana".to_string();
|
||||
profile.database.sqlite.path = "/private/SQLITE-PATH-CANARY.sqlite".to_string();
|
||||
profile.wallet.wallet_dir = "/private/WALLET-PATH-CANARY".to_string();
|
||||
if let std::option::Option::Some(endpoint) = profile.solana.http_endpoints.first_mut() {
|
||||
endpoint.url = "https://provider.invalid/?api-key=HELIUS-SECRET-CANARY".to_string();
|
||||
}
|
||||
if let std::option::Option::Some(endpoint) = profile.solana.ws_endpoints.first_mut() {
|
||||
endpoint.url = "wss://provider.invalid/WS-SECRET-CANARY".to_string();
|
||||
}
|
||||
let public = super::public_config_payload(&profile, &desktop_fixture());
|
||||
let diagnostics = super::DemoConfigDiagnosticPayload {
|
||||
sources: std::vec![],
|
||||
endpoints: profile
|
||||
.solana
|
||||
.http_endpoints
|
||||
.iter()
|
||||
.map(|endpoint| {
|
||||
return super::DemoConfigEndpointDiagnosticPayload {
|
||||
kind: "http".to_string(),
|
||||
name: endpoint.name.clone(),
|
||||
enabled: endpoint.enabled,
|
||||
provider: endpoint.provider.clone(),
|
||||
cluster: endpoint.cluster.clone(),
|
||||
url_state: super::configured_state(endpoint.url.as_str()),
|
||||
connect_timeout_ms: endpoint.connect_timeout_ms,
|
||||
request_timeout_ms: endpoint.request_timeout_ms,
|
||||
unsubscribe_timeout_ms: std::option::Option::None,
|
||||
auto_reconnect: std::option::Option::None,
|
||||
role_count: super::bounded_len(endpoint.roles.len()),
|
||||
};
|
||||
})
|
||||
.collect::<std::vec::Vec<super::DemoConfigEndpointDiagnosticPayload>>(),
|
||||
store: super::DemoConfigStoreDiagnosticPayload {
|
||||
enabled: profile.database.enabled,
|
||||
backend: profile.database.backend.clone(),
|
||||
postgres_url_state: super::configured_state(profile.database.postgres.url.as_str()),
|
||||
postgres_max_connections: profile.database.postgres.max_connections,
|
||||
postgres_connect_timeout_ms: profile.database.postgres.connect_timeout_ms,
|
||||
postgres_auto_initialize_schema: profile.database.postgres.auto_initialize_schema,
|
||||
sqlite_path_state: super::configured_state(profile.database.sqlite.path.as_str()),
|
||||
sqlite_max_connections: profile.database.sqlite.max_connections,
|
||||
sqlite_auto_initialize_schema: profile.database.sqlite.auto_initialize_schema,
|
||||
},
|
||||
wallet: super::DemoConfigWalletDiagnosticPayload {
|
||||
wallet_directory_state: super::configured_state(profile.wallet.wallet_dir.as_str()),
|
||||
cluster: profile.wallet.cluster.clone(),
|
||||
temporary_wallet_enabled: profile.wallet.temporary_wallet_enabled,
|
||||
temporary_wallet_alias: profile.wallet.temporary_wallet_alias.clone(),
|
||||
temporary_wallet_persist: profile.wallet.temporary_wallet_persist,
|
||||
},
|
||||
execution: super::execution_diagnostic(&profile.execution),
|
||||
};
|
||||
let serialized = match serde_json::to_string(&(public, diagnostics)) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("safe projections must serialize: {error}"),
|
||||
};
|
||||
for secret in [
|
||||
"POSTGRES-SECRET-CANARY",
|
||||
"SQLITE-PATH-CANARY",
|
||||
"WALLET-PATH-CANARY",
|
||||
"HELIUS-SECRET-CANARY",
|
||||
"WS-SECRET-CANARY",
|
||||
] {
|
||||
assert!(!serialized.contains(secret));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_http.rs
|
||||
// version: 14
|
||||
// version: 16
|
||||
|
||||
//! HTTP JSON-RPC demo commands.
|
||||
|
||||
@@ -53,6 +53,24 @@ pub(crate) struct DemoHttpOptionsPayload {
|
||||
pub(crate) methods: std::vec::Vec<crate::DemoHttpMethodOption>,
|
||||
}
|
||||
|
||||
/// UI-safe snapshot of one configured HTTP endpoint.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_http/DemoHttpEndpointPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoHttpEndpointPayload {
|
||||
/// Logical endpoint name.
|
||||
pub(crate) endpoint_name: std::string::String,
|
||||
/// Provider name.
|
||||
pub(crate) provider: std::string::String,
|
||||
/// UI-safe role and local-limit snapshots.
|
||||
pub(crate) roles: std::vec::Vec<crate::DemoEndpointRolePayload>,
|
||||
/// Local endpoint status.
|
||||
pub(crate) status: std::string::String,
|
||||
}
|
||||
|
||||
/// Request payload for one HTTP JSON-RPC demo execution.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -85,8 +103,6 @@ pub(crate) struct DemoHttpExecutionPayload {
|
||||
pub(crate) endpoint_name: std::string::String,
|
||||
/// Selected provider name.
|
||||
pub(crate) provider: std::string::String,
|
||||
/// Selected endpoint URL.
|
||||
pub(crate) endpoint_url: std::string::String,
|
||||
/// Required role used by the selection.
|
||||
pub(crate) role: std::string::String,
|
||||
/// JSON-RPC method name.
|
||||
@@ -146,7 +162,6 @@ pub(crate) async fn demo_http_execute_request(
|
||||
return std::result::Result::Ok(crate::DemoHttpExecutionPayload {
|
||||
endpoint_name: selected_client.endpoint_name().to_string(),
|
||||
provider: selected_client.provider().to_string(),
|
||||
endpoint_url: selected_client.endpoint_url().to_string(),
|
||||
role,
|
||||
request_kind: ks_onchain_transport::request_kind_from_method(&method),
|
||||
method,
|
||||
@@ -157,8 +172,13 @@ pub(crate) async fn demo_http_execute_request(
|
||||
|
||||
pub(crate) fn demo_http_list_pool_clients(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::vec::Vec<ks_onchain_transport::HttpPoolClientSnapshot> {
|
||||
return state.http_pool().snapshot();
|
||||
) -> std::vec::Vec<crate::DemoHttpEndpointPayload> {
|
||||
let snapshots = state.http_pool().snapshot();
|
||||
let mut endpoints = std::vec::Vec::new();
|
||||
for snapshot in snapshots {
|
||||
endpoints.push(http_endpoint_payload(snapshot));
|
||||
}
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
pub(crate) fn demo_http_options(
|
||||
@@ -170,6 +190,21 @@ pub(crate) fn demo_http_options(
|
||||
};
|
||||
}
|
||||
|
||||
fn http_endpoint_payload(
|
||||
snapshot: ks_onchain_transport::HttpPoolClientSnapshot,
|
||||
) -> crate::DemoHttpEndpointPayload {
|
||||
let mut roles = std::vec::Vec::new();
|
||||
for role in snapshot.roles {
|
||||
roles.push(crate::DemoEndpointRolePayload::from_snapshot(role));
|
||||
}
|
||||
return crate::DemoHttpEndpointPayload {
|
||||
endpoint_name: snapshot.endpoint_name,
|
||||
provider: snapshot.provider,
|
||||
roles,
|
||||
status: snapshot.status,
|
||||
};
|
||||
}
|
||||
|
||||
fn build_http_role_options(
|
||||
snapshots: std::vec::Vec<ks_onchain_transport::HttpPoolClientSnapshot>,
|
||||
) -> std::vec::Vec<crate::DemoHttpRoleOption> {
|
||||
@@ -412,4 +447,23 @@ mod tests {
|
||||
}
|
||||
assert!(!options.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn endpoint_payload_never_serializes_resolved_url() {
|
||||
let snapshot = ks_onchain_transport::HttpPoolClientSnapshot {
|
||||
endpoint_name: "helius".to_string(),
|
||||
provider: "helius".to_string(),
|
||||
roles: std::vec::Vec::new(),
|
||||
status: "idle".to_string(),
|
||||
};
|
||||
let payload = super::http_endpoint_payload(snapshot);
|
||||
let serialized = match serde_json::to_string(&payload) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
panic!("cannot serialize HTTP endpoint payload: {error}")
|
||||
},
|
||||
};
|
||||
assert!(!serialized.contains("HTTP-SECRET-CANARY"));
|
||||
assert!(!serialized.contains("endpointUrl"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_spl_token_2022.rs
|
||||
// version: 14
|
||||
// version: 15
|
||||
|
||||
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
|
||||
|
||||
@@ -13,8 +13,6 @@ use ts_rs::TS; // rust-rules: derive-import
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_spl_token_2022/DemoSplToken2022FixturePayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoSplToken2022FixturePayload {
|
||||
/// Fixture file used by the application.
|
||||
pub(crate) fixture_path: std::string::String,
|
||||
/// Token-2022 program ID.
|
||||
pub(crate) program_id: std::string::String,
|
||||
/// Mint account.
|
||||
@@ -231,11 +229,10 @@ pub(crate) fn demo_spl_token_2022_fixture(
|
||||
let fixture_path = wallet_dir.join("spl_token_2022_validation").join("fixture.env");
|
||||
let contents = match std::fs::read_to_string(&fixture_path) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(format!(
|
||||
"unable to read Token-2022 fixture {}: {error}",
|
||||
fixture_path.display()
|
||||
));
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(
|
||||
"unable to read configured Token-2022 fixture".to_string(),
|
||||
);
|
||||
},
|
||||
};
|
||||
let values = parse_fixture(contents.as_str());
|
||||
@@ -292,7 +289,6 @@ pub(crate) fn demo_spl_token_2022_fixture(
|
||||
},
|
||||
};
|
||||
return std::result::Result::Ok(crate::DemoSplToken2022FixturePayload {
|
||||
fixture_path: fixture_path.display().to_string(),
|
||||
program_id,
|
||||
mint,
|
||||
source,
|
||||
@@ -581,6 +577,31 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fixture_payload_never_serializes_wallet_path() {
|
||||
let payload = crate::DemoSplToken2022FixturePayload {
|
||||
program_id: "program".to_string(),
|
||||
mint: "mint".to_string(),
|
||||
source: "source".to_string(),
|
||||
destination: "destination".to_string(),
|
||||
close_account: "close".to_string(),
|
||||
delegate: "delegate".to_string(),
|
||||
authority: "authority".to_string(),
|
||||
freeze_authority: "freeze".to_string(),
|
||||
decimals: 9,
|
||||
mint_amount_raw: "1".to_string(),
|
||||
transfer_amount_raw: "1".to_string(),
|
||||
approve_amount_raw: "1".to_string(),
|
||||
burn_amount_raw: "1".to_string(),
|
||||
};
|
||||
let serialized = serde_json::to_string(&payload);
|
||||
assert!(serialized.is_ok());
|
||||
if let std::result::Result::Ok(json) = serialized {
|
||||
assert!(!json.contains("fixturePath"));
|
||||
assert!(!json.contains("WALLET-PATH-CANARY"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_public_scenario_builds_one_typed_operation() {
|
||||
for scenario in [
|
||||
|
||||
52
kb-app-demo-desktop/src/demo_transport.rs
Normal file
52
kb-app-demo-desktop/src/demo_transport.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
// file: kb-app-demo-desktop/src/demo_transport.rs
|
||||
// version: 1
|
||||
|
||||
//! UI-safe transport DTOs shared by HTTP and WebSocket demo surfaces.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// UI-safe endpoint role and local routing limits.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_transport/DemoEndpointRolePayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoEndpointRolePayload {
|
||||
/// Role code used by endpoint pools.
|
||||
pub(crate) role: std::string::String,
|
||||
/// Whether the role is enabled.
|
||||
pub(crate) enabled: bool,
|
||||
/// Request or subscription kinds handled by this role.
|
||||
pub(crate) request_kinds: std::vec::Vec<std::string::String>,
|
||||
/// Role priority where lower values are preferred.
|
||||
pub(crate) priority: u32,
|
||||
/// Requests per second allowed for this role.
|
||||
pub(crate) requests_per_second: u32,
|
||||
/// Burst capacity allowed for this role.
|
||||
pub(crate) burst_capacity: u32,
|
||||
/// Maximum concurrent requests allowed for this role.
|
||||
pub(crate) max_concurrent_requests: u32,
|
||||
/// Maximum subscriptions allowed for this role.
|
||||
pub(crate) max_subscriptions: u32,
|
||||
/// Pause after a rate limit response in milliseconds.
|
||||
#[ts(type = "number")]
|
||||
pub(crate) pause_after_rate_limit_ms: u64,
|
||||
}
|
||||
|
||||
impl crate::DemoEndpointRolePayload {
|
||||
/// Converts one backend transport role snapshot into the UI-safe application contract.
|
||||
pub(crate) fn from_snapshot(snapshot: ks_onchain_transport::EndpointRoleSnapshot) -> Self {
|
||||
return Self {
|
||||
role: snapshot.role,
|
||||
enabled: snapshot.enabled,
|
||||
request_kinds: snapshot.request_kinds,
|
||||
priority: snapshot.priority,
|
||||
requests_per_second: snapshot.requests_per_second,
|
||||
burst_capacity: snapshot.burst_capacity,
|
||||
max_concurrent_requests: snapshot.max_concurrent_requests,
|
||||
max_subscriptions: snapshot.max_subscriptions,
|
||||
pause_after_rate_limit_ms: snapshot.pause_after_rate_limit_ms,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_ws.rs
|
||||
// version: 20
|
||||
// version: 23
|
||||
|
||||
//! Standard Solana WebSocket demo commands backed by `ks_onchain_transport::WsSession`.
|
||||
|
||||
@@ -59,6 +59,24 @@ pub(crate) struct DemoWsOptionsPayload {
|
||||
pub(crate) methods: std::vec::Vec<crate::DemoWsMethodOption>,
|
||||
}
|
||||
|
||||
/// UI-safe snapshot of one configured WebSocket endpoint.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_ws/DemoWsEndpointPayload.ts"
|
||||
)]
|
||||
pub(crate) struct DemoWsEndpointPayload {
|
||||
/// Logical endpoint name.
|
||||
pub(crate) endpoint_name: std::string::String,
|
||||
/// Provider name.
|
||||
pub(crate) provider: std::string::String,
|
||||
/// UI-safe role and local-limit snapshots.
|
||||
pub(crate) roles: std::vec::Vec<crate::DemoEndpointRolePayload>,
|
||||
/// Local endpoint status.
|
||||
pub(crate) status: std::string::String,
|
||||
}
|
||||
|
||||
/// Request payload for one WebSocket subscription demo command.
|
||||
#[derive(Clone, Debug, serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -91,8 +109,6 @@ pub(crate) struct DemoWsExecutionPayload {
|
||||
pub(crate) endpoint_name: std::string::String,
|
||||
/// Selected provider name.
|
||||
pub(crate) provider: std::string::String,
|
||||
/// Selected endpoint URL.
|
||||
pub(crate) endpoint_url: std::string::String,
|
||||
/// Required role used by the selection.
|
||||
pub(crate) role: std::string::String,
|
||||
/// JSON-RPC method name.
|
||||
@@ -120,8 +136,6 @@ pub(crate) struct DemoWsStatusPayload {
|
||||
pub(crate) connected: bool,
|
||||
/// Selected endpoint name.
|
||||
pub(crate) endpoint_name: std::option::Option<std::string::String>,
|
||||
/// Selected endpoint URL.
|
||||
pub(crate) endpoint_url: std::option::Option<std::string::String>,
|
||||
/// Last subscription method.
|
||||
pub(crate) method: std::option::Option<std::string::String>,
|
||||
/// Last unsubscribe method.
|
||||
@@ -230,7 +244,6 @@ pub(crate) async fn demo_ws_connect(
|
||||
return std::result::Result::Ok(crate::DemoWsExecutionPayload {
|
||||
endpoint_name: selected_client.endpoint_name().to_string(),
|
||||
provider: selected_client.provider().to_string(),
|
||||
endpoint_url: selected_client.endpoint_url().to_string(),
|
||||
role,
|
||||
method: method.clone(),
|
||||
request_kind: ks_onchain_transport::request_kind_from_method(&method),
|
||||
@@ -259,15 +272,17 @@ pub(crate) async fn demo_ws_status(
|
||||
/// Lists WebSocket endpoints available through the configured pool.
|
||||
pub(crate) fn demo_ws_list_pool_clients(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<
|
||||
std::vec::Vec<ks_onchain_transport::WsPoolClientSnapshot>,
|
||||
std::string::String,
|
||||
> {
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoWsEndpointPayload>, std::string::String> {
|
||||
let pool = match state.demo_ws_pool() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
return std::result::Result::Ok(pool.snapshot());
|
||||
let snapshots = pool.snapshot();
|
||||
let mut endpoints = std::vec::Vec::new();
|
||||
for snapshot in snapshots {
|
||||
endpoints.push(ws_endpoint_payload(snapshot));
|
||||
}
|
||||
return std::result::Result::Ok(endpoints);
|
||||
}
|
||||
|
||||
/// Lists selectable WebSocket roles and methods for the demo UI.
|
||||
@@ -393,7 +408,7 @@ async fn ensure_session(
|
||||
if let std::option::Option::Some(session) = existing {
|
||||
let snapshot = session.snapshot().await;
|
||||
if snapshot.state != ks_onchain_transport::WsSessionState::Disconnected {
|
||||
if snapshot.endpoint_url != selected_client.endpoint_url() {
|
||||
if snapshot.endpoint_name.as_str() != selected_client.endpoint_name() {
|
||||
return std::result::Result::Err(format!(
|
||||
"demo WebSocket session already uses endpoint '{}'; disconnect before selecting '{}'",
|
||||
snapshot.endpoint_name,
|
||||
@@ -569,7 +584,6 @@ fn status_from_snapshot(
|
||||
return crate::DemoWsStatusPayload {
|
||||
connected: snapshot.state != ks_onchain_transport::WsSessionState::Disconnected,
|
||||
endpoint_name: std::option::Option::Some(snapshot.endpoint_name),
|
||||
endpoint_url: std::option::Option::Some(snapshot.endpoint_url),
|
||||
method,
|
||||
unsubscribe_method,
|
||||
subscription_id,
|
||||
@@ -585,7 +599,6 @@ fn disconnected_status() -> crate::DemoWsStatusPayload {
|
||||
return crate::DemoWsStatusPayload {
|
||||
connected: false,
|
||||
endpoint_name: std::option::Option::None,
|
||||
endpoint_url: std::option::Option::None,
|
||||
method: std::option::Option::None,
|
||||
unsubscribe_method: std::option::Option::None,
|
||||
subscription_id: std::option::Option::None,
|
||||
@@ -594,6 +607,21 @@ fn disconnected_status() -> crate::DemoWsStatusPayload {
|
||||
};
|
||||
}
|
||||
|
||||
fn ws_endpoint_payload(
|
||||
snapshot: ks_onchain_transport::WsPoolClientSnapshot,
|
||||
) -> crate::DemoWsEndpointPayload {
|
||||
let mut roles = std::vec::Vec::new();
|
||||
for role in snapshot.roles {
|
||||
roles.push(crate::DemoEndpointRolePayload::from_snapshot(role));
|
||||
}
|
||||
return crate::DemoWsEndpointPayload {
|
||||
endpoint_name: snapshot.endpoint_name,
|
||||
provider: snapshot.provider,
|
||||
roles,
|
||||
status: snapshot.status,
|
||||
};
|
||||
}
|
||||
|
||||
fn build_ws_role_options(
|
||||
snapshots: std::vec::Vec<ks_onchain_transport::WsPoolClientSnapshot>,
|
||||
) -> std::vec::Vec<crate::DemoWsRoleOption> {
|
||||
@@ -957,10 +985,46 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn endpoint_and_status_payloads_never_serialize_resolved_url() {
|
||||
let endpoint_snapshot = ks_onchain_transport::WsPoolClientSnapshot {
|
||||
endpoint_name: "helius".to_string(),
|
||||
provider: "helius".to_string(),
|
||||
roles: std::vec::Vec::new(),
|
||||
status: "idle".to_string(),
|
||||
};
|
||||
let endpoint_payload = super::ws_endpoint_payload(endpoint_snapshot);
|
||||
let endpoint_json = match serde_json::to_string(&endpoint_payload) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
panic!("cannot serialize WebSocket endpoint payload: {error}")
|
||||
},
|
||||
};
|
||||
assert!(!endpoint_json.contains("WS-SECRET-CANARY"));
|
||||
assert!(!endpoint_json.contains("endpointUrl"));
|
||||
let status = super::status_from_snapshot(ks_onchain_transport::WsSessionSnapshot {
|
||||
endpoint_name: "helius".to_string(),
|
||||
provider: "helius".to_string(),
|
||||
state: ks_onchain_transport::WsSessionState::Connected,
|
||||
reconnect_count: 0,
|
||||
capabilities: ks_onchain_transport::StandardWsCapabilities::default(),
|
||||
subscriptions: std::vec::Vec::new(),
|
||||
});
|
||||
let status_json = match serde_json::to_string(&status) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
panic!("cannot serialize WebSocket status payload: {error}")
|
||||
},
|
||||
};
|
||||
assert!(!status_json.contains("WS-SECRET-CANARY"));
|
||||
assert!(!status_json.contains("endpointUrl"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tauri_numeric_bindings_use_json_compatible_numbers() {
|
||||
let config = ts_rs::Config::default();
|
||||
let declarations = [
|
||||
<crate::DemoEndpointRolePayload as TS>::decl(&config),
|
||||
<crate::DemoWsExecutionPayload as TS>::decl(&config),
|
||||
<crate::DemoWsStatusPayload as TS>::decl(&config),
|
||||
<crate::DemoWsSubscriptionStatusPayload as TS>::decl(&config),
|
||||
|
||||
96
kb-app-demo-desktop/src/desktop_config.rs
Normal file
96
kb-app-demo-desktop/src/desktop_config.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
// file: kb-app-demo-desktop/src/desktop_config.rs
|
||||
// version: 2
|
||||
|
||||
//! Desktop-owned application settings embedded in one generic binary composition profile.
|
||||
|
||||
const DESKTOP_APPLICATION_SCHEMA: &str =
|
||||
include_str!("../../config/schemas/kb-app-demo-desktop.application.config.schema.json");
|
||||
|
||||
/// Desktop-owned settings for one selected composition profile.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub(crate) struct DesktopApplicationConfig {
|
||||
/// Application display/name identity.
|
||||
pub(crate) name: std::string::String,
|
||||
/// Human-readable environment classification.
|
||||
pub(crate) environment: std::string::String,
|
||||
/// Desktop demo feature flags.
|
||||
pub(crate) demo: crate::DesktopDemoConfig,
|
||||
}
|
||||
|
||||
/// Desktop-owned demo feature flags.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub(crate) struct DesktopDemoConfig {
|
||||
/// Enables live demo pages.
|
||||
pub(crate) live_demo_enabled: bool,
|
||||
/// Enables trading demo pages.
|
||||
pub(crate) trading_demo_enabled: bool,
|
||||
}
|
||||
|
||||
/// Parses and validates desktop-owned settings from one generic composition profile.
|
||||
pub(crate) fn desktop_application_config(
|
||||
profile: &ks_config::CompositionProfileConfig,
|
||||
) -> ks_core::Result<DesktopApplicationConfig> {
|
||||
let application = match profile.application.as_ref() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(ks_core::Error::new(
|
||||
"desktop_application_config_missing",
|
||||
profile.name.clone(),
|
||||
));
|
||||
},
|
||||
};
|
||||
match ks_config::validate_json_value_against_schema(
|
||||
DESKTOP_APPLICATION_SCHEMA,
|
||||
application,
|
||||
"desktop_application_config_schema_validation_failed",
|
||||
) {
|
||||
std::result::Result::Ok(()) => (),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
return match serde_json::from_value::<DesktopApplicationConfig>(application.clone()) {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(_) => std::result::Result::Err(ks_core::Error::new(
|
||||
"desktop_application_config_decode_failed",
|
||||
"desktop application configuration could not be decoded",
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn default_composition_contains_valid_desktop_application_fragments() {
|
||||
let raw = include_str!("../../config/kb-app-demo-desktop.default.config.json");
|
||||
let composition = match ks_config::parse_composition_json(raw) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("default composition must parse: {error}"),
|
||||
};
|
||||
for profile in &composition.profiles {
|
||||
let result = super::desktop_application_config(profile);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_fragment_rejects_unknown_or_missing_fields() {
|
||||
let profile = ks_config::CompositionProfileConfig {
|
||||
name: "test".to_string(),
|
||||
application: std::option::Option::Some(serde_json::json!({
|
||||
"name": "kb-app-demo-desktop",
|
||||
"environment": "test",
|
||||
"demo": {
|
||||
"live_demo_enabled": true,
|
||||
"trading_demo_enabled": false
|
||||
},
|
||||
"secret": "must-not-be-accepted"
|
||||
})),
|
||||
logging_profile: std::option::Option::None,
|
||||
transport_profile: std::option::Option::None,
|
||||
listeners_profile: std::option::Option::None,
|
||||
store_profile: std::option::Option::None,
|
||||
wallet_profile: std::option::Option::None,
|
||||
execution_profile: std::option::Option::None,
|
||||
};
|
||||
assert!(super::desktop_application_config(&profile).is_err());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/lib.rs
|
||||
// version: 34
|
||||
// version: 37
|
||||
|
||||
//! Tauri desktop demo application for `khadhroony-bot3`.
|
||||
|
||||
@@ -29,7 +29,9 @@ mod demo_sql_diag;
|
||||
mod demo_sql_pg_core;
|
||||
mod demo_sql_pg_raw;
|
||||
mod demo_sql_replay_candidates;
|
||||
mod demo_transport;
|
||||
mod demo_ws;
|
||||
mod desktop_config;
|
||||
mod frontend_log;
|
||||
mod main_window;
|
||||
mod splash;
|
||||
@@ -64,8 +66,28 @@ pub(crate) use self::demo_backfill::demo_backfill_cancel;
|
||||
pub(crate) use self::demo_backfill::demo_backfill_execute;
|
||||
/// Returns endpoint roles, known programs and conservative backfill defaults.
|
||||
pub(crate) use self::demo_backfill::demo_backfill_options;
|
||||
/// Bounded internal configuration diagnostics shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigDiagnosticPayload;
|
||||
/// Bounded endpoint diagnostics shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigEndpointDiagnosticPayload;
|
||||
/// Bounded execution diagnostics shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigExecutionDiagnosticPayload;
|
||||
/// Configuration payload shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigPayload;
|
||||
/// Public desktop demo feature flags.
|
||||
pub(crate) use self::demo_config::DemoConfigPublicDemoPayload;
|
||||
/// Public endpoint identity shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigPublicEndpointPayload;
|
||||
/// Public listener summary shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigPublicListenersPayload;
|
||||
/// Explicit public configuration projection shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigPublicPayload;
|
||||
/// Bounded source-path diagnostics shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigSourceDiagnosticPayload;
|
||||
/// Bounded store diagnostics shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigStoreDiagnosticPayload;
|
||||
/// Bounded wallet diagnostics shown by the configuration window.
|
||||
pub(crate) use self::demo_config::DemoConfigWalletDiagnosticPayload;
|
||||
/// Builds the configuration payload from shared application state.
|
||||
pub(crate) use self::demo_config::demo_config_payload;
|
||||
/// Internal demo core extraction item.
|
||||
@@ -228,6 +250,8 @@ pub(crate) use self::demo_execution_solana_core::select_devnet_profile;
|
||||
pub(crate) use self::demo_execution_spl::DevnetSplValidationScenarioPayload;
|
||||
/// Returns the complete ordered Devnet SPL validation inventory for milestone 0.4.6.
|
||||
pub(crate) use self::demo_execution_spl::demo_execution_spl_validation_scenarios;
|
||||
/// UI-safe HTTP endpoint snapshot.
|
||||
pub(crate) use self::demo_http::DemoHttpEndpointPayload;
|
||||
/// HTTP demo response payload.
|
||||
pub(crate) use self::demo_http::DemoHttpExecutionPayload;
|
||||
/// One selectable HTTP JSON-RPC method.
|
||||
@@ -336,6 +360,10 @@ pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_entities;
|
||||
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_programs;
|
||||
/// Loads bounded transaction replay candidates from PostgreSQL.
|
||||
pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_transactions;
|
||||
/// UI-safe endpoint role shared by HTTP and WebSocket diagnostics.
|
||||
pub(crate) use self::demo_transport::DemoEndpointRolePayload;
|
||||
/// UI-safe WebSocket endpoint snapshot.
|
||||
pub(crate) use self::demo_ws::DemoWsEndpointPayload;
|
||||
/// WebSocket execution response payload.
|
||||
pub(crate) use self::demo_ws::DemoWsExecutionPayload;
|
||||
/// WebSocket message emitted to the frontend.
|
||||
@@ -368,6 +396,12 @@ pub(crate) use self::demo_ws::demo_ws_status;
|
||||
pub(crate) use self::demo_ws::demo_ws_unsubscribe;
|
||||
/// Disconnects the persistent WebSocket session during application shutdown.
|
||||
pub(crate) use self::demo_ws::disconnect_demo_ws_app_state;
|
||||
/// Desktop-owned application settings selected from the active composition profile.
|
||||
pub(crate) use self::desktop_config::DesktopApplicationConfig;
|
||||
/// Desktop-owned demo feature flags.
|
||||
pub(crate) use self::desktop_config::DesktopDemoConfig;
|
||||
/// Parses desktop-owned settings from one composition profile.
|
||||
pub(crate) use self::desktop_config::desktop_application_config;
|
||||
/// Frontend logging payload.
|
||||
pub(crate) use self::frontend_log::FrontendLogPayload;
|
||||
/// Emits one normalized frontend log event.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/tauri.rs
|
||||
// version: 37
|
||||
// version: 38
|
||||
|
||||
//! Tauri runtime assembly and private command wrappers.
|
||||
|
||||
@@ -257,7 +257,7 @@ fn open_demo_http_window(
|
||||
#[tauri::command]
|
||||
fn demo_http_list_pool_clients(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::vec::Vec<ks_onchain_transport::HttpPoolClientSnapshot> {
|
||||
) -> std::vec::Vec<crate::DemoHttpEndpointPayload> {
|
||||
return crate::demo_http_list_pool_clients(state);
|
||||
}
|
||||
|
||||
@@ -284,10 +284,7 @@ fn open_demo_ws_window(
|
||||
#[tauri::command]
|
||||
fn demo_ws_list_pool_clients(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<
|
||||
std::vec::Vec<ks_onchain_transport::WsPoolClientSnapshot>,
|
||||
std::string::String,
|
||||
> {
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoWsEndpointPayload>, std::string::String> {
|
||||
return crate::demo_ws_list_pool_clients(state);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user