0.3.15-pre.010-fix.001
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 617
|
||||
# version: 619
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-raw-transaction-ingest-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.15-pre.9.fix.3"
|
||||
version = "0.3.15-pre.10.fix.1"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
/// Runtime-neutral boxed future resolving to one newer concrete RAW transaction ingest Worker snapshot.
|
||||
pub type RawTransactionIngestSnapshotFuture<'a> =
|
||||
@@ -533,6 +533,36 @@ impl crate::RawTransactionIngestSnapshot {
|
||||
return &self.worker;
|
||||
}
|
||||
|
||||
/// Reports whether continuity policy has emitted a run-local health projection.
|
||||
#[must_use]
|
||||
pub const fn continuity_policy_observed(&self) -> bool {
|
||||
return self.continuity_policy_observed;
|
||||
}
|
||||
|
||||
/// Returns the latest run-local continuity frontier when continuity policy has observed one.
|
||||
#[must_use]
|
||||
pub const fn continuity_frontier_slot(&self) -> std::option::Option<u64> {
|
||||
return self.continuity_frontier_slot;
|
||||
}
|
||||
|
||||
/// Reports whether the continuity policy currently retains one or more open gaps.
|
||||
#[must_use]
|
||||
pub const fn continuity_has_open_gaps(&self) -> bool {
|
||||
return self.continuity_has_open_gaps;
|
||||
}
|
||||
|
||||
/// Reports whether all failed-source continuity obligations have been reconciled for this run.
|
||||
#[must_use]
|
||||
pub const fn failed_source_losses_reconciled(&self) -> bool {
|
||||
return self.failed_source_losses_reconciled;
|
||||
}
|
||||
|
||||
/// Reports whether current source coverage proves the active target can continue into future slots.
|
||||
#[must_use]
|
||||
pub const fn future_target_coverage(&self) -> bool {
|
||||
return self.future_target_coverage;
|
||||
}
|
||||
|
||||
/// Returns the configured bounded admission queue capacity.
|
||||
#[must_use]
|
||||
pub const fn admission_queue_capacity(&self) -> usize {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
fn snapshot_foundation_with_source_total(
|
||||
source_total: usize,
|
||||
@@ -346,3 +346,24 @@ fn v0_3_14_pre_011_snapshot_carries_checked_gap_observability() {
|
||||
assert_eq!(snapshot.oldest_open_gap_start_slot(), std::option::Option::Some(100));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v0_3_15_pre_010_public_continuity_health_getters_are_source_neutral() {
|
||||
let (mut publisher, source) = match snapshot_foundation_with_source_total(2) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return,
|
||||
};
|
||||
let projection = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(90), std::option::Option::None)
|
||||
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 1, 1, 0)
|
||||
.with_source_counts(2, 2, 0, 0)
|
||||
.with_continuity_health(std::option::Option::Some(90), false, true)
|
||||
.with_failed_source_losses_reconciled(true);
|
||||
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, projection).is_ok());
|
||||
let snapshot = source.current();
|
||||
assert!(snapshot.continuity_policy_observed());
|
||||
assert_eq!(snapshot.continuity_frontier_slot(), std::option::Option::Some(90));
|
||||
assert!(!snapshot.continuity_has_open_gaps());
|
||||
assert!(snapshot.failed_source_losses_reconciled());
|
||||
assert!(snapshot.future_target_coverage());
|
||||
return;
|
||||
}
|
||||
|
||||
113
deltas/0.3.15/pre.010-fix.001.md
Normal file
113
deltas/0.3.15/pre.010-fix.001.md
Normal file
@@ -0,0 +1,113 @@
|
||||
<!-- file: deltas/0.3.15/pre.010-fix.001.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.3.15-pre.010-fix.001`
|
||||
|
||||
## Base
|
||||
|
||||
```text
|
||||
base archive : ksp-general-0.3.15-pre.010.zip
|
||||
base SHA-256 : c7365dad72c306c4781e351c7b4f1111ebcdce0ed34946b158f332d6b1523fa5
|
||||
base version : 0.3.15-pre.10
|
||||
```
|
||||
|
||||
## Objectif
|
||||
|
||||
Corriger le faux positif du canari Desktop Security introduit par `pre.010` sans modifier le contrat de monitoring, le runtime, l'acquisition ni l'IPC.
|
||||
|
||||
Le test `pre_010_monitoring_projection_is_source_neutral_payload_free_and_javascript_exact` interdisait la sous-chaîne générique `raw_transaction` dans l'ensemble du fichier `route_monitoring.rs`. Cette chaîne est légitime dans le nom de crate `ksp_worker_raw_transaction_ingest_lib` et dans les chemins d'export TypeScript de l'application ; sa présence ne prouve aucune exposition de payload RAW.
|
||||
|
||||
Le correctif borne le scan RAW à la déclaration de `RawIngestRouteMonitoringDto`, c'est-à-dire à la surface effectivement sérialisable vers le frontend. Dans cette surface, les marqueurs suivants restent interdits :
|
||||
|
||||
```text
|
||||
raw_transaction
|
||||
signature
|
||||
payload
|
||||
canonical_bytes
|
||||
wire_bytes
|
||||
transaction_bytes
|
||||
```
|
||||
|
||||
Les interdictions physiques/secrets restent en parallèle appliquées à l'ensemble de `route_monitoring.rs` :
|
||||
|
||||
```text
|
||||
endpoint_url
|
||||
connection_uri
|
||||
api_key
|
||||
source_key
|
||||
worker_id
|
||||
x-token
|
||||
```
|
||||
|
||||
Aucune structure DTO, aucun getter Worker, aucun événement Tauri, aucune commande IPC et aucune logique runtime n'est modifié.
|
||||
|
||||
## Version
|
||||
|
||||
```text
|
||||
workspace.package.version : 0.3.15-pre.10.fix.1
|
||||
root Cargo header counter : 619
|
||||
```
|
||||
|
||||
Le bump est requis car le correctif modifie un test Rust participant au code/build de validation de la prerelease.
|
||||
|
||||
## Fichiers ajoutés
|
||||
|
||||
```text
|
||||
deltas/0.3.15/pre.010-fix.001.md
|
||||
```
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_security.rs
|
||||
docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md
|
||||
```
|
||||
|
||||
## Fichiers supprimés
|
||||
|
||||
Aucun.
|
||||
|
||||
## Validations exécutées dans l'environnement d'assemblage
|
||||
|
||||
```text
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.15
|
||||
```
|
||||
|
||||
Des canaris statiques complémentaires vérifient que :
|
||||
|
||||
```text
|
||||
workspace.package.version vaut 0.3.15-pre.10.fix.1
|
||||
le scan interdit encore les marqueurs endpoint/secret/handle
|
||||
le scan RAW est borné à la déclaration sérialisable RawIngestRouteMonitoringDto
|
||||
la chaîne raw_transaction reste interdite dans cette surface DTO mais n'est plus confondue avec les noms de crate/bindings
|
||||
aucun fichier runtime de pre.010 n'est modifié par ce fix
|
||||
```
|
||||
|
||||
## Validations non exécutées dans l'environnement d'assemblage
|
||||
|
||||
Cargo/rustfmt ne sont pas disponibles dans cet environnement ; les gates suivants ne sont donc pas déclarés PASS ici :
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo fmt --all -- --check
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
cargo test -p ksp-app-raw-transaction-ingest-desk --all-targets --all-features
|
||||
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
|
||||
cargo test --workspace --all-targets --all-features
|
||||
(cd crates/ksp-app-raw-transaction-ingest-desk && cargo tauri dev)
|
||||
```
|
||||
|
||||
Aucun `npm run build` manuel n'est requis ; le frontend reste géré par le workflow Tauri/Vite du projet.
|
||||
|
||||
## Décisions
|
||||
|
||||
- Conserver le contrat monitoring `pre.010` inchangé : le gate n'a détecté aucune fuite DTO/runtime, seulement un scan textuel trop large.
|
||||
- Borner le scan RAW à la déclaration DTO sérialisable et conserver un scan fichier complet séparé pour les marqueurs physiques/secrets.
|
||||
- Ne pas toucher README, USAGE, plan, Worker ni runtime dans ce correctif ciblé.
|
||||
|
||||
## Questions ouvertes
|
||||
|
||||
Aucune pour ce correctif. La poursuite fonctionnelle reste `pre.011` après validation opérateur de `pre.010-fix.001`.
|
||||
153
deltas/0.3.15/pre.010.md
Normal file
153
deltas/0.3.15/pre.010.md
Normal file
@@ -0,0 +1,153 @@
|
||||
<!-- file: deltas/0.3.15/pre.010.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Delta `0.3.15-pre.010`
|
||||
|
||||
## Base
|
||||
|
||||
```text
|
||||
base archive : ksp-general-0.3.15-pre.009-fix.003.zip
|
||||
base SHA-256 : 7882f39d8ac213712f81b4dbef1681e4b63064e1ebdf0b8515e343cf3d2094ef
|
||||
base version : 0.3.15-pre.9.fix.3
|
||||
```
|
||||
|
||||
Le gate opérateur de la base confirme les audits Rust/Markdown, `cargo check --workspace`, Clippy strict, les tests Worker Raw Transaction Ingest, `workspace_logging`, le workspace all-targets/all-features et le lancement Tauri. Le correctif `pre.009-fix.003` supprime le fault synthétique `source.admission_closed` observé lors du Stop coopératif des routes Mainnet Yellowstone et HTTP Block Polling.
|
||||
|
||||
## Objectif
|
||||
|
||||
Matérialiser l'observabilité runtime prévue pour la Desk sans modifier l'acquisition : relayer les snapshots latest-value de chaque Worker indépendant vers un contrat IPC sûr couvrant lifecycle, health, activity, admission/persistence, backpressure, reconnect/replay, continuité, gaps et repair.
|
||||
|
||||
## Projection latest-value sûre
|
||||
|
||||
Le nouveau module `route_monitoring` consomme exclusivement `RawTransactionIngestSnapshotSource` et `RawTransactionIngestSnapshot`. Il construit deux DTO frontend-safe :
|
||||
|
||||
```text
|
||||
RawIngestRouteMonitoringDto
|
||||
RawIngestRouteGapDto
|
||||
```
|
||||
|
||||
La projection comprend notamment :
|
||||
|
||||
```text
|
||||
lifecycle / terminal / fault code stable
|
||||
health / activity
|
||||
admission queue / persistence concurrency
|
||||
admitted / canonicalized / persisted
|
||||
entity / observation outcomes
|
||||
store/source failures
|
||||
backpressure waits
|
||||
hydration pending / processing frontier
|
||||
continuity policy / frontier / open-gap state / future coverage
|
||||
source active/reconnecting/failed
|
||||
reconnect / replay-attempt / replay-retention-gap counters
|
||||
gap details bornés
|
||||
repair totals par mécanisme
|
||||
```
|
||||
|
||||
Aucune URL, credential, provider physique, source key, `WorkerId`, handle, URI Store, signature ou payload `RawTransaction` n'est projeté.
|
||||
|
||||
Les séquences, compteurs et slots issus de `u64` traversent IPC sous forme de texte décimal afin d'éviter toute perte de précision JavaScript. Les cardinalités bornées issues de `usize` sont converties en `u32` avec échec fermé lorsqu'elles ne sont pas représentables.
|
||||
|
||||
## Relais Tauri et resynchronisation
|
||||
|
||||
Chaque Start conserve le monitor terminal/cleanup existant et ajoute un monitor latest-value indépendant :
|
||||
|
||||
```text
|
||||
Worker handle
|
||||
-> RawTransactionIngestSnapshotSource clone
|
||||
-> projection sûre
|
||||
-> event Tauri ksp-raw-ingest-route-status
|
||||
```
|
||||
|
||||
Le monitor d'observabilité ne possède ni Store ni lifecycle. Une erreur d'émission UI n'arrête pas le Worker et ne modifie pas le cleanup terminal.
|
||||
|
||||
La commande :
|
||||
|
||||
```text
|
||||
get_route_monitoring
|
||||
```
|
||||
|
||||
retourne les latest values des routes actives ainsi que les derniers terminaux sûrs retenus dans la session runtime same-network. Un nouveau Start de la même identité remplace son ancien terminal ; une nouvelle session réseau repart sans terminaux hérités.
|
||||
|
||||
Cette commande permet une resynchronisation explicite même si le frontend a manqué un événement intermédiaire.
|
||||
|
||||
## Extension minimale du snapshot Worker
|
||||
|
||||
Le snapshot concret portait déjà les cinq informations de continuité nécessaires au mapping UI mais ne les exposait pas par getters publics. `pre.010` ouvre uniquement :
|
||||
|
||||
```text
|
||||
continuity_policy_observed()
|
||||
continuity_frontier_slot()
|
||||
continuity_has_open_gaps()
|
||||
failed_source_losses_reconciled()
|
||||
future_target_coverage()
|
||||
```
|
||||
|
||||
Aucun nouveau type Worker, aucune logique de retry/reconnect/replay/repair, aucune persistence et aucune dépendance ne sont ajoutés.
|
||||
|
||||
## Frontend
|
||||
|
||||
La tranche fournit le contrat backend/event/resync mais ne finalise pas encore l'affichage détaillé de supervision. La consommation UI des métriques, le refresh dédié et le tracing TypeScript associé restent `pre.011`.
|
||||
|
||||
## Version
|
||||
|
||||
```text
|
||||
workspace.package.version : 0.3.15-pre.10
|
||||
root Cargo header counter : 618
|
||||
```
|
||||
|
||||
## Validation d'assemblage
|
||||
|
||||
L'environnement d'assemblage ne fournit ni Cargo ni rustfmt ; aucun résultat Cargo local n'est donc revendiqué.
|
||||
|
||||
Le gate opérateur attendu est :
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo fmt --all -- --check
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.15
|
||||
cargo check --workspace
|
||||
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
cargo test -p ksp-app-raw-transaction-ingest-desk --all-targets --all-features
|
||||
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
|
||||
cargo test --workspace --all-targets --all-features
|
||||
(cd crates/ksp-app-raw-transaction-ingest-desk && cargo tauri dev)
|
||||
```
|
||||
|
||||
Aucun script npm applicatif n'est exécuté directement par l'opérateur.
|
||||
|
||||
## Inventaire exact du delta
|
||||
|
||||
Le delta contient `3` ajouts, `16` modifications et aucune suppression, soit `19` entrées.
|
||||
|
||||
Ajouts :
|
||||
|
||||
```text
|
||||
crates/ksp-app-raw-transaction-ingest-desk/src/route_monitoring.rs
|
||||
crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_monitoring.rs
|
||||
deltas/0.3.15/pre.010.md
|
||||
```
|
||||
|
||||
Modifications :
|
||||
|
||||
```text
|
||||
Cargo.toml
|
||||
crates/ksp-app-raw-transaction-ingest-desk/README.md
|
||||
crates/ksp-app-raw-transaction-ingest-desk/USAGE.md
|
||||
crates/ksp-app-raw-transaction-ingest-desk/src/app_state.rs
|
||||
crates/ksp-app-raw-transaction-ingest-desk/src/lib.rs
|
||||
crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs
|
||||
crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
|
||||
crates/ksp-app-raw-transaction-ingest-desk/tests/dependency_boundary.rs
|
||||
crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_contract.rs
|
||||
crates/ksp-app-raw-transaction-ingest-desk/tests/desktop_security.rs
|
||||
crates/ksp-app-raw-transaction-ingest-desk/tests/release_completeness.rs
|
||||
crates/ksp-app-raw-transaction-ingest-desk/unit_tests/route_runtime.rs
|
||||
crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
|
||||
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs
|
||||
docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md
|
||||
docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md
|
||||
```
|
||||
|
||||
Suppressions : aucune.
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md -->
|
||||
<!-- version: 16 -->
|
||||
<!-- version: 17 -->
|
||||
|
||||
# Plan v0.3.15 — Raw Transaction Ingest Desk
|
||||
|
||||
@@ -811,7 +811,13 @@ Budget cible : une tranche runtime/throughput Mainnet + multi-route.
|
||||
|
||||
### `pre.010` — snapshots et monitoring runtime
|
||||
|
||||
Relayer les snapshots/events sûrs nécessaires à la Desk : lifecycle, health, activity, backpressure, reconnect, replay, gaps et repair, sans payload RawTransaction ni identité provider sensible.
|
||||
**État : implémenté.** Chaque route active fournit désormais une source latest-value indépendante dérivée directement du `RawTransactionIngestSnapshotSource` de son Worker. Le backend projette lifecycle, health, activity, admission/persistence, backpressure, hydration pending, processing frontier, source counts, reconnect/replay, continuité, gaps et repair sans exposer `WorkerId`, source key, endpoint, provider physique, URI Store, signature ni payload `RawTransaction`.
|
||||
|
||||
Le relais Tauri suit le pattern KSP déjà utilisé par Backfill Desk : l'événement `ksp-raw-ingest-route-status` transporte une projection complète à chaque latest value, tandis que `get_route_monitoring` fournit une resynchronisation explicite des Workers actifs et des derniers terminaux retenus dans la session same-network. Le monitor d'événements est séparé du monitor de cleanup Store/Worker : perdre un événement UI ne modifie jamais le lifecycle ni la fermeture du Store.
|
||||
|
||||
Les compteurs, séquences et slots issus de `u64` sont sérialisés en texte décimal pour éviter toute perte de précision JavaScript. Les cardinalités bornées `usize` sont converties en `u32` avec échec fermé si elles deviennent non représentables. Le Worker ouvre uniquement cinq getters publics source-neutral déjà portés par son snapshot (`continuity_policy_observed`, `continuity_frontier_slot`, `continuity_has_open_gaps`, `failed_source_losses_reconciled`, `future_target_coverage`) ; aucune logique d'acquisition, retry, repair ou persistence n'est déplacée.
|
||||
|
||||
Le frontend détaillé ne consomme pas encore cette surface pour construire la vue de supervision complète : ce rendu et son tracing TypeScript restent `pre.011`.
|
||||
|
||||
Budget cible : une tranche observabilité.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md -->
|
||||
<!-- version: 17 -->
|
||||
<!-- version: 19 -->
|
||||
|
||||
# Validation v0.3.15 — Raw Transaction Ingest Desk
|
||||
|
||||
@@ -431,7 +431,13 @@ Le profil `publicnode_mainnet` contient deux endpoints HTTP de même rôle : Pub
|
||||
|
||||
### `pre.010` — monitoring
|
||||
|
||||
Snapshots/events/monitoring lifecycle-health-activity-backpressure-reconnect-replay-gaps-repair.
|
||||
Implémenté dans l'assemblage de tranche. Le nouveau module `route_monitoring` projette les snapshots Worker existants vers un DTO latest-value source-neutral comprenant lifecycle, health, activity, admission/persistence, backpressure, hydration pending, processing frontier, continuité, source counts, reconnect/replay, gaps et repair. Le Worker expose uniquement les cinq getters de continuité qui manquaient à la façade publique de son snapshot ; aucun type ni comportement d'acquisition n'est ajouté.
|
||||
|
||||
Le Tauri backend démarre un monitor de snapshot indépendant pour chaque Worker lancé et émet `ksp-raw-ingest-route-status` vers la fenêtre principale. La commande `get_route_monitoring` permet une resynchronisation complète sans dépendre de la livraison de tous les événements intermédiaires. Le runtime conserve le dernier snapshot terminal sûr de chaque route jusqu'à un nouveau Start de la même identité ou au changement de session réseau.
|
||||
|
||||
Sécurité attendue : aucune URL, credential, provider physique, source key, `WorkerId`, handle, URI Store, signature ou payload RAW dans le DTO. Les `u64` traversant IPC sont encodés en texte décimal ; les cardinalités bornées sont projetées en `u32`. Le monitor d'observabilité ne possède ni Store ni lifecycle et ne peut pas arrêter/redémarrer un Worker.
|
||||
|
||||
La consommation UI détaillée de ces snapshots et le tracing TypeScript associé restent `pre.011`.
|
||||
|
||||
### `pre.011` — frontend
|
||||
|
||||
@@ -647,3 +653,31 @@ La cause Mainnet est une race interne commune : le superviseur Worker ferme le r
|
||||
|
||||
Validation attendue après `fix.003` : le drain coopératif ne ferme plus l’admission au début ; il consomme la file jusqu’à destruction naturelle des senders après propagation du Stop. Le hard-close reste dans le chemin `shutdown_drain_timeout`. Un test unitaire force cette fenêtre de propagation et exige `Stopped` sans source failure.
|
||||
|
||||
### `pre.010-fix.001` — faux positif du canari de sécurité monitoring
|
||||
|
||||
Le gate opérateur de `0.3.15-pre.010` confirme que la tranche compile et que son comportement runtime Mainnet attendu est opérationnel, mais détecte un unique échec déterministe dans le canari Desktop Security :
|
||||
|
||||
```text
|
||||
pre_010_monitoring_projection_is_source_neutral_payload_free_and_javascript_exact
|
||||
FAIL : monitoring projection exposes forbidden marker raw_transaction
|
||||
```
|
||||
|
||||
Le marqueur est trop large : `route_monitoring.rs` doit nécessairement référencer `ksp_worker_raw_transaction_ingest_lib` et les chemins d'export TypeScript propres à `ksp-app-raw-transaction-ingest-desk`. Ces identifiants contiennent la chaîne `raw_transaction` sans exposer aucune donnée RAW.
|
||||
|
||||
Le gate opérateur confirme en parallèle :
|
||||
|
||||
```text
|
||||
audit Rust workspace PASS
|
||||
audit Markdown PASS (318 tables / 212 fichiers)
|
||||
cargo check --workspace PASS
|
||||
Clippy workspace strict -D warnings PASS
|
||||
tests Worker PASS : 160 unitaires + suites d'intégration
|
||||
Raw Transaction Ingest Desk 1 seul échec : canari desktop_security ci-dessus
|
||||
workspace all-targets/all-features même échec déterministe
|
||||
cargo tauri dev lancé et application opérationnelle
|
||||
```
|
||||
|
||||
Le live Mainnet exécuté pendant ce gate confirme aussi que `yellowstone-hydrated` et `http-block-polling` peuvent fonctionner simultanément sur le Store partagé, puis terminer toutes deux en `Stopped` après Stop ciblé. Aucun fault `source.admission_closed` n'est réapparu.
|
||||
|
||||
Le correctif ne modifie pas la projection runtime. Il sépare le canari en deux niveaux : les marqueurs physiques/secrets (`endpoint_url`, `connection_uri`, `api_key`, `source_key`, `worker_id`, `x-token`) restent interdits dans tout `route_monitoring.rs`, tandis que les marqueurs RAW (`raw_transaction`, `signature`, `payload`, `canonical_bytes`, `wire_bytes`, `transaction_bytes`) sont interdits dans la seule surface déclarative de `RawIngestRouteMonitoringDto`. Le test vérifie explicitement les bornes de cette déclaration. Il reste donc strict sur l'IPC sans confondre les noms normaux des crates/bindings avec des champs réellement projetés.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user