Files
khadhroony-bot3/kb-app-demo-desktop/src/demo_core_extraction.rs
2026-08-09 19:34:08 +02:00

357 lines
14 KiB
Rust

// file: kb-app-demo-desktop/src/demo_core_extraction.rs
// version: 14
//! 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
/// One registered program offered as a non-blocking core extraction suggestion.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_app_demo_desktop/demo_core_extraction/DemoCoreExtractionProgramOption.ts"
)]
pub(crate) struct DemoCoreExtractionProgramOption {
/// Stable registry code.
pub(crate) code: std::string::String,
/// Canonical Solana program id.
pub(crate) program_id: std::string::String,
}
/// 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_desktop/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,
/// Registered program ids offered without restricting free text entry.
pub(crate) programs: std::vec::Vec<crate::DemoCoreExtractionProgramOption>,
}
/// 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_desktop/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_desktop/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_desktop/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 ks_pipeline::CoreExtractionObserver for crate::DemoCoreExtractionObserver<'_> {
fn on_progress(&self, event: &ks_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 demo_core_extraction_options_payload(
state: &crate::AppState,
) -> crate::DemoCoreExtractionOptionsPayload {
return crate::DemoCoreExtractionOptionsPayload {
processor_version: ks_pipeline::CORE_EXTRACTION_PROCESSOR_VERSION.to_string(),
default_limit: 100,
default_max_concurrent_extractions: 4,
running: state.demo_core_extraction_running().load(std::sync::atomic::Ordering::Acquire),
programs: ks_program_ids::registered_program_ids()
.iter()
.map(|entry| {
return crate::DemoCoreExtractionProgramOption {
code: entry.code().to_string(),
program_id: entry.program_id().to_string(),
};
})
.collect(),
};
}
pub(crate) async fn demo_core_extraction_execute(
app_handle: tauri::AppHandle,
state: &crate::AppState,
request: crate::DemoCoreExtractionRequest,
) -> std::result::Result<crate::DemoCoreExtractionSummaryPayload, std::string::String> {
let acquire_result = state.demo_core_extraction_running().compare_exchange(
false,
true,
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire,
);
if acquire_result.is_err() {
return std::result::Result::Err(
"a core extraction campaign is already running".to_string(),
);
}
let _run_guard = crate::DemoCoreExtractionRunGuard {
running: state.demo_core_extraction_running(),
};
state
.demo_core_extraction_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let pipeline_request_result = build_pipeline_request(request);
let pipeline_request = match pipeline_request_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let store_result = crate::connect_postgres_store(state.active_profile()).await;
let store = match store_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let observer = crate::DemoCoreExtractionObserver {
app_handle,
cancel_requested: state.demo_core_extraction_cancel_requested(),
};
let summary_result =
ks_pipeline::execute_core_extraction(&store, &pipeline_request, &observer).await;
let summary = match summary_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(summary_payload(summary));
}
pub(crate) fn demo_core_extraction_cancel(state: &crate::AppState) -> bool {
let running = state.demo_core_extraction_running().load(std::sync::atomic::Ordering::Acquire);
state
.demo_core_extraction_cancel_requested()
.store(true, std::sync::atomic::Ordering::Release);
return running;
}
fn build_pipeline_request(
request: crate::DemoCoreExtractionRequest,
) -> std::result::Result<ks_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(ks_pipeline::CoreExtractionSource::Signatures(signatures))
},
"pending" => std::result::Result::Ok(ks_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(ks_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(ks_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 = ks_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 summary_payload(
summary: ks_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,
};
}
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;
}
#[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()]);
}
}