v0.1.0-pre.076
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: kb-app-demo-desktop/src/demo_decode_replay.rs
|
||||
// version: 33
|
||||
// version: 37
|
||||
|
||||
//! Tauri commands and UI payloads for contextual instruction decode replay.
|
||||
|
||||
@@ -357,7 +357,289 @@ impl std::ops::Drop for crate::DemoDecodeReplayRunGuard<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn register_active_campaign(
|
||||
/// Returns available decoders and default replay bounds.
|
||||
pub(crate) fn demo_decode_replay_options(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> crate::DemoDecodeReplayOptionsPayload {
|
||||
let decoders = available_decoders();
|
||||
let decoder_options = decoders
|
||||
.iter()
|
||||
.map(|decoder| {
|
||||
let identity = decoder.identity();
|
||||
return crate::DemoDecodeReplayDecoderOption {
|
||||
name: identity.name,
|
||||
version: identity.version,
|
||||
program_ids: decoder
|
||||
.surfaces()
|
||||
.iter()
|
||||
.map(|surface| return surface.program_id.to_string())
|
||||
.collect(),
|
||||
};
|
||||
})
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let materializers = available_materializers();
|
||||
let materializer_names = materializers
|
||||
.iter()
|
||||
.map(|materializer| {
|
||||
let identity = materializer.identity();
|
||||
return format!("{}@{}", identity.name, identity.version);
|
||||
})
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let programs = kb_program_ids::registered_program_ids()
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
return crate::DemoDecodeReplayProgramOption {
|
||||
code: entry.code().to_string(),
|
||||
program_id: entry.program_id().to_string(),
|
||||
};
|
||||
})
|
||||
.collect();
|
||||
let running = state.demo_decode_replay_running().load(std::sync::atomic::Ordering::Acquire);
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "load_options",
|
||||
pipeline_version = kb_pipeline::DECODE_PIPELINE_VERSION,
|
||||
decoder_count = decoder_options.len(),
|
||||
materializer_names = ?materializer_names,
|
||||
default_limit = 100_u32,
|
||||
default_max_concurrent_inputs = 4_u32,
|
||||
running,
|
||||
"return contextual decode replay options"
|
||||
);
|
||||
return crate::DemoDecodeReplayOptionsPayload {
|
||||
pipeline_version: kb_pipeline::DECODE_PIPELINE_VERSION.to_string(),
|
||||
decoders: decoder_options,
|
||||
materializer_names,
|
||||
programs,
|
||||
default_limit: 100,
|
||||
default_max_concurrent_inputs: 4,
|
||||
running,
|
||||
};
|
||||
}
|
||||
|
||||
/// Executes one bounded contextual decode and optional materialization campaign.
|
||||
pub(crate) async fn demo_decode_replay_execute(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoDecodeReplayRequest,
|
||||
) -> std::result::Result<crate::DemoDecodeReplaySummaryPayload, std::string::String> {
|
||||
let campaign_id = kb_pipeline::new_decode_campaign_id();
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "execute",
|
||||
campaign_id = %campaign_id,
|
||||
instruction_state = %request.instruction_state,
|
||||
signature_line_count = optional_line_count(&request.signatures_text),
|
||||
program_id = ?request.program_id,
|
||||
instruction_paths_text = ?request.instruction_paths_text,
|
||||
decoder_names = ?request.decoder_names,
|
||||
limit = request.limit,
|
||||
max_concurrent_inputs = request.max_concurrent_inputs,
|
||||
all_compatible = request.all_compatible,
|
||||
force_replay = request.force_replay,
|
||||
force_replay_all_matching = request.force_replay_all_matching,
|
||||
materialize_after_decode = request.materialize_after_decode,
|
||||
"received contextual decode replay command"
|
||||
);
|
||||
let acquire_result = state.demo_decode_replay_running().compare_exchange(
|
||||
false,
|
||||
true,
|
||||
std::sync::atomic::Ordering::AcqRel,
|
||||
std::sync::atomic::Ordering::Acquire,
|
||||
);
|
||||
if acquire_result.is_err() {
|
||||
tracing::warn!(target: crate::TRACING_TARGET, action = "execute", campaign_id = %campaign_id, accepted = false, reason = "already_running", "reject contextual decode replay command");
|
||||
return std::result::Result::Err(
|
||||
"a contextual decode replay campaign is already running".to_string(),
|
||||
);
|
||||
}
|
||||
let _run_guard = crate::DemoDecodeReplayRunGuard {
|
||||
running: state.demo_decode_replay_running(),
|
||||
campaign_id: state.demo_decode_replay_campaign_id(),
|
||||
};
|
||||
let register_result =
|
||||
register_active_campaign(state.demo_decode_replay_campaign_id(), campaign_id.as_str());
|
||||
if let std::result::Result::Err(error) = register_result {
|
||||
tracing::error!(target: crate::TRACING_TARGET, action = "register_campaign", campaign_id = %campaign_id, error = %error, "cannot lock active contextual decode campaign slot");
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
state
|
||||
.demo_decode_replay_cancel_requested()
|
||||
.store(false, std::sync::atomic::Ordering::Release);
|
||||
let pipeline_request_result = build_pipeline_request(request, campaign_id.clone());
|
||||
let pipeline_request = match pipeline_request_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
tracing::error!(target: crate::TRACING_TARGET, action = "build_request", campaign_id = %campaign_id, error = %error, "cannot build contextual decode replay request");
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "execute_pipeline",
|
||||
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,
|
||||
min_slot = ?pipeline_request.selection.min_slot,
|
||||
max_slot = ?pipeline_request.selection.max_slot,
|
||||
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,
|
||||
"start contextual decode replay pipeline"
|
||||
);
|
||||
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) => {
|
||||
tracing::error!(target: crate::TRACING_TARGET, action = "connect_store", campaign_id = %pipeline_request.campaign_id, error = %error, "cannot connect contextual decode replay store");
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
let decoders = available_decoders();
|
||||
let materializers = available_materializers();
|
||||
let observer = crate::DemoDecodeReplayObserver {
|
||||
app_handle,
|
||||
campaign_id: pipeline_request.campaign_id.clone(),
|
||||
cancel_requested: state.demo_decode_replay_cancel_requested(),
|
||||
};
|
||||
let summary_result = kb_pipeline::execute_decode_replay(
|
||||
&store,
|
||||
&pipeline_request,
|
||||
decoders.as_slice(),
|
||||
materializers.as_slice(),
|
||||
&observer,
|
||||
)
|
||||
.await;
|
||||
let summary = match summary_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
tracing::error!(target: crate::TRACING_TARGET, action = "execute_pipeline", campaign_id = %pipeline_request.campaign_id, error = %error, "contextual decode replay pipeline failed");
|
||||
return std::result::Result::Err(error.to_string());
|
||||
},
|
||||
};
|
||||
tracing::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
action = "execute_pipeline",
|
||||
campaign_id = %summary.campaign_id,
|
||||
selected = summary.selected,
|
||||
started = summary.started,
|
||||
completed = summary.completed,
|
||||
unmatched = summary.unmatched,
|
||||
not_started = summary.not_started,
|
||||
failed_inputs = summary.failed_inputs,
|
||||
processing_error_inputs = summary.processing_error_inputs,
|
||||
cancelled = summary.cancelled,
|
||||
processors = ?summary.processors,
|
||||
"contextual decode replay command completed"
|
||||
);
|
||||
return std::result::Result::Ok(summary_payload(summary));
|
||||
}
|
||||
|
||||
/// Requests cooperative cancellation of the current decode replay campaign.
|
||||
pub(crate) fn demo_decode_replay_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
|
||||
let running = state.demo_decode_replay_running().load(std::sync::atomic::Ordering::Acquire);
|
||||
state
|
||||
.demo_decode_replay_cancel_requested()
|
||||
.store(true, std::sync::atomic::Ordering::Release);
|
||||
let campaign_lock_result = state.demo_decode_replay_campaign_id().lock();
|
||||
let campaign_id = match campaign_lock_result {
|
||||
std::result::Result::Ok(active_campaign_id) => active_campaign_id.clone(),
|
||||
std::result::Result::Err(_) => std::option::Option::None,
|
||||
};
|
||||
tracing::debug!(target: crate::TRACING_TARGET, action = "cancel", campaign_id = ?campaign_id, running, cancellation_requested = true, "contextual decode replay cancellation command handled");
|
||||
return running;
|
||||
}
|
||||
|
||||
/// Loads read-only decode, materialization, ledger and coverage diagnostics.
|
||||
pub(crate) async fn demo_decode_replay_diagnostics(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::DemoDecodeDiagnosticsPayload, std::string::String> {
|
||||
tracing::debug!(target: crate::TRACING_TARGET, action = "load_diagnostics", coverage_limit = 500_u32, "load contextual decode replay diagnostics");
|
||||
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 table_result = store.known_table_diagnostics().await;
|
||||
let all_tables = match table_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let selected_tables: std::vec::Vec<crate::DemoSqlTableSnapshot> = all_tables
|
||||
.iter()
|
||||
.filter(|table| {
|
||||
return table.table_name.starts_with("kb_sol_decode_")
|
||||
|| table.table_name.starts_with("kb_sol_mat_")
|
||||
|| table.table_name == "kb_sol_ops_processing_ledger";
|
||||
})
|
||||
.map(crate::table_snapshot_from_pg)
|
||||
.collect();
|
||||
let coverage_result = kb_store::DecodePipelineStore::list_decode_coverage_summary(
|
||||
&store,
|
||||
std::option::Option::None,
|
||||
std::option::Option::None,
|
||||
500,
|
||||
)
|
||||
.await;
|
||||
let coverage = match coverage_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
tracing::debug!(target: crate::TRACING_TARGET, action = "load_diagnostics", table_count = selected_tables.len(), coverage_count = coverage.len(), "contextual decode replay diagnostics loaded");
|
||||
return std::result::Result::Ok(crate::DemoDecodeDiagnosticsPayload {
|
||||
tables: selected_tables,
|
||||
coverage: coverage.into_iter().map(coverage_payload).collect(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Loads a bounded journal of committed SPL Memo transaction annotations.
|
||||
pub(crate) async fn demo_decode_replay_annotations(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
request: crate::DemoTransactionAnnotationRequest,
|
||||
) -> std::result::Result<std::vec::Vec<crate::DemoTransactionAnnotationRow>, std::string::String> {
|
||||
let filter_result = kb_store::MaterializedEventFilter::new(
|
||||
std::option::Option::Some("materializer.transaction.annotations".to_string()),
|
||||
std::option::Option::Some("transaction_annotation".to_string()),
|
||||
request.signature_contains,
|
||||
request.limit,
|
||||
);
|
||||
let filter = match filter_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
tracing::debug!(target: crate::TRACING_TARGET, action = "load_transaction_annotations", signature_contains = ?filter.signature_contains, limit = filter.limit, "load bounded committed transaction annotation journal");
|
||||
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 rows_result =
|
||||
kb_store::DecodePipelineStore::list_materialized_events(&store, &filter).await;
|
||||
let rows = match rows_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
|
||||
};
|
||||
let mut output = std::vec::Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let mapped = annotation_payload(row);
|
||||
match mapped {
|
||||
std::result::Result::Ok(value) => output.push(value),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
}
|
||||
tracing::debug!(target: crate::TRACING_TARGET, action = "load_transaction_annotations", row_count = output.len(), "bounded committed transaction annotation journal loaded");
|
||||
return std::result::Result::Ok(output);
|
||||
}
|
||||
|
||||
fn register_active_campaign(
|
||||
campaign_slot: &std::sync::Mutex<std::option::Option<std::string::String>>,
|
||||
campaign_id: &str,
|
||||
) -> std::result::Result<(), std::string::String> {
|
||||
@@ -374,8 +656,7 @@ pub(crate) fn register_active_campaign(
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
pub(crate) fn available_materializers()
|
||||
-> std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> {
|
||||
fn available_materializers() -> std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> {
|
||||
return std::vec![
|
||||
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
|
||||
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer),
|
||||
@@ -388,8 +669,7 @@ pub(crate) fn available_materializers()
|
||||
];
|
||||
}
|
||||
|
||||
pub(crate) fn available_decoders()
|
||||
-> std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> {
|
||||
fn available_decoders() -> std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> {
|
||||
return std::vec![
|
||||
std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder),
|
||||
std::sync::Arc::new(kb_lib::DcMetadataMetaplexTokenMetadataDecoder),
|
||||
@@ -401,7 +681,7 @@ pub(crate) fn available_decoders()
|
||||
];
|
||||
}
|
||||
|
||||
pub(crate) fn build_demo_decode_replay_pipeline_request(
|
||||
fn build_pipeline_request(
|
||||
request: crate::DemoDecodeReplayRequest,
|
||||
campaign_id: std::string::String,
|
||||
) -> std::result::Result<kb_pipeline::DecodeReplayRequest, std::string::String> {
|
||||
@@ -474,7 +754,7 @@ pub(crate) fn build_demo_decode_replay_pipeline_request(
|
||||
return std::result::Result::Ok(pipeline_request);
|
||||
}
|
||||
|
||||
pub(crate) fn optional_line_count(value: &std::option::Option<std::string::String>) -> usize {
|
||||
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()
|
||||
@@ -483,7 +763,7 @@ pub(crate) fn optional_line_count(value: &std::option::Option<std::string::Strin
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn text_sample(values: &[std::string::String], limit: usize) -> std::vec::Vec<&str> {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -538,7 +818,7 @@ fn split_lines(text: std::option::Option<&str>) -> std::vec::Vec<std::string::St
|
||||
return output;
|
||||
}
|
||||
|
||||
pub(crate) fn demo_decode_replay_summary_payload(
|
||||
fn summary_payload(
|
||||
summary: kb_pipeline::DecodeReplaySummary,
|
||||
) -> crate::DemoDecodeReplaySummaryPayload {
|
||||
return crate::DemoDecodeReplaySummaryPayload {
|
||||
@@ -576,7 +856,7 @@ pub(crate) fn demo_decode_replay_summary_payload(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn coverage_payload(
|
||||
fn coverage_payload(
|
||||
value: kb_store::DecodeCoverageSummaryRow,
|
||||
) -> crate::DemoDecodeCoverageSummaryPayload {
|
||||
return crate::DemoDecodeCoverageSummaryPayload {
|
||||
@@ -597,7 +877,7 @@ pub(crate) fn coverage_payload(
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn annotation_payload(
|
||||
fn annotation_payload(
|
||||
row: kb_store::MaterializedEventQueryRow,
|
||||
) -> std::result::Result<crate::DemoTransactionAnnotationRow, std::string::String> {
|
||||
if row.processor_name != "materializer.transaction.annotations"
|
||||
@@ -742,7 +1022,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn native_memo_token_2022_elgamal_classic_token_and_ata_decoders_are_registered() {
|
||||
let decoders = crate::available_decoders();
|
||||
let decoders = super::available_decoders();
|
||||
assert_eq!(decoders.len(), 7);
|
||||
let mut names = decoders
|
||||
.iter()
|
||||
@@ -764,7 +1044,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn runtime_materializer_registry_is_complete_for_current_instructional_surfaces() {
|
||||
let materializers = crate::available_materializers();
|
||||
let materializers = super::available_materializers();
|
||||
assert_eq!(materializers.len(), 8);
|
||||
let names = materializers
|
||||
.iter()
|
||||
@@ -798,7 +1078,7 @@ mod tests {
|
||||
),
|
||||
] {
|
||||
let observation = ata_observation(entry);
|
||||
let owners = crate::available_materializers()
|
||||
let owners = super::available_materializers()
|
||||
.iter()
|
||||
.filter(|materializer| return materializer.accepts_observation(&observation))
|
||||
.map(|materializer| return materializer.identity().name)
|
||||
@@ -836,7 +1116,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn force_replay_requires_signatures_or_all_matching_authorization() {
|
||||
let result = crate::build_demo_decode_replay_pipeline_request(
|
||||
let result = super::build_pipeline_request(
|
||||
request(std::option::Option::None, true, false, false),
|
||||
"decode-test".to_string(),
|
||||
);
|
||||
@@ -845,7 +1125,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn force_replay_all_matching_is_forwarded() {
|
||||
let result = crate::build_demo_decode_replay_pipeline_request(
|
||||
let result = super::build_pipeline_request(
|
||||
request(std::option::Option::None, true, true, false),
|
||||
"decode-test".to_string(),
|
||||
);
|
||||
@@ -880,7 +1160,7 @@ mod tests {
|
||||
#[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");
|
||||
let result = super::register_active_campaign(&campaign_id, "decode-test");
|
||||
assert!(result.is_ok());
|
||||
let lock_result = campaign_id.lock();
|
||||
let active_campaign_id = match lock_result {
|
||||
@@ -894,10 +1174,7 @@ mod tests {
|
||||
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 result = super::build_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}"),
|
||||
@@ -944,7 +1221,7 @@ mod tests {
|
||||
created_at: "created".to_string(),
|
||||
updated_at: "updated".to_string(),
|
||||
};
|
||||
let result = crate::annotation_payload(row);
|
||||
let result = super::annotation_payload(row);
|
||||
let mapped = match result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("annotation mapping failed: {error}"),
|
||||
@@ -971,6 +1248,6 @@ mod tests {
|
||||
created_at: "created".to_string(),
|
||||
updated_at: "updated".to_string(),
|
||||
};
|
||||
assert!(crate::annotation_payload(row).is_err());
|
||||
assert!(super::annotation_payload(row).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user