0.3.15-pre.010-fix.001
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/README.md -->
|
||||
<!-- version: 10 -->
|
||||
<!-- version: 11 -->
|
||||
|
||||
# `ksp-app-raw-transaction-ingest-desk`
|
||||
|
||||
@@ -48,7 +48,7 @@ http-block-polling
|
||||
|
||||
Les profils standard publics committés déclarent `Block` en plus de `Logs`; `standard-block-direct` est donc composable depuis Config sur Devnet, Mainnet et Testnet. Cette déclaration ne transforme pas `blockSubscribe` en méthode stable et ne remplace pas la revalidation runtime.
|
||||
|
||||
Une route `Configured` est uniquement **composable depuis Config**. La disponibilité réelle du Store et du Worker est revalidée lors du Start avant publication de l'accusé runtime. Les états spontanés après Start ne sont pas encore streamés vers le frontend ; l'observabilité continue est ajoutée par la tranche monitoring dédiée.
|
||||
Une route `Configured` est uniquement **composable depuis Config**. La disponibilité réelle du Store et du Worker est revalidée lors du Start avant publication de l'accusé runtime. Le backend relaie désormais chaque Worker actif par un snapshot latest-value sûr : lifecycle, health, activity, admission/persistence, backpressure, reconnect/replay, continuité, gaps et repair. Les mises à jour sont émises via l'événement Tauri `ksp-raw-ingest-route-status` et peuvent être resynchronisées avec `get_route_monitoring`. Les compteurs et slots `u64` sont projetés en texte décimal afin de rester exacts côté JavaScript. L'UI détaillée de supervision reste la tranche `pre.011`.
|
||||
|
||||
Pour `yellowstone-hydrated`, la stratégie Mainnet n'effectue plus un `getTransaction` par notification transactionnelle. Le Desk construit un abonnement Yellowstone Block léger ; chaque bloc observé déclenche une reconciliation HTTP `getBlock` Full/Base64 sur le même réseau. Le profil `publicnode_mainnet` expose un pool HTTP logique `default` contenant PublicNode et le RPC public Solana comme fallback de même priorité. Le pool conserve les limites propres à chaque endpoint, son health/cooldown et son round-robin interne. La reconciliation Yellowstone tolère un décalage bref entre le gRPC et les RPC HTTP grâce à un retry borné et interruptible par Stop. Le chemin protobuf -> RAW direct reste différé tant que la canonicalisation exacte du meta n'est pas prouvée pour toutes les formes supportées.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: crates/ksp-app-raw-transaction-ingest-desk/USAGE.md -->
|
||||
<!-- version: 9 -->
|
||||
<!-- version: 10 -->
|
||||
|
||||
# Utilisation de `ksp-app-raw-transaction-ingest-desk`
|
||||
|
||||
@@ -42,7 +42,7 @@ Pour une route `Configured`, choisir `confirmed` ou `finalized` puis utiliser `S
|
||||
|
||||
Plusieurs routes peuvent être actives simultanément lorsqu'elles appartiennent au même réseau logique. Chaque route conserve son Worker indépendant et son Stop ciblé. Le Store est partagé entre ces Workers ; le dernier Worker terminal déclenche la fermeture explicite du Store. Une route `Faulted` ne force pas l'arrêt des autres routes. Un Start d'un autre réseau est refusé tant que le Store partagé du réseau courant reste ouvert.
|
||||
|
||||
Le frontend ne fournit pas encore de streaming continu des snapshots runtime : une terminaison spontanée est nettoyée côté backend et un Stop tardif peut resynchroniser la route avec son dernier terminal sûr.
|
||||
Le backend expose désormais un flux latest-value par route via l'événement Tauri `ksp-raw-ingest-route-status`. Chaque projection contient uniquement l'identité logique sûre de la route, lifecycle/health/activity, compteurs admission/persistence, backpressure, reconnect/replay, continuité, gaps et repair. La commande `get_route_monitoring` permet une resynchronisation explicite des routes actives et des derniers terminaux retenus dans la session runtime courante. Les séquences, slots et compteurs `u64` sont transmis en texte décimal pour préserver leur exactitude côté JavaScript. L'affichage frontend complet et les contrôles de refresh dédiés sont finalisés en `pre.011`.
|
||||
|
||||
Sur Mainnet, `yellowstone-hydrated` utilise un abonnement Yellowstone Block comme signal de temps réel puis reconcile chaque slot via `getBlock` Full/Base64. Cette stratégie évite l'hydration `getTransaction` unitaire par transaction. Le profil PublicNode peut exposer plusieurs endpoints HTTP sous le même rôle logique ; Transport distribue alors les requêtes selon ses règles de priorité, disponibilité, concurrence et cooldown. La conversion directe du payload Yellowstone vers le RAW canonique reste réservée à une évolution ultérieure tant que la parité complète du meta n'est pas prouvée.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/app_state.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Shared backend state owned by the Raw Transaction Ingest Desk Tauri application.
|
||||
|
||||
@@ -137,6 +137,11 @@ impl crate::AppState {
|
||||
return crate::start_route_runtime(std::sync::Arc::clone(&self.route_runtime), prepared).await;
|
||||
}
|
||||
|
||||
/// Returns complete latest-value monitoring projections for active and recent terminal routes in the current same-network runtime session.
|
||||
pub(crate) fn route_monitoring(&self) -> ksp_core_lib::Result<std::vec::Vec<crate::RawIngestRouteMonitoringDto>> {
|
||||
return self.route_runtime.monitoring_statuses();
|
||||
}
|
||||
|
||||
/// Requests cooperative Stop for one exact logical route and waits until its Worker cleanup is complete.
|
||||
pub(crate) async fn stop_route(&self, request: &crate::RawIngestRouteStopRequestDto) -> ksp_core_lib::Result<crate::RawIngestRouteRuntimeDto> {
|
||||
return self.route_runtime.stop_and_wait(request).await;
|
||||
@@ -175,7 +180,7 @@ impl crate::AppState {
|
||||
application_version: env!("CARGO_PKG_VERSION").to_owned(),
|
||||
config_document_count: document_count,
|
||||
fallback_logging_active: runtime.fallback_active,
|
||||
shell_phase: "pre.009-multi-route-mainnet".to_owned(),
|
||||
shell_phase: "pre.010-runtime-monitoring".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/lib.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Tauri desktop application scaffold for composing and supervising KSP live RAW transaction ingest routes.
|
||||
|
||||
@@ -16,6 +16,7 @@ mod errors;
|
||||
mod frontend_logging;
|
||||
mod logging_runtime;
|
||||
mod route_inventory;
|
||||
mod route_monitoring;
|
||||
mod route_runtime;
|
||||
mod route_start;
|
||||
mod splash;
|
||||
@@ -158,6 +159,14 @@ pub(crate) use self::logging_runtime::launch_identity;
|
||||
pub(crate) use self::route_inventory::build_route_inventory;
|
||||
/// Rebuilds the route inventory from one already captured Config environment snapshot.
|
||||
pub(crate) use self::route_inventory::build_route_inventory_with_environment;
|
||||
/// Stable Tauri event carrying one complete latest-value route monitoring projection.
|
||||
pub(crate) use self::route_monitoring::RAW_INGEST_ROUTE_STATUS_EVENT_NAME;
|
||||
/// Frontend-safe bounded projection of one run-local continuity gap.
|
||||
pub(crate) use self::route_monitoring::RawIngestRouteGapDto;
|
||||
/// Frontend-safe latest-value monitoring projection of one independent route Worker.
|
||||
pub(crate) use self::route_monitoring::RawIngestRouteMonitoringDto;
|
||||
/// Projects one complete concrete Worker snapshot to the safe route monitoring contract.
|
||||
pub(crate) use self::route_monitoring::project_route_monitoring;
|
||||
/// Launch ownership for one independent route Worker sharing the application Store when eligible.
|
||||
pub(crate) use self::route_runtime::RouteRuntimeLaunch;
|
||||
/// Shared multi-route runtime state with one independent Worker per route and one same-network Store.
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_monitoring.rs
|
||||
// version: 1
|
||||
|
||||
//! Frontend-safe latest-value monitoring projection for Raw Transaction Ingest Desk route Workers.
|
||||
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Stable Tauri event carrying one complete latest-value route monitoring projection.
|
||||
pub(crate) const RAW_INGEST_ROUTE_STATUS_EVENT_NAME: &str = "ksp-raw-ingest-route-status";
|
||||
|
||||
/// Frontend-safe bounded projection of one run-local continuity gap.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/route_monitoring/RawIngestRouteGapDto.ts")]
|
||||
pub(crate) struct RawIngestRouteGapDto {
|
||||
/// Run-local gap identifier encoded as decimal text to avoid JavaScript integer truncation.
|
||||
pub(crate) gap_id: String,
|
||||
/// Inclusive first slot encoded as decimal text.
|
||||
pub(crate) start_slot: String,
|
||||
/// Inclusive last slot encoded as decimal text.
|
||||
pub(crate) end_slot: String,
|
||||
/// Stable source-neutral gap lifecycle code.
|
||||
pub(crate) state: String,
|
||||
/// Stable source-neutral reason code.
|
||||
pub(crate) reason: String,
|
||||
/// Stable source-neutral latest repair mechanism code, when any.
|
||||
pub(crate) last_method: std::option::Option<String>,
|
||||
}
|
||||
|
||||
/// Frontend-safe latest-value monitoring projection of one independent route Worker.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/route_monitoring/RawIngestRouteMonitoringDto.ts")]
|
||||
pub(crate) struct RawIngestRouteMonitoringDto {
|
||||
/// Confirmed/finalized commitment bound to this Worker run.
|
||||
pub(crate) commitment: crate::RawIngestCommitment,
|
||||
/// Inventory generation revalidated before this Worker started.
|
||||
pub(crate) inventory_generation: u32,
|
||||
/// Logical network shared by Worker and Store.
|
||||
pub(crate) network: String,
|
||||
/// Safe logical network-profile identifier.
|
||||
pub(crate) profile_id: String,
|
||||
/// Stable logical route identifier.
|
||||
pub(crate) route_id: crate::RawIngestRouteId,
|
||||
/// Monotone Worker snapshot sequence encoded as decimal text.
|
||||
pub(crate) sequence: String,
|
||||
/// Safe application-owned lifecycle projection.
|
||||
pub(crate) state: crate::RawIngestRouteState,
|
||||
/// Stable generic Worker health code.
|
||||
pub(crate) health: String,
|
||||
/// Stable generic Worker activity code.
|
||||
pub(crate) activity: String,
|
||||
/// Whether this latest Worker snapshot is terminal.
|
||||
pub(crate) terminal: bool,
|
||||
/// Stable terminal fault domain when lifecycle is faulted.
|
||||
pub(crate) fault_domain: std::option::Option<String>,
|
||||
/// Stable terminal fault code when lifecycle is faulted.
|
||||
pub(crate) fault_code: std::option::Option<String>,
|
||||
/// Configured bounded admission queue capacity.
|
||||
pub(crate) admission_queue_capacity: u32,
|
||||
/// Latest admission queue depth.
|
||||
pub(crate) admission_queue_depth: u32,
|
||||
/// Configured Store persistence concurrency.
|
||||
pub(crate) persistence_concurrency: u32,
|
||||
/// Latest number of in-flight Store persistence operations.
|
||||
pub(crate) in_flight_persistence: u32,
|
||||
/// Total admitted ingress entries encoded as decimal text.
|
||||
pub(crate) admitted_total: String,
|
||||
/// Total canonicalized ingress entries encoded as decimal text.
|
||||
pub(crate) canonicalized_total: String,
|
||||
/// Total successful Store persistence outcomes encoded as decimal text.
|
||||
pub(crate) persisted_total: String,
|
||||
/// Total newly inserted canonical RAW entities encoded as decimal text.
|
||||
pub(crate) entity_inserted_total: String,
|
||||
/// Total canonical RAW entities already present encoded as decimal text.
|
||||
pub(crate) entity_already_present_total: String,
|
||||
/// Total purge tombstones respected by persistence encoded as decimal text.
|
||||
pub(crate) entity_skipped_purged_total: String,
|
||||
/// Total newly inserted acquisition observations encoded as decimal text.
|
||||
pub(crate) observation_inserted_total: String,
|
||||
/// Total acquisition observations already present encoded as decimal text.
|
||||
pub(crate) observation_already_present_total: String,
|
||||
/// Total durable content conflicts encoded as decimal text.
|
||||
pub(crate) content_conflict_total: String,
|
||||
/// Total non-conflict Store failures encoded as decimal text.
|
||||
pub(crate) store_failure_total: String,
|
||||
/// Total source-task failures encoded as decimal text.
|
||||
pub(crate) source_failure_total: String,
|
||||
/// Total bounded-admission backpressure waits encoded as decimal text.
|
||||
pub(crate) backpressure_wait_total: String,
|
||||
/// Latest number of source signals awaiting hydration/admission processing.
|
||||
pub(crate) hydration_pending: u32,
|
||||
/// Run-local processing frontier encoded as decimal text when established.
|
||||
pub(crate) processing_frontier_slot: std::option::Option<String>,
|
||||
/// Oldest slot still owning pending source work encoded as decimal text when present.
|
||||
pub(crate) oldest_pending_slot: std::option::Option<String>,
|
||||
/// Whether continuity health policy has emitted a run-local projection.
|
||||
pub(crate) continuity_policy_observed: bool,
|
||||
/// Latest run-local continuity frontier encoded as decimal text when established.
|
||||
pub(crate) continuity_frontier_slot: std::option::Option<String>,
|
||||
/// Whether one or more continuity gaps are currently open.
|
||||
pub(crate) continuity_has_open_gaps: bool,
|
||||
/// Whether all failed-source continuity obligations have been reconciled.
|
||||
pub(crate) failed_source_losses_reconciled: bool,
|
||||
/// Whether current coverage proves the target can continue into future slots.
|
||||
pub(crate) future_target_coverage: bool,
|
||||
/// Stable source-neutral latest productive-source lifecycle code.
|
||||
pub(crate) source_state: std::option::Option<String>,
|
||||
/// Configured logical live-source count.
|
||||
pub(crate) source_total: u32,
|
||||
/// Latest Active source count.
|
||||
pub(crate) source_active: u32,
|
||||
/// Latest Reconnecting source count.
|
||||
pub(crate) source_reconnecting: u32,
|
||||
/// Latest Failed source count.
|
||||
pub(crate) source_failed: u32,
|
||||
/// Successful source reconnect count encoded as decimal text.
|
||||
pub(crate) source_reconnect_total: String,
|
||||
/// Replay-bearing reconnect attempt count encoded as decimal text.
|
||||
pub(crate) source_replay_attempt_total: String,
|
||||
/// Proven replay-retention continuity gap count encoded as decimal text.
|
||||
pub(crate) source_continuity_gap_total: String,
|
||||
/// Number of currently open continuity gaps.
|
||||
pub(crate) open_gap_count: u32,
|
||||
/// Number of continuity gaps currently under repair.
|
||||
pub(crate) repairing_gap_count: u32,
|
||||
/// Cumulative repaired-gap count encoded as decimal text.
|
||||
pub(crate) repaired_gap_total: String,
|
||||
/// Cumulative unresolved-gap count encoded as decimal text.
|
||||
pub(crate) unresolved_gap_total: String,
|
||||
/// Cumulative replay repair count encoded as decimal text.
|
||||
pub(crate) replay_repair_total: String,
|
||||
/// Cumulative redundant-coverage repair count encoded as decimal text.
|
||||
pub(crate) redundant_coverage_repair_total: String,
|
||||
/// Cumulative bounded HTTP scan repair count encoded as decimal text.
|
||||
pub(crate) http_scan_repair_total: String,
|
||||
/// Cumulative direct block-fetch repair count encoded as decimal text.
|
||||
pub(crate) repair_block_fetch_total: String,
|
||||
/// Cumulative transaction-hydration repair count encoded as decimal text.
|
||||
pub(crate) repair_transaction_hydration_total: String,
|
||||
/// Oldest currently open gap start slot encoded as decimal text when present.
|
||||
pub(crate) oldest_open_gap_start_slot: std::option::Option<String>,
|
||||
/// Bounded source-neutral details for current and recent continuity gaps.
|
||||
pub(crate) gaps: std::vec::Vec<crate::RawIngestRouteGapDto>,
|
||||
}
|
||||
|
||||
/// Projects one complete concrete Worker snapshot to the safe route monitoring contract.
|
||||
pub(crate) fn project_route_monitoring(
|
||||
runtime: &crate::RawIngestRouteRuntimeDto,
|
||||
snapshot: &ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot,
|
||||
) -> ksp_core_lib::Result<crate::RawIngestRouteMonitoringDto> {
|
||||
let worker = snapshot.worker_snapshot();
|
||||
let fault = worker.state().fault_code();
|
||||
let admission_queue_capacity = usize_to_u32(snapshot.admission_queue_capacity(), "admission_queue_capacity");
|
||||
let admission_queue_capacity = match admission_queue_capacity {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let admission_queue_depth = usize_to_u32(snapshot.admission_queue_depth(), "admission_queue_depth");
|
||||
let admission_queue_depth = match admission_queue_depth {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let persistence_concurrency = usize_to_u32(snapshot.persistence_concurrency(), "persistence_concurrency");
|
||||
let persistence_concurrency = match persistence_concurrency {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let in_flight_persistence = usize_to_u32(snapshot.in_flight_persistence(), "in_flight_persistence");
|
||||
let in_flight_persistence = match in_flight_persistence {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let hydration_pending = usize_to_u32(snapshot.hydration_pending(), "hydration_pending");
|
||||
let hydration_pending = match hydration_pending {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let source_total = usize_to_u32(snapshot.source_total(), "source_total");
|
||||
let source_total = match source_total {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let source_active = usize_to_u32(snapshot.source_active(), "source_active");
|
||||
let source_active = match source_active {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let source_reconnecting = usize_to_u32(snapshot.source_reconnecting(), "source_reconnecting");
|
||||
let source_reconnecting = match source_reconnecting {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let source_failed = usize_to_u32(snapshot.source_failed(), "source_failed");
|
||||
let source_failed = match source_failed {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let open_gap_count = usize_to_u32(snapshot.open_gap_count(), "open_gap_count");
|
||||
let open_gap_count = match open_gap_count {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let repairing_gap_count = usize_to_u32(snapshot.repairing_gap_count(), "repairing_gap_count");
|
||||
let repairing_gap_count = match repairing_gap_count {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let gaps = snapshot.gaps().iter().map(project_gap).collect::<std::vec::Vec<_>>();
|
||||
return std::result::Result::Ok(crate::RawIngestRouteMonitoringDto {
|
||||
commitment: runtime.commitment,
|
||||
inventory_generation: runtime.inventory_generation,
|
||||
network: runtime.network.clone(),
|
||||
profile_id: runtime.profile_id.clone(),
|
||||
route_id: runtime.route_id,
|
||||
sequence: worker.sequence().value().to_string(),
|
||||
state: project_worker_state(worker.state()),
|
||||
health: worker.health().code().to_owned(),
|
||||
activity: worker.activity().code().to_owned(),
|
||||
terminal: worker.state().is_terminal(),
|
||||
fault_domain: fault.map(|code| return code.domain().to_owned()),
|
||||
fault_code: fault.map(|code| return code.code().to_owned()),
|
||||
admission_queue_capacity,
|
||||
admission_queue_depth,
|
||||
persistence_concurrency,
|
||||
in_flight_persistence,
|
||||
admitted_total: snapshot.admitted_total().to_string(),
|
||||
canonicalized_total: snapshot.canonicalized_total().to_string(),
|
||||
persisted_total: snapshot.persisted_total().to_string(),
|
||||
entity_inserted_total: snapshot.entity_inserted_total().to_string(),
|
||||
entity_already_present_total: snapshot.entity_already_present_total().to_string(),
|
||||
entity_skipped_purged_total: snapshot.entity_skipped_purged_total().to_string(),
|
||||
observation_inserted_total: snapshot.observation_inserted_total().to_string(),
|
||||
observation_already_present_total: snapshot.observation_already_present_total().to_string(),
|
||||
content_conflict_total: snapshot.content_conflict_total().to_string(),
|
||||
store_failure_total: snapshot.store_failure_total().to_string(),
|
||||
source_failure_total: snapshot.source_failure_total().to_string(),
|
||||
backpressure_wait_total: snapshot.backpressure_wait_total().to_string(),
|
||||
hydration_pending,
|
||||
processing_frontier_slot: slot_text(snapshot.processing_frontier_slot()),
|
||||
oldest_pending_slot: slot_text(snapshot.oldest_pending_slot()),
|
||||
continuity_policy_observed: snapshot.continuity_policy_observed(),
|
||||
continuity_frontier_slot: slot_text(snapshot.continuity_frontier_slot()),
|
||||
continuity_has_open_gaps: snapshot.continuity_has_open_gaps(),
|
||||
failed_source_losses_reconciled: snapshot.failed_source_losses_reconciled(),
|
||||
future_target_coverage: snapshot.future_target_coverage(),
|
||||
source_state: snapshot.source_state().map(|state| return source_state_code(state).to_owned()),
|
||||
source_total,
|
||||
source_active,
|
||||
source_reconnecting,
|
||||
source_failed,
|
||||
source_reconnect_total: snapshot.source_reconnect_total().to_string(),
|
||||
source_replay_attempt_total: snapshot.source_replay_attempt_total().to_string(),
|
||||
source_continuity_gap_total: snapshot.source_continuity_gap_total().to_string(),
|
||||
open_gap_count,
|
||||
repairing_gap_count,
|
||||
repaired_gap_total: snapshot.repaired_gap_total().to_string(),
|
||||
unresolved_gap_total: snapshot.unresolved_gap_total().to_string(),
|
||||
replay_repair_total: snapshot.replay_repair_total().to_string(),
|
||||
redundant_coverage_repair_total: snapshot.redundant_coverage_repair_total().to_string(),
|
||||
http_scan_repair_total: snapshot.http_scan_repair_total().to_string(),
|
||||
repair_block_fetch_total: snapshot.repair_block_fetch_total().to_string(),
|
||||
repair_transaction_hydration_total: snapshot.repair_transaction_hydration_total().to_string(),
|
||||
oldest_open_gap_start_slot: slot_text(snapshot.oldest_open_gap_start_slot()),
|
||||
gaps,
|
||||
});
|
||||
}
|
||||
|
||||
fn project_gap(gap: &ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot) -> crate::RawIngestRouteGapDto {
|
||||
return crate::RawIngestRouteGapDto {
|
||||
gap_id: gap.gap_id().value().to_string(),
|
||||
start_slot: gap.start_slot().to_string(),
|
||||
end_slot: gap.end_slot().to_string(),
|
||||
state: gap_state_code(gap.state()).to_owned(),
|
||||
reason: gap_reason_code(gap.reason()).to_owned(),
|
||||
last_method: gap.last_method().map(|method| return repair_method_code(method).to_owned()),
|
||||
};
|
||||
}
|
||||
|
||||
fn project_worker_state(state: ksp_worker_api::WorkerState) -> crate::RawIngestRouteState {
|
||||
return match state {
|
||||
ksp_worker_api::WorkerState::Created | ksp_worker_api::WorkerState::Starting => crate::RawIngestRouteState::Starting,
|
||||
ksp_worker_api::WorkerState::Running => crate::RawIngestRouteState::Running,
|
||||
ksp_worker_api::WorkerState::Stopping => crate::RawIngestRouteState::Stopping,
|
||||
ksp_worker_api::WorkerState::Stopped => crate::RawIngestRouteState::Stopped,
|
||||
ksp_worker_api::WorkerState::Faulted(_) => crate::RawIngestRouteState::Faulted,
|
||||
_ => crate::RawIngestRouteState::Faulted,
|
||||
};
|
||||
}
|
||||
|
||||
fn source_state_code(state: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState) -> &'static str {
|
||||
return match state {
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Active => "active",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Reconnecting => "reconnecting",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Closing => "closing",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Closed => "closed",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Failed => "failed",
|
||||
};
|
||||
}
|
||||
|
||||
fn gap_state_code(state: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapState) -> &'static str {
|
||||
return match state {
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapState::Pending => "pending",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapState::Repairing => "repairing",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapState::Repaired => "repaired",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapState::Unresolved => "unresolved",
|
||||
};
|
||||
}
|
||||
|
||||
fn gap_reason_code(reason: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapReason) -> &'static str {
|
||||
return match reason {
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapReason::HttpProducedBlockUnavailable => "http_produced_block_unavailable",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapReason::KnownReferenceMissing => "known_reference_missing",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapReason::SourceFailure => "source_failure",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapReason::TransportOverflow => "transport_overflow",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapReason::WebSocketReconnect => "websocket_reconnect",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapReason::YellowstoneRetention => "yellowstone_retention",
|
||||
};
|
||||
}
|
||||
|
||||
fn repair_method_code(method: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRepairMethod) -> &'static str {
|
||||
return match method {
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRepairMethod::Replay => "replay",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRepairMethod::RedundantCoverage => "redundant_coverage",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRepairMethod::HttpScan => "http_scan",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRepairMethod::BlockFetch => "block_fetch",
|
||||
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRepairMethod::TransactionHydration => "transaction_hydration",
|
||||
};
|
||||
}
|
||||
|
||||
fn slot_text(value: std::option::Option<u64>) -> std::option::Option<String> {
|
||||
return value.map(|slot| return slot.to_string());
|
||||
}
|
||||
|
||||
fn usize_to_u32(value: usize, field: &'static str) -> ksp_core_lib::Result<u32> {
|
||||
let converted = u32::try_from(value);
|
||||
return match converted {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_APP_STATE_INVALID,
|
||||
"Raw Transaction Ingest Desk cannot project a bounded Worker counter to the frontend",
|
||||
)
|
||||
.with_context("field", field)
|
||||
.with_source(error),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/route_monitoring.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
//! Multi-route Store and independent Worker lifecycle owned by Raw Transaction Ingest Desk.
|
||||
|
||||
@@ -8,6 +8,7 @@ const STORE_RECLAIM_POLL_INTERVAL: std::time::Duration = std::time::Duration::fr
|
||||
const STORE_RECLAIM_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
|
||||
|
||||
struct RouteRuntimeInner {
|
||||
last_monitoring: std::vec::Vec<crate::RawIngestRouteMonitoringDto>,
|
||||
last_terminals: std::vec::Vec<crate::RawIngestRouteRuntimeDto>,
|
||||
network: std::option::Option<String>,
|
||||
next_sequence: u64,
|
||||
@@ -28,6 +29,7 @@ impl crate::RouteRuntimeState {
|
||||
pub(crate) fn new() -> Self {
|
||||
return Self {
|
||||
inner: std::sync::Mutex::new(RouteRuntimeInner {
|
||||
last_monitoring: std::vec::Vec::new(),
|
||||
last_terminals: std::vec::Vec::new(),
|
||||
network: std::option::Option::None,
|
||||
next_sequence: 0,
|
||||
@@ -99,9 +101,11 @@ impl crate::RouteRuntimeState {
|
||||
route_id: prepared.route_id,
|
||||
};
|
||||
if inner.network.is_none() {
|
||||
inner.last_monitoring.clear();
|
||||
inner.last_terminals.clear();
|
||||
inner.network = std::option::Option::Some(prepared.network.clone());
|
||||
}
|
||||
inner.last_monitoring.retain(|status| return !identity.matches_monitoring(status));
|
||||
inner.last_terminals.retain(|terminal| return !identity.matches_dto(terminal));
|
||||
inner.next_sequence = sequence;
|
||||
if needs_store_open {
|
||||
@@ -196,7 +200,12 @@ impl crate::RouteRuntimeState {
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
}
|
||||
|
||||
fn finish(&self, token: u64, terminal: crate::RawIngestRouteRuntimeDto) -> ksp_core_lib::Result<std::option::Option<std::sync::Arc<ksp_store_lib::Store>>> {
|
||||
fn finish(
|
||||
&self,
|
||||
token: u64,
|
||||
terminal: crate::RawIngestRouteRuntimeDto,
|
||||
monitoring: std::option::Option<crate::RawIngestRouteMonitoringDto>,
|
||||
) -> ksp_core_lib::Result<std::option::Option<std::sync::Arc<ksp_store_lib::Store>>> {
|
||||
let inner = self.inner.lock();
|
||||
let mut inner = match inner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -209,6 +218,10 @@ impl crate::RouteRuntimeState {
|
||||
}
|
||||
inner.last_terminals.retain(|previous| return previous.profile_id != terminal.profile_id || previous.route_id != terminal.route_id);
|
||||
inner.last_terminals.push(terminal);
|
||||
if let std::option::Option::Some(status) = monitoring {
|
||||
inner.last_monitoring.retain(|previous| return previous.profile_id != status.profile_id || previous.route_id != status.route_id);
|
||||
inner.last_monitoring.push(status);
|
||||
}
|
||||
if inner.routes.is_empty() {
|
||||
let store = inner.store.take();
|
||||
if store.is_some() {
|
||||
@@ -235,6 +248,30 @@ impl crate::RouteRuntimeState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns complete latest-value monitoring projections for active and recent terminal routes in the current runtime session.
|
||||
pub(crate) fn monitoring_statuses(&self) -> ksp_core_lib::Result<std::vec::Vec<crate::RawIngestRouteMonitoringDto>> {
|
||||
let inner = self.inner.lock();
|
||||
let inner = match inner {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
|
||||
};
|
||||
let mut values = inner.last_monitoring.clone();
|
||||
for slot in &inner.routes {
|
||||
if let RouteRuntimeSlot::Active { handle, identity, .. } = slot {
|
||||
let snapshot = handle.snapshot_source().current();
|
||||
let runtime = identity.dto(project_worker_state(snapshot.worker_snapshot().state()));
|
||||
let projected = crate::project_route_monitoring(&runtime, &snapshot);
|
||||
let projected = match projected {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
values.retain(|previous| return previous.profile_id != projected.profile_id || previous.route_id != projected.route_id);
|
||||
values.push(projected);
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(values);
|
||||
}
|
||||
|
||||
/// Requests cooperative Stop for one exact logical route and waits for its Worker cleanup.
|
||||
pub(crate) async fn stop_and_wait(&self, request: &crate::RawIngestRouteStopRequestDto) -> ksp_core_lib::Result<crate::RawIngestRouteRuntimeDto> {
|
||||
let target = self.stop_target(request);
|
||||
@@ -389,6 +426,18 @@ impl crate::RouteRuntimeLaunch {
|
||||
return self.acknowledgement.clone();
|
||||
}
|
||||
|
||||
/// Returns the complete safe logical route identity used to project Worker monitoring snapshots.
|
||||
#[must_use]
|
||||
pub(crate) fn monitoring_identity(&self) -> crate::RawIngestRouteRuntimeDto {
|
||||
return self.acknowledgement.clone();
|
||||
}
|
||||
|
||||
/// Returns an independent latest-value source for frontend-safe monitoring relay.
|
||||
#[must_use]
|
||||
pub(crate) fn monitoring_source(&self) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshotSource {
|
||||
return self.handle.snapshot_source();
|
||||
}
|
||||
|
||||
/// Waits for this Worker terminal state, removes only its route and closes Store only after the final same-network route terminates.
|
||||
pub(crate) async fn monitor(self) {
|
||||
let terminal = self.handle.wait_terminal().await;
|
||||
@@ -415,7 +464,22 @@ impl crate::RouteRuntimeLaunch {
|
||||
};
|
||||
let mut terminal = self.acknowledgement;
|
||||
terminal.state = terminal_state;
|
||||
let store = self.runtime_state.finish(self.token, terminal);
|
||||
let snapshot = self.handle.snapshot_source().current();
|
||||
let monitoring = crate::project_route_monitoring(&terminal, &snapshot);
|
||||
let monitoring = match monitoring {
|
||||
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||
std::result::Result::Err(error) => {
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_ROUTE_RUNTIME,
|
||||
error_domain = error.code().domain(),
|
||||
error_code = error.code().code(),
|
||||
"Raw Transaction Ingest Desk could not retain terminal route monitoring projection"
|
||||
);
|
||||
std::option::Option::None
|
||||
},
|
||||
};
|
||||
let store = self.runtime_state.finish(self.token, terminal, monitoring);
|
||||
let store = match store {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
@@ -592,6 +656,10 @@ impl RouteRuntimeIdentity {
|
||||
fn matches_dto(&self, value: &crate::RawIngestRouteRuntimeDto) -> bool {
|
||||
return self.profile_id == value.profile_id && self.route_id == value.route_id;
|
||||
}
|
||||
|
||||
fn matches_monitoring(&self, value: &crate::RawIngestRouteMonitoringDto) -> bool {
|
||||
return self.profile_id == value.profile_id && self.route_id == value.route_id;
|
||||
}
|
||||
}
|
||||
|
||||
enum RouteStopTarget {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Tauri runtime assembly for the KSP Raw Transaction Ingest desktop application.
|
||||
|
||||
use tauri::Emitter; // rust-rules: trait-import
|
||||
use tauri::Manager; // rust-rules: trait-import
|
||||
|
||||
/// Runs the Raw Transaction Ingest desktop application.
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
|
||||
@@ -78,6 +81,7 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
|
||||
emit_frontend_log,
|
||||
get_route_foundation,
|
||||
get_route_inventory,
|
||||
get_route_monitoring,
|
||||
get_runtime_status,
|
||||
splash_frontend_ready,
|
||||
start_route,
|
||||
@@ -121,6 +125,17 @@ fn get_route_inventory(state: tauri::State<'_, crate::AppState>) -> std::result:
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_route_monitoring(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<std::vec::Vec<crate::RawIngestRouteMonitoringDto>, crate::CommandErrorDto> {
|
||||
let result = state.route_monitoring();
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(project_command_error("get_route_monitoring", crate::TRACING_DOMAIN_ROUTE_RUNTIME, &error)),
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::ShellStatusDto, crate::CommandErrorDto> {
|
||||
let result = state.shell_status();
|
||||
@@ -145,6 +160,7 @@ async fn splash_frontend_ready(
|
||||
|
||||
#[tauri::command]
|
||||
async fn start_route(
|
||||
app: tauri::AppHandle,
|
||||
request: crate::RawIngestRouteStartRequestDto,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::RawIngestRouteRuntimeDto, crate::CommandErrorDto> {
|
||||
@@ -156,13 +172,60 @@ async fn start_route(
|
||||
},
|
||||
};
|
||||
let acknowledgement = launch.acknowledgement();
|
||||
let monitor = tauri::async_runtime::spawn(async move {
|
||||
let monitoring_identity = launch.monitoring_identity();
|
||||
let monitoring_source = launch.monitoring_source();
|
||||
let monitoring_app = app.clone();
|
||||
let monitoring_task = tauri::async_runtime::spawn(async move {
|
||||
monitor_route_status(monitoring_app, monitoring_identity, monitoring_source).await;
|
||||
});
|
||||
std::mem::drop(monitoring_task);
|
||||
let terminal_task = tauri::async_runtime::spawn(async move {
|
||||
launch.monitor().await;
|
||||
});
|
||||
std::mem::drop(monitor);
|
||||
std::mem::drop(terminal_task);
|
||||
return std::result::Result::Ok(acknowledgement);
|
||||
}
|
||||
|
||||
async fn monitor_route_status(
|
||||
app: tauri::AppHandle,
|
||||
runtime: crate::RawIngestRouteRuntimeDto,
|
||||
source: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshotSource,
|
||||
) {
|
||||
let mut snapshot = source.current();
|
||||
loop {
|
||||
let projected = crate::project_route_monitoring(&runtime, &snapshot);
|
||||
match projected {
|
||||
std::result::Result::Ok(status) => {
|
||||
let main = app.get_webview_window("main");
|
||||
if let std::option::Option::Some(window) = main {
|
||||
let emitted = window.emit(crate::RAW_INGEST_ROUTE_STATUS_EVENT_NAME, status);
|
||||
if emitted.is_err() {
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_ROUTE_RUNTIME,
|
||||
"Raw Transaction Ingest Desk could not emit latest-value route monitoring status"
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
std::result::Result::Err(error) => {
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_ROUTE_RUNTIME,
|
||||
error_domain = error.code().domain(),
|
||||
error_code = error.code().code(),
|
||||
"Raw Transaction Ingest Desk could not project latest-value route monitoring status"
|
||||
);
|
||||
},
|
||||
}
|
||||
if snapshot.worker_snapshot().state().is_terminal() {
|
||||
return;
|
||||
}
|
||||
let observed = snapshot.worker_snapshot().sequence();
|
||||
snapshot = source.wait_for_change(observed).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn stop_route(
|
||||
request: crate::RawIngestRouteStopRequestDto,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/dependency_boundary.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
//! Dependency-boundary canaries for Raw Transaction Ingest Desk Start-resource reconstruction.
|
||||
|
||||
@@ -85,3 +85,14 @@ fn pre_008_route_runtime_owns_real_store_worker_start_stop_without_physical_back
|
||||
assert!(!source.contains(forbidden), "pre.008 crosses physical or secret boundary {forbidden}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_010_monitoring_projects_existing_worker_snapshot_facade_without_new_backend_edges() {
|
||||
let source = read_text(app_root().join("src/route_monitoring.rs").as_path());
|
||||
for required in ["RawTransactionIngestSnapshot", "worker_snapshot()", "source_reconnect_total()", "source_replay_attempt_total()", "gaps()"] {
|
||||
assert!(source.contains(required), "missing Worker snapshot facade marker {required}");
|
||||
}
|
||||
for forbidden in ["tokio_postgres", "ksp_store_postgres_lib", "reqwest", "tonic", "yellowstone_grpc_client"] {
|
||||
assert!(!source.contains(forbidden), "monitoring opens forbidden backend edge {forbidden}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_contract.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
//! Desktop scaffold contract canaries for Raw Transaction Ingest Desk.
|
||||
|
||||
@@ -201,3 +201,19 @@ fn pre_009_start_stop_controls_are_multi_route_targeted_and_backend_owned() {
|
||||
assert!(main.contains(required), "missing pre.009 frontend control marker {required}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_010_runtime_monitoring_is_backend_owned_event_driven_and_resynchronizable() {
|
||||
let tauri = read_text(app_root().join("src/tauri.rs").as_path());
|
||||
for required in [
|
||||
"get_route_monitoring",
|
||||
"monitor_route_status",
|
||||
"window.emit(crate::RAW_INGEST_ROUTE_STATUS_EVENT_NAME, status)",
|
||||
"source.wait_for_change(observed).await",
|
||||
] {
|
||||
assert!(tauri.contains(required), "missing pre.010 monitoring bridge marker {required}");
|
||||
}
|
||||
let state = read_text(app_root().join("src/app_state.rs").as_path());
|
||||
assert!(state.contains("route_monitoring(&self)"));
|
||||
assert!(state.contains("pre.010-runtime-monitoring"));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_security.rs
|
||||
// version: 5
|
||||
// version: 7
|
||||
|
||||
//! Desktop security canaries for the Raw Transaction Ingest Desk scaffold.
|
||||
|
||||
@@ -126,3 +126,37 @@ fn pre_008_runtime_acknowledgement_and_frontend_tracing_remain_secret_and_handle
|
||||
assert!(!main.contains(forbidden), "frontend runtime trace exposes forbidden marker {forbidden}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_010_monitoring_projection_is_source_neutral_payload_free_and_javascript_exact() {
|
||||
let monitoring = read_text(app_root().join("src/route_monitoring.rs").as_path());
|
||||
for required in [
|
||||
"sequence: String",
|
||||
"processing_frontier_slot: std::option::Option<String>",
|
||||
"continuity_frontier_slot: std::option::Option<String>",
|
||||
"fault_domain",
|
||||
"fault_code",
|
||||
"gaps: std::vec::Vec<crate::RawIngestRouteGapDto>",
|
||||
] {
|
||||
assert!(monitoring.contains(required), "missing pre.010 safe monitoring marker {required}");
|
||||
}
|
||||
for forbidden in ["endpoint_url", "connection_uri", "api_key", "source_key", "worker_id", "x-token"] {
|
||||
assert!(!monitoring.contains(forbidden), "monitoring projection exposes forbidden physical marker {forbidden}");
|
||||
}
|
||||
let dto_start = monitoring.find("pub(crate) struct RawIngestRouteMonitoringDto");
|
||||
assert!(dto_start.is_some(), "monitoring DTO declaration is missing");
|
||||
let dto_start = match dto_start {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let dto_end = monitoring[dto_start..].find("/// Projects one complete concrete Worker snapshot");
|
||||
assert!(dto_end.is_some(), "monitoring DTO declaration boundary is missing");
|
||||
let dto_end = match dto_end {
|
||||
std::option::Option::Some(value) => dto_start + value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let dto_surface = &monitoring[dto_start..dto_end];
|
||||
for forbidden in ["raw_transaction", "signature", "payload", "canonical_bytes", "wire_bytes", "transaction_bytes"] {
|
||||
assert!(!dto_surface.contains(forbidden), "monitoring DTO exposes forbidden RAW marker {forbidden}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs
|
||||
// version: 8
|
||||
// version: 9
|
||||
|
||||
//! Release-completeness canaries for Raw Transaction Ingest Desk Start-resource reconstruction.
|
||||
|
||||
@@ -21,7 +21,7 @@ fn read_text(path: &std::path::Path) -> String {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_008_production_module_inventory_adds_only_route_runtime_to_pre_007_surface() {
|
||||
fn pre_010_production_module_inventory_adds_only_route_monitoring_to_pre_009_surface() {
|
||||
let lib = read_text(app_root().join("src/lib.rs").as_path());
|
||||
let expected = [
|
||||
"mod app_state;",
|
||||
@@ -33,6 +33,7 @@ fn pre_008_production_module_inventory_adds_only_route_runtime_to_pre_007_surfac
|
||||
"mod frontend_logging;",
|
||||
"mod logging_runtime;",
|
||||
"mod route_inventory;",
|
||||
"mod route_monitoring;",
|
||||
"mod route_runtime;",
|
||||
"mod route_start;",
|
||||
"mod splash;",
|
||||
@@ -100,7 +101,7 @@ fn pre_009_multi_route_runtime_keeps_independent_workers_on_one_same_network_sto
|
||||
"ERROR_CODE_ROUTE_RUNTIME_NETWORK_MISMATCH",
|
||||
"already owns one Worker for the selected logical route",
|
||||
"shared_store(prepared.network.as_str())",
|
||||
"self.runtime_state.finish(self.token, terminal)",
|
||||
"self.runtime_state.finish(self.token, terminal, monitoring)",
|
||||
"Arc::try_unwrap",
|
||||
] {
|
||||
assert!(runtime.contains(required), "missing pre.009 shared-Store marker {required}");
|
||||
@@ -141,3 +142,28 @@ fn pre_009_yellowstone_uses_block_subscription_and_one_get_block_path_instead_of
|
||||
assert!(workspace.contains("RAW_TRANSACTION_INGEST_YELLOWSTONE_BLOCK_RECONCILIATION_ATTEMPTS"));
|
||||
assert!(workspace.contains("stop_receiver.changed()"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_010_latest_value_monitoring_reuses_worker_snapshot_source_and_tauri_event_bridge() {
|
||||
let monitoring = read_text(app_root().join("src/route_monitoring.rs").as_path());
|
||||
for required in [
|
||||
"RawIngestRouteMonitoringDto",
|
||||
"RawIngestRouteGapDto",
|
||||
"project_route_monitoring",
|
||||
"continuity_frontier_slot",
|
||||
"future_target_coverage",
|
||||
"source_reconnect_total",
|
||||
"source_replay_attempt_total",
|
||||
"repaired_gap_total",
|
||||
"backpressure_wait_total",
|
||||
] {
|
||||
assert!(monitoring.contains(required), "missing pre.010 monitoring marker {required}");
|
||||
}
|
||||
let tauri = read_text(app_root().join("src/tauri.rs").as_path());
|
||||
for required in ["get_route_monitoring", "RAW_INGEST_ROUTE_STATUS_EVENT_NAME", "monitor_route_status", "source.wait_for_change(observed).await"] {
|
||||
assert!(tauri.contains(required), "missing pre.010 event/resync marker {required}");
|
||||
}
|
||||
let runtime = read_text(app_root().join("src/route_runtime.rs").as_path());
|
||||
assert!(runtime.contains("monitoring_statuses"));
|
||||
assert!(runtime.contains("last_monitoring"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_monitoring.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn pre_010_source_gap_and_repair_codes_are_exact_and_source_neutral() {
|
||||
assert_eq!(super::source_state_code(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Active), "active");
|
||||
assert_eq!(super::source_state_code(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSourceState::Reconnecting), "reconnecting");
|
||||
assert_eq!(super::gap_state_code(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapState::Repairing), "repairing");
|
||||
assert_eq!(super::gap_reason_code(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapReason::SourceFailure), "source_failure");
|
||||
assert_eq!(
|
||||
super::repair_method_code(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRepairMethod::TransactionHydration),
|
||||
"transaction_hydration"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_010_slot_projection_uses_decimal_text_for_javascript_safe_exactness() {
|
||||
assert_eq!(super::slot_text(std::option::Option::Some(u64::MAX)), std::option::Option::Some(u64::MAX.to_string()));
|
||||
assert_eq!(super::slot_text(std::option::Option::None), std::option::Option::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_010_bounded_cardinality_projection_rejects_only_unrepresentable_values() {
|
||||
let zero = super::usize_to_u32(0, "zero");
|
||||
assert!(matches!(zero, std::result::Result::Ok(0)));
|
||||
let maximum = usize::try_from(u32::MAX);
|
||||
assert!(maximum.is_ok());
|
||||
if let std::result::Result::Ok(maximum) = maximum {
|
||||
let converted = super::usize_to_u32(maximum, "maximum");
|
||||
assert!(matches!(converted, std::result::Result::Ok(u32::MAX)));
|
||||
}
|
||||
if usize::BITS > u32::BITS {
|
||||
let overflow_value = usize::try_from(u64::from(u32::MAX) + 1);
|
||||
assert!(overflow_value.is_ok());
|
||||
if let std::result::Result::Ok(overflow_value) = overflow_value {
|
||||
let overflow = super::usize_to_u32(overflow_value, "overflow");
|
||||
assert!(overflow.is_err());
|
||||
if let std::result::Result::Err(error) = overflow {
|
||||
assert_eq!(error.code(), crate::ERROR_CODE_APP_STATE_INVALID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_runtime.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
#[test]
|
||||
fn pre_009_multi_route_runtime_starts_without_workers_or_shared_store_material() {
|
||||
@@ -8,6 +8,7 @@ fn pre_009_multi_route_runtime_starts_without_workers_or_shared_store_material()
|
||||
assert!(inner.is_ok());
|
||||
if let std::result::Result::Ok(inner) = inner {
|
||||
assert!(inner.routes.is_empty());
|
||||
assert!(inner.last_monitoring.is_empty());
|
||||
assert!(inner.store.is_none());
|
||||
assert!(inner.network.is_none());
|
||||
assert!(inner.store_opening_token.is_none());
|
||||
|
||||
Reference in New Issue
Block a user