v0.3.15-pre.006
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/app_state.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Shared backend state owned by the Raw Transaction Ingest Desk Tauri application.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
pub(crate) struct AppState {
|
||||
active_composite_profile: std::option::Option<String>,
|
||||
config_management: ksp_config_lib::ConfigManagement,
|
||||
inventory_generation: std::sync::Mutex<u32>,
|
||||
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
|
||||
splash_settings: crate::SplashSettings,
|
||||
splash_sequence_started: std::sync::atomic::AtomicBool,
|
||||
@@ -47,6 +48,7 @@ impl crate::AppState {
|
||||
return std::result::Result::Ok(Self {
|
||||
active_composite_profile,
|
||||
config_management,
|
||||
inventory_generation: std::sync::Mutex::new(0),
|
||||
logging_runtime: std::sync::Mutex::new(LoggingRuntimeState {
|
||||
guard: logging_startup.guard,
|
||||
active_profile_id: logging_startup.active_profile_id,
|
||||
@@ -65,6 +67,37 @@ impl crate::AppState {
|
||||
return crate::RawIngestRouteFoundationDto::scaffold();
|
||||
}
|
||||
|
||||
/// Rebuilds the complete Config-only route inventory and publishes a fresh monotonic generation.
|
||||
pub(crate) fn route_inventory(&self) -> ksp_core_lib::Result<crate::RawIngestRouteInventoryDto> {
|
||||
let generation = self.inventory_generation.lock();
|
||||
let mut generation = match generation {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_APP_STATE_LOCK_FAILED,
|
||||
"Raw Transaction Ingest Desk route inventory generation lock is poisoned",
|
||||
));
|
||||
},
|
||||
};
|
||||
let next_generation = generation.checked_add(1);
|
||||
let next_generation = match next_generation {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_ROUTE_INVENTORY_INVALID,
|
||||
"Raw Transaction Ingest Desk route inventory generation is exhausted",
|
||||
));
|
||||
},
|
||||
};
|
||||
let inventory = crate::build_route_inventory(&self.config_management, next_generation);
|
||||
let inventory = match inventory {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
*generation = next_generation;
|
||||
return std::result::Result::Ok(inventory);
|
||||
}
|
||||
|
||||
/// Builds the safe scaffold status exposed by the shell.
|
||||
pub(crate) fn shell_status(&self) -> ksp_core_lib::Result<crate::ShellStatusDto> {
|
||||
let document_count = self.config_management.engine().registry().descriptors().count();
|
||||
@@ -98,7 +131,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.005-scaffold".to_owned(),
|
||||
shell_phase: "pre.006-route-inventory".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/bootstrap.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Config composite and Logging bootstrap for Raw Transaction Ingest Desk.
|
||||
|
||||
@@ -46,13 +46,21 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
|
||||
/// Loads the concrete Raw Transaction Ingest Desk Config composite using its registered logical `file_id` and autonomous default profile.
|
||||
pub(crate) fn load_raw_transaction_ingest_desk_composite(
|
||||
management: &ksp_config_lib::ConfigManagement,
|
||||
) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigComposite> {
|
||||
return crate::load_raw_transaction_ingest_desk_composite_profile(management, std::option::Option::None);
|
||||
}
|
||||
|
||||
/// Loads one explicit or default Raw Transaction Ingest Desk composite profile by registered logical file identifier.
|
||||
pub(crate) fn load_raw_transaction_ingest_desk_composite_profile(
|
||||
management: &ksp_config_lib::ConfigManagement,
|
||||
requested_profile: std::option::Option<&str>,
|
||||
) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigComposite> {
|
||||
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_RAW_TRANSACTION_INGEST_DESK);
|
||||
let file_id = match file_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return management.engine().load_resolved_composite(&file_id, std::option::Option::None);
|
||||
return management.engine().load_resolved_composite(&file_id, requested_profile);
|
||||
}
|
||||
|
||||
/// Returns one required standard profile from the Raw Transaction Ingest Desk composite after validating the referenced logical `file_id`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/constants.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Application-owned tracing targets and Config component identifiers.
|
||||
|
||||
@@ -13,6 +13,8 @@ pub(crate) const COMPOSITE_COMPONENT_ID_TRANSPORT: &str = "transport";
|
||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "raw_transaction_ingest.bootstrap";
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||
/// Structured domain used while rebuilding Config-only route inventory.
|
||||
pub(crate) const TRACING_DOMAIN_INVENTORY: &str = "raw_transaction_ingest.inventory";
|
||||
/// Structured domain used by the Raw Transaction Ingest Desk shell.
|
||||
pub(crate) const TRACING_DOMAIN_SHELL: &str = "raw_transaction_ingest.shell";
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/dto_route.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Safe route-identity and lifecycle-state foundation for Raw Transaction Ingest Desk.
|
||||
//! Safe route-identity, Config-composability and lifecycle DTOs for Raw Transaction Ingest Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
@@ -28,6 +28,72 @@ impl crate::RawIngestRouteId {
|
||||
pub(crate) fn all() -> std::vec::Vec<Self> {
|
||||
return vec![Self::YellowstoneHydrated, Self::StandardLogsHydrated, Self::StandardBlockDirect, Self::HeliusTransactionHydrated, Self::HttpBlockPolling];
|
||||
}
|
||||
|
||||
/// Returns the application-owned route family used by safe UI projections.
|
||||
#[must_use]
|
||||
pub(crate) const fn family(self) -> crate::RawIngestRouteFamily {
|
||||
return match self {
|
||||
Self::YellowstoneHydrated => crate::RawIngestRouteFamily::Yellowstone,
|
||||
Self::StandardLogsHydrated => crate::RawIngestRouteFamily::StandardLogs,
|
||||
Self::StandardBlockDirect => crate::RawIngestRouteFamily::StandardBlock,
|
||||
Self::HeliusTransactionHydrated => crate::RawIngestRouteFamily::HeliusTransaction,
|
||||
Self::HttpBlockPolling => crate::RawIngestRouteFamily::HttpBlockPolling,
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns the stable operator-facing route label without exposing physical endpoint metadata.
|
||||
#[must_use]
|
||||
pub(crate) const fn label(self) -> &'static str {
|
||||
return match self {
|
||||
Self::YellowstoneHydrated => "Yellowstone + HTTP hydration",
|
||||
Self::StandardLogsHydrated => "Standard Logs + HTTP hydration",
|
||||
Self::StandardBlockDirect => "Standard Block direct",
|
||||
Self::HeliusTransactionHydrated => "Helius Transaction + HTTP hydration",
|
||||
Self::HttpBlockPolling => "HTTP Block Polling",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Stable application-owned route families used only for safe presentation and orchestration.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteFamily.ts")]
|
||||
pub(crate) enum RawIngestRouteFamily {
|
||||
/// Yellowstone gRPC subscription route.
|
||||
Yellowstone,
|
||||
/// Standard Solana logs WebSocket route.
|
||||
StandardLogs,
|
||||
/// Standard Solana block WebSocket route.
|
||||
StandardBlock,
|
||||
/// Helius transaction WebSocket route.
|
||||
HeliusTransaction,
|
||||
/// HTTP block polling route.
|
||||
HttpBlockPolling,
|
||||
}
|
||||
|
||||
/// Safe reasons explaining why one logical route cannot currently be composed from Config.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteUnavailableReason.ts")]
|
||||
pub(crate) enum RawIngestRouteUnavailableReason {
|
||||
/// The selected composite or one of its typed Config components could not be resolved safely.
|
||||
ProfileUnresolved,
|
||||
/// Transport and Store do not prove one identical logical network.
|
||||
NetworkMismatch,
|
||||
/// No HTTP role can satisfy `getTransaction` for same-network hydration.
|
||||
MissingHttpGetTransaction,
|
||||
/// No HTTP role can satisfy the complete block-polling method set.
|
||||
MissingHttpBlockScan,
|
||||
/// No enabled standard WebSocket endpoint explicitly declares `Logs`.
|
||||
MissingWsLogsCapability,
|
||||
/// No enabled standard WebSocket endpoint explicitly declares `Block`.
|
||||
MissingWsBlockCapability,
|
||||
/// No enabled Helius WebSocket endpoint explicitly declares `HeliusTransaction`.
|
||||
MissingHeliusTransactionCapability,
|
||||
/// No enabled same-network Yellowstone gRPC endpoint is configured.
|
||||
MissingYellowstoneGrpc,
|
||||
/// A required Config secret is not resolved for the selected composite profile.
|
||||
MissingRequiredSecret,
|
||||
}
|
||||
|
||||
/// Stable Raw Transaction Ingest Desk route lifecycle states.
|
||||
@@ -59,25 +125,104 @@ impl crate::RawIngestRouteState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Static route/state foundation exposed by the scaffold before Config route inventory exists.
|
||||
/// Static route/state foundation retained as the bounded vocabulary behind the live Config inventory.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteFoundationDto.ts")]
|
||||
pub(crate) struct RawIngestRouteFoundationDto {
|
||||
/// Five stable V1 logical route identifiers; this is not a composability claim.
|
||||
pub(crate) route_ids: std::vec::Vec<crate::RawIngestRouteId>,
|
||||
/// Stable lifecycle state vocabulary reserved for later runtime projections.
|
||||
/// Stable lifecycle state vocabulary reserved for runtime projections.
|
||||
pub(crate) states: std::vec::Vec<crate::RawIngestRouteState>,
|
||||
}
|
||||
|
||||
impl crate::RawIngestRouteFoundationDto {
|
||||
/// Builds the static scaffold contract without resolving endpoints, secrets or Worker resources.
|
||||
/// Builds the bounded route/state vocabulary without resolving endpoints, secrets or Worker resources.
|
||||
#[must_use]
|
||||
pub(crate) fn scaffold() -> Self {
|
||||
return Self { route_ids: crate::RawIngestRouteId::all(), states: crate::RawIngestRouteState::all() };
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe Config-only composability projection for one logical route.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteDto.ts")]
|
||||
pub(crate) struct RawIngestRouteDto {
|
||||
/// Stable route family used for presentation and bounded orchestration.
|
||||
pub(crate) family: crate::RawIngestRouteFamily,
|
||||
/// Stable operator-facing label with no physical resource identity.
|
||||
pub(crate) label: String,
|
||||
/// Logical network proven by Config when available.
|
||||
pub(crate) network: std::option::Option<String>,
|
||||
/// Safe reason explaining an unavailable route.
|
||||
pub(crate) reason: std::option::Option<crate::RawIngestRouteUnavailableReason>,
|
||||
/// Stable logical V1 route identifier.
|
||||
pub(crate) route_id: crate::RawIngestRouteId,
|
||||
/// Whether all Config requirements for the route are currently composable.
|
||||
pub(crate) selectable: bool,
|
||||
/// Config-level lifecycle projection; `Configured` is not a runtime health claim.
|
||||
pub(crate) state: crate::RawIngestRouteState,
|
||||
}
|
||||
|
||||
impl crate::RawIngestRouteDto {
|
||||
/// Builds one route that is composable from Config without claiming runtime operability.
|
||||
#[must_use]
|
||||
pub(crate) fn configured(route_id: crate::RawIngestRouteId, network: &str) -> Self {
|
||||
return Self {
|
||||
family: route_id.family(),
|
||||
label: route_id.label().to_owned(),
|
||||
network: std::option::Option::Some(network.to_owned()),
|
||||
reason: std::option::Option::None,
|
||||
route_id,
|
||||
selectable: true,
|
||||
state: crate::RawIngestRouteState::Configured,
|
||||
};
|
||||
}
|
||||
|
||||
/// Builds one route that is not composable from the current Config profile.
|
||||
#[must_use]
|
||||
pub(crate) fn unavailable(route_id: crate::RawIngestRouteId, network: std::option::Option<&str>, reason: crate::RawIngestRouteUnavailableReason) -> Self {
|
||||
return Self {
|
||||
family: route_id.family(),
|
||||
label: route_id.label().to_owned(),
|
||||
network: network.map(str::to_owned),
|
||||
reason: std::option::Option::Some(reason),
|
||||
route_id,
|
||||
selectable: false,
|
||||
state: crate::RawIngestRouteState::Unavailable,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe route inventory for one logical Raw Transaction Ingest Desk composite profile.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestProfileInventoryDto.ts")]
|
||||
pub(crate) struct RawIngestProfileInventoryDto {
|
||||
/// Whether this profile is the composite's autonomous default.
|
||||
pub(crate) is_default: bool,
|
||||
/// Logical network proven from the resolved Config profile when available.
|
||||
pub(crate) network: std::option::Option<String>,
|
||||
/// Stable composite profile identifier.
|
||||
pub(crate) profile_id: String,
|
||||
/// Five logical routes in stable V1 presentation order.
|
||||
pub(crate) routes: std::vec::Vec<crate::RawIngestRouteDto>,
|
||||
}
|
||||
|
||||
/// Deterministic Config-only route inventory returned to the frontend.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteInventoryDto.ts")]
|
||||
pub(crate) struct RawIngestRouteInventoryDto {
|
||||
/// Composite default profile identifier.
|
||||
pub(crate) default_profile: String,
|
||||
/// Monotonic application-owned generation incremented after each successful inventory rebuild.
|
||||
pub(crate) generation: u32,
|
||||
/// Safe inventories for every validated composite profile in source order.
|
||||
pub(crate) profiles: std::vec::Vec<crate::RawIngestProfileInventoryDto>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/dto_route.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/errors.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Stable Raw Transaction Ingest Desk application error codes.
|
||||
|
||||
@@ -20,6 +20,9 @@ pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode
|
||||
/// Raw Transaction Ingest Desk could not install managed Logging or its safe fallback.
|
||||
pub(crate) const ERROR_CODE_LOGGING_BOOTSTRAP_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "logging_bootstrap_failed");
|
||||
/// Config-only route inventory cannot be projected safely from the validated composite.
|
||||
pub(crate) const ERROR_CODE_ROUTE_INVENTORY_INVALID: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_inventory_invalid");
|
||||
/// Splash readiness was invoked from a window other than the splash window.
|
||||
pub(crate) const ERROR_CODE_SPLASH_ORIGIN_INVALID: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "splash_origin_invalid");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/lib.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Tauri desktop application scaffold for composing and supervising KSP live RAW transaction ingest routes.
|
||||
|
||||
@@ -15,6 +15,7 @@ mod dto_route;
|
||||
mod errors;
|
||||
mod frontend_logging;
|
||||
mod logging_runtime;
|
||||
mod route_inventory;
|
||||
mod splash;
|
||||
mod tauri;
|
||||
mod tw_main;
|
||||
@@ -33,6 +34,8 @@ pub(crate) use self::bootstrap::config_management;
|
||||
pub(crate) use self::bootstrap::initialize_logging;
|
||||
/// Loads the Raw Transaction Ingest Desk composite selected by its registered logical file identifier.
|
||||
pub(crate) use self::bootstrap::load_raw_transaction_ingest_desk_composite;
|
||||
/// Loads one explicit or default Raw Transaction Ingest Desk composite profile.
|
||||
pub(crate) use self::bootstrap::load_raw_transaction_ingest_desk_composite_profile;
|
||||
/// Resolves one required standard Config profile from the Raw Transaction Ingest Desk composite.
|
||||
pub(crate) use self::bootstrap::required_composite_component_profile;
|
||||
/// Composite-local identifier for the standard Logging component.
|
||||
@@ -45,6 +48,8 @@ pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_TRANSPORT;
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||
/// Structured domain used while rebuilding Config-only route inventory.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_INVENTORY;
|
||||
/// Structured domain used by the Raw Transaction Ingest Desk shell.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
@@ -61,12 +66,22 @@ pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
|
||||
pub(crate) use self::dto_common::CommandErrorDto;
|
||||
/// Safe scaffold/runtime snapshot exposed to the shell.
|
||||
pub(crate) use self::dto_common::ShellStatusDto;
|
||||
/// Static route/state foundation exposed before route inventory is implemented.
|
||||
/// Safe route inventory for one logical composite profile.
|
||||
pub(crate) use self::dto_route::RawIngestProfileInventoryDto;
|
||||
/// Safe Config-only composability projection for one logical route.
|
||||
pub(crate) use self::dto_route::RawIngestRouteDto;
|
||||
/// Stable application-owned route family.
|
||||
pub(crate) use self::dto_route::RawIngestRouteFamily;
|
||||
/// Static route/state foundation retained as the bounded vocabulary behind the Config inventory.
|
||||
pub(crate) use self::dto_route::RawIngestRouteFoundationDto;
|
||||
/// Stable logical V1 route identifiers owned by the Desk.
|
||||
pub(crate) use self::dto_route::RawIngestRouteId;
|
||||
/// Stable route lifecycle states reserved for later runtime projections.
|
||||
/// Complete deterministic Config-only route inventory.
|
||||
pub(crate) use self::dto_route::RawIngestRouteInventoryDto;
|
||||
/// Stable route lifecycle states reserved for runtime projections.
|
||||
pub(crate) use self::dto_route::RawIngestRouteState;
|
||||
/// Safe reason explaining why one logical route is not composable from Config.
|
||||
pub(crate) use self::dto_route::RawIngestRouteUnavailableReason;
|
||||
/// Shared Raw Transaction Ingest Desk application state is internally inconsistent.
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
||||
/// Shared Raw Transaction Ingest Desk runtime state cannot be locked safely.
|
||||
@@ -79,6 +94,8 @@ pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID;
|
||||
/// Managed and fallback Logging initialization failed.
|
||||
pub(crate) use self::errors::ERROR_CODE_LOGGING_BOOTSTRAP_FAILED;
|
||||
/// Config-only route inventory cannot be projected safely.
|
||||
pub(crate) use self::errors::ERROR_CODE_ROUTE_INVENTORY_INVALID;
|
||||
/// Splash readiness originated from an invalid window.
|
||||
pub(crate) use self::errors::ERROR_CODE_SPLASH_ORIGIN_INVALID;
|
||||
/// Splash timing configuration is invalid.
|
||||
@@ -95,6 +112,8 @@ pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
||||
pub(crate) use self::frontend_logging::emit_frontend_log_event;
|
||||
/// Creates the stable runtime identity for this Raw Transaction Ingest Desk process launch.
|
||||
pub(crate) use self::logging_runtime::launch_identity;
|
||||
/// Rebuilds the complete safe route inventory from validated Config without runtime I/O.
|
||||
pub(crate) use self::route_inventory::build_route_inventory;
|
||||
/// Command emitted by Rust to the splash frontend.
|
||||
pub(crate) use self::splash::SplashOrderDto;
|
||||
/// Runtime timings used by the common desk splash lifecycle.
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_inventory.rs
|
||||
// version: 1
|
||||
|
||||
//! Config-only logical route inventory for Raw Transaction Ingest Desk.
|
||||
|
||||
struct CompositeProfileCatalog {
|
||||
default_profile: String,
|
||||
profile_ids: std::vec::Vec<String>,
|
||||
}
|
||||
|
||||
/// Rebuilds the complete safe route inventory from validated Config without opening any network, Store or Worker resource.
|
||||
pub(crate) fn build_route_inventory(management: &ksp_config_lib::ConfigManagement, generation: u32) -> ksp_core_lib::Result<crate::RawIngestRouteInventoryDto> {
|
||||
let catalog = composite_profile_catalog(management);
|
||||
let catalog = match catalog {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||
let environment = match environment {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut profiles = std::vec::Vec::with_capacity(catalog.profile_ids.len());
|
||||
for profile_id in &catalog.profile_ids {
|
||||
profiles.push(build_profile_inventory(management, &environment, profile_id.as_str(), profile_id == &catalog.default_profile));
|
||||
}
|
||||
ksp_logging_lib::debug!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = crate::TRACING_DOMAIN_INVENTORY,
|
||||
generation,
|
||||
profile_count = profiles.len(),
|
||||
"rebuilt Raw Transaction Ingest Desk Config-only route inventory"
|
||||
);
|
||||
return std::result::Result::Ok(crate::RawIngestRouteInventoryDto { default_profile: catalog.default_profile, generation, profiles });
|
||||
}
|
||||
|
||||
fn composite_profile_catalog(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<CompositeProfileCatalog> {
|
||||
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_RAW_TRANSACTION_INGEST_DESK);
|
||||
let file_id = match file_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let document = management.engine().load_validated_document(&file_id);
|
||||
let document = match document {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let root = document.value().as_object();
|
||||
let root = match root {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return inventory_invalid("validated Raw Transaction Ingest Desk composite root is not an object"),
|
||||
};
|
||||
let default_profile = root.get("default_profile").and_then(|value| return value.as_str());
|
||||
let default_profile = match default_profile {
|
||||
std::option::Option::Some(value) if !value.is_empty() => value.to_owned(),
|
||||
_ => return inventory_invalid("validated Raw Transaction Ingest Desk composite has no readable default profile"),
|
||||
};
|
||||
let profile_values = root.get("profiles").and_then(|value| return value.as_array());
|
||||
let profile_values = match profile_values {
|
||||
std::option::Option::Some(value) if !value.is_empty() => value,
|
||||
_ => return inventory_invalid("validated Raw Transaction Ingest Desk composite has no profile inventory"),
|
||||
};
|
||||
let mut profile_ids = std::vec::Vec::with_capacity(profile_values.len());
|
||||
let mut seen = std::collections::BTreeSet::<String>::new();
|
||||
for profile in profile_values {
|
||||
let profile_id = profile.get("profile_id").and_then(|value| return value.as_str());
|
||||
let profile_id = match profile_id {
|
||||
std::option::Option::Some(value) if !value.is_empty() => value,
|
||||
_ => return inventory_invalid("validated Raw Transaction Ingest Desk composite contains an unreadable profile identifier"),
|
||||
};
|
||||
if !seen.insert(profile_id.to_owned()) {
|
||||
return inventory_invalid("validated Raw Transaction Ingest Desk composite contains duplicate profile identifiers");
|
||||
}
|
||||
profile_ids.push(profile_id.to_owned());
|
||||
}
|
||||
if !seen.contains(default_profile.as_str()) {
|
||||
return inventory_invalid("validated Raw Transaction Ingest Desk composite default profile is absent from the profile inventory");
|
||||
}
|
||||
return std::result::Result::Ok(CompositeProfileCatalog { default_profile, profile_ids });
|
||||
}
|
||||
|
||||
fn build_profile_inventory(
|
||||
management: &ksp_config_lib::ConfigManagement,
|
||||
environment: &ksp_config_lib::ConfigEnvironment,
|
||||
profile_id: &str,
|
||||
is_default: bool,
|
||||
) -> crate::RawIngestProfileInventoryDto {
|
||||
let composite = crate::load_raw_transaction_ingest_desk_composite_profile(management, std::option::Option::Some(profile_id));
|
||||
let composite = match composite {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return unavailable_profile(profile_id, is_default, std::option::Option::None, crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
|
||||
},
|
||||
};
|
||||
let transport_profile =
|
||||
crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_TRANSPORT, ksp_config_lib::FILE_ID_STD_TRANSPORT);
|
||||
let transport_profile = match transport_profile {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return unavailable_profile(profile_id, is_default, std::option::Option::None, crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
|
||||
},
|
||||
};
|
||||
let store_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_STORE, ksp_config_lib::FILE_ID_STD_STORE);
|
||||
let store_profile = match store_profile {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return unavailable_profile(profile_id, is_default, std::option::Option::None, crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
|
||||
},
|
||||
};
|
||||
let transport = management.engine().resolve_transport_config_profile(&transport_profile, environment);
|
||||
let store = management.engine().resolve_store_config_profile(&store_profile, environment);
|
||||
let network_hint = resolved_network_hint(&transport, &store);
|
||||
if transport.is_err() || store.is_err() {
|
||||
let reason = resolution_failure_reason(&transport, &store);
|
||||
return unavailable_profile(profile_id, is_default, network_hint.as_deref(), reason);
|
||||
}
|
||||
let transport = match transport {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return unavailable_profile(profile_id, is_default, network_hint.as_deref(), crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
|
||||
},
|
||||
};
|
||||
let store = match store {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => {
|
||||
return unavailable_profile(profile_id, is_default, network_hint.as_deref(), crate::RawIngestRouteUnavailableReason::ProfileUnresolved);
|
||||
},
|
||||
};
|
||||
let store_network = store.settings().network().as_str().to_owned();
|
||||
if !transport_matches_network(&transport, store_network.as_str()) {
|
||||
return unavailable_profile(
|
||||
profile_id,
|
||||
is_default,
|
||||
std::option::Option::Some(store_network.as_str()),
|
||||
crate::RawIngestRouteUnavailableReason::NetworkMismatch,
|
||||
);
|
||||
}
|
||||
let routes = build_configured_profile_routes(&transport, store_network.as_str());
|
||||
return crate::RawIngestProfileInventoryDto {
|
||||
is_default,
|
||||
network: std::option::Option::Some(store_network),
|
||||
profile_id: profile_id.to_owned(),
|
||||
routes,
|
||||
};
|
||||
}
|
||||
|
||||
fn build_configured_profile_routes(transport: &ksp_config_lib::ResolvedTransportConfig, network: &str) -> std::vec::Vec<crate::RawIngestRouteDto> {
|
||||
let has_get_transaction = http_role_supports_methods(transport.http_settings(), &["getTransaction"]);
|
||||
let has_http_block_scan = http_role_supports_methods(transport.http_settings(), &["getSlot", "getBlocksWithLimit", "getBlock"]);
|
||||
let has_yellowstone = has_yellowstone_grpc(transport, network);
|
||||
let has_standard_logs =
|
||||
has_ws_capability(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Logs);
|
||||
let has_standard_block =
|
||||
has_ws_capability(transport, network, ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard, ksp_onchain_transport_lib::WsSubscriptionKind::Block);
|
||||
let has_helius_transaction = has_ws_capability(
|
||||
transport,
|
||||
network,
|
||||
ksp_onchain_transport_lib::WsProtocolKind::HeliusLaserStream,
|
||||
ksp_onchain_transport_lib::WsSubscriptionKind::HeliusTransaction,
|
||||
);
|
||||
return vec![
|
||||
route_with_requirements(
|
||||
crate::RawIngestRouteId::YellowstoneHydrated,
|
||||
network,
|
||||
&[
|
||||
(has_yellowstone, crate::RawIngestRouteUnavailableReason::MissingYellowstoneGrpc),
|
||||
(has_get_transaction, crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction),
|
||||
],
|
||||
),
|
||||
route_with_requirements(
|
||||
crate::RawIngestRouteId::StandardLogsHydrated,
|
||||
network,
|
||||
&[
|
||||
(has_standard_logs, crate::RawIngestRouteUnavailableReason::MissingWsLogsCapability),
|
||||
(has_get_transaction, crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction),
|
||||
],
|
||||
),
|
||||
route_with_requirements(
|
||||
crate::RawIngestRouteId::StandardBlockDirect,
|
||||
network,
|
||||
&[(has_standard_block, crate::RawIngestRouteUnavailableReason::MissingWsBlockCapability)],
|
||||
),
|
||||
route_with_requirements(
|
||||
crate::RawIngestRouteId::HeliusTransactionHydrated,
|
||||
network,
|
||||
&[
|
||||
(has_helius_transaction, crate::RawIngestRouteUnavailableReason::MissingHeliusTransactionCapability),
|
||||
(has_get_transaction, crate::RawIngestRouteUnavailableReason::MissingHttpGetTransaction),
|
||||
],
|
||||
),
|
||||
route_with_requirements(
|
||||
crate::RawIngestRouteId::HttpBlockPolling,
|
||||
network,
|
||||
&[(has_http_block_scan, crate::RawIngestRouteUnavailableReason::MissingHttpBlockScan)],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
fn route_with_requirements(
|
||||
route_id: crate::RawIngestRouteId,
|
||||
network: &str,
|
||||
requirements: &[(bool, crate::RawIngestRouteUnavailableReason)],
|
||||
) -> crate::RawIngestRouteDto {
|
||||
for (available, reason) in requirements.iter().copied() {
|
||||
if !available {
|
||||
return crate::RawIngestRouteDto::unavailable(route_id, std::option::Option::Some(network), reason);
|
||||
}
|
||||
}
|
||||
return crate::RawIngestRouteDto::configured(route_id, network);
|
||||
}
|
||||
|
||||
fn unavailable_profile(
|
||||
profile_id: &str,
|
||||
is_default: bool,
|
||||
network: std::option::Option<&str>,
|
||||
reason: crate::RawIngestRouteUnavailableReason,
|
||||
) -> crate::RawIngestProfileInventoryDto {
|
||||
let routes = crate::RawIngestRouteId::all()
|
||||
.into_iter()
|
||||
.map(|route_id| return crate::RawIngestRouteDto::unavailable(route_id, network, reason))
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
return crate::RawIngestProfileInventoryDto { is_default, network: network.map(str::to_owned), profile_id: profile_id.to_owned(), routes };
|
||||
}
|
||||
|
||||
fn resolution_failure_reason(
|
||||
transport: &ksp_core_lib::Result<ksp_config_lib::ResolvedTransportConfig>,
|
||||
store: &ksp_core_lib::Result<ksp_config_lib::ResolvedStoreConfig>,
|
||||
) -> crate::RawIngestRouteUnavailableReason {
|
||||
let transport_missing_secret = match transport {
|
||||
std::result::Result::Err(error) => error.code() == ksp_config_lib::ERROR_CODE_ENVIRONMENT_VARIABLE_MISSING,
|
||||
std::result::Result::Ok(_) => false,
|
||||
};
|
||||
let store_missing_secret = match store {
|
||||
std::result::Result::Err(error) => error.code() == ksp_config_lib::ERROR_CODE_ENVIRONMENT_VARIABLE_MISSING,
|
||||
std::result::Result::Ok(_) => false,
|
||||
};
|
||||
if transport_missing_secret || store_missing_secret {
|
||||
return crate::RawIngestRouteUnavailableReason::MissingRequiredSecret;
|
||||
}
|
||||
return crate::RawIngestRouteUnavailableReason::ProfileUnresolved;
|
||||
}
|
||||
|
||||
fn resolved_network_hint(
|
||||
transport: &ksp_core_lib::Result<ksp_config_lib::ResolvedTransportConfig>,
|
||||
store: &ksp_core_lib::Result<ksp_config_lib::ResolvedStoreConfig>,
|
||||
) -> std::option::Option<String> {
|
||||
if let std::result::Result::Ok(store) = store {
|
||||
return std::option::Option::Some(store.settings().network().as_str().to_owned());
|
||||
}
|
||||
if let std::result::Result::Ok(transport) = transport {
|
||||
let networks = enabled_transport_networks(transport);
|
||||
if networks.len() == 1 {
|
||||
return networks.into_iter().next();
|
||||
}
|
||||
}
|
||||
return std::option::Option::None;
|
||||
}
|
||||
|
||||
fn transport_matches_network(transport: &ksp_config_lib::ResolvedTransportConfig, network: &str) -> bool {
|
||||
let networks = enabled_transport_networks(transport);
|
||||
return networks.len() == 1 && networks.contains(network);
|
||||
}
|
||||
|
||||
fn enabled_transport_networks(transport: &ksp_config_lib::ResolvedTransportConfig) -> std::collections::BTreeSet<String> {
|
||||
let mut networks = std::collections::BTreeSet::<String>::new();
|
||||
for endpoint in transport.http_settings().endpoints() {
|
||||
if endpoint.enabled() {
|
||||
networks.insert(endpoint.cluster().as_str().to_owned());
|
||||
}
|
||||
}
|
||||
if let std::option::Option::Some(settings) = transport.ws_settings() {
|
||||
for endpoint in settings.endpoints() {
|
||||
if endpoint.enabled() {
|
||||
networks.insert(endpoint.cluster().as_str().to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let std::option::Option::Some(settings) = transport.grpc_settings() {
|
||||
for endpoint in settings.endpoints() {
|
||||
if endpoint.enabled() {
|
||||
networks.insert(endpoint.cluster().as_str().to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
return networks;
|
||||
}
|
||||
|
||||
fn has_ws_capability(
|
||||
transport: &ksp_config_lib::ResolvedTransportConfig,
|
||||
network: &str,
|
||||
protocol: ksp_onchain_transport_lib::WsProtocolKind,
|
||||
capability: ksp_onchain_transport_lib::WsSubscriptionKind,
|
||||
) -> bool {
|
||||
let settings = match transport.ws_settings() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return false,
|
||||
};
|
||||
for endpoint in settings.endpoints() {
|
||||
if endpoint.enabled() && endpoint.cluster().as_str() == network && endpoint.protocol() == protocol && endpoint.supports_subscription(capability) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn has_yellowstone_grpc(transport: &ksp_config_lib::ResolvedTransportConfig, network: &str) -> bool {
|
||||
let settings = match transport.grpc_settings() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return false,
|
||||
};
|
||||
return settings.endpoints().iter().any(|endpoint| return endpoint.enabled() && endpoint.cluster().as_str() == network);
|
||||
}
|
||||
|
||||
fn http_role_supports_methods(settings: &ksp_onchain_transport_lib::HttpTransportSettings, methods: &[&'static str]) -> bool {
|
||||
let mut request_kinds = std::vec::Vec::<&'static str>::with_capacity(methods.len());
|
||||
for method_name in methods {
|
||||
let descriptor = ksp_onchain_transport_lib::find_http_rpc_method(method_name);
|
||||
let descriptor = match descriptor {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return false,
|
||||
};
|
||||
request_kinds.push(descriptor.request_kind());
|
||||
}
|
||||
let mut role_names = std::collections::BTreeSet::<String>::new();
|
||||
for endpoint in settings.endpoints() {
|
||||
if !endpoint.enabled() {
|
||||
continue;
|
||||
}
|
||||
for role in endpoint.roles() {
|
||||
if role.enabled() {
|
||||
role_names.insert(role.role().as_str().to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
for role_name in role_names {
|
||||
let mut complete = true;
|
||||
for request_kind in &request_kinds {
|
||||
if !http_role_supports_request_kind(settings, role_name.as_str(), request_kind) {
|
||||
complete = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if complete {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn http_role_supports_request_kind(settings: &ksp_onchain_transport_lib::HttpTransportSettings, role_name: &str, request_kind: &str) -> bool {
|
||||
for endpoint in settings.endpoints() {
|
||||
if !endpoint.enabled() {
|
||||
continue;
|
||||
}
|
||||
for role in endpoint.roles() {
|
||||
if !role.enabled() || role.role().as_str() != role_name {
|
||||
continue;
|
||||
}
|
||||
for capability in role.request_kinds() {
|
||||
if capability.is_wildcard() || capability.as_str() == request_kind {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn inventory_invalid(message: &'static str) -> ksp_core_lib::Result<CompositeProfileCatalog> {
|
||||
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_ROUTE_INVENTORY_INVALID, message));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/route_inventory.rs"]
|
||||
mod tests;
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Tauri runtime assembly for the KSP Raw Transaction Ingest desktop application.
|
||||
|
||||
@@ -74,7 +74,13 @@ fn configure_packaged_runtime(context: &tauri::Context<tauri::Wry>) -> ksp_core_
|
||||
|
||||
#[allow(clippy::question_mark_used)] // Tauri generates the question-mark operator internally for async command dispatch.
|
||||
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||
return builder.invoke_handler(tauri::generate_handler![emit_frontend_log, get_route_foundation, get_runtime_status, splash_frontend_ready]);
|
||||
return builder.invoke_handler(tauri::generate_handler![
|
||||
emit_frontend_log,
|
||||
get_route_foundation,
|
||||
get_route_inventory,
|
||||
get_runtime_status,
|
||||
splash_frontend_ready
|
||||
]);
|
||||
}
|
||||
|
||||
fn project_command_error(command: &'static str, domain: &'static str, error: &ksp_core_lib::Error) -> crate::CommandErrorDto {
|
||||
@@ -103,6 +109,15 @@ fn get_route_foundation(state: tauri::State<'_, crate::AppState>) -> crate::RawI
|
||||
return state.route_foundation();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_route_inventory(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::RawIngestRouteInventoryDto, crate::CommandErrorDto> {
|
||||
let result = state.route_inventory();
|
||||
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_inventory", crate::TRACING_DOMAIN_INVENTORY, &error)),
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::ShellStatusDto, crate::CommandErrorDto> {
|
||||
let result = state.shell_status();
|
||||
|
||||
Reference in New Issue
Block a user