This commit is contained in:
2026-07-23 16:37:12 +02:00
parent 99c345f2f2
commit 0da75c1311
2159 changed files with 230833 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
// file: kb_app_demo/src/app_state.rs
// version: 11
//! Shared Tauri application state and startup state initialization.
/// Shared state managed by Tauri for the demo application.
pub(crate) struct AppState {
config_path: std::string::String,
app_config: kb_config::AppConfig,
active_profile: kb_config::ProfileConfig,
logging_guard: std::sync::Mutex<kb_logging::LoggingGuard>,
http_pool: kb_rpc::HttpEndpointPool,
ws_pool: kb_rpc::WsEndpointPool,
demo_ws_session: tokio::sync::Mutex<std::option::Option<std::sync::Arc<kb_rpc::WsSession>>>,
demo_backfill_running: std::sync::atomic::AtomicBool,
demo_backfill_cancel_requested: std::sync::atomic::AtomicBool,
demo_core_extraction_running: std::sync::atomic::AtomicBool,
demo_core_extraction_cancel_requested: std::sync::atomic::AtomicBool,
demo_decode_replay_running: std::sync::atomic::AtomicBool,
demo_decode_replay_cancel_requested: std::sync::atomic::AtomicBool,
demo_decode_replay_campaign_id: std::sync::Mutex<std::option::Option<std::string::String>>,
demo_execution_solana_core_running: std::sync::atomic::AtomicBool,
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool,
}
impl crate::AppState {
/// Initializes configuration, logging and shared runtime state.
pub(crate) fn initialize() -> kb_core::Result<crate::AppState> {
let config_path = resolve_config_path();
let app_config = match kb_config::read_config_json_file(&config_path) {
std::result::Result::Ok(config) => config,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let active_profile = match kb_config::active_profile(&app_config) {
std::result::Result::Ok(profile) => profile.clone(),
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let logging_config = convert_logging_config(&active_profile.logging);
let logging_guard = match kb_logging::init_logging(&logging_config) {
std::result::Result::Ok(guard) => guard,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let http_pool = match kb_rpc::HttpEndpointPool::from_profile(&active_profile) {
std::result::Result::Ok(pool) => pool,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let ws_pool = match kb_rpc::WsEndpointPool::from_profile(&active_profile) {
std::result::Result::Ok(pool) => pool,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::AppState {
config_path: config_path.display().to_string(),
app_config,
active_profile,
logging_guard: std::sync::Mutex::new(logging_guard),
http_pool,
ws_pool,
demo_ws_session: tokio::sync::Mutex::new(std::option::Option::None),
demo_backfill_running: std::sync::atomic::AtomicBool::new(false),
demo_backfill_cancel_requested: std::sync::atomic::AtomicBool::new(false),
demo_core_extraction_running: std::sync::atomic::AtomicBool::new(false),
demo_core_extraction_cancel_requested: std::sync::atomic::AtomicBool::new(false),
demo_decode_replay_running: std::sync::atomic::AtomicBool::new(false),
demo_decode_replay_cancel_requested: std::sync::atomic::AtomicBool::new(false),
demo_decode_replay_campaign_id: std::sync::Mutex::new(std::option::Option::None),
demo_execution_solana_core_running: std::sync::atomic::AtomicBool::new(false),
demo_execution_solana_core_cancel_requested: std::sync::atomic::AtomicBool::new(false),
});
}
/// Returns the path used to load the configuration file.
pub(crate) fn config_path(&self) -> &str {
return self.config_path.as_str();
}
/// Returns the complete parsed application configuration.
pub(crate) fn app_config(&self) -> &kb_config::AppConfig {
return &self.app_config;
}
/// Returns the active profile selected from the configuration.
pub(crate) fn active_profile(&self) -> &kb_config::ProfileConfig {
return &self.active_profile;
}
/// Returns the configured HTTP endpoint pool.
pub(crate) fn http_pool(&self) -> &kb_rpc::HttpEndpointPool {
return &self.http_pool;
}
/// Returns the configured WebSocket endpoint pool.
pub(crate) fn ws_pool(&self) -> &kb_rpc::WsEndpointPool {
return &self.ws_pool;
}
/// Returns the demo WebSocket runtime session slot.
pub(crate) fn demo_ws_session(
&self,
) -> &tokio::sync::Mutex<std::option::Option<std::sync::Arc<kb_rpc::WsSession>>> {
return &self.demo_ws_session;
}
/// Returns the single-campaign execution flag used by the backfill demo.
pub(crate) fn demo_backfill_running(&self) -> &std::sync::atomic::AtomicBool {
return &self.demo_backfill_running;
}
/// Returns the cooperative cancellation flag used by the backfill demo.
pub(crate) fn demo_backfill_cancel_requested(&self) -> &std::sync::atomic::AtomicBool {
return &self.demo_backfill_cancel_requested;
}
/// Returns the single-campaign execution flag used by the core extraction demo.
pub(crate) fn demo_core_extraction_running(&self) -> &std::sync::atomic::AtomicBool {
return &self.demo_core_extraction_running;
}
/// Returns the cooperative cancellation flag used by the core extraction demo.
pub(crate) fn demo_core_extraction_cancel_requested(&self) -> &std::sync::atomic::AtomicBool {
return &self.demo_core_extraction_cancel_requested;
}
/// Returns the single-campaign execution flag used by the decode replay demo.
pub(crate) fn demo_decode_replay_running(&self) -> &std::sync::atomic::AtomicBool {
return &self.demo_decode_replay_running;
}
/// Returns the cooperative cancellation flag used by the decode replay demo.
pub(crate) fn demo_decode_replay_cancel_requested(&self) -> &std::sync::atomic::AtomicBool {
return &self.demo_decode_replay_cancel_requested;
}
/// Returns the active contextual decode campaign identifier slot.
pub(crate) fn demo_decode_replay_campaign_id(
&self,
) -> &std::sync::Mutex<std::option::Option<std::string::String>> {
return &self.demo_decode_replay_campaign_id;
}
/// Returns the single-execution flag used by the Solana Core execution demo.
pub(crate) fn demo_execution_solana_core_running(&self) -> &std::sync::atomic::AtomicBool {
return &self.demo_execution_solana_core_running;
}
/// Returns the cooperative cancellation flag used by the execution demo.
pub(crate) fn demo_execution_solana_core_cancel_requested(
&self,
) -> &std::sync::atomic::AtomicBool {
return &self.demo_execution_solana_core_cancel_requested;
}
/// Returns the number of logging routes held by the logging guard.
pub(crate) fn logging_route_count(&self) -> usize {
let lock_result = self.logging_guard.lock();
let guard = match lock_result {
std::result::Result::Ok(guard) => guard,
std::result::Result::Err(_) => return 0,
};
return guard.route_count();
}
}
fn convert_logging_config(config: &kb_config::LoggingConfig) -> kb_logging::LoggingConfig {
let mut targets = std::vec::Vec::<kb_logging::LogTargetConfig>::new();
for target in &config.targets {
targets.push(kb_logging::LogTargetConfig {
name: target.name.clone(),
enabled: target.enabled,
sink: target.sink.clone(),
level: target.level.clone(),
path: target.path.clone(),
rotation: target.rotation.clone(),
format: target.format.clone(),
ansi: target.ansi,
targets: target.targets.clone(),
});
}
let mut target_filters = std::vec::Vec::<kb_logging::LogTargetFilterConfig>::new();
for filter in &config.target_filters {
target_filters.push(kb_logging::LogTargetFilterConfig {
target: filter.target.clone(),
level: filter.level.clone(),
});
}
return kb_logging::LoggingConfig {
default_level: config.default_level.clone(),
targets,
target_filters,
};
}
fn resolve_config_path() -> std::path::PathBuf {
let configured = std::env::var("KB_CONFIG_PATH").ok();
return resolve_config_path_from_value(configured.as_deref());
}
fn resolve_config_path_from_value(value: std::option::Option<&str>) -> std::path::PathBuf {
if let std::option::Option::Some(configured) = value {
let trimmed = configured.trim();
if !trimmed.is_empty() {
return std::path::PathBuf::from(trimmed);
}
}
return crate::workspace_root_dir().join("config/example.config.json");
}
#[cfg(test)]
mod tests {
#[test]
fn resolve_config_path_uses_workspace_example_by_default() {
let path = super::resolve_config_path_from_value(std::option::Option::None);
assert_eq!(
path.file_name().and_then(std::ffi::OsStr::to_str),
std::option::Option::Some("example.config.json")
);
}
}

View File

@@ -0,0 +1,7 @@
// file: kb_app_demo/src/constants.rs
// version: 1
//! Local constants for the `kb_app_demo` crate.
/// Canonical tracing target for this crate.
pub(crate) const TRACING_TARGET: &str = "kb_app_demo";

View File

@@ -0,0 +1,457 @@
// file: kb_app_demo/src/demo_backfill.rs
// version: 9
//! Tauri commands and UI payloads for bounded HTTP transaction backfills.
use tauri::Emitter; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import
/// One endpoint role capable of discovering and hydrating transaction history.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_backfill/DemoBackfillRoleOption.ts"
)]
pub(crate) struct DemoBackfillRoleOption {
/// Stable endpoint role code.
pub(crate) role: std::string::String,
/// Providers exposing this role.
pub(crate) providers: std::vec::Vec<std::string::String>,
}
/// Initial options shown by the backfill demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_backfill/DemoBackfillOptionsPayload.ts"
)]
pub(crate) struct DemoBackfillOptionsPayload {
/// Selectable endpoint roles.
pub(crate) roles: std::vec::Vec<DemoBackfillRoleOption>,
/// Preferred role when configured.
pub(crate) default_role: std::option::Option<std::string::String>,
/// Default transaction commitment.
pub(crate) default_commitment: std::string::String,
/// Default history page size.
pub(crate) default_page_size: u16,
/// Default maximum number of history pages.
pub(crate) default_max_pages: u32,
/// Default hydration concurrency requested by the operator.
pub(crate) default_max_concurrent_requests: u32,
/// Default retries after the initial attempt.
pub(crate) default_max_retries: u32,
/// Whether one campaign is currently running.
pub(crate) running: bool,
}
/// UI request for one bounded backfill campaign.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_backfill/DemoBackfillRequest.ts"
)]
pub(crate) struct DemoBackfillRequest {
/// Endpoint role used for history and transaction requests.
pub(crate) role: std::string::String,
/// Commitment used by standard Solana HTTP requests.
pub(crate) commitment: std::string::String,
/// Source mode: explicit_signatures, program, token or pool.
pub(crate) mode: std::string::String,
/// Newline-separated signatures for explicit mode.
pub(crate) signatures_text: std::option::Option<std::string::String>,
/// Program, token mint or pool address for address history modes.
pub(crate) address: std::option::Option<std::string::String>,
/// Optional anchor transaction signature. It is required only for newer-history scans.
pub(crate) anchor_signature: std::option::Option<std::string::String>,
/// Direction relative to the anchor, or from the latest entry when `before` has no anchor.
pub(crate) direction: std::option::Option<std::string::String>,
/// Maximum number of address-history signatures to hydrate.
pub(crate) limit: u32,
/// Maximum RPC page size.
pub(crate) page_size: u16,
/// Maximum number of history pages inspected.
pub(crate) max_pages: u32,
/// Operator concurrency cap.
pub(crate) max_concurrent_requests: u32,
/// Retries after the initial HTTP attempt.
pub(crate) max_retries: u32,
}
/// One progress event emitted to the backfill window.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_backfill/DemoBackfillProgressPayload.ts"
)]
pub(crate) struct DemoBackfillProgressPayload {
/// RFC 3339 timestamp.
pub(crate) timestamp: std::string::String,
/// Stable severity code.
pub(crate) level: std::string::String,
/// Human-readable message.
pub(crate) message: std::string::String,
/// Completed candidate count when known.
#[ts(type = "number | null")]
pub(crate) completed: std::option::Option<u64>,
/// Total candidate count when known.
#[ts(type = "number | null")]
pub(crate) total: std::option::Option<u64>,
}
/// Final UI-safe summary for one backfill campaign.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_backfill/DemoBackfillSummaryPayload.ts"
)]
pub(crate) struct DemoBackfillSummaryPayload {
/// Unique capture session identifier.
pub(crate) capture_session_id: std::string::String,
/// Stable filter code.
pub(crate) filter_code: std::string::String,
/// Endpoint role.
pub(crate) role: std::string::String,
/// Provider used for hydration.
pub(crate) provider: std::string::String,
/// Endpoint code used for hydration.
pub(crate) endpoint_code: std::string::String,
/// Number of history pages fetched.
#[ts(type = "number")]
pub(crate) pages_fetched: u64,
/// Number of unique candidates selected.
#[ts(type = "number")]
pub(crate) candidates_selected: u64,
/// Number of candidates admitted to the bounded execution queue.
#[ts(type = "number")]
pub(crate) candidates_started: u64,
/// Number of candidates that reached a terminal outcome.
#[ts(type = "number")]
pub(crate) candidates_completed: u64,
/// Number of admitted candidates interrupted before a terminal outcome.
#[ts(type = "number")]
pub(crate) candidates_cancelled: u64,
/// Number of selected candidates never admitted after cancellation.
#[ts(type = "number")]
pub(crate) candidates_not_started: u64,
/// Number of transactions received and normalized.
#[ts(type = "number")]
pub(crate) transactions_received: u64,
/// Number of canonical rows inserted.
#[ts(type = "number")]
pub(crate) canonical_inserted: u64,
/// Number of canonical inserts skipped by idempotence.
#[ts(type = "number")]
pub(crate) canonical_skipped: u64,
/// Number of signatures skipped before hydration because they already existed.
#[ts(type = "number")]
pub(crate) existing_skipped: u64,
/// Number of missing transactions after retries.
#[ts(type = "number")]
pub(crate) missing: u64,
/// Number of failed candidates.
#[ts(type = "number")]
pub(crate) failed: u64,
/// Number of observation rows inserted.
#[ts(type = "number")]
pub(crate) observations_inserted: u64,
/// Number of source attempts.
#[ts(type = "number")]
pub(crate) attempts: u64,
/// Whether the operator cancelled the campaign.
pub(crate) cancelled: bool,
/// Optional cursor for continuing an older-history scan.
pub(crate) resume_before_signature: std::option::Option<std::string::String>,
/// Campaign start time.
pub(crate) started_at: std::string::String,
/// Campaign completion time.
pub(crate) finished_at: std::string::String,
}
pub(crate) struct DemoBackfillObserver<'a> {
pub(crate) app_handle: tauri::AppHandle,
pub(crate) cancel_requested: &'a std::sync::atomic::AtomicBool,
}
impl kb_pipeline::BackfillObserver for crate::DemoBackfillObserver<'_> {
fn on_progress(&self, event: &kb_pipeline::BackfillProgressEvent) {
let payload = crate::DemoBackfillProgressPayload {
timestamp: event.timestamp.clone(),
level: event.level.code().to_string(),
message: event.message.clone(),
completed: event.completed,
total: event.total,
};
let emit_result =
self.app_handle.emit_to("demo_backfill", "demo-backfill-progress", payload);
if let std::result::Result::Err(error) = emit_result {
tracing::warn!(
target: crate::TRACING_TARGET,
"cannot emit backfill progress: {error}"
);
}
}
fn is_cancelled(&self) -> bool {
return self.cancel_requested.load(std::sync::atomic::Ordering::Relaxed);
}
}
pub(crate) struct DemoBackfillRunGuard<'a> {
pub(crate) running: &'a std::sync::atomic::AtomicBool,
}
impl std::ops::Drop for crate::DemoBackfillRunGuard<'_> {
fn drop(&mut self) {
self.running.store(false, std::sync::atomic::Ordering::Release);
}
}
pub(crate) fn build_role_options(
snapshots: std::vec::Vec<kb_rpc::HttpPoolClientSnapshot>,
) -> std::vec::Vec<DemoBackfillRoleOption> {
let mut role_methods = std::collections::BTreeMap::<
std::string::String,
(
std::collections::BTreeSet<std::string::String>,
std::collections::BTreeSet<std::string::String>,
),
>::new();
for snapshot in snapshots {
for role in snapshot.roles {
if !role.enabled {
continue;
}
let entry = role_methods.entry(role.role).or_default();
for request_kind in role.request_kinds {
entry.0.insert(request_kind);
}
entry.1.insert(snapshot.provider.clone());
}
}
let mut output = std::vec::Vec::new();
for (role, (request_kinds, providers)) in role_methods {
let supports_all = request_kinds.contains("*");
if !supports_all
&& (!request_kinds.contains("get_signatures_for_address")
|| !request_kinds.contains("get_transaction"))
{
continue;
}
output.push(DemoBackfillRoleOption {
role,
providers: providers.into_iter().collect(),
});
}
return output;
}
pub(crate) fn build_demo_backfill_pipeline_request(
request: DemoBackfillRequest,
) -> std::result::Result<kb_pipeline::BackfillRequest, std::string::String> {
let source_result = match request.mode.trim() {
"explicit_signatures" => explicit_source(request.signatures_text.as_deref()),
"program" => address_source(
kb_pipeline::BackfillAddressKind::Program,
request.address.as_deref(),
request.anchor_signature.as_deref(),
request.direction.as_deref(),
request.limit,
),
"token" => address_source(
kb_pipeline::BackfillAddressKind::Token,
request.address.as_deref(),
request.anchor_signature.as_deref(),
request.direction.as_deref(),
request.limit,
),
"pool" => address_source(
kb_pipeline::BackfillAddressKind::Pool,
request.address.as_deref(),
request.anchor_signature.as_deref(),
request.direction.as_deref(),
request.limit,
),
_ => std::result::Result::Err("unsupported backfill mode".to_string()),
};
let source = match source_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let pipeline_request = kb_pipeline::BackfillRequest {
role: request.role,
commitment: request.commitment,
source,
page_size: request.page_size,
max_pages: request.max_pages,
max_concurrent_requests: request.max_concurrent_requests,
max_retries: request.max_retries,
};
let validation_result = pipeline_request.validate();
if let std::result::Result::Err(error) = validation_result {
return std::result::Result::Err(error.to_string());
}
return std::result::Result::Ok(pipeline_request);
}
fn explicit_source(
signatures_text: std::option::Option<&str>,
) -> std::result::Result<kb_pipeline::BackfillSource, std::string::String> {
let text = match signatures_text {
std::option::Option::Some(value) => value,
std::option::Option::None => "",
};
let signatures = text
.lines()
.map(str::trim)
.filter(|value| return !value.is_empty())
.map(str::to_string)
.collect::<std::vec::Vec<_>>();
if signatures.is_empty() {
return std::result::Result::Err(
"the signatures textarea must contain at least one signature".to_string(),
);
}
return std::result::Result::Ok(kb_pipeline::BackfillSource::ExplicitSignatures(signatures));
}
fn address_source(
kind: kb_pipeline::BackfillAddressKind,
address: std::option::Option<&str>,
anchor_signature: std::option::Option<&str>,
direction: std::option::Option<&str>,
limit: u32,
) -> std::result::Result<kb_pipeline::BackfillSource, std::string::String> {
let address_text = match address {
std::option::Option::Some(value) => value,
std::option::Option::None => "",
};
let address_value = address_text.trim();
if address_value.is_empty() {
return std::result::Result::Err("address is required".to_string());
}
let direction_text = match direction {
std::option::Option::Some(value) => value,
std::option::Option::None => "",
};
let direction_value = match direction_text.trim() {
"before" => kb_pipeline::BackfillDirection::Before,
"after" => kb_pipeline::BackfillDirection::After,
_ => {
return std::result::Result::Err("direction must be before or after".to_string());
},
};
let anchor_value = anchor_signature
.map(str::trim)
.filter(|value| return !value.is_empty())
.map(str::to_string);
if direction_value == kb_pipeline::BackfillDirection::After && anchor_value.is_none() {
return std::result::Result::Err(
"anchor signature is required for newer-history backfill".to_string(),
);
}
let limit_value = match usize::try_from(limit) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("signature limit conversion failed: {error}"));
},
};
return std::result::Result::Ok(kb_pipeline::BackfillSource::AddressHistory {
kind,
address: address_value.to_string(),
anchor_signature: anchor_value,
direction: direction_value,
limit: limit_value,
});
}
pub(crate) fn demo_backfill_summary_payload(
summary: kb_pipeline::BackfillSummary,
) -> DemoBackfillSummaryPayload {
return crate::DemoBackfillSummaryPayload {
capture_session_id: summary.capture_session_id,
filter_code: summary.filter_code,
role: summary.role,
provider: summary.provider,
endpoint_code: summary.endpoint_code,
pages_fetched: summary.pages_fetched,
candidates_selected: summary.candidates_selected,
candidates_started: summary.candidates_started,
candidates_completed: summary.candidates_completed,
candidates_cancelled: summary.candidates_cancelled,
candidates_not_started: summary.candidates_not_started,
transactions_received: summary.transactions_received,
canonical_inserted: summary.canonical_inserted,
canonical_skipped: summary.canonical_skipped,
existing_skipped: summary.existing_skipped,
missing: summary.missing,
failed: summary.failed,
observations_inserted: summary.observations_inserted,
attempts: summary.attempts,
cancelled: summary.cancelled,
resume_before_signature: summary.resume_before_signature,
started_at: summary.started_at,
finished_at: summary.finished_at,
};
}
#[cfg(test)]
mod tests {
#[test]
fn explicit_source_splits_lines_and_ignores_empty_rows() {
let result = super::explicit_source(std::option::Option::Some(" first \n\n second\n"));
let source = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("source failed: {error}"),
};
assert_eq!(
source,
kb_pipeline::BackfillSource::ExplicitSignatures(std::vec![
"first".to_string(),
"second".to_string(),
])
);
}
#[test]
fn older_history_accepts_missing_anchor_and_uses_latest_filter() {
let source_result = super::address_source(
kb_pipeline::BackfillAddressKind::Program,
std::option::Option::Some("11111111111111111111111111111111"),
std::option::Option::Some(" "),
std::option::Option::Some("before"),
100,
);
let source = match source_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("source failed: {error}"),
};
let request = kb_pipeline::BackfillRequest {
role: "history_backfill".to_string(),
commitment: "confirmed".to_string(),
source,
page_size: 100,
max_pages: 1,
max_concurrent_requests: 1,
max_retries: 0,
};
assert!(request.validate().is_ok());
assert_eq!(request.filter_code(), "program_latest");
}
#[test]
fn newer_history_rejects_missing_anchor() {
let source_result = super::address_source(
kb_pipeline::BackfillAddressKind::Program,
std::option::Option::Some("11111111111111111111111111111111"),
std::option::Option::None,
std::option::Option::Some("after"),
100,
);
assert!(source_result.is_err());
}
}

View File

@@ -0,0 +1,27 @@
// file: kb_app_demo/src/demo_config.rs
// version: 8
//! Configuration demo commands.
use ts_rs::TS; // rust-rules: derive-import
/// Serializable payload shown by the configuration demo page.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/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.
pub(crate) active_profile_name: std::string::String,
/// Active profile environment.
pub(crate) environment: std::string::String,
/// Entire parsed configuration.
pub(crate) app_config: kb_config::AppConfig,
/// Active profile configuration.
pub(crate) active_profile: kb_config::ProfileConfig,
/// Embedded JSON Schema text used during loading.
pub(crate) schema_json: std::string::String,
}

View File

@@ -0,0 +1,267 @@
// file: kb_app_demo/src/demo_core_extraction.rs
// version: 9
//! Tauri commands and UI payloads for canonical transaction to core extraction.
use tauri::Emitter; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import
/// Initial options shown by the core extraction demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_core_extraction/DemoCoreExtractionOptionsPayload.ts"
)]
pub(crate) struct DemoCoreExtractionOptionsPayload {
/// Current extractor implementation version.
pub(crate) processor_version: std::string::String,
/// Default maximum selected canonical transactions.
pub(crate) default_limit: u32,
/// Default maximum concurrent extractions.
pub(crate) default_max_concurrent_extractions: u32,
/// Whether one extraction campaign is currently running.
pub(crate) running: bool,
}
/// UI request for one bounded core extraction campaign.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_core_extraction/DemoCoreExtractionRequest.ts"
)]
pub(crate) struct DemoCoreExtractionRequest {
/// Source mode: signatures, pending or slot_range.
pub(crate) mode: std::string::String,
/// Newline-separated signatures for exact selection.
pub(crate) signatures_text: std::option::Option<std::string::String>,
/// Optional program id already present in core instructions.
pub(crate) program_id: std::option::Option<std::string::String>,
/// Optional inclusive minimum slot.
#[ts(type = "number | null")]
pub(crate) min_slot: std::option::Option<u64>,
/// Optional inclusive maximum slot.
#[ts(type = "number | null")]
pub(crate) max_slot: std::option::Option<u64>,
/// Maximum selected canonical transactions.
pub(crate) limit: u32,
/// Maximum concurrent extraction operations.
pub(crate) max_concurrent_extractions: u32,
/// Forces replacement of already current core rows.
pub(crate) force_replay: bool,
}
/// One progress event emitted to the core extraction window.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_core_extraction/DemoCoreExtractionProgressPayload.ts"
)]
pub(crate) struct DemoCoreExtractionProgressPayload {
/// RFC 3339 timestamp.
pub(crate) timestamp: std::string::String,
/// Stable severity code.
pub(crate) level: std::string::String,
/// Human-readable message.
pub(crate) message: std::string::String,
/// Number of terminal candidates.
#[ts(type = "number")]
pub(crate) completed: u64,
/// Total selected candidates.
#[ts(type = "number")]
pub(crate) total: u64,
}
/// Final UI-safe summary for one extraction campaign.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_core_extraction/DemoCoreExtractionSummaryPayload.ts"
)]
pub(crate) struct DemoCoreExtractionSummaryPayload {
/// Current extractor implementation version.
pub(crate) processor_version: std::string::String,
/// Number of selected canonical transactions.
#[ts(type = "number")]
pub(crate) selected: u64,
/// Number admitted to the bounded execution queue.
#[ts(type = "number")]
pub(crate) started: u64,
/// Number reaching a terminal result.
#[ts(type = "number")]
pub(crate) completed: u64,
/// Number skipped by version/hash idempotence.
#[ts(type = "number")]
pub(crate) skipped: u64,
/// Number extracted and committed.
#[ts(type = "number")]
pub(crate) extracted: u64,
/// Number failed.
#[ts(type = "number")]
pub(crate) failed: u64,
/// Number admitted but cancelled before a terminal result.
#[ts(type = "number")]
pub(crate) cancelled_candidates: u64,
/// Number selected but not started after cancellation.
#[ts(type = "number")]
pub(crate) not_started: u64,
/// Whether the campaign was cancelled.
pub(crate) cancelled: bool,
/// Campaign start timestamp.
pub(crate) started_at: std::string::String,
/// Campaign finish timestamp.
pub(crate) finished_at: std::string::String,
}
pub(crate) struct DemoCoreExtractionObserver<'a> {
pub(crate) app_handle: tauri::AppHandle,
pub(crate) cancel_requested: &'a std::sync::atomic::AtomicBool,
}
impl kb_pipeline::CoreExtractionObserver for crate::DemoCoreExtractionObserver<'_> {
fn on_progress(&self, event: &kb_pipeline::CoreExtractionProgressEvent) {
let payload = crate::DemoCoreExtractionProgressPayload {
timestamp: event.timestamp.clone(),
level: event.level.code().to_string(),
message: event.message.clone(),
completed: event.completed,
total: event.total,
};
let emit_result = self.app_handle.emit_to(
"demo_core_extraction",
"demo-core-extraction-progress",
payload,
);
if let std::result::Result::Err(error) = emit_result {
tracing::warn!(
target: crate::TRACING_TARGET,
"cannot emit core extraction progress: {error}"
);
}
}
fn is_cancelled(&self) -> bool {
return self.cancel_requested.load(std::sync::atomic::Ordering::Relaxed);
}
}
pub(crate) struct DemoCoreExtractionRunGuard<'a> {
pub(crate) running: &'a std::sync::atomic::AtomicBool,
}
impl std::ops::Drop for crate::DemoCoreExtractionRunGuard<'_> {
fn drop(&mut self) {
self.running.store(false, std::sync::atomic::Ordering::Release);
}
}
pub(crate) fn build_demo_core_extraction_pipeline_request(
request: crate::DemoCoreExtractionRequest,
) -> std::result::Result<kb_pipeline::CoreExtractionRequest, std::string::String> {
let source_result = match request.mode.trim() {
"signatures" => {
let signatures = split_signatures(request.signatures_text.as_deref());
std::result::Result::Ok(kb_pipeline::CoreExtractionSource::Signatures(signatures))
},
"pending" => std::result::Result::Ok(kb_pipeline::CoreExtractionSource::Pending),
"program_id" => {
let program_id = match request.program_id {
std::option::Option::Some(value) if !value.trim().is_empty() => value,
_ => {
return std::result::Result::Err(
"program id is required for program extraction".to_string(),
);
},
};
std::result::Result::Ok(kb_pipeline::CoreExtractionSource::ProgramId { program_id })
},
"slot_range" => {
let min_slot = match request.min_slot {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"minimum slot is required for slot range extraction".to_string(),
);
},
};
let max_slot = match request.max_slot {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"maximum slot is required for slot range extraction".to_string(),
);
},
};
std::result::Result::Ok(kb_pipeline::CoreExtractionSource::SlotRange {
min_slot,
max_slot,
})
},
_ => std::result::Result::Err("unsupported core extraction mode".to_string()),
};
let source = match source_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let pipeline_request = kb_pipeline::CoreExtractionRequest {
source,
limit: request.limit,
max_concurrent_extractions: request.max_concurrent_extractions,
force_replay: request.force_replay,
};
let validation_result = pipeline_request.validate();
if let std::result::Result::Err(error) = validation_result {
return std::result::Result::Err(error.to_string());
}
return std::result::Result::Ok(pipeline_request);
}
fn split_signatures(text: std::option::Option<&str>) -> std::vec::Vec<std::string::String> {
let source = match text {
std::option::Option::Some(value) => value,
std::option::Option::None => "",
};
let mut unique = std::collections::BTreeSet::<std::string::String>::new();
let mut output = std::vec::Vec::new();
for line in source.lines() {
let signature = line.trim();
if signature.is_empty() {
continue;
}
if unique.insert(signature.to_string()) {
output.push(signature.to_string());
}
}
return output;
}
pub(crate) fn demo_core_extraction_summary_payload(
summary: kb_pipeline::CoreExtractionSummary,
) -> crate::DemoCoreExtractionSummaryPayload {
return crate::DemoCoreExtractionSummaryPayload {
processor_version: summary.processor_version,
selected: summary.selected,
started: summary.started,
completed: summary.completed,
skipped: summary.skipped,
extracted: summary.extracted,
failed: summary.failed,
cancelled_candidates: summary.cancelled_candidates,
not_started: summary.not_started,
cancelled: summary.cancelled,
started_at: summary.started_at,
finished_at: summary.finished_at,
};
}
#[cfg(test)]
mod tests {
#[test]
fn signature_source_splits_deduplicates_and_ignores_empty_rows() {
let values = super::split_signatures(std::option::Option::Some("alpha\n\n beta \nalpha\n"));
assert_eq!(values, std::vec!["alpha".to_string(), "beta".to_string()]);
}
}

View File

@@ -0,0 +1,953 @@
// file: kb_app_demo/src/demo_decode_replay.rs
// version: 24
//! Tauri commands and UI payloads for contextual instruction decode replay.
use tauri::Emitter; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import
/// One decoder selectable by the decode replay demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_decode_replay/DemoDecodeReplayDecoderOption.ts"
)]
pub(crate) struct DemoDecodeReplayDecoderOption {
/// Stable decoder name.
pub(crate) name: std::string::String,
/// Stable decoder version.
pub(crate) version: std::string::String,
/// Exact supported program identifiers.
pub(crate) program_ids: std::vec::Vec<std::string::String>,
}
/// Initial options shown by the contextual decode replay demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_decode_replay/DemoDecodeReplayOptionsPayload.ts"
)]
pub(crate) struct DemoDecodeReplayOptionsPayload {
/// Common orchestration version.
pub(crate) pipeline_version: std::string::String,
/// Available contextual decoders.
pub(crate) decoders: std::vec::Vec<crate::DemoDecodeReplayDecoderOption>,
/// Stable names of materializers currently registered by the demo.
pub(crate) materializer_names: std::vec::Vec<std::string::String>,
/// Default bounded selection limit.
pub(crate) default_limit: u32,
/// Default maximum concurrent contextual inputs.
pub(crate) default_max_concurrent_inputs: u32,
/// Whether one replay campaign is running.
pub(crate) running: bool,
}
/// UI request for one bounded contextual decode replay campaign.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_decode_replay/DemoDecodeReplayRequest.ts"
)]
pub(crate) struct DemoDecodeReplayRequest {
/// Newline-separated exact transaction signatures.
pub(crate) signatures_text: std::option::Option<std::string::String>,
/// Optional exact program identifier.
pub(crate) program_id: std::option::Option<std::string::String>,
/// Instruction processing state code, actionable or incomplete_signatures.
pub(crate) instruction_state: std::string::String,
/// Newline-separated exact stable instruction paths.
pub(crate) instruction_paths_text: std::option::Option<std::string::String>,
/// Explicit selected decoder names.
pub(crate) decoder_names: std::vec::Vec<std::string::String>,
/// Maximum selected contextual inputs.
pub(crate) limit: u32,
/// Maximum concurrent contextual inputs.
pub(crate) max_concurrent_inputs: u32,
/// Whether every compatible decoder must run.
pub(crate) all_compatible: bool,
/// Replaces only processor-owned outputs for the same version and input.
pub(crate) force_replay: bool,
/// Explicitly authorizes a bounded force replay without exact signatures.
pub(crate) force_replay_all_matching: bool,
/// Runs compatible materializers after decoded event persistence.
pub(crate) materialize_after_decode: bool,
}
/// One progress event emitted to the decode replay window.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_decode_replay/DemoDecodeReplayProgressPayload.ts"
)]
pub(crate) struct DemoDecodeReplayProgressPayload {
/// Stable process-local campaign identifier.
pub(crate) campaign_id: std::string::String,
/// RFC 3339 timestamp.
pub(crate) timestamp: std::string::String,
/// Stable severity code.
pub(crate) level: std::string::String,
/// Human-readable message.
pub(crate) message: std::string::String,
/// Number of terminal contextual inputs.
#[ts(type = "number")]
pub(crate) completed: u64,
/// Total selected contextual inputs.
#[ts(type = "number")]
pub(crate) total: u64,
}
/// One processor counter row returned to the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_decode_replay/DemoDecodeProcessorSummaryPayload.ts"
)]
pub(crate) struct DemoDecodeProcessorSummaryPayload {
/// Stable processor name.
pub(crate) processor_name: std::string::String,
/// Stable processor version.
pub(crate) processor_version: std::string::String,
/// Compatible dispatch count.
#[ts(type = "number")]
pub(crate) dispatched: u64,
/// Version and hash skip count.
#[ts(type = "number")]
pub(crate) skipped: u64,
/// Decoded input count.
#[ts(type = "number")]
pub(crate) decoded: u64,
/// Ignored input count.
#[ts(type = "number")]
pub(crate) ignored: u64,
/// Unsupported input count.
#[ts(type = "number")]
pub(crate) unsupported: u64,
/// Failed input count.
#[ts(type = "number")]
pub(crate) failed: u64,
/// Materialized output count.
#[ts(type = "number")]
pub(crate) materialized_outputs: u64,
/// Materialization refusal count.
#[ts(type = "number")]
pub(crate) materialization_refused: u64,
}
/// Final UI-safe summary for one contextual decode replay campaign.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_decode_replay/DemoDecodeReplaySummaryPayload.ts"
)]
pub(crate) struct DemoDecodeReplaySummaryPayload {
/// Stable process-local campaign identifier.
pub(crate) campaign_id: std::string::String,
/// Common pipeline version.
pub(crate) pipeline_version: std::string::String,
/// Number of selected contextual inputs.
#[ts(type = "number")]
pub(crate) selected: u64,
/// Number admitted to execution.
#[ts(type = "number")]
pub(crate) started: u64,
/// Number reaching a terminal state.
#[ts(type = "number")]
pub(crate) completed: u64,
/// Number with no compatible enabled decoder.
#[ts(type = "number")]
pub(crate) unmatched: u64,
/// Number never started after cancellation.
#[ts(type = "number")]
pub(crate) not_started: u64,
/// Number of failed contextual inputs.
#[ts(type = "number")]
pub(crate) failed_inputs: u64,
/// Whether cancellation was observed.
pub(crate) cancelled: bool,
/// Per-processor counters.
pub(crate) processors: std::vec::Vec<crate::DemoDecodeProcessorSummaryPayload>,
/// Campaign start timestamp.
pub(crate) started_at: std::string::String,
/// Campaign finish timestamp.
pub(crate) finished_at: std::string::String,
}
/// One aggregated coverage row returned to the UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_decode_replay/DemoDecodeCoverageSummaryPayload.ts"
)]
pub(crate) struct DemoDecodeCoverageSummaryPayload {
/// Stable processor name.
pub(crate) processor_name: std::string::String,
/// Stable processor version.
pub(crate) processor_version: std::string::String,
/// Exact program identifier.
pub(crate) program_id: std::string::String,
/// Optional stable surface code.
pub(crate) surface_code: std::option::Option<std::string::String>,
/// Stable entry classifier.
pub(crate) entry_code: std::string::String,
/// Declared count.
#[ts(type = "number")]
pub(crate) declared_count: i64,
/// Observed count.
#[ts(type = "number")]
pub(crate) observed_count: i64,
/// Recognized count.
#[ts(type = "number")]
pub(crate) recognized_count: i64,
/// Decoded observation count.
#[ts(type = "number")]
pub(crate) decoded_count: i64,
/// Materialized output count.
#[ts(type = "number")]
pub(crate) materialized_count: i64,
/// Error count.
#[ts(type = "number")]
pub(crate) error_count: i64,
/// Unknown or unsupported observation count.
#[ts(type = "number")]
pub(crate) unknown_count: i64,
/// Successful source transaction count.
#[ts(type = "number")]
pub(crate) successful_transaction_count: i64,
/// Failed source transaction count.
#[ts(type = "number")]
pub(crate) failed_transaction_count: i64,
}
/// Read-only decode, ledger and coverage diagnostics.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_decode_replay/DemoDecodeDiagnosticsPayload.ts"
)]
pub(crate) struct DemoDecodeDiagnosticsPayload {
/// Decode, materialization and ledger table diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
/// Aggregated declared and observed coverage.
pub(crate) coverage: std::vec::Vec<crate::DemoDecodeCoverageSummaryPayload>,
}
/// Bounded read request for committed transaction annotations.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_decode_replay/DemoTransactionAnnotationRequest.ts"
)]
pub(crate) struct DemoTransactionAnnotationRequest {
/// Optional partial transaction signature.
pub(crate) signature_contains: std::option::Option<std::string::String>,
/// Maximum returned rows.
pub(crate) limit: u32,
}
/// UI-safe committed transaction annotation row.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_decode_replay/DemoTransactionAnnotationRow.ts"
)]
pub(crate) struct DemoTransactionAnnotationRow {
/// Materializer processor version.
pub(crate) processor_version: std::string::String,
/// Source decoder and version.
pub(crate) decoder: std::string::String,
/// Transaction signature.
pub(crate) signature: std::string::String,
/// Decimal slot rendered as text to preserve JSON precision.
pub(crate) slot: std::string::String,
/// Stable outer or inner instruction path.
pub(crate) instruction_path: std::string::String,
/// Exact Memo generation.
pub(crate) generation: std::string::String,
/// Exact Memo Program ID.
pub(crate) program_id: std::string::String,
/// Complete bounded UTF-8 Memo text.
pub(crate) text: std::string::String,
/// Exact payload byte length.
pub(crate) payload_length_bytes: u32,
/// Canonical payload SHA-256.
pub(crate) payload_sha256: std::string::String,
/// Runtime-verified ordered signer keys.
pub(crate) verified_signers: std::vec::Vec<std::string::String>,
/// Stable materializer idempotence key.
pub(crate) idempotence_key: std::string::String,
/// Database creation timestamp.
pub(crate) created_at: std::string::String,
/// Database replacement timestamp.
pub(crate) updated_at: std::string::String,
}
pub(crate) struct DemoDecodeReplayObserver<'a> {
pub(crate) app_handle: tauri::AppHandle,
pub(crate) campaign_id: std::string::String,
pub(crate) cancel_requested: &'a std::sync::atomic::AtomicBool,
}
impl kb_pipeline::DecodeReplayObserver for crate::DemoDecodeReplayObserver<'_> {
fn on_progress(&self, event: &kb_pipeline::DecodeReplayProgressEvent) {
let payload = DemoDecodeReplayProgressPayload {
campaign_id: self.campaign_id.clone(),
timestamp: event.timestamp.clone(),
level: event.level.code().to_string(),
message: event.message.clone(),
completed: event.completed,
total: event.total,
};
let emit_result =
self.app_handle
.emit_to("demo_decode_replay", "demo-decode-replay-progress", payload);
if let std::result::Result::Err(error) = emit_result {
tracing::warn!(target: crate::TRACING_TARGET, action = "emit_progress", campaign_id = %self.campaign_id, error = %error, "cannot emit decode replay progress");
}
}
fn is_cancelled(&self) -> bool {
return self.cancel_requested.load(std::sync::atomic::Ordering::Relaxed);
}
}
pub(crate) struct DemoDecodeReplayRunGuard<'a> {
pub(crate) running: &'a std::sync::atomic::AtomicBool,
pub(crate) campaign_id: &'a std::sync::Mutex<std::option::Option<std::string::String>>,
}
impl std::ops::Drop for crate::DemoDecodeReplayRunGuard<'_> {
fn drop(&mut self) {
self.running.store(false, std::sync::atomic::Ordering::Release);
let lock_result = self.campaign_id.lock();
if let std::result::Result::Ok(mut active_campaign_id) = lock_result {
*active_campaign_id = std::option::Option::None;
}
}
}
pub(crate) fn register_active_campaign(
campaign_slot: &std::sync::Mutex<std::option::Option<std::string::String>>,
campaign_id: &str,
) -> std::result::Result<(), std::string::String> {
let lock_result = campaign_slot.lock();
let mut active_campaign_id = match lock_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
"cannot register active contextual decode campaign".to_string(),
);
},
};
*active_campaign_id = std::option::Option::Some(campaign_id.to_string());
return std::result::Result::Ok(());
}
pub(crate) fn available_materializers()
-> std::vec::Vec<std::sync::Arc<dyn kb_materializer_api::EventMaterializer>> {
return std::vec![
std::sync::Arc::new(kb_materializer_admin::AdminMaterializer),
std::sync::Arc::new(kb_materializer_compliance_audit::ComplianceAuditMaterializer),
std::sync::Arc::new(kb_materializer_fees::FeesMaterializer),
std::sync::Arc::new(kb_materializer_lifecycle::LifecycleMaterializer),
std::sync::Arc::new(kb_materializer_staking::StakingMaterializer),
std::sync::Arc::new(kb_materializer_token_accounts::TokenAccountsMaterializer),
std::sync::Arc::new(kb_materializer_risk::RiskMaterializer),
std::sync::Arc::new(
kb_materializer_transaction_annotations::TransactionAnnotationMaterializer,
),
];
}
pub(crate) fn available_decoders()
-> std::vec::Vec<std::sync::Arc<dyn kb_decoder_api::InstructionDecoder>> {
return std::vec![
std::sync::Arc::new(kb_decoder_solana_core::SolanaCoreDecoder),
std::sync::Arc::new(
kb_decoder_spl_associated_token_account::SplAssociatedTokenAccountDecoder,
),
std::sync::Arc::new(kb_decoder_spl_elgamal_registry::SplElgamalRegistryDecoder),
std::sync::Arc::new(kb_decoder_spl_memo::SplMemoDecoder),
std::sync::Arc::new(kb_decoder_spl_token::SplTokenDecoder),
std::sync::Arc::new(kb_decoder_spl_token_2022::SplToken2022Decoder),
];
}
pub(crate) fn build_demo_decode_replay_pipeline_request(
request: crate::DemoDecodeReplayRequest,
campaign_id: std::string::String,
) -> std::result::Result<kb_pipeline::DecodeReplayRequest, std::string::String> {
let states_result = processing_states(request.instruction_state.as_str());
let states = match states_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let signatures = split_lines(request.signatures_text.as_deref());
let instruction_paths = split_lines(request.instruction_paths_text.as_deref());
let incomplete_signatures = request.instruction_state.trim() == "incomplete_signatures";
let program_ids = match request.program_id {
std::option::Option::Some(value) if !value.trim().is_empty() => {
std::vec![value.trim().to_string()]
},
_ => std::vec::Vec::new(),
};
let selection_result = kb_store_core::DecodeSelectionFilter::new(
signatures,
states,
std::option::Option::None,
std::option::Option::None,
program_ids,
instruction_paths,
incomplete_signatures,
request.limit,
);
let selection = match selection_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let dispatch_policy = if request.all_compatible {
kb_pipeline::DecodeDispatchPolicy::AllCompatible
} else {
kb_pipeline::DecodeDispatchPolicy::HighestPriority
};
let pipeline_request = kb_pipeline::DecodeReplayRequest {
campaign_id,
selection,
decoder_names: request.decoder_names,
dispatch_policy,
max_concurrent_inputs: request.max_concurrent_inputs,
force_replay: request.force_replay,
force_replay_all_matching: request.force_replay_all_matching,
materialize_after_decode: request.materialize_after_decode,
};
let validation_result = pipeline_request.validate();
if let std::result::Result::Err(error) = validation_result {
return std::result::Result::Err(error.to_string());
}
tracing::debug!(
target: crate::TRACING_TARGET,
action = "build_request",
campaign_id = %pipeline_request.campaign_id,
signature_count = pipeline_request.selection.signatures.len(),
signature_sample = ?text_sample(pipeline_request.selection.signatures.as_slice(), 5),
processing_states = ?pipeline_request.selection.processing_states,
program_ids = ?pipeline_request.selection.program_ids,
instruction_paths = ?pipeline_request.selection.instruction_paths,
incomplete_signatures = pipeline_request.selection.incomplete_signatures,
limit = pipeline_request.selection.limit,
decoder_names = ?pipeline_request.decoder_names,
dispatch_policy = ?pipeline_request.dispatch_policy,
max_concurrent_inputs = pipeline_request.max_concurrent_inputs,
force_replay = pipeline_request.force_replay,
force_replay_all_matching = pipeline_request.force_replay_all_matching,
materialize_after_decode = pipeline_request.materialize_after_decode,
"built normalized contextual decode replay request"
);
return std::result::Result::Ok(pipeline_request);
}
pub(crate) fn optional_line_count(value: &std::option::Option<std::string::String>) -> usize {
return match value {
std::option::Option::Some(text) => {
text.lines().filter(|line| return !line.trim().is_empty()).count()
},
std::option::Option::None => 0,
};
}
pub(crate) fn text_sample(values: &[std::string::String], limit: usize) -> std::vec::Vec<&str> {
return values.iter().take(limit).map(std::string::String::as_str).collect();
}
fn processing_states(
value: &str,
) -> std::result::Result<
std::vec::Vec<kb_store_core::CoreInstructionProcessingState>,
std::string::String,
> {
return match value.trim() {
"incomplete_signatures" | "actionable" => std::result::Result::Ok(std::vec![
kb_store_core::CoreInstructionProcessingState::Pending,
kb_store_core::CoreInstructionProcessingState::Failed,
kb_store_core::CoreInstructionProcessingState::ReplayRequested,
]),
"pending" => std::result::Result::Ok(std::vec![
kb_store_core::CoreInstructionProcessingState::Pending
]),
"failed" => std::result::Result::Ok(std::vec![
kb_store_core::CoreInstructionProcessingState::Failed
]),
"replay_requested" => std::result::Result::Ok(std::vec![
kb_store_core::CoreInstructionProcessingState::ReplayRequested
]),
"decoded" => std::result::Result::Ok(std::vec![
kb_store_core::CoreInstructionProcessingState::Decoded
]),
"ignored" => std::result::Result::Ok(std::vec![
kb_store_core::CoreInstructionProcessingState::Ignored
]),
"materialized" => std::result::Result::Ok(std::vec![
kb_store_core::CoreInstructionProcessingState::Materialized
]),
_ => std::result::Result::Err("unsupported instruction processing state".to_string()),
};
}
fn split_lines(text: std::option::Option<&str>) -> std::vec::Vec<std::string::String> {
let source = match text {
std::option::Option::Some(value) => value,
std::option::Option::None => "",
};
let mut unique = std::collections::BTreeSet::<std::string::String>::new();
let mut output = std::vec::Vec::new();
for line in source.lines() {
let value = line.trim();
if value.is_empty() {
continue;
}
if unique.insert(value.to_string()) {
output.push(value.to_string());
}
}
return output;
}
pub(crate) fn demo_decode_replay_summary_payload(
summary: kb_pipeline::DecodeReplaySummary,
) -> crate::DemoDecodeReplaySummaryPayload {
return crate::DemoDecodeReplaySummaryPayload {
campaign_id: summary.campaign_id,
pipeline_version: summary.pipeline_version,
selected: summary.selected,
started: summary.started,
completed: summary.completed,
unmatched: summary.unmatched,
not_started: summary.not_started,
failed_inputs: summary.failed_inputs,
cancelled: summary.cancelled,
processors: summary
.processors
.into_iter()
.map(|processor| {
return crate::DemoDecodeProcessorSummaryPayload {
processor_name: processor.processor_name,
processor_version: processor.processor_version,
dispatched: processor.dispatched,
skipped: processor.skipped,
decoded: processor.decoded,
ignored: processor.ignored,
unsupported: processor.unsupported,
failed: processor.failed,
materialized_outputs: processor.materialized_outputs,
materialization_refused: processor.materialization_refused,
};
})
.collect(),
started_at: summary.started_at,
finished_at: summary.finished_at,
};
}
pub(crate) fn coverage_payload(
value: kb_store_core::DecodeCoverageSummaryRow,
) -> crate::DemoDecodeCoverageSummaryPayload {
return crate::DemoDecodeCoverageSummaryPayload {
processor_name: value.processor_name,
processor_version: value.processor_version,
program_id: value.program_id,
surface_code: value.surface_code,
entry_code: value.entry_code,
declared_count: value.declared_count,
observed_count: value.observed_count,
recognized_count: value.recognized_count,
decoded_count: value.decoded_count,
materialized_count: value.materialized_count,
error_count: value.error_count,
unknown_count: value.unknown_count,
successful_transaction_count: value.successful_transaction_count,
failed_transaction_count: value.failed_transaction_count,
};
}
pub(crate) fn annotation_payload(
row: kb_store_core::MaterializedEventQueryRow,
) -> std::result::Result<crate::DemoTransactionAnnotationRow, std::string::String> {
if row.processor_name != "transaction_annotations"
|| row.materialized_family != "transaction_annotation"
{
return std::result::Result::Err(
"materialized row is not a transaction annotation".to_string(),
);
}
let payload = &row.payload_json;
let byte_length = match payload.get("payloadLengthBytes").and_then(serde_json::Value::as_u64) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"transaction annotation payloadLengthBytes is absent".to_string(),
);
},
};
let byte_length_result = u32::try_from(byte_length);
let payload_length_bytes = match byte_length_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"transaction annotation payloadLengthBytes is not UI-safe: {error}"
));
},
};
let signers = match payload.get("signers") {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"transaction annotation signers are absent".to_string(),
);
},
};
let verified_values = match signers.get("verified").and_then(serde_json::Value::as_array) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"transaction annotation verified signers are invalid".to_string(),
);
},
};
let mut verified_signers = std::vec::Vec::with_capacity(verified_values.len());
for value in verified_values {
match value.as_str() {
std::option::Option::Some(signer) if !signer.trim().is_empty() => {
verified_signers.push(signer.to_string());
},
_ => {
return std::result::Result::Err(
"transaction annotation verified signer is invalid".to_string(),
);
},
}
}
let instruction_path = match required_annotation_text(payload, "instructionPath") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let generation = match required_annotation_text(payload, "generation") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let program_id = match required_annotation_text(payload, "programId") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let text = match required_annotation_text(payload, "text") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let payload_sha256 = match required_annotation_text(payload, "payloadSha256") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let idempotence_key = match required_annotation_text(payload, "idempotenceKey") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::DemoTransactionAnnotationRow {
processor_version: row.processor_version,
decoder: format!("{}@{}", row.source_decoder_name, row.source_decoder_version),
signature: row.signature,
slot: row.slot.to_string(),
instruction_path,
generation,
program_id,
text,
payload_length_bytes,
payload_sha256,
verified_signers,
idempotence_key,
created_at: row.created_at,
updated_at: row.updated_at,
});
}
fn required_annotation_text(
payload: &serde_json::Value,
field: &str,
) -> std::result::Result<std::string::String, std::string::String> {
return match payload.get(field).and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => std::result::Result::Ok(value.to_string()),
std::option::Option::None => {
std::result::Result::Err(format!("transaction annotation {field} is absent"))
},
};
}
#[cfg(test)]
mod tests {
fn ata_observation(entry: &str) -> kb_decoder_api::DecodedObservation {
return kb_decoder_api::DecodedObservation {
event_key: format!("ata:{entry}:0"),
event: kb_model::DecodedProtocolEvent {
signature: kb_model::Signature("signature".to_string()),
slot: kb_model::Slot(1),
instruction_path: kb_model::InstructionPath("0".to_string()),
program_id: kb_model::ProgramId(
kb_program_ids::ASSOCIATED_TOKEN_PROGRAM_ID.to_string(),
),
protocol_code: kb_model::ProtocolCode("spl_associated_token_account".to_string()),
surface_code: kb_model::SurfaceCode("spl_associated_token_account".to_string()),
event_code: kb_model::EventCode(format!("spl_associated_token_account.{entry}")),
event_name: kb_model::EventName(entry.to_string()),
event_family: kb_model::EventFamily::Lifecycle,
source_kind: kb_model::EventSourceKind::Instruction,
confidence: kb_model::DecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({}),
transaction_failed: false,
transaction_error: std::option::Option::None,
observation_committed: true,
proof: kb_decoder_api::DecoderProof {
kind: kb_decoder_api::DecoderProofKind::Manual,
confidence: kb_model::DecoderConfidence::ManualExact,
evidence: std::vec!["fixture".to_string()],
},
};
}
#[test]
fn native_memo_token_2022_elgamal_classic_token_and_ata_decoders_are_registered() {
let decoders = crate::available_decoders();
assert_eq!(decoders.len(), 6);
let names = decoders
.iter()
.map(|decoder| return decoder.identity().name)
.collect::<std::vec::Vec<_>>();
assert_eq!(
names,
std::vec![
"solana_native_classifier".to_string(),
"spl_associated_token_account".to_string(),
"spl_elgamal_registry".to_string(),
"spl_memo".to_string(),
"spl_token".to_string(),
"spl_token_2022".to_string(),
]
);
}
#[test]
fn runtime_materializer_registry_is_complete_for_current_instructional_surfaces() {
let materializers = crate::available_materializers();
assert_eq!(materializers.len(), 8);
let names = materializers
.iter()
.map(|materializer| return materializer.identity().name)
.collect::<std::vec::Vec<_>>();
assert_eq!(
names,
std::vec![
"solana_native_admin".to_string(),
"solana_native_compliance_audit".to_string(),
"fees".to_string(),
"solana_native_lifecycle".to_string(),
"solana_native_staking".to_string(),
"spl_token_accounts".to_string(),
"spl_token_risk".to_string(),
"transaction_annotations".to_string(),
]
);
}
#[test]
fn ata_materializer_ownership_is_exact_in_the_runtime_registry() {
for (entry, expected) in [
("create", std::vec!["spl_token_accounts".to_string()]),
(
"recover_nested",
std::vec!["spl_token_accounts".to_string(), "spl_token_risk".to_string(),],
),
] {
let observation = ata_observation(entry);
let owners = crate::available_materializers()
.iter()
.filter(|materializer| return materializer.accepts_observation(&observation))
.map(|materializer| return materializer.identity().name)
.collect::<std::vec::Vec<_>>();
assert_eq!(owners, expected);
}
}
#[test]
fn line_selection_is_trimmed_and_deduplicated() {
let values = super::split_lines(std::option::Option::Some(" a \n\na\nb\n"));
assert_eq!(values, std::vec!["a".to_string(), "b".to_string()]);
}
fn request(
signatures_text: std::option::Option<&str>,
force_replay: bool,
force_replay_all_matching: bool,
materialize_after_decode: bool,
) -> crate::DemoDecodeReplayRequest {
return crate::DemoDecodeReplayRequest {
signatures_text: signatures_text.map(|value| return value.to_string()),
program_id: std::option::Option::None,
instruction_state: "actionable".to_string(),
instruction_paths_text: std::option::Option::None,
decoder_names: std::vec!["solana_native_classifier".to_string()],
limit: 10,
max_concurrent_inputs: 2,
all_compatible: false,
force_replay,
force_replay_all_matching,
materialize_after_decode,
};
}
#[test]
fn force_replay_requires_signatures_or_all_matching_authorization() {
let result = crate::build_demo_decode_replay_pipeline_request(
request(std::option::Option::None, true, false, false),
"decode-test".to_string(),
);
assert!(result.is_err());
}
#[test]
fn force_replay_all_matching_is_forwarded() {
let result = crate::build_demo_decode_replay_pipeline_request(
request(std::option::Option::None, true, true, false),
"decode-test".to_string(),
);
let request = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("request mapping failed: {error}"),
};
assert!(request.force_replay_all_matching);
assert!(request.selection.signatures.is_empty());
}
#[test]
fn run_guard_clears_active_campaign_state() {
let running = std::sync::atomic::AtomicBool::new(true);
let campaign_id =
std::sync::Mutex::new(std::option::Option::Some("decode-test".to_string()));
{
let _guard = crate::DemoDecodeReplayRunGuard {
running: &running,
campaign_id: &campaign_id,
};
}
assert!(!running.load(std::sync::atomic::Ordering::Acquire));
let lock_result = campaign_id.lock();
let active_campaign_id = match lock_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => panic!("campaign id mutex must not be poisoned"),
};
assert!(active_campaign_id.is_none());
}
#[test]
fn register_active_campaign_sets_slot_without_exposing_guard() {
let campaign_id = std::sync::Mutex::new(std::option::Option::None);
let result = crate::register_active_campaign(&campaign_id, "decode-test");
assert!(result.is_ok());
let lock_result = campaign_id.lock();
let active_campaign_id = match lock_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => panic!("campaign id mutex must not be poisoned"),
};
assert_eq!(active_campaign_id.as_deref(), std::option::Option::Some("decode-test"));
}
#[test]
fn incomplete_signature_state_uses_actionable_states_and_expansion() {
let mut request = request(std::option::Option::None, false, false, true);
request.instruction_state = "incomplete_signatures".to_string();
let result = crate::build_demo_decode_replay_pipeline_request(
request,
"decode-incomplete".to_string(),
);
let pipeline_request = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("request mapping failed: {error}"),
};
assert!(pipeline_request.selection.incomplete_signatures);
assert_eq!(pipeline_request.selection.processing_states.len(), 3);
assert!(!pipeline_request.force_replay);
assert!(pipeline_request.materialize_after_decode);
}
#[test]
fn actionable_state_contains_pending_failed_and_replay_requested() {
let result = super::processing_states("actionable");
let states = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("state mapping failed: {error}"),
};
assert_eq!(states.len(), 3);
}
#[test]
fn committed_annotation_row_is_mapped_to_ui_safe_contract() {
let row = kb_store_core::MaterializedEventQueryRow {
processor_name: "transaction_annotations".to_string(),
processor_version: "0.4.3".to_string(),
input_key: "input".to_string(),
output_key: "output".to_string(),
source_event_key: "memo:0".to_string(),
source_decoder_name: "spl_memo".to_string(),
source_decoder_version: "0.4.3".to_string(),
signature: "signature".to_string(),
slot: u64::MAX,
materialized_family: "transaction_annotation".to_string(),
payload_json: serde_json::json!({
"instructionPath": "1/0",
"generation": "v4",
"programId": kb_program_ids::SPL_MEMO_V4_PROGRAM_ID,
"text": "annotation",
"payloadLengthBytes": 10,
"payloadSha256": "11",
"signers": {"verified": ["signer"]},
"idempotenceKey": "stable"
}),
created_at: "created".to_string(),
updated_at: "updated".to_string(),
};
let result = crate::annotation_payload(row);
let mapped = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("annotation mapping failed: {error}"),
};
assert_eq!(mapped.slot, u64::MAX.to_string());
assert_eq!(mapped.text, "annotation");
assert_eq!(mapped.verified_signers, std::vec!["signer".to_string()]);
}
#[test]
fn malformed_annotation_payload_fails_closed() {
let row = kb_store_core::MaterializedEventQueryRow {
processor_name: "transaction_annotations".to_string(),
processor_version: "0.4.3".to_string(),
input_key: "input".to_string(),
output_key: "output".to_string(),
source_event_key: "memo:0".to_string(),
source_decoder_name: "spl_memo".to_string(),
source_decoder_version: "0.4.3".to_string(),
signature: "signature".to_string(),
slot: 1,
materialized_family: "transaction_annotation".to_string(),
payload_json: serde_json::json!({}),
created_at: "created".to_string(),
updated_at: "updated".to_string(),
};
assert!(crate::annotation_payload(row).is_err());
}
}

View File

@@ -0,0 +1,664 @@
// file: kb_app_demo/src/demo_execution_solana_core.rs
// version: 5
//! Tauri adapter for bounded Solana Core execution on Devnet.
use tauri::Emitter; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import
/// One Devnet execution profile exposed to the demo window.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_execution_solana_core/DemoExecutionSolanaCoreProfileOption.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreProfileOption {
/// Stable profile name.
pub(crate) name: std::string::String,
/// Non-secret temporary wallet alias.
pub(crate) wallet_alias: std::string::String,
/// Maximum Devnet spend allowed by the profile.
#[ts(type = "number")]
pub(crate) max_spend_lamports: u64,
/// Maximum faucet request allowed by the profile.
#[ts(type = "number")]
pub(crate) max_airdrop_lamports: u64,
/// Whether signed Devnet submission is enabled.
pub(crate) send_enabled: bool,
/// Whether explicit operator confirmation is required.
pub(crate) require_operator_confirmation: bool,
}
/// Initial options shown by the Solana Core execution demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_execution_solana_core/DemoExecutionSolanaCoreOptionsPayload.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreOptionsPayload {
/// Devnet profiles compatible with the execution laboratory.
pub(crate) profiles: std::vec::Vec<crate::DemoExecutionSolanaCoreProfileOption>,
/// Suggested profile name.
pub(crate) default_profile_name: std::option::Option<std::string::String>,
/// Default transfer amount suitable for a new zero-data account on Devnet.
#[ts(type = "number")]
pub(crate) default_transfer_lamports: u64,
/// Whether one execution is currently running.
pub(crate) running: bool,
}
/// Request sent by the execution demo.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_execution_solana_core/DemoExecutionSolanaCoreRequest.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Recipient public key.
pub(crate) recipient: std::string::String,
/// Lamports transferred by the System instruction.
#[ts(type = "number")]
pub(crate) lamports: u64,
/// Optional bounded faucet request when the source wallet is underfunded.
#[ts(type = "number")]
pub(crate) airdrop_lamports: u64,
/// Whether the transaction should be signed and submitted after simulation.
pub(crate) submit: bool,
/// Explicit operator confirmation for signed submission.
pub(crate) operator_confirmed: bool,
/// Whether post-execution core/decode outputs should be force-replayed.
pub(crate) force_post_validation_replay: bool,
/// Whether compatible materializers should run after decode replay.
pub(crate) materialize_after_decode: bool,
}
/// Request sent by the Memo v4 Devnet execution panel.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_execution_solana_core/DemoExecutionMemoRequest.ts"
)]
pub(crate) struct DemoExecutionMemoRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Exact UTF-8 Memo payload.
pub(crate) message: std::string::String,
/// Whether the wallet is supplied as a readonly Memo signer account.
pub(crate) include_wallet_as_memo_signer: bool,
/// Whether the transaction should be signed and submitted after simulation.
pub(crate) submit: bool,
/// Explicit operator confirmation for signed submission.
pub(crate) operator_confirmed: bool,
/// Whether post-execution core and first decode outputs should be force-replayed.
pub(crate) force_post_validation_replay: bool,
}
/// Public key generated for a disposable Devnet recipient.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_execution_solana_core/DemoExecutionSolanaCoreGeneratedRecipientPayload.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreGeneratedRecipientPayload {
/// Base58 public key. The private key is not persisted or exposed.
pub(crate) public_key: std::string::String,
}
/// Progress event emitted during execution and post-validation.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_execution_solana_core/DemoExecutionSolanaCoreProgressPayload.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreProgressPayload {
/// RFC 3339 timestamp.
pub(crate) timestamp: std::string::String,
/// Stable severity code.
pub(crate) level: std::string::String,
/// Stable stage code.
pub(crate) stage: std::string::String,
/// Human-readable message.
pub(crate) message: std::string::String,
/// Transaction signature when already available.
pub(crate) signature: std::option::Option<std::string::String>,
}
/// UI-safe result of one execution orchestration.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_execution_solana_core/DemoExecutionSolanaCoreSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionSolanaCoreSummaryPayload {
/// Profile used by the execution.
pub(crate) profile_name: std::string::String,
/// Classified cluster code.
pub(crate) cluster: std::string::String,
/// Genesis hash returned by the endpoint.
pub(crate) genesis_hash: std::string::String,
/// Source wallet public key.
pub(crate) wallet_public_key: std::string::String,
/// Recipient public key.
pub(crate) recipient: std::string::String,
/// Whether the recipient account existed before execution.
pub(crate) recipient_existed_before: bool,
/// Recipient balance before execution when present.
#[ts(type = "number | null")]
pub(crate) recipient_balance_before_lamports: std::option::Option<u64>,
/// Rent-exempt minimum required for a new zero-data account.
#[ts(type = "number")]
pub(crate) recipient_minimum_balance_lamports: u64,
/// Source balance before optional funding.
#[ts(type = "number")]
pub(crate) balance_before_lamports: u64,
/// Source balance after optional funding.
#[ts(type = "number")]
pub(crate) balance_after_funding_lamports: u64,
/// Estimated transaction fee.
#[ts(type = "number | null")]
pub(crate) fee_lamports: std::option::Option<u64>,
/// Whether exact simulation succeeded.
pub(crate) simulation_success: bool,
/// Runtime simulation error when present.
pub(crate) simulation_error: std::option::Option<std::string::String>,
/// Runtime simulation logs.
pub(crate) simulation_logs: std::vec::Vec<std::string::String>,
/// Faucet signature when funding was requested.
pub(crate) airdrop_signature: std::option::Option<std::string::String>,
/// Submitted transaction signature.
pub(crate) transaction_signature: std::option::Option<std::string::String>,
/// Terminal confirmation status.
pub(crate) confirmation_status: std::option::Option<std::string::String>,
/// Canonical rows inserted after submission.
#[ts(type = "number | null")]
pub(crate) canonical_inserted: std::option::Option<u64>,
/// Core transactions extracted after submission.
#[ts(type = "number | null")]
pub(crate) core_extracted: std::option::Option<u64>,
/// Contextual decode inputs completed after submission.
#[ts(type = "number | null")]
pub(crate) decode_completed: std::option::Option<u64>,
/// Contextual decode input failures after submission.
#[ts(type = "number | null")]
pub(crate) decode_failed_inputs: std::option::Option<u64>,
/// Pretty JSON representation of the exact execution plan.
pub(crate) plan_json: std::string::String,
/// Pretty JSON representation of the simulation result.
pub(crate) simulation_json: std::string::String,
/// Pretty JSON representation of confirmation and post-validation diagnostics.
pub(crate) diagnostics_json: std::string::String,
}
/// UI-safe result of one Memo v4 Devnet execution orchestration.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_execution_solana_core/DemoExecutionMemoSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionMemoSummaryPayload {
/// Profile used by the execution.
pub(crate) profile_name: std::string::String,
/// Classified cluster code.
pub(crate) cluster: std::string::String,
/// Genesis hash returned by the endpoint.
pub(crate) genesis_hash: std::string::String,
/// Source wallet public key.
pub(crate) wallet_public_key: std::string::String,
/// Exact wallet balance rendered without JSON integer precision loss.
pub(crate) balance_lamports: std::string::String,
/// Estimated transaction fee rendered without JSON integer precision loss.
pub(crate) fee_lamports: std::option::Option<std::string::String>,
/// Whether exact simulation succeeded.
pub(crate) simulation_success: bool,
/// Runtime simulation error when present.
pub(crate) simulation_error: std::option::Option<std::string::String>,
/// Submitted transaction signature.
pub(crate) transaction_signature: std::option::Option<std::string::String>,
/// Terminal confirmation status.
pub(crate) confirmation_status: std::option::Option<std::string::String>,
/// Whether canonical hydration was validated.
pub(crate) canonical_inserted: bool,
/// Whether core extraction was validated.
pub(crate) core_extracted: bool,
/// Whether Memo decode replay was validated.
pub(crate) decode_replayed: bool,
/// Whether the committed transaction annotation was validated.
pub(crate) materialized: bool,
/// Number of exact annotation rows returned by the bounded query.
pub(crate) annotation_count: u32,
/// Whether the second replay produced no failures or new materialized output.
pub(crate) idempotence_validated: bool,
/// Pretty JSON representation of the exact execution plan.
pub(crate) plan_json: std::string::String,
/// Pretty JSON representation of the simulation result.
pub(crate) simulation_json: std::string::String,
/// Pretty JSON representation of annotations and post-validation diagnostics.
pub(crate) diagnostics_json: std::string::String,
}
pub(crate) struct DemoExecutionSolanaCoreObserver<'a> {
pub(crate) app_handle: tauri::AppHandle,
pub(crate) cancel_requested: &'a std::sync::atomic::AtomicBool,
}
impl crate::DemoExecutionSolanaCoreObserver<'_> {
fn emit(
&self,
timestamp: std::string::String,
level: &str,
stage: &str,
message: std::string::String,
signature: std::option::Option<std::string::String>,
) {
let payload = crate::DemoExecutionSolanaCoreProgressPayload {
timestamp,
level: level.to_string(),
stage: stage.to_string(),
message,
signature,
};
let emit_result = self.app_handle.emit_to(
"demo_execution_solana_core",
"demo-execution-solana-core-progress",
payload,
);
if let std::result::Result::Err(error) = emit_result {
tracing::warn!(
target: crate::TRACING_TARGET,
action = "emit_execution_progress",
error = %error,
"cannot emit Solana Core execution progress"
);
}
}
fn cancelled(&self) -> bool {
return self.cancel_requested.load(std::sync::atomic::Ordering::Relaxed);
}
}
impl kb_pipeline::BackfillObserver for crate::DemoExecutionSolanaCoreObserver<'_> {
fn on_progress(&self, event: &kb_pipeline::BackfillProgressEvent) {
self.emit(
event.timestamp.clone(),
event.level.code(),
"canonical_insert",
event.message.clone(),
std::option::Option::None,
);
}
fn is_cancelled(&self) -> bool {
return self.cancelled();
}
}
impl kb_pipeline::CoreExtractionObserver for crate::DemoExecutionSolanaCoreObserver<'_> {
fn on_progress(&self, event: &kb_pipeline::CoreExtractionProgressEvent) {
self.emit(
event.timestamp.clone(),
event.level.code(),
"core_extraction",
event.message.clone(),
std::option::Option::None,
);
}
fn is_cancelled(&self) -> bool {
return self.cancelled();
}
}
impl kb_pipeline::DecodeReplayObserver for crate::DemoExecutionSolanaCoreObserver<'_> {
fn on_progress(&self, event: &kb_pipeline::DecodeReplayProgressEvent) {
self.emit(
event.timestamp.clone(),
event.level.code(),
"decode_replay",
event.message.clone(),
std::option::Option::None,
);
}
fn is_cancelled(&self) -> bool {
return self.cancelled();
}
}
impl kb_pipeline::SolanaExecutionObserver for crate::DemoExecutionSolanaCoreObserver<'_> {
fn on_execution_progress(&self, event: &kb_pipeline::SolanaExecutionProgressEvent) {
self.emit(
event.timestamp.clone(),
event.level.code(),
event.stage.as_str(),
event.message.clone(),
event.signature.clone(),
);
}
fn is_execution_cancelled(&self) -> bool {
return self.cancelled();
}
}
pub(crate) struct DemoExecutionSolanaCoreRunGuard<'a> {
pub(crate) running: &'a std::sync::atomic::AtomicBool,
}
impl std::ops::Drop for crate::DemoExecutionSolanaCoreRunGuard<'_> {
fn drop(&mut self) {
self.running.store(false, std::sync::atomic::Ordering::Release);
}
}
pub(crate) fn devnet_profile_options(
config: &kb_config::AppConfig,
) -> std::vec::Vec<crate::DemoExecutionSolanaCoreProfileOption> {
let mut output = std::vec::Vec::new();
for profile in &config.profiles {
if profile.wallet.cluster != "devnet"
|| !profile.wallet.temporary_wallet_enabled
|| !profile.wallet.temporary_wallet_persist
{
continue;
}
output.push(crate::DemoExecutionSolanaCoreProfileOption {
name: profile.name.clone(),
wallet_alias: profile.wallet.temporary_wallet_alias.clone(),
max_spend_lamports: profile.execution.devnet_max_spend_lamports,
max_airdrop_lamports: profile.execution.devnet_airdrop_max_lamports,
send_enabled: profile.wallet.devnet_send_enabled,
require_operator_confirmation: profile.execution.require_operator_confirmation,
});
}
return output;
}
pub(crate) fn select_devnet_profile(
config: &kb_config::AppConfig,
profile_name: &str,
) -> std::result::Result<kb_config::ProfileConfig, std::string::String> {
for profile in &config.profiles {
if profile.name == profile_name
&& profile.wallet.cluster == "devnet"
&& profile.wallet.temporary_wallet_enabled
&& profile.wallet.temporary_wallet_persist
{
return std::result::Result::Ok(profile.clone());
}
}
return std::result::Result::Err(format!(
"Devnet execution profile '{profile_name}' is unavailable"
));
}
pub(crate) fn demo_execution_solana_core_summary_payload(
summary: kb_pipeline::DevnetSystemTransferSummary,
) -> crate::DemoExecutionSolanaCoreSummaryPayload {
let plan_json = crate::pretty_json(&summary.plan);
let simulation_json = crate::pretty_json(&summary.simulation);
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status = summary.confirmation.as_ref().map(|value| {
return crate::confirmation_status_code(value.status);
});
let canonical_inserted = summary.backfill.as_ref().map(|value| return value.canonical_inserted);
let core_extracted = summary.core_extraction.as_ref().map(|value| return value.extracted);
let decode_completed = summary.decode_replay.as_ref().map(|value| return value.completed);
let decode_failed_inputs =
summary.decode_replay.as_ref().map(|value| return value.failed_inputs);
let diagnostics_value = serde_json::json!({
"airdropConfirmation": summary.airdrop_confirmation.as_ref(),
"confirmation": summary.confirmation.as_ref(),
"postExecution": summary.post_execution.as_ref(),
"backfill": summary.backfill.as_ref().map(|value| {
return serde_json::json!({
"canonicalInserted": value.canonical_inserted,
"canonicalSkipped": value.canonical_skipped,
"existingSkipped": value.existing_skipped,
"missing": value.missing,
"failed": value.failed
});
}),
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
return serde_json::json!({
"selected": value.selected,
"extracted": value.extracted,
"skipped": value.skipped,
"failed": value.failed
});
}),
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
return serde_json::json!({
"campaignId": value.campaign_id,
"selected": value.selected,
"completed": value.completed,
"unmatched": value.unmatched,
"failedInputs": value.failed_inputs,
"cancelled": value.cancelled
});
})
});
let diagnostics_json = crate::pretty_json(&diagnostics_value);
let cluster = crate::execution_cluster_code(summary.cluster);
return crate::DemoExecutionSolanaCoreSummaryPayload {
profile_name: summary.profile_name,
cluster,
genesis_hash: summary.genesis_hash,
wallet_public_key: summary.wallet.public_key,
recipient: summary.recipient.0,
recipient_existed_before: summary.recipient_existed_before,
recipient_balance_before_lamports: summary.recipient_balance_before_lamports,
recipient_minimum_balance_lamports: summary.recipient_minimum_balance_lamports,
balance_before_lamports: summary.balance_before_lamports,
balance_after_funding_lamports: summary.balance_after_funding_lamports,
fee_lamports: summary.fee.fee_lamports,
simulation_success: summary.simulation.success,
simulation_error: summary.simulation.error,
simulation_logs: summary.simulation.logs,
airdrop_signature: summary.airdrop_signature.map(|value| return value.0),
transaction_signature,
confirmation_status,
canonical_inserted,
core_extracted,
decode_completed,
decode_failed_inputs,
plan_json,
simulation_json,
diagnostics_json,
};
}
pub(crate) fn demo_execution_solana_core_memo_summary_payload(
summary: kb_pipeline::DevnetMemoExecutionSummary,
) -> crate::DemoExecutionMemoSummaryPayload {
let plan_json = crate::pretty_json(&summary.plan);
let simulation_json = crate::pretty_json(&summary.simulation);
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status = summary.confirmation.as_ref().map(|value| {
return crate::confirmation_status_code(value.status);
});
let canonical_inserted = summary
.post_execution
.as_ref()
.is_some_and(|value| return value.canonical_inserted);
let core_extracted =
summary.post_execution.as_ref().is_some_and(|value| return value.core_extracted);
let decode_replayed = summary
.post_execution
.as_ref()
.is_some_and(|value| return value.decode_replayed);
let materialized =
summary.post_execution.as_ref().is_some_and(|value| return value.materialized);
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& !replay.cancelled
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
})
&& replay.processors.iter().map(|processor| return processor.skipped).sum::<u64>()
>= 1;
});
let annotation_count = match u32::try_from(summary.annotations.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => u32::MAX,
};
let diagnostics_value = serde_json::json!({
"confirmation": summary.confirmation.as_ref(),
"postExecution": summary.post_execution.as_ref(),
"backfill": summary.backfill.as_ref().map(|value| {
return serde_json::json!({
"canonicalInserted": value.canonical_inserted,
"canonicalSkipped": value.canonical_skipped,
"existingSkipped": value.existing_skipped,
"missing": value.missing,
"failed": value.failed
});
}),
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
return serde_json::json!({
"selected": value.selected,
"extracted": value.extracted,
"skipped": value.skipped,
"failed": value.failed
});
}),
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
return serde_json::json!({
"campaignId": value.campaign_id,
"selected": value.selected,
"completed": value.completed,
"failedInputs": value.failed_inputs,
"processors": value.processors.iter().map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
"version": processor.processor_version,
"decoded": processor.decoded,
"skipped": processor.skipped,
"failed": processor.failed,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
}).collect::<std::vec::Vec<_>>()
});
}),
"idempotenceReplay": summary.idempotence_replay.as_ref().map(|value| {
return serde_json::json!({
"campaignId": value.campaign_id,
"selected": value.selected,
"completed": value.completed,
"failedInputs": value.failed_inputs,
"processors": value.processors.iter().map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
"version": processor.processor_version,
"decoded": processor.decoded,
"skipped": processor.skipped,
"failed": processor.failed,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
}).collect::<std::vec::Vec<_>>()
});
}),
"annotations": summary.annotations
});
let diagnostics_json = crate::pretty_json(&diagnostics_value);
return crate::DemoExecutionMemoSummaryPayload {
profile_name: summary.profile_name,
cluster: crate::execution_cluster_code(summary.cluster),
genesis_hash: summary.genesis_hash,
wallet_public_key: summary.wallet.public_key,
balance_lamports: summary.balance_lamports.to_string(),
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
simulation_success: summary.simulation.success,
simulation_error: summary.simulation.error,
transaction_signature,
confirmation_status,
canonical_inserted,
core_extracted,
decode_replayed,
materialized,
annotation_count,
idempotence_validated,
plan_json,
simulation_json,
diagnostics_json,
};
}
pub(crate) fn execution_cluster_code(
cluster: kb_execution_api::ExecutionCluster,
) -> std::string::String {
return match cluster {
kb_execution_api::ExecutionCluster::Localnet => "localnet".to_string(),
kb_execution_api::ExecutionCluster::Devnet => "devnet".to_string(),
kb_execution_api::ExecutionCluster::Testnet => "testnet".to_string(),
kb_execution_api::ExecutionCluster::Mainnet => "mainnet".to_string(),
};
}
pub(crate) fn confirmation_status_code(
status: kb_execution_api::ExecutionConfirmationStatus,
) -> std::string::String {
return match status {
kb_execution_api::ExecutionConfirmationStatus::Processed => "processed".to_string(),
kb_execution_api::ExecutionConfirmationStatus::Confirmed => "confirmed".to_string(),
kb_execution_api::ExecutionConfirmationStatus::Finalized => "finalized".to_string(),
kb_execution_api::ExecutionConfirmationStatus::Failed => "failed".to_string(),
kb_execution_api::ExecutionConfirmationStatus::Expired => "expired".to_string(),
kb_execution_api::ExecutionConfirmationStatus::TimedOut => "timed_out".to_string(),
};
}
pub(crate) fn pretty_json<T>(value: &T) -> std::string::String
where
T: serde::Serialize,
{
return match serde_json::to_string_pretty(value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
serde_json::json!({"serializationError": error.to_string()}).to_string()
},
};
}
#[cfg(test)]
mod tests {
#[test]
fn example_config_exposes_one_devnet_execution_profile() {
let config =
match kb_config::parse_config_json(include_str!("../../config/example.config.json")) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
};
let profiles = crate::devnet_profile_options(&config);
assert_eq!(profiles.len(), 1);
assert_eq!(profiles[0].name, "local_devnet");
assert!(profiles[0].send_enabled);
assert!(profiles[0].max_spend_lamports >= 1_000_000);
}
#[test]
fn profile_selection_rejects_mainnet_profiles() {
let config =
match kb_config::parse_config_json(include_str!("../../config/example.config.json")) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
};
assert!(crate::select_devnet_profile(&config, "local_devnet").is_ok());
assert!(crate::select_devnet_profile(&config, "mainnet").is_err());
}
}

View File

@@ -0,0 +1,71 @@
// file: kb_app_demo/src/demo_execution_spl.rs
// version: 3
//! Dedicated Tauri window for bounded SPL execution on Devnet.
/// Frontend payload for one independent Devnet SPL validation scenario.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ts_rs::TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_execution_spl/DevnetSplValidationScenarioPayload.ts"
)]
pub(crate) struct DevnetSplValidationScenarioPayload {
/// Stable scenario identifier.
pub id: std::string::String,
/// Operator-visible label.
pub label: std::string::String,
/// Stable family code.
pub family: std::string::String,
/// Stable executor operation code.
pub operation_code: std::string::String,
/// Current implementation status.
pub implementation_status: std::string::String,
/// Whether the scenario requires proof material.
pub proof_required: bool,
/// Ordered required fixture variables.
pub required_fixture_variables: std::vec::Vec<std::string::String>,
}
#[cfg(test)]
mod tests {
#[test]
fn memo_summary_keeps_lamports_as_json_strings() {
let payload = crate::DemoExecutionMemoSummaryPayload {
profile_name: "local_devnet".to_string(),
cluster: "devnet".to_string(),
genesis_hash: "EtWTRABZaYq6iMfeYKouRu166VU2xqa1".to_string(),
wallet_public_key: kb_program_ids::SYSTEM_PROGRAM_ID.to_string(),
balance_lamports: u64::MAX.to_string(),
fee_lamports: std::option::Option::Some(u64::MAX.to_string()),
simulation_success: true,
simulation_error: std::option::Option::None,
transaction_signature: std::option::Option::None,
confirmation_status: std::option::Option::None,
canonical_inserted: false,
core_extracted: false,
decode_replayed: false,
materialized: false,
annotation_count: 0,
idempotence_validated: false,
plan_json: "{}".to_string(),
simulation_json: "{}".to_string(),
diagnostics_json: "{}".to_string(),
};
let json = match serde_json::to_value(payload) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
assert_eq!(
std::option::Option::Some(error.to_string()),
std::option::Option::None,
"Memo summary serialization failed"
);
return;
},
};
assert!(json["balanceLamports"].is_string());
assert!(json["feeLamports"].is_string());
assert_eq!(json["balanceLamports"], u64::MAX.to_string());
}
}

View File

@@ -0,0 +1,361 @@
// file: kb_app_demo/src/demo_http.rs
// version: 8
//! HTTP JSON-RPC demo commands.
use ts_rs::TS; // rust-rules: derive-import
/// One selectable role shown by the HTTP demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_http/DemoHttpRoleOption.ts"
)]
pub(crate) struct DemoHttpRoleOption {
/// Endpoint role code.
pub(crate) role: std::string::String,
/// Request kinds accepted by this role.
pub(crate) request_kinds: std::vec::Vec<std::string::String>,
}
/// One selectable HTTP JSON-RPC method shown by the HTTP demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_http/DemoHttpMethodOption.ts"
)]
pub(crate) struct DemoHttpMethodOption {
/// JSON-RPC method name.
pub(crate) method: std::string::String,
/// Derived request kind used for endpoint routing.
pub(crate) request_kind: std::string::String,
/// Human-readable method label.
pub(crate) label: std::string::String,
/// Whether this method needs the first argument field.
pub(crate) requires_first_arg: bool,
/// Whether this method supports an optional configuration object.
pub(crate) supports_config_json: bool,
}
/// HTTP demo options derived from configuration and local method presets.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_http/DemoHttpOptionsPayload.ts"
)]
pub(crate) struct DemoHttpOptionsPayload {
/// Selectable roles.
pub(crate) roles: std::vec::Vec<crate::DemoHttpRoleOption>,
/// Selectable methods.
pub(crate) methods: std::vec::Vec<crate::DemoHttpMethodOption>,
}
/// Request payload for one HTTP JSON-RPC demo execution.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_http/DemoHttpRequest.ts"
)]
pub(crate) struct DemoHttpRequest {
/// Required endpoint role used by the HTTP pool.
pub(crate) role: std::string::String,
/// JSON-RPC method name.
pub(crate) method: std::string::String,
/// Optional first argument string.
pub(crate) first_arg: std::option::Option<std::string::String>,
/// Optional JSON configuration string appended after the first argument.
pub(crate) config_json: std::option::Option<std::string::String>,
/// Optional raw JSON-RPC params array. When present, it overrides firstArg and configJson.
pub(crate) params_json: std::option::Option<std::string::String>,
}
/// Response payload for one HTTP JSON-RPC demo execution.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_http/DemoHttpExecutionPayload.ts"
)]
pub(crate) struct DemoHttpExecutionPayload {
/// Selected endpoint name.
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.
pub(crate) method: std::string::String,
/// Derived request kind.
pub(crate) request_kind: std::string::String,
/// Classified method family.
pub(crate) method_class: std::string::String,
/// Pretty JSON response text.
pub(crate) response_json: std::string::String,
}
pub(crate) async fn demo_http_execute_request_inner(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoHttpRequest,
) -> std::result::Result<crate::DemoHttpExecutionPayload, std::string::String> {
let role = request.role.trim().to_string();
if role.is_empty() {
return std::result::Result::Err("demo HTTP role must not be empty".to_string());
}
let method = request.method.trim().to_string();
if method.is_empty() {
return std::result::Result::Err("demo HTTP method must not be empty".to_string());
}
let params_json_value = match parse_optional_params_json(request.params_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let config_json_value = match parse_optional_json(request.config_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let params = match build_demo_http_params(
&method,
request.first_arg.as_deref(),
config_json_value,
params_json_value,
) {
std::result::Result::Ok(params) => params,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let selected_client = match state.http_pool().select_client_for_role_and_method(&role, &method)
{
std::result::Result::Ok(client) => client,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let response_value =
match selected_client.execute_json_rpc_result_raw(method.clone(), params).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let response_json = match serde_json::to_string_pretty(&response_value) {
std::result::Result::Ok(text) => text,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let method_class = kb_rpc::HttpClient::classify_method(&method);
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: kb_rpc::request_kind_from_method(&method),
method,
method_class: method_class_to_string(method_class).to_string(),
response_json,
});
}
pub(crate) fn build_http_role_options(
snapshots: std::vec::Vec<kb_rpc::HttpPoolClientSnapshot>,
) -> std::vec::Vec<crate::DemoHttpRoleOption> {
let mut roles = std::collections::BTreeMap::<
std::string::String,
std::collections::BTreeSet<std::string::String>,
>::new();
for snapshot in snapshots {
for role in snapshot.roles {
if !role.enabled {
continue;
}
let role_entry = roles.entry(role.role).or_default();
for request_kind in role.request_kinds {
role_entry.insert(request_kind);
}
}
}
let mut options = std::vec::Vec::new();
for (role, request_kinds) in roles {
options.push(crate::DemoHttpRoleOption {
role,
request_kinds: request_kinds.into_iter().collect(),
});
}
return options;
}
pub(crate) fn build_http_method_options() -> std::vec::Vec<crate::DemoHttpMethodOption> {
let methods = [
("getAccountInfo", "Compte Solana", true, true),
("getBalance", "Balance SOL", true, true),
("getBlock", "Bloc par slot", true, true),
("getBlockCommitment", "Commitment dun bloc", true, false),
("getBlockHeight", "Hauteur de bloc", false, true),
("getBlockProduction", "Production de blocs", false, true),
("getBlocks", "Liste de blocs", true, true),
("getBlocksWithLimit", "Liste de blocs avec limite", true, true),
("getClusterNodes", "Nœuds du cluster", false, false),
("getEpochInfo", "Époque courante", false, true),
("getEpochSchedule", "Planning des époques", false, false),
("getFeeForMessage", "Frais pour message", true, true),
("getFirstAvailableBlock", "Premier bloc disponible", false, false),
("getGenesisHash", "Genesis hash", false, false),
("getHealth", "Santé du nœud", false, false),
("getHighestSnapshotSlot", "Dernier snapshot", false, false),
("getIdentity", "Identité du nœud", false, false),
("getInflationGovernor", "Gouverneur inflation", false, true),
("getInflationRate", "Taux dinflation", false, false),
("getInflationReward", "Récompense inflation", true, true),
("getLargestAccounts", "Plus gros comptes", false, true),
("getLatestBlockhash", "Dernier blockhash", false, true),
("getLeaderSchedule", "Planning leaders", false, true),
("getMaxRetransmitSlot", "Slot retransmis maximum", false, false),
("getMaxShredInsertSlot", "Shred insert slot maximum", false, false),
("getMinimumBalanceForRentExemption", "Rent exemption", true, true),
("getMultipleAccounts", "Comptes multiples", true, true),
("getProgramAccounts", "Comptes de programme", true, true),
("getRecentPerformanceSamples", "Samples performance", false, true),
("getRecentPrioritizationFees", "Frais de priorité récents", false, true),
("getSignaturesForAddress", "Signatures par adresse", true, true),
("getSignatureStatuses", "Statuts signatures", true, true),
("getSlot", "Slot courant", false, true),
("getSlotLeader", "Leader du slot", false, true),
("getSlotLeaders", "Leaders de slots", true, true),
("getStakeActivation", "Activation stake", true, true),
("getStakeMinimumDelegation", "Minimum delegation", false, true),
("getSupply", "Supply SOL", false, true),
("getTokenAccountBalance", "Balance token account", true, true),
("getTokenAccountsByDelegate", "Token accounts par delegate", true, true),
("getTokenAccountsByOwner", "Token accounts par owner", true, true),
("getTokenLargestAccounts", "Plus gros comptes token", true, true),
("getTokenSupply", "Supply token", true, true),
("getTransaction", "Transaction", true, true),
("getTransactionCount", "Nombre de transactions", false, true),
("getVersion", "Version du nœud", false, false),
("getVoteAccounts", "Vote accounts", false, true),
("isBlockhashValid", "Validité blockhash", true, true),
("requestAirdrop", "Airdrop devnet", true, true),
("sendTransaction", "Envoi de transaction", true, true),
("simulateTransaction", "Simulation transaction", true, true),
];
let mut options = std::vec::Vec::new();
for (method, label, requires_first_arg, supports_config_json) in methods {
options.push(crate::DemoHttpMethodOption {
method: method.to_string(),
request_kind: kb_rpc::request_kind_from_method(method),
label: label.to_string(),
requires_first_arg,
supports_config_json,
});
}
return options;
}
fn parse_optional_json(
config_json: std::option::Option<std::string::String>,
) -> std::result::Result<std::option::Option<serde_json::Value>, std::string::String> {
let config_text = match config_json {
std::option::Option::Some(value) => value.trim().to_string(),
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
};
if config_text.is_empty() {
return std::result::Result::Ok(std::option::Option::None);
}
let parse_result = serde_json::from_str::<serde_json::Value>(&config_text);
return match parse_result {
std::result::Result::Ok(value) => std::result::Result::Ok(std::option::Option::Some(value)),
std::result::Result::Err(error) => {
std::result::Result::Err(format!("invalid configJson: {error}"))
},
};
}
fn parse_optional_params_json(
params_json: std::option::Option<std::string::String>,
) -> std::result::Result<std::option::Option<std::vec::Vec<serde_json::Value>>, std::string::String>
{
let params_text = match params_json {
std::option::Option::Some(value) => value.trim().to_string(),
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
};
if params_text.is_empty() {
return std::result::Result::Ok(std::option::Option::None);
}
let parse_result = serde_json::from_str::<serde_json::Value>(&params_text);
let value = match parse_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("invalid paramsJson: {error}"));
},
};
let array = match value.as_array() {
std::option::Option::Some(array) => array.clone(),
std::option::Option::None => {
return std::result::Result::Err("paramsJson must be a JSON array".to_string());
},
};
return std::result::Result::Ok(std::option::Option::Some(array));
}
fn build_demo_http_params(
method: &str,
first_arg: std::option::Option<&str>,
config_json: std::option::Option<serde_json::Value>,
params_json: std::option::Option<std::vec::Vec<serde_json::Value>>,
) -> std::result::Result<std::vec::Vec<serde_json::Value>, std::string::String> {
if let std::option::Option::Some(params) = params_json {
return std::result::Result::Ok(params);
}
let needs_first_arg = method == "getBalance"
|| method == "getAccountInfo"
|| method == "getBlock"
|| method == "getBlockCommitment"
|| method == "getBlocks"
|| method == "getBlocksWithLimit"
|| method == "getFeeForMessage"
|| method == "getInflationReward"
|| method == "getMinimumBalanceForRentExemption"
|| method == "getMultipleAccounts"
|| method == "getProgramAccounts"
|| method == "getSignaturesForAddress"
|| method == "getSignatureStatuses"
|| method == "getSlotLeaders"
|| method == "getStakeActivation"
|| method == "getTokenAccountBalance"
|| method == "getTokenAccountsByDelegate"
|| method == "getTokenAccountsByOwner"
|| method == "getTokenLargestAccounts"
|| method == "getTokenSupply"
|| method == "getTransaction"
|| method == "isBlockhashValid"
|| method == "requestAirdrop"
|| method == "sendTransaction"
|| method == "simulateTransaction";
if needs_first_arg {
let first_arg_value = match first_arg {
std::option::Option::Some(value) => value.trim(),
std::option::Option::None => "",
};
if first_arg_value.is_empty() {
return std::result::Result::Err(format!("method '{method}' requires firstArg"));
}
let mut params = std::vec::Vec::new();
params.push(serde_json::Value::String(first_arg_value.to_string()));
if let std::option::Option::Some(config_value) = config_json {
params.push(config_value);
}
return std::result::Result::Ok(params);
}
let mut params = std::vec::Vec::new();
if let std::option::Option::Some(config_value) = config_json {
params.push(config_value);
}
return std::result::Result::Ok(params);
}
fn method_class_to_string(method_class: kb_rpc::HttpMethodClass) -> &'static str {
return match method_class {
kb_rpc::HttpMethodClass::GeneralRpc => "GeneralRpc",
kb_rpc::HttpMethodClass::SendTransaction => "SendTransaction",
kb_rpc::HttpMethodClass::HeavyRead => "HeavyRead",
};
}

View File

@@ -0,0 +1,447 @@
// file: kb_app_demo/src/demo_spl_ata.rs
// version: 3
//! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads.
use ts_rs::TS; // rust-rules: derive-import
/// Request for one representative ATA creation on Devnet.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_ata/DemoExecutionSplAtaRequest.ts"
)]
pub(crate) struct DemoExecutionSplAtaRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Wallet owner used by canonical derivation.
pub(crate) wallet_owner: std::string::String,
/// Mint account.
pub(crate) mint: std::string::String,
/// `classic` or `token_2022`.
pub(crate) token_program: std::string::String,
/// `create` or `create_idempotent`.
pub(crate) mode: std::string::String,
/// Whether to sign and submit after simulation.
pub(crate) submit: bool,
/// Explicit operator confirmation.
pub(crate) operator_confirmed: bool,
/// Whether post-execution outputs should be force-replayed.
pub(crate) force_post_validation_replay: bool,
}
/// Request used to derive the representative wallet ATA before execution.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_ata/DemoSplAtaDerivationRequest.ts"
)]
pub(crate) struct DemoSplAtaDerivationRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Mint account.
pub(crate) mint: std::string::String,
/// `classic` or `token_2022`.
pub(crate) token_program: std::string::String,
}
/// Readonly payer, wallet, Token Program and derived ATA values.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_ata/DemoSplAtaDerivationPayload.ts"
)]
pub(crate) struct DemoSplAtaDerivationPayload {
/// Profile wallet used as payer.
pub(crate) payer: std::string::String,
/// Wallet owner selected by the representative panel.
pub(crate) wallet_owner: std::string::String,
/// Exact Token Program ID.
pub(crate) token_program_id: std::string::String,
/// Canonically derived ATA.
pub(crate) associated_token_account: std::string::String,
}
/// UI-safe ATA execution result.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_ata/DemoExecutionSplAtaSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionSplAtaSummaryPayload {
/// Stable operation code.
pub(crate) operation: std::string::String,
/// Profile name.
pub(crate) profile_name: std::string::String,
/// Profile wallet public key.
pub(crate) wallet_public_key: std::string::String,
/// Target Token Program ID.
pub(crate) token_program_id: std::string::String,
/// Derived ATA from the exact plan.
pub(crate) associated_token_account: std::string::String,
/// Wallet balance as lossless JSON text.
pub(crate) balance_lamports: std::string::String,
/// Fee estimate as lossless JSON text.
pub(crate) fee_lamports: std::option::Option<std::string::String>,
/// Aggregate stateful readiness.
pub(crate) readiness_status: std::string::String,
/// Exact simulation outcome.
pub(crate) simulation_success: bool,
/// Runtime simulation error.
pub(crate) simulation_error: std::option::Option<std::string::String>,
/// Submitted signature.
pub(crate) transaction_signature: std::option::Option<std::string::String>,
/// Terminal confirmation status.
pub(crate) confirmation_status: std::option::Option<std::string::String>,
/// Number of ATA-owned materialized facts.
pub(crate) materialization_count: u32,
/// Whether the second replay created no output.
pub(crate) idempotence_validated: bool,
/// Exact plan JSON.
pub(crate) plan_json: std::string::String,
/// Stateful readiness JSON.
pub(crate) readiness_json: std::string::String,
/// Simulation JSON.
pub(crate) simulation_json: std::string::String,
/// Confirmation and replay diagnostics JSON.
pub(crate) diagnostics_json: std::string::String,
}
/// Bounded ATA lifecycle journal request.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_ata/DemoSplAtaJournalRequest.ts"
)]
pub(crate) struct DemoSplAtaJournalRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Optional partial signature.
pub(crate) signature_contains: std::option::Option<std::string::String>,
/// Optional exact mint.
pub(crate) mint: std::option::Option<std::string::String>,
/// Optional exact ATA.
pub(crate) associated_token_account: std::option::Option<std::string::String>,
/// Optional exact materialized operation.
pub(crate) operation: std::option::Option<std::string::String>,
/// Maximum returned rows.
pub(crate) limit: u32,
}
/// One UI-safe ATA lifecycle journal row.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_ata/DemoSplAtaJournalRow.ts"
)]
pub(crate) struct DemoSplAtaJournalRow {
/// Transaction signature.
pub(crate) signature: std::string::String,
/// Slot as lossless JSON text.
pub(crate) slot: std::string::String,
/// Materialized family.
pub(crate) family: std::string::String,
/// Lifecycle operation.
pub(crate) operation: std::option::Option<std::string::String>,
/// Exact mint when present.
pub(crate) mint: std::option::Option<std::string::String>,
/// Exact ATA when present.
pub(crate) associated_token_account: std::option::Option<std::string::String>,
/// Full bounded payload JSON.
pub(crate) payload_json: std::string::String,
}
pub(crate) async fn load_profile_wallet(
profile: &kb_config::ProfileConfig,
) -> std::result::Result<kb_wallet::TemporaryWallet, std::string::String> {
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
let directory = if configured.is_absolute() {
configured
} else {
crate::workspace_root_dir().join(configured)
};
let store = match kb_wallet::TemporaryWalletStore::new(directory) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let alias = match kb_wallet::WalletAlias::parse(profile.wallet.temporary_wallet_alias.clone()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return match store.load_or_create(alias).await {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
pub(crate) fn parse_token_program(
value: &str,
) -> std::result::Result<
kb_executor_spl_associated_token_account::SplAssociatedTokenProgram,
std::string::String,
> {
return match value.trim() {
"classic" => std::result::Result::Ok(
kb_executor_spl_associated_token_account::SplAssociatedTokenProgram::Classic,
),
"token_2022" => std::result::Result::Ok(
kb_executor_spl_associated_token_account::SplAssociatedTokenProgram::Token2022,
),
_ => std::result::Result::Err("Token Program must be classic or token_2022".to_string()),
};
}
pub(crate) fn derive_ata(
wallet: &str,
mint: &str,
token_program: &str,
) -> std::result::Result<std::string::String, std::string::String> {
let wallet: solana_pubkey::Pubkey = match wallet.parse() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("invalid wallet: {error}"));
},
};
let mint: solana_pubkey::Pubkey = match mint.parse() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("invalid mint: {error}"));
},
};
let token_program: solana_pubkey::Pubkey = match token_program.parse() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("invalid Token Program: {error}"));
},
};
return std::result::Result::Ok(
spl_associated_token_account_interface::address::get_associated_token_address_with_program_id(
&wallet,
&mint,
&token_program,
)
.to_string(),
);
}
pub(crate) fn demo_spl_ata_summary_payload(
summary: kb_pipeline::DevnetSplAssociatedTokenAccountExecutionSummary,
) -> crate::DemoExecutionSplAtaSummaryPayload {
let (associated_token_account, token_program_id) = match summary.plan.instructions.first() {
std::option::Option::Some(instruction) => {
let ata = match instruction.accounts.get(1) {
std::option::Option::Some(account) => account.pubkey.0.clone(),
std::option::Option::None => std::string::String::new(),
};
let token_program = match instruction.accounts.last() {
std::option::Option::Some(account) => account.pubkey.0.clone(),
std::option::Option::None => std::string::String::new(),
};
(ata, token_program)
},
std::option::Option::None => (std::string::String::new(), std::string::String::new()),
};
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status = summary.confirmation.as_ref().map(|value| {
return crate::confirmation_status_code(value.status);
});
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
});
});
let materialization_count = match u32::try_from(summary.materializations.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => u32::MAX,
};
let diagnostics = serde_json::json!({
"confirmation": summary.confirmation.as_ref(),
"postStateValidation": summary.post_state_validation.as_ref(),
"postExecution": summary.post_execution.as_ref(),
"backfill": summary.backfill.as_ref().map(|value| {
return serde_json::json!({
"candidatesCompleted": value.candidates_completed,
"candidatesCancelled": value.candidates_cancelled,
"candidatesNotStarted": value.candidates_not_started,
"canonicalInserted": value.canonical_inserted,
"canonicalSkipped": value.canonical_skipped,
"existingSkipped": value.existing_skipped,
"missing": value.missing,
"failed": value.failed
});
}),
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
return serde_json::json!({
"selected": value.selected,
"extracted": value.extracted,
"skipped": value.skipped,
"failed": value.failed,
"cancelled": value.cancelled
});
}),
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
return serde_json::json!({
"campaignId": value.campaign_id,
"selected": value.selected,
"completed": value.completed,
"unmatched": value.unmatched,
"failedInputs": value.failed_inputs,
"cancelled": value.cancelled
});
}),
"idempotenceReplay": summary.idempotence_replay.as_ref().map(|value| {
return serde_json::json!({
"campaignId": value.campaign_id,
"selected": value.selected,
"completed": value.completed,
"unmatched": value.unmatched,
"failedInputs": value.failed_inputs,
"cancelled": value.cancelled,
"materializedOutputs": value.processors.iter().map(|processor| {
return processor.materialized_outputs;
}).sum::<u64>(),
"materializationRefused": value.processors.iter().map(|processor| {
return processor.materialization_refused;
}).sum::<u64>()
});
}),
"materializations": summary.materializations.iter().map(|value| {
return serde_json::json!({
"processorName": value.processor_name,
"outputKey": value.output_key,
"family": value.materialized_family,
"signature": value.signature,
"slot": value.slot.to_string(),
"payload": value.payload_json
});
}).collect::<std::vec::Vec<_>>()
});
let plan_json = crate::pretty_json(&summary.plan);
let readiness_json = crate::pretty_json(&summary.stateful_readiness);
let simulation_json = crate::pretty_json(&summary.simulation);
let diagnostics_json = crate::pretty_json(&diagnostics);
return crate::DemoExecutionSplAtaSummaryPayload {
operation: summary.stateful_readiness.operation_code,
profile_name: summary.profile_name,
wallet_public_key: summary.wallet.public_key,
token_program_id,
associated_token_account,
balance_lamports: summary.balance_lamports.to_string(),
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
readiness_status: match summary.stateful_readiness.status {
kb_pipeline::SplAssociatedTokenAccountStatefulReadinessStatus::Ready => {
"ready".to_string()
},
kb_pipeline::SplAssociatedTokenAccountStatefulReadinessStatus::Blocked => {
"blocked".to_string()
},
},
simulation_success: summary.simulation.success,
simulation_error: summary.simulation.error,
transaction_signature,
confirmation_status,
materialization_count,
idempotence_validated,
plan_json,
readiness_json,
simulation_json,
diagnostics_json,
};
}
pub(crate) fn journal_matches(
payload: &serde_json::Value,
request: &crate::DemoSplAtaJournalRequest,
) -> bool {
for (expected, key) in [
(&request.operation, "operation"),
(&request.mint, "mint"),
(&request.associated_token_account, "associatedTokenAccount"),
] {
if expected.as_ref().is_some_and(|value| {
return payload.get(key).and_then(serde_json::Value::as_str)
!= std::option::Option::Some(value.as_str());
}) {
return false;
}
}
return true;
}
pub(crate) fn journal_row(
row: kb_store_core::MaterializedEventQueryRow,
) -> crate::DemoSplAtaJournalRow {
let text = |key: &str| {
return row
.payload_json
.get(key)
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
};
let operation = text("operation");
let mint = text("mint");
let associated_token_account = text("associatedTokenAccount");
let payload_json = crate::pretty_json(&row.payload_json);
return crate::DemoSplAtaJournalRow {
signature: row.signature,
slot: row.slot.to_string(),
family: row.materialized_family,
operation,
mint,
associated_token_account,
payload_json,
};
}
#[cfg(test)]
mod tests {
#[test]
fn classic_and_token_2022_derivations_are_distinct_and_canonical() {
let wallet = kb_program_ids::SYSTEM_PROGRAM_ID;
let mint = "So11111111111111111111111111111111111111112";
let classic = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_PROGRAM_ID)
.unwrap_or_else(|error| panic!("classic derivation failed: {error}"));
let token_2022 = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID)
.unwrap_or_else(|error| panic!("Token-2022 derivation failed: {error}"));
assert_eq!(classic, "aqxoAhCwpy3oB1BpNw9hL1HdLYLgPpbPjzxDrrQj3Fs");
assert_eq!(token_2022, "2sZUUBGq1i6aE47ZoxCaCW89jmYm2EXLPPmNMgMDXHMS");
}
#[test]
fn lifecycle_journal_filters_are_exact() {
let request = crate::DemoSplAtaJournalRequest {
profile_name: "local_devnet".to_string(),
signature_contains: std::option::Option::None,
mint: std::option::Option::Some("mint111".to_string()),
associated_token_account: std::option::Option::Some("ata111".to_string()),
operation: std::option::Option::Some("create_idempotent".to_string()),
limit: 100,
};
assert!(crate::journal_matches(
&serde_json::json!({
"operation": "create_idempotent",
"mint": "mint111",
"associatedTokenAccount": "ata111"
}),
&request,
));
assert!(!crate::journal_matches(
&serde_json::json!({
"operation": "create",
"mint": "mint111",
"associatedTokenAccount": "ata111"
}),
&request,
));
}
}

View File

@@ -0,0 +1,493 @@
// file: kb_app_demo/src/demo_spl_token.rs
// version: 2
//! Thin Tauri adapter for classic SPL Token execution and materialized journals.
use ts_rs::TS; // rust-rules: derive-import
/// Request for one checked classic SPL Token transfer on Devnet.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_token/DemoExecutionSplTokenRequest.ts"
)]
pub(crate) struct DemoExecutionSplTokenRequest {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Source token account.
pub(crate) source: std::string::String,
/// Exact mint account carried by `TransferChecked`.
pub(crate) mint: std::string::String,
/// Destination token account.
pub(crate) destination: std::string::String,
/// Simple authority resolved by the selected profile wallet.
pub(crate) authority: std::string::String,
/// Exact raw amount represented as a decimal string.
pub(crate) amount_raw: std::string::String,
/// Expected mint decimals carried by the wire.
pub(crate) decimals: u8,
/// Whether the transaction should be signed and submitted after simulation.
pub(crate) submit: bool,
/// Explicit operator confirmation for signed submission.
pub(crate) operator_confirmed: bool,
/// Whether post-execution core and decode outputs should be force-replayed.
pub(crate) force_post_validation_replay: bool,
}
/// UI-safe result of one checked SPL Token transfer orchestration.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_token/DemoExecutionSplTokenSummaryPayload.ts"
)]
pub(crate) struct DemoExecutionSplTokenSummaryPayload {
/// Stable operation code.
pub(crate) operation: std::string::String,
/// Profile used by the orchestration.
pub(crate) profile_name: std::string::String,
/// Classified cluster code.
pub(crate) cluster: std::string::String,
/// Genesis hash returned by the endpoint.
pub(crate) genesis_hash: std::string::String,
/// Source wallet public key.
pub(crate) wallet_public_key: std::string::String,
/// Exact wallet balance rendered without JSON integer precision loss.
pub(crate) balance_lamports: std::string::String,
/// Estimated fee rendered without JSON integer precision loss.
pub(crate) fee_lamports: std::option::Option<std::string::String>,
/// Exact raw token amount.
pub(crate) amount_raw: std::string::String,
/// Expected mint decimals.
pub(crate) decimals: u8,
/// Aggregate stateful preflight status.
pub(crate) readiness_status: std::string::String,
/// Whether exact simulation succeeded.
pub(crate) simulation_success: bool,
/// Runtime simulation error when present.
pub(crate) simulation_error: std::option::Option<std::string::String>,
/// Submitted transaction signature.
pub(crate) transaction_signature: std::option::Option<std::string::String>,
/// Terminal confirmation status.
pub(crate) confirmation_status: std::option::Option<std::string::String>,
/// Whether canonical hydration was validated.
pub(crate) canonical_inserted: bool,
/// Whether core extraction was validated.
pub(crate) core_extracted: bool,
/// Whether SPL Token decode replay was validated.
pub(crate) decode_replayed: bool,
/// Number of materialized rows for the submitted instruction.
pub(crate) materialization_count: u32,
/// Whether the second replay produced no failure or new output.
pub(crate) idempotence_validated: bool,
/// Pretty JSON representation of the exact execution plan.
pub(crate) plan_json: std::string::String,
/// Pretty JSON representation of stateful readiness.
pub(crate) readiness_json: std::string::String,
/// Pretty JSON representation of the exact simulation result.
pub(crate) simulation_json: std::string::String,
/// Pretty JSON representation of confirmation and replay diagnostics.
pub(crate) diagnostics_json: std::string::String,
}
/// Bounded exact filters for the classic SPL Token materialized journal.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_token/DemoSplTokenJournalRequest.ts"
)]
pub(crate) struct DemoSplTokenJournalRequest {
/// Devnet profile whose PostgreSQL store is queried.
pub(crate) profile_name: std::string::String,
/// Optional partial signature handled by the bounded store query.
pub(crate) signature_contains: std::option::Option<std::string::String>,
/// Optional exact mint account.
pub(crate) mint: std::option::Option<std::string::String>,
/// Optional exact account occurring in the ordered account list.
pub(crate) account: std::option::Option<std::string::String>,
/// Optional exact materialized family.
pub(crate) family: std::option::Option<std::string::String>,
/// Optional exact operation code without the `spl_token.` prefix.
pub(crate) operation: std::option::Option<std::string::String>,
/// Maximum number of rows returned after typed filtering.
pub(crate) limit: u32,
}
/// UI-safe materialized classic SPL Token journal row.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_token/DemoSplTokenJournalRow.ts"
)]
pub(crate) struct DemoSplTokenJournalRow {
/// Materializer processor name.
pub(crate) processor_name: std::string::String,
/// Materializer processor version.
pub(crate) processor_version: std::string::String,
/// Stable processor-owned output key.
pub(crate) output_key: std::string::String,
/// Transaction signature.
pub(crate) signature: std::string::String,
/// Decimal slot rendered as text to preserve JSON precision.
pub(crate) slot: std::string::String,
/// Materialized family code.
pub(crate) family: std::string::String,
/// Exact operation when present.
pub(crate) operation: std::option::Option<std::string::String>,
/// Exact mint account when explicitly available.
pub(crate) mint: std::option::Option<std::string::String>,
/// Exact raw amount when carried by the materialized fact.
pub(crate) amount_raw: std::option::Option<std::string::String>,
/// Stable outer or inner instruction path when present.
pub(crate) instruction_path: std::option::Option<std::string::String>,
/// Ordered account keys preserved by the decoder.
pub(crate) account_keys: std::vec::Vec<std::string::String>,
/// Complete bounded typed payload rendered as JSON text.
pub(crate) payload_json: std::string::String,
/// Database creation timestamp.
pub(crate) created_at: std::string::String,
/// Database replacement timestamp.
pub(crate) updated_at: std::string::String,
}
pub(crate) fn demo_spl_token_summary_payload(
summary: kb_pipeline::DevnetSplTokenExecutionSummary,
amount_raw: std::string::String,
decimals: u8,
) -> crate::DemoExecutionSplTokenSummaryPayload {
let plan_json = crate::pretty_json(&summary.plan);
let readiness_json = crate::pretty_json(&summary.stateful_readiness);
let simulation_json = crate::pretty_json(&summary.simulation);
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status = summary.confirmation.as_ref().map(|value| {
return crate::confirmation_status_code(value.status);
});
let post_execution = summary.post_execution.as_ref();
let canonical_inserted = post_execution.is_some_and(|value| return value.canonical_inserted);
let core_extracted = post_execution.is_some_and(|value| return value.core_extracted);
let decode_replayed = post_execution.is_some_and(|value| return value.decode_replayed);
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& !replay.cancelled
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
});
});
let materialization_count = match u32::try_from(summary.materializations.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => u32::MAX,
};
let diagnostics = serde_json::json!({
"confirmation": summary.confirmation,
"postExecution": summary.post_execution,
"backfill": summary.backfill.as_ref().map(|value| {
return serde_json::json!({
"canonicalInserted": value.canonical_inserted,
"canonicalSkipped": value.canonical_skipped,
"existingSkipped": value.existing_skipped,
"missing": value.missing,
"failed": value.failed
});
}),
"coreExtraction": summary.core_extraction.as_ref().map(|value| {
return serde_json::json!({
"selected": value.selected,
"extracted": value.extracted,
"skipped": value.skipped,
"failed": value.failed,
"cancelled": value.cancelled
});
}),
"decodeReplay": summary.decode_replay.as_ref().map(|value| {
return replay_diagnostics(value);
}),
"idempotenceReplay": summary.idempotence_replay.as_ref().map(|value| {
return replay_diagnostics(value);
}),
"materializations": summary.materializations
});
return crate::DemoExecutionSplTokenSummaryPayload {
operation: summary.stateful_readiness.operation_code,
profile_name: summary.profile_name,
cluster: crate::execution_cluster_code(summary.cluster),
genesis_hash: summary.genesis_hash,
wallet_public_key: summary.wallet.public_key,
balance_lamports: summary.balance_lamports.to_string(),
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
amount_raw,
decimals,
readiness_status: match summary.stateful_readiness.status {
kb_pipeline::SplTokenStatefulReadinessStatus::Ready => "ready".to_string(),
kb_pipeline::SplTokenStatefulReadinessStatus::Blocked => "blocked".to_string(),
},
simulation_success: summary.simulation.success,
simulation_error: summary.simulation.error,
transaction_signature,
confirmation_status,
canonical_inserted,
core_extracted,
decode_replayed,
materialization_count,
idempotence_validated,
plan_json,
readiness_json,
simulation_json,
diagnostics_json: crate::pretty_json(&diagnostics),
};
}
fn replay_diagnostics(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json::Value {
return serde_json::json!({
"campaignId": summary.campaign_id,
"selected": summary.selected,
"completed": summary.completed,
"failedInputs": summary.failed_inputs,
"cancelled": summary.cancelled,
"processors": summary.processors.iter().map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
"version": processor.processor_version,
"decoded": processor.decoded,
"skipped": processor.skipped,
"failed": processor.failed,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
}).collect::<std::vec::Vec<_>>()
});
}
pub(crate) fn validate_journal_request(
request: crate::DemoSplTokenJournalRequest,
) -> std::result::Result<crate::DemoSplTokenJournalRequest, std::string::String> {
if request.limit == 0 || request.limit > kb_store_core::MAX_MATERIALIZED_EVENT_QUERY_ROWS {
return std::result::Result::Err(format!(
"journal limit must be between 1 and {}",
kb_store_core::MAX_MATERIALIZED_EVENT_QUERY_ROWS
));
}
if request.profile_name.trim().is_empty() {
return std::result::Result::Err("journal profile name must not be empty".to_string());
}
let signature_contains =
match bounded_optional(request.signature_contains, "signature filter", 128) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mint = match bounded_optional(request.mint, "mint filter", 64) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let account = match bounded_optional(request.account, "account filter", 64) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let operation = match bounded_optional(request.operation, "operation filter", 64) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let family = match bounded_optional(request.family, "family filter", 32) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if family.as_ref().is_some_and(|value| {
return !matches!(value.as_str(), "token_account" | "admin" | "risk");
}) {
return std::result::Result::Err(
"journal family must be token_account, admin or risk".to_string(),
);
}
return std::result::Result::Ok(crate::DemoSplTokenJournalRequest {
profile_name: request.profile_name.trim().to_string(),
signature_contains,
mint,
account,
family,
operation,
limit: request.limit,
});
}
fn bounded_optional(
value: std::option::Option<std::string::String>,
label: &str,
maximum_length: usize,
) -> std::result::Result<std::option::Option<std::string::String>, std::string::String> {
let trimmed = value.map(|text| return text.trim().to_string());
let trimmed = trimmed.filter(|text| return !text.is_empty());
if trimmed.as_ref().is_some_and(|text| return text.len() > maximum_length) {
return std::result::Result::Err(format!("{label} must not exceed {maximum_length} bytes"));
}
return std::result::Result::Ok(trimmed);
}
pub(crate) fn payload_matches(
payload: &serde_json::Value,
request: &crate::DemoSplTokenJournalRequest,
) -> bool {
if request.operation.as_ref().is_some_and(|expected| {
return payload.get("operation").and_then(serde_json::Value::as_str)
!= std::option::Option::Some(expected.as_str());
}) {
return false;
}
if request.mint.as_ref().is_some_and(|expected| {
return payload_mint(payload).as_deref() != std::option::Option::Some(expected.as_str());
}) {
return false;
}
if request.account.as_ref().is_some_and(|expected| {
return !payload_account_keys(payload).iter().any(|value| return value == expected);
}) {
return false;
}
return true;
}
pub(crate) fn demo_spl_token_journal_row(
row: kb_store_core::MaterializedEventQueryRow,
) -> DemoSplTokenJournalRow {
let operation = row
.payload_json
.get("operation")
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
let amount_raw = row
.payload_json
.get("amountRaw")
.or_else(|| {
return row
.payload_json
.get("parameters")
.and_then(|value| return value.get("amountRaw"));
})
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
let instruction_path = row
.payload_json
.get("instructionPath")
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
let payload_json = crate::pretty_json(&row.payload_json);
return DemoSplTokenJournalRow {
processor_name: row.processor_name,
processor_version: row.processor_version,
output_key: row.output_key,
signature: row.signature,
slot: row.slot.to_string(),
family: row.materialized_family,
operation,
mint: payload_mint(&row.payload_json),
amount_raw,
instruction_path,
account_keys: payload_account_keys(&row.payload_json),
payload_json,
created_at: row.created_at,
updated_at: row.updated_at,
};
}
fn payload_mint(payload: &serde_json::Value) -> std::option::Option<std::string::String> {
let direct = payload.get("mint").and_then(serde_json::Value::as_str);
if let std::option::Option::Some(value) = direct {
return std::option::Option::Some(value.to_string());
}
return payload.get("accounts").and_then(serde_json::Value::as_array).and_then(|rows| {
return rows.iter().find_map(|row| {
if row.get("role").and_then(serde_json::Value::as_str)
!= std::option::Option::Some("mint")
{
return std::option::Option::None;
}
return row
.get("accountKey")
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
});
});
}
fn payload_account_keys(payload: &serde_json::Value) -> std::vec::Vec<std::string::String> {
let values = payload.get("accounts").and_then(serde_json::Value::as_array).map(|rows| {
return rows
.iter()
.filter_map(|row| {
return row
.get("accountKey")
.and_then(serde_json::Value::as_str)
.map(std::string::ToString::to_string);
})
.collect();
});
return match values {
std::option::Option::Some(values) => values,
std::option::Option::None => std::vec::Vec::new(),
};
}
#[cfg(test)]
mod tests {
#[test]
fn journal_filters_are_exact_bounded_and_preserve_raw_amounts() {
let request = crate::DemoSplTokenJournalRequest {
profile_name: "local_devnet".to_string(),
signature_contains: std::option::Option::None,
mint: std::option::Option::Some("mint111".to_string()),
account: std::option::Option::Some("source111".to_string()),
family: std::option::Option::Some("token_account".to_string()),
operation: std::option::Option::Some("transfer_checked".to_string()),
limit: 25,
};
let payload = serde_json::json!({
"operation": "transfer_checked",
"mint": "mint111",
"amountRaw": "18446744073709551615",
"accounts": [
{"role": "source", "accountKey": "source111"},
{"role": "mint", "accountKey": "mint111"}
]
});
assert!(crate::payload_matches(&payload, &request));
assert_eq!(
payload.get("amountRaw").and_then(serde_json::Value::as_str),
std::option::Option::Some("18446744073709551615")
);
let row = crate::demo_spl_token_journal_row(kb_store_core::MaterializedEventQueryRow {
processor_name: "spl_token_accounts".to_string(),
processor_version: "0.4.4".to_string(),
input_key: "input".to_string(),
output_key: "output".to_string(),
source_event_key: "event".to_string(),
source_decoder_name: "spl_token".to_string(),
source_decoder_version: "0.4.4".to_string(),
signature: "signature".to_string(),
slot: u64::MAX,
materialized_family: "token_account".to_string(),
payload_json: payload,
created_at: "created".to_string(),
updated_at: "updated".to_string(),
});
assert_eq!(row.slot, u64::MAX.to_string());
assert_eq!(row.amount_raw, std::option::Option::Some(u64::MAX.to_string()));
}
#[test]
fn journal_rejects_unbounded_or_unknown_family_requests() {
let request = crate::DemoSplTokenJournalRequest {
profile_name: "local_devnet".to_string(),
signature_contains: std::option::Option::None,
mint: std::option::Option::None,
account: std::option::Option::None,
family: std::option::Option::Some("fee".to_string()),
operation: std::option::Option::None,
limit: 501,
};
assert!(crate::validate_journal_request(request).is_err());
}
}

View File

@@ -0,0 +1,423 @@
// file: kb_app_demo/src/demo_spl_token_2022.rs
// version: 3
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
use ts_rs::TS; // rust-rules: derive-import
/// Public values loaded from one persisted Token-2022 validation fixture.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/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.
pub(crate) mint: std::string::String,
/// Source token account.
pub(crate) source: std::string::String,
/// Destination token account.
pub(crate) destination: std::string::String,
/// Dedicated empty account reserved for CloseAccount validation.
pub(crate) close_account: std::string::String,
/// Delegate account.
pub(crate) delegate: std::string::String,
/// Profile-wallet authority.
pub(crate) authority: std::string::String,
/// Freeze authority when configured on the mint.
pub(crate) freeze_authority: std::string::String,
/// Mint decimals.
pub(crate) decimals: u8,
/// Default raw amount for the selected scenario.
pub(crate) default_amount_raw: std::string::String,
}
/// Request for one independent public Token-2022 Devnet scenario.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_token_2022/DemoExecutionSplToken2022Request.ts"
)]
pub(crate) struct DemoExecutionSplToken2022Request {
/// Selected Devnet profile.
pub(crate) profile_name: std::string::String,
/// Stable scenario identifier.
pub(crate) scenario_id: std::string::String,
/// Source or target token account depending on the selected operation.
pub(crate) source: std::string::String,
/// Exact mint account.
pub(crate) mint: std::string::String,
/// Destination token account or close-account lamport destination.
pub(crate) destination: std::string::String,
/// Delegate account used by `ApproveChecked`.
pub(crate) delegate: std::string::String,
/// Owner or mint authority resolved by the selected profile wallet.
pub(crate) authority: std::string::String,
/// Freeze authority used by freeze and thaw scenarios.
pub(crate) freeze_authority: std::string::String,
/// Exact raw amount represented as a decimal string.
pub(crate) amount_raw: std::string::String,
/// Expected mint decimals carried by checked instructions.
pub(crate) decimals: u8,
/// Whether the transaction should be signed and submitted after simulation.
pub(crate) submit: bool,
/// Explicit operator confirmation for signed submission.
pub(crate) operator_confirmed: bool,
/// Whether post-execution core and decode outputs should be force-replayed.
pub(crate) force_post_validation_replay: bool,
}
/// UI-safe result of one public Token-2022 Devnet orchestration.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_spl_token_2022/DemoExecutionSplToken2022SummaryPayload.ts"
)]
pub(crate) struct DemoExecutionSplToken2022SummaryPayload {
/// Stable scenario identifier.
pub(crate) scenario_id: std::string::String,
/// Stable operation code.
pub(crate) operation: std::string::String,
/// Profile used by the orchestration.
pub(crate) profile_name: std::string::String,
/// Classified cluster code.
pub(crate) cluster: std::string::String,
/// Genesis hash returned by the endpoint.
pub(crate) genesis_hash: std::string::String,
/// Source wallet public key.
pub(crate) wallet_public_key: std::string::String,
/// Exact wallet balance rendered without JSON integer precision loss.
pub(crate) balance_lamports: std::string::String,
/// Estimated fee rendered without JSON integer precision loss.
pub(crate) fee_lamports: std::option::Option<std::string::String>,
/// Whether exact simulation succeeded.
pub(crate) simulation_success: bool,
/// Runtime simulation error when present.
pub(crate) simulation_error: std::option::Option<std::string::String>,
/// Submitted transaction signature.
pub(crate) transaction_signature: std::option::Option<std::string::String>,
/// Terminal confirmation status.
pub(crate) confirmation_status: std::option::Option<std::string::String>,
/// Whether canonical hydration was validated.
pub(crate) canonical_inserted: bool,
/// Whether core extraction was validated.
pub(crate) core_extracted: bool,
/// Whether Token-2022 decode replay was validated.
pub(crate) decode_replayed: bool,
/// Number of materialized rows for the submitted instruction.
pub(crate) materialization_count: u32,
/// Whether the second replay produced no failure or new output.
pub(crate) idempotence_validated: bool,
/// Pretty JSON representation of the exact execution plan.
pub(crate) plan_json: std::string::String,
/// Pretty JSON representation of stateful preflight.
pub(crate) preflight_json: std::string::String,
/// Pretty JSON representation of the exact simulation result.
pub(crate) simulation_json: std::string::String,
/// Pretty JSON representation of confirmation and replay diagnostics.
pub(crate) diagnostics_json: std::string::String,
}
pub(crate) fn operation_from_request(
request: &crate::DemoExecutionSplToken2022Request,
) -> std::result::Result<kb_executor_spl_token_2022::SplToken2022Operation, std::string::String> {
let authority = kb_executor_spl_token_2022::SplTokenAuthority {
authority: kb_model::Pubkey(request.authority.trim().to_string()),
multisig_signers: std::vec::Vec::new(),
};
let freeze_authority = kb_executor_spl_token_2022::SplTokenAuthority {
authority: kb_model::Pubkey(request.freeze_authority.trim().to_string()),
multisig_signers: std::vec::Vec::new(),
};
let value = match request.scenario_id.trim() {
"token_2022_mint_to_checked" => {
kb_executor_spl_token_2022::SplTokenSingleOperation::MintToChecked {
mint: kb_model::Pubkey(request.mint.trim().to_string()),
destination: kb_model::Pubkey(request.source.trim().to_string()),
authority,
amount: kb_executor_spl_token_2022::SplTokenAmount(
request.amount_raw.trim().to_string(),
),
decimals: request.decimals,
}
},
"token_2022_transfer_checked" => {
kb_executor_spl_token_2022::SplTokenSingleOperation::TransferChecked {
source: kb_model::Pubkey(request.source.trim().to_string()),
mint: kb_model::Pubkey(request.mint.trim().to_string()),
destination: kb_model::Pubkey(request.destination.trim().to_string()),
authority,
amount: kb_executor_spl_token_2022::SplTokenAmount(
request.amount_raw.trim().to_string(),
),
decimals: request.decimals,
}
},
"token_2022_approve_checked" => {
kb_executor_spl_token_2022::SplTokenSingleOperation::ApproveChecked {
source: kb_model::Pubkey(request.source.trim().to_string()),
mint: kb_model::Pubkey(request.mint.trim().to_string()),
delegate: kb_model::Pubkey(request.delegate.trim().to_string()),
authority,
amount: kb_executor_spl_token_2022::SplTokenAmount(
request.amount_raw.trim().to_string(),
),
decimals: request.decimals,
}
},
"token_2022_revoke" => kb_executor_spl_token_2022::SplTokenSingleOperation::Revoke {
source: kb_model::Pubkey(request.source.trim().to_string()),
authority,
},
"token_2022_burn_checked" => {
kb_executor_spl_token_2022::SplTokenSingleOperation::BurnChecked {
source: kb_model::Pubkey(request.source.trim().to_string()),
mint: kb_model::Pubkey(request.mint.trim().to_string()),
authority,
amount: kb_executor_spl_token_2022::SplTokenAmount(
request.amount_raw.trim().to_string(),
),
decimals: request.decimals,
}
},
"token_2022_freeze_account" => {
kb_executor_spl_token_2022::SplTokenSingleOperation::FreezeAccount {
account: kb_model::Pubkey(request.source.trim().to_string()),
mint: kb_model::Pubkey(request.mint.trim().to_string()),
authority: freeze_authority,
}
},
"token_2022_thaw_account" => {
kb_executor_spl_token_2022::SplTokenSingleOperation::ThawAccount {
account: kb_model::Pubkey(request.source.trim().to_string()),
mint: kb_model::Pubkey(request.mint.trim().to_string()),
authority: freeze_authority,
}
},
"token_2022_close_destination" => {
kb_executor_spl_token_2022::SplTokenSingleOperation::CloseAccount {
account: kb_model::Pubkey(request.source.trim().to_string()),
destination: kb_model::Pubkey(request.destination.trim().to_string()),
authority,
}
},
other => {
return std::result::Result::Err(format!(
"unsupported public Token-2022 Devnet scenario {other}"
));
},
};
return std::result::Result::Ok(
kb_executor_spl_token_2022::SplToken2022Operation::Instruction {
value: std::boxed::Box::new(value),
},
);
}
pub(crate) fn demo_spl_token_2022_summary_payload(
scenario_id: std::string::String,
operation: std::string::String,
summary: kb_pipeline::DevnetSplToken2022ExecutionSummary,
) -> crate::DemoExecutionSplToken2022SummaryPayload {
let plan_json = crate::pretty_json(&summary.plan);
let preflight_json = crate::pretty_json(&summary.stateful_preflight);
let simulation_json = crate::pretty_json(&summary.simulation);
let transaction_signature =
summary.send_result.as_ref().map(|value| return value.signature.0.clone());
let confirmation_status = summary.confirmation.as_ref().map(|value| {
return crate::confirmation_status_code(value.status);
});
let post_execution = summary.post_execution.as_ref();
let canonical_inserted = post_execution.is_some_and(|value| return value.canonical_inserted);
let core_extracted = post_execution.is_some_and(|value| return value.core_extracted);
let decode_replayed = post_execution.is_some_and(|value| return value.decode_replayed);
let idempotence_validated = summary.idempotence_replay.as_ref().is_some_and(|replay| {
return replay.failed_inputs == 0
&& !replay.cancelled
&& replay.processors.iter().all(|processor| {
return processor.failed == 0
&& processor.materialized_outputs == 0
&& processor.materialization_refused == 0;
});
});
let materialization_count = match u32::try_from(summary.materializations.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => u32::MAX,
};
let diagnostics = serde_json::json!({
"confirmation": summary.confirmation,
"postExecution": summary.post_execution,
"backfill": summary.backfill.as_ref().map(backfill_summary_json),
"coreExtraction": summary.core_extraction.as_ref().map(core_extraction_summary_json),
"decodeReplay": summary.decode_replay.as_ref().map(decode_replay_summary_json),
"idempotenceReplay": summary.idempotence_replay.as_ref().map(decode_replay_summary_json),
"materializations": summary.materializations
});
return crate::DemoExecutionSplToken2022SummaryPayload {
scenario_id,
operation,
profile_name: summary.profile_name,
cluster: crate::execution_cluster_code(summary.cluster),
genesis_hash: summary.genesis_hash,
wallet_public_key: summary.wallet.public_key,
balance_lamports: summary.balance_lamports.to_string(),
fee_lamports: summary.fee.fee_lamports.map(|value| return value.to_string()),
simulation_success: summary.simulation.success,
simulation_error: summary.simulation.error,
transaction_signature,
confirmation_status,
canonical_inserted,
core_extracted,
decode_replayed,
materialization_count,
idempotence_validated,
plan_json,
preflight_json,
simulation_json,
diagnostics_json: crate::pretty_json(&diagnostics),
};
}
pub(crate) fn parse_fixture(
contents: &str,
) -> std::collections::BTreeMap<std::string::String, std::string::String> {
let mut values = std::collections::BTreeMap::new();
for line in contents.lines() {
let trimmed = line.trim();
let assignment = match trimmed.strip_prefix("export ") {
std::option::Option::Some(value) => value,
std::option::Option::None => continue,
};
let (name, raw_value) = match assignment.split_once('=') {
std::option::Option::Some(value) => value,
std::option::Option::None => continue,
};
let value = raw_value.trim().trim_matches('\'').trim_matches('"').to_string();
values.insert(name.trim().to_string(), value);
}
return values;
}
fn backfill_summary_json(summary: &kb_pipeline::BackfillSummary) -> serde_json::Value {
return serde_json::json!({
"captureSessionId": summary.capture_session_id,
"filterCode": summary.filter_code,
"role": summary.role,
"provider": summary.provider,
"endpointCode": summary.endpoint_code,
"pagesFetched": summary.pages_fetched,
"candidatesSelected": summary.candidates_selected,
"candidatesStarted": summary.candidates_started,
"candidatesCompleted": summary.candidates_completed,
"candidatesCancelled": summary.candidates_cancelled,
"candidatesNotStarted": summary.candidates_not_started,
"transactionsReceived": summary.transactions_received,
"canonicalInserted": summary.canonical_inserted,
"canonicalSkipped": summary.canonical_skipped,
"existingSkipped": summary.existing_skipped,
"missing": summary.missing,
"failed": summary.failed,
"observationsInserted": summary.observations_inserted,
"attempts": summary.attempts,
"cancelled": summary.cancelled,
"resumeBeforeSignature": summary.resume_before_signature,
"startedAt": summary.started_at,
"finishedAt": summary.finished_at
});
}
fn core_extraction_summary_json(summary: &kb_pipeline::CoreExtractionSummary) -> serde_json::Value {
return serde_json::json!({
"processorVersion": summary.processor_version,
"selected": summary.selected,
"started": summary.started,
"completed": summary.completed,
"skipped": summary.skipped,
"extracted": summary.extracted,
"failed": summary.failed,
"cancelledCandidates": summary.cancelled_candidates,
"notStarted": summary.not_started,
"cancelled": summary.cancelled,
"startedAt": summary.started_at,
"finishedAt": summary.finished_at
});
}
fn decode_replay_summary_json(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json::Value {
let processors = summary
.processors
.iter()
.map(|processor| {
return serde_json::json!({
"name": processor.processor_name,
"version": processor.processor_version,
"dispatched": processor.dispatched,
"skipped": processor.skipped,
"decoded": processor.decoded,
"ignored": processor.ignored,
"unsupported": processor.unsupported,
"failed": processor.failed,
"materializedOutputs": processor.materialized_outputs,
"materializationRefused": processor.materialization_refused
});
})
.collect::<std::vec::Vec<serde_json::Value>>();
return serde_json::json!({
"campaignId": summary.campaign_id,
"pipelineVersion": summary.pipeline_version,
"selected": summary.selected,
"started": summary.started,
"completed": summary.completed,
"unmatched": summary.unmatched,
"notStarted": summary.not_started,
"failedInputs": summary.failed_inputs,
"cancelled": summary.cancelled,
"processors": processors,
"startedAt": summary.started_at,
"finishedAt": summary.finished_at
});
}
#[cfg(test)]
mod tests {
fn request(scenario_id: &str) -> crate::DemoExecutionSplToken2022Request {
return crate::DemoExecutionSplToken2022Request {
profile_name: "local_devnet".to_string(),
scenario_id: scenario_id.to_string(),
source: "source".to_string(),
mint: "mint".to_string(),
destination: "destination".to_string(),
delegate: "delegate".to_string(),
authority: "authority".to_string(),
freeze_authority: "freeze".to_string(),
amount_raw: "1".to_string(),
decimals: 9,
submit: false,
operator_confirmed: false,
force_post_validation_replay: true,
};
}
#[test]
fn every_public_scenario_builds_one_typed_operation() {
for scenario in [
"token_2022_mint_to_checked",
"token_2022_transfer_checked",
"token_2022_approve_checked",
"token_2022_revoke",
"token_2022_burn_checked",
"token_2022_freeze_account",
"token_2022_thaw_account",
"token_2022_close_destination",
] {
assert!(crate::operation_from_request(&request(scenario)).is_ok());
}
}
}

View File

@@ -0,0 +1,349 @@
// file: kb_app_demo/src/demo_sql_common.rs
// version: 7
//! Shared SQL demo helpers and serializable payloads.
use tauri::Manager; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import
/// UI-safe table diagnostics shown by SQL demo windows.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql/DemoSqlTableSnapshot.ts"
)]
pub(crate) struct DemoSqlTableSnapshot {
/// Table name.
pub(crate) table_name: std::string::String,
/// Logical Solana domain encoded in the table name.
pub(crate) domain: std::string::String,
/// Human-readable table role.
pub(crate) role: std::string::String,
/// Whether the table currently exists.
pub(crate) exists: bool,
/// Row count when the table exists.
#[ts(type = "number | null")]
pub(crate) row_count: std::option::Option<i64>,
/// Minimum slot when available.
#[ts(type = "number | null")]
pub(crate) min_slot: std::option::Option<i64>,
/// Maximum slot when available.
#[ts(type = "number | null")]
pub(crate) max_slot: std::option::Option<i64>,
/// Latest insertion timestamp rendered by PostgreSQL when available.
pub(crate) latest_created_at: std::option::Option<std::string::String>,
}
/// Opens or focuses a SQL demo window.
pub(crate) fn open_sql_demo_window(
app_handle: tauri::AppHandle,
label: &str,
html_path: &str,
title: &str,
) -> std::result::Result<(), std::string::String> {
tracing::info!(target: crate::TRACING_TARGET, window = label, "open SQL demo window");
let existing_window = app_handle.get_webview_window(label);
if let std::option::Option::Some(window) = existing_window {
let show_result = window.show();
if let std::result::Result::Err(error) = show_result {
return std::result::Result::Err(
kb_core::Error::tauri(format!("cannot show {label} window: {error}")).to_string(),
);
}
let focus_result = window.set_focus();
if let std::result::Result::Err(error) = focus_result {
return std::result::Result::Err(
kb_core::Error::tauri(format!("cannot focus {label} window: {error}")).to_string(),
);
}
return std::result::Result::Ok(());
}
let builder = tauri::WebviewWindowBuilder::new(
&app_handle,
label,
tauri::WebviewUrl::App(html_path.into()),
)
.title(title)
.inner_size(1220.0, 780.0)
.min_inner_size(920.0, 560.0)
.resizable(true)
.visible(true);
let build_result = builder.build();
return match build_result {
std::result::Result::Ok(window) => {
let focus_result = window.set_focus();
if let std::result::Result::Err(error) = focus_result {
return std::result::Result::Err(
kb_core::Error::tauri(format!("cannot focus created {label} window: {error}"))
.to_string(),
);
}
std::result::Result::Ok(())
},
std::result::Result::Err(error) => std::result::Result::Err(
kb_core::Error::tauri(format!("cannot create {label} window: {error}")).to_string(),
),
};
}
/// Connects to PostgreSQL without reapplying schemas initialized at application startup.
pub(crate) async fn connect_postgres_store(
profile: &kb_config::ProfileConfig,
) -> std::result::Result<kb_store_pg::PostgresStore, std::string::String> {
if !profile.database.enabled {
return std::result::Result::Err(std::string::String::from(
"database configuration is disabled in the active profile",
));
}
let options_result = kb_store_pg::PostgresStoreOptions::from_profile_config(profile);
let mut options = match options_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
options.auto_initialize_schema = false;
let store_result = kb_store_pg::PostgresStore::connect(options).await;
return match store_result {
std::result::Result::Ok(store) => std::result::Result::Ok(store),
std::result::Result::Err(error) => std::result::Result::Err(error.to_string()),
};
}
/// Converts store table diagnostics to the UI payload shape.
pub(crate) fn table_snapshot_from_pg(
value: &kb_store_pg::PostgresTableDiagnostics,
) -> crate::DemoSqlTableSnapshot {
let row_count = match &value.statistics {
std::option::Option::Some(statistics) => std::option::Option::Some(statistics.row_count),
std::option::Option::None => std::option::Option::None,
};
let min_slot = match &value.statistics {
std::option::Option::Some(statistics) => statistics.min_slot,
std::option::Option::None => std::option::Option::None,
};
let max_slot = match &value.statistics {
std::option::Option::Some(statistics) => statistics.max_slot,
std::option::Option::None => std::option::Option::None,
};
let latest_created_at = match &value.statistics {
std::option::Option::Some(statistics) => statistics.latest_created_at.clone(),
std::option::Option::None => std::option::Option::None,
};
return crate::DemoSqlTableSnapshot {
table_name: value.table_name.clone(),
domain: value.domain.clone(),
role: value.role.clone(),
exists: value.exists,
row_count,
min_slot,
max_slot,
latest_created_at,
};
}
/// Converts many table diagnostics to UI payloads.
pub(crate) fn table_snapshots_from_pg(
values: &[kb_store_pg::PostgresTableDiagnostics],
) -> std::vec::Vec<crate::DemoSqlTableSnapshot> {
let mut output = std::vec::Vec::new();
for value in values {
output.push(table_snapshot_from_pg(value));
}
return output;
}
/// Initializes PostgreSQL raw, core, decode and materialization schemas during Tauri startup.
pub(crate) async fn initialize_postgres_schema_for_startup(
state: &crate::AppState,
splash_window: &tauri::WebviewWindow,
) {
let profile = state.active_profile();
if !profile.database.enabled {
emit_sql_startup_splash(
splash_window,
"SQL store disabled; schema initialization skipped.",
"info",
false,
);
tracing::debug!(target: crate::TRACING_TARGET, "database is disabled; skip SQL schema initialization");
return;
}
if profile.database.backend != "postgres" {
emit_sql_startup_splash(
splash_window,
"SQL backend is not PostgreSQL; schema initialization skipped.",
"info",
false,
);
tracing::debug!(target: crate::TRACING_TARGET, backend = profile.database.backend.as_str(), "database backend is not postgres; skip SQL schema initialization");
return;
}
let options_result = kb_store_pg::PostgresStoreOptions::from_profile_config(profile);
let mut options = match options_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let error_message = error.to_string();
emit_sql_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "cannot build PostgreSQL store options");
return;
},
};
if !options.auto_initialize_schema {
emit_sql_startup_splash(
splash_window,
"PostgreSQL schema auto-initialization disabled.",
"info",
false,
);
let masked_dsn = options.masked_dsn();
tracing::debug!(target: crate::TRACING_TARGET, dsn = masked_dsn.as_str(), "PostgreSQL schema auto-initialization disabled");
return;
}
let masked_dsn = options.masked_dsn();
options.auto_initialize_schema = false;
emit_sql_startup_splash(
splash_window,
"Checking PostgreSQL raw/core/decode tables...",
"info",
true,
);
tracing::debug!(target: crate::TRACING_TARGET, dsn = masked_dsn.as_str(), "start PostgreSQL schema initialization");
let store_result = kb_store_pg::PostgresStore::connect(options).await;
let store = match store_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let error_message = error.to_string();
emit_sql_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "PostgreSQL connection failed during startup schema initialization");
return;
},
};
let before_result = store.known_table_diagnostics().await;
let before_tables = match before_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let error_message = error.to_string();
emit_sql_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "cannot read table diagnostics before schema initialization");
return;
},
};
let initialize_result = store.initialize_store_schema().await;
if let std::result::Result::Err(error) = initialize_result {
let error_message = error.to_string();
emit_sql_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "PostgreSQL store schema initialization failed");
return;
}
let after_result = store.known_table_diagnostics().await;
let after_tables = match after_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
let error_message = error.to_string();
emit_sql_startup_error(splash_window, error_message.as_str());
tracing::error!(target: crate::TRACING_TARGET, error = error_message.as_str(), "cannot read table diagnostics after schema initialization");
return;
},
};
emit_sql_startup_table_report(splash_window, before_tables.as_slice(), after_tables.as_slice());
}
fn emit_sql_startup_table_report(
splash_window: &tauri::WebviewWindow,
before_tables: &[kb_store_pg::PostgresTableDiagnostics],
after_tables: &[kb_store_pg::PostgresTableDiagnostics],
) {
let mut created_count = 0_u32;
let mut existing_count = 0_u32;
let mut missing_count = 0_u32;
for table in after_tables {
let before_exists = table_exists_in(before_tables, table.table_name.as_str());
if table.exists && before_exists == std::option::Option::Some(false) {
created_count += 1;
emit_sql_startup_splash(
splash_window,
format!("Created table {}", table.table_name).as_str(),
"success",
true,
);
tracing::debug!(target: crate::TRACING_TARGET, table = table.table_name.as_str(), "PostgreSQL table created");
} else if table.exists {
existing_count += 1;
emit_sql_startup_splash(
splash_window,
format!("Table {} already exists", table.table_name).as_str(),
"info",
true,
);
tracing::debug!(target: crate::TRACING_TARGET, table = table.table_name.as_str(), "PostgreSQL table already exists");
} else {
missing_count += 1;
emit_sql_startup_splash(
splash_window,
format!("Table {} is missing after initialization", table.table_name).as_str(),
"warning",
true,
);
tracing::error!(target: crate::TRACING_TARGET, table = table.table_name.as_str(), "PostgreSQL table missing after initialization");
}
}
emit_sql_startup_splash(
splash_window,
format!(
"PostgreSQL schema ready: {created_count} created, {existing_count} already present, {missing_count} missing."
)
.as_str(),
schema_report_status(missing_count),
false,
);
tracing::debug!(target: crate::TRACING_TARGET, created = created_count, existing = existing_count, missing = missing_count, "PostgreSQL schema initialization completed");
}
fn table_exists_in(
tables: &[kb_store_pg::PostgresTableDiagnostics],
table_name: &str,
) -> std::option::Option<bool> {
for table in tables {
if table.table_name.as_str() == table_name {
return std::option::Option::Some(table.exists);
}
}
return std::option::Option::None;
}
fn schema_report_status(missing_count: u32) -> &'static str {
if missing_count == 0 {
return "success";
}
return "warning";
}
fn emit_sql_startup_error(splash_window: &tauri::WebviewWindow, message: &str) {
emit_sql_startup_splash(
splash_window,
format!("PostgreSQL schema initialization error: {message}").as_str(),
"danger",
false,
);
}
fn emit_sql_startup_splash(
splash_window: &tauri::WebviewWindow,
message: &str,
status: &str,
log_only: bool,
) {
let order = if log_only { "add_log" } else { "add_msg" };
crate::emit_splash_order(
splash_window,
order,
std::option::Option::Some(message),
std::option::Option::Some(status),
std::option::Option::None,
);
}
/// Formats debug enums for UI display.
pub(crate) fn debug_status<T: std::fmt::Debug>(value: T) -> std::string::String {
return format!("{value:?}");
}

View File

@@ -0,0 +1,38 @@
// file: kb_app_demo/src/demo_sql_diag.rs
// version: 2
//! SQL diagnostic demo commands.
use ts_rs::TS; // rust-rules: derive-import
/// Complete SQL diagnostic payload shown by `demo_sql_diag`.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_diag/DemoSqlDiagPayload.ts"
)]
pub(crate) struct DemoSqlDiagPayload {
/// Configuration file path.
pub(crate) config_path: std::string::String,
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Configured database backend.
pub(crate) backend: std::string::String,
/// Masked PostgreSQL DSN.
pub(crate) masked_dsn: std::string::String,
/// PostgreSQL current schema.
pub(crate) current_schema: std::option::Option<std::string::String>,
/// PostgreSQL health status.
pub(crate) health_status: std::string::String,
/// PostgreSQL health message.
pub(crate) health_message: std::option::Option<std::string::String>,
/// Migration status.
pub(crate) migration_status: std::string::String,
/// Migration diagnostic message.
pub(crate) migration_message: std::option::Option<std::string::String>,
/// PostgreSQL server version when available.
pub(crate) server_version: std::option::Option<std::string::String>,
/// Known raw/core table diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
}

View File

@@ -0,0 +1,22 @@
// file: kb_app_demo/src/demo_sql_pg_core.rs
// version: 3
//! PostgreSQL core store demo commands.
use ts_rs::TS; // rust-rules: derive-import
/// PostgreSQL core store payload shown by `demo_sql_pg_core`.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_pg_core/DemoSqlPgCorePayload.ts"
)]
pub(crate) struct DemoSqlPgCorePayload {
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Masked PostgreSQL DSN.
pub(crate) masked_dsn: std::string::String,
/// Core table diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
}

View File

@@ -0,0 +1,22 @@
// file: kb_app_demo/src/demo_sql_pg_raw.rs
// version: 4
//! PostgreSQL canonical acquisition store demo commands.
use ts_rs::TS; // rust-rules: derive-import
/// PostgreSQL canonical acquisition payload shown by `demo_sql_pg_raw`.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_pg_raw/DemoSqlPgRawPayload.ts"
)]
pub(crate) struct DemoSqlPgRawPayload {
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Masked PostgreSQL DSN.
pub(crate) masked_dsn: std::string::String,
/// Raw table diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
}

View File

@@ -0,0 +1,409 @@
// file: kb_app_demo/src/demo_sql_replay_candidates.rs
// version: 6
//! Read-only SQL replay candidate browser commands.
use ts_rs::TS; // rust-rules: derive-import
/// One program identifier exposed by the runtime `kb_program_ids` registry.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_replay_candidates/DemoSqlReplayKnownProgramOption.ts"
)]
pub(crate) struct DemoSqlReplayKnownProgramOption {
/// Stable lower snake case program code.
pub(crate) code: std::string::String,
/// Base58 Solana program identifier.
pub(crate) program_id: std::string::String,
}
/// Static options for the replay candidate browser.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_replay_candidates/DemoSqlReplayOptionsPayload.ts"
)]
pub(crate) struct DemoSqlReplayOptionsPayload {
/// Active profile name.
pub(crate) active_profile_name: std::string::String,
/// Masked PostgreSQL DSN.
pub(crate) masked_dsn: std::string::String,
/// Maximum rows accepted by one query.
pub(crate) maximum_limit: u32,
/// Program identifiers enumerable from `kb_program_ids`.
pub(crate) known_programs: std::vec::Vec<crate::DemoSqlReplayKnownProgramOption>,
}
/// UI request for bounded transaction replay candidates.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_replay_candidates/DemoSqlReplayTransactionRequest.ts"
)]
pub(crate) struct DemoSqlReplayTransactionRequest {
/// Optional partial signature search.
pub(crate) signature_contains: std::option::Option<std::string::String>,
/// Optional inclusive minimum slot.
#[ts(type = "number | null")]
pub(crate) min_slot: std::option::Option<u64>,
/// Optional inclusive maximum slot.
#[ts(type = "number | null")]
pub(crate) max_slot: std::option::Option<u64>,
/// Optional raw processing state.
pub(crate) raw_processing_state: std::option::Option<std::string::String>,
/// Optional latest ledger status.
pub(crate) ledger_status: std::option::Option<std::string::String>,
/// Optional exact program id.
pub(crate) program_id: std::option::Option<std::string::String>,
/// Program scope code: any, outer, inner or logs.
pub(crate) program_scope: std::string::String,
/// Optional entity kind: mint, owner or account_key.
pub(crate) entity_kind: std::option::Option<std::string::String>,
/// Optional exact entity value.
pub(crate) entity_value: std::option::Option<std::string::String>,
/// Maximum returned rows.
pub(crate) limit: u32,
/// Orders newest slots first when true.
pub(crate) newest_first: bool,
}
/// One transaction row shown by the replay candidate browser.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_replay_candidates/DemoSqlReplayTransactionRow.ts"
)]
pub(crate) struct DemoSqlReplayTransactionRow {
/// Canonical transaction signature.
pub(crate) signature: std::string::String,
/// Transaction slot.
#[ts(type = "number")]
pub(crate) slot: i64,
/// Current raw processing state.
pub(crate) raw_processing_state: std::string::String,
/// Current raw retention state.
pub(crate) retention_state: std::string::String,
/// Whether a core transaction exists.
pub(crate) has_core_transaction: bool,
/// Core transaction failure flag when available.
pub(crate) transaction_failed: std::option::Option<bool>,
/// Latest core extraction ledger status.
pub(crate) ledger_status: std::string::String,
/// Latest processor version when available.
pub(crate) processor_version: std::option::Option<std::string::String>,
/// Latest attempt count.
pub(crate) attempt_count: i32,
/// Number of top-level instructions.
#[ts(type = "number")]
pub(crate) outer_instruction_count: i64,
/// Number of inner instructions.
#[ts(type = "number")]
pub(crate) inner_instruction_count: i64,
/// Number of distinct top-level programs.
#[ts(type = "number")]
pub(crate) outer_program_count: i64,
/// Number of distinct inner programs.
#[ts(type = "number")]
pub(crate) inner_program_count: i64,
/// Raw row update timestamp.
pub(crate) updated_at: std::string::String,
}
/// UI request for bounded program summaries.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_replay_candidates/DemoSqlReplayProgramRequest.ts"
)]
pub(crate) struct DemoSqlReplayProgramRequest {
/// Optional partial program id search.
pub(crate) program_id_contains: std::option::Option<std::string::String>,
/// Maximum returned rows.
pub(crate) limit: u32,
}
/// Aggregated program row shown by the replay candidate browser.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_replay_candidates/DemoSqlReplayProgramRow.ts"
)]
pub(crate) struct DemoSqlReplayProgramRow {
/// Optional stable code from `kb_program_ids`.
pub(crate) program_code: std::option::Option<std::string::String>,
/// Program id.
pub(crate) program_id: std::string::String,
/// Number of distinct transactions.
#[ts(type = "number")]
pub(crate) transaction_count: i64,
/// Number of top-level instruction occurrences.
#[ts(type = "number")]
pub(crate) outer_instruction_count: i64,
/// Number of inner instruction occurrences.
#[ts(type = "number")]
pub(crate) inner_instruction_count: i64,
/// Number of reliably linked log occurrences.
#[ts(type = "number")]
pub(crate) log_count: i64,
/// Lowest observed slot.
#[ts(type = "number")]
pub(crate) min_slot: i64,
/// Highest observed slot.
#[ts(type = "number")]
pub(crate) max_slot: i64,
}
/// UI request for bounded core entity summaries.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_replay_candidates/DemoSqlReplayEntityRequest.ts"
)]
pub(crate) struct DemoSqlReplayEntityRequest {
/// Entity kind: mint, owner or account_key.
pub(crate) entity_kind: std::string::String,
/// Optional partial entity value search.
pub(crate) entity_value_contains: std::option::Option<std::string::String>,
/// Maximum returned rows.
pub(crate) limit: u32,
}
/// Aggregated core entity row shown by the replay candidate browser.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_sql_replay_candidates/DemoSqlReplayEntityRow.ts"
)]
pub(crate) struct DemoSqlReplayEntityRow {
/// Stable entity kind code.
pub(crate) entity_kind: std::string::String,
/// Mint, owner or account-key address.
pub(crate) entity_value: std::string::String,
/// Number of distinct transactions.
#[ts(type = "number")]
pub(crate) transaction_count: i64,
/// Total number of core-table occurrences.
#[ts(type = "number")]
pub(crate) occurrence_count: i64,
/// Lowest observed slot.
#[ts(type = "number")]
pub(crate) min_slot: i64,
/// Highest observed slot.
#[ts(type = "number")]
pub(crate) max_slot: i64,
}
pub(crate) fn transaction_row_from_pg(
row: kb_store_pg::PostgresReplayTransactionCandidate,
) -> crate::DemoSqlReplayTransactionRow {
return crate::DemoSqlReplayTransactionRow {
signature: row.signature,
slot: row.slot,
raw_processing_state: row.raw_processing_state,
retention_state: row.retention_state,
has_core_transaction: row.has_core_transaction,
transaction_failed: row.transaction_failed,
ledger_status: row.ledger_status,
processor_version: row.processor_version,
attempt_count: row.attempt_count,
outer_instruction_count: row.outer_instruction_count,
inner_instruction_count: row.inner_instruction_count,
outer_program_count: row.outer_program_count,
inner_program_count: row.inner_program_count,
updated_at: row.updated_at,
};
}
pub(crate) fn program_row_from_pg(
row: kb_store_pg::PostgresReplayProgramSummary,
) -> crate::DemoSqlReplayProgramRow {
let program_code = kb_program_ids::find_registered_program_id(&row.program_id)
.map(|entry| return entry.code().to_owned());
return crate::DemoSqlReplayProgramRow {
program_code,
program_id: row.program_id,
transaction_count: row.transaction_count,
outer_instruction_count: row.outer_instruction_count,
inner_instruction_count: row.inner_instruction_count,
log_count: row.log_count,
min_slot: row.min_slot,
max_slot: row.max_slot,
};
}
pub(crate) fn entity_row_from_pg(
row: kb_store_pg::PostgresReplayEntitySummary,
) -> crate::DemoSqlReplayEntityRow {
return crate::DemoSqlReplayEntityRow {
entity_kind: row.entity_kind,
entity_value: row.entity_value,
transaction_count: row.transaction_count,
occurrence_count: row.occurrence_count,
min_slot: row.min_slot,
max_slot: row.max_slot,
};
}
pub(crate) fn validated_csv_file_name(
file_name: &str,
) -> std::result::Result<&'static str, std::string::String> {
return match file_name {
"replay_transactions.csv" => std::result::Result::Ok("replay_transactions.csv"),
"replay_programs.csv" => std::result::Result::Ok("replay_programs.csv"),
"replay_mints.csv" => std::result::Result::Ok("replay_mints.csv"),
"replay_owners.csv" => std::result::Result::Ok("replay_owners.csv"),
"replay_account_keys.csv" => std::result::Result::Ok("replay_account_keys.csv"),
_ => std::result::Result::Err(format!("unsupported replay CSV file name: {file_name}")),
};
}
pub(crate) fn csv_export_directory_from_current_dir(
current_dir: &std::path::Path,
) -> std::path::PathBuf {
let current_name = current_dir.file_name().and_then(std::ffi::OsStr::to_str);
if let (std::option::Option::Some("kb_app_demo"), std::option::Option::Some(parent)) =
(current_name, current_dir.parent())
{
return parent.join("data").join("exports_csv");
}
return current_dir.join("data").join("exports_csv");
}
pub(crate) async fn available_csv_export_path(
export_dir: &std::path::Path,
file_name: &str,
) -> std::result::Result<std::path::PathBuf, std::string::String> {
let direct_path = export_dir.join(file_name);
let direct_exists_result = tokio::fs::try_exists(&direct_path).await;
let direct_exists = match direct_exists_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("cannot inspect CSV export path: {error}"));
},
};
if !direct_exists {
return std::result::Result::Ok(direct_path);
}
let stem = file_name.trim_end_matches(".csv");
for suffix in 1_u16..=999_u16 {
let candidate = export_dir.join(format!("{stem}_{suffix}.csv"));
let exists_result = tokio::fs::try_exists(&candidate).await;
let exists = match exists_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"cannot inspect CSV export path: {error}"
));
},
};
if !exists {
return std::result::Result::Ok(candidate);
}
}
return std::result::Result::Err("cannot allocate a unique CSV export path".to_owned());
}
pub(crate) fn program_scope_from_code(
code: &str,
) -> std::result::Result<kb_store_pg::PostgresReplayProgramScope, std::string::String> {
return match code {
"any" => std::result::Result::Ok(kb_store_pg::PostgresReplayProgramScope::Any),
"outer" => std::result::Result::Ok(kb_store_pg::PostgresReplayProgramScope::Outer),
"inner" => std::result::Result::Ok(kb_store_pg::PostgresReplayProgramScope::Inner),
"logs" => std::result::Result::Ok(kb_store_pg::PostgresReplayProgramScope::Logs),
_ => std::result::Result::Err(format!("unsupported replay program scope: {code}")),
};
}
pub(crate) fn optional_entity_kind_from_code(
code: std::option::Option<&str>,
) -> std::result::Result<
std::option::Option<kb_store_pg::PostgresReplayEntityKind>,
std::string::String,
> {
return match code {
std::option::Option::Some(value) => {
let result = entity_kind_from_code(value);
match result {
std::result::Result::Ok(kind) => {
std::result::Result::Ok(std::option::Option::Some(kind))
},
std::result::Result::Err(error) => std::result::Result::Err(error),
}
},
std::option::Option::None => std::result::Result::Ok(std::option::Option::None),
};
}
pub(crate) fn entity_kind_from_code(
code: &str,
) -> std::result::Result<kb_store_pg::PostgresReplayEntityKind, std::string::String> {
return match code {
"mint" => std::result::Result::Ok(kb_store_pg::PostgresReplayEntityKind::Mint),
"owner" => std::result::Result::Ok(kb_store_pg::PostgresReplayEntityKind::Owner),
"account_key" => std::result::Result::Ok(kb_store_pg::PostgresReplayEntityKind::AccountKey),
_ => std::result::Result::Err(format!("unsupported replay entity kind: {code}")),
};
}
#[cfg(test)]
mod tests {
#[test]
fn program_scope_parser_accepts_logs() {
let result = crate::program_scope_from_code("logs");
assert_eq!(result, std::result::Result::Ok(kb_store_pg::PostgresReplayProgramScope::Logs));
}
#[test]
fn entity_kind_parser_rejects_unknown_code() {
let result = crate::entity_kind_from_code("unknown");
assert!(result.is_err());
}
#[test]
fn csv_file_name_rejects_unknown_name() {
let result = crate::validated_csv_file_name("arbitrary.csv");
assert!(result.is_err());
}
#[test]
fn csv_export_directory_uses_workspace_root_from_demo_crate() {
let current_dir = std::path::Path::new("/tmp/khadhroony-bot2/kb_app_demo");
let result = crate::csv_export_directory_from_current_dir(current_dir);
assert_eq!(result, std::path::Path::new("/tmp/khadhroony-bot2/data/exports_csv"));
}
#[test]
fn csv_file_name_accepts_split_entity_exports() {
let mint_result = crate::validated_csv_file_name("replay_mints.csv");
let owner_result = crate::validated_csv_file_name("replay_owners.csv");
let account_result = crate::validated_csv_file_name("replay_account_keys.csv");
assert!(mint_result.is_ok());
assert!(owner_result.is_ok());
assert!(account_result.is_ok());
}
#[test]
fn program_row_exposes_registered_code() {
let row = kb_store_pg::PostgresReplayProgramSummary {
program_id: kb_program_ids::SYSTEM_PROGRAM_ID.to_owned(),
transaction_count: 1,
outer_instruction_count: 1,
inner_instruction_count: 0,
log_count: 1,
min_slot: 1,
max_slot: 1,
};
let converted = crate::program_row_from_pg(row);
assert_eq!(converted.program_code.as_deref(), std::option::Option::Some("system"));
}
}

View File

@@ -0,0 +1,869 @@
// file: kb_app_demo/src/demo_ws.rs
// version: 13
//! Standard Solana WebSocket demo commands backed by `kb_rpc::WsSession`.
use tauri::Emitter; // rust-rules: trait-import
use tauri::Manager; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import
/// One selectable role shown by the WebSocket demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_ws/DemoWsRoleOption.ts"
)]
pub(crate) struct DemoWsRoleOption {
/// Endpoint role code.
pub(crate) role: std::string::String,
/// Request kinds accepted by this role.
pub(crate) request_kinds: std::vec::Vec<std::string::String>,
}
/// One selectable WebSocket JSON-RPC method shown by the WebSocket demo.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_ws/DemoWsMethodOption.ts"
)]
pub(crate) struct DemoWsMethodOption {
/// JSON-RPC subscribe method name.
pub(crate) method: std::string::String,
/// Matching unsubscribe method name.
pub(crate) unsubscribe_method: std::string::String,
/// Derived request kind used for endpoint routing.
pub(crate) request_kind: std::string::String,
/// Human-readable method label.
pub(crate) label: std::string::String,
/// Whether this method needs the target field.
pub(crate) requires_target: bool,
/// Whether this method needs the filter JSON field.
pub(crate) requires_filter_json: bool,
/// Whether this method supports an optional configuration object.
pub(crate) supports_config_json: bool,
}
/// WebSocket demo options derived from configuration and the standard registry.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_ws/DemoWsOptionsPayload.ts"
)]
pub(crate) struct DemoWsOptionsPayload {
/// Selectable roles.
pub(crate) roles: std::vec::Vec<crate::DemoWsRoleOption>,
/// Selectable methods.
pub(crate) methods: std::vec::Vec<crate::DemoWsMethodOption>,
}
/// Request payload for one WebSocket subscription demo command.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_ws/DemoWsRequest.ts"
)]
pub(crate) struct DemoWsRequest {
/// Required endpoint role used by the WebSocket pool.
pub(crate) role: std::string::String,
/// JSON-RPC WebSocket subscribe method name.
pub(crate) method: std::string::String,
/// Optional target string used by account/program/signature subscriptions.
pub(crate) target: std::option::Option<std::string::String>,
/// Optional JSON filter string used by logsSubscribe or blockSubscribe.
pub(crate) filter_json: std::option::Option<std::string::String>,
/// Optional JSON configuration string.
pub(crate) config_json: std::option::Option<std::string::String>,
}
/// Response payload for one WebSocket subscription response.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_ws/DemoWsExecutionPayload.ts"
)]
pub(crate) struct DemoWsExecutionPayload {
/// Selected endpoint name.
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.
pub(crate) method: std::string::String,
/// Derived request kind.
pub(crate) request_kind: std::string::String,
/// Parsed response kind.
pub(crate) response_kind: std::string::String,
/// Remote subscription id returned by the node.
#[ts(type = "number | null")]
pub(crate) subscription_id: std::option::Option<u64>,
/// Pretty JSON response text.
pub(crate) response_json: std::string::String,
}
/// Current WebSocket demo session status.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_ws/DemoWsStatusPayload.ts"
)]
pub(crate) struct DemoWsStatusPayload {
/// Whether a session is currently connected.
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.
pub(crate) unsubscribe_method: std::option::Option<std::string::String>,
/// Last remote subscription id.
#[ts(type = "number | null")]
pub(crate) subscription_id: std::option::Option<u64>,
/// Number of active subscriptions on the current connection.
pub(crate) subscription_count: u32,
/// Active subscriptions kept by the session.
pub(crate) subscriptions: std::vec::Vec<crate::DemoWsSubscriptionStatusPayload>,
}
/// One active WebSocket subscription shown by the demo UI.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_ws/DemoWsSubscriptionStatusPayload.ts"
)]
pub(crate) struct DemoWsSubscriptionStatusPayload {
/// Remote subscription id.
#[ts(type = "number")]
pub(crate) subscription_id: u64,
/// Subscribe method used to create this subscription.
pub(crate) method: std::string::String,
/// Matching unsubscribe method.
pub(crate) unsubscribe_method: std::string::String,
}
/// Request payload for one explicit WebSocket unsubscribe command.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_ws/DemoWsUnsubscribeRequest.ts"
)]
pub(crate) struct DemoWsUnsubscribeRequest {
/// Remote subscription id to unsubscribe.
#[ts(type = "number")]
pub(crate) subscription_id: u64,
}
/// WebSocket event payload emitted to the demo window.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/demo_ws/DemoWsMessagePayload.ts"
)]
pub(crate) struct DemoWsMessagePayload {
/// Message kind.
pub(crate) kind: std::string::String,
/// Pretty JSON or text payload.
pub(crate) payload_json: std::string::String,
}
const DEMO_WS_UI_RATE_WINDOW_MS: u64 = 1000;
const DEMO_WS_UI_MAX_MESSAGES_PER_WINDOW: u32 = 12;
const DEMO_WS_UI_MAX_PAYLOAD_CHARS: usize = 8_000;
const DEMO_WS_RECONNECT_ATTEMPTS: u32 = 3;
const DEMO_WS_RECONNECT_INITIAL_DELAY_MS: u64 = 250;
const DEMO_WS_RECONNECT_MAX_DELAY_MS: u64 = 2_000;
struct DemoWsUiRateLimiter {
window_started_at: std::time::Instant,
emitted_in_window: u32,
dropped_in_window: u32,
}
impl DemoWsUiRateLimiter {
fn new() -> Self {
return Self {
window_started_at: std::time::Instant::now(),
emitted_in_window: 0,
dropped_in_window: 0,
};
}
fn should_emit(&mut self, app_handle: &tauri::AppHandle) -> bool {
let now = std::time::Instant::now();
if now.duration_since(self.window_started_at)
>= std::time::Duration::from_millis(DEMO_WS_UI_RATE_WINDOW_MS)
{
if self.dropped_in_window > 0 {
emit_demo_ws_message(
app_handle,
"throttled",
format!(
"{} WebSocket notification(s) masquée(s) pendant la dernière fenêtre UI.",
self.dropped_in_window
),
);
}
self.window_started_at = now;
self.emitted_in_window = 0;
self.dropped_in_window = 0;
}
if self.emitted_in_window >= DEMO_WS_UI_MAX_MESSAGES_PER_WINDOW {
self.dropped_in_window = self.dropped_in_window.saturating_add(1);
return false;
}
self.emitted_in_window = self.emitted_in_window.saturating_add(1);
return true;
}
}
pub(crate) async fn demo_ws_status_inner(
state: &crate::AppState,
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
let session = {
let guard = state.demo_ws_session().lock().await;
guard.clone()
};
return match session {
std::option::Option::Some(session) => {
std::result::Result::Ok(status_from_snapshot(session.snapshot().await))
},
std::option::Option::None => std::result::Result::Ok(disconnected_status()),
};
}
pub(crate) async fn demo_ws_connect_inner(
app_handle: tauri::AppHandle,
state: &crate::AppState,
request: crate::DemoWsRequest,
) -> std::result::Result<crate::DemoWsExecutionPayload, std::string::String> {
let role = request.role.trim().to_string();
if role.is_empty() {
return std::result::Result::Err("demo WebSocket role must not be empty".to_string());
}
let method = request.method.trim().to_string();
if method.is_empty() {
return std::result::Result::Err("demo WebSocket method must not be empty".to_string());
}
let standard_request = match build_standard_ws_request(
&method,
request.target,
request.filter_json,
request.config_json,
) {
std::result::Result::Ok(request) => request,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let selected_client = match state.ws_pool().select_client_for_role_and_method(&role, &method) {
std::result::Result::Ok(client) => client,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let session = match ensure_demo_ws_session(&app_handle, state, selected_client.clone()).await {
std::result::Result::Ok(session) => session,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let acknowledgement = match session.subscribe(standard_request).await {
std::result::Result::Ok(acknowledgement) => acknowledgement,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let response_value = match acknowledgement.response.to_value() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let response_json = match serde_json::to_string_pretty(&response_value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
emit_demo_ws_status(&app_handle, session.snapshot().await);
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: kb_rpc::request_kind_from_method(&method),
response_kind: acknowledgement.response.kind_name().to_string(),
subscription_id: acknowledgement.subscription.remote_subscription_id,
response_json,
});
}
async fn ensure_demo_ws_session(
app_handle: &tauri::AppHandle,
state: &crate::AppState,
selected_client: kb_rpc::WsClient,
) -> std::result::Result<std::sync::Arc<kb_rpc::WsSession>, std::string::String> {
let existing = {
let guard = state.demo_ws_session().lock().await;
guard.clone()
};
if let std::option::Option::Some(session) = existing {
let snapshot = session.snapshot().await;
if snapshot.state != kb_rpc::WsSessionState::Disconnected {
if snapshot.endpoint_url != selected_client.endpoint_url() {
return std::result::Result::Err(format!(
"demo WebSocket session already uses endpoint '{}'; disconnect before selecting '{}'",
snapshot.endpoint_name,
selected_client.endpoint_name()
));
}
return std::result::Result::Ok(session);
}
}
let reconnect_policy = if selected_client.endpoint_config().auto_reconnect {
match kb_rpc::WsReconnectPolicy::bounded(
DEMO_WS_RECONNECT_ATTEMPTS,
DEMO_WS_RECONNECT_INITIAL_DELAY_MS,
DEMO_WS_RECONNECT_MAX_DELAY_MS,
) {
std::result::Result::Ok(policy) => policy,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}
} else {
kb_rpc::WsReconnectPolicy::disabled()
};
let capabilities =
kb_rpc::StandardWsCapabilities::from_endpoint(selected_client.endpoint_config());
let session =
match kb_rpc::WsSession::connect(selected_client, capabilities, reconnect_policy).await {
std::result::Result::Ok(session) => std::sync::Arc::new(session),
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
spawn_demo_ws_event_bridge(app_handle.clone(), session.clone());
{
let mut guard = state.demo_ws_session().lock().await;
*guard = std::option::Option::Some(session.clone());
}
return std::result::Result::Ok(session);
}
pub(crate) async fn demo_ws_unsubscribe_inner(
app_handle: tauri::AppHandle,
state: &crate::AppState,
remote_subscription_id: u64,
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
let session = {
let guard = state.demo_ws_session().lock().await;
guard.clone()
};
let session = match session {
std::option::Option::Some(session) => session,
std::option::Option::None => {
return std::result::Result::Err("demo WebSocket session is not connected".to_string());
},
};
let unsubscribe_result = session.unsubscribe(remote_subscription_id).await;
if let std::result::Result::Err(error) = unsubscribe_result {
return std::result::Result::Err(error.to_string());
}
let snapshot = session.snapshot().await;
emit_demo_ws_status(&app_handle, snapshot.clone());
return std::result::Result::Ok(status_from_snapshot(snapshot));
}
pub(crate) async fn demo_ws_disconnect_inner(
state: &crate::AppState,
) -> std::result::Result<crate::DemoWsStatusPayload, std::string::String> {
let session = {
let mut guard = state.demo_ws_session().lock().await;
guard.take()
};
if let std::option::Option::Some(session) = session {
if let std::result::Result::Err(error) = session.disconnect().await {
return std::result::Result::Err(error.to_string());
}
}
return std::result::Result::Ok(disconnected_status());
}
/// Disconnects the demo WebSocket session from application lifecycle hooks.
pub(crate) async fn disconnect_demo_ws_app_state(state: &crate::AppState, _wait_for_relay: bool) {
let disconnect_result = demo_ws_disconnect_inner(state).await;
if let std::result::Result::Err(error) = disconnect_result {
tracing::debug!(target: crate::TRACING_TARGET, "demo ws lifecycle disconnect failed: {error}");
}
}
fn spawn_demo_ws_event_bridge(
app_handle: tauri::AppHandle,
session: std::sync::Arc<kb_rpc::WsSession>,
) {
let mut receiver = session.subscribe_events();
tauri::async_runtime::spawn(async move {
let mut rate_limiter = DemoWsUiRateLimiter::new();
loop {
let event = receiver.recv().await;
let event = match event {
std::result::Result::Ok(event) => event,
std::result::Result::Err(tokio::sync::broadcast::error::RecvError::Lagged(
count,
)) => {
emit_demo_ws_message(
&app_handle,
"lagged",
format!("{count} événement(s) WebSocket interne(s) perdu(s)"),
);
continue;
},
std::result::Result::Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
};
match event {
kb_rpc::WsSessionEvent::Notification { notification, .. } => {
if rate_limiter.should_emit(&app_handle) {
let payload = match serde_json::to_string_pretty(&notification) {
std::result::Result::Ok(payload) => payload,
std::result::Result::Err(error) => {
format!("cannot serialize typed WebSocket notification: {error}")
},
};
emit_demo_ws_message(
&app_handle,
"notification",
truncate_payload(payload),
);
}
},
kb_rpc::WsSessionEvent::Diagnostic { code, message } => {
emit_demo_ws_message(&app_handle, &code, truncate_payload(message));
},
kb_rpc::WsSessionEvent::Reconnecting { attempt, maximum_attempts } => {
emit_demo_ws_message(
&app_handle,
"reconnecting",
format!("tentative {attempt}/{maximum_attempts}"),
);
emit_demo_ws_status(&app_handle, session.snapshot().await);
},
kb_rpc::WsSessionEvent::Reconnected { reconnect_count } => {
emit_demo_ws_message(
&app_handle,
"reconnected",
format!("reconnexion numéro {reconnect_count}"),
);
emit_demo_ws_status(&app_handle, session.snapshot().await);
},
kb_rpc::WsSessionEvent::Connected
| kb_rpc::WsSessionEvent::SubscriptionAdded(_)
| kb_rpc::WsSessionEvent::SubscriptionRemapped { .. }
| kb_rpc::WsSessionEvent::SubscriptionRemoved(_) => {
emit_demo_ws_status(&app_handle, session.snapshot().await);
},
kb_rpc::WsSessionEvent::Disconnected => {
emit_demo_ws_status(&app_handle, session.snapshot().await);
break;
},
}
}
});
}
fn emit_demo_ws_status(app_handle: &tauri::AppHandle, snapshot: kb_rpc::WsSessionSnapshot) {
let window = match app_handle.get_webview_window("demo_ws") {
std::option::Option::Some(window) => window,
std::option::Option::None => return,
};
let status = status_from_snapshot(snapshot);
if let std::result::Result::Err(error) = window.emit("demo-ws-status", status) {
tracing::debug!(target: crate::TRACING_TARGET, "cannot emit demo ws status: {error}");
}
}
fn emit_demo_ws_message(
app_handle: &tauri::AppHandle,
kind: &str,
payload_json: std::string::String,
) {
let window = match app_handle.get_webview_window("demo_ws") {
std::option::Option::Some(window) => window,
std::option::Option::None => return,
};
if let std::result::Result::Err(error) = window.emit(
"demo-ws-message",
crate::DemoWsMessagePayload { kind: kind.to_string(), payload_json },
) {
tracing::debug!(target: crate::TRACING_TARGET, "cannot emit demo ws message: {error}");
}
}
fn status_from_snapshot(snapshot: kb_rpc::WsSessionSnapshot) -> crate::DemoWsStatusPayload {
let mut subscriptions = std::vec::Vec::new();
for subscription in &snapshot.subscriptions {
if let std::option::Option::Some(remote_subscription_id) =
subscription.remote_subscription_id
{
subscriptions.push(crate::DemoWsSubscriptionStatusPayload {
subscription_id: remote_subscription_id,
method: subscription.subscribe_method.clone(),
unsubscribe_method: subscription.unsubscribe_method.clone(),
});
}
}
let (method, unsubscribe_method, subscription_id) = match subscriptions.last() {
std::option::Option::Some(subscription) => (
std::option::Option::Some(subscription.method.clone()),
std::option::Option::Some(subscription.unsubscribe_method.clone()),
std::option::Option::Some(subscription.subscription_id),
),
std::option::Option::None => {
(std::option::Option::None, std::option::Option::None, std::option::Option::None)
},
};
return crate::DemoWsStatusPayload {
connected: snapshot.state != kb_rpc::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,
subscription_count: match u32::try_from(subscriptions.len()) {
std::result::Result::Ok(count) => count,
std::result::Result::Err(_) => u32::MAX,
},
subscriptions,
};
}
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,
subscription_count: 0,
subscriptions: std::vec::Vec::new(),
};
}
pub(crate) fn build_ws_role_options(
snapshots: std::vec::Vec<kb_rpc::WsPoolClientSnapshot>,
) -> std::vec::Vec<crate::DemoWsRoleOption> {
let mut roles = std::collections::BTreeMap::<
std::string::String,
std::collections::BTreeSet<std::string::String>,
>::new();
for snapshot in snapshots {
for role in snapshot.roles {
if !role.enabled {
continue;
}
let role_entry = roles.entry(role.role).or_default();
for request_kind in role.request_kinds {
role_entry.insert(request_kind);
}
}
}
let mut options = std::vec::Vec::new();
for (role, request_kinds) in roles {
options.push(crate::DemoWsRoleOption {
role,
request_kinds: request_kinds.into_iter().collect(),
});
}
return options;
}
pub(crate) fn build_ws_method_options() -> std::vec::Vec<crate::DemoWsMethodOption> {
let labels = std::collections::BTreeMap::from([
("accountSubscribe", ("Compte", true, false, true)),
("blockSubscribe", ("Bloc", false, true, true)),
("logsSubscribe", ("Logs", false, true, true)),
("programSubscribe", ("Programme", true, false, true)),
("rootSubscribe", ("Roots", false, false, false)),
("signatureSubscribe", ("Signature", true, false, true)),
("slotSubscribe", ("Slots", false, false, false)),
("slotsUpdatesSubscribe", ("Mises à jour slots", false, false, false)),
("voteSubscribe", ("Votes", false, false, false)),
]);
let mut options = std::vec::Vec::new();
for specification in &kb_rpc::STANDARD_WS_SUBSCRIPTIONS {
let metadata = labels.get(specification.subscribe_method);
let (label, requires_target, requires_filter_json, supports_config_json) = match metadata {
std::option::Option::Some(metadata) => *metadata,
std::option::Option::None => continue,
};
options.push(crate::DemoWsMethodOption {
method: specification.subscribe_method.to_string(),
unsubscribe_method: specification.unsubscribe_method.to_string(),
request_kind: kb_rpc::request_kind_from_method(specification.subscribe_method),
label: label.to_string(),
requires_target,
requires_filter_json,
supports_config_json,
});
}
return options;
}
fn build_standard_ws_request(
method: &str,
target: std::option::Option<std::string::String>,
filter_json: std::option::Option<std::string::String>,
config_json: std::option::Option<std::string::String>,
) -> std::result::Result<kb_rpc::StandardWsRequest, std::string::String> {
return match method {
"accountSubscribe" => {
let target = match required_target(target, method) {
std::result::Result::Ok(target) => target,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let config = match parse_optional_json_as::<kb_rpc::WsAccountSubscribeConfig>(
config_json,
"configJson",
) {
std::result::Result::Ok(config) => config,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::result::Result::Err(error) =
reject_present(filter_json, "filterJson", method)
{
return std::result::Result::Err(error);
}
std::result::Result::Ok(kb_rpc::StandardWsRequest::Account(
kb_rpc::AccountSubscribeRequest { pubkey: target, config },
))
},
"blockSubscribe" => {
if let std::result::Result::Err(error) = reject_present(target, "target", method) {
return std::result::Result::Err(error);
}
let filter =
match parse_required_json_as::<kb_rpc::WsBlockFilter>(filter_json, "filterJson") {
std::result::Result::Ok(filter) => filter,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let config = match parse_optional_json_as::<kb_rpc::WsBlockSubscribeConfig>(
config_json,
"configJson",
) {
std::result::Result::Ok(config) => config,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
std::result::Result::Ok(kb_rpc::StandardWsRequest::Block(
kb_rpc::BlockSubscribeRequest { filter, config },
))
},
"logsSubscribe" => {
if let std::result::Result::Err(error) = reject_present(target, "target", method) {
return std::result::Result::Err(error);
}
let filter =
match parse_required_json_as::<kb_rpc::WsLogsFilter>(filter_json, "filterJson") {
std::result::Result::Ok(filter) => filter,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let config = match parse_optional_json_as::<kb_rpc::WsLogsSubscribeConfig>(
config_json,
"configJson",
) {
std::result::Result::Ok(config) => config,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
std::result::Result::Ok(kb_rpc::StandardWsRequest::Logs(kb_rpc::LogsSubscribeRequest {
filter,
config,
}))
},
"programSubscribe" => {
let target = match required_target(target, method) {
std::result::Result::Ok(target) => target,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::result::Result::Err(error) =
reject_present(filter_json, "filterJson", method)
{
return std::result::Result::Err(error);
}
let config = match parse_optional_json_as::<kb_rpc::WsProgramSubscribeConfig>(
config_json,
"configJson",
) {
std::result::Result::Ok(config) => config,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
std::result::Result::Ok(kb_rpc::StandardWsRequest::Program(
kb_rpc::ProgramSubscribeRequest { program_id: target, config },
))
},
"signatureSubscribe" => {
let target = match required_target(target, method) {
std::result::Result::Ok(target) => target,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::result::Result::Err(error) =
reject_present(filter_json, "filterJson", method)
{
return std::result::Result::Err(error);
}
let config = match parse_optional_json_as::<kb_rpc::WsSignatureSubscribeConfig>(
config_json,
"configJson",
) {
std::result::Result::Ok(config) => config,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
std::result::Result::Ok(kb_rpc::StandardWsRequest::Signature(
kb_rpc::SignatureSubscribeRequest { signature: target, config },
))
},
"rootSubscribe" => {
if let std::result::Result::Err(error) =
reject_parameterless_inputs(target, filter_json, config_json, method)
{
return std::result::Result::Err(error);
}
std::result::Result::Ok(kb_rpc::StandardWsRequest::Root(kb_rpc::RootSubscribeRequest))
},
"slotSubscribe" => {
if let std::result::Result::Err(error) =
reject_parameterless_inputs(target, filter_json, config_json, method)
{
return std::result::Result::Err(error);
}
std::result::Result::Ok(kb_rpc::StandardWsRequest::Slot(kb_rpc::SlotSubscribeRequest))
},
"slotsUpdatesSubscribe" => {
if let std::result::Result::Err(error) =
reject_parameterless_inputs(target, filter_json, config_json, method)
{
return std::result::Result::Err(error);
}
std::result::Result::Ok(kb_rpc::StandardWsRequest::SlotsUpdates(
kb_rpc::SlotsUpdatesSubscribeRequest,
))
},
"voteSubscribe" => {
if let std::result::Result::Err(error) =
reject_parameterless_inputs(target, filter_json, config_json, method)
{
return std::result::Result::Err(error);
}
std::result::Result::Ok(kb_rpc::StandardWsRequest::Vote(kb_rpc::VoteSubscribeRequest))
},
_ => std::result::Result::Err(format!("unsupported standard WebSocket method '{method}'")),
};
}
fn required_target(
target: std::option::Option<std::string::String>,
method: &str,
) -> std::result::Result<std::string::String, std::string::String> {
let target = match target {
std::option::Option::Some(target) => target.trim().to_string(),
std::option::Option::None => std::string::String::new(),
};
if target.is_empty() {
return std::result::Result::Err(format!("method '{method}' requires target"));
}
return std::result::Result::Ok(target);
}
fn reject_parameterless_inputs(
target: std::option::Option<std::string::String>,
filter_json: std::option::Option<std::string::String>,
config_json: std::option::Option<std::string::String>,
method: &str,
) -> std::result::Result<(), std::string::String> {
if let std::result::Result::Err(error) = reject_present(target, "target", method) {
return std::result::Result::Err(error);
}
if let std::result::Result::Err(error) = reject_present(filter_json, "filterJson", method) {
return std::result::Result::Err(error);
}
if let std::result::Result::Err(error) = reject_present(config_json, "configJson", method) {
return std::result::Result::Err(error);
}
return std::result::Result::Ok(());
}
fn reject_present(
value: std::option::Option<std::string::String>,
field: &str,
method: &str,
) -> std::result::Result<(), std::string::String> {
if let std::option::Option::Some(value) = value {
if !value.trim().is_empty() {
return std::result::Result::Err(format!("method '{method}' does not accept {field}"));
}
}
return std::result::Result::Ok(());
}
fn parse_required_json_as<ValueType: serde::de::DeserializeOwned>(
text: std::option::Option<std::string::String>,
field: &str,
) -> std::result::Result<ValueType, std::string::String> {
let text = match text {
std::option::Option::Some(text) if !text.trim().is_empty() => text,
_ => return std::result::Result::Err(format!("{field} is required")),
};
return match serde_json::from_str::<ValueType>(&text) {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => {
std::result::Result::Err(format!("invalid {field}: {error}"))
},
};
}
fn parse_optional_json_as<ValueType: serde::de::DeserializeOwned>(
text: std::option::Option<std::string::String>,
field: &str,
) -> std::result::Result<std::option::Option<ValueType>, std::string::String> {
let text = match text {
std::option::Option::Some(text) if !text.trim().is_empty() => text,
_ => return std::result::Result::Ok(std::option::Option::None),
};
return match serde_json::from_str::<ValueType>(&text) {
std::result::Result::Ok(value) => std::result::Result::Ok(std::option::Option::Some(value)),
std::result::Result::Err(error) => {
std::result::Result::Err(format!("invalid {field}: {error}"))
},
};
}
fn truncate_payload(payload: std::string::String) -> std::string::String {
if payload.chars().count() <= DEMO_WS_UI_MAX_PAYLOAD_CHARS {
return payload;
}
let mut truncated = payload
.chars()
.take(DEMO_WS_UI_MAX_PAYLOAD_CHARS)
.collect::<std::string::String>();
truncated.push_str("\n… payload tronqué côté Rust …");
return truncated;
}
#[cfg(test)]
mod tests {
use ts_rs::TS; // rust-rules: derive-import
#[test]
fn tauri_numeric_bindings_use_json_compatible_numbers() {
let config = ts_rs::Config::default();
let declarations = [
<crate::DemoWsExecutionPayload as TS>::decl(&config),
<crate::DemoWsStatusPayload as TS>::decl(&config),
<crate::DemoWsSubscriptionStatusPayload as TS>::decl(&config),
<crate::DemoWsUnsubscribeRequest as TS>::decl(&config),
<crate::DemoBackfillProgressPayload as TS>::decl(&config),
<crate::DemoBackfillSummaryPayload as TS>::decl(&config),
<crate::DemoSqlTableSnapshot as TS>::decl(&config),
];
for declaration in declarations {
assert!(!declaration.contains("bigint"));
}
}
}

View File

@@ -0,0 +1,191 @@
// file: kb_app_demo/src/frontend_log.rs
// version: 10
//! Frontend logging bridge commands for Tauri WebView scripts.
use ts_rs::TS; // rust-rules: derive-import
/// Log payload sent by frontend scripts when a typed tracing target is required.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo/frontend_log/FrontendLogPayload.ts"
)]
pub(crate) struct FrontendLogPayload {
/// Lowercase tracing level: `trace`, `debug`, `info`, `warn`, or `error`.
pub(crate) level: std::string::String,
/// Explicit tracing target used for route-based filtering.
pub(crate) target: std::string::String,
/// Message rendered by the frontend before forwarding it to Rust.
pub(crate) message: std::string::String,
}
pub(crate) fn emit_trace(target: &str, message: &str) {
tracing::trace!(
target: crate::TRACING_TARGET,
action = "frontend_log",
frontend_target = target,
"{}",
message
);
}
pub(crate) fn emit_debug(target: &str, message: &str) {
tracing::debug!(
target: crate::TRACING_TARGET,
action = "frontend_log",
frontend_target = target,
"{}",
message
);
}
pub(crate) fn emit_info(target: &str, message: &str) {
tracing::info!(
target: crate::TRACING_TARGET,
action = "frontend_log",
frontend_target = target,
"{}",
message
);
}
pub(crate) fn emit_warn(target: &str, message: &str) {
tracing::warn!(
target: crate::TRACING_TARGET,
action = "frontend_log",
frontend_target = target,
"{}",
message
);
}
pub(crate) fn emit_error(target: &str, message: &str) {
tracing::error!(
target: crate::TRACING_TARGET,
action = "frontend_log",
frontend_target = target,
"{}",
message
);
}
pub(crate) fn normalize_frontend_target(target: &str) -> std::string::String {
let trimmed = target.trim();
if trimmed.is_empty() {
return "kb_app_demo.frontend".to_string();
}
if trimmed == "kb_app_demo.frontend.main" {
return trimmed.to_string();
}
if trimmed == "kb_app_demo.frontend.demo_config" {
return trimmed.to_string();
}
if trimmed == "kb_app_demo.frontend.demo_http" {
return trimmed.to_string();
}
if trimmed == "kb_app_demo.frontend.demo_ws" {
return trimmed.to_string();
}
if trimmed == "kb_app_demo.frontend.demo_backfill" {
return trimmed.to_string();
}
if trimmed == "kb_app_demo.frontend.demo_core_extraction" {
return trimmed.to_string();
}
if trimmed == "kb_app_demo.frontend.demo_decode_replay" {
return trimmed.to_string();
}
if trimmed == "kb_app_demo.frontend.demo_execution_solana_core" {
return trimmed.to_string();
}
if trimmed == "kb_app_demo.frontend.demo_execution_spl" {
return trimmed.to_string();
}
if trimmed == "kb_app_demo.frontend.demo_sql_replay_candidates" {
return trimmed.to_string();
}
if trimmed == "kb_app_demo.frontend.splash" {
return trimmed.to_string();
}
return "kb_app_demo.frontend".to_string();
}
#[cfg(test)]
mod tests {
#[test]
fn normalize_frontend_target_uses_default_for_empty_target() {
let normalized = crate::normalize_frontend_target(" ");
assert_eq!(normalized, "kb_app_demo.frontend");
}
#[test]
fn normalize_frontend_target_keeps_main_target() {
let normalized = crate::normalize_frontend_target("kb_app_demo.frontend.main");
assert_eq!(normalized, "kb_app_demo.frontend.main");
}
#[test]
fn normalize_frontend_target_keeps_demo_config_target() {
let normalized = crate::normalize_frontend_target("kb_app_demo.frontend.demo_config");
assert_eq!(normalized, "kb_app_demo.frontend.demo_config");
}
#[test]
fn normalize_frontend_target_keeps_demo_http_target() {
let normalized = crate::normalize_frontend_target("kb_app_demo.frontend.demo_http");
assert_eq!(normalized, "kb_app_demo.frontend.demo_http");
}
#[test]
fn normalize_frontend_target_keeps_demo_ws_target() {
let normalized = crate::normalize_frontend_target("kb_app_demo.frontend.demo_ws");
assert_eq!(normalized, "kb_app_demo.frontend.demo_ws");
}
#[test]
fn normalize_frontend_target_keeps_demo_backfill_target() {
let normalized = crate::normalize_frontend_target("kb_app_demo.frontend.demo_backfill");
assert_eq!(normalized, "kb_app_demo.frontend.demo_backfill");
}
#[test]
fn normalize_frontend_target_keeps_demo_core_extraction_target() {
let normalized =
crate::normalize_frontend_target("kb_app_demo.frontend.demo_core_extraction");
assert_eq!(normalized, "kb_app_demo.frontend.demo_core_extraction");
}
#[test]
fn normalize_frontend_target_keeps_demo_decode_replay_target() {
let normalized =
crate::normalize_frontend_target("kb_app_demo.frontend.demo_decode_replay");
assert_eq!(normalized, "kb_app_demo.frontend.demo_decode_replay");
}
#[test]
fn normalize_frontend_target_keeps_demo_execution_solana_core_target() {
let normalized =
crate::normalize_frontend_target("kb_app_demo.frontend.demo_execution_solana_core");
assert_eq!(normalized, "kb_app_demo.frontend.demo_execution_solana_core");
}
#[test]
fn normalize_frontend_target_keeps_demo_sql_replay_candidates_target() {
let normalized =
crate::normalize_frontend_target("kb_app_demo.frontend.demo_sql_replay_candidates");
assert_eq!(normalized, "kb_app_demo.frontend.demo_sql_replay_candidates");
}
#[test]
fn normalize_frontend_target_keeps_splash_target() {
let normalized = crate::normalize_frontend_target("kb_app_demo.frontend.splash");
assert_eq!(normalized, "kb_app_demo.frontend.splash");
}
#[test]
fn normalize_frontend_target_rejects_unknown_dynamic_targets() {
let normalized = crate::normalize_frontend_target("other.frontend.target");
assert_eq!(normalized, "kb_app_demo.frontend");
}
}

View File

@@ -0,0 +1,338 @@
// file: kb_app_demo/src/lib.rs
// version: 30
//! Tauri application demo library for `khadhroony-bot2`.
//!
//! This crate loads the shared configuration, initializes workspace logging and
//! exposes focused demo windows for early runtime validation.
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
mod app_state;
mod constants;
mod demo_backfill;
mod demo_config;
mod demo_core_extraction;
mod demo_decode_replay;
mod demo_execution_solana_core;
mod demo_execution_spl;
mod demo_http;
mod demo_spl_ata;
mod demo_spl_token;
mod demo_spl_token_2022;
mod demo_sql_common;
mod demo_sql_diag;
mod demo_sql_pg_core;
mod demo_sql_pg_raw;
mod demo_sql_replay_candidates;
mod demo_ws;
mod frontend_log;
mod main_window;
mod splash;
mod tauri;
/// Shared state managed by Tauri for the demo application.
pub(crate) use crate::app_state::AppState;
/// Canonical tracing target for this crate.
pub(crate) use crate::constants::TRACING_TARGET;
/// ????
pub(crate) use crate::demo_backfill::DemoBackfillObserver;
/// Initial options shown by the backfill demo.
pub(crate) use crate::demo_backfill::DemoBackfillOptionsPayload;
/// One progress event emitted to the backfill window.
pub(crate) use crate::demo_backfill::DemoBackfillProgressPayload;
/// UI request for one bounded backfill campaign.
pub(crate) use crate::demo_backfill::DemoBackfillRequest;
/// ????
pub(crate) use crate::demo_backfill::DemoBackfillRunGuard;
/// Final UI-safe summary for one backfill campaign.
pub(crate) use crate::demo_backfill::DemoBackfillSummaryPayload;
/// ????
pub(crate) use crate::demo_backfill::build_demo_backfill_pipeline_request;
/// ????
pub(crate) use crate::demo_backfill::build_role_options;
/// ????
pub(crate) use crate::demo_backfill::demo_backfill_summary_payload;
/// Serializable payload shown by the configuration demo page.
pub(crate) use crate::demo_config::DemoConfigPayload;
/// ????
pub(crate) use crate::demo_core_extraction::DemoCoreExtractionObserver;
/// Initial options shown by the core extraction demo.
pub(crate) use crate::demo_core_extraction::DemoCoreExtractionOptionsPayload;
/// One progress event emitted to the core extraction window.
pub(crate) use crate::demo_core_extraction::DemoCoreExtractionProgressPayload;
/// UI request for one bounded core extraction campaign.
pub(crate) use crate::demo_core_extraction::DemoCoreExtractionRequest;
/// ????
pub(crate) use crate::demo_core_extraction::DemoCoreExtractionRunGuard;
/// Final UI-safe summary for one extraction campaign.
pub(crate) use crate::demo_core_extraction::DemoCoreExtractionSummaryPayload;
/// ????
pub(crate) use crate::demo_core_extraction::build_demo_core_extraction_pipeline_request;
/// ????
pub(crate) use crate::demo_core_extraction::demo_core_extraction_summary_payload;
/// One aggregated coverage row returned to the UI.
pub(crate) use crate::demo_decode_replay::DemoDecodeCoverageSummaryPayload;
/// Read-only decode, ledger and coverage diagnostics.
pub(crate) use crate::demo_decode_replay::DemoDecodeDiagnosticsPayload;
/// One processor counter row returned to the UI.
pub(crate) use crate::demo_decode_replay::DemoDecodeProcessorSummaryPayload;
/// One decoder selectable by the decode replay demo.
pub(crate) use crate::demo_decode_replay::DemoDecodeReplayDecoderOption;
/// ????
pub(crate) use crate::demo_decode_replay::DemoDecodeReplayObserver;
/// Initial options shown by the contextual decode replay demo.
pub(crate) use crate::demo_decode_replay::DemoDecodeReplayOptionsPayload;
/// UI request for one bounded contextual decode replay campaign.
pub(crate) use crate::demo_decode_replay::DemoDecodeReplayRequest;
/// ????
pub(crate) use crate::demo_decode_replay::DemoDecodeReplayRunGuard;
/// Final UI-safe summary for one contextual decode replay campaign.
pub(crate) use crate::demo_decode_replay::DemoDecodeReplaySummaryPayload;
/// Bounded read request for committed transaction annotations.
pub(crate) use crate::demo_decode_replay::DemoTransactionAnnotationRequest;
/// UI-safe committed transaction annotation row.
pub(crate) use crate::demo_decode_replay::DemoTransactionAnnotationRow;
/// ????
pub(crate) use crate::demo_decode_replay::annotation_payload;
/// ????
pub(crate) use crate::demo_decode_replay::available_decoders;
/// ????
pub(crate) use crate::demo_decode_replay::available_materializers;
/// ????
pub(crate) use crate::demo_decode_replay::build_demo_decode_replay_pipeline_request;
/// ????
pub(crate) use crate::demo_decode_replay::coverage_payload;
/// ????
pub(crate) use crate::demo_decode_replay::demo_decode_replay_summary_payload;
/// ????
pub(crate) use crate::demo_decode_replay::optional_line_count;
/// ????
pub(crate) use crate::demo_decode_replay::register_active_campaign;
/// ????
pub(crate) use crate::demo_decode_replay::text_sample;
/// Request sent by the Memo v4 Devnet execution panel.
pub(crate) use crate::demo_execution_solana_core::DemoExecutionMemoRequest;
/// UI-safe result of one Memo v4 Devnet execution orchestration.
pub(crate) use crate::demo_execution_solana_core::DemoExecutionMemoSummaryPayload;
/// Public key generated for a disposable Devnet recipient.
pub(crate) use crate::demo_execution_solana_core::DemoExecutionSolanaCoreGeneratedRecipientPayload;
/// ????
pub(crate) use crate::demo_execution_solana_core::DemoExecutionSolanaCoreObserver;
/// Initial options shown by the Solana Core execution demo.
pub(crate) use crate::demo_execution_solana_core::DemoExecutionSolanaCoreOptionsPayload;
/// One Devnet execution profile exposed to the demo window.
pub(crate) use crate::demo_execution_solana_core::DemoExecutionSolanaCoreProfileOption;
/// Progress event emitted during execution and post-validation.
pub(crate) use crate::demo_execution_solana_core::DemoExecutionSolanaCoreProgressPayload;
/// Request sent by the execution demo.
pub(crate) use crate::demo_execution_solana_core::DemoExecutionSolanaCoreRequest;
/// ????
pub(crate) use crate::demo_execution_solana_core::DemoExecutionSolanaCoreRunGuard;
/// UI-safe result of one execution orchestration.
pub(crate) use crate::demo_execution_solana_core::DemoExecutionSolanaCoreSummaryPayload;
/// ????
pub(crate) use crate::demo_execution_solana_core::confirmation_status_code;
/// ????
pub(crate) use crate::demo_execution_solana_core::demo_execution_solana_core_memo_summary_payload;
/// ????
pub(crate) use crate::demo_execution_solana_core::demo_execution_solana_core_summary_payload;
/// ????
pub(crate) use crate::demo_execution_solana_core::devnet_profile_options;
/// ????
pub(crate) use crate::demo_execution_solana_core::execution_cluster_code;
/// ????
pub(crate) use crate::demo_execution_solana_core::pretty_json;
/// ????
pub(crate) use crate::demo_execution_solana_core::select_devnet_profile;
/// Frontend payload for one independent Devnet SPL validation scenario.
pub(crate) use crate::demo_execution_spl::DevnetSplValidationScenarioPayload;
/// Response payload for one HTTP JSON-RPC demo execution.
pub(crate) use crate::demo_http::DemoHttpExecutionPayload;
/// One selectable HTTP JSON-RPC method shown by the HTTP demo.
pub(crate) use crate::demo_http::DemoHttpMethodOption;
/// HTTP demo options derived from configuration and local method presets.
pub(crate) use crate::demo_http::DemoHttpOptionsPayload;
/// Request payload for one HTTP JSON-RPC demo execution.
pub(crate) use crate::demo_http::DemoHttpRequest;
/// One selectable role shown by the HTTP demo.
pub(crate) use crate::demo_http::DemoHttpRoleOption;
/// ????
pub(crate) use crate::demo_http::build_http_method_options;
/// ????
pub(crate) use crate::demo_http::build_http_role_options;
/// ????
pub(crate) use crate::demo_http::demo_http_execute_request_inner;
/// Request for one representative ATA creation on Devnet.
pub(crate) use crate::demo_spl_ata::DemoExecutionSplAtaRequest;
/// UI-safe ATA execution result.
pub(crate) use crate::demo_spl_ata::DemoExecutionSplAtaSummaryPayload;
/// Readonly payer, wallet, Token Program and derived ATA values.
pub(crate) use crate::demo_spl_ata::DemoSplAtaDerivationPayload;
/// Request used to derive the representative wallet ATA before execution.
pub(crate) use crate::demo_spl_ata::DemoSplAtaDerivationRequest;
/// Bounded ATA lifecycle journal request.
pub(crate) use crate::demo_spl_ata::DemoSplAtaJournalRequest;
/// One UI-safe ATA lifecycle journal row.
pub(crate) use crate::demo_spl_ata::DemoSplAtaJournalRow;
/// ????
pub(crate) use crate::demo_spl_ata::demo_spl_ata_summary_payload;
/// ????
pub(crate) use crate::demo_spl_ata::derive_ata;
/// ????
pub(crate) use crate::demo_spl_ata::journal_matches;
/// ????
pub(crate) use crate::demo_spl_ata::journal_row;
/// ????
pub(crate) use crate::demo_spl_ata::load_profile_wallet;
/// ????
pub(crate) use crate::demo_spl_ata::parse_token_program;
/// Request for one checked classic SPL Token transfer on Devnet.
pub(crate) use crate::demo_spl_token::DemoExecutionSplTokenRequest;
/// UI-safe result of one checked SPL Token transfer orchestration.
pub(crate) use crate::demo_spl_token::DemoExecutionSplTokenSummaryPayload;
/// Bounded exact filters for the classic SPL Token materialized journal.
pub(crate) use crate::demo_spl_token::DemoSplTokenJournalRequest;
/// UI-safe materialized classic SPL Token journal row.
pub(crate) use crate::demo_spl_token::DemoSplTokenJournalRow;
/// ????
pub(crate) use crate::demo_spl_token::demo_spl_token_journal_row;
/// Executes one checked SPL Token simulation or explicitly authorized Devnet submission.
pub(crate) use crate::demo_spl_token::demo_spl_token_summary_payload;
/// ????
pub(crate) use crate::demo_spl_token::payload_matches;
/// Loads a bounded, typed journal of committed classic SPL Token projections.
pub(crate) use crate::demo_spl_token::validate_journal_request;
/// Request for one independent public Token-2022 Devnet scenario.
pub(crate) use crate::demo_spl_token_2022::DemoExecutionSplToken2022Request;
/// UI-safe result of one public Token-2022 Devnet orchestration.
pub(crate) use crate::demo_spl_token_2022::DemoExecutionSplToken2022SummaryPayload;
/// Public values loaded from one persisted Token-2022 validation fixture.
pub(crate) use crate::demo_spl_token_2022::DemoSplToken2022FixturePayload;
/// ????
pub(crate) use crate::demo_spl_token_2022::demo_spl_token_2022_summary_payload;
/// ????
pub(crate) use crate::demo_spl_token_2022::operation_from_request;
/// ????
pub(crate) use crate::demo_spl_token_2022::parse_fixture;
/// UI-safe table diagnostics shown by SQL demo windows.
pub(crate) use crate::demo_sql_common::DemoSqlTableSnapshot;
/// Connects to PostgreSQL without reapplying schemas initialized at application startup.
pub(crate) use crate::demo_sql_common::connect_postgres_store;
/// Formats debug enums for UI display.
pub(crate) use crate::demo_sql_common::debug_status;
/// Initializes PostgreSQL raw, core, decode and materialization schemas during Tauri startup.
pub(crate) use crate::demo_sql_common::initialize_postgres_schema_for_startup;
/// Opens or focuses a SQL demo window.
pub(crate) use crate::demo_sql_common::open_sql_demo_window;
/// Converts store table diagnostics to the UI payload shape.
pub(crate) use crate::demo_sql_common::table_snapshot_from_pg;
/// Converts many table diagnostics to UI payloads.
pub(crate) use crate::demo_sql_common::table_snapshots_from_pg;
/// Complete SQL diagnostic payload shown by `demo_sql_diag`.
pub(crate) use crate::demo_sql_diag::DemoSqlDiagPayload;
/// PostgreSQL core store payload shown by `demo_sql_pg_core`.
pub(crate) use crate::demo_sql_pg_core::DemoSqlPgCorePayload;
/// PostgreSQL canonical acquisition payload shown by `demo_sql_pg_raw`.
pub(crate) use crate::demo_sql_pg_raw::DemoSqlPgRawPayload;
/// UI request for bounded core entity summaries.
pub(crate) use crate::demo_sql_replay_candidates::DemoSqlReplayEntityRequest;
/// Aggregated core entity row shown by the replay candidate browser.
pub(crate) use crate::demo_sql_replay_candidates::DemoSqlReplayEntityRow;
/// One program identifier exposed by the runtime `kb_program_ids` registry.
pub(crate) use crate::demo_sql_replay_candidates::DemoSqlReplayKnownProgramOption;
/// Static options for the replay candidate browser.
pub(crate) use crate::demo_sql_replay_candidates::DemoSqlReplayOptionsPayload;
/// UI request for bounded program summaries.
pub(crate) use crate::demo_sql_replay_candidates::DemoSqlReplayProgramRequest;
/// Aggregated program row shown by the replay candidate browser.
pub(crate) use crate::demo_sql_replay_candidates::DemoSqlReplayProgramRow;
/// UI request for bounded transaction replay candidates.
pub(crate) use crate::demo_sql_replay_candidates::DemoSqlReplayTransactionRequest;
/// One transaction row shown by the replay candidate browser.
pub(crate) use crate::demo_sql_replay_candidates::DemoSqlReplayTransactionRow;
/// ????
pub(crate) use crate::demo_sql_replay_candidates::available_csv_export_path;
/// ????
pub(crate) use crate::demo_sql_replay_candidates::csv_export_directory_from_current_dir;
/// ????
pub(crate) use crate::demo_sql_replay_candidates::entity_kind_from_code;
/// ????
pub(crate) use crate::demo_sql_replay_candidates::entity_row_from_pg;
/// ????
pub(crate) use crate::demo_sql_replay_candidates::optional_entity_kind_from_code;
/// ????
pub(crate) use crate::demo_sql_replay_candidates::program_row_from_pg;
/// ????
pub(crate) use crate::demo_sql_replay_candidates::program_scope_from_code;
/// ????
pub(crate) use crate::demo_sql_replay_candidates::transaction_row_from_pg;
/// ????
pub(crate) use crate::demo_sql_replay_candidates::validated_csv_file_name;
/// Response payload for one WebSocket subscription response.
pub(crate) use crate::demo_ws::DemoWsExecutionPayload;
/// WebSocket event payload emitted to the demo window.
pub(crate) use crate::demo_ws::DemoWsMessagePayload;
/// One selectable WebSocket JSON-RPC method shown by the WebSocket demo.
pub(crate) use crate::demo_ws::DemoWsMethodOption;
/// WebSocket demo options derived from configuration and the standard registry.
pub(crate) use crate::demo_ws::DemoWsOptionsPayload;
/// Request payload for one WebSocket subscription demo command.
pub(crate) use crate::demo_ws::DemoWsRequest;
/// One selectable role shown by the WebSocket demo.
pub(crate) use crate::demo_ws::DemoWsRoleOption;
/// Current WebSocket demo session status.
pub(crate) use crate::demo_ws::DemoWsStatusPayload;
/// One active WebSocket subscription shown by the demo UI.
pub(crate) use crate::demo_ws::DemoWsSubscriptionStatusPayload;
/// Request payload for one explicit WebSocket unsubscribe command.
pub(crate) use crate::demo_ws::DemoWsUnsubscribeRequest;
/// ????
pub(crate) use crate::demo_ws::build_ws_method_options;
/// ????
pub(crate) use crate::demo_ws::build_ws_role_options;
/// ????
pub(crate) use crate::demo_ws::demo_ws_connect_inner;
/// ????
pub(crate) use crate::demo_ws::demo_ws_disconnect_inner;
/// ????
pub(crate) use crate::demo_ws::demo_ws_status_inner;
/// ????
pub(crate) use crate::demo_ws::demo_ws_unsubscribe_inner;
/// Disconnects the demo WebSocket session from application lifecycle hooks.
pub(crate) use crate::demo_ws::disconnect_demo_ws_app_state;
/// Log payload sent by frontend scripts when a typed tracing target is required.
pub(crate) use crate::frontend_log::FrontendLogPayload;
/// ????
pub(crate) use crate::frontend_log::emit_debug;
/// ????
pub(crate) use crate::frontend_log::emit_error;
/// ????
pub(crate) use crate::frontend_log::emit_info;
/// ????
pub(crate) use crate::frontend_log::emit_trace;
/// ????
pub(crate) use crate::frontend_log::emit_warn;
/// ????
pub(crate) use crate::frontend_log::normalize_frontend_target;
/// ????
pub(crate) use crate::main_window::strip_readme_metadata_comments;
/// Resolves the workspace root directory used by the demo application.
pub(crate) use crate::main_window::workspace_root_dir;
/// Delay after the fade-out command before destroying the splash window.
pub(crate) use crate::splash::SPLASH_CLOSE_WAIT_MS;
/// Splash fade-in and fade-out animation duration.
pub(crate) use crate::splash::SPLASH_FADE_MS;
/// Minimum startup splash duration before fade-out starts.
pub(crate) use crate::splash::SPLASH_MINIMUM_MS;
/// Command payload sent from Rust to the splash frontend.
pub(crate) use crate::splash::SplashOrder;
/// Emits a splash-screen order to the splash WebView.
pub(crate) use crate::splash::emit_splash_order;
/// Waits until the minimum splash duration has elapsed.
pub(crate) use crate::splash::wait_until_minimum;
/// Runs the Tauri application.
pub use crate::tauri::run;

View File

@@ -0,0 +1,43 @@
// file: kb_app_demo/src/main.rs
// version: 6
//! Binary entry point for the `kb_app_demo` crate.
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use fs2::FileExt; // rust-rules: trait-import
/// Entrypoint of the kb app binary.
#[tokio::main]
async fn main() -> std::process::ExitCode {
let mut lock_path = std::env::temp_dir();
lock_path.push("com_khadhroony_bot2_kb_app_demo.lock");
let lock_file = match std::fs::File::create(&lock_path) {
std::result::Result::Ok(lock) => lock,
std::result::Result::Err(error) => {
eprintln!("cannot create application lock '{}': {error}", lock_path.display());
return std::process::ExitCode::FAILURE;
},
};
if let std::result::Result::Err(error) = lock_file.try_lock_exclusive() {
if error.kind() == std::io::ErrorKind::WouldBlock {
eprintln!("another kb_app_demo instance is already running");
return std::process::ExitCode::FAILURE;
}
eprintln!("cannot acquire application lock '{}': {error}", lock_path.display());
return std::process::ExitCode::FAILURE;
}
let _lock_file = lock_file;
let run_result = kb_app_demo_lib::run().await;
return match run_result {
std::result::Result::Ok(()) => std::process::ExitCode::SUCCESS,
std::result::Result::Err(error) => {
eprintln!("application error: {error}");
std::process::ExitCode::FAILURE
},
};
}

View File

@@ -0,0 +1,54 @@
// file: kb_app_demo/src/main_window.rs
// version: 3
//! Main window commands and workspace README loading helpers.
pub(crate) fn workspace_root_dir() -> std::path::PathBuf {
let manifest_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
return match manifest_dir.parent() {
std::option::Option::Some(parent) => parent.to_path_buf(),
std::option::Option::None => manifest_dir,
};
}
pub(crate) fn strip_readme_metadata_comments(content: &str) -> std::string::String {
let mut output = std::string::String::new();
let mut leading_metadata = true;
for line in content.lines() {
let trimmed = line.trim();
if leading_metadata && is_readme_metadata_comment(trimmed) {
continue;
}
leading_metadata = false;
output.push_str(line);
output.push('\n');
}
return output;
}
fn is_readme_metadata_comment(line: &str) -> bool {
if line.starts_with("<!-- file:") || line.starts_with("<!--- file:") {
return true;
}
if line.starts_with("<!-- version:") || line.starts_with("<!--- version:") {
return true;
}
return false;
}
#[cfg(test)]
mod tests {
#[test]
fn strip_readme_metadata_comments_removes_workspace_header_lines() {
let content = "<!-- file: README.md -->\n<!-- version: 7 -->\n# Title\nBody\n";
let cleaned = crate::strip_readme_metadata_comments(content);
assert_eq!(cleaned, "# Title\nBody\n");
}
#[test]
fn strip_readme_metadata_comments_keeps_later_html_comments() {
let content = "<!-- file: README.md -->\n# Title\n<!-- normal comment -->\nBody\n";
let cleaned = crate::strip_readme_metadata_comments(content);
assert_eq!(cleaned, "# Title\n<!-- normal comment -->\nBody\n");
}
}

View File

@@ -0,0 +1,80 @@
// file: kb_app_demo/src/splash.rs
// version: 5
//! Shared splash-screen payload types and helpers.
//!
//! The startup sequence is intentionally wired in `lib.rs` so it stays close to
//! the Tauri builder setup pattern used by the previous demo application.
use tauri::Emitter; // rust-rules: trait-import
use ts_rs::TS; // rust-rules: derive-import
/// Minimum startup splash duration before fade-out starts.
pub(crate) const SPLASH_MINIMUM_MS: u64 = 3100;
/// Splash fade-in and fade-out animation duration.
pub(crate) const SPLASH_FADE_MS: u32 = 3000;
/// Delay after the fade-out command before destroying the splash window.
pub(crate) const SPLASH_CLOSE_WAIT_MS: u64 = 3100;
/// Command payload sent from Rust to the splash frontend.
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, TS)]
#[ts(export, export_to = "../frontend/ts/bindings/kb_app_demo/splash/SplashOrder.ts")]
pub(crate) struct SplashOrder {
/// Splash command name such as `fadein`, `fadeout`, `add_msg`, or `add_log`.
pub(crate) order: std::string::String,
/// Optional message payload attached to the command.
pub(crate) msg: std::option::Option<std::string::String>,
/// Optional status payload attached to the command.
pub(crate) status: std::option::Option<std::string::String>,
/// Optional animation duration in milliseconds.
pub(crate) duration_ms: std::option::Option<u32>,
}
/// Emits a splash-screen order to the splash WebView.
pub(crate) fn emit_splash_order(
splash_window: &tauri::WebviewWindow,
order: &str,
msg: std::option::Option<&str>,
status: std::option::Option<&str>,
duration_ms: std::option::Option<u32>,
) {
let payload = crate::SplashOrder {
order: order.to_string(),
msg: msg.map(std::string::ToString::to_string),
status: status.map(std::string::ToString::to_string),
duration_ms,
};
let emit_result = splash_window.emit("splash", payload);
if let std::result::Result::Err(error) = emit_result {
tracing::error!(target: crate::TRACING_TARGET, "error emitting splash event '{order}': {error:?}");
}
}
/// Waits until the minimum splash duration has elapsed.
pub(crate) async fn wait_until_minimum(start: tokio::time::Instant, minimum_ms: u64) {
let minimum = std::time::Duration::from_millis(minimum_ms);
let elapsed = start.elapsed();
if elapsed >= minimum {
return;
}
tokio::time::sleep(minimum - elapsed).await;
}
#[cfg(test)]
mod tests {
#[test]
fn splash_order_serializes_duration_ms() {
let order = crate::SplashOrder {
order: "fadein".to_string(),
msg: std::option::Option::None,
status: std::option::Option::None,
duration_ms: std::option::Option::Some(3000),
};
let value_result = serde_json::to_value(order);
let value = match value_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("cannot serialize splash order: {error}"),
};
assert_eq!(value["duration_ms"], serde_json::json!(3000));
}
}

File diff suppressed because it is too large Load Diff