v0.1.0-pre.076

This commit is contained in:
2026-08-01 16:23:15 +02:00
parent f4331cf48a
commit d7284ab71e
27 changed files with 2273 additions and 2020 deletions

View File

@@ -93,3 +93,6 @@ Les onze crates possèdent désormais `README.md`, `TODO.md`, `USAGE.md` et `CHA
- [Extraction Core, replay et matérialisation](guides/REPLAY_CORE_EXTRACTION_AND_MATERIALIZATION.md) - [Extraction Core, replay et matérialisation](guides/REPLAY_CORE_EXTRACTION_AND_MATERIALIZATION.md)
- [PostgreSQL et stockage](guides/POSTGRES_STORAGE.md) - [PostgreSQL et stockage](guides/POSTGRES_STORAGE.md)
- [Validation Devnet](guides/DEVNET_VALIDATION.md) - [Validation Devnet](guides/DEVNET_VALIDATION.md)
- [`validation/PRE_062_DEVNET_VALIDATION_REPORT.md`](validation/PRE_062_DEVNET_VALIDATION_REPORT.md) ;
- [`validation/WEBSOCKET_MAINNET_RESEARCH_VALIDATION_REPORT.md`](validation/WEBSOCKET_MAINNET_RESEARCH_VALIDATION_REPORT.md) ;
- [`validation/MAINNET_RESEARCH_BACKFILL_VALIDATION_SCENARIO.md`](validation/MAINNET_RESEARCH_BACKFILL_VALIDATION_SCENARIO.md) ;

View File

@@ -1,5 +1,5 @@
<!-- file: docs/guides/DEVNET_0_4_6_OPERATOR_GUIDE.md --> <!-- file: docs/guides/DEVNET_0_4_6_OPERATOR_GUIDE.md -->
<!-- version: 1 --> <!-- version: 2 -->
# Guide opérateur Devnet pour lalignement `0.4.6` # Guide opérateur Devnet pour lalignement `0.4.6`
@@ -37,41 +37,17 @@ Depuis les fenêtres SQL :
7. Rejouer la même sélection. 7. Rejouer la même sélection.
8. Confirmer lidempotence et labsence de duplications. 8. Confirmer lidempotence et labsence de duplications.
## 5. Exécution Solana Core ## 5. Exécutions Solana Core déjà validées
Pour System Transfer : System Transfer et Memo v4 ont déjà été validés pendant `0.1.0-pre.062`. Leur réexécution nest pas un prérequis systématique de `0.4.6`, sauf si une correction ultérieure touche directement leur exécuteur, leur transport, leur insertion canonique, leur replay ou leur matérialisation.
- vérifier le wallet et la balance ; La preuve active est conservée dans [`PRE_062_DEVNET_VALIDATION_REPORT.md`](../validation/PRE_062_DEVNET_VALIDATION_REPORT.md).
- générer ou saisir un destinataire ;
- utiliser un montant minimal ;
- exécuter dabord la simulation ;
- confirmer explicitement lenvoi ;
- vérifier la signature finalisée ;
- effectuer backfill, Core extraction et replay ;
- vérifier les observations et projections attendues.
## 6. Exécution SPL ## 6. Exécutions SPL déjà validées
Valider séparément : ATA classique, ATA Token-2022, SPL Token classique `TransferChecked` et les huit opérations publiques Token-2022 documentées ont déjà été validés pendant `0.1.0-pre.062`, avec PostgreSQL, insertion canonique, extraction Core, replay, matérialisation et idempotence.
- Memo v4 ; Ces scénarios ne doivent être rejoués que si une correction postérieure affecte leur parcours. La campagne `0.4.6` restante se concentre sur les écarts réellement modifiés et sur un backfill borné `mainnet_research`.
- ATA classique ;
- ATA Token-2022 ;
- SPL Token classique `TransferChecked` ;
- opérations Token-2022 documentées comme supportées.
Pour chaque scénario :
1. vérifier les préconditions et comptes ;
2. vérifier les signers ;
3. simuler ;
4. confirmer lenvoi ;
5. vérifier la confirmation réseau ;
6. effectuer le backfill ;
7. effectuer Core extraction ;
8. effectuer Decode replay et matérialisation ;
9. rejouer pour lidempotence ;
10. vérifier létat final via CLI ou RPC lorsque pertinent.
## 7. WebSocket ## 7. WebSocket

View File

@@ -0,0 +1,46 @@
<!-- file: docs/validation/MAINNET_RESEARCH_BACKFILL_VALIDATION_SCENARIO.md -->
<!-- version: 1 -->
# Scénario de validation backfill `mainnet_research`
## Objectif
Valider un parcours borné `raw → Core → decode → materialize` sur Mainnet sans envoi de transaction.
## Paramètres recommandés
- profil : `mainnet_research` ;
- rôle HTTP : endpoint standard Mainnet configuré ;
- commitment : `finalized` ;
- taille de page : `20` ;
- pages maximales : `1` ;
- concurrence : `2` ;
- retries : `2` ;
- mode : Programme Solana ;
- Program ID : `MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr` ;
- direction : `before` ;
- signature dancrage : vide ;
- nombre de signatures : `20`.
Memo v4 est choisi pour limiter le volume et disposer dun décodeur et dun matérialisateur connus.
## Étapes
1. Relever les diagnostics PostgreSQL avant campagne.
2. Lancer le backfill et conserver le résumé JSON.
3. Vérifier les nouvelles lignes raw et observations.
4. Exécuter Core extraction uniquement sur les lignes non traitées.
5. Exécuter Decode replay avec Memo v4 et matérialisation activée.
6. Vérifier les annotations matérialisées.
7. Rejouer la même sélection et confirmer lidempotence.
8. Relever les diagnostics PostgreSQL après campagne.
## Critères de réussite
- campagne bornée terminée sans échec non expliqué ;
- transactions hydratées et stockées ;
- extraction Core sans doublon ;
- décodage Memo cohérent ;
- matérialisation attendue ;
- second replay sans duplication ;
- aucune écriture ou transaction envoyée sur Mainnet.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/PRE_062_DEVNET_VALIDATION_REPORT.md --> <!-- file: docs/validation/PRE_062_DEVNET_VALIDATION_REPORT.md -->
<!-- version: 1 --> <!-- version: 2 -->
# Rapport de validation Devnet — `0.1.0-pre.062` # Rapport de validation Devnet — `0.1.0-pre.062`

View File

@@ -0,0 +1,26 @@
<!-- file: docs/validation/WEBSOCKET_MAINNET_RESEARCH_VALIDATION_REPORT.md -->
<!-- version: 1 -->
# Validation WebSocket `mainnet_research` — `0.1.0-pre.075`
## Portée
La campagne a utilisé le profil `mainnet_research` et une souscription `logsSubscribe`. Les secrets et URLs contenant une clé API ne sont pas reproduits dans ce rapport.
## Preuves observées
- ouverture de `demo_ws` à `11:14:17` ;
- connexion de la session persistante à `11:15:02` ;
- souscription `logsSubscribe` créée avec lidentifiant `16376948` ;
- nouvelles ouvertures de `demo_ws` à `11:16:16` puis `11:16:51` sans déconnexion intermédiaire ;
- notifications reçues avec le même identifiant de souscription après réouverture ;
- désabonnement explicite à `11:16:56` ;
- déconnexion explicite de la session à `11:17:04`.
## Conclusion
La campagne valide que la fermeture et la réouverture de la fenêtre ne détruisent pas la session WebSocket applicative, que létat de souscription reste récupérable et que la fermeture réseau intervient sur action explicite.
## Limite observée
Le Program ID SPL Token classique produit un volume trop élevé pour un test opérateur confortable. Les logs montrent du throttling UI et des événements internes perdus. Les prochaines campagnes doivent utiliser un programme moins actif ou une fenêtre courte, sans interpréter ces pertes UI comme une perte de la session réseau.

View File

@@ -1,8 +1,22 @@
<!-- file: kb-app-demo-desktop/CHANGELOG.md --> <!-- file: kb-app-demo-desktop/CHANGELOG.md -->
<!-- version: 3 --> <!-- version: 6 -->
# CHANGELOG — kb-app-demo-desktop # CHANGELOG — kb-app-demo-desktop
## 0.1.0-pre.076
- correction de larchitecture Tauri : `tauri.rs` reste lunique propriétaire des `#[tauri::command]` et délègue via la façade `crate::*` ;
- suppression des modules intermédiaires `tauri_*` ajoutés à tort ;
- suppression de 43 exports `pub(crate)` qui ne franchissaient aucune frontière de module ;
- renommage des fonctions `*_inner` en noms fonctionnels directs ;
- suppression du helper IPC utilisé une seule fois et simplification de linitialisation de létat ;
- complétion des rustdoc manquantes dans `lib.rs` ;
- correction de laudit statique pour contrôler la minceur sémantique de `tauri.rs` plutôt quun seuil arbitraire de lignes ;
- réduction de `tauri.rs` à lassemblage runtime et aux wrappers strictement nécessaires ;
- répartition des commandes Tauri par domaine dans des modules dédiés ;
- validation Mainnet de la persistance de session WebSocket après fermeture et réouverture de `demo_ws` ;
- ajout du scénario borné de backfill `mainnet_research`.
## 0.1.0-pre.075 ## 0.1.0-pre.075
- ajout de laudit statique Tauri/frontend et du scénario de validation du cycle de vie WebSocket ; - ajout de laudit statique Tauri/frontend et du scénario de validation du cycle de vie WebSocket ;

View File

@@ -1,17 +1,12 @@
<!-- file: kb-app-demo-desktop/TODO.md --> <!-- file: kb-app-demo-desktop/TODO.md -->
<!-- version: 3 --> <!-- version: 4 -->
# TODO — kb-app-demo-desktop # TODO — kb-app-demo-desktop
## Bloquants avant alignement `0.4.6` ## Bloquants avant alignement `0.4.6`
- [ ] Architecture - déplacer hors de `tauri.rs` lorchestration pipeline, stockage et exécution encore substantielle.
- [ ] Architecture - conserver dans `tauri.rs` uniquement les wrappers Tauri, lassemblage runtime et les adaptations minimales.
- [ ] Validation - vérifier intégralement la synchronisation des adaptateurs Tauri et des payloads TS-RS avec les APIs réutilisables. - [ ] Validation - vérifier intégralement la synchronisation des adaptateurs Tauri et des payloads TS-RS avec les APIs réutilisables.
- [ ] Validation - couvrir explicitement les cycles douverture, fermeture et réouverture des fenêtres concernées. - [ ] Validation - couvrir explicitement les cycles douverture, fermeture et réouverture des fenêtres concernées.
- [ ] WebSocket - valider que la fermeture de `demo_ws` ne ferme pas une session WebSocket active.
- [ ] WebSocket - valider que la réouverture de `demo_ws` restaure létat courant de la session WebSocket.
## Entre `0.4.6` et `0.4.7` ## Entre `0.4.6` et `0.4.7`

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_backfill.rs // file: kb-app-demo-desktop/src/demo_backfill.rs
// version: 12 // version: 15
//! Tauri commands and UI payloads for bounded HTTP transaction backfills. //! Tauri commands and UI payloads for bounded HTTP transaction backfills.
@@ -227,7 +227,103 @@ impl std::ops::Drop for crate::DemoBackfillRunGuard<'_> {
} }
} }
pub(crate) fn build_role_options( pub(crate) fn demo_backfill_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
let running = state.demo_backfill_running().load(std::sync::atomic::Ordering::Acquire);
state
.demo_backfill_cancel_requested()
.store(true, std::sync::atomic::Ordering::Release);
return running;
}
pub(crate) fn demo_backfill_options(
state: tauri::State<'_, crate::AppState>,
) -> crate::DemoBackfillOptionsPayload {
let roles = build_role_options(state.http_pool().snapshot());
let default_role = if roles.iter().any(|item| return item.role == "history_backfill") {
std::option::Option::Some("history_backfill".to_string())
} else {
roles.first().map(|item| return item.role.clone())
};
let programs = kb_program_ids::registered_program_ids()
.iter()
.map(|entry| {
return crate::DemoBackfillProgramOption {
code: entry.code().to_string(),
program_id: entry.program_id().to_string(),
};
})
.collect();
return crate::DemoBackfillOptionsPayload {
roles,
programs,
default_role,
default_commitment: "confirmed".to_string(),
default_page_size: 100,
default_max_pages: 20,
default_max_concurrent_requests: 4,
default_max_retries: 2,
running: state.demo_backfill_running().load(std::sync::atomic::Ordering::Acquire),
};
}
pub(crate) async fn demo_backfill_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoBackfillRequest,
) -> std::result::Result<crate::DemoBackfillSummaryPayload, std::string::String> {
let acquire_result = state.demo_backfill_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 backfill campaign is already running".to_string());
}
let _run_guard = crate::DemoBackfillRunGuard { running: state.demo_backfill_running() };
state
.demo_backfill_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let pipeline_request = match build_pipeline_request(request) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let profile = state.active_profile();
let store_options = match kb_store::PostgresStoreOptions::new(
profile.database.postgres.url.clone(),
profile.database.postgres.max_connections,
profile.database.postgres.connect_timeout_ms,
false,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match kb_store::PostgresStore::connect(store_options).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let observer = crate::DemoBackfillObserver {
app_handle,
cancel_requested: state.demo_backfill_cancel_requested(),
};
let summary = match kb_pipeline::execute_http_backfill(
state.http_pool(),
&store,
&pipeline_request,
&observer,
)
.await
{
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));
}
fn build_role_options(
snapshots: std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot>, snapshots: std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot>,
) -> std::vec::Vec<crate::DemoBackfillRoleOption> { ) -> std::vec::Vec<crate::DemoBackfillRoleOption> {
let mut role_methods = std::collections::BTreeMap::< let mut role_methods = std::collections::BTreeMap::<
@@ -266,7 +362,7 @@ pub(crate) fn build_role_options(
return output; return output;
} }
pub(crate) fn build_demo_backfill_pipeline_request( fn build_pipeline_request(
request: DemoBackfillRequest, request: DemoBackfillRequest,
) -> std::result::Result<kb_pipeline::BackfillRequest, std::string::String> { ) -> std::result::Result<kb_pipeline::BackfillRequest, std::string::String> {
let source_result = match request.mode.trim() { let source_result = match request.mode.trim() {
@@ -385,9 +481,7 @@ fn address_source(
}); });
} }
pub(crate) fn demo_backfill_summary_payload( fn summary_payload(summary: kb_pipeline::BackfillSummary) -> DemoBackfillSummaryPayload {
summary: kb_pipeline::BackfillSummary,
) -> DemoBackfillSummaryPayload {
return crate::DemoBackfillSummaryPayload { return crate::DemoBackfillSummaryPayload {
capture_session_id: summary.capture_session_id, capture_session_id: summary.capture_session_id,
filter_code: summary.filter_code, filter_code: summary.filter_code,

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_core_extraction.rs // file: kb-app-demo-desktop/src/demo_core_extraction.rs
// version: 11 // version: 13
//! Tauri commands and UI payloads for canonical transaction to core extraction. //! Tauri commands and UI payloads for canonical transaction to core extraction.
@@ -194,7 +194,7 @@ pub(crate) fn demo_core_extraction_options_payload(
}; };
} }
pub(crate) async fn execute_demo_core_extraction( pub(crate) async fn demo_core_extraction_execute(
app_handle: tauri::AppHandle, app_handle: tauri::AppHandle,
state: &crate::AppState, state: &crate::AppState,
request: crate::DemoCoreExtractionRequest, request: crate::DemoCoreExtractionRequest,
@@ -216,7 +216,7 @@ pub(crate) async fn execute_demo_core_extraction(
state state
.demo_core_extraction_cancel_requested() .demo_core_extraction_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release); .store(false, std::sync::atomic::Ordering::Release);
let pipeline_request_result = crate::build_demo_core_extraction_pipeline_request(request); let pipeline_request_result = build_pipeline_request(request);
let pipeline_request = match pipeline_request_result { let pipeline_request = match pipeline_request_result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
@@ -236,10 +236,10 @@ pub(crate) async fn execute_demo_core_extraction(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
return std::result::Result::Ok(crate::demo_core_extraction_summary_payload(summary)); return std::result::Result::Ok(summary_payload(summary));
} }
pub(crate) fn cancel_demo_core_extraction(state: &crate::AppState) -> bool { pub(crate) fn demo_core_extraction_cancel(state: &crate::AppState) -> bool {
let running = state.demo_core_extraction_running().load(std::sync::atomic::Ordering::Acquire); let running = state.demo_core_extraction_running().load(std::sync::atomic::Ordering::Acquire);
state state
.demo_core_extraction_cancel_requested() .demo_core_extraction_cancel_requested()
@@ -247,7 +247,7 @@ pub(crate) fn cancel_demo_core_extraction(state: &crate::AppState) -> bool {
return running; return running;
} }
pub(crate) fn build_demo_core_extraction_pipeline_request( fn build_pipeline_request(
request: crate::DemoCoreExtractionRequest, request: crate::DemoCoreExtractionRequest,
) -> std::result::Result<kb_pipeline::CoreExtractionRequest, std::string::String> { ) -> std::result::Result<kb_pipeline::CoreExtractionRequest, std::string::String> {
let source_result = match request.mode.trim() { let source_result = match request.mode.trim() {
@@ -308,6 +308,25 @@ pub(crate) fn build_demo_core_extraction_pipeline_request(
return std::result::Result::Ok(pipeline_request); return std::result::Result::Ok(pipeline_request);
} }
fn 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,
};
}
fn split_signatures(text: std::option::Option<&str>) -> std::vec::Vec<std::string::String> { fn split_signatures(text: std::option::Option<&str>) -> std::vec::Vec<std::string::String> {
let source = match text { let source = match text {
std::option::Option::Some(value) => value, std::option::Option::Some(value) => value,
@@ -327,25 +346,6 @@ fn split_signatures(text: std::option::Option<&str>) -> std::vec::Vec<std::strin
return output; 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)] #[cfg(test)]
mod tests { mod tests {
#[test] #[test]

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_decode_replay.rs // file: kb-app-demo-desktop/src/demo_decode_replay.rs
// version: 33 // version: 37
//! Tauri commands and UI payloads for contextual instruction decode replay. //! 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_slot: &std::sync::Mutex<std::option::Option<std::string::String>>,
campaign_id: &str, campaign_id: &str,
) -> std::result::Result<(), std::string::String> { ) -> std::result::Result<(), std::string::String> {
@@ -374,8 +656,7 @@ pub(crate) fn register_active_campaign(
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
pub(crate) fn available_materializers() fn available_materializers() -> std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> {
-> std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> {
return std::vec![ return std::vec![
std::sync::Arc::new(kb_lib::MtAdminMaterializer), std::sync::Arc::new(kb_lib::MtAdminMaterializer),
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer), std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer),
@@ -388,8 +669,7 @@ pub(crate) fn available_materializers()
]; ];
} }
pub(crate) fn available_decoders() fn available_decoders() -> std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> {
-> std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> {
return std::vec![ return std::vec![
std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder), std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder),
std::sync::Arc::new(kb_lib::DcMetadataMetaplexTokenMetadataDecoder), 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, request: crate::DemoDecodeReplayRequest,
campaign_id: std::string::String, campaign_id: std::string::String,
) -> std::result::Result<kb_pipeline::DecodeReplayRequest, 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); 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 { return match value {
std::option::Option::Some(text) => { std::option::Option::Some(text) => {
text.lines().filter(|line| return !line.trim().is_empty()).count() 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(); 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; return output;
} }
pub(crate) fn demo_decode_replay_summary_payload( fn summary_payload(
summary: kb_pipeline::DecodeReplaySummary, summary: kb_pipeline::DecodeReplaySummary,
) -> crate::DemoDecodeReplaySummaryPayload { ) -> crate::DemoDecodeReplaySummaryPayload {
return 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, value: kb_store::DecodeCoverageSummaryRow,
) -> crate::DemoDecodeCoverageSummaryPayload { ) -> crate::DemoDecodeCoverageSummaryPayload {
return 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, row: kb_store::MaterializedEventQueryRow,
) -> std::result::Result<crate::DemoTransactionAnnotationRow, std::string::String> { ) -> std::result::Result<crate::DemoTransactionAnnotationRow, std::string::String> {
if row.processor_name != "materializer.transaction.annotations" if row.processor_name != "materializer.transaction.annotations"
@@ -742,7 +1022,7 @@ mod tests {
#[test] #[test]
fn native_memo_token_2022_elgamal_classic_token_and_ata_decoders_are_registered() { 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); assert_eq!(decoders.len(), 7);
let mut names = decoders let mut names = decoders
.iter() .iter()
@@ -764,7 +1044,7 @@ mod tests {
#[test] #[test]
fn runtime_materializer_registry_is_complete_for_current_instructional_surfaces() { 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); assert_eq!(materializers.len(), 8);
let names = materializers let names = materializers
.iter() .iter()
@@ -798,7 +1078,7 @@ mod tests {
), ),
] { ] {
let observation = ata_observation(entry); let observation = ata_observation(entry);
let owners = crate::available_materializers() let owners = super::available_materializers()
.iter() .iter()
.filter(|materializer| return materializer.accepts_observation(&observation)) .filter(|materializer| return materializer.accepts_observation(&observation))
.map(|materializer| return materializer.identity().name) .map(|materializer| return materializer.identity().name)
@@ -836,7 +1116,7 @@ mod tests {
#[test] #[test]
fn force_replay_requires_signatures_or_all_matching_authorization() { 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), request(std::option::Option::None, true, false, false),
"decode-test".to_string(), "decode-test".to_string(),
); );
@@ -845,7 +1125,7 @@ mod tests {
#[test] #[test]
fn force_replay_all_matching_is_forwarded() { 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), request(std::option::Option::None, true, true, false),
"decode-test".to_string(), "decode-test".to_string(),
); );
@@ -880,7 +1160,7 @@ mod tests {
#[test] #[test]
fn register_active_campaign_sets_slot_without_exposing_guard() { fn register_active_campaign_sets_slot_without_exposing_guard() {
let campaign_id = std::sync::Mutex::new(std::option::Option::None); 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()); assert!(result.is_ok());
let lock_result = campaign_id.lock(); let lock_result = campaign_id.lock();
let active_campaign_id = match lock_result { let active_campaign_id = match lock_result {
@@ -894,10 +1174,7 @@ mod tests {
fn incomplete_signature_state_uses_actionable_states_and_expansion() { fn incomplete_signature_state_uses_actionable_states_and_expansion() {
let mut request = request(std::option::Option::None, false, false, true); let mut request = request(std::option::Option::None, false, false, true);
request.instruction_state = "incomplete_signatures".to_string(); request.instruction_state = "incomplete_signatures".to_string();
let result = crate::build_demo_decode_replay_pipeline_request( let result = super::build_pipeline_request(request, "decode-incomplete".to_string());
request,
"decode-incomplete".to_string(),
);
let pipeline_request = match result { let pipeline_request = match result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("request mapping failed: {error}"), std::result::Result::Err(error) => panic!("request mapping failed: {error}"),
@@ -944,7 +1221,7 @@ mod tests {
created_at: "created".to_string(), created_at: "created".to_string(),
updated_at: "updated".to_string(), updated_at: "updated".to_string(),
}; };
let result = crate::annotation_payload(row); let result = super::annotation_payload(row);
let mapped = match result { let mapped = match result {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("annotation mapping failed: {error}"), std::result::Result::Err(error) => panic!("annotation mapping failed: {error}"),
@@ -971,6 +1248,6 @@ mod tests {
created_at: "created".to_string(), created_at: "created".to_string(),
updated_at: "updated".to_string(), updated_at: "updated".to_string(),
}; };
assert!(crate::annotation_payload(row).is_err()); assert!(super::annotation_payload(row).is_err());
} }
} }

View File

@@ -27,14 +27,6 @@ pub(crate) struct DemoExecutionDevnetStoreReadinessPayload {
pub(crate) expected_tables: u32, pub(crate) expected_tables: u32,
} }
/// Converts one collection length to the bounded UI counter type.
fn bounded_u32(value: usize) -> u32 {
return match u32::try_from(value) {
std::result::Result::Ok(converted) => converted,
std::result::Result::Err(_) => u32::MAX,
};
}
/// Converts the reusable scenario readiness report to the desktop TS-RS payload. /// Converts the reusable scenario readiness report to the desktop TS-RS payload.
pub(crate) fn devnet_store_readiness_payload( pub(crate) fn devnet_store_readiness_payload(
readiness: kb_pipeline_demo_scenarios::DevnetProfileStoreReadiness, readiness: kb_pipeline_demo_scenarios::DevnetProfileStoreReadiness,
@@ -48,3 +40,24 @@ pub(crate) fn devnet_store_readiness_payload(
expected_tables: bounded_u32(readiness.expected_tables), expected_tables: bounded_u32(readiness.expected_tables),
}; };
} }
/// Serializes a diagnostic value to indented JSON without panicking.
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()
},
};
}
/// Converts one collection length to the bounded UI counter type.
fn bounded_u32(value: usize) -> u32 {
return match u32::try_from(value) {
std::result::Result::Ok(converted) => converted,
std::result::Result::Err(_) => u32::MAX,
};
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_solana_core.rs // file: kb-app-demo-desktop/src/demo_execution_solana_core.rs
// version: 8 // version: 12
//! Tauri adapter for bounded Solana Core execution on Devnet. //! Tauri adapter for bounded Solana Core execution on Devnet.
@@ -368,7 +368,263 @@ impl std::ops::Drop for crate::DemoExecutionSolanaCoreRunGuard<'_> {
} }
} }
pub(crate) fn devnet_profile_options( pub(crate) fn select_devnet_profile(
config: &kb_config::AppConfig,
profile_name: &str,
) -> std::result::Result<kb_config::ProfileConfig, std::string::String> {
return kb_pipeline_demo_scenarios::resolve_demo_devnet_profile(
config,
std::option::Option::Some(profile_name),
)
.map_err(|error| return error.to_string());
}
pub(crate) fn execution_cluster_code(
cluster: kb_lib::ExApiExecutionCluster,
) -> std::string::String {
return match cluster {
kb_lib::ExApiExecutionCluster::Localnet => "localnet".to_string(),
kb_lib::ExApiExecutionCluster::Devnet => "devnet".to_string(),
kb_lib::ExApiExecutionCluster::Testnet => "testnet".to_string(),
kb_lib::ExApiExecutionCluster::Mainnet => "mainnet".to_string(),
};
}
pub(crate) fn confirmation_status_code(
status: kb_lib::ExApiExecutionConfirmationStatus,
) -> std::string::String {
return match status {
kb_lib::ExApiExecutionConfirmationStatus::Processed => "processed".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Confirmed => "confirmed".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Finalized => "finalized".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Failed => "failed".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Expired => "expired".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::TimedOut => "timed_out".to_string(),
};
}
/// Verifies or initializes the PostgreSQL schema selected by one Devnet profile.
pub(crate) async fn demo_execution_devnet_prepare_profile(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
) -> std::result::Result<crate::DemoExecutionDevnetStoreReadinessPayload, std::string::String> {
let profile = match crate::select_devnet_profile(state.app_config(), profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let readiness =
match kb_pipeline_demo_scenarios::prepare_demo_devnet_profile_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(error.to_string());
},
};
return std::result::Result::Ok(crate::devnet_store_readiness_payload(readiness));
}
/// Returns compatible Devnet profiles and conservative defaults.
pub(crate) fn demo_execution_solana_core_options(
state: tauri::State<'_, crate::AppState>,
) -> crate::DemoExecutionSolanaCoreOptionsPayload {
let profiles = devnet_profile_options(state.app_config());
let default_profile_name = profiles.first().map(|profile| return profile.name.clone());
return crate::DemoExecutionSolanaCoreOptionsPayload {
profiles,
default_profile_name,
default_transfer_lamports: 1_000_000,
running: state
.demo_execution_solana_core_running()
.load(std::sync::atomic::Ordering::Acquire),
};
}
/// Generates a disposable recipient public key without exposing its private key.
pub(crate) fn demo_execution_solana_core_generate_recipient()
-> std::result::Result<crate::DemoExecutionSolanaCoreGeneratedRecipientPayload, std::string::String>
{
let alias = match kb_wallet::WalletAlias::parse("demo-devnet-recipient") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let wallet = kb_wallet::TemporaryWallet::generate(alias);
return std::result::Result::Ok(crate::DemoExecutionSolanaCoreGeneratedRecipientPayload {
public_key: wallet.public_key(),
});
}
/// Executes one bounded System transfer simulation or Devnet submission.
pub(crate) async fn demo_execution_solana_core_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionSolanaCoreRequest,
) -> std::result::Result<crate::DemoExecutionSolanaCoreSummaryPayload, std::string::String> {
let acquire_result = state.demo_execution_solana_core_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 Solana Core execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let initialize_result = store.initialize_store_schema().await;
if let std::result::Result::Err(error) = initialize_result {
return std::result::Result::Err(error.to_string());
}
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSystemTransferRequest::new(
format!("demo-execution-{}", chrono::Utc::now().timestamp_micros()),
kb_lib::MdPubkey(request.recipient.trim().to_string()),
request.lamports,
);
pipeline_request.airdrop_lamports = request.airdrop_lamports;
pipeline_request.submit = request.submit;
pipeline_request.operator_confirmed = request.operator_confirmed;
pipeline_request.post_validation_max_retries = 20;
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
pipeline_request.materialize_after_decode = request.materialize_after_decode;
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
std::vec![std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder)];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> =
if request.materialize_after_decode {
std::vec![
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer,),
std::sync::Arc::new(kb_lib::MtLifecycleMaterializer),
std::sync::Arc::new(kb_lib::MtStakingMaterializer),
]
} else {
std::vec::Vec::new()
};
let observer = crate::DemoExecutionSolanaCoreObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::execute_devnet_system_transfer(
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&pipeline_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
{
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));
}
/// Executes one Memo v4 simulation or explicitly authorized Devnet submission.
pub(crate) async fn demo_execution_spl_memo_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionMemoRequest,
) -> std::result::Result<crate::DemoExecutionMemoSummaryPayload, std::string::String> {
let acquire_result = state.demo_execution_solana_core_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 Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetMemoExecutionRequest::new(
format!("demo-memo-execution-{}", chrono::Utc::now().timestamp_micros()),
request.message,
);
pipeline_request.include_wallet_as_memo_signer = request.include_wallet_as_memo_signer;
pipeline_request.submit = request.submit;
pipeline_request.operator_confirmed = request.operator_confirmed;
pipeline_request.post_validation_max_retries = 20;
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
std::vec![std::sync::Arc::new(kb_lib::DcSplMemoDecoder)];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> =
std::vec![std::sync::Arc::new(kb_lib::MtTransactionAnnotationMaterializer,)];
let observer = crate::DemoExecutionSolanaCoreObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::execute_devnet_memo(
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&pipeline_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(memo_summary_payload(summary));
}
/// Requests cooperative cancellation before the next execution stage.
pub(crate) fn demo_execution_solana_core_cancel(state: tauri::State<'_, crate::AppState>) -> bool {
let running = state
.demo_execution_solana_core_running()
.load(std::sync::atomic::Ordering::Acquire);
if !running {
return false;
}
state
.demo_execution_solana_core_cancel_requested()
.store(true, std::sync::atomic::Ordering::Release);
return true;
}
fn devnet_profile_options(
config: &kb_config::AppConfig, config: &kb_config::AppConfig,
) -> std::vec::Vec<crate::DemoExecutionSolanaCoreProfileOption> { ) -> std::vec::Vec<crate::DemoExecutionSolanaCoreProfileOption> {
let mut output = std::vec::Vec::new(); let mut output = std::vec::Vec::new();
@@ -391,18 +647,7 @@ pub(crate) fn devnet_profile_options(
return output; return output;
} }
pub(crate) fn select_devnet_profile( fn summary_payload(
config: &kb_config::AppConfig,
profile_name: &str,
) -> std::result::Result<kb_config::ProfileConfig, std::string::String> {
return kb_pipeline_demo_scenarios::resolve_demo_devnet_profile(
config,
std::option::Option::Some(profile_name),
)
.map_err(|error| return error.to_string());
}
pub(crate) fn demo_execution_solana_core_summary_payload(
summary: kb_pipeline_demo_scenarios::DevnetSystemTransferSummary, summary: kb_pipeline_demo_scenarios::DevnetSystemTransferSummary,
) -> crate::DemoExecutionSolanaCoreSummaryPayload { ) -> crate::DemoExecutionSolanaCoreSummaryPayload {
let plan_json = crate::pretty_json(&summary.plan); let plan_json = crate::pretty_json(&summary.plan);
@@ -483,7 +728,7 @@ pub(crate) fn demo_execution_solana_core_summary_payload(
}; };
} }
pub(crate) fn demo_execution_solana_core_memo_summary_payload( fn memo_summary_payload(
summary: kb_pipeline_demo_scenarios::DevnetMemoExecutionSummary, summary: kb_pipeline_demo_scenarios::DevnetMemoExecutionSummary,
) -> crate::DemoExecutionMemoSummaryPayload { ) -> crate::DemoExecutionMemoSummaryPayload {
let plan_json = crate::pretty_json(&summary.plan); let plan_json = crate::pretty_json(&summary.plan);
@@ -610,42 +855,6 @@ pub(crate) fn demo_execution_solana_core_memo_summary_payload(
}; };
} }
pub(crate) fn execution_cluster_code(
cluster: kb_lib::ExApiExecutionCluster,
) -> std::string::String {
return match cluster {
kb_lib::ExApiExecutionCluster::Localnet => "localnet".to_string(),
kb_lib::ExApiExecutionCluster::Devnet => "devnet".to_string(),
kb_lib::ExApiExecutionCluster::Testnet => "testnet".to_string(),
kb_lib::ExApiExecutionCluster::Mainnet => "mainnet".to_string(),
};
}
pub(crate) fn confirmation_status_code(
status: kb_lib::ExApiExecutionConfirmationStatus,
) -> std::string::String {
return match status {
kb_lib::ExApiExecutionConfirmationStatus::Processed => "processed".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Confirmed => "confirmed".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Finalized => "finalized".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Failed => "failed".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::Expired => "expired".to_string(),
kb_lib::ExApiExecutionConfirmationStatus::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)] #[cfg(test)]
mod tests { mod tests {
#[test] #[test]
@@ -655,7 +864,7 @@ mod tests {
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("example config parse failed: {error}"), std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
}; };
let profiles = crate::devnet_profile_options(&config); let profiles = super::devnet_profile_options(&config);
assert_eq!(profiles.len(), 1); assert_eq!(profiles.len(), 1);
assert_eq!(profiles[0].name, "local_devnet"); assert_eq!(profiles[0].name, "local_devnet");
assert!(profiles[0].send_enabled); assert!(profiles[0].send_enabled);

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_execution_spl.rs // file: kb-app-demo-desktop/src/demo_execution_spl.rs
// version: 3 // version: 5
//! Dedicated Tauri window for bounded SPL execution on Devnet. //! Dedicated Tauri window for bounded SPL execution on Devnet.
@@ -27,6 +27,45 @@ pub(crate) struct DevnetSplValidationScenarioPayload {
pub required_fixture_variables: std::vec::Vec<std::string::String>, pub required_fixture_variables: std::vec::Vec<std::string::String>,
} }
/// Returns the complete ordered Devnet SPL validation inventory for milestone 0.4.6.
pub(crate) fn demo_execution_spl_validation_scenarios()
-> std::vec::Vec<crate::DevnetSplValidationScenarioPayload> {
return kb_pipeline_demo_scenarios::devnet_spl_validation_scenarios()
.into_iter()
.map(|scenario| {
return crate::DevnetSplValidationScenarioPayload {
id: scenario.id,
label: scenario.label,
family: match scenario.family {
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::Token2022Public => {
"token_2022_public".to_string()
},
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::ElGamalRegistry => {
"elgamal_registry".to_string()
},
kb_pipeline_demo_scenarios::DevnetSplValidationFamily::Token2022Confidential => {
"token_2022_confidential".to_string()
},
},
operation_code: scenario.operation_code,
implementation_status: match scenario.implementation_status {
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::Executable => {
"executable".to_string()
},
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::BackendReady => {
"backend_ready".to_string()
},
kb_pipeline_demo_scenarios::DevnetSplValidationImplementationStatus::ProofFixtureRequired => {
"proof_fixture_required".to_string()
},
},
proof_required: scenario.proof_required,
required_fixture_variables: scenario.required_fixture_variables,
};
})
.collect();
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_http.rs // file: kb-app-demo-desktop/src/demo_http.rs
// version: 9 // version: 13
//! HTTP JSON-RPC demo commands. //! HTTP JSON-RPC demo commands.
@@ -99,7 +99,7 @@ pub(crate) struct DemoHttpExecutionPayload {
pub(crate) response_json: std::string::String, pub(crate) response_json: std::string::String,
} }
pub(crate) async fn demo_http_execute_request_inner( pub(crate) async fn demo_http_execute_request(
state: tauri::State<'_, crate::AppState>, state: tauri::State<'_, crate::AppState>,
request: crate::DemoHttpRequest, request: crate::DemoHttpRequest,
) -> std::result::Result<crate::DemoHttpExecutionPayload, std::string::String> { ) -> std::result::Result<crate::DemoHttpExecutionPayload, std::string::String> {
@@ -119,7 +119,7 @@ pub(crate) async fn demo_http_execute_request_inner(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
let params = match build_demo_http_params( let params = match build_http_params(
&method, &method,
request.first_arg.as_deref(), request.first_arg.as_deref(),
config_json_value, config_json_value,
@@ -155,7 +155,22 @@ pub(crate) async fn demo_http_execute_request_inner(
}); });
} }
pub(crate) fn build_http_role_options( pub(crate) fn demo_http_list_pool_clients(
state: tauri::State<'_, crate::AppState>,
) -> std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot> {
return state.http_pool().snapshot();
}
pub(crate) fn demo_http_options(
state: tauri::State<'_, crate::AppState>,
) -> crate::DemoHttpOptionsPayload {
return crate::DemoHttpOptionsPayload {
roles: build_http_role_options(state.http_pool().snapshot()),
methods: build_http_method_options(),
};
}
fn build_http_role_options(
snapshots: std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot>, snapshots: std::vec::Vec<kb_onchain_transport::HttpPoolClientSnapshot>,
) -> std::vec::Vec<crate::DemoHttpRoleOption> { ) -> std::vec::Vec<crate::DemoHttpRoleOption> {
let mut roles = std::collections::BTreeMap::< let mut roles = std::collections::BTreeMap::<
@@ -183,7 +198,7 @@ pub(crate) fn build_http_role_options(
return options; return options;
} }
pub(crate) fn build_http_method_options() -> std::vec::Vec<crate::DemoHttpMethodOption> { fn build_http_method_options() -> std::vec::Vec<crate::DemoHttpMethodOption> {
let methods = [ let methods = [
("getAccountInfo", "Compte Solana", true, true), ("getAccountInfo", "Compte Solana", true, true),
("getBalance", "Balance SOL", true, true), ("getBalance", "Balance SOL", true, true),
@@ -250,53 +265,7 @@ pub(crate) fn build_http_method_options() -> std::vec::Vec<crate::DemoHttpMethod
return options; return options;
} }
fn parse_optional_json( fn build_http_params(
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, method: &str,
first_arg: std::option::Option<&str>, first_arg: std::option::Option<&str>,
config_json: std::option::Option<serde_json::Value>, config_json: std::option::Option<serde_json::Value>,
@@ -352,6 +321,52 @@ fn build_demo_http_params(
return std::result::Result::Ok(params); return std::result::Result::Ok(params);
} }
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 method_class_to_string(method_class: kb_onchain_transport::HttpMethodClass) -> &'static str { fn method_class_to_string(method_class: kb_onchain_transport::HttpMethodClass) -> &'static str {
return match method_class { return match method_class {
kb_onchain_transport::HttpMethodClass::GeneralRpc => "GeneralRpc", kb_onchain_transport::HttpMethodClass::GeneralRpc => "GeneralRpc",
@@ -364,7 +379,7 @@ fn method_class_to_string(method_class: kb_onchain_transport::HttpMethodClass) -
mod tests { mod tests {
#[test] #[test]
fn raw_params_override_first_argument_and_config() { fn raw_params_override_first_argument_and_config() {
let params = super::build_demo_http_params( let params = super::build_http_params(
"getBalance", "getBalance",
std::option::Option::Some("ignored"), std::option::Option::Some("ignored"),
std::option::Option::Some(serde_json::json!({"commitment":"confirmed"})), std::option::Option::Some(serde_json::json!({"commitment":"confirmed"})),
@@ -375,7 +390,7 @@ mod tests {
#[test] #[test]
fn required_first_argument_fails_closed() { fn required_first_argument_fails_closed() {
let params = super::build_demo_http_params( let params = super::build_http_params(
"getBalance", "getBalance",
std::option::Option::None, std::option::Option::None,
std::option::Option::None, std::option::Option::None,
@@ -386,7 +401,7 @@ mod tests {
#[test] #[test]
fn method_inventory_is_unique_and_classified() { fn method_inventory_is_unique_and_classified() {
let options = crate::build_http_method_options(); let options = super::build_http_method_options();
let mut names = std::collections::BTreeSet::new(); let mut names = std::collections::BTreeSet::new();
for option in &options { for option in &options {
assert!(names.insert(option.method.as_str())); assert!(names.insert(option.method.as_str()));

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_ata.rs // file: kb-app-demo-desktop/src/demo_spl_ata.rs
// version: 7 // version: 11
//! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads. //! Thin Tauri adapter for ATA execution, derivation and lifecycle journal reads.
@@ -157,7 +157,193 @@ pub(crate) struct DemoSplAtaJournalRow {
pub(crate) payload_json: std::string::String, pub(crate) payload_json: std::string::String,
} }
pub(crate) async fn load_profile_wallet( /// Derives the representative profile wallet ATA without exposing private key material.
pub(crate) async fn demo_spl_ata_derive(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoSplAtaDerivationRequest,
) -> std::result::Result<crate::DemoSplAtaDerivationPayload, std::string::String> {
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet = match load_profile_wallet(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_owner = wallet.public_key();
let token_program = match parse_token_program(&request.token_program) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let associated_token_account =
match derive_ata(wallet_owner.as_str(), request.mint.trim(), token_program.program_id()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::DemoSplAtaDerivationPayload {
payer: wallet_owner.clone(),
wallet_owner,
token_program_id: token_program.program_id().to_string(),
associated_token_account,
});
}
/// Executes one representative Create or CreateIdempotent ATA flow.
pub(crate) async fn demo_execution_spl_ata_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionSplAtaRequest,
) -> std::result::Result<crate::DemoExecutionSplAtaSummaryPayload, std::string::String> {
let acquired = state.demo_execution_solana_core_running().compare_exchange(
false,
true,
std::sync::atomic::Ordering::AcqRel,
std::sync::atomic::Ordering::Acquire,
);
if acquired.is_err() {
return std::result::Result::Err("a Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let profile_wallet = match load_profile_wallet(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if request.wallet_owner.trim() != profile_wallet.public_key() {
return std::result::Result::Err(
"the representative ATA panel requires the profile wallet as wallet owner".to_string(),
);
}
let token_program = match parse_token_program(&request.token_program) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let wallet_owner = kb_lib::MdPubkey(request.wallet_owner.trim().to_string());
let mint = kb_lib::MdPubkey(request.mint.trim().to_string());
let operation = match request.mode.as_str() {
"create" => kb_lib::ExSplAssociatedTokenAccountOperation::Create {
wallet_owner,
mint,
token_program,
},
"create_idempotent" => kb_lib::ExSplAssociatedTokenAccountOperation::CreateIdempotent {
wallet_owner,
mint,
token_program,
},
_ => {
return std::result::Result::Err(
"ATA mode must be create or create_idempotent".to_string(),
);
},
};
let mut pipeline_request =
kb_pipeline_demo_scenarios::DevnetSplAssociatedTokenAccountExecutionRequest::new(
format!("demo-spl-ata-execution-{}", chrono::Utc::now().timestamp_micros()),
operation,
);
pipeline_request.submit = request.submit;
pipeline_request.operator_confirmed = request.operator_confirmed;
pipeline_request.post_validation_max_retries = 20;
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> = std::vec![
std::sync::Arc::new(kb_lib::DcSplAssociatedTokenAccountDecoder),
std::sync::Arc::new(kb_lib::DcSplTokenDecoder),
];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
];
let observer = crate::DemoExecutionSolanaCoreObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_associated_token_account(
&http_pool,
&store,
&profile,
crate::workspace_root_dir().as_path(),
&pipeline_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
{
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));
}
/// Loads a bounded journal of ATA-owned lifecycle facts.
pub(crate) async fn demo_spl_ata_journal(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoSplAtaJournalRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSplAtaJournalRow>, std::string::String> {
if request.limit == 0 || request.limit > 500 {
return std::result::Result::Err("ATA journal limit must be between 1 and 500".to_string());
}
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let filter = match kb_store::MaterializedEventFilter::new(
std::option::Option::Some("materializer.token.accounts".to_string()),
std::option::Option::None,
request.signature_contains.clone(),
500,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let rows = match kb_store::DecodePipelineStore::list_materialized_events(&store, &filter).await
{
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::new();
for row in rows {
if row.source_decoder_name != "spl.associated_token_account"
|| !journal_matches(&row.payload_json, &request)
{
continue;
}
output.push(journal_row(row));
if output.len() >= request.limit as usize {
break;
}
}
return std::result::Result::Ok(output);
}
async fn load_profile_wallet(
profile: &kb_config::ProfileConfig, profile: &kb_config::ProfileConfig,
) -> std::result::Result<kb_wallet::TemporaryWallet, std::string::String> { ) -> std::result::Result<kb_wallet::TemporaryWallet, std::string::String> {
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str()); let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
@@ -180,7 +366,7 @@ pub(crate) async fn load_profile_wallet(
}; };
} }
pub(crate) fn parse_token_program( fn parse_token_program(
value: &str, value: &str,
) -> std::result::Result<kb_lib::ExSplAssociatedTokenProgram, std::string::String> { ) -> std::result::Result<kb_lib::ExSplAssociatedTokenProgram, std::string::String> {
return match value.trim() { return match value.trim() {
@@ -190,7 +376,7 @@ pub(crate) fn parse_token_program(
}; };
} }
pub(crate) fn derive_ata( fn derive_ata(
wallet: &str, wallet: &str,
mint: &str, mint: &str,
token_program: &str, token_program: &str,
@@ -223,7 +409,7 @@ pub(crate) fn derive_ata(
); );
} }
pub(crate) fn demo_spl_ata_summary_payload( fn summary_payload(
summary: kb_pipeline_demo_scenarios::DevnetSplAssociatedTokenAccountExecutionSummary, summary: kb_pipeline_demo_scenarios::DevnetSplAssociatedTokenAccountExecutionSummary,
) -> crate::DemoExecutionSplAtaSummaryPayload { ) -> crate::DemoExecutionSplAtaSummaryPayload {
let (associated_token_account, token_program_id) = match summary.plan.instructions.first() { let (associated_token_account, token_program_id) = match summary.plan.instructions.first() {
@@ -356,10 +542,7 @@ pub(crate) fn demo_spl_ata_summary_payload(
}; };
} }
pub(crate) fn journal_matches( fn journal_matches(payload: &serde_json::Value, request: &crate::DemoSplAtaJournalRequest) -> bool {
payload: &serde_json::Value,
request: &crate::DemoSplAtaJournalRequest,
) -> bool {
for (expected, key) in [ for (expected, key) in [
(&request.operation, "operation"), (&request.operation, "operation"),
(&request.mint, "mint"), (&request.mint, "mint"),
@@ -375,7 +558,7 @@ pub(crate) fn journal_matches(
return true; return true;
} }
pub(crate) fn journal_row(row: kb_store::MaterializedEventQueryRow) -> crate::DemoSplAtaJournalRow { fn journal_row(row: kb_store::MaterializedEventQueryRow) -> crate::DemoSplAtaJournalRow {
let text = |key: &str| { let text = |key: &str| {
return row return row
.payload_json .payload_json
@@ -404,9 +587,9 @@ mod tests {
fn classic_and_token_2022_derivations_are_distinct_and_canonical() { fn classic_and_token_2022_derivations_are_distinct_and_canonical() {
let wallet = kb_program_ids::SYSTEM_PROGRAM_ID; let wallet = kb_program_ids::SYSTEM_PROGRAM_ID;
let mint = "So11111111111111111111111111111111111111112"; let mint = "So11111111111111111111111111111111111111112";
let classic = crate::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_PROGRAM_ID) let classic = super::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_PROGRAM_ID)
.unwrap_or_else(|error| panic!("classic derivation failed: {error}")); .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) let token_2022 = super::derive_ata(wallet, mint, kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID)
.unwrap_or_else(|error| panic!("Token-2022 derivation failed: {error}")); .unwrap_or_else(|error| panic!("Token-2022 derivation failed: {error}"));
assert_eq!(classic, "aqxoAhCwpy3oB1BpNw9hL1HdLYLgPpbPjzxDrrQj3Fs"); assert_eq!(classic, "aqxoAhCwpy3oB1BpNw9hL1HdLYLgPpbPjzxDrrQj3Fs");
assert_eq!(token_2022, "2sZUUBGq1i6aE47ZoxCaCW89jmYm2EXLPPmNMgMDXHMS"); assert_eq!(token_2022, "2sZUUBGq1i6aE47ZoxCaCW89jmYm2EXLPPmNMgMDXHMS");
@@ -422,7 +605,7 @@ mod tests {
operation: std::option::Option::Some("create_idempotent".to_string()), operation: std::option::Option::Some("create_idempotent".to_string()),
limit: 100, limit: 100,
}; };
assert!(crate::journal_matches( assert!(super::journal_matches(
&serde_json::json!({ &serde_json::json!({
"operation": "create_idempotent", "operation": "create_idempotent",
"mint": "mint111", "mint": "mint111",
@@ -430,7 +613,7 @@ mod tests {
}), }),
&request, &request,
)); ));
assert!(!crate::journal_matches( assert!(!super::journal_matches(
&serde_json::json!({ &serde_json::json!({
"operation": "create", "operation": "create",
"mint": "mint111", "mint": "mint111",

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_token.rs // file: kb-app-demo-desktop/src/demo_spl_token.rs
// version: 4 // version: 8
//! Thin Tauri adapter for classic SPL Token execution and materialized journals. //! Thin Tauri adapter for classic SPL Token execution and materialized journals.
@@ -153,7 +153,152 @@ pub(crate) struct DemoSplTokenJournalRow {
pub(crate) updated_at: std::string::String, pub(crate) updated_at: std::string::String,
} }
pub(crate) fn demo_spl_token_summary_payload( /// Executes one checked SPL Token simulation or explicitly authorized Devnet submission.
pub(crate) async fn demo_execution_spl_token_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionSplTokenRequest,
) -> std::result::Result<crate::DemoExecutionSplTokenSummaryPayload, std::string::String> {
let acquire_result = state.demo_execution_solana_core_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 Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let amount_raw = request.amount_raw.trim().to_string();
let operation = kb_lib::ExSplClassicTokenOperation::Instruction {
value: kb_lib::ExSplClassicTokenSingleOperation::TransferChecked {
source: kb_lib::MdPubkey(request.source.trim().to_string()),
mint: kb_lib::MdPubkey(request.mint.trim().to_string()),
destination: kb_lib::MdPubkey(request.destination.trim().to_string()),
authority: kb_lib::ExSplClassicTokenAuthority {
authority: kb_lib::MdPubkey(request.authority.trim().to_string()),
multisig_signers: std::vec::Vec::new(),
},
amount: kb_lib::ExSplClassicTokenAmount(amount_raw.clone()),
decimals: request.decimals,
},
};
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSplTokenExecutionRequest::new(
format!("demo-spl-token-execution-{}", chrono::Utc::now().timestamp_micros()),
operation,
);
pipeline_request.submit = request.submit;
pipeline_request.operator_confirmed = request.operator_confirmed;
pipeline_request.post_validation_max_retries = 20;
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
std::vec![std::sync::Arc::new(kb_lib::DcSplTokenDecoder)];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
];
let observer = crate::DemoExecutionSolanaCoreObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_token(
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&pipeline_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
{
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, amount_raw, request.decimals));
}
/// Loads a bounded, typed journal of committed classic SPL Token projections.
pub(crate) async fn demo_spl_token_journal(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoSplTokenJournalRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSplTokenJournalRow>, std::string::String> {
let validated = match validate_journal_request(request) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let profile =
match crate::select_devnet_profile(state.app_config(), validated.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let needs_payload_filter =
validated.mint.is_some() || validated.account.is_some() || validated.operation.is_some();
let query_limit = if needs_payload_filter {
kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS
} else {
validated.limit
};
let filter = match kb_store::MaterializedEventFilter::new(
std::option::Option::None,
validated.family.clone(),
validated.signature_contains.clone(),
query_limit,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let rows = match kb_store::DecodePipelineStore::list_materialized_events(&store, &filter).await
{
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::new();
for row in rows {
if row.source_decoder_name != "spl.token" || !payload_matches(&row.payload_json, &validated)
{
continue;
}
output.push(journal_row(row));
if output.len() >= validated.limit as usize {
break;
}
}
return std::result::Result::Ok(output);
}
fn summary_payload(
summary: kb_pipeline_demo_scenarios::DevnetSplTokenExecutionSummary, summary: kb_pipeline_demo_scenarios::DevnetSplTokenExecutionSummary,
amount_raw: std::string::String, amount_raw: std::string::String,
decimals: u8, decimals: u8,
@@ -267,7 +412,7 @@ fn replay_diagnostics(summary: &kb_pipeline::DecodeReplaySummary) -> serde_json:
}); });
} }
pub(crate) fn validate_journal_request( fn validate_journal_request(
request: crate::DemoSplTokenJournalRequest, request: crate::DemoSplTokenJournalRequest,
) -> std::result::Result<crate::DemoSplTokenJournalRequest, std::string::String> { ) -> std::result::Result<crate::DemoSplTokenJournalRequest, std::string::String> {
if request.limit == 0 || request.limit > kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS { if request.limit == 0 || request.limit > kb_store::MAX_MATERIALIZED_EVENT_QUERY_ROWS {
@@ -331,7 +476,7 @@ fn bounded_optional(
return std::result::Result::Ok(trimmed); return std::result::Result::Ok(trimmed);
} }
pub(crate) fn payload_matches( fn payload_matches(
payload: &serde_json::Value, payload: &serde_json::Value,
request: &crate::DemoSplTokenJournalRequest, request: &crate::DemoSplTokenJournalRequest,
) -> bool { ) -> bool {
@@ -354,9 +499,7 @@ pub(crate) fn payload_matches(
return true; return true;
} }
pub(crate) fn demo_spl_token_journal_row( fn journal_row(row: kb_store::MaterializedEventQueryRow) -> DemoSplTokenJournalRow {
row: kb_store::MaterializedEventQueryRow,
) -> DemoSplTokenJournalRow {
let operation = row let operation = row
.payload_json .payload_json
.get("operation") .get("operation")
@@ -457,12 +600,12 @@ mod tests {
{"role": "mint", "accountKey": "mint111"} {"role": "mint", "accountKey": "mint111"}
] ]
}); });
assert!(crate::payload_matches(&payload, &request)); assert!(super::payload_matches(&payload, &request));
assert_eq!( assert_eq!(
payload.get("amountRaw").and_then(serde_json::Value::as_str), payload.get("amountRaw").and_then(serde_json::Value::as_str),
std::option::Option::Some("18446744073709551615") std::option::Option::Some("18446744073709551615")
); );
let row = crate::demo_spl_token_journal_row(kb_store::MaterializedEventQueryRow { let row = super::journal_row(kb_store::MaterializedEventQueryRow {
processor_name: "materializer.token.accounts".to_string(), processor_name: "materializer.token.accounts".to_string(),
processor_version: "0.4.4".to_string(), processor_version: "0.4.4".to_string(),
input_key: "input".to_string(), input_key: "input".to_string(),
@@ -492,6 +635,6 @@ mod tests {
operation: std::option::Option::None, operation: std::option::Option::None,
limit: 501, limit: 501,
}; };
assert!(crate::validate_journal_request(request).is_err()); assert!(super::validate_journal_request(request).is_err());
} }
} }

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_spl_token_2022.rs // file: kb-app-demo-desktop/src/demo_spl_token_2022.rs
// version: 7 // version: 11
//! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios. //! Thin Tauri adapter for independent public Token-2022 Devnet validation scenarios.
@@ -131,7 +131,195 @@ pub(crate) struct DemoExecutionSplToken2022SummaryPayload {
pub(crate) diagnostics_json: std::string::String, pub(crate) diagnostics_json: std::string::String,
} }
pub(crate) fn operation_from_request( /// Executes one public Token-2022 simulation or explicitly authorized Devnet submission.
pub(crate) async fn demo_execution_spl_token_2022_execute(
app_handle: tauri::AppHandle,
state: tauri::State<'_, crate::AppState>,
request: crate::DemoExecutionSplToken2022Request,
) -> std::result::Result<crate::DemoExecutionSplToken2022SummaryPayload, std::string::String> {
let acquire_result = state.demo_execution_solana_core_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 Devnet execution is already running".to_string());
}
let _run_guard = crate::DemoExecutionSolanaCoreRunGuard {
running: state.demo_execution_solana_core_running(),
};
state
.demo_execution_solana_core_cancel_requested()
.store(false, std::sync::atomic::Ordering::Release);
let profile =
match crate::select_devnet_profile(state.app_config(), request.profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let http_pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let store = match crate::connect_postgres_store(&profile).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
if let std::result::Result::Err(error) = store.initialize_store_schema().await {
return std::result::Result::Err(error.to_string());
}
let operation = match operation_from_request(&request) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let operation_code = operation.operation_code().to_string();
let mut pipeline_request = kb_pipeline_demo_scenarios::DevnetSplToken2022ExecutionRequest::new(
format!("demo-spl-token-2022-execution-{}", chrono::Utc::now().timestamp_micros()),
operation,
);
pipeline_request.submit = request.submit;
pipeline_request.operator_confirmed = request.operator_confirmed;
pipeline_request.post_validation_max_retries = 20;
pipeline_request.force_post_validation_replay = request.force_post_validation_replay;
let decoders: std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstructionDecoder>> =
std::vec![std::sync::Arc::new(kb_lib::DcSplToken2022Decoder)];
let materializers: std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEventMaterializer>> = std::vec![
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
std::sync::Arc::new(kb_lib::MtAdminMaterializer),
std::sync::Arc::new(kb_lib::MtRiskMaterializer),
std::sync::Arc::new(kb_lib::MtFeesMaterializer),
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer),
];
let observer = crate::DemoExecutionSolanaCoreObserver {
app_handle,
cancel_requested: state.demo_execution_solana_core_cancel_requested(),
};
let workspace_root = crate::workspace_root_dir();
let summary = match kb_pipeline_demo_scenarios::execute_devnet_spl_token_2022(
&http_pool,
&store,
&profile,
workspace_root.as_path(),
&pipeline_request,
decoders.as_slice(),
materializers.as_slice(),
&observer,
)
.await
{
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(request.scenario_id, operation_code, summary));
}
/// Loads public Token-2022 fixture values without reading private key bytes.
pub(crate) fn demo_spl_token_2022_fixture(
state: tauri::State<'_, crate::AppState>,
profile_name: std::string::String,
) -> std::result::Result<crate::DemoSplToken2022FixturePayload, std::string::String> {
let profile = match crate::select_devnet_profile(state.app_config(), profile_name.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let configured = std::path::PathBuf::from(profile.wallet.wallet_dir.as_str());
let wallet_dir = if configured.is_absolute() {
configured
} else {
crate::workspace_root_dir().join(configured)
};
let fixture_path = wallet_dir.join("spl_token_2022_validation").join("fixture.env");
let contents = match std::fs::read_to_string(&fixture_path) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"unable to read Token-2022 fixture {}: {error}",
fixture_path.display()
));
},
};
let values = parse_fixture(contents.as_str());
let required = |name: &str| -> std::result::Result<std::string::String, std::string::String> {
return match values.get(name) {
std::option::Option::Some(value) if !value.trim().is_empty() => {
std::result::Result::Ok(value.clone())
},
_ => std::result::Result::Err(format!("Token-2022 fixture is missing {name}")),
};
};
let program_id = match required("TOKEN_2022_PROGRAM") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mint = match required("TOKEN_2022_MINT") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let source = match required("TOKEN_2022_SOURCE") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let destination = match required("TOKEN_2022_DESTINATION") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let close_account = match required("TOKEN_2022_CLOSE_ACCOUNT") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let delegate = match required("TOKEN_2022_DELEGATE") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let authority = match required("TOKEN_2022_AUTHORITY") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let freeze_authority = match required("TOKEN_2022_FREEZE_AUTHORITY") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let decimals_text = match required("TOKEN_2022_DECIMALS") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let decimals = match decimals_text.parse::<u8>() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!("invalid TOKEN_2022_DECIMALS value: {error}"));
},
};
return std::result::Result::Ok(crate::DemoSplToken2022FixturePayload {
fixture_path: fixture_path.display().to_string(),
program_id,
mint,
source,
destination,
close_account,
delegate,
authority,
freeze_authority,
decimals,
mint_amount_raw: match required("TOKEN_2022_MINT_AMOUNT_RAW") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
},
transfer_amount_raw: match required("TOKEN_2022_TRANSFER_AMOUNT_RAW") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
},
approve_amount_raw: match required("TOKEN_2022_APPROVE_AMOUNT_RAW") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
},
burn_amount_raw: match required("TOKEN_2022_BURN_AMOUNT_RAW") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
},
});
}
fn operation_from_request(
request: &crate::DemoExecutionSplToken2022Request, request: &crate::DemoExecutionSplToken2022Request,
) -> std::result::Result<kb_lib::ExSplToken2022Operation, std::string::String> { ) -> std::result::Result<kb_lib::ExSplToken2022Operation, std::string::String> {
let authority = kb_lib::ExSplTokenAuthority { let authority = kb_lib::ExSplTokenAuthority {
@@ -203,7 +391,7 @@ pub(crate) fn operation_from_request(
}); });
} }
pub(crate) fn demo_spl_token_2022_summary_payload( fn summary_payload(
scenario_id: std::string::String, scenario_id: std::string::String,
operation: std::string::String, operation: std::string::String,
summary: kb_pipeline_demo_scenarios::DevnetSplToken2022ExecutionSummary, summary: kb_pipeline_demo_scenarios::DevnetSplToken2022ExecutionSummary,
@@ -269,7 +457,7 @@ pub(crate) fn demo_spl_token_2022_summary_payload(
}; };
} }
pub(crate) fn parse_fixture( fn parse_fixture(
contents: &str, contents: &str,
) -> std::collections::BTreeMap<std::string::String, std::string::String> { ) -> std::collections::BTreeMap<std::string::String, std::string::String> {
let mut values = std::collections::BTreeMap::new(); let mut values = std::collections::BTreeMap::new();
@@ -403,7 +591,7 @@ mod tests {
"token_2022_thaw_account", "token_2022_thaw_account",
"token_2022_close_destination", "token_2022_close_destination",
] { ] {
assert!(crate::operation_from_request(&request(scenario)).is_ok()); assert!(super::operation_from_request(&request(scenario)).is_ok());
} }
} }
} }

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_sql_common.rs // file: kb-app-demo-desktop/src/demo_sql_common.rs
// version: 11 // version: 12
//! Shared SQL demo helpers and serializable payloads. //! Shared SQL demo helpers and serializable payloads.
@@ -211,6 +211,11 @@ pub(crate) async fn initialize_postgres_schema_for_startup(
emit_sql_startup_table_report(splash_window, before_tables.as_slice(), after_tables.as_slice()); emit_sql_startup_table_report(splash_window, before_tables.as_slice(), after_tables.as_slice());
} }
/// Formats debug enums for UI display.
pub(crate) fn debug_status<T: std::fmt::Debug>(value: T) -> std::string::String {
return format!("{value:?}");
}
fn emit_sql_startup_table_report( fn emit_sql_startup_table_report(
splash_window: &tauri::WebviewWindow, splash_window: &tauri::WebviewWindow,
before_tables: &[kb_store::PostgresTableDiagnostics], before_tables: &[kb_store::PostgresTableDiagnostics],
@@ -305,8 +310,3 @@ fn emit_sql_startup_splash(
std::option::Option::None, 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

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_sql_diag.rs // file: kb-app-demo-desktop/src/demo_sql_diag.rs
// version: 3 // version: 5
//! SQL diagnostic demo commands. //! SQL diagnostic demo commands.
@@ -36,3 +36,38 @@ pub(crate) struct DemoSqlDiagPayload {
/// Known raw/core table diagnostics. /// Known raw/core table diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>, pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
} }
/// Loads read-only SQL diagnostics from the active profile.
pub(crate) async fn load_demo_sql_diag(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlDiagPayload, std::string::String> {
let profile = state.active_profile().clone();
let store_result = crate::connect_postgres_store(&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 diagnostics_result = store.backend_diagnostics().await;
let diagnostics = match diagnostics_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let tables_result = store.known_table_diagnostics().await;
let tables = match tables_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(crate::DemoSqlDiagPayload {
config_path: state.config_path().to_string(),
active_profile_name: profile.name.clone(),
backend: profile.database.backend.clone(),
masked_dsn: store.options().masked_dsn(),
current_schema: diagnostics.descriptor.current_schema,
health_status: crate::debug_status(diagnostics.health.status),
health_message: diagnostics.health.message,
migration_status: crate::debug_status(diagnostics.migrations.status),
migration_message: diagnostics.migrations.message,
server_version: diagnostics.server_version,
tables: crate::table_snapshots_from_pg(tables.as_slice()),
});
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_sql_pg_core.rs // file: kb-app-demo-desktop/src/demo_sql_pg_core.rs
// version: 4 // version: 6
//! PostgreSQL core store demo commands. //! PostgreSQL core store demo commands.
@@ -20,3 +20,25 @@ pub(crate) struct DemoSqlPgCorePayload {
/// Core table diagnostics. /// Core table diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>, pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
} }
/// Loads read-only core table diagnostics from PostgreSQL.
pub(crate) async fn load_demo_sql_pg_core(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlPgCorePayload, std::string::String> {
let profile = state.active_profile().clone();
let store_result = crate::connect_postgres_store(&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 tables_result = store.core_table_diagnostics().await;
let tables = match tables_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(crate::DemoSqlPgCorePayload {
active_profile_name: profile.name.clone(),
masked_dsn: store.options().masked_dsn(),
tables: crate::table_snapshots_from_pg(tables.as_slice()),
});
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_sql_pg_raw.rs // file: kb-app-demo-desktop/src/demo_sql_pg_raw.rs
// version: 5 // version: 7
//! PostgreSQL canonical acquisition store demo commands. //! PostgreSQL canonical acquisition store demo commands.
@@ -20,3 +20,25 @@ pub(crate) struct DemoSqlPgRawPayload {
/// Raw table diagnostics. /// Raw table diagnostics.
pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>, pub(crate) tables: std::vec::Vec<crate::DemoSqlTableSnapshot>,
} }
/// Loads read-only raw table diagnostics from PostgreSQL.
pub(crate) async fn load_demo_sql_pg_raw(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlPgRawPayload, std::string::String> {
let profile = state.active_profile().clone();
let store_result = crate::connect_postgres_store(&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 tables_result = store.raw_table_diagnostics().await;
let tables = match tables_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(crate::DemoSqlPgRawPayload {
active_profile_name: profile.name.clone(),
masked_dsn: store.options().masked_dsn(),
tables: crate::table_snapshots_from_pg(tables.as_slice()),
});
}

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_sql_replay_candidates.rs // file: kb-app-demo-desktop/src/demo_sql_replay_candidates.rs
// version: 8 // version: 12
//! Read-only SQL replay candidate browser commands. //! Read-only SQL replay candidate browser commands.
@@ -202,7 +202,187 @@ pub(crate) struct DemoSqlReplayEntityRow {
pub(crate) max_slot: i64, pub(crate) max_slot: i64,
} }
pub(crate) fn transaction_row_from_pg( /// Writes a bounded replay-candidate CSV file into `./data/exports_csv/`.
pub(crate) async fn export_demo_sql_replay_csv(
file_name: std::string::String,
content: std::string::String,
) -> std::result::Result<std::string::String, std::string::String> {
if content.is_empty() {
return std::result::Result::Err("CSV export content must not be empty".to_owned());
}
if content.len() > 16 * 1024 * 1024 {
return std::result::Result::Err("CSV export content exceeds 16 MiB".to_owned());
}
let normalized_name_result = validated_csv_file_name(&file_name);
let normalized_name = match normalized_name_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let export_dir = crate::workspace_root_dir().join("data").join("exports_csv");
let create_result = tokio::fs::create_dir_all(&export_dir).await;
if let std::result::Result::Err(error) = create_result {
return std::result::Result::Err(format!("cannot create CSV export directory: {error}"));
}
let export_path_result = available_csv_export_path(&export_dir, normalized_name).await;
let export_path = match export_path_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut bytes = std::vec::Vec::with_capacity(content.len() + 3);
bytes.extend_from_slice(&[0xEF, 0xBB, 0xBF]);
bytes.extend_from_slice(content.as_bytes());
let write_result = tokio::fs::write(&export_path, bytes).await;
if let std::result::Result::Err(error) = write_result {
return std::result::Result::Err(format!("cannot write CSV export: {error}"));
}
let display_path = export_path.to_string_lossy().into_owned();
tracing::info!(target: crate::TRACING_TARGET, path = %display_path, "CSV replay candidate export written");
return std::result::Result::Ok(display_path);
}
/// Loads static browser options from the active PostgreSQL profile.
pub(crate) async fn demo_sql_replay_options(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoSqlReplayOptionsPayload, std::string::String> {
let profile = state.active_profile().clone();
let options_result = crate::postgres_store_options_from_profile(&profile, false);
let options = match options_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let mut known_programs =
std::vec::Vec::with_capacity(kb_program_ids::registered_program_ids().len());
for entry in kb_program_ids::registered_program_ids() {
known_programs.push(crate::DemoSqlReplayKnownProgramOption {
code: entry.code().to_owned(),
program_id: entry.program_id().to_owned(),
});
}
return std::result::Result::Ok(crate::DemoSqlReplayOptionsPayload {
active_profile_name: profile.name,
masked_dsn: options.masked_dsn(),
maximum_limit: kb_store::MAX_REPLAY_CANDIDATE_ROWS,
known_programs,
});
}
/// Loads bounded transaction replay candidates from PostgreSQL.
pub(crate) async fn load_demo_sql_replay_transactions(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoSqlReplayTransactionRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayTransactionRow>, std::string::String> {
let program_scope_result = program_scope_from_code(request.program_scope.as_str());
let program_scope = match program_scope_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let entity_kind_result = optional_entity_kind_from_code(request.entity_kind.as_deref());
let entity_kind = match entity_kind_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let filter_result = kb_store::PostgresReplayTransactionFilter::new(
request.signature_contains,
request.min_slot,
request.max_slot,
request.raw_processing_state,
request.ledger_status,
request.program_id,
program_scope,
entity_kind,
request.entity_value,
request.limit,
request.newest_first,
);
let filter = match filter_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
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 = store.replay_transaction_candidates(&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()),
};
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay transaction candidates");
let mut output = std::vec::Vec::with_capacity(rows.len());
for row in rows {
output.push(transaction_row_from_pg(row));
}
return std::result::Result::Ok(output);
}
/// Loads bounded program summaries from PostgreSQL.
pub(crate) async fn load_demo_sql_replay_programs(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoSqlReplayProgramRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayProgramRow>, std::string::String> {
let filter_result =
kb_store::PostgresReplayProgramFilter::new(request.program_id_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()),
};
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 = store.replay_program_summaries(&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()),
};
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay program summaries");
let mut output = std::vec::Vec::with_capacity(rows.len());
for row in rows {
output.push(program_row_from_pg(row));
}
return std::result::Result::Ok(output);
}
/// Loads bounded mint, owner or account-key summaries from PostgreSQL core tables.
pub(crate) async fn load_demo_sql_replay_entities(
state: tauri::State<'_, crate::AppState>,
request: crate::DemoSqlReplayEntityRequest,
) -> std::result::Result<std::vec::Vec<crate::DemoSqlReplayEntityRow>, std::string::String> {
let entity_kind_result = entity_kind_from_code(request.entity_kind.as_str());
let entity_kind = match entity_kind_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let filter_result = kb_store::PostgresReplayEntityFilter::new(
entity_kind,
request.entity_value_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()),
};
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 = store.replay_entity_summaries(&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()),
};
tracing::debug!(target: crate::TRACING_TARGET, rows = rows.len(), "loaded replay entity summaries");
let mut output = std::vec::Vec::with_capacity(rows.len());
for row in rows {
output.push(entity_row_from_pg(row));
}
return std::result::Result::Ok(output);
}
fn transaction_row_from_pg(
row: kb_store::PostgresReplayTransactionCandidate, row: kb_store::PostgresReplayTransactionCandidate,
) -> crate::DemoSqlReplayTransactionRow { ) -> crate::DemoSqlReplayTransactionRow {
return crate::DemoSqlReplayTransactionRow { return crate::DemoSqlReplayTransactionRow {
@@ -223,7 +403,7 @@ pub(crate) fn transaction_row_from_pg(
}; };
} }
pub(crate) fn program_row_from_pg( fn program_row_from_pg(
row: kb_store::PostgresReplayProgramSummary, row: kb_store::PostgresReplayProgramSummary,
) -> crate::DemoSqlReplayProgramRow { ) -> crate::DemoSqlReplayProgramRow {
let program_code = kb_program_ids::find_registered_program_id(&row.program_id) let program_code = kb_program_ids::find_registered_program_id(&row.program_id)
@@ -240,9 +420,7 @@ pub(crate) fn program_row_from_pg(
}; };
} }
pub(crate) fn entity_row_from_pg( fn entity_row_from_pg(row: kb_store::PostgresReplayEntitySummary) -> crate::DemoSqlReplayEntityRow {
row: kb_store::PostgresReplayEntitySummary,
) -> crate::DemoSqlReplayEntityRow {
return crate::DemoSqlReplayEntityRow { return crate::DemoSqlReplayEntityRow {
entity_kind: row.entity_kind, entity_kind: row.entity_kind,
entity_value: row.entity_value, entity_value: row.entity_value,
@@ -253,7 +431,7 @@ pub(crate) fn entity_row_from_pg(
}; };
} }
pub(crate) fn validated_csv_file_name( fn validated_csv_file_name(
file_name: &str, file_name: &str,
) -> std::result::Result<&'static str, std::string::String> { ) -> std::result::Result<&'static str, std::string::String> {
return match file_name { return match file_name {
@@ -266,7 +444,7 @@ pub(crate) fn validated_csv_file_name(
}; };
} }
pub(crate) async fn available_csv_export_path( async fn available_csv_export_path(
export_dir: &std::path::Path, export_dir: &std::path::Path,
file_name: &str, file_name: &str,
) -> std::result::Result<std::path::PathBuf, std::string::String> { ) -> std::result::Result<std::path::PathBuf, std::string::String> {
@@ -300,7 +478,7 @@ pub(crate) async fn available_csv_export_path(
return std::result::Result::Err("cannot allocate a unique CSV export path".to_owned()); return std::result::Result::Err("cannot allocate a unique CSV export path".to_owned());
} }
pub(crate) fn program_scope_from_code( fn program_scope_from_code(
code: &str, code: &str,
) -> std::result::Result<kb_store::PostgresReplayProgramScope, std::string::String> { ) -> std::result::Result<kb_store::PostgresReplayProgramScope, std::string::String> {
return match code { return match code {
@@ -312,7 +490,7 @@ pub(crate) fn program_scope_from_code(
}; };
} }
pub(crate) fn optional_entity_kind_from_code( fn optional_entity_kind_from_code(
code: std::option::Option<&str>, code: std::option::Option<&str>,
) -> std::result::Result<std::option::Option<kb_store::PostgresReplayEntityKind>, std::string::String> ) -> std::result::Result<std::option::Option<kb_store::PostgresReplayEntityKind>, std::string::String>
{ {
@@ -330,7 +508,7 @@ pub(crate) fn optional_entity_kind_from_code(
}; };
} }
pub(crate) fn entity_kind_from_code( fn entity_kind_from_code(
code: &str, code: &str,
) -> std::result::Result<kb_store::PostgresReplayEntityKind, std::string::String> { ) -> std::result::Result<kb_store::PostgresReplayEntityKind, std::string::String> {
return match code { return match code {
@@ -345,27 +523,27 @@ pub(crate) fn entity_kind_from_code(
mod tests { mod tests {
#[test] #[test]
fn program_scope_parser_accepts_logs() { fn program_scope_parser_accepts_logs() {
let result = crate::program_scope_from_code("logs"); let result = super::program_scope_from_code("logs");
assert_eq!(result, std::result::Result::Ok(kb_store::PostgresReplayProgramScope::Logs)); assert_eq!(result, std::result::Result::Ok(kb_store::PostgresReplayProgramScope::Logs));
} }
#[test] #[test]
fn entity_kind_parser_rejects_unknown_code() { fn entity_kind_parser_rejects_unknown_code() {
let result = crate::entity_kind_from_code("unknown"); let result = super::entity_kind_from_code("unknown");
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test]
fn csv_file_name_rejects_unknown_name() { fn csv_file_name_rejects_unknown_name() {
let result = crate::validated_csv_file_name("arbitrary.csv"); let result = super::validated_csv_file_name("arbitrary.csv");
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test]
fn csv_file_name_accepts_split_entity_exports() { fn csv_file_name_accepts_split_entity_exports() {
let mint_result = crate::validated_csv_file_name("replay_mints.csv"); let mint_result = super::validated_csv_file_name("replay_mints.csv");
let owner_result = crate::validated_csv_file_name("replay_owners.csv"); let owner_result = super::validated_csv_file_name("replay_owners.csv");
let account_result = crate::validated_csv_file_name("replay_account_keys.csv"); let account_result = super::validated_csv_file_name("replay_account_keys.csv");
assert!(mint_result.is_ok()); assert!(mint_result.is_ok());
assert!(owner_result.is_ok()); assert!(owner_result.is_ok());
assert!(account_result.is_ok()); assert!(account_result.is_ok());
@@ -382,7 +560,7 @@ mod tests {
min_slot: 1, min_slot: 1,
max_slot: 1, max_slot: 1,
}; };
let converted = crate::program_row_from_pg(row); let converted = super::program_row_from_pg(row);
assert_eq!(converted.program_code.as_deref(), std::option::Option::Some("system")); assert_eq!(converted.program_code.as_deref(), std::option::Option::Some("system"));
} }
} }

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_ws.rs // file: kb-app-demo-desktop/src/demo_ws.rs
// version: 17 // version: 19
//! Standard Solana WebSocket demo commands backed by `kb_onchain_transport::WsSession`. //! Standard Solana WebSocket demo commands backed by `kb_onchain_transport::WsSession`.
@@ -179,72 +179,8 @@ pub(crate) struct DemoWsMessagePayload {
pub(crate) payload_json: std::string::String, pub(crate) payload_json: std::string::String,
} }
const DEMO_WS_UI_RATE_WINDOW_MS: u64 = 1000; /// Connects if needed, then subscribes through `kb_onchain_transport::WsSession`.
const DEMO_WS_UI_MAX_MESSAGES_PER_WINDOW: u32 = 12; pub(crate) async fn demo_ws_connect(
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, app_handle: tauri::AppHandle,
state: &crate::AppState, state: &crate::AppState,
request: crate::DemoWsRequest, request: crate::DemoWsRequest,
@@ -274,7 +210,7 @@ pub(crate) async fn demo_ws_connect_inner(
std::result::Result::Ok(client) => client, std::result::Result::Ok(client) => client,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), 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 { let session = match ensure_session(&app_handle, state, selected_client.clone()).await {
std::result::Result::Ok(session) => session, std::result::Result::Ok(session) => session,
std::result::Result::Err(error) => return std::result::Result::Err(error), std::result::Result::Err(error) => return std::result::Result::Err(error),
}; };
@@ -290,7 +226,7 @@ pub(crate) async fn demo_ws_connect_inner(
std::result::Result::Ok(value) => value, std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
emit_demo_ws_status(&app_handle, session.snapshot().await); emit_status(&app_handle, session.snapshot().await);
return std::result::Result::Ok(crate::DemoWsExecutionPayload { return std::result::Result::Ok(crate::DemoWsExecutionPayload {
endpoint_name: selected_client.endpoint_name().to_string(), endpoint_name: selected_client.endpoint_name().to_string(),
provider: selected_client.provider().to_string(), provider: selected_client.provider().to_string(),
@@ -304,7 +240,148 @@ pub(crate) async fn demo_ws_connect_inner(
}); });
} }
async fn ensure_demo_ws_session( /// Returns the current WebSocket demo session status.
pub(crate) async fn demo_ws_status(
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()),
};
}
/// Lists WebSocket endpoints available through the configured pool.
pub(crate) fn demo_ws_list_pool_clients(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<
std::vec::Vec<kb_onchain_transport::WsPoolClientSnapshot>,
std::string::String,
> {
let pool = match state.demo_ws_pool() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
return std::result::Result::Ok(pool.snapshot());
}
/// Lists selectable WebSocket roles and methods for the demo UI.
pub(crate) fn demo_ws_options(
state: tauri::State<'_, crate::AppState>,
) -> std::result::Result<crate::DemoWsOptionsPayload, std::string::String> {
return std::result::Result::Ok(crate::DemoWsOptionsPayload {
roles: build_ws_role_options(match state.demo_ws_pool() {
std::result::Result::Ok(pool) => pool.snapshot(),
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}),
methods: build_ws_method_options(),
});
}
/// Unsubscribes one subscription while keeping the WebSocket connection open.
pub(crate) async fn demo_ws_unsubscribe(
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_status(&app_handle, snapshot.clone());
return std::result::Result::Ok(status_from_snapshot(snapshot));
}
pub(crate) async fn demo_ws_disconnect(
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(state).await;
if let std::result::Result::Err(error) = disconnect_result {
tracing::debug!(target: crate::TRACING_TARGET, "demo ws lifecycle disconnect failed: {error}");
}
}
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_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;
}
}
async fn ensure_session(
app_handle: &tauri::AppHandle, app_handle: &tauri::AppHandle,
state: &crate::AppState, state: &crate::AppState,
selected_client: kb_onchain_transport::WsClient, selected_client: kb_onchain_transport::WsClient,
@@ -351,7 +428,7 @@ async fn ensure_demo_ws_session(
std::result::Result::Ok(session) => std::sync::Arc::new(session), std::result::Result::Ok(session) => std::sync::Arc::new(session),
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
}; };
spawn_demo_ws_event_bridge(app_handle.clone(), session.clone()); spawn_event_bridge(app_handle.clone(), session.clone());
{ {
let mut guard = state.demo_ws_session().lock().await; let mut guard = state.demo_ws_session().lock().await;
*guard = std::option::Option::Some(session.clone()); *guard = std::option::Option::Some(session.clone());
@@ -359,54 +436,7 @@ async fn ensure_demo_ws_session(
return std::result::Result::Ok(session); return std::result::Result::Ok(session);
} }
pub(crate) async fn demo_ws_unsubscribe_inner( fn spawn_event_bridge(
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, app_handle: tauri::AppHandle,
session: std::sync::Arc<kb_onchain_transport::WsSession>, session: std::sync::Arc<kb_onchain_transport::WsSession>,
) { ) {
@@ -420,7 +450,7 @@ fn spawn_demo_ws_event_bridge(
std::result::Result::Err(tokio::sync::broadcast::error::RecvError::Lagged( std::result::Result::Err(tokio::sync::broadcast::error::RecvError::Lagged(
count, count,
)) => { )) => {
emit_demo_ws_message( emit_message(
&app_handle, &app_handle,
"lagged", "lagged",
format!("{count} événement(s) WebSocket interne(s) perdu(s)"), format!("{count} événement(s) WebSocket interne(s) perdu(s)"),
@@ -447,43 +477,39 @@ fn spawn_demo_ws_event_bridge(
std::result::Result::Ok(payload) => payload, std::result::Result::Ok(payload) => payload,
std::result::Result::Err(_) => compact_payload, std::result::Result::Err(_) => compact_payload,
}; };
emit_demo_ws_message( emit_message(&app_handle, "notification", truncate_payload(ui_payload));
&app_handle,
"notification",
truncate_payload(ui_payload),
);
} }
}, },
kb_onchain_transport::WsSessionEvent::Diagnostic { code, message } => { kb_onchain_transport::WsSessionEvent::Diagnostic { code, message } => {
emit_demo_ws_message(&app_handle, &code, truncate_payload(message)); emit_message(&app_handle, &code, truncate_payload(message));
}, },
kb_onchain_transport::WsSessionEvent::Reconnecting { kb_onchain_transport::WsSessionEvent::Reconnecting {
attempt, attempt,
maximum_attempts, maximum_attempts,
} => { } => {
emit_demo_ws_message( emit_message(
&app_handle, &app_handle,
"reconnecting", "reconnecting",
format!("tentative {attempt}/{maximum_attempts}"), format!("tentative {attempt}/{maximum_attempts}"),
); );
emit_demo_ws_status(&app_handle, session.snapshot().await); emit_status(&app_handle, session.snapshot().await);
}, },
kb_onchain_transport::WsSessionEvent::Reconnected { reconnect_count } => { kb_onchain_transport::WsSessionEvent::Reconnected { reconnect_count } => {
emit_demo_ws_message( emit_message(
&app_handle, &app_handle,
"reconnected", "reconnected",
format!("reconnexion numéro {reconnect_count}"), format!("reconnexion numéro {reconnect_count}"),
); );
emit_demo_ws_status(&app_handle, session.snapshot().await); emit_status(&app_handle, session.snapshot().await);
}, },
kb_onchain_transport::WsSessionEvent::Connected kb_onchain_transport::WsSessionEvent::Connected
| kb_onchain_transport::WsSessionEvent::SubscriptionAdded(_) | kb_onchain_transport::WsSessionEvent::SubscriptionAdded(_)
| kb_onchain_transport::WsSessionEvent::SubscriptionRemapped { .. } | kb_onchain_transport::WsSessionEvent::SubscriptionRemapped { .. }
| kb_onchain_transport::WsSessionEvent::SubscriptionRemoved(_) => { | kb_onchain_transport::WsSessionEvent::SubscriptionRemoved(_) => {
emit_demo_ws_status(&app_handle, session.snapshot().await); emit_status(&app_handle, session.snapshot().await);
}, },
kb_onchain_transport::WsSessionEvent::Disconnected => { kb_onchain_transport::WsSessionEvent::Disconnected => {
emit_demo_ws_status(&app_handle, session.snapshot().await); emit_status(&app_handle, session.snapshot().await);
break; break;
}, },
} }
@@ -491,10 +517,7 @@ fn spawn_demo_ws_event_bridge(
}); });
} }
fn emit_demo_ws_status( fn emit_status(app_handle: &tauri::AppHandle, snapshot: kb_onchain_transport::WsSessionSnapshot) {
app_handle: &tauri::AppHandle,
snapshot: kb_onchain_transport::WsSessionSnapshot,
) {
let window = match app_handle.get_webview_window("demo_ws") { let window = match app_handle.get_webview_window("demo_ws") {
std::option::Option::Some(window) => window, std::option::Option::Some(window) => window,
std::option::Option::None => return, std::option::Option::None => return,
@@ -505,11 +528,7 @@ fn emit_demo_ws_status(
} }
} }
fn emit_demo_ws_message( fn emit_message(app_handle: &tauri::AppHandle, kind: &str, payload_json: std::string::String) {
app_handle: &tauri::AppHandle,
kind: &str,
payload_json: std::string::String,
) {
let window = match app_handle.get_webview_window("demo_ws") { let window = match app_handle.get_webview_window("demo_ws") {
std::option::Option::Some(window) => window, std::option::Option::Some(window) => window,
std::option::Option::None => return, std::option::Option::None => return,
@@ -575,7 +594,7 @@ fn disconnected_status() -> crate::DemoWsStatusPayload {
}; };
} }
pub(crate) fn build_ws_role_options( fn build_ws_role_options(
snapshots: std::vec::Vec<kb_onchain_transport::WsPoolClientSnapshot>, snapshots: std::vec::Vec<kb_onchain_transport::WsPoolClientSnapshot>,
) -> std::vec::Vec<crate::DemoWsRoleOption> { ) -> std::vec::Vec<crate::DemoWsRoleOption> {
let mut roles = std::collections::BTreeMap::< let mut roles = std::collections::BTreeMap::<
@@ -603,7 +622,7 @@ pub(crate) fn build_ws_role_options(
return options; return options;
} }
pub(crate) fn build_ws_method_options() -> std::vec::Vec<crate::DemoWsMethodOption> { fn build_ws_method_options() -> std::vec::Vec<crate::DemoWsMethodOption> {
let labels = std::collections::BTreeMap::from([ let labels = std::collections::BTreeMap::from([
("accountSubscribe", ("Compte", true, false, true)), ("accountSubscribe", ("Compte", true, false, true)),
("blockSubscribe", ("Bloc", false, true, true)), ("blockSubscribe", ("Bloc", false, true, true)),

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/lib.rs // file: kb-app-demo-desktop/src/lib.rs
// version: 15 // version: 25
//! Tauri desktop demo application for `khadhroony-bot3`. //! Tauri desktop demo application for `khadhroony-bot3`.
@@ -36,6 +36,8 @@ pub use self::tauri::run;
/// Shared application state managed by Tauri. /// Shared application state managed by Tauri.
pub(crate) use self::app_state::AppState; pub(crate) use self::app_state::AppState;
/// Canonical tracing target for the desktop demo application.
pub(crate) use self::constants::TRACING_TARGET;
/// Backfill observer forwarding pipeline progress to the Tauri window. /// Backfill observer forwarding pipeline progress to the Tauri window.
pub(crate) use self::demo_backfill::DemoBackfillObserver; pub(crate) use self::demo_backfill::DemoBackfillObserver;
/// Initial options shown by the backfill window. /// Initial options shown by the backfill window.
@@ -52,12 +54,12 @@ pub(crate) use self::demo_backfill::DemoBackfillRoleOption;
pub(crate) use self::demo_backfill::DemoBackfillRunGuard; pub(crate) use self::demo_backfill::DemoBackfillRunGuard;
/// Final UI-safe summary for one backfill campaign. /// Final UI-safe summary for one backfill campaign.
pub(crate) use self::demo_backfill::DemoBackfillSummaryPayload; pub(crate) use self::demo_backfill::DemoBackfillSummaryPayload;
/// Builds one validated pipeline request from the UI contract. /// Requests cooperative cancellation of the active backfill campaign.
pub(crate) use self::demo_backfill::build_demo_backfill_pipeline_request; pub(crate) use self::demo_backfill::demo_backfill_cancel;
/// Builds selectable endpoint roles from the HTTP pool snapshot. /// Executes one bounded backfill campaign and emits progress events.
pub(crate) use self::demo_backfill::build_role_options; pub(crate) use self::demo_backfill::demo_backfill_execute;
/// Converts a pipeline summary to the UI-safe summary. /// Returns endpoint roles, known programs and conservative backfill defaults.
pub(crate) use self::demo_backfill::demo_backfill_summary_payload; pub(crate) use self::demo_backfill::demo_backfill_options;
/// Configuration payload shown by the configuration window. /// Configuration payload shown by the configuration window.
pub(crate) use self::demo_config::DemoConfigPayload; pub(crate) use self::demo_config::DemoConfigPayload;
/// Builds the configuration payload from shared application state. /// Builds the configuration payload from shared application state.
@@ -77,15 +79,11 @@ pub(crate) use self::demo_core_extraction::DemoCoreExtractionRunGuard;
/// Internal demo core extraction item. /// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::DemoCoreExtractionSummaryPayload; pub(crate) use self::demo_core_extraction::DemoCoreExtractionSummaryPayload;
/// Internal demo core extraction item. /// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::build_demo_core_extraction_pipeline_request; pub(crate) use self::demo_core_extraction::demo_core_extraction_cancel;
/// Internal demo core extraction item. /// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::cancel_demo_core_extraction; pub(crate) use self::demo_core_extraction::demo_core_extraction_execute;
/// Internal demo core extraction item. /// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::demo_core_extraction_options_payload; pub(crate) use self::demo_core_extraction::demo_core_extraction_options_payload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::demo_core_extraction_summary_payload;
/// Internal demo core extraction item.
pub(crate) use self::demo_core_extraction::execute_demo_core_extraction;
/// Internal demo decode replay item. /// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoDecodeCoverageSummaryPayload; pub(crate) use self::demo_decode_replay::DemoDecodeCoverageSummaryPayload;
/// Internal demo decode replay item. /// Internal demo decode replay item.
@@ -112,35 +110,29 @@ pub(crate) use self::demo_decode_replay::DemoDecodeReplaySummaryPayload;
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRequest; pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRequest;
/// Internal demo decode replay item. /// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRow; pub(crate) use self::demo_decode_replay::DemoTransactionAnnotationRow;
/// Internal demo decode replay item. /// Loads bounded materialized transaction annotations for the replay diagnostics panel.
pub(crate) use self::demo_decode_replay::annotation_payload; pub(crate) use self::demo_decode_replay::demo_decode_replay_annotations;
/// Internal demo decode replay item. /// Requests cooperative cancellation of the active decode replay campaign.
pub(crate) use self::demo_decode_replay::available_decoders; pub(crate) use self::demo_decode_replay::demo_decode_replay_cancel;
/// Internal demo decode replay item. /// Loads aggregate diagnostics for the decode replay panel.
pub(crate) use self::demo_decode_replay::available_materializers; pub(crate) use self::demo_decode_replay::demo_decode_replay_diagnostics;
/// Internal demo decode replay item. /// Executes one bounded contextual decode replay campaign.
pub(crate) use self::demo_decode_replay::build_demo_decode_replay_pipeline_request; pub(crate) use self::demo_decode_replay::demo_decode_replay_execute;
/// Internal demo decode replay item. /// Returns available decoders and default replay bounds.
pub(crate) use self::demo_decode_replay::coverage_payload; pub(crate) use self::demo_decode_replay::demo_decode_replay_options;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::demo_decode_replay_summary_payload;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::optional_line_count;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::register_active_campaign;
/// Internal demo decode replay item.
pub(crate) use self::demo_decode_replay::text_sample;
/// Readiness report for one Devnet profile PostgreSQL store. /// Readiness report for one Devnet profile PostgreSQL store.
pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayload; pub(crate) use self::demo_devnet_common::DemoExecutionDevnetStoreReadinessPayload;
/// Converts the reusable scenario readiness report to the desktop TS-RS payload. /// Converts the reusable scenario readiness report to the desktop TS-RS payload.
pub(crate) use self::demo_devnet_common::devnet_store_readiness_payload; pub(crate) use self::demo_devnet_common::devnet_store_readiness_payload;
/// Serializes a diagnostic value to indented JSON without panicking.
pub(crate) use self::demo_devnet_common::pretty_json;
/// Request sent by the Memo v4 Devnet execution panel. /// Request sent by the Memo v4 Devnet execution panel.
pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoRequest; pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoRequest;
/// UI-safe result of one Memo v4 Devnet execution orchestration. /// UI-safe result of one Memo v4 Devnet execution orchestration.
pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoSummaryPayload; pub(crate) use self::demo_execution_solana_core::DemoExecutionMemoSummaryPayload;
/// Public key generated for a disposable Devnet recipient. /// Public key generated for a disposable Devnet recipient.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreGeneratedRecipientPayload; pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreGeneratedRecipientPayload;
/// ???? /// Bridges reusable execution progress to desktop events.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreObserver; pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreObserver;
/// Initial options shown by the Solana Core execution demo. /// Initial options shown by the Solana Core execution demo.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreOptionsPayload; pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreOptionsPayload;
@@ -150,26 +142,32 @@ pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProfileO
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProgressPayload; pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreProgressPayload;
/// Request sent by the execution demo. /// Request sent by the execution demo.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRequest; pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRequest;
/// ???? /// Restores the single-execution state flag when a run finishes.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRunGuard; pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreRunGuard;
/// UI-safe result of one execution orchestration. /// UI-safe result of one execution orchestration.
pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreSummaryPayload; pub(crate) use self::demo_execution_solana_core::DemoExecutionSolanaCoreSummaryPayload;
/// ???? /// Maps a confirmation status to its stable UI code.
pub(crate) use self::demo_execution_solana_core::confirmation_status_code; pub(crate) use self::demo_execution_solana_core::confirmation_status_code;
/// ???? /// Returns compatible Devnet profiles and conservative defaults.
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_memo_summary_payload; pub(crate) use self::demo_execution_solana_core::demo_execution_devnet_prepare_profile;
/// ???? /// Requests cooperative cancellation before the next execution stage.
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_summary_payload; pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_cancel;
/// ???? /// Executes one bounded System transfer simulation or Devnet submission.
pub(crate) use self::demo_execution_solana_core::devnet_profile_options; pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_execute;
/// ???? /// Generates a disposable recipient public key without exposing its private key.
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_generate_recipient;
/// Returns compatible Devnet profiles and conservative defaults.
pub(crate) use self::demo_execution_solana_core::demo_execution_solana_core_options;
/// Executes one Memo v4 simulation or explicitly authorized Devnet submission.
pub(crate) use self::demo_execution_solana_core::demo_execution_spl_memo_execute;
/// Maps an execution cluster to its stable UI code.
pub(crate) use self::demo_execution_solana_core::execution_cluster_code; pub(crate) use self::demo_execution_solana_core::execution_cluster_code;
/// ???? /// Selects and validates one Devnet profile by name.
pub(crate) use self::demo_execution_solana_core::pretty_json;
/// ????
pub(crate) use self::demo_execution_solana_core::select_devnet_profile; pub(crate) use self::demo_execution_solana_core::select_devnet_profile;
/// Frontend payload for one independent Devnet SPL validation scenario. /// Frontend payload for one independent Devnet SPL validation scenario.
pub(crate) use self::demo_execution_spl::DevnetSplValidationScenarioPayload; pub(crate) use self::demo_execution_spl::DevnetSplValidationScenarioPayload;
/// Returns the complete ordered Devnet SPL validation inventory for milestone 0.4.6.
pub(crate) use self::demo_execution_spl::demo_execution_spl_validation_scenarios;
/// HTTP demo response payload. /// HTTP demo response payload.
pub(crate) use self::demo_http::DemoHttpExecutionPayload; pub(crate) use self::demo_http::DemoHttpExecutionPayload;
/// One selectable HTTP JSON-RPC method. /// One selectable HTTP JSON-RPC method.
@@ -180,12 +178,12 @@ pub(crate) use self::demo_http::DemoHttpOptionsPayload;
pub(crate) use self::demo_http::DemoHttpRequest; pub(crate) use self::demo_http::DemoHttpRequest;
/// One selectable HTTP endpoint role. /// One selectable HTTP endpoint role.
pub(crate) use self::demo_http::DemoHttpRoleOption; pub(crate) use self::demo_http::DemoHttpRoleOption;
/// Builds the HTTP method inventory.
pub(crate) use self::demo_http::build_http_method_options;
/// Builds the HTTP role inventory from pool snapshots.
pub(crate) use self::demo_http::build_http_role_options;
/// Executes one raw HTTP JSON-RPC request. /// Executes one raw HTTP JSON-RPC request.
pub(crate) use self::demo_http::demo_http_execute_request_inner; pub(crate) use self::demo_http::demo_http_execute_request;
/// Returns the configured HTTP pool client snapshots.
pub(crate) use self::demo_http::demo_http_list_pool_clients;
/// Returns HTTP roles, methods and conservative defaults.
pub(crate) use self::demo_http::demo_http_options;
/// Request for one representative ATA creation on Devnet. /// Request for one representative ATA creation on Devnet.
pub(crate) use self::demo_spl_ata::DemoExecutionSplAtaRequest; pub(crate) use self::demo_spl_ata::DemoExecutionSplAtaRequest;
/// UI-safe ATA execution result. /// UI-safe ATA execution result.
@@ -198,18 +196,12 @@ pub(crate) use self::demo_spl_ata::DemoSplAtaDerivationRequest;
pub(crate) use self::demo_spl_ata::DemoSplAtaJournalRequest; pub(crate) use self::demo_spl_ata::DemoSplAtaJournalRequest;
/// One UI-safe ATA lifecycle journal row. /// One UI-safe ATA lifecycle journal row.
pub(crate) use self::demo_spl_ata::DemoSplAtaJournalRow; pub(crate) use self::demo_spl_ata::DemoSplAtaJournalRow;
/// ???? /// Executes one representative Create or CreateIdempotent ATA flow.
pub(crate) use self::demo_spl_ata::demo_spl_ata_summary_payload; pub(crate) use self::demo_spl_ata::demo_execution_spl_ata_execute;
/// ???? /// Derives the representative profile wallet ATA without exposing private key material.
pub(crate) use self::demo_spl_ata::derive_ata; pub(crate) use self::demo_spl_ata::demo_spl_ata_derive;
/// ???? /// Loads a bounded journal of ATA-owned lifecycle facts.
pub(crate) use self::demo_spl_ata::journal_matches; pub(crate) use self::demo_spl_ata::demo_spl_ata_journal;
/// ????
pub(crate) use self::demo_spl_ata::journal_row;
/// ????
pub(crate) use self::demo_spl_ata::load_profile_wallet;
/// ????
pub(crate) use self::demo_spl_ata::parse_token_program;
/// Request for one checked classic SPL Token transfer on Devnet. /// Request for one checked classic SPL Token transfer on Devnet.
pub(crate) use self::demo_spl_token::DemoExecutionSplTokenRequest; pub(crate) use self::demo_spl_token::DemoExecutionSplTokenRequest;
/// UI-safe result of one checked SPL Token transfer orchestration. /// UI-safe result of one checked SPL Token transfer orchestration.
@@ -218,26 +210,20 @@ pub(crate) use self::demo_spl_token::DemoExecutionSplTokenSummaryPayload;
pub(crate) use self::demo_spl_token::DemoSplTokenJournalRequest; pub(crate) use self::demo_spl_token::DemoSplTokenJournalRequest;
/// UI-safe materialized classic SPL Token journal row. /// UI-safe materialized classic SPL Token journal row.
pub(crate) use self::demo_spl_token::DemoSplTokenJournalRow; pub(crate) use self::demo_spl_token::DemoSplTokenJournalRow;
/// ????
pub(crate) use self::demo_spl_token::demo_spl_token_journal_row;
/// Executes one checked SPL Token simulation or explicitly authorized Devnet submission. /// Executes one checked SPL Token simulation or explicitly authorized Devnet submission.
pub(crate) use self::demo_spl_token::demo_spl_token_summary_payload; pub(crate) use self::demo_spl_token::demo_execution_spl_token_execute;
/// ????
pub(crate) use self::demo_spl_token::payload_matches;
/// Loads a bounded, typed journal of committed classic SPL Token projections. /// Loads a bounded, typed journal of committed classic SPL Token projections.
pub(crate) use self::demo_spl_token::validate_journal_request; pub(crate) use self::demo_spl_token::demo_spl_token_journal;
/// Request for one independent public Token-2022 Devnet scenario. /// Request for one independent public Token-2022 Devnet scenario.
pub(crate) use self::demo_spl_token_2022::DemoExecutionSplToken2022Request; pub(crate) use self::demo_spl_token_2022::DemoExecutionSplToken2022Request;
/// UI-safe result of one public Token-2022 Devnet orchestration. /// UI-safe result of one public Token-2022 Devnet orchestration.
pub(crate) use self::demo_spl_token_2022::DemoExecutionSplToken2022SummaryPayload; pub(crate) use self::demo_spl_token_2022::DemoExecutionSplToken2022SummaryPayload;
/// Public values loaded from one persisted Token-2022 validation fixture. /// Public values loaded from one persisted Token-2022 validation fixture.
pub(crate) use self::demo_spl_token_2022::DemoSplToken2022FixturePayload; pub(crate) use self::demo_spl_token_2022::DemoSplToken2022FixturePayload;
/// ???? /// Executes one public Token-2022 simulation or explicitly authorized Devnet submission.
pub(crate) use self::demo_spl_token_2022::demo_spl_token_2022_summary_payload; pub(crate) use self::demo_spl_token_2022::demo_execution_spl_token_2022_execute;
/// ???? /// Loads public Token-2022 fixture values without reading private key bytes.
pub(crate) use self::demo_spl_token_2022::operation_from_request; pub(crate) use self::demo_spl_token_2022::demo_spl_token_2022_fixture;
/// ????
pub(crate) use self::demo_spl_token_2022::parse_fixture;
/// UI-safe SQL table diagnostic snapshot. /// UI-safe SQL table diagnostic snapshot.
pub(crate) use self::demo_sql_common::DemoSqlTableSnapshot; pub(crate) use self::demo_sql_common::DemoSqlTableSnapshot;
/// Connects to the configured PostgreSQL store. /// Connects to the configured PostgreSQL store.
@@ -254,10 +240,16 @@ pub(crate) use self::demo_sql_common::table_snapshot_from_pg;
pub(crate) use self::demo_sql_common::table_snapshots_from_pg; pub(crate) use self::demo_sql_common::table_snapshots_from_pg;
/// SQL diagnostic payload. /// SQL diagnostic payload.
pub(crate) use self::demo_sql_diag::DemoSqlDiagPayload; pub(crate) use self::demo_sql_diag::DemoSqlDiagPayload;
/// Loads read-only SQL diagnostics from the active profile.
pub(crate) use self::demo_sql_diag::load_demo_sql_diag;
/// PostgreSQL core-table payload. /// PostgreSQL core-table payload.
pub(crate) use self::demo_sql_pg_core::DemoSqlPgCorePayload; pub(crate) use self::demo_sql_pg_core::DemoSqlPgCorePayload;
/// Loads read-only core table diagnostics from PostgreSQL.
pub(crate) use self::demo_sql_pg_core::load_demo_sql_pg_core;
/// PostgreSQL raw-table payload. /// PostgreSQL raw-table payload.
pub(crate) use self::demo_sql_pg_raw::DemoSqlPgRawPayload; pub(crate) use self::demo_sql_pg_raw::DemoSqlPgRawPayload;
/// Loads read-only raw table diagnostics from PostgreSQL.
pub(crate) use self::demo_sql_pg_raw::load_demo_sql_pg_raw;
/// SQL replay entity request. /// SQL replay entity request.
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayEntityRequest; pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayEntityRequest;
/// SQL replay entity row. /// SQL replay entity row.
@@ -274,22 +266,16 @@ pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayProgramRow;
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayTransactionRequest; pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayTransactionRequest;
/// SQL replay transaction row. /// SQL replay transaction row.
pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayTransactionRow; pub(crate) use self::demo_sql_replay_candidates::DemoSqlReplayTransactionRow;
/// Resolves an available CSV path. /// Loads static browser options from the active PostgreSQL profile.
pub(crate) use self::demo_sql_replay_candidates::available_csv_export_path; pub(crate) use self::demo_sql_replay_candidates::demo_sql_replay_options;
/// Parses an entity-kind code. /// Writes a bounded replay-candidate CSV file into `./data/exports_csv/`.
pub(crate) use self::demo_sql_replay_candidates::entity_kind_from_code; pub(crate) use self::demo_sql_replay_candidates::export_demo_sql_replay_csv;
/// Converts a PostgreSQL entity row. /// Loads bounded mint, owner or account-key summaries from PostgreSQL core tables.
pub(crate) use self::demo_sql_replay_candidates::entity_row_from_pg; pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_entities;
/// Parses an optional entity-kind code. /// Loads bounded program summaries from PostgreSQL.
pub(crate) use self::demo_sql_replay_candidates::optional_entity_kind_from_code; pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_programs;
/// Converts a PostgreSQL program row. /// Loads bounded transaction replay candidates from PostgreSQL.
pub(crate) use self::demo_sql_replay_candidates::program_row_from_pg; pub(crate) use self::demo_sql_replay_candidates::load_demo_sql_replay_transactions;
/// Parses a program-scope code.
pub(crate) use self::demo_sql_replay_candidates::program_scope_from_code;
/// Converts a PostgreSQL transaction row.
pub(crate) use self::demo_sql_replay_candidates::transaction_row_from_pg;
/// Validates a CSV filename.
pub(crate) use self::demo_sql_replay_candidates::validated_csv_file_name;
/// WebSocket execution response payload. /// WebSocket execution response payload.
pub(crate) use self::demo_ws::DemoWsExecutionPayload; pub(crate) use self::demo_ws::DemoWsExecutionPayload;
/// WebSocket message emitted to the frontend. /// WebSocket message emitted to the frontend.
@@ -308,18 +294,18 @@ pub(crate) use self::demo_ws::DemoWsStatusPayload;
pub(crate) use self::demo_ws::DemoWsSubscriptionStatusPayload; pub(crate) use self::demo_ws::DemoWsSubscriptionStatusPayload;
/// WebSocket unsubscribe request. /// WebSocket unsubscribe request.
pub(crate) use self::demo_ws::DemoWsUnsubscribeRequest; pub(crate) use self::demo_ws::DemoWsUnsubscribeRequest;
/// Builds the standard WebSocket method inventory.
pub(crate) use self::demo_ws::build_ws_method_options;
/// Builds selectable WebSocket roles from pool snapshots.
pub(crate) use self::demo_ws::build_ws_role_options;
/// Connects and subscribes through the persistent WebSocket session. /// Connects and subscribes through the persistent WebSocket session.
pub(crate) use self::demo_ws::demo_ws_connect_inner; pub(crate) use self::demo_ws::demo_ws_connect;
/// Disconnects the persistent WebSocket session. /// Disconnects the persistent WebSocket session.
pub(crate) use self::demo_ws::demo_ws_disconnect_inner; pub(crate) use self::demo_ws::demo_ws_disconnect;
/// Lists WebSocket endpoints available through the configured pool.
pub(crate) use self::demo_ws::demo_ws_list_pool_clients;
/// Lists selectable WebSocket roles and methods for the demo UI.
pub(crate) use self::demo_ws::demo_ws_options;
/// Returns the current WebSocket session status. /// Returns the current WebSocket session status.
pub(crate) use self::demo_ws::demo_ws_status_inner; pub(crate) use self::demo_ws::demo_ws_status;
/// Unsubscribes one active WebSocket subscription. /// Unsubscribes one active WebSocket subscription.
pub(crate) use self::demo_ws::demo_ws_unsubscribe_inner; pub(crate) use self::demo_ws::demo_ws_unsubscribe;
/// Disconnects the persistent WebSocket session during application shutdown. /// Disconnects the persistent WebSocket session during application shutdown.
pub(crate) use self::demo_ws::disconnect_demo_ws_app_state; pub(crate) use self::demo_ws::disconnect_demo_ws_app_state;
/// Frontend logging payload. /// Frontend logging payload.
@@ -341,10 +327,4 @@ pub(crate) use self::splash::SplashOrder;
/// Emits one order to the splash frontend. /// Emits one order to the splash frontend.
pub(crate) use self::splash::emit_splash_order; pub(crate) use self::splash::emit_splash_order;
/// Runs the startup sequence after the splash frontend is ready. /// Runs the startup sequence after the splash frontend is ready.
pub(crate) use self::splash::splash_frontend_ready_inner; pub(crate) use self::splash::splash_frontend_ready;
/// Waits for the minimum splash duration.
pub(crate) use self::splash::wait_until_minimum;
// Keep the canonical tracing target as the final facade export.
/// Canonical tracing target for the desktop demo application.
pub(crate) use self::constants::TRACING_TARGET;

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/splash.rs // file: kb-app-demo-desktop/src/splash.rs
// version: 4 // version: 5
//! Splash-window payloads and startup sequencing helpers. //! Splash-window payloads and startup sequencing helpers.
@@ -52,7 +52,7 @@ pub(crate) fn emit_splash_order(
} }
/// Runs the startup sequence after the splash frontend installed its listener. /// Runs the startup sequence after the splash frontend installed its listener.
pub(crate) async fn splash_frontend_ready_inner( pub(crate) async fn splash_frontend_ready(
app: tauri::AppHandle, app: tauri::AppHandle,
window: tauri::WebviewWindow, window: tauri::WebviewWindow,
state: &crate::AppState, state: &crate::AppState,
@@ -120,7 +120,7 @@ pub(crate) async fn splash_frontend_ready_inner(
std::option::Option::Some("success"), std::option::Option::Some("success"),
std::option::Option::None, std::option::Option::None,
); );
crate::wait_until_minimum(started_at, crate::SPLASH_MINIMUM_MS).await; wait_until_minimum(started_at, crate::SPLASH_MINIMUM_MS).await;
crate::emit_splash_order( crate::emit_splash_order(
&window, &window,
"fadeout", "fadeout",
@@ -142,7 +142,7 @@ pub(crate) async fn splash_frontend_ready_inner(
} }
/// Waits until the configured minimum splash duration has elapsed. /// Waits until the configured minimum splash duration has elapsed.
pub(crate) async fn wait_until_minimum(start: tokio::time::Instant, minimum_ms: u64) { async fn wait_until_minimum(start: tokio::time::Instant, minimum_ms: u64) {
let minimum = std::time::Duration::from_millis(minimum_ms); let minimum = std::time::Duration::from_millis(minimum_ms);
let elapsed = start.elapsed(); let elapsed = start.elapsed();
if elapsed >= minimum { if elapsed >= minimum {

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# file: scripts/audit_pre_0_4_6_static_contracts.py # file: scripts/audit_pre_0_4_6_static_contracts.py
# version: 1 # version: 4
"""Audit static contracts that must remain true before the 0.4.6 alignment.""" """Audit static contracts that must remain true before the 0.4.6 alignment."""
@@ -35,7 +35,7 @@ def registered_commands(root: pathlib.Path) -> set[str]:
match = re.search(r"generate_handler!\[(.*?)\]\);", text, re.DOTALL) match = re.search(r"generate_handler!\[(.*?)\]\);", text, re.DOTALL)
if match is None: if match is None:
return set() return set()
return set(re.findall(r"^[ \t]*([A-Za-z_][A-Za-z0-9_]*)[ \t]*,", match.group(1), re.MULTILINE)) return set(re.findall(r"([A-Za-z_][A-Za-z0-9_]*)[ \t]*,", match.group(1)))
def main() -> int: def main() -> int:
"""Run the pre-0.4.6 static audit.""" """Run the pre-0.4.6 static audit."""
@@ -62,7 +62,22 @@ def main() -> int:
fail("ALIGN002", f"frontend commands missing from Tauri registration: {missing}") fail("ALIGN002", f"frontend commands missing from Tauri registration: {missing}")
violations += 1 violations += 1
tauri_text = (root / "kb-app-demo-desktop" / "src" / "tauri.rs").read_text(encoding="utf-8") tauri_path = root / "kb-app-demo-desktop" / "src" / "tauri.rs"
tauri_text = tauri_path.read_text(encoding="utf-8")
forbidden_tauri_dependencies = (
"kb_pipeline::",
"kb_pipeline_demo_scenarios::",
"kb_store::",
)
direct_dependencies = [
dependency for dependency in forbidden_tauri_dependencies if dependency in tauri_text
]
if direct_dependencies:
fail(
"ALIGN009",
f"tauri.rs must remain a thin IPC/runtime layer, direct domain dependencies: {direct_dependencies}",
)
violations += 1
if 'window.label() != "main"' not in tauri_text or "disconnect_demo_ws_app_state" not in tauri_text: if 'window.label() != "main"' not in tauri_text or "disconnect_demo_ws_app_state" not in tauri_text:
fail("ALIGN003", "global WebSocket shutdown must be tied to main-window destruction") fail("ALIGN003", "global WebSocket shutdown must be tied to main-window destruction")
violations += 1 violations += 1
@@ -103,9 +118,37 @@ def main() -> int:
fail("ALIGN008", "operation naming matrix is missing") fail("ALIGN008", "operation naming matrix is missing")
violations += 1 violations += 1
tauri_source = (root / "kb-app-demo-desktop/src/tauri.rs").read_text(encoding="utf-8")
command_files = []
for command_path in (root / "kb-app-demo-desktop/src").glob("*.rs"):
command_source = command_path.read_text(encoding="utf-8")
if "#[tauri::command]" in command_source:
command_files.append(command_path.name)
if command_files != ["tauri.rs"]:
fail("ALIGN009", f"#[tauri::command] must exist only in tauri.rs, found: {command_files}")
violations += 1
forbidden_files = [
"tauri_backfill.rs",
"tauri_core_extraction.rs",
"tauri_decode_replay.rs",
"tauri_execution.rs",
"tauri_general_commands.rs",
"tauri_http.rs",
"tauri_sql.rs",
"tauri_support.rs",
"tauri_ws.rs",
]
for forbidden in forbidden_files:
if (root / "kb-app-demo-desktop/src" / forbidden).exists():
fail("ALIGN010", f"obsolete Tauri adapter module still exists: {forbidden}")
violations += 1
if "return crate::demo_decode_replay_options(state);" not in tauri_source:
fail("ALIGN011", "tauri.rs must delegate demo_decode_replay_options through the crate façade")
violations += 1
if violations == 0: if violations == 0:
print("Pre-0.4.6 static contract audit: clean") print("Pre-0.4.6 static contract audit: clean")
return 0 return 0
print(f"Pre-0.4.6 static contract audit: {violations} violation(s)") print(f"Pre-0.4.6 static contract audit: {violations} violation(s)")
return 0 if arguments.report_only else 1 return 0 if arguments.report_only else 1