0.3.15-pre.012

This commit is contained in:
2026-09-16 10:58:29 +02:00
parent 2f587e22be
commit 31ae38853d
21 changed files with 815 additions and 79 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/app_state.rs
// version: 7
// version: 8
//! Shared backend state owned by the Raw Transaction Ingest Desk Tauri application.
@@ -117,23 +117,26 @@ impl crate::AppState {
/// Starts one independent route Worker after repeating complete Start-time revalidation, sharing Store only on an exact network match.
pub(crate) async fn start_route(&self, request: &crate::RawIngestRouteStartRequestDto) -> ksp_core_lib::Result<crate::RouteRuntimeLaunch> {
let generation = {
let generation = self.inventory_generation.lock();
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 admission = self.route_runtime.ensure_start_admission_open();
if let std::result::Result::Err(error) = admission {
return std::result::Result::Err(error);
}
let generation = self.inventory_generation.lock();
let 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 prepared = crate::prepare_route_start(&self.config_management, generation, request);
let prepared = crate::prepare_route_start(&self.config_management, *generation, request);
let prepared = match prepared {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
std::mem::drop(generation);
return crate::start_route_runtime(std::sync::Arc::clone(&self.route_runtime), prepared).await;
}
@@ -147,6 +150,16 @@ impl crate::AppState {
return self.route_runtime.stop_and_wait(request).await;
}
/// Closes route Start admission once and requests cooperative Stop for all routes during application shutdown.
pub(crate) fn begin_shutdown(&self) -> ksp_core_lib::Result<bool> {
return self.route_runtime.begin_shutdown();
}
/// Waits for bounded cleanup of all route Workers and the shared Store during application shutdown.
pub(crate) async fn shutdown_routes(&self) -> ksp_core_lib::Result<()> {
return self.route_runtime.shutdown_and_wait().await;
}
/// 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();

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/dto_route.rs
// version: 6
// version: 7
//! Safe route-identity, Config-composability and lifecycle DTOs for Raw Transaction Ingest Desk.
@@ -99,7 +99,7 @@ impl crate::RawIngestCommitment {
/// Safe request used to revalidate and reconstruct one future route Start without accepting physical resources from the frontend.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteStartRequestDto.ts")]
pub(crate) struct RawIngestRouteStartRequestDto {
/// Confirmed/finalized commitment selected for the future Worker.
@@ -114,7 +114,7 @@ pub(crate) struct RawIngestRouteStartRequestDto {
/// Safe targeted Stop request containing only the logical Worker route identity.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/dto_route/RawIngestRouteStopRequestDto.ts")]
pub(crate) struct RawIngestRouteStopRequestDto {
/// Safe logical network-profile identifier that owns the Worker route.
@@ -123,6 +123,36 @@ pub(crate) struct RawIngestRouteStopRequestDto {
pub(crate) route_id: crate::RawIngestRouteId,
}
const RAW_INGEST_PROFILE_ID_MAX_BYTES: usize = 256;
impl crate::RawIngestRouteStartRequestDto {
/// Validates the only caller-controlled free-form logical identifier before Config or runtime reconstruction.
pub(crate) fn validate_logical_identity(&self) -> ksp_core_lib::Result<()> {
return validate_profile_id(self.profile_id.as_str());
}
}
impl crate::RawIngestRouteStopRequestDto {
/// Validates the only caller-controlled free-form logical identifier before runtime lookup.
pub(crate) fn validate_logical_identity(&self) -> ksp_core_lib::Result<()> {
return validate_profile_id(self.profile_id.as_str());
}
}
fn validate_profile_id(profile_id: &str) -> ksp_core_lib::Result<()> {
if profile_id.is_empty()
|| profile_id.len() > RAW_INGEST_PROFILE_ID_MAX_BYTES
|| profile_id.trim() != profile_id
|| profile_id.chars().any(char::is_control)
{
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_REQUEST_INVALID,
"Raw Transaction Ingest Desk route request contains an invalid logical profile identifier",
));
}
return std::result::Result::Ok(());
}
/// Safe acknowledgement that exact Start-time Transport resources were reconstructed and validated without Worker launch.
#[derive(Clone, Debug, serde::Serialize, TS)]
#[serde(rename_all = "camelCase")]

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/errors.rs
// version: 5
// version: 6
//! Stable Raw Transaction Ingest Desk application error codes.
@@ -17,6 +17,9 @@ pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode
/// Frontend logging requested a target outside the application whitelist.
pub(crate) const ERROR_CODE_FRONTEND_LOG_TARGET_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "frontend_log_target_invalid");
/// Frontend logging payload exceeds the bounded technical IPC contract.
pub(crate) const ERROR_CODE_FRONTEND_LOG_PAYLOAD_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "frontend_log_payload_invalid");
/// 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");
@@ -46,6 +49,15 @@ pub(crate) const ERROR_CODE_ROUTE_RUNTIME_STORE_SHUTDOWN_FAILED: ksp_core_lib::E
/// The multi-route runtime reservation/handle state changed unexpectedly.
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_STATE_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_state_invalid");
/// Application shutdown has closed admission for new route Worker starts.
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_SHUTTING_DOWN: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_shutting_down");
/// Application shutdown could not join all route Workers and shared Store cleanup within the bounded deadline.
pub(crate) const ERROR_CODE_ROUTE_RUNTIME_SHUTDOWN_FAILED: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_runtime_shutdown_failed");
/// A Start/Stop IPC request contains an invalid logical identifier or unsupported shape.
pub(crate) const ERROR_CODE_ROUTE_REQUEST_INVALID: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "route_request_invalid");
/// Start request references an inventory generation that is no longer current.
pub(crate) const ERROR_CODE_ROUTE_START_STALE_INVENTORY: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("raw_transaction_ingest_desk", "stale_inventory");

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/frontend_logging.rs
// version: 1
// version: 2
//! KSP-owned bridge for technical log events emitted by Raw Transaction Ingest Desk frontend scripts.
@@ -7,7 +7,7 @@ use ts_rs::TS; // rust-rules: trait-import
/// Log payload sent by Raw Transaction Ingest Desk frontend scripts.
#[derive(Clone, Debug, serde::Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_raw_transaction_ingest_desk/frontend_logging/FrontendLogPayloadDto.ts")]
pub(crate) struct FrontendLogPayloadDto {
/// Lowercase KSP log level.
@@ -34,8 +34,15 @@ enum FrontendLogTarget {
Splash,
}
const FRONTEND_LOG_MESSAGE_MAX_BYTES: usize = 8192;
const FRONTEND_LOG_SELECTOR_MAX_BYTES: usize = 16;
/// Emits one validated frontend event through the KSP Logging facade.
pub(crate) fn emit_frontend_log_event(payload: FrontendLogPayloadDto) -> ksp_core_lib::Result<()> {
let shape = validate_payload_shape(&payload);
if let std::result::Result::Err(error) = shape {
return std::result::Result::Err(error);
}
let level = parse_level(payload.level.as_str());
let level = match level {
std::result::Result::Ok(value) => value,
@@ -50,26 +57,55 @@ pub(crate) fn emit_frontend_log_event(payload: FrontendLogPayloadDto) -> ksp_cor
return std::result::Result::Ok(());
}
fn validate_payload_shape(payload: &FrontendLogPayloadDto) -> ksp_core_lib::Result<()> {
if payload.level.len() > FRONTEND_LOG_SELECTOR_MAX_BYTES
|| payload.target_id.len() > FRONTEND_LOG_SELECTOR_MAX_BYTES
|| payload.message.len() > FRONTEND_LOG_MESSAGE_MAX_BYTES
|| payload.message.chars().any(char::is_control)
{
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_FRONTEND_LOG_PAYLOAD_INVALID,
"Frontend log payload exceeds the bounded technical IPC contract",
));
}
return std::result::Result::Ok(());
}
fn parse_level(level: &str) -> ksp_core_lib::Result<FrontendLogLevel> {
return match level.trim().to_ascii_lowercase().as_str() {
"trace" => std::result::Result::Ok(FrontendLogLevel::Trace),
"debug" => std::result::Result::Ok(FrontendLogLevel::Debug),
"info" => std::result::Result::Ok(FrontendLogLevel::Info),
"warn" => std::result::Result::Ok(FrontendLogLevel::Warn),
"error" => std::result::Result::Ok(FrontendLogLevel::Error),
_ => std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID, "Frontend log level is not supported")),
};
let level = level.trim();
if level.eq_ignore_ascii_case("trace") {
return std::result::Result::Ok(FrontendLogLevel::Trace);
}
if level.eq_ignore_ascii_case("debug") {
return std::result::Result::Ok(FrontendLogLevel::Debug);
}
if level.eq_ignore_ascii_case("info") {
return std::result::Result::Ok(FrontendLogLevel::Info);
}
if level.eq_ignore_ascii_case("warn") {
return std::result::Result::Ok(FrontendLogLevel::Warn);
}
if level.eq_ignore_ascii_case("error") {
return std::result::Result::Ok(FrontendLogLevel::Error);
}
return std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID, "Frontend log level is not supported"));
}
fn parse_target(target_id: &str) -> ksp_core_lib::Result<FrontendLogTarget> {
return match target_id.trim().to_ascii_lowercase().as_str() {
"frontend" => std::result::Result::Ok(FrontendLogTarget::Frontend),
"main" => std::result::Result::Ok(FrontendLogTarget::Main),
"splash" => std::result::Result::Ok(FrontendLogTarget::Splash),
_ => {
std::result::Result::Err(ksp_core_lib::Error::new(crate::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID, "Frontend log target identifier is not supported"))
},
};
let target_id = target_id.trim();
if target_id.eq_ignore_ascii_case("frontend") {
return std::result::Result::Ok(FrontendLogTarget::Frontend);
}
if target_id.eq_ignore_ascii_case("main") {
return std::result::Result::Ok(FrontendLogTarget::Main);
}
if target_id.eq_ignore_ascii_case("splash") {
return std::result::Result::Ok(FrontendLogTarget::Splash);
}
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_FRONTEND_LOG_TARGET_INVALID,
"Frontend log target identifier is not supported",
));
}
fn emit_validated_frontend_log(level: FrontendLogLevel, target: FrontendLogTarget, message: &str) {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/lib.rs
// version: 7
// version: 8
//! Tauri desktop application scaffold for composing and supervising KSP live RAW transaction ingest routes.
@@ -113,18 +113,26 @@ pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
pub(crate) use self::errors::ERROR_CODE_CONFIG_COMPOSITE_INVALID;
/// Frontend logging requested an unsupported level.
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
/// Frontend logging payload exceeds the bounded technical IPC contract.
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_PAYLOAD_INVALID;
/// Frontend logging requested an unsupported target.
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;
/// Start/Stop IPC request contains an invalid logical shape.
pub(crate) use self::errors::ERROR_CODE_ROUTE_REQUEST_INVALID;
/// A Worker runtime already owns the same logical route in the shared application runtime.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_ACTIVE;
/// Selected route cannot share the currently open Store because its network differs.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_NETWORK_MISMATCH;
/// No selected route Worker runtime is currently active.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE;
/// Application shutdown could not complete all bounded route/Store cleanup.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_SHUTDOWN_FAILED;
/// Application shutdown has closed admission for new route Worker starts.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_SHUTTING_DOWN;
/// Store or Worker startup failed after Start-time route revalidation.
pub(crate) use self::errors::ERROR_CODE_ROUTE_RUNTIME_START_FAILED;
/// The multi-route runtime reservation or handle state changed unexpectedly.

View File

@@ -1,9 +1,10 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_runtime.rs
// version: 6
// version: 7
//! Multi-route Store and independent Worker lifecycle owned by Raw Transaction Ingest Desk.
const ROUTE_STOP_CLEANUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2);
const ROUTE_SHUTDOWN_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
const ROUTE_STOP_CLEANUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
const STORE_RECLAIM_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(5);
const STORE_RECLAIM_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
@@ -13,6 +14,7 @@ struct RouteRuntimeInner {
network: std::option::Option<String>,
next_sequence: u64,
routes: std::vec::Vec<RouteRuntimeSlot>,
shutting_down: bool,
store: std::option::Option<std::sync::Arc<ksp_store_lib::Store>>,
store_closing_token: std::option::Option<u64>,
store_opening_token: std::option::Option<u64>,
@@ -34,6 +36,7 @@ impl crate::RouteRuntimeState {
network: std::option::Option::None,
next_sequence: 0,
routes: std::vec::Vec::new(),
shutting_down: false,
store: std::option::Option::None,
store_closing_token: std::option::Option::None,
store_opening_token: std::option::Option::None,
@@ -41,12 +44,34 @@ impl crate::RouteRuntimeState {
};
}
/// Rejects new route Start work after application shutdown has closed admission.
pub(crate) fn ensure_start_admission_open(&self) -> ksp_core_lib::Result<()> {
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()),
};
if inner.shutting_down {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_SHUTTING_DOWN,
"Raw Transaction Ingest Desk cannot start a route while application shutdown is in progress",
));
}
return std::result::Result::Ok(());
}
fn reserve(&self, prepared: &crate::PreparedRouteStart) -> ksp_core_lib::Result<RouteRuntimeReservation> {
let inner = self.inner.lock();
let mut inner = match inner {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
};
if inner.shutting_down {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_SHUTTING_DOWN,
"Raw Transaction Ingest Desk cannot start a route while application shutdown is in progress",
));
}
if inner.store_closing_token.is_some() {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_ACTIVE,
@@ -111,7 +136,7 @@ impl crate::RouteRuntimeState {
if needs_store_open {
inner.store_opening_token = std::option::Option::Some(sequence);
}
inner.routes.push(RouteRuntimeSlot::Starting { identity: identity.clone(), token: sequence });
inner.routes.push(RouteRuntimeSlot::Starting { identity: identity.clone(), stop_requested: false, token: sequence });
return std::result::Result::Ok(RouteRuntimeReservation { identity, needs_store_open, token: sequence, worker_id });
}
@@ -173,7 +198,19 @@ impl crate::RouteRuntimeState {
));
},
};
let state = project_worker_state(handle.snapshot_source().current().worker_snapshot().state());
let stop_requested = match &inner.routes[position] {
RouteRuntimeSlot::Starting { stop_requested, .. } => *stop_requested,
RouteRuntimeSlot::Active { .. } => false,
};
let should_stop = stop_requested || inner.shutting_down;
if should_stop {
let _accepted = handle.request_stop();
}
let state = if should_stop {
crate::RawIngestRouteState::Stopping
} else {
project_worker_state(handle.snapshot_source().current().worker_snapshot().state())
};
inner.routes[position] = RouteRuntimeSlot::Active { handle, identity: reservation.identity.clone(), token: reservation.token };
return std::result::Result::Ok(reservation.identity.dto(state));
}
@@ -184,7 +221,12 @@ impl crate::RouteRuntimeState {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
};
let cancelled_terminal = inner.routes.iter().find(|slot| return slot.token() == token).and_then(RouteRuntimeSlot::cancelled_start_terminal);
inner.routes.retain(|slot| return slot.token() != token);
if let std::option::Option::Some(terminal) = cancelled_terminal {
inner.last_terminals.retain(|previous| return previous.profile_id != terminal.profile_id || previous.route_id != terminal.route_id);
inner.last_terminals.push(terminal);
}
if inner.store_opening_token == std::option::Option::Some(token) {
inner.store_opening_token = std::option::Option::None;
}
@@ -272,8 +314,71 @@ impl crate::RouteRuntimeState {
return std::result::Result::Ok(values);
}
/// Closes route Start admission exactly once and requests cooperative Stop for all active or still-starting routes.
pub(crate) fn begin_shutdown(&self) -> ksp_core_lib::Result<bool> {
let inner = self.inner.lock();
let mut inner = match inner {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
};
if inner.shutting_down {
return std::result::Result::Ok(false);
}
inner.shutting_down = true;
for slot in &mut inner.routes {
match slot {
RouteRuntimeSlot::Starting { stop_requested, .. } => *stop_requested = true,
RouteRuntimeSlot::Active { handle, .. } => {
let _accepted = handle.request_stop();
},
}
}
return std::result::Result::Ok(true);
}
/// Waits until all owned route Workers and the shared Store have completed bounded application-shutdown cleanup.
pub(crate) async fn shutdown_and_wait(&self) -> ksp_core_lib::Result<()> {
let begin = self.begin_shutdown();
if let std::result::Result::Err(error) = begin {
return std::result::Result::Err(error);
}
let started = std::time::Instant::now();
loop {
let complete = self.shutdown_complete();
let complete = match complete {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if complete {
return std::result::Result::Ok(());
}
if started.elapsed() >= ROUTE_SHUTDOWN_TIMEOUT {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_SHUTDOWN_FAILED,
"Raw Transaction Ingest Desk application shutdown did not release all route Workers and shared Store state before the bounded deadline",
));
}
tokio::time::sleep(STORE_RECLAIM_POLL_INTERVAL).await;
}
}
fn shutdown_complete(&self) -> ksp_core_lib::Result<bool> {
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()),
};
return std::result::Result::Ok(
inner.routes.is_empty() && inner.store.is_none() && inner.store_opening_token.is_none() && inner.store_closing_token.is_none(),
);
}
/// 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 identity = request.validate_logical_identity();
if let std::result::Result::Err(error) = identity {
return std::result::Result::Err(error);
}
let target = self.stop_target(request);
let target = match target {
std::result::Result::Ok(value) => value,
@@ -281,6 +386,7 @@ impl crate::RouteRuntimeState {
};
let (handle, identity, token) = match target {
RouteStopTarget::Active { handle, identity, token } => (handle, identity, token),
RouteStopTarget::Starting { identity, token } => return self.wait_route_cleanup(token, &identity).await,
RouteStopTarget::Terminal(value) => return std::result::Result::Ok(value),
RouteStopTarget::TerminalClosing => return self.wait_terminal_store_cleanup(request).await,
};
@@ -296,33 +402,30 @@ impl crate::RouteRuntimeState {
fn stop_target(&self, request: &crate::RawIngestRouteStopRequestDto) -> ksp_core_lib::Result<RouteStopTarget> {
let inner = self.inner.lock();
let inner = match inner {
let mut inner = match inner {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::result::Result::Err(runtime_lock_error()),
};
let slot = inner.routes.iter().find(|slot| return slot.matches_identity(request.profile_id.as_str(), request.route_id));
return match slot {
std::option::Option::Some(RouteRuntimeSlot::Active { handle, identity, token }) => {
std::result::Result::Ok(RouteStopTarget::Active { handle: handle.clone(), identity: identity.clone(), token: *token })
},
std::option::Option::Some(RouteRuntimeSlot::Starting { .. }) => std::result::Result::Err(ksp_core_lib::Error::new(
let position = inner.routes.iter().position(|slot| return slot.matches_identity(request.profile_id.as_str(), request.route_id));
if let std::option::Option::Some(position) = position {
return match &mut inner.routes[position] {
RouteRuntimeSlot::Active { handle, identity, token } => {
std::result::Result::Ok(RouteStopTarget::Active { handle: handle.clone(), identity: identity.clone(), token: *token })
},
RouteRuntimeSlot::Starting { identity, stop_requested, token } => {
*stop_requested = true;
std::result::Result::Ok(RouteStopTarget::Starting { identity: identity.clone(), token: *token })
},
};
}
let terminal = inner.last_terminals.iter().find(|terminal| return terminal.profile_id == request.profile_id && terminal.route_id == request.route_id);
return match terminal {
std::option::Option::Some(value) if inner.store_closing_token.is_none() => std::result::Result::Ok(RouteStopTarget::Terminal(value.clone())),
std::option::Option::Some(_) => std::result::Result::Ok(RouteStopTarget::TerminalClosing),
std::option::Option::None => std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE,
"Raw Transaction Ingest Desk route Worker is still starting and cannot be stopped yet",
"Raw Transaction Ingest Desk has no active Worker for the selected logical route",
)),
std::option::Option::None => {
let terminal =
inner.last_terminals.iter().find(|terminal| return terminal.profile_id == request.profile_id && terminal.route_id == request.route_id);
match terminal {
std::option::Option::Some(value) if inner.store_closing_token.is_none() => {
std::result::Result::Ok(RouteStopTarget::Terminal(value.clone()))
},
std::option::Option::Some(_) => std::result::Result::Ok(RouteStopTarget::TerminalClosing),
std::option::Option::None => std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_RUNTIME_NOT_ACTIVE,
"Raw Transaction Ingest Desk has no active Worker for the selected logical route",
)),
}
},
};
}
@@ -668,6 +771,10 @@ enum RouteStopTarget {
identity: RouteRuntimeIdentity,
token: u64,
},
Starting {
identity: RouteRuntimeIdentity,
token: u64,
},
Terminal(crate::RawIngestRouteRuntimeDto),
TerminalClosing,
}
@@ -682,6 +789,7 @@ struct RouteRuntimeReservation {
enum RouteRuntimeSlot {
Starting {
identity: RouteRuntimeIdentity,
stop_requested: bool,
token: u64,
},
Active {
@@ -706,6 +814,13 @@ impl RouteRuntimeSlot {
};
}
fn cancelled_start_terminal(&self) -> std::option::Option<crate::RawIngestRouteRuntimeDto> {
return match self {
Self::Starting { identity, stop_requested: true, .. } => std::option::Option::Some(identity.dto(crate::RawIngestRouteState::Stopped)),
Self::Starting { stop_requested: false, .. } | Self::Active { .. } => std::option::Option::None,
};
}
fn token(&self) -> u64 {
return match self {
Self::Starting { token, .. } | Self::Active { token, .. } => *token,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/route_start.rs
// version: 4
// version: 5
//! Start-time Config revalidation and exact Transport/Store resource reconstruction for route validation and runtime launch.
@@ -27,6 +27,10 @@ pub(crate) fn prepare_route_start(
current_generation: u32,
request: &crate::RawIngestRouteStartRequestDto,
) -> ksp_core_lib::Result<crate::PreparedRouteStart> {
let identity = request.validate_logical_identity();
if let std::result::Result::Err(error) = identity {
return std::result::Result::Err(error);
}
if current_generation == 0 || request.inventory_generation != current_generation {
return std::result::Result::Err(ksp_core_lib::Error::new(
crate::ERROR_CODE_ROUTE_START_STALE_INVENTORY,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
// version: 8
// version: 9
//! Tauri runtime assembly for the KSP Raw Transaction Ingest desktop application.
@@ -23,6 +23,7 @@ pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
builder = builder.manage(app_state);
builder = builder.plugin(tauri_plugin_tracing::Builder::new().build::<tauri::Wry>());
builder = configure_commands(builder);
builder = configure_window_events(builder);
builder = builder.setup(|app| {
let splash = crate::require_splash_window(app);
if let std::result::Result::Err(error) = splash {
@@ -90,6 +91,66 @@ fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tau
]);
}
fn configure_window_events(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
return builder.on_window_event(|window, event| {
if window.label() != "main" {
return;
}
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
api.prevent_close();
let app_handle = window.app_handle().clone();
let state = app_handle.state::<crate::AppState>();
let begin = state.begin_shutdown();
let begin = match begin {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
error_domain = error.code().domain(),
error_code = error.code().code(),
"Raw Transaction Ingest Desk could not enter graceful shutdown; exiting after safe error projection"
);
app_handle.exit(1);
return;
},
};
if !begin {
return;
}
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
"Raw Transaction Ingest Desk main-window close requested; route Start admission closed and cooperative shutdown requested"
);
tauri::async_runtime::spawn(async move {
let state = app_handle.state::<crate::AppState>();
let shutdown = state.shutdown_routes().await;
match shutdown {
std::result::Result::Ok(()) => {
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
"Raw Transaction Ingest Desk route and shared Store shutdown completed; exiting application"
);
app_handle.exit(0);
},
std::result::Result::Err(error) => {
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,
domain = crate::TRACING_DOMAIN_WINDOWS,
error_domain = error.code().domain(),
error_code = error.code().code(),
"Raw Transaction Ingest Desk bounded route shutdown failed; exiting application after bounded cleanup attempt"
);
app_handle.exit(1);
},
}
});
}
});
}
fn project_command_error(command: &'static str, domain: &'static str, error: &ksp_core_lib::Error) -> crate::CommandErrorDto {
ksp_logging_lib::warn!(
target: crate::TRACING_TARGET,